Skip to content

Commit

Permalink
feat: re-implement theme service with Riverpod
Browse files Browse the repository at this point in the history
  • Loading branch information
robert-virkus committed Oct 7, 2023
1 parent e739166 commit 347e535
Show file tree
Hide file tree
Showing 20 changed files with 382 additions and 276 deletions.
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ buildscript {
ext.kotlin_version = '1.8.10'
ext {
minSdkVersion = 21 // or higher
compileSdkVersion = 33 // or higher
targetSdkVersion = 33 // or higher
compileSdkVersion = 34 // or higher
targetSdkVersion = 34 // or higher
appCompatVersion = "1.2.0" // or higher
}
repositories {
Expand Down
3 changes: 2 additions & 1 deletion lib/account/providers.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:enough_mail_app/models/account.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../models/account.dart';

part 'providers.g.dart';

/// Retrieves the list of real accounts
Expand Down
6 changes: 6 additions & 0 deletions lib/app_lifecycle/provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

/// Allows to retrieve the current app life cycle
final appLifecycleStateProvider =
StateProvider<AppLifecycleState>((ref) => AppLifecycleState.resumed);
2 changes: 0 additions & 2 deletions lib/locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import 'services/navigation_service.dart';
import 'services/notification_service.dart';
import 'services/providers.dart';
import 'services/scaffold_messenger_service.dart';
import 'services/theme_service.dart';

GetIt locator = GetIt.instance;

Expand All @@ -35,7 +34,6 @@ void setupLocator() {
..registerLazySingleton(NotificationService.new)
..registerLazySingleton(BackgroundService.new)
..registerLazySingleton(AppService.new)
..registerLazySingleton(ThemeService.new)
..registerLazySingleton(LocationService.new)
..registerLazySingleton(ContactService.new)
..registerLazySingleton(KeyService.new)
Expand Down
6 changes: 5 additions & 1 deletion lib/logger.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import 'package:logger/logger.dart';

/// Global logger
final logger = Logger();
final logger = Logger(
level: Level.debug,
output: ConsoleOutput(),
printer: PrettyPrinter(),
);
185 changes: 96 additions & 89 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import 'dart:io';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:enough_platform_widgets/enough_platform_widgets.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../l10n/app_localizations.g.dart';
import 'app_lifecycle/provider.dart';
import 'l10n/extension.dart';
import 'locator.dart';
import 'logger.dart';
import 'routes.dart';
import 'screens/all_screens.dart';
import 'services/app_service.dart';
Expand All @@ -21,8 +22,8 @@ import 'services/mail_service.dart';
import 'services/navigation_service.dart';
import 'services/notification_service.dart';
import 'services/scaffold_messenger_service.dart';
import 'services/theme_service.dart';
import 'settings/provider.dart';
import 'settings/theme/provider.dart';
import 'widgets/inherited_widgets.dart';
// AppStyles appStyles = AppStyles.instance;

Expand All @@ -44,8 +45,6 @@ class MyApp extends ConsumerStatefulWidget {

class _MyAppState extends ConsumerState<MyApp> with WidgetsBindingObserver {
late Future<MailService> _appInitialization;
ThemeMode _themeMode = ThemeMode.system;
ThemeService? _themeService;
Locale? _locale;
bool _isInitialized = false;

Expand All @@ -67,18 +66,16 @@ class _MyAppState extends ConsumerState<MyApp> with WidgetsBindingObserver {
if (_isInitialized) {
final settings = ref.read(settingsProvider);
locator<AppService>().didChangeAppLifecycleState(state, settings);
ref.read(appLifecycleStateProvider.notifier).state = state;
}
}

Future<MailService> _initApp() async {
await ref.read(settingsProvider.notifier).init();
if (context.mounted) {
ref.read(themeProvider.notifier).init(context);
}
final settings = ref.read(settingsProvider);
final themeService = locator<ThemeService>();
_themeService = themeService;
themeService.addListener(() => setState(() {
_themeMode = themeService.themeMode;
}));
themeService.init(settings);
final i18nService = locator<I18nService>();
final languageTag = settings.languageTag;
if (languageTag != null) {
Expand All @@ -101,8 +98,9 @@ class _MyAppState extends ConsumerState<MyApp> with WidgetsBindingObserver {
if (mailService.messageSource != null) {
final state = MailServiceWidget.of(context);
if (state != null) {
state.account = mailService.currentAccount;
state.accounts = mailService.accounts;
state
..account = mailService.currentAccount
..accounts = mailService.accounts;
}
// on ios show the app drawer:
if (Platform.isIOS) {
Expand All @@ -111,10 +109,12 @@ class _MyAppState extends ConsumerState<MyApp> with WidgetsBindingObserver {
}

/// the app has at least one configured account
await locator<NavigationService>().push(Routes.messageSource,
arguments: mailService.messageSource,
fade: true,
replace: !Platform.isIOS);
unawaited(locator<NavigationService>().push(
Routes.messageSource,
arguments: mailService.messageSource,
fade: true,
replace: !Platform.isIOS,
));
// check for a tapped notification that started the app:
final notificationInitResult =
await locator<NotificationService>().init();
Expand All @@ -124,7 +124,7 @@ class _MyAppState extends ConsumerState<MyApp> with WidgetsBindingObserver {
await locator<AppService>().checkForShare();
}
if (settings.enableBiometricLock) {
await locator<NavigationService>().push(Routes.lockScreen);
unawaited(locator<NavigationService>().push(Routes.lockScreen));
final didAuthenticate =
await locator<BiometricsService>().authenticate();
if (didAuthenticate) {
Expand All @@ -133,85 +133,92 @@ class _MyAppState extends ConsumerState<MyApp> with WidgetsBindingObserver {
}
} else {
// this app has no mail accounts yet, so switch to welcome screen:
await locator<NavigationService>()
.push(Routes.welcome, fade: true, replace: true);
unawaited(locator<NavigationService>()
.push(Routes.welcome, fade: true, replace: true));
}
if (BackgroundService.isSupported) {
await locator<BackgroundService>().init();
}
logger.d('App initialized');
_isInitialized = true;

return mailService;
}

@override
Widget build(BuildContext context) => PlatformSnackApp(
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
locale: _locale,
debugShowCheckedModeBanner: false,
title: 'Maily',
onGenerateRoute: AppRouter.generateRoute,
initialRoute: Routes.splash,
navigatorKey: locator<NavigationService>().navigatorKey,
scaffoldMessengerKey:
locator<ScaffoldMessengerService>().scaffoldMessengerKey,
builder: (context, child) {
locator<I18nService>().init(
context.text,
Localizations.localeOf(context),
);
child ??= FutureBuilder<MailService>(
future: _appInitialization,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const SplashScreen();
case ConnectionState.done:
// in the meantime the app has navigated away
break;
}
return Container();
},
);
final mailService = locator<MailService>();
return MailServiceWidget(
account: mailService.currentAccount,
accounts: mailService.accounts,
messageSource: mailService.messageSource,
child: child,
);
},
// home: Builder(
// builder: (context) {
// locator<I18nService>().init(
// context.text!, Localizations.localeOf(context));
// return FutureBuilder<MailService>(
// future: _appInitialization,
// builder: (context, snapshot) {
// switch (snapshot.connectionState) {
// case ConnectionState.none:
// case ConnectionState.waiting:
// case ConnectionState.active:
// return SplashScreen();
// case ConnectionState.done:
// // in the meantime the app has navigated away
// break;
// }
// return Container();
// },
// );
// },
// ),
materialTheme:
_themeService?.lightTheme ?? ThemeService.defaultLightTheme,
materialDarkTheme:
_themeService?.darkTheme ?? ThemeService.defaultDarkTheme,
materialThemeMode: _themeMode,
cupertinoTheme: const CupertinoThemeData(
brightness: Brightness.light,
//TODO support theming on Cupertino
),
);
Widget build(BuildContext context) {
final themeSettingsData = ref.watch(themeProvider);

return PlatformSnackApp(
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
locale: _locale,
debugShowCheckedModeBanner: false,
title: 'Maily',
onGenerateRoute: AppRouter.generateRoute,
initialRoute: Routes.splash,
navigatorKey: locator<NavigationService>().navigatorKey,
scaffoldMessengerKey:
locator<ScaffoldMessengerService>().scaffoldMessengerKey,
builder: (context, child) {
locator<I18nService>().init(
context.text,
Localizations.localeOf(context),
);
child ??= FutureBuilder<MailService>(
future: _appInitialization,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return const SplashScreen();
case ConnectionState.done:
// in the meantime the app has navigated away
break;
}

return const SizedBox.shrink();
},
);

final mailService = locator<MailService>();

return MailServiceWidget(
account: mailService.currentAccount,
accounts: mailService.accounts,
messageSource: mailService.messageSource,
child: child,
);
},
// home: Builder(
// builder: (context) {
// locator<I18nService>().init(
// context.text!, Localizations.localeOf(context));
// return FutureBuilder<MailService>(
// future: _appInitialization,
// builder: (context, snapshot) {
// switch (snapshot.connectionState) {
// case ConnectionState.none:
// case ConnectionState.waiting:
// case ConnectionState.active:
// return SplashScreen();
// case ConnectionState.done:
// // in the meantime the app has navigated away
// break;
// }
// return Container();
// },
// );
// },
// ),
materialTheme: themeSettingsData.lightTheme,
materialDarkTheme: themeSettingsData.darkTheme,
materialThemeMode: themeSettingsData.themeMode,
cupertinoTheme: CupertinoThemeData(
brightness: themeSettingsData.brightness,
//TODO support theming on Cupertino
),
);
}
}
1 change: 1 addition & 0 deletions lib/models/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class RealAccount extends Account {
}
}
}

return signature;
}

Expand Down
1 change: 0 additions & 1 deletion lib/models/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ export 'search.dart';
export 'sender.dart';
export 'shared_data.dart';
export 'swipe.dart';
export 'theme_settings.dart';
export 'web_view_configuration.dart';
16 changes: 10 additions & 6 deletions lib/services/app_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

import '../locator.dart';
import '../logger.dart';
import '../models/compose_data.dart';
import '../models/shared_data.dart';
import '../routes.dart';
Expand All @@ -14,7 +15,6 @@ import 'background_service.dart';
import 'biometrics_service.dart';
import 'mail_service.dart';
import 'navigation_service.dart';
import 'theme_service.dart';

/// Handles app life cycle events
class AppService {
Expand Down Expand Up @@ -44,21 +44,21 @@ class AppService {
AppLifecycleState state,
Settings settings,
) async {
if (kDebugMode) {
print('AppLifecycleState = $state');
}
logger.d('didChangeAppLifecycleState: $state');
appLifecycleState = state;
switch (state) {
case AppLifecycleState.resumed:
locator<ThemeService>().checkForChangedTheme();
//locator<ThemeService>().checkForChangedTheme();
final futures = [checkForShare(), locator<MailService>().resume()];
if (settings.enableBiometricLock) {
if (_ignoreBiometricsCheckAtNextResume) {
_ignoreBiometricsCheckAtNextResume = false;
// double check time stamp, everything more than a minute requires a check
// double check time stamp,
// everything more than a minute requires a check
if (_ignoreBiometricsCheckAtNextResumeTS
.isAfter(DateTime.now().subtract(const Duration(minutes: 1)))) {
await Future.wait(futures);

return;
}
}
Expand All @@ -75,6 +75,7 @@ class AppService {
if (navService.currentRouteName != Routes.lockScreen) {
await navService.push(Routes.lockScreen);
}

return;
} else if (navService.currentRouteName == Routes.lockScreen) {
navService.pop();
Expand All @@ -93,6 +94,9 @@ class AppService {
case AppLifecycleState.detached:
// TODO: Check if AppLifecycleState.detached needs to be handled
break;
case AppLifecycleState.hidden:
// TODO: Handle this case.
break;
}
}

Expand Down
Loading

0 comments on commit 347e535

Please sign in to comment.