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 124b98c..460ae95 100644 --- a/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_bloc.dart +++ b/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_bloc.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc_advance/configuration/app_logger.dart'; +import 'package:flutter_bloc_advance/configuration/local_storage.dart'; import '../../../../data/models/menu.dart'; import '../../../../data/repository/login_repository.dart'; @@ -26,6 +27,21 @@ class DrawerBloc extends Bloc { on(_loadMenus); on(_refreshMenus); on(_onLogout); + on(_onChangeLanguage); + } + + FutureOr _onChangeLanguage(ChangeLanguageEvent event, Emitter emit) async { + _log.debug("BEGIN: onChangeLanguage ChangeLanguageEvent event: {}", []); + emit(const DrawerState(isLogout: false, status: DrawerStateStatus.loading)); + try { + await AppLocalStorage().save(StorageKeys.language.name, event.language); + emit(DrawerLanguageChanged(language: event.language)); + emit(state.copyWith(language: event.language, status: DrawerStateStatus.success)); + _log.debug("END:onChangeLanguage ChangeLanguageEvent event success: {}", [event.language]); + } catch (e) { + emit(const DrawerState(isLogout: false, status: DrawerStateStatus.error)); + _log.error("END:onChangeLanguage ChangeLanguageEvent event error: {}", [e.toString()]); + } } FutureOr _onLogout(Logout event, Emitter emit) async { 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 9bc71f9..69e6bbb 100644 --- a/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_event.dart +++ b/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_event.dart @@ -12,3 +12,12 @@ class Logout extends DrawerEvent {} class LoadMenus extends DrawerEvent {} class RefreshMenus extends DrawerEvent {} + +class ChangeLanguageEvent extends DrawerEvent { + final String language; + + const ChangeLanguageEvent({required this.language}); + + @override + List get props => [language]; +} 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 71c3718..d8b7f65 100644 --- a/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_state.dart +++ b/lib/presentation/common_widgets/drawer/drawer_bloc/drawer_state.dart @@ -6,22 +6,26 @@ class DrawerState extends Equatable { final List menus; final bool isLogout; final DrawerStateStatus status; + final String language; const DrawerState({ this.menus = const [], this.isLogout = false, this.status = DrawerStateStatus.initial, + this.language='en', }); DrawerState copyWith({ List? menus, bool? isLogout, DrawerStateStatus? status, + String? language, }) { return DrawerState( menus: menus ?? this.menus, isLogout: isLogout ?? this.isLogout, status: status ?? this.status, + language: language ?? this.language, ); } @@ -45,4 +49,8 @@ class DrawerStateError extends DrawerState { final String message; const DrawerStateError({required this.message}) : super(status: DrawerStateStatus.error); +} + +class DrawerLanguageChanged extends DrawerState { + const DrawerLanguageChanged({required super.language}) : 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 fede963..85d203e 100644 --- a/lib/presentation/common_widgets/drawer/drawer_widget.dart +++ b/lib/presentation/common_widgets/drawer/drawer_widget.dart @@ -4,12 +4,13 @@ import 'package:flutter_bloc/flutter_bloc.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/data/models/menu.dart'; +import 'package:flutter_bloc_advance/generated/l10n.dart'; +import 'package:flutter_bloc_advance/presentation/common_blocs/account/account.dart'; import 'package:flutter_bloc_advance/routes/app_router.dart'; import 'package:flutter_bloc_advance/routes/app_routes_constants.dart'; +import 'package:go_router/go_router.dart'; import 'package:string_2_icon/string_2_icon.dart'; -import '../../../generated/l10n.dart'; -import '../../common_blocs/account/account.dart'; import 'drawer_bloc/drawer_bloc.dart'; class ApplicationDrawer extends StatelessWidget { @@ -18,24 +19,78 @@ class ApplicationDrawer extends StatelessWidget { @override Widget build(BuildContext context) { return MultiBlocListener( - listeners: _buildBlocListener(context), + listeners: [ + BlocListener( + listener: (context, state) { + if (state.isLogout) { + context.read().add(Logout()); + AppRouter().push(context, ApplicationRoutesConstants.login); + } + }, + ), + BlocListener( + listener: (context, state) { + if (state.status == AccountStatus.failure) { + context.read().add(Logout()); + AppRouter().push(context, ApplicationRoutesConstants.login); + } + }, + ), + ], child: BlocBuilder( builder: (context, state) { - if (state.menus.isEmpty) { - return const Center(child: Text('No menu found')); - } + final isDarkMode = AdaptiveTheme.of(context).mode.isDark; + var isEnglish = state.language == 'en'; + var lang = isEnglish ? 'en' : 'tr'; + // if (state.menus.isEmpty) { + // return Container(); + // } final menuNodes = state.menus.where((e) => e.level == 1 && e.active).toList() ..sort((a, b) => a.orderPriority.compareTo(b.orderPriority)); return Drawer( + key: Key("drawer-${state.language}"), child: SingleChildScrollView( child: Column( children: [ _buildMenuList(menuNodes, state), const SizedBox(height: 20), - const ThemeSwitchButton(), + SwitchListTile( + key: const Key("drawer-switch-theme"), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Icon(isDarkMode ? Icons.dark_mode : Icons.light_mode), + ], + ), + value: isDarkMode, + onChanged: (value) { + if (value) { + AdaptiveTheme.of(context).setDark(); + } else { + AdaptiveTheme.of(context).setLight(); + } + }, + ), const SizedBox(height: 20), - const LanguageSwitchButton(), + SwitchListTile( + key: const Key("drawer-switch-language"), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text(isEnglish ? S.of(context).english : S.of(context).turkish)], + ), + value: isEnglish, + onChanged: (value) async { + final newLang = value ? 'en' : 'tr'; + context.read().add(ChangeLanguageEvent(language: newLang)); + + await S.load(Locale(newLang)); + Navigator.of(context).pop(); + Scaffold.of(context).closeDrawer(); + context.go(ApplicationRoutesConstants.home); + + }, + ), const SizedBox(height: 20), _buildLogoutButton(context), ], @@ -54,9 +109,9 @@ class ApplicationDrawer extends StatelessWidget { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { - debugPrint("menuNodes.length: ${menuNodes.length}"); + //debugPrint("menuNodes.length: ${menuNodes.length}"); final node = menuNodes[index]; - debugPrint("node: ${node.name}"); + // debugPrint("node: ${node.name}"); if (!_hasAccess(node, currentUserRoles)) { return const SizedBox.shrink(); @@ -67,20 +122,20 @@ class ApplicationDrawer extends StatelessWidget { ..sort((a, b) => a.orderPriority.compareTo(b.orderPriority)); if (childMenus.isEmpty) { - debugPrint("childMenus.isEmpty "); + // debugPrint("childMenus.isEmpty "); // if child menu is leaf, add click event return ListTile( leading: Icon(String2Icon.getIconDataFromString(node.icon)), title: Text(S.of(context).translate_menu_title(node.name), style: Theme.of(context).textTheme.bodyMedium), onTap: () { - debugPrint("parent Menu: ${node.name}"); + // debugPrint("parent Menu: ${node.name}"); if (node.leaf && node.url.isNotEmpty) { AppRouter().push(context, node.url); } }, ); } else { - debugPrint("childMenus.isNotEmpty : ${childMenus.toString()}"); + // debugPrint("childMenus.isNotEmpty : ${childMenus.toString()}"); // if menu is not leaf, use ExpansionTile for child menus return ExpansionTile( leading: Icon(String2Icon.getIconDataFromString(node.icon)), @@ -90,7 +145,7 @@ class ApplicationDrawer extends StatelessWidget { leading: Icon(String2Icon.getIconDataFromString(childMenu.icon)), title: Text(S.of(context).translate_menu_title(childMenu.name), style: Theme.of(context).textTheme.bodySmall), onTap: () { - debugPrint("child menu name: ${childMenu.name}"); + // debugPrint("child menu name: ${childMenu.name}"); if (childMenu.leaf! && childMenu.url.isNotEmpty) { AppRouter().push(context, childMenu.url); } @@ -120,27 +175,6 @@ class ApplicationDrawer extends StatelessWidget { ); } - _buildBlocListener(BuildContext context) { - return [ - BlocListener( - listener: (context, state) { - if (state.isLogout) { - context.read().add(Logout()); - AppRouter().push(context, ApplicationRoutesConstants.login); - } - }, - ), - BlocListener( - listener: (context, state) { - if (state.status == AccountStatus.failure) { - context.read().add(Logout()); - AppRouter().push(context, ApplicationRoutesConstants.login); - } - }, - ), - ]; - } - Future logOutDialog(BuildContext context) { return showDialog( context: context, @@ -180,82 +214,7 @@ class ApplicationDrawer extends StatelessWidget { } bool _hasAccess(Menu menu, List? userRoles) { - if(userRoles == null) return false; + if (userRoles == null) return false; final menuAuthorities = menu.authorities ?? []; return menuAuthorities.any((authority) => userRoles.contains(authority)); } - -class ThemeSwitchButton extends StatelessWidget { - const ThemeSwitchButton({super.key}); - - @override - Widget build(BuildContext context) { - final isDarkMode = AdaptiveTheme.of(context).mode.isDark; - - return SwitchListTile( - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Icon(isDarkMode ? Icons.dark_mode : Icons.light_mode), - ], - ), - value: isDarkMode, - onChanged: (value) { - if (value) { - AdaptiveTheme.of(context).setDark(); - } else { - AdaptiveTheme.of(context).setLight(); - } - }, - ); - } -} - -class LanguageSwitchButton extends StatefulWidget { - const LanguageSwitchButton({super.key}); - - @override - LanguageSwitchButtonState createState() => LanguageSwitchButtonState(); -} - -class LanguageSwitchButtonState extends State { - bool isTurkish = true; - - @override - void initState() { - super.initState(); - _loadLanguage(); - } - - Future _loadLanguage() async { - final lang = await AppLocalStorage().read(StorageKeys.language.name); - setState(() { - isTurkish = lang == 'tr'; - }); - } - - @override - Widget build(BuildContext context) { - return SwitchListTile( - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(isTurkish ? S.of(context).turkish : S.of(context).english), - ], - ), - value: isTurkish, - onChanged: (value) async { - isTurkish = value; - - final lang = isTurkish ? 'tr' : 'en'; - await AppLocalStorage().save(StorageKeys.language.name, lang); - await S.load(Locale(isTurkish ? 'tr' : 'en')); - if (mounted) { - setState( - () => AppRouter().push(context, ApplicationRoutesConstants.home), - ); - } - }, - ); - } -} diff --git a/test/presentation/screen/home/home_screen_test.dart b/test/presentation/screen/home/home_screen_test.dart index 4fcda8a..bbd69df 100644 --- a/test/presentation/screen/home/home_screen_test.dart +++ b/test/presentation/screen/home/home_screen_test.dart @@ -58,8 +58,8 @@ void main() { debugPrint("Menu list Testing"); // Menu Test expect(find.byType(Drawer), findsOneWidget); - expect(find.byType(ThemeSwitchButton), findsOneWidget); - expect(find.byType(LanguageSwitchButton), findsOneWidget); + expect(find.byKey(const Key("drawer-switch-theme")), findsOneWidget); + expect(find.byKey(const Key("drawer-switch-language")), findsOneWidget); expect(find.text("Logout"), findsOneWidget); expect(find.text("Account"), findsOneWidget); expect(find.text("Settings"), findsOneWidget); @@ -76,17 +76,23 @@ void main() { debugPrint("storage tested"); // language test - final langFinder = find.byType(LanguageSwitchButton); + debugPrint("language Testing"); + final langFinder = find.byKey(const Key("drawer-switch-language")); + debugPrint("language Testing - langFinder"); await tester.tap(langFinder); + debugPrint("language Testing - tap"); await tester.pumpAndSettle(const Duration(seconds: 5)); + debugPrint("language Testing - pumpAndSettle"); // open menu + await tester.tap(drawerButtonFinder); + debugPrint("language Testing - drawerButtonFinder"); await tester.pumpAndSettle(const Duration(seconds: 5)); - debugPrint("drawerButton PumpAndSettle"); + debugPrint("language Testing -drawerButton PumpAndSettle"); await tester.tap(langFinder); + debugPrint("language Testing - tap"); await tester.pumpAndSettle(const Duration(seconds: 5)); - debugPrint("language tested"); ///////////////////////////////////////////////////////// @@ -96,7 +102,7 @@ void main() { debugPrint("drawerButton PumpAndSettle"); //theme test - final themeFinder = find.byType(ThemeSwitchButton); + final themeFinder = find.byKey(const Key("drawer-switch-theme")); await tester.tap(themeFinder); await tester.pumpAndSettle(const Duration(seconds: 5)); debugPrint("ThemeSwitchButton PumpAndSettle");