diff --git a/lib/configuration/local_storage.dart b/lib/configuration/local_storage.dart index 524910e..dade62a 100644 --- a/lib/configuration/local_storage.dart +++ b/lib/configuration/local_storage.dart @@ -3,6 +3,7 @@ // It uses shared preferences and get storage to store data locally. // It also contains the implementation of the cache for the application. +import 'package:adaptive_theme/adaptive_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc_advance/configuration/app_logger.dart'; import 'package:get_storage/get_storage.dart'; @@ -24,6 +25,7 @@ class AppLocalStorageCached { static late List? roles; static late String? language; static late String? username; + static late String? theme; static Future loadCache() async { _log.trace("Loading cache"); @@ -31,12 +33,14 @@ class AppLocalStorageCached { roles = await AppLocalStorage().read(StorageKeys.roles.name); language = await AppLocalStorage().read(StorageKeys.language.name) ?? "en"; username = await AppLocalStorage().read(StorageKeys.username.name); - _log.trace("Loaded cache with username:{}, roles:{}, language:{}, jwtToken:{}", [username, roles, language, jwtToken]); + theme = await AppLocalStorage().read(StorageKeys.theme.name) ?? AdaptiveThemeMode.light.name; + _log.trace("Loaded cache with username:{}, roles:{}, language:{}, jwtToken:{}, theme:{}", [username, roles, language, jwtToken, theme]); } } /// LocalStorage predefined keys -enum StorageKeys { jwtToken, roles, language, username } +enum StorageKeys { jwtToken, roles, language, username, theme } + /// Application Local Storage /// diff --git a/lib/main/app.dart b/lib/main/app.dart index 497c3fb..ca92aa6 100644 --- a/lib/main/app.dart +++ b/lib/main/app.dart @@ -46,9 +46,9 @@ class App extends StatelessWidget { ); } - ThemeData _buildDarkTheme() => ThemeData(brightness: Brightness.dark, primarySwatch: Colors.blueGrey); + ThemeData _buildDarkTheme() => ThemeData(useMaterial3: false, brightness: Brightness.dark, primarySwatch: Colors.blueGrey); - ThemeData _buildLightTheme() => ThemeData(brightness: Brightness.light, colorSchemeSeed: Colors.blueGrey); + ThemeData _buildLightTheme() => ThemeData(useMaterial3: false, brightness: Brightness.light, colorSchemeSeed: Colors.blueGrey); MultiBlocProvider _buildMultiBlocProvider(ThemeData light, ThemeData dark) { return MultiBlocProvider( diff --git a/lib/main/main_local.dart b/lib/main/main_local.dart index 17b6ae4..4017477 100644 --- a/lib/main/main_local.dart +++ b/lib/main/main_local.dart @@ -25,6 +25,7 @@ void main() async { initializeJsonMapper(); WidgetsFlutterBinding.ensureInitialized(); + //TODO change to the system language(browser language) const defaultLanguage = "en"; AppLocalStorage().setStorage(StorageType.sharedPreferences); await AppLocalStorage().save(StorageKeys.language.name, defaultLanguage); @@ -36,5 +37,10 @@ void main() async { SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]).then((_) { runApp(const App(language: defaultLanguage, initialTheme: initialTheme)); }); - log.info("Started App with local environment language: {} and theme: {}", [defaultLanguage, initialTheme.name]); + + //TODO change to the system theme(browser theme) + final defaultThemeName = initialTheme.name; + await AppLocalStorage().save(StorageKeys.theme.name, defaultThemeName); + + log.info("Started App with local environment language: {} and theme: {}", [defaultLanguage, defaultThemeName]); } diff --git a/lib/main/main_prod.dart b/lib/main/main_prod.dart index 8db44cc..dc1c521 100644 --- a/lib/main/main_prod.dart +++ b/lib/main/main_prod.dart @@ -36,5 +36,10 @@ void main() async { SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]).then((_) { runApp(const App(language: defaultLanguage, initialTheme: initialTheme)); }); - log.info("Started App with env: {} language: {} and theme: {}", [Environment.prod.name, defaultLanguage, initialTheme.name]); + + //TODO change to the system theme(browser theme) + final defaultThemeName = initialTheme.name; + await AppLocalStorage().save(StorageKeys.theme.name, defaultThemeName); + + log.info("Started App with local environment language: {} and theme: {}", [defaultLanguage, defaultThemeName]); } diff --git a/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_bloc.dart b/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_bloc.dart index eaf8aba..12c9539 100644 --- a/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_bloc.dart +++ b/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:adaptive_theme/adaptive_theme.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -30,6 +31,20 @@ class DrawerBloc extends Bloc { on(_refreshMenus); on(_onLogout); on(_onChangeLanguage); + on(_onChangeTheme); + } + + FutureOr _onChangeTheme(ChangeThemeEvent event, Emitter emit) async { + _log.debug("BEGIN: onChangeTheme ChangeThemeEvent event: {}", []); + emit(state.copyWith(theme: event.theme, status: DrawerStateStatus.loading)); + try { + await AppLocalStorage().save(StorageKeys.theme.name, event.theme.name); + emit(state.copyWith(theme: event.theme, status: DrawerStateStatus.success)); + _log.debug("END:onChangeTheme ChangeThemeEvent event success: {}", [event.theme]); + } catch (e) { + emit(state.copyWith(theme: event.theme, status: DrawerStateStatus.error)); + _log.error("END:onChangeTheme ChangeThemeEvent event error: {}", [e.toString()]); + } } FutureOr _onChangeLanguage(ChangeLanguageEvent event, Emitter emit) async { @@ -64,7 +79,12 @@ class DrawerBloc extends Bloc { FutureOr _loadMenus(LoadMenus event, Emitter emit) async { _log.debug("BEGIN: loadMenus LoadMenus event: {}", []); - emit(state.copyWith(menus: [], status: DrawerStateStatus.loading, language: event.language)); + emit(state.copyWith( + menus: [], + status: DrawerStateStatus.loading, + language: event.language, + theme: event.theme, + )); try { if (MenuListCache.menus.isNotEmpty) { emit(state.copyWith(menus: MenuListCache.menus, status: DrawerStateStatus.success)); @@ -72,7 +92,7 @@ class DrawerBloc extends Bloc { return; } final menus = await _menuRepository.getMenus(); - if(menus.isEmpty) { + if (menus.isEmpty) { emit(state.copyWith(menus: menus, status: DrawerStateStatus.error)); return; } diff --git a/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_event.dart b/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_event.dart index e5508b0..e5772c0 100644 --- a/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_event.dart +++ b/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_event.dart @@ -11,11 +11,12 @@ class Logout extends DrawerEvent {} class LoadMenus extends DrawerEvent { final String language; + final AdaptiveThemeMode theme; - const LoadMenus({required this.language}); + const LoadMenus({required this.language, required this.theme}); @override - List get props => [language]; + List get props => [language, theme]; } class RefreshMenus extends DrawerEvent {} @@ -28,3 +29,12 @@ class ChangeLanguageEvent extends DrawerEvent { @override List get props => [language]; } + +class ChangeThemeEvent extends DrawerEvent { + final AdaptiveThemeMode theme; + + const ChangeThemeEvent({required this.theme}); + + @override + List get props => [theme]; +} \ No newline at end of file diff --git a/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_state.dart b/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_state.dart index ba26cbd..aa49963 100644 --- a/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_state.dart +++ b/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_state.dart @@ -7,12 +7,14 @@ class DrawerState extends Equatable { final bool isLogout; final DrawerStateStatus status; final String? language; + final AdaptiveThemeMode? theme; const DrawerState({ this.menus = const [], this.isLogout = false, this.status = DrawerStateStatus.initial, this.language, + this.theme, }); DrawerState copyWith({ @@ -20,12 +22,14 @@ class DrawerState extends Equatable { bool? isLogout, DrawerStateStatus? status, String? language, + AdaptiveThemeMode? theme, }) { return DrawerState( menus: menus ?? this.menus, isLogout: isLogout ?? this.isLogout, status: status ?? this.status, language: language ?? this.language, + theme: theme ?? this.theme, ); } @@ -33,6 +37,7 @@ class DrawerState extends Equatable { List get props => [status, menus, isLogout]; } +//TODO add default language and theme class DrawerStateInitial extends DrawerState { const DrawerStateInitial() : super(status: DrawerStateStatus.initial); } @@ -53,4 +58,8 @@ class DrawerStateError extends DrawerState { class DrawerLanguageChanged extends DrawerState { const DrawerLanguageChanged({required super.language}) : super(status: DrawerStateStatus.success); +} + +class DrawerThemeChanged extends DrawerState { + const DrawerThemeChanged({required super.theme}) : super(status: DrawerStateStatus.success); } \ No newline at end of file diff --git a/lib/presentation/common_widgets/drawer/drawer_widget.dart b/lib/presentation/common_widgets/drawer/drawer_widget.dart index 2571ea8..632e7f1 100644 --- a/lib/presentation/common_widgets/drawer/drawer_widget.dart +++ b/lib/presentation/common_widgets/drawer/drawer_widget.dart @@ -16,6 +16,7 @@ class ApplicationDrawer extends StatelessWidget { @override Widget build(BuildContext context) { + debugPrint("ApplicationDrawer build"); return MultiBlocListener( listeners: [ BlocListener( @@ -39,10 +40,14 @@ class ApplicationDrawer extends StatelessWidget { ], child: BlocBuilder( builder: (context, state) { - final isDarkMode = AdaptiveTheme.of(context).mode.isDark; + final isDarkMode = state.theme == AdaptiveThemeMode.dark; - //debugPrint("BUILDER - current language : ${AppLocalStorageCached.language}"); - //debugPrint("BUILDER - state language : ${state.language}"); + // debugPrint("BUILDER - current lang : ${AppLocalStorageCached.language}"); + // debugPrint("BUILDER - state lang : ${state.language}"); + // + // + // debugPrint("BUILDER - current theme : ${AppLocalStorageCached.theme}"); + // debugPrint("BUILDER - state theme : ${state.theme}"); var isEnglish = state.language == 'en'; @@ -50,7 +55,7 @@ class ApplicationDrawer extends StatelessWidget { ..sort((a, b) => a.orderPriority.compareTo(b.orderPriority)); return Drawer( - key: Key("drawer-${state.language}"), + key: Key("drawer-${state.language}-${state.theme}"), child: SingleChildScrollView( child: Column( children: [ @@ -60,17 +65,24 @@ class ApplicationDrawer extends StatelessWidget { key: const Key("drawer-switch-theme"), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Icon(isDarkMode ? Icons.dark_mode : Icons.light_mode), - ], + children: [Icon(isDarkMode ? Icons.dark_mode : Icons.light_mode)], ), value: isDarkMode, onChanged: (value) { + //debugPrint("BEGIN:ON_PRESSED.value - ${value}"); + final newTheme = value ? AdaptiveThemeMode.dark : AdaptiveThemeMode.light; + //debugPrint("BEGIN:ON_PRESSED - current theme : ${AppLocalStorageCached.theme}"); + //debugPrint("BEGIN:ON_PRESSED - current newTheme : ${newTheme}"); + context.read().add(ChangeThemeEvent(theme: newTheme)); if (value) { AdaptiveTheme.of(context).setDark(); } else { AdaptiveTheme.of(context).setLight(); } + Scaffold.of(context).closeDrawer(); + AppRouter().push(context, ApplicationRoutesConstants.home); + + //debugPrint("END:ON_PRESSED - current cached theme : ${AppLocalStorageCached.theme}"); }, ), const SizedBox(height: 20), @@ -81,7 +93,7 @@ class ApplicationDrawer extends StatelessWidget { children: [Text(isEnglish ? S.of(context).english : S.of(context).turkish)], ), value: isEnglish, - onChanged: (value) async { + onChanged: (value) { final newLang = value ? 'en' : 'tr'; context.read().add(ChangeLanguageEvent(language: newLang)); AppRouter().push(context, ApplicationRoutesConstants.home); diff --git a/lib/presentation/screen/home/home_screen.dart b/lib/presentation/screen/home/home_screen.dart index 7b51ef5..8b86350 100644 --- a/lib/presentation/screen/home/home_screen.dart +++ b/lib/presentation/screen/home/home_screen.dart @@ -23,7 +23,7 @@ class HomeScreen extends StatelessWidget { } Widget _buildBody(BuildContext context) { - //debugPrint("HomeScreen _buildBody"); + debugPrint("HomeScreen _buildBody theme: ${AppLocalStorageCached.theme}"); return BlocProvider( create: (context) { //debugPrint("HomeScreen account blocProvider"); @@ -41,7 +41,7 @@ class HomeScreen extends StatelessWidget { debugPrint("HomeScreen account bloc builder: ${state.status}"); if (state.status == AccountStatus.success) { return Scaffold( - appBar: AppBar(title: const Text(AppConstants.appName)), + appBar: AppBar(title: const Text(AppConstants.appName),), key: _scaffoldKey, body: Center(child: Column(children: [backgroundImage(context)])), drawer: _buildDrawer(context), @@ -97,9 +97,19 @@ class HomeScreen extends StatelessWidget { } Widget _buildDrawer(BuildContext context) { + debugPrint("HomeScreen _buildDrawer : init-theme ${AppLocalStorageCached.theme}"); + AdaptiveThemeMode initialAppThemeType; + if (AppLocalStorageCached.theme == 'light') { + initialAppThemeType = AdaptiveThemeMode.light; + } else { + initialAppThemeType = AdaptiveThemeMode.dark; + } + final initialAppLanguage = AppLocalStorageCached.language ?? 'en'; return BlocProvider( create: (context) => DrawerBloc(loginRepository: LoginRepository(), menuRepository: MenuRepository()) - ..add(LoadMenus(language: AppLocalStorageCached.language ?? 'en')), + ..add( + LoadMenus(language: initialAppLanguage, theme: initialAppThemeType), + ), child: const ApplicationDrawer(), ); } diff --git a/lib/routes/go_router_routes/user_routes.dart b/lib/routes/go_router_routes/user_routes.dart index c87c8b1..37ed033 100644 --- a/lib/routes/go_router_routes/user_routes.dart +++ b/lib/routes/go_router_routes/user_routes.dart @@ -6,7 +6,7 @@ import 'package:go_router/go_router.dart'; class UserRoutes { static final List routes = [ GoRoute(name: 'userList', path: '/user', builder: (context, state) => ListUserScreen()), - GoRoute(name: 'userNew', path: '/user/new', builder: (context, state) => CreateUserScreen()), + GoRoute(name: 'userNew', path: '/user/new', builder: (context, state) => CreateUserScreen()), // UserUpdateScreen(new, edit, view) GoRoute(name: 'userEdit', path: '/user/:id/edit', builder: (context, state) => EditUserScreen(id: state.pathParameters['id']!)), //GoRoute(name: 'userView' , path: '/user/:id/view', builder: (context, state) => EditViewScreen(id: state.pathParameters['id']!)), ]; diff --git a/test/presentation/blocs/drawer_bloc_test.dart b/test/presentation/blocs/drawer_bloc_test.dart index eb122a6..358fa93 100644 --- a/test/presentation/blocs/drawer_bloc_test.dart +++ b/test/presentation/blocs/drawer_bloc_test.dart @@ -1,3 +1,4 @@ +import 'package:adaptive_theme/adaptive_theme.dart'; import 'package:bloc_test/bloc_test.dart'; import 'package:flutter_bloc_advance/data/models/menu.dart'; import 'package:flutter_bloc_advance/data/repository/login_repository.dart'; @@ -55,12 +56,12 @@ void main() { /// Drawer Event Tests group("DrawerEvent", () { test("supports value comparisons", () { - expect(const LoadMenus(language:"en"), const LoadMenus(language: "en")); + expect(const LoadMenus(language:"en", theme: AdaptiveThemeMode.light), const LoadMenus(language: "en", theme: AdaptiveThemeMode.light)); expect(RefreshMenus(), RefreshMenus()); expect(Logout(), Logout()); }); test("props", () { - expect(const LoadMenus(language: "en").props, []); + expect(const LoadMenus(language: "en", theme: AdaptiveThemeMode.light).props, ['en', AdaptiveThemeMode.light]); expect(RefreshMenus().props, []); expect(Logout().props, []); }); @@ -76,7 +77,7 @@ void main() { }); const input = [Menu(id: "test", name: "test")]; final output = Future.value(input); - const event = LoadMenus(language: "en"); + const event = LoadMenus(language: "en", theme: AdaptiveThemeMode.light); const loadingState = DrawerState(menus: [], status: DrawerStateStatus.loading); const successState = DrawerState(menus: input, status: DrawerStateStatus.success); const failureState = DrawerState(menus: [], status: DrawerStateStatus.error); diff --git a/test/presentation/screen/home/home_screen_test.dart b/test/presentation/screen/home/home_screen_test.dart index bbd69df..d0348ad 100644 --- a/test/presentation/screen/home/home_screen_test.dart +++ b/test/presentation/screen/home/home_screen_test.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc_advance/configuration/app_key_constants.dart'; import 'package:flutter_bloc_advance/configuration/local_storage.dart'; import 'package:flutter_bloc_advance/main/app.dart'; -import 'package:flutter_bloc_advance/presentation/common_widgets/drawer/drawer_widget.dart'; import 'package:flutter_bloc_advance/presentation/screen/home/home_screen.dart'; import 'package:flutter_bloc_advance/presentation/screen/login/login_screen.dart'; import 'package:flutter_bloc_advance/utils/app_constants.dart';