kopia lustrzana https://gitlab.com/mysocialportal/relatica
231 wiersze
7.5 KiB
Dart
231 wiersze
7.5 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:indent/indent.dart';
|
|
import 'package:intl/intl.dart';
|
|
|
|
import '../controls/focus_mode_status_headline.dart';
|
|
import '../controls/padding.dart';
|
|
import '../models/focus_mode_data.dart';
|
|
import '../riverpod_controllers/focus_mode.dart';
|
|
import '../routes.dart';
|
|
import '../utils/snackbar_builder.dart';
|
|
import '../utils/string_utils.dart';
|
|
|
|
const _magicUnlockNumber = 1563948536;
|
|
final _hintText = '''
|
|
Try using a "bisection method" to guess.
|
|
For example, if the interval is 0 to 100 a first guess would be 50.
|
|
If it says it is too low try 75.
|
|
If it says it is too high try 25.
|
|
Continue until you get the right answer.
|
|
If you are stuck and the interval is very long, or forever,
|
|
reach out to the help options on the website for unlocking assistance.
|
|
'''
|
|
.unindent()
|
|
.replaceAll('\n', ' ');
|
|
|
|
class GameState {
|
|
final int maxNumber;
|
|
final int number;
|
|
final int? lastGuess;
|
|
|
|
String get hint {
|
|
if (lastGuess == null) {
|
|
return 'Guess a number between 0 and ${decimalWithCommasFormat.format(maxNumber)}';
|
|
}
|
|
|
|
if (lastGuess! < number) {
|
|
return '${decimalWithCommasFormat.format(lastGuess)} is too low. Guess a higher number';
|
|
}
|
|
|
|
if (lastGuess! > number) {
|
|
return '${decimalWithCommasFormat.format(lastGuess)} is too high. Guess a lower number';
|
|
}
|
|
|
|
return 'You got it!';
|
|
}
|
|
|
|
bool get found => number == lastGuess || lastGuess == _magicUnlockNumber;
|
|
|
|
const GameState({
|
|
required this.number,
|
|
required this.maxNumber,
|
|
this.lastGuess,
|
|
});
|
|
|
|
GameState update(int lastGuess) => GameState(
|
|
number: number,
|
|
maxNumber: maxNumber,
|
|
lastGuess: lastGuess,
|
|
);
|
|
|
|
factory GameState.newGame(int maxNumber) =>
|
|
GameState(number: Random().nextInt(maxNumber), maxNumber: maxNumber);
|
|
}
|
|
|
|
class DisableFocusModeScreen extends ConsumerStatefulWidget {
|
|
const DisableFocusModeScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<DisableFocusModeScreen> createState() =>
|
|
_DisableFocusModeScreenState();
|
|
}
|
|
|
|
class _DisableFocusModeScreenState
|
|
extends ConsumerState<DisableFocusModeScreen> {
|
|
final formKey = GlobalKey<FormState>();
|
|
final guessController = TextEditingController();
|
|
var game = GameState.newGame(1);
|
|
var maxNumber = 1;
|
|
var tryCount = 0;
|
|
var message = '';
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
maxNumber = ref.read(focusModeProvider).maxNumber;
|
|
game = GameState.newGame(maxNumber);
|
|
tryCount = 0;
|
|
message =
|
|
"If you guess the number I've picked from 0 to ${decimalWithCommasFormat.format(maxNumber)} you may disable focus mode...";
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final focusMode = ref.watch(focusModeProvider);
|
|
if (!focusMode.enabled) {
|
|
context.go(ScreenPaths.timelines);
|
|
}
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Disable Focus Mode?'),
|
|
),
|
|
body: Center(
|
|
child: Form(
|
|
key: formKey,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Column(
|
|
children: [
|
|
FocusModeStatusHeadline(disableTime: focusMode.disableTime),
|
|
Text(
|
|
message,
|
|
softWrap: true,
|
|
maxLines: 10,
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const VerticalPadding(),
|
|
TextFormField(
|
|
controller: guessController,
|
|
keyboardType: TextInputType.number,
|
|
// inputFormatters: [
|
|
// ThousandsSeparatorInputFormatter(),
|
|
// ],
|
|
enableInteractiveSelection: true,
|
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
|
validator: (value) {
|
|
if (value == null) {
|
|
return 'Please enter a number';
|
|
}
|
|
final noCommasValue = value.replaceAll(',', '');
|
|
final intValue = int.tryParse(noCommasValue);
|
|
if (intValue == null) {
|
|
return 'Please enter a number';
|
|
}
|
|
|
|
return null;
|
|
},
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderSide: const BorderSide(),
|
|
borderRadius: BorderRadius.circular(5.0),
|
|
),
|
|
),
|
|
),
|
|
const VerticalPadding(),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
final valid = formKey.currentState?.validate() ?? false;
|
|
if (!valid) {
|
|
buildSnackbar(context,
|
|
'Please enter an integer between 0 and ${decimalWithCommasFormat.format(maxNumber)}');
|
|
return;
|
|
}
|
|
|
|
final guess =
|
|
int.parse(guessController.text.replaceAll(',', ''));
|
|
game = game.update(guess);
|
|
if (game.found) {
|
|
ref
|
|
.read(focusModeProvider.notifier)
|
|
.setMode(const FocusModeData(false));
|
|
context.go(ScreenPaths.timelines);
|
|
} else {
|
|
setState(() {
|
|
tryCount++;
|
|
message = tryCount <= 10
|
|
? game.hint
|
|
: '${game.hint}.\n\n$_hintText';
|
|
});
|
|
}
|
|
},
|
|
child: const Text('Guess'))
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
// Copy/pasted from https://medium.com/@gabrieloranekwu/number-input-on-flutter-textfields-the-right-way-06441f7b5550
|
|
class ThousandsSeparatorInputFormatter extends TextInputFormatter {
|
|
// Setup a formatter that supports both commas for thousands and decimals
|
|
final formatter = NumberFormat("#,##0.###");
|
|
|
|
@override
|
|
TextEditingValue formatEditUpdate(
|
|
TextEditingValue oldValue, TextEditingValue newValue) {
|
|
if (newValue.text.isEmpty) {
|
|
return newValue;
|
|
}
|
|
|
|
if (newValue.text == '-') {
|
|
return newValue;
|
|
}
|
|
|
|
// Remove commas to check the new input and for parsing
|
|
final newText = newValue.text.replaceAll(',', '');
|
|
// Try parsing the input as a double
|
|
final num? newTextAsNum = num.tryParse(newText);
|
|
|
|
if (newTextAsNum == null) {
|
|
return oldValue; // Return old value if new value is not a number
|
|
}
|
|
|
|
// Split the input into whole number and decimal parts
|
|
final parts = newText.split('.');
|
|
if (parts.length > 1) {
|
|
// If there's a decimal part, format accordingly
|
|
final integerPart = int.tryParse(parts[0]) ?? 0;
|
|
final decimalPart = parts[1];
|
|
// Handle edge case where decimal part is present but empty (user just typed the dot)
|
|
final formattedText = '${formatter.format(integerPart)}.$decimalPart';
|
|
return newValue.copyWith(text: formattedText);
|
|
} else {
|
|
// No decimal part, format the whole number
|
|
final newFormattedText = formatter.format(newTextAsNum);
|
|
return newValue.copyWith(text: newFormattedText);
|
|
}
|
|
}
|
|
|
|
TextSelection updateCursorPosition(String text) {
|
|
return TextSelection.collapsed(offset: text.length);
|
|
}
|
|
}
|