diff --git a/lang/ja.json b/lang/ja.json index 9459b637f..ccef87351 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -279,6 +279,6 @@ "Organisation on different server, logout and scan qr again": "異なるサーバー上の組織です。ログアウトして、QRコードを再びスキャンしてください。", "Post was deleted if you had the rights!": "権限がある場合、投稿は削除されました!", "Members": " メンバー ", - "Remember me": "私を覚えてますか", + "Remember me": "ログイン情報を保存", "Login directly with": "直接ログイン" } \ No newline at end of file diff --git a/lib/view_model/pre_auth_view_models/login_view_model.dart b/lib/view_model/pre_auth_view_models/login_view_model.dart index c3ca4b7e6..4648ac053 100644 --- a/lib/view_model/pre_auth_view_models/login_view_model.dart +++ b/lib/view_model/pre_auth_view_models/login_view_model.dart @@ -154,14 +154,9 @@ class LoginViewModel extends BaseModel { result.data!['login'] as Map, ); userConfig.updateUser(loggedInUser); - await secureStorage.write(key: "userEmail", value: this.email.text); - await secureStorage.write( - key: "userPassword", - value: this.password.text, - ); } }, - apiCallSuccessUpdateUI: () { + apiCallSuccessUpdateUI: () async { // if user has not already joined any organization. if (userConfig.currentUser.joinedOrganizations!.isEmpty) { navigationService.removeAllAndPush( @@ -178,6 +173,19 @@ class LoginViewModel extends BaseModel { arguments: MainScreenArgs(mainScreenIndex: 0, fromSignUp: false), ); } + try { + await secureStorage.write( + key: "userEmail", + value: this.email.text, + ); + await secureStorage.write( + key: "userPassword", + value: this.password.text, + ); + } catch (e) { + // Handle secure storage write failure + print("Failed to save credentials: $e"); + } }, onActionException: (e) async { print('here'); diff --git a/lib/view_model/pre_auth_view_models/signup_details_view_model.dart b/lib/view_model/pre_auth_view_models/signup_details_view_model.dart index d76058fec..af29c8c07 100644 --- a/lib/view_model/pre_auth_view_models/signup_details_view_model.dart +++ b/lib/view_model/pre_auth_view_models/signup_details_view_model.dart @@ -156,12 +156,6 @@ class SignupDetailsViewModel extends BaseModel { final User signedInUser = User.fromJson( result.data!['signUp'] as Map, ); - - await secureStorage.write(key: "userEmail", value: this.email.text); - await secureStorage.write( - key: "userPassword", - value: this.password.text, - ); final bool userSaved = await userConfig.updateUser(signedInUser); final bool tokenRefreshed = await graphqlConfig.getToken() as bool; @@ -208,6 +202,19 @@ class SignupDetailsViewModel extends BaseModel { Routes.splashScreen, ); } + try { + await secureStorage.write( + key: "userEmail", + value: this.email.text, + ); + await secureStorage.write( + key: "userPassword", + value: this.password.text, + ); + } catch (e) { + // Handle secure storage write failure + print("Failed to save credentials: $e"); + } } } }, diff --git a/lib/views/after_auth_screens/app_settings/app_settings_page.dart b/lib/views/after_auth_screens/app_settings/app_settings_page.dart index f68fcda68..0abbfc65b 100644 --- a/lib/views/after_auth_screens/app_settings/app_settings_page.dart +++ b/lib/views/after_auth_screens/app_settings/app_settings_page.dart @@ -6,8 +6,8 @@ import 'package:talawa/services/size_config.dart'; import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/view_model/after_auth_view_models/settings_view_models/app_setting_view_model.dart'; import 'package:talawa/views/base_view.dart'; +import 'package:talawa/widgets/custom_alert_dialog_with_checkbox.dart'; import 'package:talawa/widgets/lang_switch.dart'; -import 'package:talawa/widgets/raised_round_edge_button.dart'; import 'package:talawa/widgets/theme_switch.dart'; /// Widget representing the App Settings page. @@ -24,9 +24,6 @@ class AppSettingsPage extends StatefulWidget { } class _AppSettingsPageState extends State { - /// This is used to check if to remember user credentials after logout. - bool _rememberMe = true; - /// Secure local storage instance. final secureStorage = const FlutterSecureStorage(); @@ -298,88 +295,27 @@ class _AppSettingsPageState extends State { ? showDialog( context: context, builder: (context) { - return StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return AlertDialog( - surfaceTintColor: Theme.of(context) - .colorScheme - .secondaryContainer, - title: Text( - AppLocalizations.of(context)! - .strictTranslate('Confirmation'), - style: Theme.of(context) - .textTheme - .headlineSmall! - .copyWith(fontWeight: FontWeight.w800), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - AppLocalizations.of(context)!.strictTranslate( - 'Are you sure you want to logout?', - ), - ), - Row( - children: [ - Text( - AppLocalizations.of(context)! - .strictTranslate("Remember me"), - ), - Checkbox( - value: _rememberMe, - onChanged: (bool? val) { - setState(() { - _rememberMe = val!; - }); - }, - ), - ], - ), - ], - ), - buttonPadding: EdgeInsets.symmetric( - horizontal: SizeConfig.screenWidth! * 0.05, - vertical: SizeConfig.screenHeight! * 0.05, - ), - actions: [ - RaisedRoundedButton( - key: const Key('Close'), - onTap: () { - navigationService.pop(); - }, - buttonLabel: AppLocalizations.of(context)! - .strictTranslate('Close'), - textColor: Colors.white, - backgroundColor: const Color(0xFF008A37), - width: SizeConfig.screenWidth! * 0.2, - height: SizeConfig.screenHeight! * 0.06, - ), - RaisedRoundedButton( - key: const Key('Logout'), - onTap: () async { - await model.logout(); - if (!_rememberMe) { - await secureStorage.delete( - key: "userEmail", - ); - await secureStorage.delete( - key: "userPassword", - ); - } - }, - buttonLabel: AppLocalizations.of(context)! - .strictTranslate('Logout'), - textColor: const Color(0xFF008A37), - backgroundColor: - Theme.of(context).colorScheme.tertiary, - width: SizeConfig.screenWidth! * 0.2, - height: SizeConfig.screenHeight! * 0.06, - ), - ], - ); + return CustomAlertDialogWithCheckbox( + success: (checkBoxVal) async { + await model.logout(); + if (checkBoxVal != null && checkBoxVal == false) { + try { + await secureStorage.delete( + key: "userEmail", + ); + await secureStorage.delete( + key: "userPassword", + ); + } catch (e) { + print("Unable to delete stored value : $e"); + } + } }, + reverse: true, + dialogSubTitle: "Are you sure you want to logout?", + checkboxLabel: "Remember me", + successText: "Logout", + initialCheckboxValue: true, ); }, ) diff --git a/lib/views/pre_auth_screens/login.dart b/lib/views/pre_auth_screens/login.dart index c91483c70..a700b159d 100644 --- a/lib/views/pre_auth_screens/login.dart +++ b/lib/views/pre_auth_screens/login.dart @@ -31,7 +31,6 @@ class _LoginState extends State { return BaseView( onModelReady: (model) { model.initialize(); - model.fetchPrevUser(); }, builder: (context, model, child) { return Scaffold( diff --git a/lib/widgets/custom_alert_dialog_with_checkbox.dart b/lib/widgets/custom_alert_dialog_with_checkbox.dart new file mode 100644 index 000000000..ad3ebe579 --- /dev/null +++ b/lib/widgets/custom_alert_dialog_with_checkbox.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:talawa/locator.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; + +/// This Class returns CustomAlertDialogWithCheckbox widget . Custom Alert Dialog with check box is a popup. +/// +/// that appeared in the screen showing Alert message along with the event buttons. +/// This extends stateful widget that means it cannot change its state during the +/// runtime of a application. +/// This has a check box which wil rewuire user input to change. +class CustomAlertDialogWithCheckbox extends StatefulWidget { + const CustomAlertDialogWithCheckbox({ + super.key, + this.successText = 'Confirm', + this.dialogTitle = 'Confirmation', + this.reverse = false, + this.secondaryButtonText = 'Close', + this.secondaryButtonTap, + required this.success, + required this.dialogSubTitle, + required this.checkboxLabel, + this.initialCheckboxValue = false, + this.onCheckboxChanged, + }); + + /// Indicates whether the order of action buttons should be reversed. + final bool reverse; + + /// Function triggered upon tapping the primary action button. + final Function(bool?) success; + + /// Function triggered upon tapping the secondary action button. + final Function()? secondaryButtonTap; + + /// Text displayed on the primary action button. + final String successText; + + /// Title displayed in the dialog (default value is 'Confirmation'). + final String dialogTitle; + + /// Subtitle or message content of the dialog. + final String dialogSubTitle; + + /// Text displayed on the secondary action button (default value is 'Close'). + final String secondaryButtonText; + + /// Label for the checkbox. + final String checkboxLabel; + + /// Initial value for the checkbox. + final bool initialCheckboxValue; + + /// Callback for checkbox value change. + final ValueChanged? onCheckboxChanged; + + @override + _CustomAlertDialogWithCheckboxState createState() => + _CustomAlertDialogWithCheckboxState(); +} + +class _CustomAlertDialogWithCheckboxState + extends State { + late bool _checkboxValue; + + @override + void initState() { + super.initState(); + _checkboxValue = widget.initialCheckboxValue; + } + + @override + Widget build(BuildContext context) { + final List actions = [ + RaisedRoundedButton( + key: Key(widget.secondaryButtonText), + onTap: () { + widget.secondaryButtonTap != null + ? widget.secondaryButtonTap!() + : navigationService.pop(); + }, + buttonLabel: AppLocalizations.of(context)! + .strictTranslate(widget.secondaryButtonText), + textColor: Colors.white, + backgroundColor: const Color(0xFF008A37), + width: SizeConfig.screenWidth! * 0.2, + height: SizeConfig.screenHeight! * 0.06, + ), + RaisedRoundedButton( + key: Key(widget.successText), + onTap: () => widget.success(_checkboxValue), + buttonLabel: + AppLocalizations.of(context)!.strictTranslate(widget.successText), + textColor: const Color(0xFF008A37), + backgroundColor: Theme.of(context).colorScheme.tertiary, + width: SizeConfig.screenWidth! * 0.2, + height: SizeConfig.screenHeight! * 0.06, + ), + ]; + + return AlertDialog( + surfaceTintColor: Theme.of(context).colorScheme.secondaryContainer, + title: Text( + AppLocalizations.of(context)!.strictTranslate(widget.dialogTitle), + style: Theme.of(context) + .textTheme + .headlineSmall! + .copyWith(fontWeight: FontWeight.w800), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + AppLocalizations.of(context)! + .strictTranslate(widget.dialogSubTitle), + ), + Row( + children: [ + Text( + AppLocalizations.of(context)! + .strictTranslate(widget.checkboxLabel), + ), + Checkbox( + value: _checkboxValue, + onChanged: (bool? val) { + setState(() { + _checkboxValue = val!; + }); + if (widget.onCheckboxChanged != null) { + widget.onCheckboxChanged?.call(_checkboxValue); + } + }, + ), + ], + ), + ], + ), + buttonPadding: EdgeInsets.symmetric( + horizontal: SizeConfig.screenWidth! * 0.05, + vertical: SizeConfig.screenHeight! * 0.05, + ), + actions: widget.reverse ? actions.reversed.toList() : actions, + ); + } +} diff --git a/test/widget_tests/widgets/custom_alert_dialog_with_checkbox_test.dart b/test/widget_tests/widgets/custom_alert_dialog_with_checkbox_test.dart new file mode 100644 index 000000000..e362fc041 --- /dev/null +++ b/test/widget_tests/widgets/custom_alert_dialog_with_checkbox_test.dart @@ -0,0 +1,182 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:talawa/services/navigation_service.dart'; +import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; +import 'package:talawa/widgets/custom_alert_dialog_with_checkbox.dart'; +import 'package:talawa/widgets/raised_round_edge_button.dart'; + +import '../../helpers/test_locator.dart'; + +bool success = false; +bool cancelled = false; +bool checkboxValue = false; + +void onSuccess(bool? value) { + success = true; + checkboxValue = value!; +} + +void onCancel() { + cancelled = true; +} + +Widget createCustomAlertDialogWithCheckbox({ + bool reverse = false, + String? dialogTitle, + bool initialCheckboxValue = false, + bool passSecondaryFunc = true, +}) { + return MaterialApp( + navigatorKey: locator().navigatorKey, + navigatorObservers: [], + locale: const Locale('en'), + supportedLocales: const [ + Locale('en', 'US'), + Locale('es', 'ES'), + Locale('fr', 'FR'), + Locale('hi', 'IN'), + Locale('zh', 'CN'), + Locale('de', 'DE'), + Locale('ja', 'JP'), + Locale('pt', 'PT'), + ], + localizationsDelegates: const [ + AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: Scaffold( + body: TextButton( + child: const Text('Open'), + onPressed: () { + navigationService.pushDialog( + CustomAlertDialogWithCheckbox( + success: onSuccess, + secondaryButtonTap: passSecondaryFunc ? onCancel : null, + dialogSubTitle: 'This is a subtitle', + checkboxLabel: 'Remember me', + reverse: reverse, + initialCheckboxValue: initialCheckboxValue, + ), + ); + }, + ), + ), + ); +} + +void main() { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + SizeConfig().test(); + testSetupLocator(); + }); + + group('Test for CustomAlertDialogWithCheckbox', () { + testWidgets('Check if the AlertDialog shows up', (tester) async { + await tester.pumpWidget(createCustomAlertDialogWithCheckbox()); + await tester.pump(); + await tester.tap(find.textContaining('Open')); + await tester.pump(const Duration(seconds: 1)); + + expect(find.byType(AlertDialog), findsOneWidget); + }); + + testWidgets('Check for the RaisedRoundedButtons', (tester) async { + await tester.pumpWidget(createCustomAlertDialogWithCheckbox()); + await tester.pump(); + await tester.tap(find.textContaining('Open')); + await tester.pump(const Duration(seconds: 1)); + + expect(find.byType(RaisedRoundedButton), findsNWidgets(2)); + }); + + testWidgets('Check if the Confirm button works', (tester) async { + await tester.pumpWidget(createCustomAlertDialogWithCheckbox()); + await tester.pump(); + await tester.tap(find.textContaining('Open')); + await tester.pump(const Duration(seconds: 1)); + + final buttonFinder = find.byType(RaisedRoundedButton); + + await tester.tap(buttonFinder.last); + await tester.pump(); + + expect(success, true); + }); + + testWidgets('Check if the Cancel button works (with secondary func)', + (tester) async { + await tester.pumpWidget(createCustomAlertDialogWithCheckbox()); + await tester.pump(); + await tester.tap(find.textContaining('Open')); + await tester.pump(const Duration(seconds: 1)); + + final buttonFinder = find.byType(RaisedRoundedButton); + + await tester.tap(buttonFinder.first); + await tester.pump(const Duration(seconds: 1)); + + expect(find.byType(AlertDialog), findsOneWidget); + }); + + testWidgets('Check if the Cancel button works (without secondary func)', + (tester) async { + await tester.pumpWidget( + createCustomAlertDialogWithCheckbox(passSecondaryFunc: false), + ); + await tester.pump(); + await tester.tap(find.textContaining('Open')); + await tester.pump(const Duration(seconds: 1)); + + final buttonFinder = find.byType(RaisedRoundedButton); + + await tester.tap(buttonFinder.first); + await tester.pump(const Duration(seconds: 1)); + + expect(find.byType(AlertDialog), findsNothing); + }); + + testWidgets('Check if the checkbox is present and clickable', + (tester) async { + await tester.pumpWidget( + createCustomAlertDialogWithCheckbox(initialCheckboxValue: false), + ); + await tester.pump(); + await tester.tap(find.textContaining('Open')); + await tester.pump(const Duration(seconds: 1)); + + final checkboxFinder = find.byType(Checkbox); + + expect(checkboxFinder, findsOneWidget); + + await tester.tap(checkboxFinder); + await tester.pump(); + + final checkbox = tester.firstWidget(checkboxFinder); + expect(checkbox.value, true); + }); + + testWidgets('Check if the checkbox value is passed to success function', + (tester) async { + await tester.pumpWidget( + createCustomAlertDialogWithCheckbox(initialCheckboxValue: false), + ); + await tester.pump(); + await tester.tap(find.textContaining('Open')); + await tester.pump(const Duration(seconds: 1)); + + final checkboxFinder = find.byType(Checkbox); + await tester.tap(checkboxFinder); + await tester.pump(); + + final buttonFinder = find.byType(RaisedRoundedButton); + await tester.tap(buttonFinder.last); + await tester.pump(); + + expect(checkboxValue, true); + }); + }); +}