mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 12:23:31 +00:00
Add difficulty levels to focus mode
This commit is contained in:
parent
39fa0fae08
commit
fe348a8020
4 changed files with 251 additions and 167 deletions
|
@ -8,7 +8,23 @@ import '../riverpod_controllers/focus_mode.dart';
|
|||
import '../routes.dart';
|
||||
import 'padding.dart';
|
||||
|
||||
const foreverDuration = Duration(days: 10000);
|
||||
enum Difficulty {
|
||||
easy(10),
|
||||
medium(1000),
|
||||
difficult(1000000),
|
||||
extreme(1000000000);
|
||||
|
||||
final int maxNumber;
|
||||
|
||||
const Difficulty(this.maxNumber);
|
||||
|
||||
String get label => switch (this) {
|
||||
easy => 'Easy',
|
||||
medium => 'Medium',
|
||||
difficult => 'Difficult',
|
||||
extreme => 'Extremely Difficult',
|
||||
};
|
||||
}
|
||||
|
||||
class FocusModeMenuItem extends ConsumerWidget {
|
||||
const FocusModeMenuItem({super.key});
|
||||
|
@ -27,16 +43,12 @@ class FocusModeMenuItem extends ConsumerWidget {
|
|||
context.pop();
|
||||
context.push(ScreenPaths.focusModeDisable);
|
||||
} else {
|
||||
final duration = await _chooseDuration(context);
|
||||
if (duration == null) {
|
||||
final focusMode = await _chooseFocusMode(context);
|
||||
if (focusMode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final disableTime = duration == foreverDuration
|
||||
? null
|
||||
: DateTime.now().add(duration);
|
||||
final update = FocusModeData(true, disableTime: disableTime);
|
||||
ref.read(focusModeProvider.notifier).setMode(update);
|
||||
ref.read(focusModeProvider.notifier).setMode(focusMode);
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
context.go(ScreenPaths.timelines);
|
||||
|
@ -48,28 +60,42 @@ class FocusModeMenuItem extends ConsumerWidget {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Duration?> _chooseDuration(
|
||||
Future<FocusModeData?> _chooseFocusMode(
|
||||
BuildContext context,
|
||||
) {
|
||||
var hours = 0;
|
||||
var minutes = 30;
|
||||
int hours = 0;
|
||||
int minutes = 30;
|
||||
var difficulty = Difficulty.medium;
|
||||
|
||||
return showDialog<Duration?>(
|
||||
return showDialog<FocusModeData?>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
builder: (BuildContext context) => StatefulBuilder(
|
||||
builder: (BuildContext context, void Function(void Function()) setState) {
|
||||
return AlertDialog(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Choose Focus Duration',
|
||||
'Choose Focus Duration and Difficulty',
|
||||
softWrap: true,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const VerticalPadding(),
|
||||
DropdownButton<Difficulty>(
|
||||
value: difficulty,
|
||||
onChanged: (v) => setState(() => difficulty = v!),
|
||||
items: Difficulty.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(e.label),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
const VerticalPadding(),
|
||||
Wrap(
|
||||
runSpacing: 10.0,
|
||||
spacing: 10.0,
|
||||
|
@ -79,9 +105,15 @@ Future<Duration?> _chooseDuration(
|
|||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
FocusModeData(
|
||||
true,
|
||||
maxNumber: difficulty.maxNumber,
|
||||
disableTime: DateTime.now().add(
|
||||
const Duration(
|
||||
minutes: 15,
|
||||
),
|
||||
),
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
|
@ -90,9 +122,15 @@ Future<Duration?> _chooseDuration(
|
|||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
FocusModeData(
|
||||
true,
|
||||
maxNumber: difficulty.maxNumber,
|
||||
disableTime: DateTime.now().add(
|
||||
const Duration(
|
||||
minutes: 30,
|
||||
),
|
||||
),
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
|
@ -101,9 +139,15 @@ Future<Duration?> _chooseDuration(
|
|||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
FocusModeData(
|
||||
true,
|
||||
maxNumber: difficulty.maxNumber,
|
||||
disableTime: DateTime.now().add(
|
||||
const Duration(
|
||||
hours: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
|
@ -112,9 +156,15 @@ Future<Duration?> _chooseDuration(
|
|||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
FocusModeData(
|
||||
true,
|
||||
maxNumber: difficulty.maxNumber,
|
||||
disableTime: DateTime.now().add(
|
||||
const Duration(
|
||||
hours: 6,
|
||||
),
|
||||
),
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
|
@ -123,9 +173,15 @@ Future<Duration?> _chooseDuration(
|
|||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
FocusModeData(
|
||||
true,
|
||||
maxNumber: difficulty.maxNumber,
|
||||
disableTime: DateTime.now().add(
|
||||
const Duration(
|
||||
hours: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
|
@ -134,9 +190,15 @@ Future<Duration?> _chooseDuration(
|
|||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
FocusModeData(
|
||||
true,
|
||||
maxNumber: difficulty.maxNumber,
|
||||
disableTime: DateTime.now().add(
|
||||
const Duration(
|
||||
days: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
|
@ -145,7 +207,10 @@ Future<Duration?> _chooseDuration(
|
|||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
foreverDuration,
|
||||
FocusModeData(
|
||||
true,
|
||||
maxNumber: difficulty.maxNumber,
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
|
@ -163,7 +228,8 @@ Future<Duration?> _chooseDuration(
|
|||
onValueChanged: (v) => hours = v,
|
||||
maxValue: 24,
|
||||
minValue: 0,
|
||||
unSelectTextStyle: const TextStyle(color: Colors.grey),
|
||||
unSelectTextStyle:
|
||||
const TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
const Text('hours'),
|
||||
|
@ -173,7 +239,8 @@ Future<Duration?> _chooseDuration(
|
|||
onValueChanged: (v) => minutes = v,
|
||||
maxValue: 59,
|
||||
minValue: 0,
|
||||
unSelectTextStyle: const TextStyle(color: Colors.grey),
|
||||
unSelectTextStyle:
|
||||
const TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
const Text('minutes'),
|
||||
|
@ -188,14 +255,21 @@ Future<Duration?> _chooseDuration(
|
|||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
FocusModeData(
|
||||
true,
|
||||
maxNumber: difficulty.maxNumber,
|
||||
disableTime: DateTime.now().add(
|
||||
Duration(
|
||||
hours: hours,
|
||||
minutes: minutes,
|
||||
),
|
||||
),
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
]);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
const defaultMaxNumber = 1000;
|
||||
|
||||
class FocusModeData {
|
||||
final DateTime? disableTime;
|
||||
final int maxNumber;
|
||||
final bool enabled;
|
||||
|
||||
const FocusModeData(this.enabled, {this.disableTime});
|
||||
const FocusModeData(this.enabled,
|
||||
{this.maxNumber = defaultMaxNumber, this.disableTime});
|
||||
|
||||
factory FocusModeData.disabled() => const FocusModeData(false);
|
||||
|
||||
factory FocusModeData.fromJson(Map<String, dynamic> json) => FocusModeData(
|
||||
json['enabled'],
|
||||
maxNumber: json['maxNumber'] ?? defaultMaxNumber,
|
||||
disableTime: DateTime.tryParse(json['disableTime'] ?? ''),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'enabled': enabled,
|
||||
'maxNumber': maxNumber,
|
||||
if (disableTime != null) 'disableTime': disableTime!.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,13 +12,9 @@ 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 _maxNumber = 1000000;
|
||||
final introMessage =
|
||||
"If you guess the number I've picked from 0 to ${decimalWithCommasFormat.format(_maxNumber)} you may disable focus mode...";
|
||||
|
||||
const magicUnlockNumber = -1563948536;
|
||||
final decimalWithCommasFormat = NumberFormat("#,##0.###");
|
||||
const _magicUnlockNumber = -1563948536;
|
||||
|
||||
class GameState {
|
||||
final int maxNumber;
|
||||
|
@ -41,7 +37,7 @@ class GameState {
|
|||
return 'You got it!';
|
||||
}
|
||||
|
||||
bool get found => number == lastGuess || lastGuess == magicUnlockNumber;
|
||||
bool get found => number == lastGuess || lastGuess == _magicUnlockNumber;
|
||||
|
||||
const GameState({
|
||||
required this.number,
|
||||
|
@ -71,8 +67,18 @@ class _DisableFocusModeScreenState
|
|||
extends ConsumerState<DisableFocusModeScreen> {
|
||||
final formKey = GlobalKey<FormState>();
|
||||
final guessController = TextEditingController();
|
||||
var game = GameState.newGame(_maxNumber);
|
||||
var message = introMessage;
|
||||
var game = GameState.newGame(1);
|
||||
var maxNumber = 1;
|
||||
var message = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
maxNumber = ref.read(focusModeProvider).maxNumber;
|
||||
game = GameState.newGame(maxNumber);
|
||||
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) {
|
||||
|
@ -126,7 +132,7 @@ class _DisableFocusModeScreenState
|
|||
final valid = formKey.currentState?.validate() ?? false;
|
||||
if (!valid) {
|
||||
buildSnackbar(context,
|
||||
'Please enter an integer between 0 and ${decimalWithCommasFormat.format(_maxNumber)}');
|
||||
'Please enter an integer between 0 and ${decimalWithCommasFormat.format(maxNumber)}');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -187,17 +193,11 @@ class ThousandsSeparatorInputFormatter extends TextInputFormatter {
|
|||
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 TextEditingValue(
|
||||
text: formattedText,
|
||||
selection: updateCursorPosition(formattedText),
|
||||
);
|
||||
return newValue.copyWith(text: formattedText);
|
||||
} else {
|
||||
// No decimal part, format the whole number
|
||||
final newFormattedText = formatter.format(newTextAsNum);
|
||||
return TextEditingValue(
|
||||
text: newFormattedText,
|
||||
selection: updateCursorPosition(newFormattedText),
|
||||
);
|
||||
return newValue.copyWith(text: newFormattedText);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:intl/intl.dart';
|
||||
|
||||
extension StringUtils on String {
|
||||
String truncate({int length = 32}) {
|
||||
if (this.length <= length) {
|
||||
|
@ -7,8 +9,10 @@ extension StringUtils on String {
|
|||
return '${substring(0, length)}...';
|
||||
}
|
||||
|
||||
String stripHyperlinks() =>
|
||||
replaceAll(RegExp(
|
||||
String stripHyperlinks() => replaceAll(
|
||||
RegExp(
|
||||
r"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?"),
|
||||
"");
|
||||
}
|
||||
|
||||
final decimalWithCommasFormat = NumberFormat("#,##0.###");
|
||||
|
|
Loading…
Reference in a new issue