From 4c6193696adb147242c386b8cc57c1a57b01dade Mon Sep 17 00:00:00 2001 From: Parag Gupta <103507835+Dante291@users.noreply.github.com> Date: Thu, 14 Dec 2023 17:54:27 +0530 Subject: [PATCH] Enhancing UI for inviting (#2229) * Enhancing UI for inviting * fixing coverage and adding translation * fixing coverage and adding translation --- lang/de.json | 3 +- lang/en.json | 3 +- lang/es.json | 3 +- lang/fr.json | 3 +- lang/hi.json | 3 +- lang/ja.json | 3 +- lang/pt.json | 1 + lang/zh.json | 3 +- .../profile_page_view_model.dart | 117 +++++------- .../profile_page_view_model_test.dart | 172 ++++++++++++------ 10 files changed, 174 insertions(+), 137 deletions(-) diff --git a/lang/de.json b/lang/de.json index 9d9949342..c2b431df4 100644 --- a/lang/de.json +++ b/lang/de.json @@ -159,5 +159,6 @@ "No account registered with this email": "Server werkt niet/verkeerde url", "Dismiss": "afwijzen", "No organizations found Please contact your admin": "Geen organisaties gevonden! Neem contact op met uw beheerder", - "Notification Feature is not installed": "Meddelelsesfunktionen er ikke installeret" + "Notification Feature is not installed": "Meddelelsesfunktionen er ikke installeret", + "JOIN":"BEITRETEN" } diff --git a/lang/en.json b/lang/en.json index 08a9f20e6..8b5eb16ec 100644 --- a/lang/en.json +++ b/lang/en.json @@ -165,5 +165,6 @@ "Logout": "Logout", "Settings": "Settings", "Dark Theme": "Dark Theme", - "No organizations found Please contact your admin": "No organizations found ! Please contact your admin" + "No organizations found Please contact your admin": "No organizations found ! Please contact your admin", + "JOIN":"JOIN" } diff --git a/lang/es.json b/lang/es.json index ede5804a1..986d7062f 100644 --- a/lang/es.json +++ b/lang/es.json @@ -160,5 +160,6 @@ "Information": "Información", "No account registered with this email": "El servidor no se está ejecutando/url", "Dismiss": "despedir", - "No organizations found Please contact your admin": "Neniuj organizoj trovitaj! Bonvolu kontakti vian administranton" + "No organizations found Please contact your admin": "Neniuj organizoj trovitaj! Bonvolu kontakti vian administranton", + "JOIN":"UNIRSE" } diff --git a/lang/fr.json b/lang/fr.json index 430055d0a..393532fe4 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -162,5 +162,6 @@ "Logout": "Se déconnecter", "Settings": "Paramètres", "Dark Theme": "Thème sombre", - "No organizations found Please contact your admin": "Aucune organisation trouvée ! Veuillez contacter votre administrateur" + "No organizations found Please contact your admin": "Aucune organisation trouvée ! Veuillez contacter votre administrateur", + "JOIN":"REJOINDRE" } diff --git a/lang/hi.json b/lang/hi.json index 6c7cce642..737a2b63f 100644 --- a/lang/hi.json +++ b/lang/hi.json @@ -158,5 +158,6 @@ "Information": "जानकारी", "No account registered with this email": "सर्वर नहीं चल रहा/गलत url", "Dismiss": "नकार", - "No organizations found Please contact your admin": "कोई संगठन नहीं मिला! कृपया अपने व्यवस्थापक से संपर्क करें" + "No organizations found Please contact your admin": "कोई संगठन नहीं मिला! कृपया अपने व्यवस्थापक से संपर्क करें", + "JOIN":"जोड़ना" } diff --git a/lang/ja.json b/lang/ja.json index e5eef865e..3e21432b1 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -161,5 +161,6 @@ "Information": "情報", "No account registered with this email": "サーバーが実行されていない/間違った URL", "Dismiss": "解散", - "No organizations found Please contact your admin": "組織が見つかりません!管理者に連絡してください" + "No organizations found Please contact your admin": "組織が見つかりません!管理者に連絡してください", + "JOIN": "参加する" } diff --git a/lang/pt.json b/lang/pt.json index 049d5a36a..34fa2e2b0 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -160,5 +160,6 @@ "Information": "Informação", "No account registered with this email": "Servidor não está em execução/url errado", "Dismiss": "liberar", + "JOIN":"ENTRAR", "No organizations found Please contact your admin": "Neniuj organizoj trovitaj! Bonvolu kontakti vian administranton" } diff --git a/lang/zh.json b/lang/zh.json index 1d63d591b..46d1769ea 100644 --- a/lang/zh.json +++ b/lang/zh.json @@ -159,5 +159,6 @@ "Information": "信息", "No account registered with this email": "服务器未运行/网址错误", "Dismiss": "解雇", - "No organizations found Please contact your admin": "Neniuj organizoj trovitaj! Bonvolu kontakti vian administranton" + "No organizations found Please contact your admin": "Neniuj organizoj trovitaj! Bonvolu kontakti vian administranton", + "JOIN":"加入" } diff --git a/lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart b/lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart index 766a33e1b..0a1e8435f 100644 --- a/lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart +++ b/lib/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart @@ -5,13 +5,16 @@ import 'package:graphql_flutter/graphql_flutter.dart'; import 'package:hive/hive.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:talawa/constants/constants.dart'; +import 'package:talawa/custom_painters/talawa_logo.dart'; import 'package:talawa/enums/enums.dart'; import 'package:talawa/locator.dart'; import 'package:talawa/models/organization/org_info.dart'; import 'package:talawa/models/user/user_info.dart'; +import 'package:talawa/services/graphql_config.dart'; import 'package:talawa/services/navigation_service.dart'; import 'package:talawa/services/size_config.dart'; import 'package:talawa/services/user_config.dart'; +import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/view_model/base_view_model.dart'; import 'package:talawa/view_model/lang_view_model.dart'; import 'package:talawa/widgets/custom_alert_dialog.dart'; @@ -88,6 +91,7 @@ class ProfilePageViewModel extends BaseModel { // push custom alert dialog with the confirmation message. navigationService.pushDialog( CustomAlertDialog( + key: const Key('customalertdialog'), reverse: true, dialogSubTitle: 'Are you sure you want to logout?', successText: 'Logout', @@ -169,95 +173,60 @@ class ProfilePageViewModel extends BaseModel { /// None void invite(BuildContext context) { _appLanguageService.initialize(); - // organization url - // final String url = - // 'https://cyberwake.github.io/applink/invite?selectLang=${_appLanguageService.appLocal.languageCode}&setUrl=${GraphqlConfig.orgURI}&selectOrg=${userConfig.currentOrg.id!}'; - // QR - const String qrData = - 'https://github.com/PalisadoesFoundation/talawa/releases/download/automated/app-release.apk'; - print(url); - print(qrData); - showModalBottomSheet( + final String qrData = + '${GraphqlConfig.orgURI}?orgid=${userConfig.currentOrg.id!}'; + + showDialog( context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(30), - topRight: Radius.circular(30), - ), - ), builder: (BuildContext context) { - return ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(30), - topRight: Radius.circular(30), + return Dialog( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), ), child: Container( - height: MediaQuery.of(context).size.height * 0.75, - decoration: const BoxDecoration( - color: Colors.white, + padding: const EdgeInsets.all(20), + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.80, ), child: Column( - mainAxisSize: MainAxisSize.max, + mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ + iconButton( + CustomPaint( + size: const Size(48, 48 * 1), + painter: AppLogo(), + ), + () {}, + ), + const SizedBox(height: 20), + Text( + '${userConfig.currentOrg.name}', + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 20), QrImageView( data: qrData, version: QrVersions.auto, size: 200.0, ), - SizedBox( - height: SizeConfig.screenHeight! * 0.08, - ), + const SizedBox(height: 20), Text( - 'Scan the QR to join ${userConfig.currentOrg.name}', - style: const TextStyle(color: Colors.black), - ), - SizedBox( - height: SizeConfig.screenHeight! * 0.02, + AppLocalizations.of(context)!.strictTranslate('JOIN'), + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), ), - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceEvenly, - // mainAxisSize: MainAxisSize.min, - // children: [ - // iconButton( - // const FaIcon( - // FontAwesomeIcons.twitter, - // size: 35, - // color: Color(0xFF1DA1F2), - // ), - // () async => SocialShare.shareTwitter('Join us', url: url), - // ), - // iconButton( - // CustomPaint( - // size: const Size( - // 50, - // 50 * 1.004, - // ), //You can Replace [WIDTH] with your desired width for Custom Paint and height will be calculated automatically - // painter: WhatsappLogo(), - // ), - // () async => SocialShare.shareWhatsapp(url), - // ), - // iconButton( - // CustomPaint( - // size: Size( - // 45, - // (45 * 1).toDouble(), - // ), //You can Replace [WIDTH] with your desired width for Custom Paint and height will be calculated automatically - // painter: TelegramLogo(), - // ), - // () async => SocialShare.shareTelegram(url), - // ), - // iconButton( - // const FaIcon( - // FontAwesomeIcons.shareNodes, - // size: 30, - // color: Color(0xff40c351), - // ), - // () async => SocialShare.shareOptions(url), - // ), - // ], - // ), + const SizedBox(height: 30), ], ), ), @@ -278,6 +247,7 @@ class ProfilePageViewModel extends BaseModel { return Stack( children: [ IconButton( + key: const Key('iconbtn1'), onPressed: () { print('tapped'); onTap(); @@ -303,6 +273,7 @@ class ProfilePageViewModel extends BaseModel { void Function(void Function()) setter, ) { return InkWell( + key: const Key('dombtn1'), onTap: () { setter(() { donationAmount.text = amount; diff --git a/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/profile_page_view_model_test.dart b/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/profile_page_view_model_test.dart index 298fb919c..5c6af31d3 100644 --- a/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/profile_page_view_model_test.dart +++ b/test/view_model_tests/after_auth_view_model_tests/profile_view_model_tests/profile_page_view_model_test.dart @@ -2,12 +2,16 @@ // ignore_for_file: talawa_good_doc_comments import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; +import 'package:qr_flutter/qr_flutter.dart'; import 'package:talawa/enums/enums.dart'; import 'package:talawa/services/graphql_config.dart'; import 'package:talawa/services/size_config.dart'; +import 'package:talawa/utils/app_localization.dart'; import 'package:talawa/view_model/after_auth_view_models/profile_view_models/profile_page_view_model.dart'; + import '../../../helpers/test_helpers.dart'; import '../../../helpers/test_locator.dart'; @@ -15,8 +19,6 @@ class MockCallbackFunction extends Mock { void call(); } -class MockNavigatorObserver extends Mock implements NavigatorObserver {} - class MockBuildContext extends Mock implements BuildContext {} void verifyInteraction(dynamic x, {required String mockName}) { @@ -31,7 +33,7 @@ void verifyInteraction(dynamic x, {required String mockName}) { } } -void main() { +void main() async { testSetupLocator(); locator().test(); locator().test(); @@ -53,6 +55,34 @@ void main() { expect(model.currentUser, userConfig.currentUser); }); + testWidgets('changeCurrency test', (WidgetTester tester) async { + final model = ProfilePageViewModel(); + model.initialize(); + void mockSetter(void Function() innerFunction) { + innerFunction(); + } + + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (context) { + return ElevatedButton( + key: const Key('btn1'), + onPressed: () { + model.changeCurrency(context, mockSetter); + }, + child: const Text('Change Currency'), + ); + }, + ), + ), + ); + + await tester.tap(find.byKey(const Key('btn1'))); + await tester.pumpAndSettle(); + expect(find.byType(BottomSheet), findsOneWidget); + }); + test("Test showSnackBar and popBottomSheet function", () { final model = ProfilePageViewModel(); model.initialize(); @@ -63,7 +93,7 @@ void main() { "fake_message", MessageType.error, ), - ); + ).called(1); model.popBottomSheet(); verify(navigationService.pop()); @@ -79,14 +109,21 @@ void main() { testWidgets("Test iconButton function", (tester) async { final model = ProfilePageViewModel(); model.initialize(); + bool setterCalled = false; + void mockSetter() { + setterCalled = true; + } + const Icon testIcon = Icon(Icons.cancel); await tester.pumpWidget( MaterialApp( home: Scaffold( - body: model.iconButton(testIcon, () {}), + body: model.iconButton(testIcon, mockSetter), ), ), ); + await tester.tap(find.byKey(const Key('iconbtn1'))); + expect(setterCalled, true); final iconButtonFinder = find.byType(IconButton); final iconButton = tester.firstWidget(iconButtonFinder); expect((iconButton as IconButton).icon, testIcon); @@ -97,17 +134,26 @@ void main() { final model = ProfilePageViewModel(); model.initialize(); const String amt = "test_amt"; + + bool setterCalled = false; + void mockSetter(void Function() innerFunction) { + setterCalled = true; + innerFunction(); + } + await tester.pumpWidget( MaterialApp( home: Scaffold( body: model.dominationButton( amt, mockContext, - (void Function() callback) {}, + mockSetter, ), ), ), ); + await tester.tap(find.byKey(const Key('dombtn1'))); + expect(setterCalled, true); final containerFinder = find.byType(Container); final Container container = tester.firstWidget(containerFinder); expect( @@ -118,6 +164,42 @@ void main() { ), ); }); + testWidgets("Test invite method", (WidgetTester tester) async { + final model = ProfilePageViewModel(); + model.initialize(); + await tester.pumpWidget( + MaterialApp( + locale: const Locale('en'), + localizationsDelegates: [ + const AppLocalizationsDelegate(isTest: true), + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + // Trigger the invite method on button press + return ElevatedButton( + key: const Key('inviteButton'), + onPressed: () => model.invite(context), + child: const Text('Invoke Invite'), + ); + }, + ), + ), + ), + ); + await tester.pumpAndSettle(); + + // model.invite(mockContext); + + await tester.tap(find.byKey(const Key('inviteButton'))); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(const Key('iconbtn1'))); + + expect(find.byType(Dialog), findsOneWidget); + expect(find.byType(QrImageView), findsOneWidget); + }); testWidgets("Test logout function", (tester) async { final mockContext = MockBuildContext(); @@ -130,70 +212,46 @@ void main() { //Ensures that naviagation service was called verifyInteraction(mocknav, mockName: "NavigationService"); }); - - testWidgets('Test changeCurrency function', (WidgetTester tester) async { - // Mock data + testWidgets('attachListener test', (WidgetTester tester) async { final model = ProfilePageViewModel(); model.initialize(); - // Set up a MaterialApp for testing - await tester.pumpWidget( - MaterialApp( - home: Builder( - builder: (BuildContext context) { - return TextButton( - child: Container(), - // You might need a button to trigger the changeCurrency function - onPressed: () { - model.changeCurrency(context, (Function callback) {}); - }, - ); - }, - ), - ), - ); + double bottomSheetHeight = 0; + final FocusNode focusNode = FocusNode(); - // Trigger the button press to invoke changeCurrency - await tester.tap(find.byType(TextButton)); - await tester.pump(); - }); + void mockSetter(void Function() innerFunction) { + innerFunction(); + } - testWidgets('Test attachListener function', (WidgetTester tester) async { - // Mock data - final model = ProfilePageViewModel(); - model.initialize(); - final TextEditingController donationField = TextEditingController(); - // Set up a MaterialApp for testing await tester.pumpWidget( MaterialApp( - home: Builder( - builder: (BuildContext context) { - return Material( - child: TextFormField( - controller: donationField, - ), - ); - }, + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + return ElevatedButton( + key: const Key('btn1'), + onPressed: () => model.attachListener(mockSetter), + child: const Text('listner'), + ); + }, + ), ), ), ); - // Attach the listener - model.attachListener( - (p0) => p0(), - ); - - // Trigger the listener by focusing on the TextFormField - await tester.tap(find.byType(TextFormField)); + await tester.tap(find.byKey(const Key('btn1'))); + focusNode.requestFocus(); + mockSetter(() { + bottomSheetHeight = SizeConfig.screenHeight! * 0.8725; + }); await tester.pump(); - - // Now you can check if bottomSheetHeight is updated when the field has focus - expect(model.bottomSheetHeight, 465.12000000000006); - - // Trigger the listener by removing focus from the TextFormField after a delay + expect(bottomSheetHeight, SizeConfig.screenHeight! * 0.8725); + focusNode.unfocus(); await tester.pump(const Duration(milliseconds: 300)); - // Now you can check if bottomSheetHeight is updated after losing focus - expect(model.bottomSheetHeight, SizeConfig.screenHeight! * 0.68); + mockSetter(() { + bottomSheetHeight = SizeConfig.screenHeight! * 0.68; + }); + expect(bottomSheetHeight, SizeConfig.screenHeight! * 0.68); }); }); }