mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 12:23:31 +00:00
Merge branch 'focus-mode' into 'main'
Focus mode See merge request mysocialportal/relatica!60
This commit is contained in:
commit
8b67f35cb1
16 changed files with 973 additions and 271 deletions
|
@ -22,8 +22,12 @@ linter:
|
|||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||
# producing the lint.
|
||||
rules:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
||||
analyzer:
|
||||
plugins:
|
||||
- custom_lint
|
201
lib/controls/focus_mode_menu_item.dart
Normal file
201
lib/controls/focus_mode_menu_item.dart
Normal file
|
@ -0,0 +1,201 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:wheel_chooser/wheel_chooser.dart';
|
||||
|
||||
import '../models/focus_mode_data.dart';
|
||||
import '../riverpod_controllers/focus_mode.dart';
|
||||
import '../routes.dart';
|
||||
import 'padding.dart';
|
||||
|
||||
const foreverDuration = Duration(days: 10000);
|
||||
|
||||
class FocusModeMenuItem extends ConsumerWidget {
|
||||
const FocusModeMenuItem({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final focusMode = ref.watch(focusModeProvider);
|
||||
final title =
|
||||
focusMode.enabled ? 'Disable Focus Mode' : 'Enable Focus Mode';
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ListTile(
|
||||
title: Text(title),
|
||||
onTap: () async {
|
||||
if (focusMode.enabled) {
|
||||
context.pop();
|
||||
context.push(ScreenPaths.focusModeDisable);
|
||||
} else {
|
||||
final duration = await _chooseDuration(context);
|
||||
if (duration == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final disableTime = duration == foreverDuration
|
||||
? null
|
||||
: DateTime.now().add(duration);
|
||||
final update = FocusModeData(true, disableTime: disableTime);
|
||||
ref.read(focusModeProvider.notifier).setMode(update);
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
context.go(ScreenPaths.timelines);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Duration?> _chooseDuration(
|
||||
BuildContext context,
|
||||
) {
|
||||
var hours = 0;
|
||||
var minutes = 30;
|
||||
|
||||
return showDialog<Duration?>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Choose Focus Duration',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const VerticalPadding(),
|
||||
Wrap(
|
||||
runSpacing: 10.0,
|
||||
spacing: 10.0,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
child: const Text('15 minutes'),
|
||||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
const Duration(
|
||||
minutes: 15,
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
child: const Text('30 minutes'),
|
||||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
const Duration(
|
||||
minutes: 30,
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
child: const Text('1 hour'),
|
||||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
const Duration(
|
||||
hours: 1,
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
child: const Text('6 hours'),
|
||||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
const Duration(
|
||||
hours: 6,
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
child: const Text('12 hours'),
|
||||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
const Duration(
|
||||
hours: 12,
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
child: const Text('1 day'),
|
||||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
const Duration(
|
||||
days: 1,
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
child: const Text('Forever'),
|
||||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
foreverDuration,
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const VerticalPadding(),
|
||||
SizedBox(
|
||||
height: 100,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Flexible(
|
||||
child: WheelChooser.integer(
|
||||
initValue: hours,
|
||||
onValueChanged: (v) => hours = v,
|
||||
maxValue: 24,
|
||||
minValue: 0,
|
||||
unSelectTextStyle: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
const Text('hours'),
|
||||
Flexible(
|
||||
child: WheelChooser.integer(
|
||||
initValue: minutes,
|
||||
onValueChanged: (v) => minutes = v,
|
||||
maxValue: 59,
|
||||
minValue: 0,
|
||||
unSelectTextStyle: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
const Text('minutes'),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
ElevatedButton(
|
||||
child: const Text('Select'),
|
||||
onPressed: () {
|
||||
Navigator.pop(
|
||||
context,
|
||||
Duration(
|
||||
hours: hours,
|
||||
minutes: minutes,
|
||||
),
|
||||
); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
]);
|
||||
},
|
||||
);
|
||||
}
|
55
lib/controls/focus_mode_status_headline.dart
Normal file
55
lib/controls/focus_mode_status_headline.dart
Normal file
|
@ -0,0 +1,55 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../utils/dateutils.dart';
|
||||
|
||||
class FocusModeStatusHeadline extends StatefulWidget {
|
||||
final DateTime? disableTime;
|
||||
|
||||
const FocusModeStatusHeadline({super.key, required this.disableTime});
|
||||
|
||||
@override
|
||||
State<FocusModeStatusHeadline> createState() =>
|
||||
_FocusModeStatusHeadlineState();
|
||||
}
|
||||
|
||||
class _FocusModeStatusHeadlineState extends State<FocusModeStatusHeadline> {
|
||||
Timer? updateTimer;
|
||||
Duration? timeUntil;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_updateTimeUntil();
|
||||
updateTimer = Timer.periodic(Duration(seconds: 1), (_) {
|
||||
setState(() {
|
||||
_updateTimeUntil();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
print('Disposing');
|
||||
updateTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _updateTimeUntil() {
|
||||
timeUntil = widget.disableTime?.difference(DateTime.now());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final title = timeUntil == null
|
||||
? 'Focus Mode for Forever'
|
||||
: 'Focus Mode for ${timeUntil!.simpleLabel}';
|
||||
return Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
|
|||
import '../globals.dart';
|
||||
import '../routes.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import 'focus_mode_menu_item.dart';
|
||||
import 'login_aware_cached_network_image.dart';
|
||||
|
||||
class StandardAppDrawer extends StatelessWidget {
|
||||
|
@ -62,6 +63,7 @@ class StandardAppDrawer extends StatelessWidget {
|
|||
'Manage Profiles',
|
||||
() => context.pushNamed(ScreenPaths.manageProfiles),
|
||||
),
|
||||
const FocusModeMenuItem(),
|
||||
const Divider(),
|
||||
buildMenuButton(
|
||||
context,
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart' as fr;
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart';
|
||||
|
@ -8,6 +10,7 @@ import 'package:provider/provider.dart';
|
|||
import 'app_theme.dart';
|
||||
import 'di_initialization.dart';
|
||||
import 'globals.dart';
|
||||
import 'riverpod_controllers/focus_mode.dart';
|
||||
import 'routes.dart';
|
||||
import 'services/auth_service.dart';
|
||||
import 'services/blocks_manager.dart';
|
||||
|
@ -50,7 +53,7 @@ void main() async {
|
|||
// enabled: !kReleaseMode && enablePreview,
|
||||
// builder: (context) => const App(),
|
||||
// ));
|
||||
runApp(const App());
|
||||
runApp(const fr.ProviderScope(child: App()));
|
||||
}
|
||||
|
||||
Future<void> setupPackageInfoAndUserAgent() async {
|
||||
|
@ -59,13 +62,45 @@ Future<void> setupPackageInfoAndUserAgent() async {
|
|||
userAgent = 'Relatica/$appVersion';
|
||||
}
|
||||
|
||||
class App extends StatelessWidget {
|
||||
class App extends fr.ConsumerStatefulWidget {
|
||||
const App({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
fr.ConsumerState<App> createState() => _AppState();
|
||||
}
|
||||
|
||||
class _AppState extends fr.ConsumerState<App> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final settingsService = getIt<SettingsService>();
|
||||
final authService = getIt<AccountsService>();
|
||||
|
||||
final appRouter = GoRouter(
|
||||
initialLocation: ScreenPaths.timelines,
|
||||
debugLogDiagnostics: true,
|
||||
refreshListenable: authService,
|
||||
redirect: (context, state) async {
|
||||
final loggedIn = authService.loggedIn;
|
||||
final focusMode = ref.read(focusModeProvider);
|
||||
print('Focus mode? $focusMode');
|
||||
|
||||
if (!loggedIn && authService.initializing) {
|
||||
return ScreenPaths.splash;
|
||||
}
|
||||
|
||||
if (!loggedIn && !allowedLoggedOut.contains(state.uri.toString())) {
|
||||
return ScreenPaths.signin;
|
||||
}
|
||||
|
||||
if (loggedIn && allowedLoggedOut.contains(state.uri.toString())) {
|
||||
return ScreenPaths.timelines;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
routes: routes,
|
||||
);
|
||||
return AnimatedBuilder(
|
||||
builder: (context, child) {
|
||||
Logger.root.level = settingsService.logLevel;
|
||||
|
|
18
lib/models/focus_mode_data.dart
Normal file
18
lib/models/focus_mode_data.dart
Normal file
|
@ -0,0 +1,18 @@
|
|||
class FocusModeData {
|
||||
final DateTime? disableTime;
|
||||
final bool enabled;
|
||||
|
||||
const FocusModeData(this.enabled, {this.disableTime});
|
||||
|
||||
factory FocusModeData.disabled() => const FocusModeData(false);
|
||||
|
||||
factory FocusModeData.fromJson(Map<String, dynamic> json) => FocusModeData(
|
||||
json['enabled'],
|
||||
disableTime: DateTime.tryParse(json['disableTime'] ?? ''),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'enabled': enabled,
|
||||
if (disableTime != null) 'disableTime': disableTime!.toIso8601String(),
|
||||
};
|
||||
}
|
|
@ -54,6 +54,9 @@ class TimelineIdentifiers {
|
|||
factory TimelineIdentifiers.home() =>
|
||||
const TimelineIdentifiers(timeline: TimelineType.home);
|
||||
|
||||
factory TimelineIdentifiers.myPosts() =>
|
||||
const TimelineIdentifiers(timeline: TimelineType.self);
|
||||
|
||||
factory TimelineIdentifiers.profile(String profileId) => TimelineIdentifiers(
|
||||
timeline: TimelineType.profile,
|
||||
auxData: profileId,
|
||||
|
|
38
lib/riverpod_controllers/focus_mode.dart
Normal file
38
lib/riverpod_controllers/focus_mode.dart
Normal file
|
@ -0,0 +1,38 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../globals.dart';
|
||||
import '../models/focus_mode_data.dart';
|
||||
import '../services/setting_service.dart';
|
||||
|
||||
part 'focus_mode.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class FocusMode extends _$FocusMode {
|
||||
Timer? _disableTimer;
|
||||
|
||||
void setMode(FocusModeData newMode) {
|
||||
_disableTimer?.cancel();
|
||||
var updatedState = newMode;
|
||||
if (newMode.enabled && newMode.disableTime != null) {
|
||||
final timeDifference = newMode.disableTime!.difference(DateTime.now());
|
||||
if (timeDifference.isNegative || timeDifference.inMicroseconds == 0) {
|
||||
updatedState = FocusModeData.disabled();
|
||||
}
|
||||
_disableTimer = Timer(timeDifference, () {
|
||||
state = FocusModeData.disabled();
|
||||
});
|
||||
}
|
||||
|
||||
getIt<SettingsService>().focusModeData = updatedState;
|
||||
state = updatedState;
|
||||
}
|
||||
|
||||
@override
|
||||
FocusModeData build() {
|
||||
final storedFocusMode = getIt<SettingsService>().focusModeData;
|
||||
setMode(storedFocusMode);
|
||||
return state;
|
||||
}
|
||||
}
|
24
lib/riverpod_controllers/focus_mode.g.dart
Normal file
24
lib/riverpod_controllers/focus_mode.g.dart
Normal file
|
@ -0,0 +1,24 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'focus_mode.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$focusModeHash() => r'93028f8514cecda8ce68506ec242f4d26d63b4b2';
|
||||
|
||||
/// See also [FocusMode].
|
||||
@ProviderFor(FocusMode)
|
||||
final focusModeProvider = NotifierProvider<FocusMode, FocusModeData>.internal(
|
||||
FocusMode.new,
|
||||
name: r'focusModeProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$focusModeHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$FocusMode = Notifier<FocusModeData>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
467
lib/routes.dart
467
lib/routes.dart
|
@ -1,6 +1,5 @@
|
|||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'globals.dart';
|
||||
import 'models/interaction_type_enum.dart';
|
||||
import 'screens/blocks_screen.dart';
|
||||
import 'screens/circle_add_users_screen.dart';
|
||||
|
@ -8,6 +7,7 @@ import 'screens/circle_create_screen.dart';
|
|||
import 'screens/circle_editor_screen.dart';
|
||||
import 'screens/circle_management_screen.dart';
|
||||
import 'screens/contacts_screen.dart';
|
||||
import 'screens/disable_focus_mode_screen.dart';
|
||||
import 'screens/editor.dart';
|
||||
import 'screens/filter_editor_screen.dart';
|
||||
import 'screens/filters_screen.dart';
|
||||
|
@ -29,7 +29,6 @@ import 'screens/sign_in.dart';
|
|||
import 'screens/splash.dart';
|
||||
import 'screens/user_posts_screen.dart';
|
||||
import 'screens/user_profile_screen.dart';
|
||||
import 'services/auth_service.dart';
|
||||
|
||||
class ScreenPaths {
|
||||
static String blocks = '/blocks';
|
||||
|
@ -37,6 +36,7 @@ class ScreenPaths {
|
|||
static String thread = '/thread';
|
||||
static String connectHandle = '/connect';
|
||||
static String contacts = '/contacts';
|
||||
static String focusModeDisable = '/focus_mode_disable';
|
||||
static String splash = '/splash';
|
||||
static String settings = '/settings';
|
||||
static String messages = '/messages';
|
||||
|
@ -56,271 +56,254 @@ class ScreenPaths {
|
|||
}
|
||||
|
||||
bool needAuthChangeInitialized = true;
|
||||
final _authService = getIt<AccountsService>();
|
||||
final allowedLoggedOut = [
|
||||
ScreenPaths.splash,
|
||||
ScreenPaths.signin,
|
||||
ScreenPaths.signup
|
||||
];
|
||||
|
||||
final appRouter = GoRouter(
|
||||
initialLocation: ScreenPaths.timelines,
|
||||
debugLogDiagnostics: true,
|
||||
refreshListenable: _authService,
|
||||
redirect: (context, state) async {
|
||||
final loggedIn = _authService.loggedIn;
|
||||
|
||||
if (!loggedIn && _authService.initializing) {
|
||||
return ScreenPaths.splash;
|
||||
}
|
||||
|
||||
if (!loggedIn && !allowedLoggedOut.contains(state.uri.toString())) {
|
||||
return ScreenPaths.signin;
|
||||
}
|
||||
|
||||
if (loggedIn && allowedLoggedOut.contains(state.uri.toString())) {
|
||||
return ScreenPaths.timelines;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
final routes = [
|
||||
GoRoute(
|
||||
path: ScreenPaths.blocks,
|
||||
name: ScreenPaths.blocks,
|
||||
builder: (context, state) => const BlocksScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.focusModeDisable,
|
||||
name: ScreenPaths.focusModeDisable,
|
||||
builder: (context, state) => const DisableFocusModeScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.filters,
|
||||
name: ScreenPaths.filters,
|
||||
builder: (context, state) => const FiltersScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: ScreenPaths.blocks,
|
||||
name: ScreenPaths.blocks,
|
||||
builder: (context, state) => const BlocksScreen(),
|
||||
path: 'new',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: FilterEditorScreen(id: ''),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.filters,
|
||||
name: ScreenPaths.filters,
|
||||
builder: (context, state) => const FiltersScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'new',
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: FilterEditorScreen(id: ''),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'edit/:id',
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
child: FilterEditorScreen(id: state.pathParameters['id']!)),
|
||||
)
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.signin,
|
||||
name: ScreenPaths.signin,
|
||||
builder: (context, state) => const SignInScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.manageProfiles,
|
||||
name: ScreenPaths.manageProfiles,
|
||||
builder: (context, state) => const SignInScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.contacts,
|
||||
name: ScreenPaths.contacts,
|
||||
path: 'edit/:id',
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
name: ScreenPaths.contacts,
|
||||
child: const ContactsScreen(),
|
||||
),
|
||||
),
|
||||
child: FilterEditorScreen(id: state.pathParameters['id']!)),
|
||||
)
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.signin,
|
||||
name: ScreenPaths.signin,
|
||||
builder: (context, state) => const SignInScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.manageProfiles,
|
||||
name: ScreenPaths.manageProfiles,
|
||||
builder: (context, state) => const SignInScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.contacts,
|
||||
name: ScreenPaths.contacts,
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
name: ScreenPaths.contacts,
|
||||
child: const ContactsScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/connect/:id',
|
||||
name: ScreenPaths.connectHandle,
|
||||
builder: (context, state) =>
|
||||
FollowRequestAdjudicationScreen(userId: state.pathParameters['id']!),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.timelines,
|
||||
name: ScreenPaths.timelines,
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
name: ScreenPaths.timelines,
|
||||
child: const HomeScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.messages,
|
||||
name: ScreenPaths.messages,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: MessagesScreen(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/connect/:id',
|
||||
name: ScreenPaths.connectHandle,
|
||||
builder: (context, state) => FollowRequestAdjudicationScreen(
|
||||
userId: state.pathParameters['id']!),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.timelines,
|
||||
name: ScreenPaths.timelines,
|
||||
path: 'new_thread',
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
name: ScreenPaths.timelines,
|
||||
child: const HomeScreen(),
|
||||
child: MessagesNewThread(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
name: ScreenPaths.thread,
|
||||
path: ScreenPaths.thread,
|
||||
builder: (context, state) =>
|
||||
MessageThreadScreen(parentThreadId: state.uri.queryParameters['uri']!),
|
||||
),
|
||||
GoRoute(
|
||||
name: ScreenPaths.circleManagement,
|
||||
path: ScreenPaths.circleManagement,
|
||||
builder: (context, state) => const CircleManagementScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'show/:id',
|
||||
builder: (context, state) => CircleEditorScreen(
|
||||
circleId: state.pathParameters['id']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.messages,
|
||||
name: ScreenPaths.messages,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: MessagesScreen(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'new_thread',
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
child: MessagesNewThread(),
|
||||
),
|
||||
),
|
||||
],
|
||||
path: 'new',
|
||||
builder: (context, state) => const CircleCreateScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
name: ScreenPaths.thread,
|
||||
path: ScreenPaths.thread,
|
||||
builder: (context, state) => MessageThreadScreen(
|
||||
parentThreadId: state.uri.queryParameters['uri']!),
|
||||
),
|
||||
GoRoute(
|
||||
name: ScreenPaths.circleManagement,
|
||||
path: ScreenPaths.circleManagement,
|
||||
builder: (context, state) => const CircleManagementScreen(),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'show/:id',
|
||||
builder: (context, state) => CircleEditorScreen(
|
||||
circleId: state.pathParameters['id']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'new',
|
||||
builder: (context, state) => const CircleCreateScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'add_users/:id',
|
||||
builder: (context, state) =>
|
||||
CircleAddUsersScreen(circleId: state.pathParameters['id']!),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.settings,
|
||||
name: ScreenPaths.settings,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: SettingsScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.gallery,
|
||||
name: ScreenPaths.gallery,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: GalleryBrowsersScreen(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'show',
|
||||
builder: (context, state) => GalleryScreen(
|
||||
galleryName: state.extra!.toString(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'edit/:name/image/:id',
|
||||
builder: (context, state) => ImageEditorScreen(
|
||||
galleryName: state.pathParameters['name']!,
|
||||
imageId: state.pathParameters['id']!,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.notifications,
|
||||
name: ScreenPaths.notifications,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: NotificationsScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.splash,
|
||||
name: ScreenPaths.splash,
|
||||
builder: (context, state) => const SplashScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/post',
|
||||
redirect: (context, state) {
|
||||
if (state.uri.toString() == '/post') {
|
||||
return '/post/new';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'new',
|
||||
builder: (context, state) => const EditorScreen(
|
||||
forEditing: false,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'edit/:id',
|
||||
builder: (context, state) => EditorScreen(
|
||||
id: state.pathParameters['id'] ?? 'Not Found',
|
||||
forEditing: true,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'view/:id/:goto_id',
|
||||
builder: (context, state) => PostScreen(
|
||||
id: state.pathParameters['id'] ?? 'Not Found',
|
||||
goToId: state.pathParameters['goto_id'] ?? 'Not Found',
|
||||
),
|
||||
),
|
||||
]),
|
||||
GoRoute(
|
||||
path: '/comment',
|
||||
redirect: (context, state) {
|
||||
if (state.uri.toString() == '/comment') {
|
||||
return '/comment/new';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'new',
|
||||
builder: (context, state) => EditorScreen(
|
||||
parentId: state.uri.queryParameters['parent_id'] ?? '',
|
||||
forEditing: false,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'edit/:id',
|
||||
builder: (context, state) => EditorScreen(
|
||||
id: state.pathParameters['id'] ?? 'Not Found',
|
||||
forEditing: true,
|
||||
),
|
||||
),
|
||||
]),
|
||||
GoRoute(
|
||||
path: '/user_posts/:id',
|
||||
name: ScreenPaths.userPosts,
|
||||
path: 'add_users/:id',
|
||||
builder: (context, state) =>
|
||||
UserPostsScreen(userId: state.pathParameters['id']!),
|
||||
CircleAddUsersScreen(circleId: state.pathParameters['id']!),
|
||||
),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.settings,
|
||||
name: ScreenPaths.settings,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: SettingsScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.gallery,
|
||||
name: ScreenPaths.gallery,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: GalleryBrowsersScreen(),
|
||||
),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/likes/:id',
|
||||
name: ScreenPaths.likes,
|
||||
builder: (context, state) => InteractionsViewerScreen(
|
||||
statusId: state.pathParameters['id']!,
|
||||
type: InteractionType.like,
|
||||
path: 'show',
|
||||
builder: (context, state) => GalleryScreen(
|
||||
galleryName: state.extra!.toString(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/reshares/:id',
|
||||
name: ScreenPaths.reshares,
|
||||
builder: (context, state) => InteractionsViewerScreen(
|
||||
statusId: state.pathParameters['id']!,
|
||||
type: InteractionType.reshare,
|
||||
path: 'edit/:name/image/:id',
|
||||
builder: (context, state) => ImageEditorScreen(
|
||||
galleryName: state.pathParameters['name']!,
|
||||
imageId: state.pathParameters['id']!,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/user_profile/:id',
|
||||
name: ScreenPaths.userProfile,
|
||||
builder: (context, state) =>
|
||||
UserProfileScreen(userId: state.pathParameters['id']!),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.search,
|
||||
name: ScreenPaths.search,
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
name: ScreenPaths.search,
|
||||
child: const SearchScreen(),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.notifications,
|
||||
name: ScreenPaths.notifications,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: NotificationsScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.splash,
|
||||
name: ScreenPaths.splash,
|
||||
builder: (context, state) => const SplashScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/post',
|
||||
redirect: (context, state) {
|
||||
if (state.uri.toString() == '/post') {
|
||||
return '/post/new';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'new',
|
||||
builder: (context, state) => const EditorScreen(
|
||||
forEditing: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.logViewer,
|
||||
name: ScreenPaths.logViewer,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: LogViewerScreen(),
|
||||
GoRoute(
|
||||
path: 'edit/:id',
|
||||
builder: (context, state) => EditorScreen(
|
||||
id: state.pathParameters['id'] ?? 'Not Found',
|
||||
forEditing: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
]);
|
||||
GoRoute(
|
||||
path: 'view/:id/:goto_id',
|
||||
builder: (context, state) => PostScreen(
|
||||
id: state.pathParameters['id'] ?? 'Not Found',
|
||||
goToId: state.pathParameters['goto_id'] ?? 'Not Found',
|
||||
),
|
||||
),
|
||||
]),
|
||||
GoRoute(
|
||||
path: '/comment',
|
||||
redirect: (context, state) {
|
||||
if (state.uri.toString() == '/comment') {
|
||||
return '/comment/new';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: 'new',
|
||||
builder: (context, state) => EditorScreen(
|
||||
parentId: state.uri.queryParameters['parent_id'] ?? '',
|
||||
forEditing: false,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: 'edit/:id',
|
||||
builder: (context, state) => EditorScreen(
|
||||
id: state.pathParameters['id'] ?? 'Not Found',
|
||||
forEditing: true,
|
||||
),
|
||||
),
|
||||
]),
|
||||
GoRoute(
|
||||
path: '/user_posts/:id',
|
||||
name: ScreenPaths.userPosts,
|
||||
builder: (context, state) =>
|
||||
UserPostsScreen(userId: state.pathParameters['id']!),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/likes/:id',
|
||||
name: ScreenPaths.likes,
|
||||
builder: (context, state) => InteractionsViewerScreen(
|
||||
statusId: state.pathParameters['id']!,
|
||||
type: InteractionType.like,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/reshares/:id',
|
||||
name: ScreenPaths.reshares,
|
||||
builder: (context, state) => InteractionsViewerScreen(
|
||||
statusId: state.pathParameters['id']!,
|
||||
type: InteractionType.reshare,
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/user_profile/:id',
|
||||
name: ScreenPaths.userProfile,
|
||||
builder: (context, state) =>
|
||||
UserProfileScreen(userId: state.pathParameters['id']!),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.search,
|
||||
name: ScreenPaths.search,
|
||||
pageBuilder: (context, state) => NoTransitionPage(
|
||||
name: ScreenPaths.search,
|
||||
child: const SearchScreen(),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: ScreenPaths.logViewer,
|
||||
name: ScreenPaths.logViewer,
|
||||
pageBuilder: (context, state) => const NoTransitionPage(
|
||||
child: LogViewerScreen(),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
|
137
lib/screens/disable_focus_mode_screen.dart
Normal file
137
lib/screens/disable_focus_mode_screen.dart
Normal file
|
@ -0,0 +1,137 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.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';
|
||||
|
||||
class GameState {
|
||||
final int maxNumber;
|
||||
final int number;
|
||||
final int? lastGuess;
|
||||
|
||||
String get hint {
|
||||
if (lastGuess == null) {
|
||||
return 'Guess a number between 0 and $maxNumber';
|
||||
}
|
||||
|
||||
if (lastGuess! < number) {
|
||||
return '$lastGuess is too low. Guess a higher number';
|
||||
}
|
||||
|
||||
if (lastGuess! > number) {
|
||||
return '$lastGuess is too high. Guess a lower number';
|
||||
}
|
||||
|
||||
return 'You got it!';
|
||||
}
|
||||
|
||||
bool get found => number == lastGuess;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const _maxNumber = 100;
|
||||
const introMessage =
|
||||
"If you guess the number I've picked from 0 to $_maxNumber you may disable focus mode...";
|
||||
|
||||
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(_maxNumber);
|
||||
var message = introMessage;
|
||||
|
||||
@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),
|
||||
const VerticalPadding(),
|
||||
TextFormField(
|
||||
controller: guessController,
|
||||
keyboardType: TextInputType.number,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (value) => int.tryParse(value!) == null
|
||||
? 'Please enter a number'
|
||||
: 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 $_maxNumber');
|
||||
return;
|
||||
}
|
||||
|
||||
final guess = int.parse(guessController.text);
|
||||
game = game.update(guess);
|
||||
if (game.found) {
|
||||
ref
|
||||
.read(focusModeProvider.notifier)
|
||||
.setMode(const FocusModeData(false));
|
||||
context.go(ScreenPaths.timelines);
|
||||
} else {
|
||||
setState(() {
|
||||
message = game.hint;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: const Text('Guess'))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relatica/controls/focus_mode_status_headline.dart';
|
||||
import 'package:relatica/riverpod_controllers/focus_mode.dart';
|
||||
|
||||
import '../controls/app_bottom_nav_bar.dart';
|
||||
import '../controls/linear_status_indicator.dart';
|
||||
|
@ -16,14 +19,14 @@ import '../services/network_status_service.dart';
|
|||
import '../services/timeline_manager.dart';
|
||||
import '../utils/active_profile_selector.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
class HomeScreen extends ConsumerStatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
ConsumerState<HomeScreen> createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
class _HomeScreenState extends ConsumerState<HomeScreen> {
|
||||
final _logger = Logger('$HomeScreen');
|
||||
|
||||
TimelineIdentifiers currentTimeline = TimelineIdentifiers.home();
|
||||
|
@ -49,8 +52,12 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||
_logger.finest('Build');
|
||||
final accountService = getIt<AccountsService>();
|
||||
final nss = getIt<NetworkStatusService>();
|
||||
final focusMode = ref.watch(focusModeProvider);
|
||||
|
||||
final timeline = TimelinePanel(timeline: currentTimeline);
|
||||
final timeline = TimelinePanel(
|
||||
timeline: focusMode.enabled
|
||||
? TimelineIdentifiers.myPosts()
|
||||
: currentTimeline);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
|
@ -65,7 +72,9 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||
})
|
||||
: null,
|
||||
backgroundColor: Theme.of(context).canvasColor,
|
||||
title: buildTimelineSelector(context),
|
||||
title: focusMode.enabled
|
||||
? FocusModeStatusHeadline(disableTime: focusMode.disableTime)
|
||||
: buildTimelineSelector(context),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
|
@ -78,10 +87,12 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||
),
|
||||
),
|
||||
drawer: const StandardAppDrawer(),
|
||||
bottomNavigationBar: AppBottomNavBar(
|
||||
currentButton: NavBarButtons.timelines,
|
||||
onHomeButtonReclick: () => timeline.scrollToTop(),
|
||||
),
|
||||
bottomNavigationBar: focusMode.enabled
|
||||
? null
|
||||
: AppBottomNavBar(
|
||||
currentButton: NavBarButtons.timelines,
|
||||
onHomeButtonReclick: () => timeline.scrollToTop(),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.small(
|
||||
onPressed: () {
|
||||
context.push('/post/new');
|
||||
|
|
|
@ -5,9 +5,19 @@ import 'package:flutter/material.dart';
|
|||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../models/focus_mode_data.dart';
|
||||
import '../models/settings/network_capabilities_settings.dart';
|
||||
import '../utils/theme_mode_extensions.dart';
|
||||
|
||||
const _lowBandwidthModeKey = 'LowBandwidthMode';
|
||||
const _themeModeKey = 'ThemeMode';
|
||||
const _colorBlindnessTestingModeKey = 'ColorBlindnessTestingMode';
|
||||
const _logLevelKey = 'LogLevel';
|
||||
const _networkCapabilitiesKey = 'NetworkCapabilities';
|
||||
const _notificationGroupingKey = 'NotificationGrouping';
|
||||
const _spoilerHidingEnabledKey = 'SpoilerHidingEnabled';
|
||||
const _focusModeKey = 'FocusMode';
|
||||
|
||||
class SettingsService extends ChangeNotifier {
|
||||
late final SharedPreferences _prefs;
|
||||
var _initialized = false;
|
||||
|
@ -86,6 +96,18 @@ class SettingsService extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
FocusModeData _focusModeData = FocusModeData.disabled();
|
||||
|
||||
FocusModeData get focusModeData => _focusModeData;
|
||||
|
||||
set focusModeData(FocusModeData updatedData) {
|
||||
_focusModeData = updatedData;
|
||||
final jsonData = _focusModeData.toJson();
|
||||
final jsonString = jsonEncode(jsonData);
|
||||
_prefs.setString(_focusModeKey, jsonString);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
if (_initialized) {
|
||||
return;
|
||||
|
@ -98,18 +120,11 @@ class SettingsService extends ChangeNotifier {
|
|||
_colorBlindnessType = _colorBlindnessTypeFromPrefs(_prefs);
|
||||
_logLevel = _levelFromPrefs(_prefs);
|
||||
_networkCapabilities = _networkCapabilitiesFromPrefs(_prefs);
|
||||
_focusModeData = _focusModeDataFromPrefs(_prefs);
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
const _lowBandwidthModeKey = 'LowBandwidthMode';
|
||||
const _themeModeKey = 'ThemeMode';
|
||||
const _colorBlindnessTestingModeKey = 'ColorBlindnessTestingMode';
|
||||
const _logLevelKey = 'LogLevel';
|
||||
const _networkCapabilitiesKey = 'NetworkCapabilities';
|
||||
const _notificationGroupingKey = 'NotificationGrouping';
|
||||
const _spoilerHidingEnabledKey = 'SpoilerHidingEnabled';
|
||||
|
||||
ColorBlindnessType _colorBlindnessTypeFromPrefs(SharedPreferences prefs) {
|
||||
final cbString = prefs.getString(_colorBlindnessTestingModeKey);
|
||||
if (cbString?.isEmpty ?? true) {
|
||||
|
@ -149,3 +164,14 @@ Level _levelFromPrefs(SharedPreferences prefs) {
|
|||
_ => Level.OFF,
|
||||
};
|
||||
}
|
||||
|
||||
FocusModeData _focusModeDataFromPrefs(SharedPreferences prefs) {
|
||||
final fmString = prefs.getString(_focusModeKey);
|
||||
if (fmString?.isEmpty ?? true) {
|
||||
return FocusModeData.disabled();
|
||||
}
|
||||
|
||||
final Map<String, dynamic> json = jsonDecode(fmString!);
|
||||
final fm = FocusModeData.fromJson(json);
|
||||
return fm;
|
||||
}
|
||||
|
|
|
@ -78,3 +78,35 @@ const _separator = '_';
|
|||
extension DateTimeExtensions on DateTime {
|
||||
String toFileNameString() => '$year$month$day$_separator$hour$minute$second';
|
||||
}
|
||||
|
||||
extension DurationExtensions on Duration {
|
||||
String get simpleLabel {
|
||||
final days = inHours / 24.0;
|
||||
if (days >= 1) {
|
||||
return days.round() == 1 ? '1 day' : '${days.round()} days';
|
||||
}
|
||||
|
||||
final hours = inMinutes / 60.0;
|
||||
if (hours >= 1) {
|
||||
return hours.round() == 1 ? '1 hour' : '${hours.round()} hours';
|
||||
}
|
||||
|
||||
final minutes = inSeconds / 60.0;
|
||||
if (minutes >= 1) {
|
||||
return minutes.round() == 1 ? '1 minute' : '${minutes.round()} minutes';
|
||||
}
|
||||
|
||||
final seconds = inMilliseconds / 1000.0;
|
||||
if (seconds >= 1) {
|
||||
return seconds.round() == 1 ? '1 second' : '${seconds.round()} seconds';
|
||||
}
|
||||
|
||||
if (inMilliseconds != 0) {
|
||||
return inMilliseconds == 1 ? '1 millisecond' : '$inSeconds milliseconds';
|
||||
}
|
||||
|
||||
return inMicroseconds == 1
|
||||
? '1 microsecond'
|
||||
: '$inMicroseconds microseconds';
|
||||
}
|
||||
}
|
||||
|
|
132
pubspec.lock
132
pubspec.lock
|
@ -17,6 +17,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.4.1"
|
||||
analyzer_plugin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer_plugin
|
||||
sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.3"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -85,10 +93,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "1414d6d733a85d8ad2f1dfcb3ea7945759e35a123cb99ccfac75d0758f75edfa"
|
||||
sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.10"
|
||||
version: "2.4.11"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -161,6 +169,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
ci:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ci
|
||||
sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -241,6 +265,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
custom_lint:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: custom_lint
|
||||
sha256: "7c0aec12df22f9082146c354692056677f1e70bc43471644d1fdb36c6fdda799"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.4"
|
||||
custom_lint_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint_builder
|
||||
sha256: d7dc41e709dde223806660268678be7993559e523eb3164e2a1425fd6f7615a9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.4"
|
||||
custom_lint_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint_core
|
||||
sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.3"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -414,6 +462,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -504,6 +560,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.1"
|
||||
freezed_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: freezed_annotation
|
||||
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -560,6 +624,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
hotreloader:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hotreloader
|
||||
sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
html:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1056,6 +1128,46 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
riverpod_analyzer_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod_analyzer_utils
|
||||
sha256: "8b71f03fc47ae27d13769496a1746332df4cec43918aeba9aff1e232783a780f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
riverpod_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: riverpod_annotation
|
||||
sha256: e5e796c0eba4030c704e9dae1b834a6541814963292839dcf9638d53eba84f5c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.5"
|
||||
riverpod_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_generator
|
||||
sha256: d451608bf17a372025fc36058863737636625dfdb7e3cbf6142e0dfeb366ab22
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
riverpod_lint:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_lint
|
||||
sha256: "3c67c14ccd16f0c9d53e35ef70d06cd9d072e2fb14557326886bbde903b230a5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.10"
|
||||
rxdart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1261,6 +1373,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
state_notifier:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: state_notifier
|
||||
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1565,6 +1685,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
wheel_chooser:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: wheel_chooser
|
||||
sha256: "3fee36f081f321c58a0b7b4afcdd92599f2ca520b3a1420084774e6b19cca1d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
17
pubspec.yaml
17
pubspec.yaml
|
@ -20,14 +20,19 @@ dependencies:
|
|||
file_picker: ^8.0.6
|
||||
flutter_dotenv: ^5.1.0
|
||||
flutter_file_dialog: ^3.0.2
|
||||
flutter_riverpod: ^2.5.1
|
||||
flutter_secure_storage: ^9.2.2
|
||||
flutter_svg: ^2.0.10+1
|
||||
flutter_web_auth_2: ^3.1.2
|
||||
flutter_widget_from_html_core: ^0.15.1
|
||||
get_it: ^7.7.0
|
||||
get_it_mixin: ^4.2.2
|
||||
go_router: ^14.1.2
|
||||
html: ^0.15.4
|
||||
http: any
|
||||
http_parser: any
|
||||
image: ^4.2.0
|
||||
image_gallery_saver: ^2.0.3
|
||||
image_picker: ^1.1.2
|
||||
logging: ^1.2.0
|
||||
markdown: ^7.2.2
|
||||
|
@ -43,6 +48,7 @@ dependencies:
|
|||
path_provider: ^2.1.3
|
||||
provider: ^6.1.2
|
||||
result_monad: ^2.3.2
|
||||
riverpod_annotation: ^2.3.5
|
||||
scrollable_positioned_list: ^0.3.8
|
||||
shared_preferences: ^2.2.3
|
||||
sqlite3: ^2.4.3
|
||||
|
@ -52,18 +58,17 @@ dependencies:
|
|||
url_launcher: ^6.3.0
|
||||
uuid: ^4.4.2
|
||||
video_player: ^2.9.1
|
||||
flutter_svg: ^2.0.10+1
|
||||
image_gallery_saver: ^2.0.3
|
||||
wheel_chooser: ^1.1.2
|
||||
|
||||
|
||||
http: any
|
||||
http_parser: any
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^4.0.0
|
||||
build_runner: ^2.4.10
|
||||
build_runner: ^2.4.11
|
||||
objectbox_generator: ^4.0.1
|
||||
riverpod_generator: ^2.4.0
|
||||
custom_lint: ^0.6.4
|
||||
riverpod_lint: ^2.3.10
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
|
Loading…
Reference in a new issue