From 516c13aafd124d1db5c8feeefb651004b28a2643 Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Mon, 24 Jun 2024 11:52:47 -0400 Subject: [PATCH 01/51] Shows code version in settings as well as --- .../xcshareddata/swiftpm/Package.resolved | 14 +++++ lib/pages/settings/settings_view.dart | 57 ++++++++++++++++--- pubspec.lock | 46 ++++++++++++++- 3 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..c4fde7525 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "qaml-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/qaml-ai/qaml-swift", + "state" : { + "branch" : "next", + "revision" : "34ee0823225a19641f03a2a38508fabfd484bdc2" + } + } + ], + "version" : 2 +} diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index c4a57a267..86c48ccb6 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -10,6 +10,9 @@ import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; import 'package:url_launcher/url_launcher_string.dart'; +import 'package:package_info_plus/package_info_plus.dart'; //adding to check app version + + import 'settings.dart'; class SettingsView extends StatelessWidget { @@ -17,6 +20,14 @@ class SettingsView extends StatelessWidget { const SettingsView(this.controller, {super.key}); + Future getAppVersion() async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + return 'Version: ${packageInfo.version}+${packageInfo.buildNumber}'; +} + + + + @override Widget build(BuildContext context) { // #Pangea @@ -44,6 +55,8 @@ class SettingsView extends StatelessWidget { key: const Key('SettingsListViewContent'), children: [ FutureBuilder( + + future: controller.profileFuture, builder: (context, snapshot) { final profile = snapshot.data; @@ -136,6 +149,8 @@ class SettingsView extends StatelessWidget { ], ); }, + + ), // #Pangea // Divider( @@ -239,17 +254,41 @@ class SettingsView extends StatelessWidget { // onTap: () => PlatformInfos.showDialog(context), // trailing: const Icon(Icons.chevron_right_outlined), // ), + + ListTile( + leading: const Icon(Icons.shield_outlined), + title: Text(L10n.of(context)!.termsAndConditions), + onTap: () => launchUrlString(AppConfig.termsOfServiceUrl), + trailing: const Icon(Icons.open_in_new_outlined), + ), + // Adding the FutureBuilder here + FutureBuilder( + future: getAppVersion(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return ListTile( + leading: Icon(Icons.info_outline), + title: Text(snapshot.data ?? 'Version Not Found'), + ); + } else if (snapshot.hasError) { + return ListTile( + leading: Icon(Icons.error_outline), + title: Text('Failed to fetch version'), + ); + } else { + return ListTile( + leading: CircularProgressIndicator(), + title: Text('Fetching version...'), + ); + } + }, + ), + // Conditional ListTile based on the environment (staging or not) + if (Environment.isStaging) ListTile( - leading: const Icon(Icons.shield_outlined), - title: Text(L10n.of(context)!.termsAndConditions), - onTap: () => launchUrlString(AppConfig.termsOfServiceUrl), - trailing: const Icon(Icons.open_in_new_outlined), + leading: const Icon(Icons.bug_report_outlined), + title: Text(L10n.of(context)!.connectedToStaging), ), - if (Environment.isStaging) - ListTile( - leading: const Icon(Icons.bug_report_outlined), - title: Text(L10n.of(context)!.connectedToStaging), - ), // Pangea# ], ), diff --git a/pubspec.lock b/pubspec.lock index afe372a2f..f64f9e0fa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -349,10 +349,10 @@ packages: dependency: "direct main" description: name: emoji_picker_flutter - sha256: "839200a2bd1af9a65d71133a5a246dbf5b24f7e4f6f4c5390130c2e0ed5f85af" + sha256: "7c6681783e06710608df27be0e38aa4ba73ca1ccac370bb0e7a1320723ae4bca" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.1" emoji_proposal: dependency: "direct main" description: @@ -975,6 +975,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + globbing: + dependency: transitive + description: + name: globbing + sha256: "4f89cfaf6fa74c9c1740a96259da06bd45411ede56744e28017cc534a12b6e2d" + url: "https://pub.dev" + source: hosted + version: "1.0.0" go_router: dependency: "direct main" description: @@ -1167,6 +1175,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.8+1" + injector: + dependency: transitive + description: + name: injector + sha256: ed389bed5b48a699d5b9561c985023d0d5cc88dd5ff2237aadcce5a5ab433e4e + url: "https://pub.dev" + source: hosted + version: "3.0.0" integration_test: dependency: "direct dev" description: flutter @@ -1749,6 +1765,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + properties: + dependency: transitive + description: + name: properties + sha256: "333f427dd4ed07bdbe8c75b9ff864a1e70b5d7a8426a2e8bdd457b65ae5ac598" + url: "https://pub.dev" + source: hosted + version: "2.1.1" provider: dependency: "direct main" description: @@ -1949,6 +1973,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.2.0" + sentry_dart_plugin: + dependency: "direct dev" + description: + name: sentry_dart_plugin + sha256: e81fa3e0ffabd04fdcfbfecd6468d4a342f02ab33edca09708c61bcd2be42b7d + url: "https://pub.dev" + source: hosted + version: "1.7.1" sentry_flutter: dependency: "direct main" description: @@ -2234,6 +2266,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0+1" + system_info2: + dependency: transitive + description: + name: system_info2 + sha256: "65206bbef475217008b5827374767550a5420ce70a04d2d7e94d1d2253f3efc9" + url: "https://pub.dev" + source: hosted + version: "4.0.0" tar: dependency: transitive description: @@ -2692,4 +2732,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.3" + flutter: ">=3.19.0" From 39db3c6e9c1a4d8478a1998bff429946c458b283 Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Thu, 27 Jun 2024 11:25:34 -0400 Subject: [PATCH 02/51] added translations --- assets/l10n/intl_en.arb | 7 +- assets/l10n/intl_es.arb | 6 +- lib/pages/settings/settings_view.dart | 15 +- needed-translations.txt | 250 ++++++++++++++++++++------ 4 files changed, 218 insertions(+), 60 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index c66ef7ac6..9cb76ad5a 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4068,5 +4068,10 @@ "@knockRestricted": {}, "nonexistentSelection": "Selection no longer exists.", "cantAddSpaceChild": "You do not have permission to add a child to this space.", - "roomAddedToSpace": "Room(s) have been added to the selected space." + "roomAddedToSpace": "Room(s) have been added to the selected space.", + "versionNotFound": "Version Not Found", + "fetchingVersion": "Fetching version...", + "versionFetchError": "Error fetching version", + "connectedToStaging": "Connected to Staging" + } \ No newline at end of file diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index 44a03f0e1..b88f8108c 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4675,5 +4675,9 @@ "tooltipInstructionsTitle": "¿No sabes para qué sirve?", "tooltipInstructionsMobileBody": "Mantenga pulsados los elementos para ver la información sobre herramientas.", "tooltipInstructionsBrowserBody": "Pase el ratón sobre los elementos para ver información sobre herramientas.", - "buildTranslation": "Construye tu traducción a partir de las opciones anteriores" + "buildTranslation": "Construye tu traducción a partir de las opciones anteriores", + "versionNotFound": "Versión no encontrada", + "fetchingVersion": "Obteniendo versión...", + "versionFetchError": "Error al obtener la versión", + "connectedToStaging": "Conectado al entorno de pruebas" } \ No newline at end of file diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index 86c48ccb6..936717b8b 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -254,7 +254,6 @@ class SettingsView extends StatelessWidget { // onTap: () => PlatformInfos.showDialog(context), // trailing: const Icon(Icons.chevron_right_outlined), // ), - ListTile( leading: const Icon(Icons.shield_outlined), title: Text(L10n.of(context)!.termsAndConditions), @@ -268,27 +267,27 @@ class SettingsView extends StatelessWidget { if (snapshot.connectionState == ConnectionState.done) { return ListTile( leading: Icon(Icons.info_outline), - title: Text(snapshot.data ?? 'Version Not Found'), + title: Text(snapshot.data ?? L10n.of(context)!.versionNotFound), ); } else if (snapshot.hasError) { return ListTile( leading: Icon(Icons.error_outline), - title: Text('Failed to fetch version'), + title: Text(L10n.of(context)!.versionFetchError), ); } else { return ListTile( leading: CircularProgressIndicator(), - title: Text('Fetching version...'), + title: Text(L10n.of(context)!.fetchingVersion), ); } }, ), // Conditional ListTile based on the environment (staging or not) if (Environment.isStaging) - ListTile( - leading: const Icon(Icons.bug_report_outlined), - title: Text(L10n.of(context)!.connectedToStaging), - ), + ListTile( + leading: const Icon(Icons.bug_report_outlined), + title: Text(L10n.of(context)!.connectedToStaging), + ), // Pangea# ], ), diff --git a/needed-translations.txt b/needed-translations.txt index 293985cfc..1fc238303 100644 --- a/needed-translations.txt +++ b/needed-translations.txt @@ -866,7 +866,10 @@ "buildTranslation", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "be": [ @@ -2369,7 +2372,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "bn": [ @@ -3868,7 +3874,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "bo": [ @@ -5371,7 +5380,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "ca": [ @@ -6276,7 +6288,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "cs": [ @@ -7263,7 +7278,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "de": [ @@ -8133,7 +8151,10 @@ "buildTranslation", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "el": [ @@ -9587,7 +9608,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "eo": [ @@ -10739,7 +10763,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "es": [ @@ -10786,7 +10813,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "et": [ @@ -11656,7 +11686,10 @@ "buildTranslation", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "eu": [ @@ -12528,7 +12561,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "fa": [ @@ -13537,7 +13573,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "fi": [ @@ -14510,7 +14549,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "fil": [ @@ -15839,7 +15881,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "fr": [ @@ -16847,7 +16892,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "ga": [ @@ -17984,7 +18032,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "gl": [ @@ -18854,7 +18905,10 @@ "buildTranslation", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "he": [ @@ -20110,7 +20164,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "hi": [ @@ -21606,7 +21663,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "hr": [ @@ -22555,7 +22615,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "hu": [ @@ -23441,7 +23504,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "ia": [ @@ -24930,7 +24996,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "id": [ @@ -25806,7 +25875,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "ie": [ @@ -27066,7 +27138,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "it": [ @@ -27993,7 +28068,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "ja": [ @@ -29031,7 +29109,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "ka": [ @@ -30388,7 +30469,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "ko": [ @@ -31260,7 +31344,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "lt": [ @@ -32298,7 +32385,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "lv": [ @@ -33176,7 +33266,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "nb": [ @@ -34378,7 +34471,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "nl": [ @@ -35344,7 +35440,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "pl": [ @@ -36319,7 +36418,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "pt": [ @@ -37800,7 +37902,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "pt_BR": [ @@ -38676,7 +38781,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "pt_PT": [ @@ -39879,7 +39987,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "ro": [ @@ -40889,7 +41000,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "ru": [ @@ -41765,7 +41879,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "sk": [ @@ -43034,7 +43151,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "sl": [ @@ -44433,7 +44553,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "sr": [ @@ -45606,7 +45729,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "sv": [ @@ -46513,7 +46639,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "ta": [ @@ -48013,7 +48142,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "th": [ @@ -49467,7 +49599,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "tr": [ @@ -50337,7 +50472,10 @@ "buildTranslation", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "uk": [ @@ -51244,7 +51382,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "vi": [ @@ -52599,7 +52740,10 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "zh": [ @@ -53469,7 +53613,10 @@ "buildTranslation", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ], "zh_Hant": [ @@ -54620,6 +54767,9 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace" + "roomAddedToSpace", + "versionNotFound", + "fetchingVersion", + "versionFetchError" ] } From 3eee9c710fd8c26a053e87204e95f65a4c208700 Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:12:44 -0400 Subject: [PATCH 03/51] removed package.resolved --- .../xcshareddata/swiftpm/Package.resolved | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index c4fde7525..000000000 --- a/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,14 +0,0 @@ -{ - "pins" : [ - { - "identity" : "qaml-swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/qaml-ai/qaml-swift", - "state" : { - "branch" : "next", - "revision" : "34ee0823225a19641f03a2a38508fabfd484bdc2" - } - } - ], - "version" : 2 -} From e5c818de5caac9ba794d2d2a17443815a280e87e Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:19:32 -0400 Subject: [PATCH 04/51] changed code so getAppVersion supported l10n, added en and es translations --- assets/l10n/intl_en.arb | 20 +++- assets/l10n/intl_es.arb | 18 ++- lib/pages/settings/settings_view.dart | 8 +- needed-translations.txt | 152 +++++++++++++++++--------- 4 files changed, 139 insertions(+), 59 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 9cb76ad5a..3281b68e4 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4072,6 +4072,24 @@ "versionNotFound": "Version Not Found", "fetchingVersion": "Fetching version...", "versionFetchError": "Error fetching version", - "connectedToStaging": "Connected to Staging" + "connectedToStaging": "Connected to Staging", + "versionText": "Version: {version}+{buildNumber}", + "@versionText": { + "description": "Text displaying the app version and build number.", + "type": "text", + "placeholders": { + "version": { + "type": "String", + "description": "The current version of the app." + }, + "buildNumber": { + "type": "String", + "description": "The build number of the app." + } + } + } + + + } \ No newline at end of file diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index b88f8108c..6cc89cc65 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4679,5 +4679,21 @@ "versionNotFound": "Versión no encontrada", "fetchingVersion": "Obteniendo versión...", "versionFetchError": "Error al obtener la versión", - "connectedToStaging": "Conectado al entorno de pruebas" + "connectedToStaging": "Conectado al entorno de pruebas", + "versionText": "Versión: {version}+{buildNumber}", + "@versionText": { + "description": "Texto que muestra la versión y el número de compilación de la aplicación.", + "type": "text", + "placeholders": { + "version": { + "type": "String", + "description": "La versión actual de la aplicación." + }, + "buildNumber": { + "type": "String", + "description": "El número de compilación de la aplicación." + } + } + } + } \ No newline at end of file diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index 936717b8b..e537f01af 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -20,9 +20,9 @@ class SettingsView extends StatelessWidget { const SettingsView(this.controller, {super.key}); - Future getAppVersion() async { - PackageInfo packageInfo = await PackageInfo.fromPlatform(); - return 'Version: ${packageInfo.version}+${packageInfo.buildNumber}'; + Future getAppVersion(BuildContext context) async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + return L10n.of(context)!.versionText(packageInfo.version, packageInfo.buildNumber); } @@ -262,7 +262,7 @@ class SettingsView extends StatelessWidget { ), // Adding the FutureBuilder here FutureBuilder( - future: getAppVersion(), + future: getAppVersion(context), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return ListTile( diff --git a/needed-translations.txt b/needed-translations.txt index 1fc238303..b0ada0925 100644 --- a/needed-translations.txt +++ b/needed-translations.txt @@ -869,7 +869,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "be": [ @@ -2375,7 +2376,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "bn": [ @@ -3877,7 +3879,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "bo": [ @@ -5383,7 +5386,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "ca": [ @@ -6291,7 +6295,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "cs": [ @@ -7281,7 +7286,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "de": [ @@ -8154,7 +8160,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "el": [ @@ -9611,7 +9618,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "eo": [ @@ -10766,7 +10774,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "es": [ @@ -10813,10 +10822,7 @@ "knockRestricted", "nonexistentSelection", "cantAddSpaceChild", - "roomAddedToSpace", - "versionNotFound", - "fetchingVersion", - "versionFetchError" + "roomAddedToSpace" ], "et": [ @@ -11689,7 +11695,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "eu": [ @@ -12564,7 +12571,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "fa": [ @@ -13576,7 +13584,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "fi": [ @@ -14552,7 +14561,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "fil": [ @@ -15884,7 +15894,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "fr": [ @@ -16895,7 +16906,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "ga": [ @@ -18035,7 +18047,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "gl": [ @@ -18908,7 +18921,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "he": [ @@ -20167,7 +20181,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "hi": [ @@ -21666,7 +21681,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "hr": [ @@ -22618,7 +22634,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "hu": [ @@ -23507,7 +23524,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "ia": [ @@ -24999,7 +25017,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "id": [ @@ -25878,7 +25897,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "ie": [ @@ -27141,7 +27161,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "it": [ @@ -28071,7 +28092,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "ja": [ @@ -29112,7 +29134,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "ka": [ @@ -30472,7 +30495,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "ko": [ @@ -31347,7 +31371,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "lt": [ @@ -32388,7 +32413,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "lv": [ @@ -33269,7 +33295,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "nb": [ @@ -34474,7 +34501,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "nl": [ @@ -35443,7 +35471,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "pl": [ @@ -36421,7 +36450,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "pt": [ @@ -37905,7 +37935,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "pt_BR": [ @@ -38784,7 +38815,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "pt_PT": [ @@ -39990,7 +40022,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "ro": [ @@ -41003,7 +41036,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "ru": [ @@ -41882,7 +41916,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "sk": [ @@ -43154,7 +43189,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "sl": [ @@ -44556,7 +44592,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "sr": [ @@ -45732,7 +45769,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "sv": [ @@ -46642,7 +46680,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "ta": [ @@ -48145,7 +48184,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "th": [ @@ -49602,7 +49642,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "tr": [ @@ -50475,7 +50516,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "uk": [ @@ -51385,7 +51427,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "vi": [ @@ -52743,7 +52786,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "zh": [ @@ -53616,7 +53660,8 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ], "zh_Hant": [ @@ -54770,6 +54815,7 @@ "roomAddedToSpace", "versionNotFound", "fetchingVersion", - "versionFetchError" + "versionFetchError", + "versionText" ] } From 49251774ccb9b6e100c9edca9d1fb94b3140e6e6 Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:28:41 -0400 Subject: [PATCH 05/51] try/catches added for minified:a7t sentry error, added back translationsfor l10n, added more to error_handler to get more info --- assets/l10n/intl_en.arb | 19 ++++++++ assets/l10n/intl_es.arb | 22 ++++++++- lib/pages/chat/chat.dart | 75 ++++++++++++++++++----------- lib/pangea/utils/error_handler.dart | 10 +++- 4 files changed, 95 insertions(+), 31 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 7f44188a6..08e36316d 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4068,6 +4068,25 @@ "hintTitle": "Hint:", "speechToTextBody": "See how well you did by looking at your Accuracy and Words Per Minute scores", "previous": "Previous", + "versionNotFound": "Version Not Found", + "fetchingVersion": "Fetching version...", + "versionFetchError": "Error fetching version", + "connectedToStaging": "Connected to Staging", + "versionText": "Version: {version}+{buildNumber}", + "@versionText": { + "description": "Text displaying the app version and build number.", + "type": "text", + "placeholders": { + "version": { + "type": "String", + "description": "The current version of the app." + }, + "buildNumber": { + "type": "String", + "description": "The build number of the app." + } + } + }, "languageButtonLabel": "Language: {currentLanguage}", "@languageButtonLabel": { "type": "text", diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index ac6b30c76..84de60836 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4717,5 +4717,25 @@ "addChatToSpaceDesc": "Añadir un chat a un espacio hará que el chat aparezca dentro del espacio para los estudiantes y les dará acceso.", "addSpaceToSpaceDesc": "Añadir un espacio a otro espacio hará que el espacio hijo aparezca dentro del espacio padre para los estudiantes y les dará acceso.", "spaceAnalytics": "Analítica espacial", - "changeAnalyticsLanguage": "Cambiar el lenguaje analítico" + "changeAnalyticsLanguage": "Cambiar el lenguaje analítico", + "versionNotFound": "Versión no encontrada", + "fetchingVersion": "Obteniendo versión...", + "versionFetchError": "Error al obtener la versión", + "connectedToStaging": "Conectado al entorno de pruebas" + "connectedToStaging": "Conectado al entorno de pruebas", + "versionText": "Versión: {version}+{buildNumber}", + "@versionText": { + "description": "Texto que muestra la versión y el número de compilación de la aplicación.", + "type": "text", + "placeholders": { + "version": { + "type": "String", + "description": "La versión actual de la aplicación." + }, + "buildNumber": { + "type": "String", + "description": "El número de compilación de la aplicación." + } + } + } } \ No newline at end of file diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 5629d809e..5ac0a6547 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -45,6 +45,7 @@ import 'package:matrix/matrix.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:universal_html/html.dart' as html; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../../utils/account_bundles.dart'; import '../../utils/localized_exception_extension.dart'; @@ -481,35 +482,35 @@ class ChatController extends State Future? _setReadMarkerFuture; void setReadMarker({String? eventId}) { - if (_setReadMarkerFuture != null) return; - if (_scrolledUp) return; - if (scrollUpBannerEventId != null) return; - if (eventId == null && - !room.hasNewMessages && - room.notificationCount == 0) { - return; - } + if (_setReadMarkerFuture != null) return; + if (_scrolledUp) return; + if (scrollUpBannerEventId != null) return; + if (eventId == null && !room.hasNewMessages && room.notificationCount == 0) { + return; + } - // Do not send read markers when app is not in foreground - // #Pangea - try { - // Pangea# - if (kIsWeb && !Matrix.of(context).webHasFocus) return; - // #Pangea - } catch (err, s) { - ErrorHandler.logError(e: err, s: s); - return; - } + // Do not send read markers when app is not in foreground + // #Pangea + try { // Pangea# - if (!kIsWeb && - WidgetsBinding.instance.lifecycleState != AppLifecycleState.resumed) { - return; - } + if (kIsWeb && !Matrix.of(context).webHasFocus) return; + // #Pangea + } catch (err, s) { + ErrorHandler.logError(e: PangeaWarningError("Web focus error: $err"), s: s); + return; + } + // Pangea# + if (!kIsWeb && WidgetsBinding.instance.lifecycleState != AppLifecycleState.resumed) { + return; + } - final timeline = this.timeline; - if (timeline == null || timeline.events.isEmpty) return; + final timeline = this.timeline; + if (timeline == null || timeline.events.isEmpty) { + ErrorHandler.logError(e: PangeaWarningError("Timeline is null or empty"), s: StackTrace.current); + return; + } - Logs().d('Set read marker...', eventId); + Logs().d('Set read marker...', eventId); // ignore: unawaited_futures _setReadMarkerFuture = timeline .setReadMarker( @@ -518,11 +519,27 @@ class ChatController extends State ) .then((_) { _setReadMarkerFuture = null; - }); - if (eventId == null || eventId == timeline.room.lastEvent?.eventId) { - Matrix.of(context).backgroundPush?.cancelNotification(roomId); - } + }).catchError((e, s) { + ErrorHandler.logError( + e: PangeaWarningError("Failed to set read marker: $e"), + s: s, + m: 'Failed to set read marker for eventId: $eventId' + ); + Sentry.captureException( + e, + stackTrace: s, + withScope: (scope) { + scope.setExtra('extra_info', 'Failed during setReadMarker with eventId: $eventId'); + scope.setTag('where', 'setReadMarker'); + } + ); + }); + + if (eventId == null || eventId == timeline.room.lastEvent?.eventId) { + Matrix.of(context).backgroundPush?.cancelNotification(roomId); } +} + @override void dispose() { diff --git a/lib/pangea/utils/error_handler.dart b/lib/pangea/utils/error_handler.dart index 2144e9f87..eb406d48a 100644 --- a/lib/pangea/utils/error_handler.dart +++ b/lib/pangea/utils/error_handler.dart @@ -53,8 +53,15 @@ class ErrorHandler { Map? data, SentryLevel level = SentryLevel.error, }) async { - if (m != null) debugPrint("error message: $m"); + if (e is PangeaWarningError) { + // Custom handling for PangeaWarningError + debugPrint("PangeaWarningError: ${e.message}"); + } else { + if (m != null) debugPrint("error message: $m"); + } + if ((e ?? m) != null) debugPrint("error to string: ${e?.toString() ?? m}"); + if (data != null) { Sentry.addBreadcrumb(Breadcrumb.fromJson(data)); debugPrint(data.toString()); @@ -68,6 +75,7 @@ class ErrorHandler { }, ); } + } class ErrorCopy { From d02acc988495f0e983bc7c72478018536e016cd1 Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Wed, 3 Jul 2024 18:07:33 -0400 Subject: [PATCH 06/51] switching to token focus of saving constructs --- .../pangea_message_event.dart | 74 ++++++++++--------- lib/pangea/models/it_response_model.dart | 28 ++++--- lib/pangea/models/lemma.dart | 15 +--- lib/pangea/models/pangea_token_model.dart | 69 ++++++++--------- lib/pangea/repo/igc_repo.dart | 20 +++-- 5 files changed, 103 insertions(+), 103 deletions(-) diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index 334c0fa78..0dfaa149a 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -695,21 +695,25 @@ class PangeaMessageEvent { if (continuance.wasClicked) { //PTODO - account for end of flow score if (continuance.level != ChoreoConstants.levelThresholdForGreen) { - uses.addAll( - _lemmasToVocabUses( - continuance.lemmas, - ConstructUseTypeEnum.incIt, - ), - ); + for (final token in continuance.tokens) { + uses.add( + _lemmaToVocabUse( + token.lemma, + ConstructUseTypeEnum.incIt, + ), + ); + } } } else { if (continuance.level != ChoreoConstants.levelThresholdForGreen) { - uses.addAll( - _lemmasToVocabUses( - continuance.lemmas, - ConstructUseTypeEnum.ignIt, - ), - ); + for (final token in continuance.tokens) { + uses.add( + _lemmaToVocabUse( + token.lemma, + ConstructUseTypeEnum.ignIt, + ), + ); + } } } } @@ -729,13 +733,13 @@ class PangeaMessageEvent { // for each token, record whether selected in ga, ta, or wa for (final token in originalSent!.tokens!) { - uses.addAll(_getVocabUseForToken(token)); + uses.add(_getVocabUseForToken(token)); } return uses; } - /// Returns a list of [OneConstructUse] objects for the given [token] + /// Returns a [OneConstructUse] for the given [token] /// If there is no [originalSent] or [originalSent.choreo], the [token] is /// considered to be a [ConstructUseTypeEnum.wa] as long as it matches the target language. /// Later on, we may want to consider putting it in some category of like 'pending' @@ -744,11 +748,11 @@ class PangeaMessageEvent { /// If the [token] is in the [originalSent.choreo.acceptedOrIgnoredMatch.choices], /// it is considered to be a [ConstructUseTypeEnum.corIt]. /// If the [token] is not included in any choreoStep, it is considered to be a [ConstructUseTypeEnum.wa]. - List _getVocabUseForToken(PangeaToken token) { + OneConstructUse _getVocabUseForToken(PangeaToken token) { if (originalSent?.choreo == null) { final bool inUserL2 = originalSent?.langCode == l2Code; - return _lemmasToVocabUses( - token.lemmas, + return _lemmaToVocabUse( + token.lemma, inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk, ); } @@ -763,42 +767,40 @@ class PangeaMessageEvent { step.text.contains(r.value), ) ?? false)) { - return _lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.ga); + return _lemmaToVocabUse(token.lemma, ConstructUseTypeEnum.ga); } if (step.itStep != null) { final bool pickedThroughIT = step.itStep!.chosenContinuance?.text.contains(token.text.content) ?? false; if (pickedThroughIT) { - return _lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.corIt); + return _lemmaToVocabUse(token.lemma, ConstructUseTypeEnum.corIt); //PTODO - check if added via custom input in IT flow } } } - return _lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.wa); + return _lemmaToVocabUse(token.lemma, ConstructUseTypeEnum.wa); } /// Convert a list of [lemmas] into a list of vocab uses /// with the given [type] - List _lemmasToVocabUses( - List lemmas, + OneConstructUse _lemmaToVocabUse( + Lemma lemma, ConstructUseTypeEnum type, ) { final List uses = []; - for (final lemma in lemmas) { - if (lemma.saveVocab) { - uses.add( - OneConstructUse( - useType: type, - chatId: event.roomId!, - timeStamp: event.originServerTs, - lemma: lemma.text, - form: lemma.form, - msgId: event.eventId, - constructType: ConstructTypeEnum.vocab, - ), - ); - } + if (lemma.saveVocab) { + uses.add( + OneConstructUse( + useType: type, + chatId: event.roomId!, + timeStamp: event.originServerTs, + lemma: lemma.text, + form: lemma.form, + msgId: event.eventId, + constructType: ConstructTypeEnum.vocab, + ), + ); } return uses; } diff --git a/lib/pangea/models/it_response_model.dart b/lib/pangea/models/it_response_model.dart index 5fda36020..6adb4b4bc 100644 --- a/lib/pangea/models/it_response_model.dart +++ b/lib/pangea/models/it_response_model.dart @@ -2,11 +2,10 @@ import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/choreo_constants.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/extensions/my_list_extension.dart'; +import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'lemma.dart'; - class ITResponseModel { String fullTextTranslation; List continuances; @@ -79,7 +78,7 @@ class Continuance { double probability; int level; String text; - List lemmas; + List tokens; /// saving this in a full json form String description; @@ -99,19 +98,18 @@ class Continuance { required this.inDictionary, required this.hasInfo, required this.gold, - required this.lemmas, + required this.tokens, }); factory Continuance.fromJson(Map json) { - final List lemmaInternal = - (json[ModelKey.lemma] != null && json[ModelKey.lemma] is Iterable) - ? (json[ModelKey.lemma] as Iterable) - .map( - (e) => Lemma.fromJson(e as Map), - ) - .toList() - .cast() - : []; + final List tokensInternal = (json[ModelKey.tokens] != null) + ? (json[ModelKey.tokens] as Iterable) + .map( + (e) => PangeaToken.fromJson(e as Map), + ) + .toList() + .cast() + : []; return Continuance( probability: json['probability'].toDouble(), level: json['level'], @@ -122,7 +120,7 @@ class Continuance { wasClicked: json['clkd'] ?? false, hasInfo: json['has_info'] ?? false, gold: json['gold'] ?? false, - lemmas: lemmaInternal, + tokens: tokensInternal, ); } @@ -132,7 +130,7 @@ class Continuance { data['level'] = level; data['text'] = text; data['clkd'] = wasClicked; - data[ModelKey.lemma] = lemmas.map((e) => e.toJson()).toList(); + data[ModelKey.tokens] = tokens.map((e) => e.toJson()).toList(); if (!condensed) { data['description'] = description; diff --git a/lib/pangea/models/lemma.dart b/lib/pangea/models/lemma.dart index 017a7ab88..1dc44c4b5 100644 --- a/lib/pangea/models/lemma.dart +++ b/lib/pangea/models/lemma.dart @@ -8,22 +8,13 @@ class Lemma { /// [saveVocab] true - whether to save the lemma to the user's vocabulary /// vocab that are not saved: emails, urls, numbers, punctuation, etc. + /// server handles this determination final bool saveVocab; - /// [pos] ex "v" - part of speech of the lemma - /// https://universaldependencies.org/u/pos/ - final String pos; - - /// [morph] ex {} - morphological features of the lemma - /// https://universaldependencies.org/u/feat/ - final Map morph; - Lemma({ required this.text, required this.saveVocab, required this.form, - this.pos = '', - this.morph = const {}, }); factory Lemma.fromJson(Map json) { @@ -31,8 +22,6 @@ class Lemma { text: json['text'], saveVocab: json['save_vocab'] ?? json['saveVocab'] ?? false, form: json["form"] ?? json['text'], - pos: json['pos'] ?? '', - morph: json['morph'] ?? '{}', ); } @@ -41,8 +30,6 @@ class Lemma { 'text': text, 'save_vocab': saveVocab, 'form': form, - 'pos': pos, - 'morph': morph, }; } diff --git a/lib/pangea/models/pangea_token_model.dart b/lib/pangea/models/pangea_token_model.dart index 9dddd149b..7055c29fa 100644 --- a/lib/pangea/models/pangea_token_model.dart +++ b/lib/pangea/models/pangea_token_model.dart @@ -1,55 +1,58 @@ import 'dart:developer'; import 'package:flutter/foundation.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; import '../constants/model_keys.dart'; -import '../utils/error_handler.dart'; import 'lemma.dart'; class PangeaToken { PangeaTokenText text; - List lemmas; + Lemma lemma; + + /// [pos] ex "VERB" - part of speech of the token + /// https://universaldependencies.org/u/pos/ + final String pos; + + /// [morph] ex {} - morphological features of the token + /// https://universaldependencies.org/u/feat/ + final Map morph; PangeaToken({ required this.text, - required this.lemmas, + required this.lemma, + required this.pos, + required this.morph, }); - static getLemmas(String text, Iterable? json) { + static _getLemmas(String text, dynamic json) { if (json != null) { - return json - .map( - (e) => Lemma.fromJson(e as Map), - ) - .toList() - .cast(); + // July 24, 2024 - we're changing from a list to a single lemma and this is for backwards compatibility + // previously sent tokens have lists of lemmas + if (json is Iterable) { + return json + .map( + (e) => Lemma.fromJson(e as Map), + ) + .toList() + .cast(); + } else { + return Lemma.fromJson(json); + } } else { - return [Lemma(text: text, saveVocab: false, form: text)]; + // earlier still, we didn't have lemmas so this is for really old tokens + return Lemma(text: text, saveVocab: false, form: text); } } factory PangeaToken.fromJson(Map json) { - try { - final PangeaTokenText text = - PangeaTokenText.fromJson(json[_textKey] as Map); - return PangeaToken( - text: text, - lemmas: getLemmas(text.content, json[_lemmaKey]), - ); - } catch (err, s) { - debugger(when: kDebugMode); - Sentry.addBreadcrumb( - Breadcrumb( - message: "PangeaToken.fromJson error", - data: { - "json": json, - }, - ), - ); - ErrorHandler.logError(e: err, s: s); - rethrow; - } + final PangeaTokenText text = + PangeaTokenText.fromJson(json[_textKey] as Map); + return PangeaToken( + text: text, + lemma: _getLemmas(text.content, json[_lemmaKey]), + pos: json['pos'] ?? '', + morph: json['morph'] ?? '{}', + ); } static const String _textKey = "text"; @@ -57,7 +60,7 @@ class PangeaToken { Map toJson() => { _textKey: text.toJson(), - _lemmaKey: lemmas.map((e) => e.toJson()).toList(), + _lemmaKey: lemma.toJson(), }; int get end => text.offset + text.length; diff --git a/lib/pangea/repo/igc_repo.dart b/lib/pangea/repo/igc_repo.dart index d6271470a..e32f6cab7 100644 --- a/lib/pangea/repo/igc_repo.dart +++ b/lib/pangea/repo/igc_repo.dart @@ -47,23 +47,33 @@ class IgcRepo { tokens: [ PangeaToken( text: PangeaTokenText(content: "This", offset: 0, length: 4), - lemmas: [Lemma(form: "This", text: "this", saveVocab: true)], + lemma: Lemma(form: "This", text: "this", saveVocab: true), + pos: "DET", + morph: {}, ), PangeaToken( text: PangeaTokenText(content: "be", offset: 5, length: 2), - lemmas: [Lemma(form: "be", text: "be", saveVocab: true)], + lemma: Lemma(form: "be", text: "be", saveVocab: true), + pos: "VERB", + morph: {}, ), PangeaToken( text: PangeaTokenText(content: "a", offset: 8, length: 1), - lemmas: [], + lemma: Lemma(form: "a", text: "a", saveVocab: true), + pos: "DET", + morph: {}, ), PangeaToken( text: PangeaTokenText(content: "sample", offset: 10, length: 6), - lemmas: [], + lemma: Lemma(form: "sample", text: "sample", saveVocab: true), + pos: "NOUN", + morph: {}, ), PangeaToken( text: PangeaTokenText(content: "text", offset: 17, length: 4), - lemmas: [], + lemma: Lemma(form: "text", text: "text", saveVocab: true), + pos: "NOUN", + morph: {}, ), ], matches: [ From 77df13f5a9c4ef8e511390c9407ff66e9032383e Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Wed, 3 Jul 2024 18:10:49 -0400 Subject: [PATCH 07/51] --amend --- .../pangea_message_event.dart | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index 0dfaa149a..530a7a547 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -687,7 +687,8 @@ class PangeaMessageEvent { for (final itStep in originalSent!.choreo!.itSteps) { for (final continuance in itStep.continuances) { - // this seems to always be false for continuances right now + final List tokensToSave = + continuance.tokens.where((t) => t.lemma.saveVocab).toList(); if (originalSent!.choreo!.finalMessage.contains(continuance.text)) { continue; @@ -695,7 +696,7 @@ class PangeaMessageEvent { if (continuance.wasClicked) { //PTODO - account for end of flow score if (continuance.level != ChoreoConstants.levelThresholdForGreen) { - for (final token in continuance.tokens) { + for (final token in tokensToSave) { uses.add( _lemmaToVocabUse( token.lemma, @@ -706,7 +707,7 @@ class PangeaMessageEvent { } } else { if (continuance.level != ChoreoConstants.levelThresholdForGreen) { - for (final token in continuance.tokens) { + for (final token in tokensToSave) { uses.add( _lemmaToVocabUse( token.lemma, @@ -732,7 +733,9 @@ class PangeaMessageEvent { } // for each token, record whether selected in ga, ta, or wa - for (final token in originalSent!.tokens!) { + for (final token in originalSent!.tokens! + .where((token) => token.lemma.saveVocab) + .toList()) { uses.add(_getVocabUseForToken(token)); } @@ -782,28 +785,19 @@ class PangeaMessageEvent { return _lemmaToVocabUse(token.lemma, ConstructUseTypeEnum.wa); } - /// Convert a list of [lemmas] into a list of vocab uses - /// with the given [type] OneConstructUse _lemmaToVocabUse( Lemma lemma, ConstructUseTypeEnum type, - ) { - final List uses = []; - if (lemma.saveVocab) { - uses.add( - OneConstructUse( - useType: type, - chatId: event.roomId!, - timeStamp: event.originServerTs, - lemma: lemma.text, - form: lemma.form, - msgId: event.eventId, - constructType: ConstructTypeEnum.vocab, - ), + ) => + OneConstructUse( + useType: type, + chatId: event.roomId!, + timeStamp: event.originServerTs, + lemma: lemma.text, + form: lemma.form, + msgId: event.eventId, + constructType: ConstructTypeEnum.vocab, ); - } - return uses; - } /// get construct uses of type grammar for the message List get _grammarConstructUses { From fbbbf5e740552bdedcd2bbe061a98122828a8431 Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Fri, 5 Jul 2024 14:26:36 -0400 Subject: [PATCH 08/51] added missing comma in intl_es, same thing for chat.dart as it was missing trailing comma, for my_analytics i added some error catches for that minified:tm bug --- assets/l10n/intl_es.arb | 2 +- lib/pages/chat/chat.dart | 4 +- .../controllers/my_analytics_controller.dart | 102 ++++++++++++------ 3 files changed, 70 insertions(+), 38 deletions(-) diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index 84de60836..684a418e3 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4721,7 +4721,7 @@ "versionNotFound": "Versión no encontrada", "fetchingVersion": "Obteniendo versión...", "versionFetchError": "Error al obtener la versión", - "connectedToStaging": "Conectado al entorno de pruebas" + "connectedToStaging": "Conectado al entorno de pruebas", "connectedToStaging": "Conectado al entorno de pruebas", "versionText": "Versión: {version}+{buildNumber}", "@versionText": { diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 5ac0a6547..07a20b287 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -523,7 +523,7 @@ class ChatController extends State ErrorHandler.logError( e: PangeaWarningError("Failed to set read marker: $e"), s: s, - m: 'Failed to set read marker for eventId: $eventId' + m: 'Failed to set read marker for eventId: $eventId', ); Sentry.captureException( e, @@ -531,7 +531,7 @@ class ChatController extends State withScope: (scope) { scope.setExtra('extra_info', 'Failed during setReadMarker with eventId: $eventId'); scope.setTag('where', 'setReadMarker'); - } + }, ); }); diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 535af6b8b..88d5ee1d2 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart' import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import '../extensions/client_extension/client_extension.dart'; import '../extensions/pangea_room_extension/pangea_room_extension.dart'; @@ -113,20 +114,36 @@ class MyAnalyticsController { // adds an event ID to the cache of un-added event IDs // if the event IDs isn't already added void addMessageSinceUpdate(String eventId) { - final List currentCache = messagesSinceUpdate; - if (!currentCache.contains(eventId)) { - currentCache.add(eventId); - _pangeaController.pStoreService.save( - PLocalKey.messagesSinceUpdate, - currentCache, - local: true, - ); - } + try{ + final List currentCache = messagesSinceUpdate; + if (!currentCache.contains(eventId)) { + currentCache.add(eventId); + _pangeaController.pStoreService.save( + PLocalKey.messagesSinceUpdate, + currentCache, + local: true, + ); + } - // if the cached has reached if max-length, update analytics - if (messagesSinceUpdate.length > _maxMessagesCached) { - debugPrint("reached max messages, updating"); - updateAnalytics(); + // if the cached has reached if max-length, update analytics + if (messagesSinceUpdate.length > _maxMessagesCached) { + debugPrint("reached max messages, updating"); + updateAnalytics(); + } + } catch (exception, stackTrace) { + ErrorHandler.logError( + e: PangeaWarningError("Failed to add message since update: $exception"), + s: stackTrace, + m: 'Failed to add message since update for eventId: $eventId' + ); + Sentry.captureException( + exception, + stackTrace: stackTrace, + withScope: (scope) { + scope.setExtra('extra_info', 'Failed during addMessageSinceUpdate with eventId: $eventId'); + scope.setTag('where', 'addMessageSinceUpdate'); + } + ); } } @@ -143,30 +160,45 @@ class MyAnalyticsController { // it's possible for this cache to be invalid or deleted // It's a proxy measure for messages sent since last update List get messagesSinceUpdate { - final dynamic locallySaved = _pangeaController.pStoreService.read( - PLocalKey.messagesSinceUpdate, - local: true, - ); - if (locallySaved == null) { - _pangeaController.pStoreService.save( - PLocalKey.messagesSinceUpdate, - [], - local: true, - ); - return []; - } - try { - return locallySaved as List; - } catch (err) { - _pangeaController.pStoreService.save( - PLocalKey.messagesSinceUpdate, - [], - local: true, - ); - return []; + Logs().d('Reading messages since update from local storage'); + final dynamic locallySaved = _pangeaController.pStoreService.read( + PLocalKey.messagesSinceUpdate, + local: true, + ); + if (locallySaved == null) { + Logs().d('No locally saved messages found, initializing empty list.'); + _pangeaController.pStoreService.save( + PLocalKey.messagesSinceUpdate, + [], + local: true, + ); + return []; + } + return locallySaved as List; + } catch (exception, stackTrace) { + ErrorHandler.logError( + e: PangeaWarningError("Failed to get messages since update: $exception"), + s: stackTrace, + m: 'Failed to retrieve messages since update' + ); + Sentry.captureException( + exception, + stackTrace: stackTrace, + withScope: (scope) { + scope.setExtra('extra_info', 'Error during messagesSinceUpdate getter'); + scope.setTag('where', 'messagesSinceUpdate'); + } + ); + _pangeaController.pStoreService.save( + PLocalKey.messagesSinceUpdate, + [], + local: true, + ); + return []; } - } +} + Completer? _updateCompleter; Future updateAnalytics() async { From bca1b87677aa62cce8d730cadd012b21c6d48dd7 Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Sun, 7 Jul 2024 19:31:29 -0400 Subject: [PATCH 09/51] added span display details to activity --- .../controllers/language_list_controller.dart | 1 - .../activity_display_instructions_enum.dart | 13 +++++ lib/pangea/models/pangea_token_model.dart | 16 ++++-- .../multiple_choice_activity_model.dart | 7 +++ .../practice_activity_model.dart | 56 +++++++++++++++++++ 5 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 lib/pangea/enum/activity_display_instructions_enum.dart diff --git a/lib/pangea/controllers/language_list_controller.dart b/lib/pangea/controllers/language_list_controller.dart index 31e4513fa..77f4ae9e2 100644 --- a/lib/pangea/controllers/language_list_controller.dart +++ b/lib/pangea/controllers/language_list_controller.dart @@ -93,7 +93,6 @@ class PangeaLanguage { } static LanguageModel byLangCode(String langCode) { - final list = _langList; for (final element in _langList) { if (element.langCode == langCode) return element; } diff --git a/lib/pangea/enum/activity_display_instructions_enum.dart b/lib/pangea/enum/activity_display_instructions_enum.dart new file mode 100644 index 000000000..9a96d669c --- /dev/null +++ b/lib/pangea/enum/activity_display_instructions_enum.dart @@ -0,0 +1,13 @@ +enum ActivityDisplayInstructionsEnum { highlight, hide } + +extension ActivityDisplayInstructionsEnumExt + on ActivityDisplayInstructionsEnum { + String get string { + switch (this) { + case ActivityDisplayInstructionsEnum.highlight: + return 'highlight'; + case ActivityDisplayInstructionsEnum.hide: + return 'hide'; + } + } +} diff --git a/lib/pangea/models/pangea_token_model.dart b/lib/pangea/models/pangea_token_model.dart index 7055c29fa..608d5b9d1 100644 --- a/lib/pangea/models/pangea_token_model.dart +++ b/lib/pangea/models/pangea_token_model.dart @@ -24,17 +24,19 @@ class PangeaToken { required this.morph, }); - static _getLemmas(String text, dynamic json) { + static Lemma _getLemmas(String text, dynamic json) { if (json != null) { // July 24, 2024 - we're changing from a list to a single lemma and this is for backwards compatibility // previously sent tokens have lists of lemmas if (json is Iterable) { return json - .map( - (e) => Lemma.fromJson(e as Map), - ) - .toList() - .cast(); + .map( + (e) => Lemma.fromJson(e as Map), + ) + .toList() + .cast() + .firstOrNull ?? + Lemma(text: text, saveVocab: false, form: text); } else { return Lemma.fromJson(json); } @@ -61,6 +63,8 @@ class PangeaToken { Map toJson() => { _textKey: text.toJson(), _lemmaKey: lemma.toJson(), + 'pos': pos, + 'morph': morph, }; int get end => text.offset + text.length; diff --git a/lib/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart b/lib/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart index 68376d410..18302bd43 100644 --- a/lib/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart +++ b/lib/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart @@ -1,15 +1,18 @@ import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart'; import 'package:flutter/material.dart'; class MultipleChoice { final String question; final List choices; final String answer; + final RelevantSpanDisplayDetails? spanDisplayDetails; MultipleChoice({ required this.question, required this.choices, required this.answer, + this.spanDisplayDetails, }); bool isCorrect(int index) => index == correctAnswerIndex; @@ -28,6 +31,9 @@ class MultipleChoice { question: json['question'] as String, choices: (json['choices'] as List).map((e) => e as String).toList(), answer: json['answer'] ?? json['correct_answer'] as String, + spanDisplayDetails: json['span_display_details'] != null + ? RelevantSpanDisplayDetails.fromJson(json['span_display_details']) + : null, ); } @@ -36,6 +42,7 @@ class MultipleChoice { 'question': question, 'choices': choices, 'answer': answer, + 'span_display_details': spanDisplayDetails, }; } } diff --git a/lib/pangea/models/practice_activities.dart/practice_activity_model.dart b/lib/pangea/models/practice_activities.dart/practice_activity_model.dart index fa3e25acf..645d550e5 100644 --- a/lib/pangea/models/practice_activities.dart/practice_activity_model.dart +++ b/lib/pangea/models/practice_activities.dart/practice_activity_model.dart @@ -1,5 +1,7 @@ import 'dart:developer'; +import 'package:collection/collection.dart'; +import 'package:fluffychat/pangea/enum/activity_display_instructions_enum.dart'; import 'package:fluffychat/pangea/enum/activity_type_enum.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart'; @@ -279,4 +281,58 @@ class PracticeActivityModel { 'free_response': freeResponse?.toJson(), }; } + + RelevantSpanDisplayDetails? getRelevantSpanDisplayDetails() { + switch (activityType) { + case ActivityTypeEnum.multipleChoice: + return multipleChoice?.spanDisplayDetails; + case ActivityTypeEnum.listening: + return null; + case ActivityTypeEnum.speaking: + return null; + case ActivityTypeEnum.freeResponse: + return null; + default: + debugger(when: kDebugMode); + return null; + } + } +} + +/// For those activities with a relevant span, this class will hold the details +/// of the span and how it should be displayed +/// e.g. hide the span for conjugation activities +class RelevantSpanDisplayDetails { + final int offset; + final int length; + final ActivityDisplayInstructionsEnum displayInstructions; + + RelevantSpanDisplayDetails({ + required this.offset, + required this.length, + required this.displayInstructions, + }); + + factory RelevantSpanDisplayDetails.fromJson(Map json) { + final ActivityDisplayInstructionsEnum? display = + ActivityDisplayInstructionsEnum.values.firstWhereOrNull( + (e) => e.string == json['display_instructions'], + ); + if (display == null) { + debugger(when: kDebugMode); + } + return RelevantSpanDisplayDetails( + offset: json['offset'] as int, + length: json['length'] as int, + displayInstructions: display ?? ActivityDisplayInstructionsEnum.hide, + ); + } + + Map toJson() { + return { + 'offset': offset, + 'length': length, + 'display_instructions': displayInstructions, + }; + } } From 4bc49420c29bf1d955a81824a16a7099f6bbeebb Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 8 Jul 2024 10:43:31 -0400 Subject: [PATCH 10/51] some formatting fixes --- lib/pages/chat/chat.dart | 106 ++++++++++-------- lib/pages/settings/settings_view.dart | 89 +++++++-------- .../controllers/my_analytics_controller.dart | 103 +++++++++-------- lib/pangea/utils/error_handler.dart | 6 +- 4 files changed, 161 insertions(+), 143 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 07a20b287..f998b62b3 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -43,9 +43,9 @@ import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import 'package:matrix/matrix.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:universal_html/html.dart' as html; -import 'package:sentry_flutter/sentry_flutter.dart'; import '../../utils/account_bundles.dart'; import '../../utils/localized_exception_extension.dart'; @@ -482,35 +482,46 @@ class ChatController extends State Future? _setReadMarkerFuture; void setReadMarker({String? eventId}) { - if (_setReadMarkerFuture != null) return; - if (_scrolledUp) return; - if (scrollUpBannerEventId != null) return; - if (eventId == null && !room.hasNewMessages && room.notificationCount == 0) { - return; - } + if (_setReadMarkerFuture != null) return; + if (_scrolledUp) return; + if (scrollUpBannerEventId != null) return; + if (eventId == null && + !room.hasNewMessages && + room.notificationCount == 0) { + return; + } - // Do not send read markers when app is not in foreground - // #Pangea - try { - // Pangea# - if (kIsWeb && !Matrix.of(context).webHasFocus) return; + // Do not send read markers when app is not in foreground // #Pangea - } catch (err, s) { - ErrorHandler.logError(e: PangeaWarningError("Web focus error: $err"), s: s); - return; - } - // Pangea# - if (!kIsWeb && WidgetsBinding.instance.lifecycleState != AppLifecycleState.resumed) { - return; - } + try { + // Pangea# + if (kIsWeb && !Matrix.of(context).webHasFocus) return; + // #Pangea + } catch (err, s) { + ErrorHandler.logError( + e: PangeaWarningError("Web focus error: $err"), + s: s, + ); + return; + } + // Pangea# + if (!kIsWeb && + WidgetsBinding.instance.lifecycleState != AppLifecycleState.resumed) { + return; + } - final timeline = this.timeline; - if (timeline == null || timeline.events.isEmpty) { - ErrorHandler.logError(e: PangeaWarningError("Timeline is null or empty"), s: StackTrace.current); - return; - } + final timeline = this.timeline; + if (timeline == null || timeline.events.isEmpty) { + // #Pangea + ErrorHandler.logError( + e: PangeaWarningError("Timeline is null or empty"), + s: StackTrace.current, + ); + // Pangea# + return; + } - Logs().d('Set read marker...', eventId); + Logs().d('Set read marker...', eventId); // ignore: unawaited_futures _setReadMarkerFuture = timeline .setReadMarker( @@ -519,27 +530,32 @@ class ChatController extends State ) .then((_) { _setReadMarkerFuture = null; - }).catchError((e, s) { - ErrorHandler.logError( - e: PangeaWarningError("Failed to set read marker: $e"), - s: s, - m: 'Failed to set read marker for eventId: $eventId', - ); - Sentry.captureException( - e, - stackTrace: s, - withScope: (scope) { - scope.setExtra('extra_info', 'Failed during setReadMarker with eventId: $eventId'); - scope.setTag('where', 'setReadMarker'); - }, - ); - }); + }) + // #Pangea + .catchError((e, s) { + ErrorHandler.logError( + e: PangeaWarningError("Failed to set read marker: $e"), + s: s, + m: 'Failed to set read marker for eventId: $eventId', + ); + Sentry.captureException( + e, + stackTrace: s, + withScope: (scope) { + scope.setExtra( + 'extra_info', + 'Failed during setReadMarker with eventId: $eventId', + ); + scope.setTag('where', 'setReadMarker'); + }, + ); + }); + // Pangea# - if (eventId == null || eventId == timeline.room.lastEvent?.eventId) { - Matrix.of(context).backgroundPush?.cancelNotification(roomId); + if (eventId == null || eventId == timeline.room.lastEvent?.eventId) { + Matrix.of(context).backgroundPush?.cancelNotification(roomId); + } } -} - @override void dispose() { diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index 06e4855ec..c7e9d6e64 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -8,10 +8,8 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; -import 'package:url_launcher/url_launcher_string.dart'; - import 'package:package_info_plus/package_info_plus.dart'; //adding to check app version - +import 'package:url_launcher/url_launcher_string.dart'; import 'settings.dart'; @@ -20,13 +18,13 @@ class SettingsView extends StatelessWidget { const SettingsView(this.controller, {super.key}); + // #Pangea Future getAppVersion(BuildContext context) async { - PackageInfo packageInfo = await PackageInfo.fromPlatform(); - return L10n.of(context)!.versionText(packageInfo.version, packageInfo.buildNumber); -} - - - + final PackageInfo packageInfo = await PackageInfo.fromPlatform(); + return L10n.of(context)! + .versionText(packageInfo.version, packageInfo.buildNumber); + } + // Pangea# @override Widget build(BuildContext context) { @@ -55,8 +53,6 @@ class SettingsView extends StatelessWidget { key: const Key('SettingsListViewContent'), children: [ FutureBuilder( - - future: controller.profileFuture, builder: (context, snapshot) { final profile = snapshot.data; @@ -149,8 +145,6 @@ class SettingsView extends StatelessWidget { ], ); }, - - ), // #Pangea // Divider( @@ -260,40 +254,41 @@ class SettingsView extends StatelessWidget { // onTap: () => PlatformInfos.showDialog(context), // trailing: const Icon(Icons.chevron_right_outlined), // ), - ListTile( - leading: const Icon(Icons.shield_outlined), - title: Text(L10n.of(context)!.termsAndConditions), - onTap: () => launchUrlString(AppConfig.termsOfServiceUrl), - trailing: const Icon(Icons.open_in_new_outlined), - ), - // Adding the FutureBuilder here - FutureBuilder( - future: getAppVersion(context), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - return ListTile( - leading: Icon(Icons.info_outline), - title: Text(snapshot.data ?? L10n.of(context)!.versionNotFound), - ); - } else if (snapshot.hasError) { - return ListTile( - leading: Icon(Icons.error_outline), - title: Text(L10n.of(context)!.versionFetchError), - ); - } else { - return ListTile( - leading: CircularProgressIndicator(), - title: Text(L10n.of(context)!.fetchingVersion), - ); - } - }, - ), - // Conditional ListTile based on the environment (staging or not) - if (Environment.isStaging) - ListTile( - leading: const Icon(Icons.bug_report_outlined), - title: Text(L10n.of(context)!.connectedToStaging), - ), + ListTile( + leading: const Icon(Icons.shield_outlined), + title: Text(L10n.of(context)!.termsAndConditions), + onTap: () => launchUrlString(AppConfig.termsOfServiceUrl), + trailing: const Icon(Icons.open_in_new_outlined), + ), + FutureBuilder( + future: getAppVersion(context), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return ListTile( + leading: const Icon(Icons.info_outline), + title: Text( + snapshot.data ?? L10n.of(context)!.versionNotFound, + ), + ); + } else if (snapshot.hasError) { + return ListTile( + leading: const Icon(Icons.error_outline), + title: Text(L10n.of(context)!.versionFetchError), + ); + } else { + return ListTile( + leading: const CircularProgressIndicator(), + title: Text(L10n.of(context)!.fetchingVersion), + ); + } + }, + ), + // Conditional ListTile based on the environment (staging or not) + if (Environment.isStaging) + ListTile( + leading: const Icon(Icons.bug_report_outlined), + title: Text(L10n.of(context)!.connectedToStaging), + ), // Pangea# ], ), diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 88d5ee1d2..f7017ad0e 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -114,7 +114,7 @@ class MyAnalyticsController { // adds an event ID to the cache of un-added event IDs // if the event IDs isn't already added void addMessageSinceUpdate(String eventId) { - try{ + try { final List currentCache = messagesSinceUpdate; if (!currentCache.contains(eventId)) { currentCache.add(eventId); @@ -130,20 +130,23 @@ class MyAnalyticsController { debugPrint("reached max messages, updating"); updateAnalytics(); } - } catch (exception, stackTrace) { - ErrorHandler.logError( - e: PangeaWarningError("Failed to add message since update: $exception"), - s: stackTrace, - m: 'Failed to add message since update for eventId: $eventId' - ); - Sentry.captureException( - exception, - stackTrace: stackTrace, - withScope: (scope) { - scope.setExtra('extra_info', 'Failed during addMessageSinceUpdate with eventId: $eventId'); - scope.setTag('where', 'addMessageSinceUpdate'); - } - ); + } catch (exception, stackTrace) { + ErrorHandler.logError( + e: PangeaWarningError("Failed to add message since update: $exception"), + s: stackTrace, + m: 'Failed to add message since update for eventId: $eventId', + ); + Sentry.captureException( + exception, + stackTrace: stackTrace, + withScope: (scope) { + scope.setExtra( + 'extra_info', + 'Failed during addMessageSinceUpdate with eventId: $eventId', + ); + scope.setTag('where', 'addMessageSinceUpdate'); + }, + ); } } @@ -161,44 +164,48 @@ class MyAnalyticsController { // It's a proxy measure for messages sent since last update List get messagesSinceUpdate { try { - Logs().d('Reading messages since update from local storage'); - final dynamic locallySaved = _pangeaController.pStoreService.read( + Logs().d('Reading messages since update from local storage'); + final dynamic locallySaved = _pangeaController.pStoreService.read( + PLocalKey.messagesSinceUpdate, + local: true, + ); + if (locallySaved == null) { + Logs().d('No locally saved messages found, initializing empty list.'); + _pangeaController.pStoreService.save( PLocalKey.messagesSinceUpdate, + [], local: true, ); - if (locallySaved == null) { - Logs().d('No locally saved messages found, initializing empty list.'); - _pangeaController.pStoreService.save( - PLocalKey.messagesSinceUpdate, - [], - local: true, - ); - return []; - } - return locallySaved as List; - } catch (exception, stackTrace) { - ErrorHandler.logError( - e: PangeaWarningError("Failed to get messages since update: $exception"), - s: stackTrace, - m: 'Failed to retrieve messages since update' - ); - Sentry.captureException( - exception, - stackTrace: stackTrace, - withScope: (scope) { - scope.setExtra('extra_info', 'Error during messagesSinceUpdate getter'); - scope.setTag('where', 'messagesSinceUpdate'); - } - ); - _pangeaController.pStoreService.save( - PLocalKey.messagesSinceUpdate, - [], - local: true, - ); return []; + } + return locallySaved.cast(); + } catch (exception, stackTrace) { + ErrorHandler.logError( + e: PangeaWarningError( + "Failed to get messages since update: $exception", + ), + s: stackTrace, + m: 'Failed to retrieve messages since update', + ); + Sentry.captureException( + exception, + stackTrace: stackTrace, + withScope: (scope) { + scope.setExtra( + 'extra_info', + 'Error during messagesSinceUpdate getter', + ); + scope.setTag('where', 'messagesSinceUpdate'); + }, + ); + _pangeaController.pStoreService.save( + PLocalKey.messagesSinceUpdate, + [], + local: true, + ); + return []; } -} - + } Completer? _updateCompleter; Future updateAnalytics() async { diff --git a/lib/pangea/utils/error_handler.dart b/lib/pangea/utils/error_handler.dart index eb406d48a..65f73c15b 100644 --- a/lib/pangea/utils/error_handler.dart +++ b/lib/pangea/utils/error_handler.dart @@ -11,6 +11,9 @@ import 'package:sentry_flutter/sentry_flutter.dart'; class PangeaWarningError implements Exception { final String message; PangeaWarningError(message) : message = "Pangea Warning Error: $message"; + + @override + String toString() => message; } class ErrorHandler { @@ -60,8 +63,6 @@ class ErrorHandler { if (m != null) debugPrint("error message: $m"); } - if ((e ?? m) != null) debugPrint("error to string: ${e?.toString() ?? m}"); - if (data != null) { Sentry.addBreadcrumb(Breadcrumb.fromJson(data)); debugPrint(data.toString()); @@ -75,7 +76,6 @@ class ErrorHandler { }, ); } - } class ErrorCopy { From 6557da36fc8917fa72e5b3c4986c6fe7de4d177e Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 8 Jul 2024 14:39:36 -0400 Subject: [PATCH 11/51] make send button color based on igc state --- lib/pangea/choreographer/widgets/send_button.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/pangea/choreographer/widgets/send_button.dart b/lib/pangea/choreographer/widgets/send_button.dart index fa3189df5..3f034b736 100644 --- a/lib/pangea/choreographer/widgets/send_button.dart +++ b/lib/pangea/choreographer/widgets/send_button.dart @@ -1,4 +1,4 @@ -import 'package:fluffychat/pangea/constants/colors.dart'; +import 'package:fluffychat/pangea/enum/assistance_state_enum.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -28,10 +28,8 @@ class ChoreographerSendButton extends StatelessWidget { alignment: Alignment.center, child: IconButton( icon: const Icon(Icons.send_outlined), - color: controller.choreographer.igc.canSendMessage || - !controller.choreographer.isAutoIGCEnabled - ? null - : PangeaColors.igcError, + color: + controller.choreographer.assistanceState.stateColor(context), onPressed: () { controller.choreographer.send(context); }, From 9431559e49309b425973b7a7163b90bacf52c0b5 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 8 Jul 2024 14:43:36 -0400 Subject: [PATCH 12/51] removed function that adds dms to spaces --- lib/pangea/controllers/class_controller.dart | 36 -------------------- 1 file changed, 36 deletions(-) diff --git a/lib/pangea/controllers/class_controller.dart b/lib/pangea/controllers/class_controller.dart index b4bc18b87..64a8340ae 100644 --- a/lib/pangea/controllers/class_controller.dart +++ b/lib/pangea/controllers/class_controller.dart @@ -17,7 +17,6 @@ import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/matrix.dart'; import '../../widgets/matrix.dart'; -import '../utils/bot_name.dart'; import '../utils/firebase_analytics.dart'; import 'base_controller.dart'; @@ -69,41 +68,6 @@ class ClassController extends BaseController { } } - /// if not bot chat return - /// if bot chat, get pangeaClassContext - /// for all classes not in pangeaClassContext, add bot chat to that class - /// PTODO - add analytics bot to all chats and have that do this work - Future> addDirectChatsToClasses(Room room) async { - if (!room.isDirectChat) return []; - final List existingParentsIds = - room.pangeaSpaceParents.map((e) => e.id).toList(); - final List spaces = _pangeaController.matrixState.client.spacesImIn; - - //make sure we have the latest participants - await Future.wait(spaces.map((e) => e.requestParticipants())); - - //get spaces where, - //other chat participant is the bot OR is in the space AND the chat is not - final List spacesToAdd = spaces - .where( - (s) => - (room.directChatMatrixID == BotName.byEnvironment || - s - .getParticipants() - .map( - (u) => u.id, - ) - .contains(room.directChatMatrixID)) && - !existingParentsIds.contains(s.id), - ) - .toList(); - - //set the space child for each space - return Future.wait( - spacesToAdd.map((s) => s.setSpaceChild(room.id, suggested: true)), - ).then((value) => spaces); - } - Future joinClasswithCode(BuildContext context, String classCode) async { try { final QueryPublicRoomsResponse queryPublicRoomsResponse = From 9fa9078ea469f7882bb0a006da9a96b323c53682 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 8 Jul 2024 15:19:55 -0400 Subject: [PATCH 13/51] prevent adding a room to multiple spaces --- lib/pages/chat_list/chat_list.dart | 17 +++----- lib/pages/chat_list/chat_list_header.dart | 6 ++- lib/pages/chat_list/space_view.dart | 34 ++++++++------- lib/pages/new_group/new_group.dart | 2 +- lib/pages/new_space/new_space.dart | 2 +- .../children_and_parents_extension.dart | 23 +++++++++++ .../pangea_room_extension.dart | 6 +++ .../room_settings_extension.dart | 2 +- lib/pangea/utils/add_to_space.dart | 41 ------------------- lib/pangea/utils/class_chat_power_levels.dart | 7 ++-- .../widgets/class/add_space_toggles.dart | 38 +++++++---------- 11 files changed, 76 insertions(+), 102 deletions(-) delete mode 100644 lib/pangea/utils/add_to_space.dart diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 89f31fd68..803b773e1 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -9,7 +9,6 @@ import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/utils/add_to_space.dart'; import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; @@ -843,18 +842,12 @@ class ChatListController extends State if (firstSelectedRoom.isSpace && !space.isRoomAdmin) { throw L10n.of(context)!.cantAddSpaceChild; } - await pangeaAddToSpace( - space, - selectedRoomIds.toList(), - context, - pangeaController, - ); - // if (space.canSendDefaultStates) { - // for (final roomId in selectedRoomIds) { - // await space.setSpaceChild(roomId); - // } - // } + if (space.canSendDefaultStates) { + for (final roomId in selectedRoomIds) { + await space.pangeaSetSpaceChild(roomId); + } + } // Pangea# }, ); diff --git a/lib/pages/chat_list/chat_list_header.dart b/lib/pages/chat_list/chat_list_header.dart index 20f41ea08..b60a8b5be 100644 --- a/lib/pages/chat_list/chat_list_header.dart +++ b/lib/pages/chat_list/chat_list_header.dart @@ -137,7 +137,11 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget { ] : selectMode == SelectMode.select ? [ - if (controller.spaces.isNotEmpty) + // #Pangea + // if (controller.spaces.isNotEmpty) + if (controller.spaces.isNotEmpty && + controller.selectedRoomIds.length == 1) + // Pangea# IconButton( tooltip: L10n.of(context)!.addToSpace, icon: const Icon(Icons.workspaces_outlined), diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index e6c85a617..f3f9749a1 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -856,24 +856,22 @@ class _SpaceViewState extends State { ), // Pangea# ), - // #Pangea - // if (activeSpace?.canChangeStateEvent( - // EventTypes.SpaceChild, - // ) == - // true) - // Material( - // child: ListTile( - // leading: const CircleAvatar( - // child: Icon(Icons.group_add_outlined), - // ), - // title: - // Text(L10n.of(context)!.addChatOrSubSpace), - // trailing: - // const Icon(Icons.chevron_right_outlined), - // onTap: _addChatOrSubSpace, - // ), - // ), - // Pangea# + if (activeSpace?.canChangeStateEvent( + EventTypes.SpaceChild, + ) == + true) + Material( + child: ListTile( + leading: const CircleAvatar( + child: Icon(Icons.group_add_outlined), + ), + title: + Text(L10n.of(context)!.addChatOrSubSpace), + trailing: + const Icon(Icons.chevron_right_outlined), + onTap: _addChatOrSubSpace, + ), + ), ], ); } diff --git a/lib/pages/new_group/new_group.dart b/lib/pages/new_group/new_group.dart index 7e77efe56..9c6e22f85 100644 --- a/lib/pages/new_group/new_group.dart +++ b/lib/pages/new_group/new_group.dart @@ -134,7 +134,7 @@ class NewGroupController extends State { powerLevelContentOverride: await ClassChatPowerLevels.powerLevelOverrideForClassChat( context, - addToSpaceKey.currentState!.parents, + addToSpaceKey.currentState!.parent, ), invite: [ if (addConversationBotKey.currentState?.addBot ?? false) diff --git a/lib/pages/new_space/new_space.dart b/lib/pages/new_space/new_space.dart index 7a7759349..1903a6200 100644 --- a/lib/pages/new_space/new_space.dart +++ b/lib/pages/new_space/new_space.dart @@ -171,7 +171,7 @@ class NewSpaceController extends State { powerLevelContentOverride: addToSpaceKey.currentState != null ? await ClassChatPowerLevels.powerLevelOverrideForClassChat( context, - addToSpaceKey.currentState!.parents, + addToSpaceKey.currentState!.parent, ) : null, // initialState: [ diff --git a/lib/pangea/extensions/pangea_room_extension/children_and_parents_extension.dart b/lib/pangea/extensions/pangea_room_extension/children_and_parents_extension.dart index 2dc3f6f59..e89740788 100644 --- a/lib/pangea/extensions/pangea_room_extension/children_and_parents_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/children_and_parents_extension.dart @@ -138,4 +138,27 @@ extension ChildrenAndParentsRoomExtension on Room { child != null ? child._allSpaceChildRoomIds.contains(id) : false; return canAddChild && !isCycle; } + + /// Wrapper around call to setSpaceChild with added functionality + /// to prevent adding one room to multiple spaces + Future _pangeaSetSpaceChild( + String roomId, { + bool? suggested, + }) async { + final Room? child = client.getRoomById(roomId); + if (child != null) { + final List spaceParents = child.pangeaSpaceParents; + for (final Room parent in spaceParents) { + try { + await parent.removeSpaceChild(roomId); + } catch (e) { + ErrorHandler.logError( + e: e, + m: 'Failed to remove child from parent', + ); + } + } + await setSpaceChild(roomId, suggested: suggested); + } + } } diff --git a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart index 21f5abac5..8632f5e1e 100644 --- a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart @@ -116,6 +116,12 @@ extension PangeaRoom on Room { return _canAddAsParentOf(child, spaceMode: spaceMode); } + Future pangeaSetSpaceChild( + String roomId, { + bool? suggested, + }) async => + await _pangeaSetSpaceChild(roomId, suggested: suggested); + // class_and_exchange_settings DateTime? get rulesUpdatedAt => _rulesUpdatedAt; diff --git a/lib/pangea/extensions/pangea_room_extension/room_settings_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_settings_extension.dart index 399382f1c..4199e6f64 100644 --- a/lib/pangea/extensions/pangea_room_extension/room_settings_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_settings_extension.dart @@ -116,7 +116,7 @@ extension RoomSettingsRoomExtension on Room { Future _setSuggestedInSpace(bool suggest, Room space) async { try { - await space.setSpaceChild(id, suggested: suggest); + await space.pangeaSetSpaceChild(id, suggested: suggest); } catch (err) { ErrorHandler.logError( e: "Failed to set suggestion status of room $id in space ${space.id}", diff --git a/lib/pangea/utils/add_to_space.dart b/lib/pangea/utils/add_to_space.dart deleted file mode 100644 index f2ed26be1..000000000 --- a/lib/pangea/utils/add_to_space.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; - -bool canAddToSpace(Room space, PangeaController pangeaController) { - final bool pangeaPermission = - pangeaController.permissionsController.canUserGroupChat(roomID: space.id); - final Map powerLevelsMap = - space.getState(EventTypes.RoomPowerLevels)?.content ?? {}; - final pl = powerLevelsMap - .tryGetMap('events') - ?.tryGet(EventTypes.SpaceChild) ?? - powerLevelsMap.tryGet('events_default') ?? - 50; - return space.ownPowerLevel >= pl && pangeaPermission; -} - -bool chatIsInSpace(Room chat, Room space) { - return chat.spaceParents.map((e) => e.roomId).toList().contains(space.id); -} - -Future pangeaAddToSpace( - Room space, - List selectedRoomIds, - BuildContext context, - PangeaController pangeaController, { - bool suggested = true, -}) async { - if (!canAddToSpace(space, pangeaController)) { - throw L10n.of(context)!.noAddToSpacePermissions; - } - for (final roomId in selectedRoomIds) { - final Room? room = Matrix.of(context).client.getRoomById(roomId); - if (room != null && chatIsInSpace(room, space)) { - throw L10n.of(context)!.alreadyInSpace; - } - await space.setSpaceChild(roomId, suggested: suggested); - } -} diff --git a/lib/pangea/utils/class_chat_power_levels.dart b/lib/pangea/utils/class_chat_power_levels.dart index 1fbaff1bf..9c1c25b31 100644 --- a/lib/pangea/utils/class_chat_power_levels.dart +++ b/lib/pangea/utils/class_chat_power_levels.dart @@ -8,7 +8,7 @@ import '../extensions/pangea_room_extension/pangea_room_extension.dart'; class ClassChatPowerLevels { static Future> powerLevelOverrideForClassChat( BuildContext context, - List spaceParents, + Room? parent, ) async { final Client client = Matrix.of(context).client; final Map powerLevelOverride = {}; @@ -18,8 +18,9 @@ class ClassChatPowerLevels { powerLevelOverride['users'] = {}; final List spaceAdmin = []; - for (final classRoom in spaceParents) { - final List classTeachers = await classRoom.teachers; + + if (parent != null) { + final List classTeachers = await parent.teachers; spaceAdmin.addAll(classTeachers); } diff --git a/lib/pangea/widgets/class/add_space_toggles.dart b/lib/pangea/widgets/class/add_space_toggles.dart index abbef7746..c7d4fba71 100644 --- a/lib/pangea/widgets/class/add_space_toggles.dart +++ b/lib/pangea/widgets/class/add_space_toggles.dart @@ -32,7 +32,7 @@ class AddToSpaceToggles extends StatefulWidget { class AddToSpaceState extends State { late Room? room; - late List parents; + late Room? parent; late List possibleParents; late bool isOpen; late bool isSuggested; @@ -70,20 +70,17 @@ class AddToSpaceState extends State { ) .toList(); - parents = widget.roomId != null - ? possibleParents - .where( - (r) => - r.spaceChildren.any((room) => room.roomId == widget.roomId), - ) - .toList() - : []; + parent = widget.roomId != null + ? possibleParents.firstWhereOrNull( + (r) => r.spaceChildren.any((room) => room.roomId == widget.roomId), + ) + : null; if (widget.activeSpaceId != null) { final activeSpace = Matrix.of(context).client.getRoomById(widget.activeSpaceId!); if (activeSpace != null && activeSpace.canIAddSpaceChild(null)) { - parents.add(activeSpace); + parent = activeSpace; } else { ErrorHandler.logError( e: Exception('activeSpaceId ${widget.activeSpaceId} not found'), @@ -95,9 +92,9 @@ class AddToSpaceState extends State { //if possibleParent in parents, put first //use sort but use any instead of contains because contains uses == and we want to compare by id possibleParents.sort((a, b) { - if (parents.any((parent) => parent.id == a.id)) { + if (parent?.id == a.id) { return -1; - } else if (parents.any((parent) => parent.id == b.id)) { + } else if (parent?.id == b.id) { return 1; } else { return a.name.compareTo(b.name); @@ -109,18 +106,15 @@ class AddToSpaceState extends State { Future _addSingleSpace(String roomToAddId, Room newParent) async { GoogleAnalytics.addParent(roomToAddId, newParent.classCode); - await newParent.setSpaceChild( + await newParent.pangeaSetSpaceChild( roomToAddId, suggested: isSuggested, ); } Future addSpaces(String roomToAddId) async { - final List> addFutures = []; - for (final Room parent in parents) { - addFutures.add(_addSingleSpace(roomToAddId, parent)); - } - await addFutures.wait; + if (parent == null) return; + await _addSingleSpace(roomToAddId, parent!); } Future handleAdd(bool add, Room possibleParent) async { @@ -142,11 +136,7 @@ class AddToSpaceState extends State { } setState( - () => add - ? parents.add(possibleParent) - : parents.removeWhere( - (parent) => parent.id == possibleParent.id, - ), + () => add ? parent = possibleParent : parent = null, ); } @@ -164,7 +154,7 @@ class AddToSpaceState extends State { SwitchListTile.adaptive( title: possibleParent.nameAndRoomTypeIcon(), activeColor: AppConfig.activeToggleColor, - value: parents.any((r) => r.id == possibleParent.id), + value: parent?.id == possibleParent.id, onChanged: (bool add) => canAdd ? handleAdd(add, possibleParent) : ScaffoldMessenger.of(context).showSnackBar( From bd55811883777473536350f2e28d2834273d1896 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 8 Jul 2024 16:05:20 -0400 Subject: [PATCH 14/51] when user sees message translation for messages being dispalying in l1, show inline tooltip with explanation --- assets/l10n/intl_en.arb | 5 +- lib/pangea/enum/instructions_enum.dart | 21 ++++++-- lib/pangea/utils/inline_tooltip.dart | 3 +- lib/pangea/utils/instructions.dart | 36 +++++++------- .../chat/message_speech_to_text_card.dart | 8 +++- .../chat/message_translation_card.dart | 48 +++++++++++++++---- 6 files changed, 84 insertions(+), 37 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 08e36316d..09fa273e0 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4068,7 +4068,7 @@ "hintTitle": "Hint:", "speechToTextBody": "See how well you did by looking at your Accuracy and Words Per Minute scores", "previous": "Previous", - "versionNotFound": "Version Not Found", + "versionNotFound": "Version Not Found", "fetchingVersion": "Fetching version...", "versionFetchError": "Error fetching version", "connectedToStaging": "Connected to Staging", @@ -4104,5 +4104,6 @@ "type": "text", "placeholders": {} }, - "changeAnalyticsView": "Change Analytics View" + "changeAnalyticsView": "Change Analytics View", + "l1TranslationBody": "Oops! It looks like this message wasn't sent in your target language. Messages not sent in your target language will not be translated." } \ No newline at end of file diff --git a/lib/pangea/enum/instructions_enum.dart b/lib/pangea/enum/instructions_enum.dart index c684d5b14..376d004b5 100644 --- a/lib/pangea/enum/instructions_enum.dart +++ b/lib/pangea/enum/instructions_enum.dart @@ -7,7 +7,6 @@ enum InstructionsEnum { clickMessage, blurMeansTranslate, tooltipInstructions, - speechToText, } extension Copy on InstructionsEnum { @@ -21,8 +20,6 @@ extension Copy on InstructionsEnum { return L10n.of(context)!.blurMeansTranslateTitle; case InstructionsEnum.tooltipInstructions: return L10n.of(context)!.tooltipInstructionsTitle; - case InstructionsEnum.speechToText: - return L10n.of(context)!.hintTitle; } } @@ -34,8 +31,6 @@ extension Copy on InstructionsEnum { return L10n.of(context)!.clickMessageBody; case InstructionsEnum.blurMeansTranslate: return L10n.of(context)!.blurMeansTranslateBody; - case InstructionsEnum.speechToText: - return L10n.of(context)!.speechToTextBody; case InstructionsEnum.tooltipInstructions: return PlatformInfos.isMobile ? L10n.of(context)!.tooltipInstructionsMobileBody @@ -43,3 +38,19 @@ extension Copy on InstructionsEnum { } } } + +enum InlineInstructions { + speechToText, + l1Translation, +} + +extension InlineInstructionsExtension on InlineInstructions { + String body(BuildContext context) { + switch (this) { + case InlineInstructions.speechToText: + return L10n.of(context)!.speechToTextBody; + case InlineInstructions.l1Translation: + return L10n.of(context)!.l1TranslationBody; + } + } +} diff --git a/lib/pangea/utils/inline_tooltip.dart b/lib/pangea/utils/inline_tooltip.dart index 253288a84..21fc7321a 100644 --- a/lib/pangea/utils/inline_tooltip.dart +++ b/lib/pangea/utils/inline_tooltip.dart @@ -34,7 +34,7 @@ class InlineTooltip extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(10), child: RichText( - textAlign: TextAlign.justify, + textAlign: TextAlign.center, text: TextSpan( children: [ const WidgetSpan( @@ -50,6 +50,7 @@ class InlineTooltip extends StatelessWidget { text: body, style: TextStyle( color: Theme.of(context).colorScheme.onSurface, + height: 1.5, ), ), ], diff --git a/lib/pangea/utils/instructions.dart b/lib/pangea/utils/instructions.dart index 382268082..948d6a1e4 100644 --- a/lib/pangea/utils/instructions.dart +++ b/lib/pangea/utils/instructions.dart @@ -18,13 +18,13 @@ class InstructionsController { // We have these three methods to make sure that the instructions are not shown too much /// Instruction popup was closed by the user - final Map _instructionsClosed = {}; + final Map _instructionsClosed = {}; /// Instruction popup has already been shown this session - final Map _instructionsShown = {}; + final Map _instructionsShown = {}; /// Returns true if the user requested this popup not be shown again - bool? toggledOff(InstructionsEnum key) => + bool? toggledOff(String key) => _pangeaController.pStoreService.read(key.toString()); InstructionsController(PangeaController pangeaController) { @@ -33,18 +33,17 @@ class InstructionsController { /// Returns true if the instructions were closed /// or turned off by the user via the toggle switch - bool wereInstructionsTurnedOff(InstructionsEnum key) => + bool wereInstructionsTurnedOff(String key) => toggledOff(key) ?? _instructionsClosed[key] ?? false; - void turnOffInstruction(InstructionsEnum key) => - _instructionsClosed[key] = true; + void turnOffInstruction(String key) => _instructionsClosed[key] = true; Future updateEnableInstructions( - InstructionsEnum key, + String key, bool value, ) async => await _pangeaController.pStoreService.save( - key.toString(), + key, value, ); @@ -56,12 +55,12 @@ class InstructionsController { String transformTargetKey, [ bool showToggle = true, ]) async { - if (_instructionsShown[key] ?? false) { + if (_instructionsShown[key.toString()] ?? false) { return; } - _instructionsShown[key] = true; + _instructionsShown[key.toString()] = true; - if (wereInstructionsTurnedOff(key)) { + if (wereInstructionsTurnedOff(key.toString())) { return; } if (L10n.of(context) == null) { @@ -90,7 +89,7 @@ class InstructionsController { CardHeader( text: key.title(context), botExpression: BotExpression.idle, - onClose: () => {_instructionsClosed[key] = true}, + onClose: () => {_instructionsClosed[key.toString()] = true}, ), const SizedBox(height: 10.0), Expanded( @@ -118,10 +117,10 @@ class InstructionsController { /// which displays hint text defined in the enum extension Widget getInstructionInlineTooltip( BuildContext context, - InstructionsEnum key, + InlineInstructions key, VoidCallback onClose, ) { - if (wereInstructionsTurnedOff(key)) { + if (wereInstructionsTurnedOff(key.toString())) { return const SizedBox(); } @@ -134,7 +133,7 @@ class InstructionsController { } return InlineTooltip( - body: InstructionsEnum.speechToText.body(context), + body: InlineInstructions.speechToText.body(context), onClose: onClose, ); } @@ -167,11 +166,12 @@ class InstructionsToggleState extends State { return SwitchListTile.adaptive( activeColor: AppConfig.activeToggleColor, title: Text(L10n.of(context)!.doNotShowAgain), - value: pangeaController.instructions - .wereInstructionsTurnedOff(widget.instructionsKey), + value: pangeaController.instructions.wereInstructionsTurnedOff( + widget.instructionsKey.toString(), + ), onChanged: ((value) async { await pangeaController.instructions.updateEnableInstructions( - widget.instructionsKey, + widget.instructionsKey.toString(), value, ); setState(() {}); diff --git a/lib/pangea/widgets/chat/message_speech_to_text_card.dart b/lib/pangea/widgets/chat/message_speech_to_text_card.dart index a26a36658..3c06a45f3 100644 --- a/lib/pangea/widgets/chat/message_speech_to_text_card.dart +++ b/lib/pangea/widgets/chat/message_speech_to_text_card.dart @@ -67,7 +67,11 @@ class MessageSpeechToTextCardState extends State { void closeHint() { MatrixState.pangeaController.instructions.turnOffInstruction( - InstructionsEnum.speechToText, + InlineInstructions.speechToText.toString(), + ); + MatrixState.pangeaController.instructions.updateEnableInstructions( + InlineInstructions.speechToText.toString(), + true, ); setState(() {}); } @@ -204,7 +208,7 @@ class MessageSpeechToTextCardState extends State { ), MatrixState.pangeaController.instructions.getInstructionInlineTooltip( context, - InstructionsEnum.speechToText, + InlineInstructions.speechToText, closeHint, ), ], diff --git a/lib/pangea/widgets/chat/message_translation_card.dart b/lib/pangea/widgets/chat/message_translation_card.dart index 31b6addb2..fd80df134 100644 --- a/lib/pangea/widgets/chat/message_translation_card.dart +++ b/lib/pangea/widgets/chat/message_translation_card.dart @@ -1,8 +1,10 @@ +import 'package:fluffychat/pangea/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/repo/full_text_translation_repo.dart'; import 'package:fluffychat/pangea/utils/bot_style.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:fluffychat/pangea/utils/inline_tooltip.dart'; import 'package:fluffychat/pangea/widgets/chat/message_text_selection.dart'; import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart'; import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart'; @@ -119,6 +121,17 @@ class MessageTranslationCardState extends State { } } + void closeHint() { + MatrixState.pangeaController.instructions.turnOffInstruction( + InlineInstructions.l1Translation.toString(), + ); + MatrixState.pangeaController.instructions.updateEnableInstructions( + InlineInstructions.l1Translation.toString(), + true, + ); + setState(() {}); + } + @override Widget build(BuildContext context) { if (!_fetchingRepresentation && @@ -127,18 +140,35 @@ class MessageTranslationCardState extends State { return const CardErrorWidget(); } + final bool showWarning = l2Code != null && + !widget.immersionMode && + widget.messageEvent.originalSent?.langCode != l2Code && + !MatrixState.pangeaController.instructions.wereInstructionsTurnedOff( + InlineInstructions.l1Translation.toString(), + ); + return Container( child: _fetchingRepresentation ? const ToolbarContentLoadingIndicator() - : selectionTranslation != null - ? Text( - selectionTranslation!, - style: BotStyle.text(context), - ) - : Text( - repEvent!.text, - style: BotStyle.text(context), - ), + : Column( + children: [ + selectionTranslation != null + ? Text( + selectionTranslation!, + style: BotStyle.text(context), + ) + : Text( + repEvent!.text, + style: BotStyle.text(context), + ), + const SizedBox(height: 10), + if (showWarning) + InlineTooltip( + body: InlineInstructions.l1Translation.body(context), + onClose: closeHint, + ), + ], + ), ); } } From df0f290c41777b3e4b40e4173756b58a90461921 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 8 Jul 2024 16:33:50 -0400 Subject: [PATCH 15/51] Change copy in chat permissions page away from using numbers --- .../permission_list_tile.dart | 17 +++++++++++++---- .../client_extension/client_extension.dart | 4 ++++ .../general_info_extension.dart | 6 ++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/pages/chat_permissions_settings/permission_list_tile.dart b/lib/pages/chat_permissions_settings/permission_list_tile.dart index af447cbec..04a2d3a60 100644 --- a/lib/pages/chat_permissions_settings/permission_list_tile.dart +++ b/lib/pages/chat_permissions_settings/permission_list_tile.dart @@ -1,10 +1,10 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; - import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/config/app_config.dart'; - class PermissionsListTile extends StatelessWidget { final String permissionKey; final int permission; @@ -72,7 +72,16 @@ class PermissionsListTile extends StatelessWidget { return ListTile( title: Text(getLocalizedPowerLevelString(context)), subtitle: Text( - L10n.of(context)!.minimumPowerLevel(permission.toString()), + // #Pangea + // L10n.of(context)!.minimumPowerLevel(permission.toString()), + L10n.of(context)!.minimumPowerLevel( + Matrix.of(context).client.powerLevelName( + permission, + L10n.of(context)!, + ) ?? + permission.toString(), + ), + // Pangea# ), trailing: Material( borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), diff --git a/lib/pangea/extensions/client_extension/client_extension.dart b/lib/pangea/extensions/client_extension/client_extension.dart index 3dda99237..addcfd0ed 100644 --- a/lib/pangea/extensions/client_extension/client_extension.dart +++ b/lib/pangea/extensions/client_extension/client_extension.dart @@ -10,6 +10,7 @@ import 'package:fluffychat/pangea/models/space_model.dart'; import 'package:fluffychat/pangea/utils/bot_name.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; part "client_analytics_extension.dart"; @@ -76,4 +77,7 @@ extension PangeaClient on Client { String eventId, ) async => await _getEditHistory(roomId, eventId); + + String? powerLevelName(int powerLevel, L10n l10n) => + _powerLevelName(powerLevel, l10n); } diff --git a/lib/pangea/extensions/client_extension/general_info_extension.dart b/lib/pangea/extensions/client_extension/general_info_extension.dart index 26b87b533..aada940d7 100644 --- a/lib/pangea/extensions/client_extension/general_info_extension.dart +++ b/lib/pangea/extensions/client_extension/general_info_extension.dart @@ -72,4 +72,10 @@ extension GeneralInfoClientExtension on Client { editEvents.add(originalEvent); return editEvents.slice(1).map((e) => e.eventId).toList(); } + + String? _powerLevelName(int powerLevel, L10n l10n) => { + 0: l10n.user, + 50: l10n.moderator, + 100: l10n.admin, + }[powerLevel]; } From 8e1423709de222f4c32818d773e19acf72b89e30 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 9 Jul 2024 11:47:53 -0400 Subject: [PATCH 16/51] warn subscribed users to delete their subscription before deleting their accounts --- assets/l10n/intl_en.arb | 6 +++- .../settings_security/settings_security.dart | 30 ++++++++++++++++--- lib/pangea/models/base_subscription_info.dart | 15 ++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 09fa273e0..7d853de70 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4105,5 +4105,9 @@ "placeholders": {} }, "changeAnalyticsView": "Change Analytics View", - "l1TranslationBody": "Oops! It looks like this message wasn't sent in your target language. Messages not sent in your target language will not be translated." + "l1TranslationBody": "Oops! It looks like this message wasn't sent in your target language. Messages not sent in your target language will not be translated.", + "continueText": "Continue", + "deleteSubscriptionWarningTitle": "You have an active subscription", + "deleteSubscriptionWarningBody": "Deleting your account will not automatically cancel your subscription.", + "manageSubscription": "Manage Subscription" } \ No newline at end of file diff --git a/lib/pages/settings_security/settings_security.dart b/lib/pages/settings_security/settings_security.dart index 69069d693..859e33e89 100644 --- a/lib/pages/settings_security/settings_security.dart +++ b/lib/pages/settings_security/settings_security.dart @@ -1,12 +1,12 @@ -import 'package:flutter/material.dart'; - import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:fluffychat/widgets/app_lock.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/matrix.dart'; +import 'package:url_launcher/url_launcher_string.dart'; -import 'package:fluffychat/widgets/app_lock.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import '../bootstrap/bootstrap_dialog.dart'; import 'settings_security_view.dart'; @@ -51,6 +51,28 @@ class SettingsSecurityController extends State { } void deleteAccountAction() async { + // #Pangea + final subscriptionController = + MatrixState.pangeaController.subscriptionController; + if (subscriptionController.subscription?.isPaidSubscription == true && + subscriptionController.subscription?.defaultManagementURL != null) { + final resp = await showOkCancelAlertDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context)!.deleteSubscriptionWarningTitle, + message: L10n.of(context)!.deleteSubscriptionWarningBody, + okLabel: L10n.of(context)!.manageSubscription, + cancelLabel: L10n.of(context)!.continueText, + ); + if (resp == OkCancelResult.ok) { + launchUrlString( + subscriptionController.subscription!.defaultManagementURL!, + mode: LaunchMode.externalApplication, + ); + return; + } + } + // Pangea# if (await showOkCancelAlertDialog( useRootNavigator: false, context: context, diff --git a/lib/pangea/models/base_subscription_info.dart b/lib/pangea/models/base_subscription_info.dart index 399ba042c..469ac5786 100644 --- a/lib/pangea/models/base_subscription_info.dart +++ b/lib/pangea/models/base_subscription_info.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/subscription_controller.dart'; import 'package:fluffychat/pangea/repo/subscription_repo.dart'; @@ -51,6 +52,11 @@ class SubscriptionInfo { bool get currentSubscriptionIsPromotional => currentSubscriptionId?.startsWith("rc_promo") ?? false; + bool get isPaidSubscription => + (currentSubscription != null || currentSubscriptionId != null) && + !isNewUserTrial && + !currentSubscriptionIsPromotional; + bool get isLifetimeSubscription => currentSubscriptionIsPromotional && expirationDate != null && @@ -86,4 +92,13 @@ class SubscriptionInfo { } Future setCustomerInfo() async {} + + String? get defaultManagementURL { + final String? purchaseAppId = currentSubscription?.appId; + return purchaseAppId == appIds?.androidId + ? AppConfig.googlePlayMangementUrl + : purchaseAppId == appIds?.appleId + ? AppConfig.appleMangementUrl + : Environment.stripeManagementUrl; + } } From ef12529af4a2940b4f865828ce8b623036094822 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 9 Jul 2024 15:28:33 -0400 Subject: [PATCH 17/51] use match data to construct input underlines, without using tokens --- .../controllers/choreographer.dart | 15 +-- .../controllers/igc_controller.dart | 58 -------- lib/pangea/models/igc_text_data_model.dart | 126 ++++++++---------- 3 files changed, 61 insertions(+), 138 deletions(-) diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 66e11808b..24756f7a6 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -180,18 +180,9 @@ class Choreographer { return; } - if ([ - EditType.igc, - ].contains(_textController.editType)) { - // this may be unnecessary now that tokens are not used - // to allow click of words in the input field and we're getting this at the end - // TODO - turn it off and tested that this is fine - igc.justGetTokensAndAddThemToIGCTextData(); - - // we set editType to keyboard here because that is the default for it - // and we want to make sure that the next change is treated as a keyboard change - // unless the system explicity sets it to something else. this - textController.editType = EditType.keyboard; + if (_textController.editType == EditType.igc) { + _lastChecked = _textController.text; + _textController.editType = EditType.keyboard; return; } diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index 533b36c83..6295efa16 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -7,11 +7,9 @@ import 'package:fluffychat/pangea/choreographer/controllers/span_data_controller import 'package:fluffychat/pangea/models/igc_text_data_model.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; import 'package:fluffychat/pangea/repo/igc_repo.dart'; -import 'package:fluffychat/pangea/repo/tokens_repo.dart'; import 'package:fluffychat/pangea/widgets/igc/span_card.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; import '../../models/span_card_model.dart'; import '../../utils/error_handler.dart'; @@ -83,62 +81,6 @@ class IgcController { } } - Future justGetTokensAndAddThemToIGCTextData() async { - try { - if (igcTextData == null) { - debugger(when: kDebugMode); - choreographer.getLanguageHelp(); - return; - } - igcTextData!.loading = true; - choreographer.startLoading(); - if (igcTextData!.originalInput != choreographer.textController.text) { - debugger(when: kDebugMode); - ErrorHandler.logError( - m: "igcTextData fullText does not match current text", - s: StackTrace.current, - data: igcTextData!.toJson(), - ); - } - - if (choreographer.l1LangCode == null || - choreographer.l2LangCode == null) { - debugger(when: kDebugMode); - ErrorHandler.logError( - m: "l1LangCode and/or l2LangCode is null", - s: StackTrace.current, - data: { - "l1LangCode": choreographer.l1LangCode, - "l2LangCode": choreographer.l2LangCode, - }, - ); - return; - } - - final TokensResponseModel res = await TokensRepo.tokenize( - await choreographer.pangeaController.userController.accessToken, - TokensRequestModel( - fullText: igcTextData!.originalInput, - userL1: choreographer.l1LangCode!, - userL2: choreographer.l2LangCode!, - ), - ); - igcTextData?.tokens = res.tokens; - } catch (err, stack) { - debugger(when: kDebugMode); - choreographer.errorService.setError( - ChoreoError(type: ChoreoErrorType.unknown, raw: err), - ); - Sentry.addBreadcrumb( - Breadcrumb.fromJson({"igctextDdata": igcTextData?.toJson()}), - ); - ErrorHandler.logError(e: err, s: stack); - } finally { - igcTextData?.loading = false; - choreographer.stopLoading(); - } - } - void showFirstMatch(BuildContext context) { if (igcTextData == null || igcTextData!.matches.isEmpty) { debugger(when: kDebugMode); diff --git a/lib/pangea/models/igc_text_data_model.dart b/lib/pangea/models/igc_text_data_model.dart index 442bf4a60..fe052a5ec 100644 --- a/lib/pangea/models/igc_text_data_model.dart +++ b/lib/pangea/models/igc_text_data_model.dart @@ -1,14 +1,12 @@ import 'dart:developer'; +import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/controllers/language_detection_controller.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/models/span_card_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/pangea/utils/overlay.dart'; -import 'package:fluffychat/pangea/widgets/igc/span_card.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; @@ -262,7 +260,20 @@ class IGCTextData { return matchTokens; } + TextSpan getSpanItem({ + required int start, + required int end, + TextStyle? style, + }) { + return TextSpan( + text: originalInput.characters.getRange(start, end).toString(), + style: style, + ); + } + //PTODO - handle multitoken spans + /// Returns a list of [TextSpan]s used to display the text in the input field + /// with the appropriate styling for each error match. List constructTokenSpan({ required BuildContext context, TextStyle? defaultStyle, @@ -282,79 +293,58 @@ class IGCTextData { ]; } - final List matchTokens = getMatchTokens(); + final List> matchRanges = matches + .map( + (match) => [ + match.match.offset, + match.match.length + match.match.offset, + ], + ) + .toList(); - for (int tokenIndex = 0; tokenIndex < matchTokens.length; tokenIndex++) { - final MatchToken matchToken = matchTokens[tokenIndex]; - final Widget? cardToShow = - matchToken.match != null && spanCardModel != null - ? SpanCard(scm: spanCardModel) - : null; - - int nextTokenIndex = matchTokens.indexWhere( - (e) => matchToken.match != null - ? e.match != matchToken.match - : e.match != null, - tokenIndex, + // create a pointer to the current index in the original input + // and iterate until the pointer has reached the end of the input + int currentIndex = 0; + while (currentIndex < originalInput.characters.length - 1) { + // check if the pointer is at a match, and if so, get the index of the match + final int matchIndex = matchRanges.indexWhere( + (range) => currentIndex >= range[0] && currentIndex < range[1], ); + final bool inMatch = matchIndex != -1; - if (nextTokenIndex < 0) { - nextTokenIndex = matchTokens.length; - } - - String matchText; - try { - final int start = matchTokens[tokenIndex].token.text.offset; - final int end = matchTokens[nextTokenIndex - 1].token.end; - matchText = originalInput.characters.getRange(start, end).toString(); - } catch (err) { - return [ - TextSpan( - text: originalInput, - style: defaultStyle, - ), - ]; - } - - items.add( - TextSpan( - text: matchText, - style: matchTokens[tokenIndex].match?.textStyle(defaultStyle) ?? - defaultStyle, - recognizer: handleClick && cardToShow != null - ? (TapGestureRecognizer() - ..onTapDown = (details) => OverlayUtil.showPositionedCard( - context: context, - cardToShow: cardToShow, - cardSize: - matchTokens[tokenIndex].match?.isITStart ?? false - ? const Size(350, 220) - : const Size(350, 400), - transformTargetId: transformTargetId, - )) - : null, - ), - ); - - final String beforeNextToken = originalInput.characters - .getRange( - matchTokens[nextTokenIndex - 1].token.end, - nextTokenIndex < matchTokens.length - ? matchTokens[nextTokenIndex].token.text.offset - : originalInput.length, - ) - .toString(); - - if (beforeNextToken.isNotEmpty) { + if (inMatch) { + // if the pointer is in a match, then add that match to items + // and then move the pointer to the end of the match range + final PangeaMatch match = matches[matchIndex]; items.add( - TextSpan( - text: beforeNextToken, + getSpanItem( + start: match.match.offset, + end: match.match.offset + match.match.length, + style: match.textStyle(defaultStyle), + ), + ); + + currentIndex = match.match.offset + match.match.length; + } else { + // otherwise, if the pointer is not at a match, then add all the text + // until the next match (or, if there is not next match, the end of the + // text) to items and move the pointer to the start of the next match + final int nextIndex = matchRanges + .firstWhereOrNull( + (range) => range[0] > currentIndex, + ) + ?.first ?? + originalInput.characters.length; + + items.add( + getSpanItem( + start: currentIndex, + end: nextIndex, style: defaultStyle, ), ); + currentIndex = nextIndex; } - - tokenIndex = nextTokenIndex - 1; } return items; From 2494827e1d426d6654e9e57eeecc061d4aa631d4 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 9 Jul 2024 15:37:05 -0400 Subject: [PATCH 18/51] removed block icon on user bottom sheet for bot --- lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index b3e861b79..19e4a524d 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/utils/bot_name.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/url_launcher.dart'; @@ -91,7 +92,12 @@ class UserBottomSheetView extends StatelessWidget { ), actions: [ if (userId != client.userID && - !client.ignoredUsers.contains(userId)) + !client.ignoredUsers.contains(userId) + // #Pangea + && + userId != BotName.byEnvironment + // Pangea# + ) Padding( padding: const EdgeInsets.only(right: 8.0), child: IconButton( From 96c641557f09ddbb7e6ddafbfb12d119bf799a7e Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 9 Jul 2024 15:57:03 -0400 Subject: [PATCH 19/51] Make create space/chat button float --- assets/l10n/intl_en.arb | 4 +- lib/pages/new_group/new_group_view.dart | 61 ++++++++++++++----------- lib/pages/new_space/new_space_view.dart | 61 ++++++++++++++----------- 3 files changed, 73 insertions(+), 53 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 7d853de70..be109614e 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4109,5 +4109,7 @@ "continueText": "Continue", "deleteSubscriptionWarningTitle": "You have an active subscription", "deleteSubscriptionWarningBody": "Deleting your account will not automatically cancel your subscription.", - "manageSubscription": "Manage Subscription" + "manageSubscription": "Manage Subscription", + "createSpace": "Create space", + "createChat": "Create chat" } \ No newline at end of file diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index b8b43c3f1..84bfbfbc5 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -27,6 +27,15 @@ class NewGroupView extends StatelessWidget { ), title: Text(L10n.of(context)!.createGroup), ), + // #Pangea + floatingActionButton: FloatingActionButton.extended( + onPressed: controller.loading ? null : controller.submitAction, + icon: controller.loading ? null : const Icon(Icons.chat_bubble_outline), + label: controller.loading + ? const CircularProgressIndicator.adaptive() + : Text(L10n.of(context)!.createChat), + ), + // Pangea# body: MaxWidthBody( child: Column( mainAxisSize: MainAxisSize.min, @@ -134,33 +143,33 @@ class NewGroupView extends StatelessWidget { // value: !controller.publicGroup, // onChanged: null, // ), + // Padding( + // padding: const EdgeInsets.all(16.0), + // child: SizedBox( + // width: double.infinity, + // child: ElevatedButton( + // style: ElevatedButton.styleFrom( + // foregroundColor: Theme.of(context).colorScheme.onPrimary, + // backgroundColor: Theme.of(context).colorScheme.primary, + // ), + // onPressed: + // controller.loading ? null : controller.submitAction, + // child: controller.loading + // ? const LinearProgressIndicator() + // : Row( + // children: [ + // Expanded( + // child: Text( + // L10n.of(context)!.createGroupAndInviteUsers, + // ), + // ), + // Icon(Icons.adaptive.arrow_forward_outlined), + // ], + // ), + // ), + // ), + // ), // Pangea# - Padding( - padding: const EdgeInsets.all(16.0), - child: SizedBox( - width: double.infinity, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.onPrimary, - backgroundColor: Theme.of(context).colorScheme.primary, - ), - onPressed: - controller.loading ? null : controller.submitAction, - child: controller.loading - ? const LinearProgressIndicator() - : Row( - children: [ - Expanded( - child: Text( - L10n.of(context)!.createGroupAndInviteUsers, - ), - ), - Icon(Icons.adaptive.arrow_forward_outlined), - ], - ), - ), - ), - ), AnimatedSize( duration: FluffyThemes.animationDuration, child: error == null diff --git a/lib/pages/new_space/new_space_view.dart b/lib/pages/new_space/new_space_view.dart index f8c7baf4e..22751a787 100644 --- a/lib/pages/new_space/new_space_view.dart +++ b/lib/pages/new_space/new_space_view.dart @@ -32,6 +32,15 @@ class NewSpaceView extends StatelessWidget { // Pangea# title: Text(L10n.of(context)!.createNewSpace), ), + // #Pangea + floatingActionButton: FloatingActionButton.extended( + onPressed: controller.loading ? null : controller.submitAction, + icon: controller.loading ? null : const Icon(Icons.workspaces_outlined), + label: controller.loading + ? const CircularProgressIndicator.adaptive() + : Text(L10n.of(context)!.createSpace), + ), + // Pangea# body: MaxWidthBody( child: Column( mainAxisSize: MainAxisSize.min, @@ -158,33 +167,33 @@ class NewSpaceView extends StatelessWidget { // value: controller.publicGroup, // onChanged: controller.setPublicGroup, // ), + // Padding( + // padding: const EdgeInsets.all(16.0), + // child: SizedBox( + // width: double.infinity, + // child: ElevatedButton( + // style: ElevatedButton.styleFrom( + // foregroundColor: Theme.of(context).colorScheme.onPrimary, + // backgroundColor: Theme.of(context).colorScheme.primary, + // ), + // onPressed: + // controller.loading ? null : controller.submitAction, + // child: controller.loading + // ? const LinearProgressIndicator() + // : Row( + // children: [ + // Expanded( + // child: Text( + // L10n.of(context)!.createNewSpace, + // ), + // ), + // Icon(Icons.adaptive.arrow_forward_outlined), + // ], + // ), + // ), + // ), + // ), // Pangea# - Padding( - padding: const EdgeInsets.all(16.0), - child: SizedBox( - width: double.infinity, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - foregroundColor: Theme.of(context).colorScheme.onPrimary, - backgroundColor: Theme.of(context).colorScheme.primary, - ), - onPressed: - controller.loading ? null : controller.submitAction, - child: controller.loading - ? const LinearProgressIndicator() - : Row( - children: [ - Expanded( - child: Text( - L10n.of(context)!.createNewSpace, - ), - ), - Icon(Icons.adaptive.arrow_forward_outlined), - ], - ), - ), - ), - ), ], ), ), From a5cdafcb659d0f03ffa1658ff09e1d40063fa4bc Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Wed, 10 Jul 2024 13:24:13 -0400 Subject: [PATCH 20/51] Added null checks, error handling, and used non-null version of l10n --- lib/utils/error_reporter.dart | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/utils/error_reporter.dart b/lib/utils/error_reporter.dart index f3b3bfabd..76e39c193 100644 --- a/lib/utils/error_reporter.dart +++ b/lib/utils/error_reporter.dart @@ -9,15 +9,26 @@ class ErrorReporter { const ErrorReporter(this.context, [this.message]); void onErrorCallback(Object error, [StackTrace? stackTrace]) async { - Logs().e(message ?? 'Error caught', error, stackTrace); - // #Pangea - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - L10n.of(context)!.oopsSomethingWentWrong, - ), + Logs().e(message ?? 'Error caught', error, stackTrace); + // #Pangea + // Attempt to retrieve the L10n instance using the current context + final L10n? l10n = L10n.of(context); + + // Check if the L10n instance is null + if (l10n == null) { + // Log an error message saying that the localization object is null + Logs().e('Localization object is null, cannot show error message.'); + // Exits early to prevent further execution + return; + } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + l10n.oopsSomethingWentWrong, // Use the non-null L10n instance to get the error message ), - ); + ), + ); +} // final text = '$error\n${stackTrace ?? ''}'; // await showAdaptiveDialog( // context: context, @@ -64,4 +75,4 @@ class ErrorReporter { // ); // Pangea# } -} + From 425b7ddcf12b85611d0edaf14f94f2509f5bfb88 Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Wed, 10 Jul 2024 13:37:39 -0400 Subject: [PATCH 21/51] oops formatting fix --- lib/utils/error_reporter.dart | 127 +++++++++++++++++----------------- 1 file changed, 63 insertions(+), 64 deletions(-) diff --git a/lib/utils/error_reporter.dart b/lib/utils/error_reporter.dart index 76e39c193..d44b48dcb 100644 --- a/lib/utils/error_reporter.dart +++ b/lib/utils/error_reporter.dart @@ -9,70 +9,69 @@ class ErrorReporter { const ErrorReporter(this.context, [this.message]); void onErrorCallback(Object error, [StackTrace? stackTrace]) async { - Logs().e(message ?? 'Error caught', error, stackTrace); - // #Pangea - // Attempt to retrieve the L10n instance using the current context - final L10n? l10n = L10n.of(context); + Logs().e(message ?? 'Error caught', error, stackTrace); + // #Pangea + // Attempt to retrieve the L10n instance using the current context + final L10n? l10n = L10n.of(context); - // Check if the L10n instance is null - if (l10n == null) { - // Log an error message saying that the localization object is null - Logs().e('Localization object is null, cannot show error message.'); - // Exits early to prevent further execution - return; - } - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - l10n.oopsSomethingWentWrong, // Use the non-null L10n instance to get the error message + // Check if the L10n instance is null + if (l10n == null) { + // Log an error message saying that the localization object is null + Logs().e('Localization object is null, cannot show error message.'); + // Exits early to prevent further execution + return; + } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + l10n.oopsSomethingWentWrong, // Use the non-null L10n instance to get the error message + ), ), - ), - ); -} - // final text = '$error\n${stackTrace ?? ''}'; - // await showAdaptiveDialog( - // context: context, - // builder: (context) => AlertDialog.adaptive( - // title: Text(L10n.of(context)!.reportErrorDescription), - // content: SizedBox( - // height: 256, - // width: 256, - // child: SingleChildScrollView( - // child: HighlightView( - // text, - // language: 'sh', - // theme: shadesOfPurpleTheme, - // ), - // ), - // ), - // actions: [ - // TextButton( - // onPressed: () => Navigator.of(context).pop(), - // child: Text(L10n.of(context)!.close), - // ), - // TextButton( - // onPressed: () => Clipboard.setData( - // ClipboardData(text: text), - // ), - // child: Text(L10n.of(context)!.copy), - // ), - // TextButton( - // onPressed: () => launchUrl( - // AppConfig.newIssueUrl.resolveUri( - // Uri( - // queryParameters: { - // 'template': 'bug_report.yaml', - // 'title': '[BUG]: ${message ?? error.toString()}', - // }, - // ), - // ), - // mode: LaunchMode.externalApplication, - // ), - // child: Text(L10n.of(context)!.report), - // ), - // ], - // ), - // ); - // Pangea# + ); } - + // final text = '$error\n${stackTrace ?? ''}'; + // await showAdaptiveDialog( + // context: context, + // builder: (context) => AlertDialog.adaptive( + // title: Text(L10n.of(context)!.reportErrorDescription), + // content: SizedBox( + // height: 256, + // width: 256, + // child: SingleChildScrollView( + // child: HighlightView( + // text, + // language: 'sh', + // theme: shadesOfPurpleTheme, + // ), + // ), + // ), + // actions: [ + // TextButton( + // onPressed: () => Navigator.of(context).pop(), + // child: Text(L10n.of(context)!.close), + // ), + // TextButton( + // onPressed: () => Clipboard.setData( + // ClipboardData(text: text), + // ), + // child: Text(L10n.of(context)!.copy), + // ), + // TextButton( + // onPressed: () => launchUrl( + // AppConfig.newIssueUrl.resolveUri( + // Uri( + // queryParameters: { + // 'template': 'bug_report.yaml', + // 'title': '[BUG]: ${message ?? error.toString()}', + // }, + // ), + // ), + // mode: LaunchMode.externalApplication, + // ), + // child: Text(L10n.of(context)!.report), + // ), + // ], + // ), + // ); + // Pangea# +} From 5494019be5407fa720dd54186ef87b5d6d1b252a Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Wed, 10 Jul 2024 16:47:22 -0400 Subject: [PATCH 22/51] set max char size for input field --- lib/pages/chat/input_bar.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index a02626978..04d1be9db 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -490,8 +490,10 @@ class InputBar extends StatelessWidget { keyboardType: keyboardType!, textInputAction: textInputAction, autofocus: autofocus!, + //setting max character count to 1000 + //after max, nothing else can be typed inputFormatters: [ - LengthLimitingTextInputFormatter((maxPDUSize / 3).floor()), + LengthLimitingTextInputFormatter(1000), ], onSubmitted: (text) { // fix for library for now From f69ab79c9d7cdf36acb8c16964931b3f0c69d313 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 10 Jul 2024 16:55:52 -0400 Subject: [PATCH 23/51] made matrix profile save one json value instead of one value per field, added documentation to user controller and user model --- lib/pages/chat_list/space_view.dart | 6 +- .../controllers/alternative_translator.dart | 25 +- .../controllers/choreographer.dart | 9 +- .../widgets/it_feedback_card.dart | 14 +- lib/pangea/constants/local.key.dart | 8 - lib/pangea/constants/model_keys.dart | 6 + lib/pangea/controllers/class_controller.dart | 6 +- .../contextual_definition_controller.dart | 17 +- .../controllers/it_feedback_controller.dart | 11 +- .../language_detection_controller.dart | 16 +- lib/pangea/controllers/local_settings.dart | 34 -- .../message_analytics_controller.dart | 4 - .../controllers/message_data_controller.dart | 20 +- .../controllers/my_analytics_controller.dart | 5 - lib/pangea/controllers/pangea_controller.dart | 7 +- .../controllers/permissions_controller.dart | 35 +- .../speech_to_text_controller.dart | 14 +- .../controllers/subscription_controller.dart | 22 +- .../text_to_speech_controller.dart | 14 +- lib/pangea/controllers/user_controller.dart | 569 ++++++++---------- .../controllers/word_net_controller.dart | 19 +- .../pangea_message_event.dart | 19 +- lib/pangea/models/space_model.dart | 16 + lib/pangea/models/user_model.dart | 196 ++++-- .../pages/find_partner/find_partner.dart | 13 +- lib/pangea/pages/p_user_age/p_user_age.dart | 2 +- .../settings_learning_view.dart | 46 +- lib/pangea/utils/p_store.dart | 148 ++--- lib/pangea/widgets/chat/message_toolbar.dart | 9 +- .../chat/message_translation_card.dart | 10 +- lib/pangea/widgets/class/join_with_link.dart | 3 +- lib/pangea/widgets/igc/span_card.dart | 38 +- .../p_settings_switch_list_tile.dart | 35 +- lib/widgets/matrix.dart | 7 +- 34 files changed, 740 insertions(+), 663 deletions(-) delete mode 100644 lib/pangea/controllers/local_settings.dart diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index f3f9749a1..b57e4bdd6 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -50,10 +50,7 @@ class _SpaceViewState extends State { final String _chatCountsKey = 'chatCounts'; Map get chatCounts => Map.from( - widget.controller.pangeaController.pStoreService.read( - _chatCountsKey, - local: true, - ) ?? + widget.controller.pangeaController.pStoreService.read(_chatCountsKey) ?? {}, ); // Pangea# @@ -550,7 +547,6 @@ class _SpaceViewState extends State { await widget.controller.pangeaController.pStoreService.save( _chatCountsKey, updatedChatCounts, - local: true, ); } diff --git a/lib/pangea/choreographer/controllers/alternative_translator.dart b/lib/pangea/choreographer/controllers/alternative_translator.dart index 4221a3479..ccba733e2 100644 --- a/lib/pangea/choreographer/controllers/alternative_translator.dart +++ b/lib/pangea/choreographer/controllers/alternative_translator.dart @@ -1,15 +1,14 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:http/http.dart' as http; - import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; import 'package:fluffychat/pangea/repo/full_text_translation_repo.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:http/http.dart' as http; + import '../../repo/similarity_repo.dart'; class AlternativeTranslator { @@ -90,9 +89,19 @@ class AlternativeTranslator { final String? goldRouteTranslation = choreographer.itController.goldRouteTracker.fullTranslation; + final accessToken = await choreographer.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + m: "accessToken null in setTranslationFeedback", + s: StackTrace.current, + ); + translationFeedbackKey = FeedbackKey.loadingPleaseWait; + return; + } + final FullTextTranslationResponseModel results = await FullTextTranslationRepo.translate( - accessToken: await choreographer.accessToken, + accessToken: accessToken, request: FullTextTranslationRequestModel( text: choreographer.itController.sourceText!, tgtLang: choreographer.l2LangCode!, @@ -118,7 +127,7 @@ class AlternativeTranslator { } similarityResponse = await SimilarityRepo.get( - accessToken: await choreographer.accessToken, + accessToken: accessToken, request: SimilarityRequestModel( benchmark: results.bestTranslation, toCompare: [userTranslation!], diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 24756f7a6..db317cd4e 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -407,7 +407,8 @@ class Choreographer { PangeaTextController get textController => _textController; - Future get accessToken => pangeaController.userController.accessToken; + Future get accessToken => + pangeaController.userController.accessToken; clear() { choreoMode = ChoreoMode.igc; @@ -514,11 +515,7 @@ class Choreographer { chatController.room, ); - bool get itAutoPlayEnabled => - pangeaController.pStoreService.read( - MatrixProfile.itAutoPlay.title, - ) ?? - false; + bool get itAutoPlayEnabled => MatrixProfile.itAutoPlay; bool get definitionsEnabled => pangeaController.permissionsController.isToolEnabled( diff --git a/lib/pangea/choreographer/widgets/it_feedback_card.dart b/lib/pangea/choreographer/widgets/it_feedback_card.dart index 5424310a8..840843ed5 100644 --- a/lib/pangea/choreographer/widgets/it_feedback_card.dart +++ b/lib/pangea/choreographer/widgets/it_feedback_card.dart @@ -72,8 +72,18 @@ class ITFeedbackCardController extends State { setState(() { isTranslating = true; }); + + final String? accessToken = await controller.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + m: "Cannot translate feedback because accessToken is null", + ); + error = "Cannot translate feedback because accessToken is null"; + return; + } + FullTextTranslationRepo.translate( - accessToken: await controller.userController.accessToken, + accessToken: accessToken, request: FullTextTranslationRequestModel( text: res!.text, tgtLang: controller.languageController.userL1?.langCode ?? @@ -197,7 +207,7 @@ class TranslateButton extends StatelessWidget { return TextButton( onPressed: loading ? null : onPress, style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( + backgroundColor: WidgetStateProperty.all( AppConfig.primaryColor.withOpacity(0.1), ), ), diff --git a/lib/pangea/constants/local.key.dart b/lib/pangea/constants/local.key.dart index 40dd393c5..b8c1a4f5d 100644 --- a/lib/pangea/constants/local.key.dart +++ b/lib/pangea/constants/local.key.dart @@ -1,16 +1,8 @@ class PLocalKey { static const String user = 'user'; - - static const String classes = 'classes'; - static const String cachedClassCodeToJoin = "cachedclasscodetojoin"; static const String beganWebPayment = "beganWebPayment"; - - // making this a random string so that it's harder to guess - static const String activatedTrialKey = '7C4EuKIsph'; static const String dismissedPaywall = 'dismissedPaywall'; static const String paywallBackoff = 'paywallBackoff'; - static const String autoPlayMessages = 'autoPlayMessages'; - static const String itAutoPlay = 'itAutoPlay'; static const String messagesSinceUpdate = 'messagesSinceLastUpdate'; } diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart index 372e72606..08164d9f6 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -17,6 +17,12 @@ class ModelKey { static const String publicProfile = 'public'; static const String userId = 'user_id'; + // matrix profile keys + // making this a random string so that it's harder to guess + static const String activatedTrialKey = '7C4EuKIsph'; + static const String autoPlayMessages = 'autoPlayMessages'; + static const String itAutoPlay = 'itAutoPlay'; + static const String clientClassCity = "city"; static const String clientClassCountry = "country"; static const String clientClassDominantLanguage = "dominantLanguage"; diff --git a/lib/pangea/controllers/class_controller.dart b/lib/pangea/controllers/class_controller.dart index 64a8340ae..5fa59622d 100644 --- a/lib/pangea/controllers/class_controller.dart +++ b/lib/pangea/controllers/class_controller.dart @@ -48,15 +48,13 @@ class ClassController extends BaseController { Future checkForClassCodeAndSubscription(BuildContext context) async { final String? classCode = _pangeaController.pStoreService.read( PLocalKey.cachedClassCodeToJoin, - addClientIdToKey: false, - local: true, + isAccountData: false, ); if (classCode != null) { await _pangeaController.pStoreService.delete( PLocalKey.cachedClassCodeToJoin, - addClientIdToKey: false, - local: true, + isAccountData: false, ); await joinClasswithCode( context, diff --git a/lib/pangea/controllers/contextual_definition_controller.dart b/lib/pangea/controllers/contextual_definition_controller.dart index c13a428b8..ecc08a234 100644 --- a/lib/pangea/controllers/contextual_definition_controller.dart +++ b/lib/pangea/controllers/contextual_definition_controller.dart @@ -1,12 +1,11 @@ import 'dart:convert'; -import 'package:flutter/foundation.dart'; - import 'package:collection/collection.dart'; -import 'package:http/http.dart'; - import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:flutter/foundation.dart'; +import 'package:http/http.dart'; + import '../constants/model_keys.dart'; import '../network/requests.dart'; import '../network/urls.dart'; @@ -50,9 +49,17 @@ class ContextualDefinitionController { ContextualDefinitionRequestModel request, ) async { try { + final accessToken = await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in contextual definition controller", + s: StackTrace.current, + ); + return null; + } final ContextualDefinitionResponseModel res = await _ContextualDefinitionRepo.define( - await _pangeaController.userController.accessToken, + accessToken, request, ); return res; diff --git a/lib/pangea/controllers/it_feedback_controller.dart b/lib/pangea/controllers/it_feedback_controller.dart index ffef9123f..c2cd96cad 100644 --- a/lib/pangea/controllers/it_feedback_controller.dart +++ b/lib/pangea/controllers/it_feedback_controller.dart @@ -51,8 +51,17 @@ class ITFeedbackController { ITFeedbackRequestModel request, ) async { try { + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in it feedback controller", + s: StackTrace.current, + ); + return null; + } final ITFeedbackResponseModel res = await _ITFeedbackRepo.get( - await _pangeaController.userController.accessToken, + accessToken, request, ); return res; diff --git a/lib/pangea/controllers/language_detection_controller.dart b/lib/pangea/controllers/language_detection_controller.dart index a3e07b0a3..5ba0f8879 100644 --- a/lib/pangea/controllers/language_detection_controller.dart +++ b/lib/pangea/controllers/language_detection_controller.dart @@ -6,6 +6,7 @@ import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/models/language_detection_model.dart'; import 'package:fluffychat/pangea/network/urls.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:http/http.dart' as http; import '../network/requests.dart'; @@ -125,7 +126,7 @@ class LanguageDetectionController { _cacheClearTimer?.cancel(); } - Future detectLanguage( + Future detectLanguage( String fullText, String? userL2, String? userL1, @@ -138,14 +139,23 @@ class LanguageDetectionController { return get(params); } - Future get( + Future get( LanguageDetectionRequest params, ) async { if (_cache.containsKey(params)) { return _cache[params]!.data; } else { + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in language detection controller", + s: StackTrace.current, + ); + return null; + } final Future response = _fetchResponse( - await _pangeaController.userController.accessToken, + accessToken, params, ); _cache[params] = _LanguageDetectionCacheItem(data: response); diff --git a/lib/pangea/controllers/local_settings.dart b/lib/pangea/controllers/local_settings.dart deleted file mode 100644 index 95b88ae14..000000000 --- a/lib/pangea/controllers/local_settings.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/models/space_model.dart'; - -class LocalSettings { - late PangeaController _pangeaController; - - LocalSettings(PangeaController pangeaController) : super() { - _pangeaController = pangeaController; - } - - bool userLanguageToolSetting(ToolSetting setting) { - final profileSetting = - _pangeaController.pStoreService.read(setting.toString()); - if (profileSetting != null) { - return profileSetting; - } - return setting == ToolSetting.immersionMode ? false : true; - } - - // bool get userEnableIT => - // _pangeaController.pStoreService.read(ToolSetting.interactiveTranslator.toString()) ?? true; - - // bool get userEnableIGC => - // _pangeaController.pStoreService.read(ToolSetting.interactiveGrammar.toString()) ?? true; - - // bool get userImmersionMode => - // _pangeaController.pStoreService.read(ToolSetting.immersionMode.toString()) ?? true; - - // bool get userTranslationsTool => - // _pangeaController.pStoreService.read(ToolSetting.translations.toString()) ?? true; - - // bool get userDefinitionsTool => - // _pangeaController.pStoreService.read(ToolSetting.definitions.toString()) ?? true; -} diff --git a/lib/pangea/controllers/message_analytics_controller.dart b/lib/pangea/controllers/message_analytics_controller.dart index 3739b2596..f58a56698 100644 --- a/lib/pangea/controllers/message_analytics_controller.dart +++ b/lib/pangea/controllers/message_analytics_controller.dart @@ -42,7 +42,6 @@ class AnalyticsController extends BaseController { try { final String? str = _pangeaController.pStoreService.read( _analyticsTimeSpanKey, - local: true, ); return str != null ? TimeSpan.values.firstWhere((e) { @@ -60,7 +59,6 @@ class AnalyticsController extends BaseController { await _pangeaController.pStoreService.save( _analyticsTimeSpanKey, timeSpan.toString(), - local: true, ); setState(); } @@ -72,7 +70,6 @@ class AnalyticsController extends BaseController { try { final String? str = _pangeaController.pStoreService.read( _analyticsSpaceLangKey, - local: true, ); return str != null ? PangeaLanguage.byLangCode(str) @@ -88,7 +85,6 @@ class AnalyticsController extends BaseController { await _pangeaController.pStoreService.save( _analyticsSpaceLangKey, lang.langCode, - local: true, ); setState(); } diff --git a/lib/pangea/controllers/message_data_controller.dart b/lib/pangea/controllers/message_data_controller.dart index 7b3a3e6d2..7ad3a748f 100644 --- a/lib/pangea/controllers/message_data_controller.dart +++ b/lib/pangea/controllers/message_data_controller.dart @@ -44,6 +44,14 @@ class MessageDataController extends BaseController { ) async { final accessToken = await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in _getTokens", + s: StackTrace.current, + ); + return null; + } + final TokensResponseModel igcTextData = await TokensRepo.tokenize(accessToken, req); @@ -193,9 +201,19 @@ class MessageDataController extends BaseController { ); try { + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in _getPangeaRepresentation", + s: StackTrace.current, + ); + return null; + } + final FullTextTranslationResponseModel res = await FullTextTranslationRepo.translate( - accessToken: await _pangeaController.userController.accessToken, + accessToken: accessToken, request: req, ); diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index f7017ad0e..058bad359 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -121,7 +121,6 @@ class MyAnalyticsController { _pangeaController.pStoreService.save( PLocalKey.messagesSinceUpdate, currentCache, - local: true, ); } @@ -155,7 +154,6 @@ class MyAnalyticsController { _pangeaController.pStoreService.save( PLocalKey.messagesSinceUpdate, [], - local: true, ); } @@ -167,14 +165,12 @@ class MyAnalyticsController { Logs().d('Reading messages since update from local storage'); final dynamic locallySaved = _pangeaController.pStoreService.read( PLocalKey.messagesSinceUpdate, - local: true, ); if (locallySaved == null) { Logs().d('No locally saved messages found, initializing empty list.'); _pangeaController.pStoreService.save( PLocalKey.messagesSinceUpdate, [], - local: true, ); return []; } @@ -201,7 +197,6 @@ class MyAnalyticsController { _pangeaController.pStoreService.save( PLocalKey.messagesSinceUpdate, [], - local: true, ); return []; } diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index 7fbf91fd0..7e24fa955 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -9,7 +9,6 @@ import 'package:fluffychat/pangea/controllers/contextual_definition_controller.d import 'package:fluffychat/pangea/controllers/language_controller.dart'; import 'package:fluffychat/pangea/controllers/language_detection_controller.dart'; import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; -import 'package:fluffychat/pangea/controllers/local_settings.dart'; import 'package:fluffychat/pangea/controllers/message_data_controller.dart'; import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/permissions_controller.dart'; @@ -47,7 +46,6 @@ class PangeaController { late AnalyticsController analytics; late MyAnalyticsController myAnalytics; late WordController wordNet; - late LocalSettings localSettings; late MessageDataController messageData; late ContextualDefinitionController definitions; late ITFeedbackController itFeedback; @@ -60,7 +58,7 @@ class PangeaController { late PracticeGenerationController practiceGenerationController; ///store Services - late PLocalStore pStoreService; + late PStore pStoreService; final pLanguageStore = PangeaLanguage(); ///Matrix Variables @@ -89,10 +87,9 @@ class PangeaController { /// Initialize controllers _addRefInObjects() { - pStoreService = PLocalStore(pangeaController: this); + pStoreService = PStore(pangeaController: this); userController = UserController(this); languageController = LanguageController(this); - localSettings = LocalSettings(this); classController = ClassController(this); permissionsController = PermissionsController(this); analytics = AnalyticsController(this); diff --git a/lib/pangea/controllers/permissions_controller.dart b/lib/pangea/controllers/permissions_controller.dart index 363accf8e..3932eb7d2 100644 --- a/lib/pangea/controllers/permissions_controller.dart +++ b/lib/pangea/controllers/permissions_controller.dart @@ -31,14 +31,11 @@ class PermissionsController extends BaseController { } /// Returns false if user is null - bool isUser18() { - final dob = _pangeaController.pStoreService.read( - MatrixProfile.dateOfBirth.title, - ); - return dob != null - ? DateTime.parse(dob).isAtLeastYearsOld(AgeLimits.toAccessFeatures) - : false; - } + bool isUser18() => + MatrixProfile.dateOfBirth?.isAtLeastYearsOld( + AgeLimits.toAccessFeatures, + ) ?? + false; /// A user can private chat if /// 1) they are 18 and outside a class context or @@ -99,18 +96,32 @@ class PermissionsController extends BaseController { return classPermission == 0; } - bool userToolSetting(ToolSetting setting) => - _pangeaController.localSettings.userLanguageToolSetting(setting); + bool userToolSetting(MatrixProfileEnum setting) { + switch (setting.asToolSetting) { + case ToolSetting.interactiveTranslator: + return MatrixProfile.interactiveTranslator; + case ToolSetting.interactiveGrammar: + return MatrixProfile.interactiveGrammar; + case ToolSetting.immersionMode: + return MatrixProfile.immersionMode; + case ToolSetting.definitions: + return MatrixProfile.definitions; + case ToolSetting.autoIGC: + return MatrixProfile.autoIGC; + default: + return false; + } + } bool isToolEnabled(ToolSetting setting, Room? room) { if (room?.isSpaceAdmin ?? false) { - return userToolSetting(setting); + return userToolSetting(setting.asMatrixProfileField); } final int? classPermission = room != null ? classLanguageToolPermission(room, setting) : 1; if (classPermission == 0) return false; if (classPermission == 2) return true; - return userToolSetting(setting); + return userToolSetting(setting.asMatrixProfileField); } bool isWritingAssistanceEnabled(Room? room) { diff --git a/lib/pangea/controllers/speech_to_text_controller.dart b/lib/pangea/controllers/speech_to_text_controller.dart index 67462bcef..3daeada07 100644 --- a/lib/pangea/controllers/speech_to_text_controller.dart +++ b/lib/pangea/controllers/speech_to_text_controller.dart @@ -44,7 +44,7 @@ class SpeechToTextController { _cacheClearTimer?.cancel(); } - Future get( + Future get( SpeechToTextRequestModel requestModel, ) async { final int cacheKey = requestModel.hashCode; @@ -52,8 +52,18 @@ class SpeechToTextController { if (_cache.containsKey(cacheKey)) { return _cache[cacheKey]!.data; } else { + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: 'null accessToken in speech to text controller', + s: StackTrace.current, + ); + return null; + } + final Future response = _fetchResponse( - accessToken: await _pangeaController.userController.accessToken, + accessToken: accessToken, requestModel: requestModel, ); _cache[cacheKey] = _SpeechToTextCacheItem(data: response); diff --git a/lib/pangea/controllers/subscription_controller.dart b/lib/pangea/controllers/subscription_controller.dart index 36e965137..9e5d59b05 100644 --- a/lib/pangea/controllers/subscription_controller.dart +++ b/lib/pangea/controllers/subscription_controller.dart @@ -97,12 +97,10 @@ class SubscriptionController extends BaseController { } else { final bool? beganWebPayment = _pangeaController.pStoreService.read( PLocalKey.beganWebPayment, - local: true, ); if (beganWebPayment ?? false) { await _pangeaController.pStoreService.delete( PLocalKey.beganWebPayment, - local: true, ); if (_pangeaController.subscriptionController.isSubscribed) { subscriptionStream.add(true); @@ -142,7 +140,6 @@ class SubscriptionController extends BaseController { await _pangeaController.pStoreService.save( PLocalKey.beganWebPayment, true, - local: true, ); setState(); launchUrlString( @@ -184,18 +181,12 @@ class SubscriptionController extends BaseController { bool get _activatedNewUserTrial => _pangeaController.userController.inTrialWindow && - (_pangeaController.pStoreService.read( - MatrixProfile.activatedFreeTrial.title, - ) ?? - false); + MatrixProfile.activatedFreeTrial; void activateNewUserTrial() { - _pangeaController.pStoreService - .save( - MatrixProfile.activatedFreeTrial.title, - true, - ) - .then((_) { + MatrixProfile.saveProfileData({ + MatrixProfileEnum.activatedFreeTrial.title: true, + }).then((_) { setNewUserTrial(); trialActivationStream.add(true); }); @@ -242,7 +233,6 @@ class SubscriptionController extends BaseController { DateTime? get _lastDismissedPaywall { final lastDismissed = _pangeaController.pStoreService.read( PLocalKey.dismissedPaywall, - local: true, ); if (lastDismissed == null) return null; return DateTime.tryParse(lastDismissed); @@ -251,7 +241,6 @@ class SubscriptionController extends BaseController { int? get _paywallBackoff { final backoff = _pangeaController.pStoreService.read( PLocalKey.paywallBackoff, - local: true, ); if (backoff == null) return null; return backoff; @@ -269,20 +258,17 @@ class SubscriptionController extends BaseController { await _pangeaController.pStoreService.save( PLocalKey.dismissedPaywall, DateTime.now().toString(), - local: true, ); if (_paywallBackoff == null) { await _pangeaController.pStoreService.save( PLocalKey.paywallBackoff, 1, - local: true, ); } else { await _pangeaController.pStoreService.save( PLocalKey.paywallBackoff, _paywallBackoff! + 1, - local: true, ); } } diff --git a/lib/pangea/controllers/text_to_speech_controller.dart b/lib/pangea/controllers/text_to_speech_controller.dart index b34092618..b00fa23a4 100644 --- a/lib/pangea/controllers/text_to_speech_controller.dart +++ b/lib/pangea/controllers/text_to_speech_controller.dart @@ -6,6 +6,7 @@ import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/network/urls.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:http/http.dart'; import '../network/requests.dart'; @@ -93,14 +94,23 @@ class TextToSpeechController { _cacheClearTimer?.cancel(); } - Future get( + Future get( TextToSpeechRequest params, ) async { if (_cache.containsKey(params)) { return _cache[params]!.data; } else { + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in text to speech controller", + s: StackTrace.current, + ); + return null; + } final Future response = _fetchResponse( - await _pangeaController.userController.accessToken, + accessToken, params, ); _cache[params] = _TextToSpeechCacheItem(data: response); diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index 873397969..fa72e6b58 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -5,7 +5,7 @@ import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:flutter/material.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:matrix/matrix.dart' as matrix; @@ -13,14 +13,44 @@ import '../constants/local.key.dart'; import '../models/user_model.dart'; import '../repo/user_repo.dart'; +/// Controller that manages saving and reading of user/profile information class UserController extends BaseController { late PangeaController _pangeaController; - final Completer _completer = Completer(); UserController(PangeaController pangeaController) : super() { _pangeaController = pangeaController; } - Future createPangeaUser({required String dob}) async { + /// Convenience function that returns the user ID currently stored in the client. + String? get userId => _pangeaController.matrixState.client.userID; + + /// Convenience function that returns the accessToken currently stored in the client. + String? get _matrixAccessToken => + _pangeaController.matrixState.client.accessToken; + + /// Returns the [PUserModel] object representing the current user. + /// + /// This method retrieves the user data from the local storage using the [PLocalKey.user] key. + /// If the data exists, it is converted to a [PUserModel] object using the [PUserModel.fromJson] method. + /// If the data is null, indicating that the user is not logged in (or that + /// profile fetching has not yet completed, or had an error), null is returned. + PUserModel? get userModel { + final data = _pangeaController.pStoreService.read(PLocalKey.user); + return data != null ? PUserModel.fromJson(data) : null; + } + + /// Creates a user pangea chat profile, saves the user's profile information + /// locally, and set the user's DOB in their matrix profile. + /// + /// The [dob] parameter is required and represents the date of birth of the user. + /// This method creates a new [PUserModel] using the [PUserRepo.repoCreatePangeaUser] method, + /// and saves the user model in local storage. + /// It also updates the user's matrix profile using the [updateMatrixProfile] method. + Future createProfile({required String dob}) async { + if (userId == null || _matrixAccessToken == null) { + ErrorHandler.logError( + e: "calling createProfile with userId == null or matrixAccessToken == null", + ); + } final PUserModel newUserModel = await PUserRepo.repoCreatePangeaUser( userID: userId!, fullName: fullname, @@ -28,139 +58,167 @@ class UserController extends BaseController { matrixAccessToken: _matrixAccessToken!, ); newUserModel.save(_pangeaController); - await updateMatrixProfile(dateOfBirth: dob); + await MatrixProfile.saveProfileData( + {MatrixProfileEnum.dateOfBirth.title: dob}, + waitForDataInSync: true, + ); } + /// A boolean flag indicating whether the profile data is currently being fetched. + bool _isFetching = false; + + /// A completer for the profile model of a user. + Completer _profileCompleter = Completer(); + + /// Fetches the user model. + /// + /// This method retrieves the user model asynchronously. If the profile completer is already completed, + /// it returns the future value of the completer. If the user model is currently being fetched, + /// it waits for the completion of the completer and returns the future value. Otherwise, it sets + /// the fetching flag, fetches the user model, completes the profile completer with the fetched user model, + /// and returns the future value of the completer. + /// + /// Returns the future value of the user model completer. Future fetchUserModel() async { - try { - if (_matrixAccessToken == null) { - throw Exception( - "calling fetchUserModel with matrixAccesstoken == null", - ); - } - - final PUserModel? newUserModel = await PUserRepo.fetchPangeaUserInfo( - userID: userId!, - matrixAccessToken: _matrixAccessToken!, - ); - newUserModel?.save(_pangeaController); - await migrateMatrixProfile(); - _completeCompleter(); - - return newUserModel; - } catch (err) { - debugPrint( - "User model not found. Probably first signup and needs Pangea account", - ); - rethrow; + if (_profileCompleter.isCompleted) return _profileCompleter.future; + if (_isFetching) { + await _profileCompleter.future; + return _profileCompleter.future; } + + _isFetching = true; + PUserModel? fetchedUserModel; + + try { + fetchedUserModel = await _fetchUserModel(); + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); + return null; + } + + _isFetching = false; + _profileCompleter.complete(fetchedUserModel); + return _profileCompleter.future; } - dynamic migratedProfileInfo(MatrixProfile key) { - final dynamic localValue = _pangeaController.pStoreService.read( - key.title, - local: true, + /// Fetches the user model asynchronously. + /// + /// This method fetches the user model by calling the [fetchPangeaUserInfo] method + /// from the [PUserRepo] class. It requires the [_matrixAccessToken] and [userId] + /// to be non-null. If either of them is null, an error is logged. + /// + /// The fetched [newUserModel] is then saved locally. + /// The [migrateMatrixProfile] method is called, to migrate any information that is + /// already saved in the user's pangea profile but is not yet saved in the + /// user's matrix profile. Finally, the [newUserModel] is returned. + Future _fetchUserModel() async { + if (_matrixAccessToken == null || userId == null) { + ErrorHandler.logError( + e: "calling fetchUserModel with userId == null or matrixAccessToken == null", + ); + return null; + } + final PUserModel? newUserModel = await PUserRepo.fetchPangeaUserInfo( + userID: userId!, + matrixAccessToken: _matrixAccessToken!, ); - final dynamic matrixValue = _pangeaController.pStoreService.read( - key.title, - ); - return localValue != null && matrixValue != localValue ? localValue : null; + newUserModel?.save(_pangeaController); + await migrateMatrixProfile(); + return newUserModel; } + /// Reinitializes the user's profile + /// + /// This method sets up the necessary variables and fetches the user model. + /// It completes the [_profileCompleter] with the fetched user model. + /// This method should be called whenever the user's login status changes + Future reinitialize() async { + _profileCompleter = Completer(); + _isFetching = false; + await fetchUserModel(); + } + + /// Migrates the user's profile from Pangea to Matrix. + /// + /// This method retrieves the user's profile / local settings information from Pangea and checks for corresponding information stored in Matrix. + /// If any of the profile fields in Pangea have information, but the corresponding fields in Matrix are null, the values are updated in Matrix. + /// The profile fields that are checked for migration include date of birth, creation date, target language, source language, country, and public profile. + /// Additionally, several profile settings related to auto play, trial activation, interactive features, and instructional messages are also checked for migration. + /// + /// This method calls the [updateMatrixProfile] method to update the user's profile in Matrix with the migrated values. + /// + /// Note: This method assumes that the [userModel] and [_pangeaController] instances are properly initialized before calling this method. Future migrateMatrixProfile() async { + // This function relies on the client's account data being loaded. + // The account data is loaded during + // the first sync, so wait for that to complete. + final client = _pangeaController.matrixState.client; + if (client.prevBatch == null) { + await client.onSync.stream.first; + } + + final Map profileUpdates = {}; final Profile? pangeaProfile = userModel?.profile; - final String? pangeaDob = pangeaProfile?.dateOfBirth; - final String? matrixDob = _pangeaController.pStoreService.read( - ModelKey.userDateOfBirth, - ); - final String? dob = - pangeaDob != null && matrixDob != pangeaDob ? pangeaDob : null; + for (final field in MatrixProfile.pangeaProfileFields) { + final dynamic matrixValue = MatrixProfile.getProfileData(field); + dynamic pangeaValue; + switch (field) { + case MatrixProfileEnum.dateOfBirth: + pangeaValue = pangeaProfile?.dateOfBirth; + break; + case MatrixProfileEnum.createdAt: + pangeaValue = pangeaProfile?.createdAt; + break; + case MatrixProfileEnum.targetLanguage: + pangeaValue = pangeaProfile?.targetLanguage; + break; + case MatrixProfileEnum.sourceLanguage: + pangeaValue = pangeaProfile?.sourceLanguage; + break; + case MatrixProfileEnum.country: + pangeaValue = pangeaProfile?.country; + break; + case MatrixProfileEnum.publicProfile: + pangeaValue = pangeaProfile?.publicProfile; + break; + default: + break; + } + if (pangeaValue != null && matrixValue == null) { + profileUpdates[field.title] = pangeaValue; + } + } - final pangeaCreatedAt = pangeaProfile?.createdAt; - final matrixCreatedAt = _pangeaController.pStoreService.read( - MatrixProfile.createdAt.title, - ); - final String? createdAt = - pangeaCreatedAt != null && matrixCreatedAt != pangeaCreatedAt - ? pangeaCreatedAt - : null; + for (final value in MatrixProfileEnum.values) { + if (profileUpdates.containsKey(value.title)) continue; + final dynamic localValue = + _pangeaController.pStoreService.read(value.title); + final dynamic matrixValue = MatrixProfile.getProfileData(value); + final dynamic unmigratedValue = + localValue != null && matrixValue == null ? localValue : null; + if (unmigratedValue != null) { + profileUpdates[value.title] = unmigratedValue; + } + } - final String? pangeaTargetLanguage = pangeaProfile?.targetLanguage; - final String? matrixTargetLanguage = _pangeaController.pStoreService.read( - MatrixProfile.targetLanguage.title, - ); - final String? targetLanguage = pangeaTargetLanguage != null && - matrixTargetLanguage != pangeaTargetLanguage - ? pangeaTargetLanguage - : null; - - final String? pangeaSourceLanguage = pangeaProfile?.sourceLanguage; - final String? matrixSourceLanguage = _pangeaController.pStoreService.read( - MatrixProfile.sourceLanguage.title, - ); - final String? sourceLanguage = pangeaSourceLanguage != null && - matrixSourceLanguage != pangeaSourceLanguage - ? pangeaSourceLanguage - : null; - - final String? pangeaCountry = pangeaProfile?.country; - final String? matrixCountry = _pangeaController.pStoreService.read( - MatrixProfile.country.title, - ); - final String? country = - pangeaCountry != null && matrixCountry != pangeaCountry - ? pangeaCountry - : null; - - final bool? pangeaPublicProfile = pangeaProfile?.publicProfile; - final bool? matrixPublicProfile = _pangeaController.pStoreService.read( - MatrixProfile.publicProfile.title, - ); - final bool? publicProfile = pangeaPublicProfile != null && - matrixPublicProfile != pangeaPublicProfile - ? pangeaPublicProfile - : null; - - final bool? autoPlay = migratedProfileInfo(MatrixProfile.autoPlayMessages); - final bool? itAutoPlay = migratedProfileInfo(MatrixProfile.itAutoPlay); - final bool? trial = migratedProfileInfo(MatrixProfile.activatedFreeTrial); - final bool? interactiveTranslator = - migratedProfileInfo(MatrixProfile.interactiveTranslator); - final bool? interactiveGrammar = - migratedProfileInfo(MatrixProfile.interactiveGrammar); - final bool? immersionMode = - migratedProfileInfo(MatrixProfile.immersionMode); - final bool? definitions = migratedProfileInfo(MatrixProfile.definitions); - // final bool? translations = migratedProfileInfo(MatrixProfile.translations); - final bool? showItInstructions = - migratedProfileInfo(MatrixProfile.showedItInstructions); - final bool? showClickMessage = - migratedProfileInfo(MatrixProfile.showedClickMessage); - final bool? showBlurMeansTranslate = - migratedProfileInfo(MatrixProfile.showedBlurMeansTranslate); - - await updateMatrixProfile( - dateOfBirth: dob, - autoPlayMessages: autoPlay, - itAutoPlay: itAutoPlay, - activatedFreeTrial: trial, - interactiveTranslator: interactiveTranslator, - interactiveGrammar: interactiveGrammar, - immersionMode: immersionMode, - definitions: definitions, - // translations: translations, - showedItInstructions: showItInstructions, - showedClickMessage: showClickMessage, - showedBlurMeansTranslate: showBlurMeansTranslate, - createdAt: createdAt, - targetLanguage: targetLanguage, - sourceLanguage: sourceLanguage, - country: country, - publicProfile: publicProfile, + await MatrixProfile.saveProfileData( + profileUpdates, + waitForDataInSync: true, ); } + /// Updates the user's profile with the provided information. + /// + /// The [dateOfBirth] parameter is the new date of birth for the user. + /// The [targetLanguage] parameter is the new target language for the user. + /// The [sourceLanguage] parameter is the new source language for the user. + /// The [country] parameter is the new country for the user. + /// The [interests] parameter is a list of new interests for the user. + /// The [speaks] parameter is a list of new languages the user speaks. + /// The [publicProfile] parameter indicates whether the user's profile should be public or not. + /// + /// Throws an error if [userModel] or [accessToken] is null. Future updateUserProfile({ String? dateOfBirth, String? targetLanguage, @@ -170,7 +228,14 @@ class UserController extends BaseController { List? speaks, bool? publicProfile, }) async { - if (userModel == null) throw Exception("Local userModel not defined"); + final String? accessToken = await this.accessToken; + if (userModel == null || accessToken == null) { + ErrorHandler.logError( + e: "calling updateUserProfile with userModel == null or accessToken == null", + ); + return; + } + final profileJson = userModel!.profile!.toJson(); if (dateOfBirth != null) { @@ -194,230 +259,95 @@ class UserController extends BaseController { if (publicProfile != null) { profileJson[ModelKey.publicProfile] = publicProfile; } + final Profile updatedUserProfile = await PUserRepo.updateUserProfile( Profile.fromJson(profileJson), - await accessToken, + accessToken, ); PUserModel( - access: await accessToken, + access: accessToken, refresh: userModel!.refresh, profile: updatedUserProfile, ).save(_pangeaController); - await updateMatrixProfile( - dateOfBirth: dateOfBirth, - targetLanguage: targetLanguage, - sourceLanguage: sourceLanguage, - country: country, - publicProfile: publicProfile, - ); - } - - PUserModel? get userModel { - final data = _pangeaController.pStoreService.read( - PLocalKey.user, - local: true, - ); - return data != null ? PUserModel.fromJson(data) : null; - } - - Future updateMatrixProfile({ - String? dateOfBirth, - bool? autoPlayMessages, - bool? itAutoPlay, - bool? activatedFreeTrial, - bool? interactiveTranslator, - bool? interactiveGrammar, - bool? immersionMode, - bool? definitions, - // bool? translations, - bool? showedItInstructions, - bool? showedClickMessage, - bool? showedBlurMeansTranslate, - bool? showedTooltipInstructions, - String? createdAt, - String? targetLanguage, - String? sourceLanguage, - String? country, - bool? publicProfile, - }) async { - if (dateOfBirth != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.dateOfBirth.title, - dateOfBirth, - ); - } - if (autoPlayMessages != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.autoPlayMessages.title, - autoPlayMessages, - ); - } - if (itAutoPlay != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.itAutoPlay.title, - itAutoPlay, - ); - } - if (activatedFreeTrial != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.activatedFreeTrial.title, - activatedFreeTrial, - ); - } - if (interactiveTranslator != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.interactiveTranslator.title, - interactiveTranslator, - ); - } - if (interactiveGrammar != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.interactiveGrammar.title, - interactiveGrammar, - ); - } - if (immersionMode != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.immersionMode.title, - immersionMode, - ); - } - if (definitions != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.definitions.title, - definitions, - ); - } - // if (translations != null) { - // await _pangeaController.pStoreService.save( - // MatrixProfile.translations.title, - // translations, - // ); - // } - if (showedItInstructions != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.showedItInstructions.title, - showedItInstructions, - ); - } - if (showedClickMessage != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.showedClickMessage.title, - showedClickMessage, - ); - } - if (showedBlurMeansTranslate != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.showedBlurMeansTranslate.title, - showedBlurMeansTranslate, - ); - } - if (showedTooltipInstructions != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.showedTooltipInstructions.title, - showedTooltipInstructions, - ); - } - if (createdAt != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.createdAt.title, - createdAt, - ); - } - if (targetLanguage != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.targetLanguage.title, - targetLanguage, - ); - } - if (sourceLanguage != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.sourceLanguage.title, - sourceLanguage, - ); - } - if (country != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.country.title, - country, - ); - } - if (publicProfile != null) { - await _pangeaController.pStoreService.save( - MatrixProfile.publicProfile.title, - publicProfile, - ); - } - } - - void _completeCompleter() { - if (!_completer.isCompleted) { - _completer.complete(null); - } - } - - Future get completer async { - if (await isPUserDataAvailable) { - _completeCompleter(); - } - return _completer; + MatrixProfile.saveProfileData({ + MatrixProfileEnum.dateOfBirth.title: dateOfBirth, + MatrixProfileEnum.targetLanguage.title: targetLanguage, + MatrixProfileEnum.sourceLanguage.title: sourceLanguage, + MatrixProfileEnum.country.title: country, + MatrixProfileEnum.publicProfile.title: publicProfile, + }); } + /// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed. + /// It checks if the `userModel` has a non-null `access` token and if the token is expired using the `Jwt.isExpired()` method. + /// If the `userModel` is null or the `access` token is null, it returns true indicating that a new JWT is needed. bool get needNewJWT => userModel?.access != null ? Jwt.isExpired(userModel!.access) : true; - Future get accessToken async { - await (await completer).future; - // if userModel null or access token expired then fetchUserModel + /// Retrieves the access token for the user. + /// + /// If the locally stored user model is null or the access token has + /// expired, it fetches the user model. + /// If the user model is still null after fetching, an error is logged. + /// + /// Returns the access token as a string, or null if the user model is null. + Future get accessToken async { final PUserModel? useThisOne = needNewJWT ? await fetchUserModel() : userModel; if (useThisOne == null) { - //debugger(when: kDebugMode); - throw Exception("trying to get accessToken with userModel = null"); + ErrorHandler.logError( + e: "trying to get accessToken with userModel = null", + ); } - return useThisOne.access; + return useThisOne?.access; } - String? get userId { - return _pangeaController.matrixState.client.userID; - } - - String get fullname { - final String? userID = userId; - if (userID == null) { - throw Exception('User ID not found'); + /// Returns the full name of the user. + /// If the [userId] is null, an error will be logged and null will be returned. + /// The full name is obtained by extracting the substring before the first occurrence of ":" in the [userId] + /// and then replacing all occurrences of "@" with an empty string. + String? get fullname { + if (userId == null) { + ErrorHandler.logError( + e: "calling fullname with userId == null", + ); + return null; } - return userID.substring(0, userID.indexOf(":")).replaceAll("@", ""); + return userId!.substring(0, userId!.indexOf(":")).replaceAll("@", ""); } + /// Checks if the user data is available. + /// Returns a [Future] that completes with a [bool] value + /// indicating whether the user data is available or not. Future get isPUserDataAvailable async { try { final PUserModel? toCheck = userModel ?? (await fetchUserModel()); - return toCheck != null ? true : false; - } catch (err) { + return toCheck != null; + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); return false; } } + /// Checks if user data is available and the date of birth is set. + /// Returns a [Future] that completes with a [bool] value indicating + /// whether the user data is available and the date of birth is set. Future get isUserDataAvailableAndDateOfBirthSet async { try { - final client = _pangeaController.matrixState.client; - if (client.prevBatch == null) { - await client.onSync.stream.first; - } + // the function fetchUserModel() uses a completer, so it shouldn't + // re-call the endpoint if it has already been called await fetchUserModel(); - final localAccountData = _pangeaController.pStoreService.read( - ModelKey.userDateOfBirth, - ); - return localAccountData != null; - } catch (err) { + return MatrixProfile.dateOfBirth != null; + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); return false; } } + /// Returns a boolean value indicating whether the user is currently in the trial window. bool get inTrialWindow { final String? createdAt = userModel?.profile?.createdAt; if (createdAt == null) { @@ -428,6 +358,14 @@ class UserController extends BaseController { ); } + /// Checks if the user's languages are set. + /// Returns a [Future] that completes with a [bool] value + /// indicating whether the user's languages are set. + /// + /// A user's languages are considered set if the source and target languages + /// are not null, not empty, and not equal to the [LanguageKeys.unknownLanguage] constant. + /// + /// If an error occurs during the process, it logs the error and returns `false`. Future get areUserLanguagesSet async { try { final PUserModel? toCheck = userModel ?? (await fetchUserModel()); @@ -442,18 +380,29 @@ class UserController extends BaseController { tgtLang.isNotEmpty && srcLang != LanguageKeys.unknownLanguage && tgtLang != LanguageKeys.unknownLanguage; - } catch (err) { + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); return false; } } - String? get _matrixAccessToken => - _pangeaController.matrixState.client.accessToken; - + /// Returns a boolean value indicating whether the user's profile is public. bool get isPublic => _pangeaController.userController.userModel?.profile?.publicProfile ?? false; + /// Retrieves the user's email address. + /// + /// This method fetches the user's email address by making a request to the + /// Matrix server. It uses the `_pangeaController` instance to access the + /// Matrix client and retrieve the account's third-party identifiers. It then + /// filters the identifiers to find the first one with the medium set to + /// `ThirdPartyIdentifierMedium.email`. Finally, it returns the email address + /// associated with the identifier, or `null` if no email address is found. + /// + /// Returns: + /// - The user's email address as a [String], or `null` if no email address + /// is found. Future get userEmail async { final List? identifiers = await _pangeaController.matrixState.client.getAccount3PIDs(); diff --git a/lib/pangea/controllers/word_net_controller.dart b/lib/pangea/controllers/word_net_controller.dart index a67941465..43d1a66b0 100644 --- a/lib/pangea/controllers/word_net_controller.dart +++ b/lib/pangea/controllers/word_net_controller.dart @@ -1,8 +1,9 @@ import 'package:collection/collection.dart'; -import 'package:http/http.dart' as http; - import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/repo/word_repo.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:http/http.dart' as http; + import '../models/word_data_model.dart'; import 'base_controller.dart'; import 'pangea_controller.dart'; @@ -31,7 +32,7 @@ class WordController extends BaseController { ), ); - Future getWordDataGlobal({ + Future getWordDataGlobal({ required String word, required String fullText, required String? userL1, @@ -53,8 +54,18 @@ class WordController extends BaseController { if (local != null) return local; + final String? accessToken = + await _pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in word controller", + s: StackTrace.current, + ); + return null; + } + final WordData remote = await WordRepo.getWordNetData( - accessToken: await _pangeaController.userController.accessToken, + accessToken: accessToken, fullText: fullText, word: word, userL1: userL1, diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index 334c0fa78..06f34cf1f 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -88,7 +88,7 @@ class PangeaMessageEvent { return _latestEdit; } - Future getMatrixAudioFile( + Future getMatrixAudioFile( String langCode, BuildContext context, ) async { @@ -102,11 +102,15 @@ class PangeaMessageEvent { langCode: langCode, ); - final TextToSpeechResponse response = + final TextToSpeechResponse? response = await MatrixState.pangeaController.textToSpeech.get( params, ); + if (response == null) { + return null; + } + final audioBytes = base64.decode(response.audioContent); final eventIdParam = _event.eventId; final fileName = @@ -177,10 +181,13 @@ class PangeaMessageEvent { langCode: langCode, ); - final TextToSpeechResponse response = + final TextToSpeechResponse? response = await MatrixState.pangeaController.textToSpeech.get( params, ); + if (response == null) { + return null; + } final audioBytes = base64.decode(response.audioContent); @@ -323,7 +330,7 @@ class PangeaMessageEvent { debugPrint("mimeType ${matrixFile.mimeType}"); debugPrint("encoding ${mimeTypeToAudioEncoding(matrixFile.mimeType)}"); - final SpeechToTextModel response = + final SpeechToTextModel? response = await MatrixState.pangeaController.speechToText.get( SpeechToTextRequestModel( audioContent: matrixFile.bytes, @@ -339,6 +346,10 @@ class PangeaMessageEvent { ), ); + if (response == null) { + return null; + } + _representations?.add( RepresentationEvent( timeline: timeline, diff --git a/lib/pangea/models/space_model.dart b/lib/pangea/models/space_model.dart index 946da6dc7..fae097ee0 100644 --- a/lib/pangea/models/space_model.dart +++ b/lib/pangea/models/space_model.dart @@ -1,5 +1,6 @@ import 'dart:developer'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -309,4 +310,19 @@ extension SettingCopy on ToolSetting { return L10n.of(context)!.autoIGCToolDescription; } } + + MatrixProfileEnum get asMatrixProfileField { + switch (this) { + case ToolSetting.interactiveTranslator: + return MatrixProfileEnum.interactiveTranslator; + case ToolSetting.interactiveGrammar: + return MatrixProfileEnum.interactiveGrammar; + case ToolSetting.immersionMode: + return MatrixProfileEnum.immersionMode; + case ToolSetting.definitions: + return MatrixProfileEnum.definitions; + case ToolSetting.autoIGC: + return MatrixProfileEnum.autoIGC; + } + } } diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index 3e3a508cd..af7de1ee5 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -1,22 +1,18 @@ -import 'dart:convert'; - import 'package:country_picker/country_picker.dart'; import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; import '../constants/language_constants.dart'; import 'language_model.dart'; -PUserModel pUserModelFromJson(String str) => - PUserModel.fromJson(json.decode(str)); - -String pUserModelToJson(PUserModel data) => json.encode(data.toJson()); - class PUserModel { String access; String refresh; @@ -46,12 +42,12 @@ class PUserModel { await pangeaController.pStoreService.save( PLocalKey.user, toJson(), - local: true, ); } } -enum MatrixProfile { +/// A list of all the fields in the user profile saved to matrix +enum MatrixProfileEnum { dateOfBirth, autoPlayMessages, itAutoPlay, @@ -60,7 +56,6 @@ enum MatrixProfile { interactiveGrammar, immersionMode, definitions, - // translations, showedItInstructions, showedClickMessage, showedBlurMeansTranslate, @@ -73,49 +68,182 @@ enum MatrixProfile { autoIGC, } -extension MatrixProfileExtension on MatrixProfile { +extension MatrixProfileEnumExtension on MatrixProfileEnum { String get title { switch (this) { - case MatrixProfile.dateOfBirth: + case MatrixProfileEnum.dateOfBirth: return ModelKey.userDateOfBirth; - case MatrixProfile.autoPlayMessages: - return PLocalKey.autoPlayMessages; - case MatrixProfile.itAutoPlay: - return PLocalKey.itAutoPlay; - case MatrixProfile.activatedFreeTrial: - return PLocalKey.activatedTrialKey; - case MatrixProfile.interactiveTranslator: + case MatrixProfileEnum.autoPlayMessages: + return ModelKey.autoPlayMessages; + case MatrixProfileEnum.itAutoPlay: + return ModelKey.itAutoPlay; + case MatrixProfileEnum.activatedFreeTrial: + return ModelKey.activatedTrialKey; + case MatrixProfileEnum.interactiveTranslator: return ToolSetting.interactiveTranslator.toString(); - case MatrixProfile.interactiveGrammar: + case MatrixProfileEnum.interactiveGrammar: return ToolSetting.interactiveGrammar.toString(); - case MatrixProfile.immersionMode: + case MatrixProfileEnum.immersionMode: return ToolSetting.immersionMode.toString(); - case MatrixProfile.definitions: + case MatrixProfileEnum.definitions: return ToolSetting.definitions.toString(); - // case MatrixProfile.translations: - // return ToolSetting.translations.toString(); - case MatrixProfile.autoIGC: + case MatrixProfileEnum.autoIGC: return ToolSetting.autoIGC.toString(); - case MatrixProfile.showedItInstructions: + case MatrixProfileEnum.showedItInstructions: return InstructionsEnum.itInstructions.toString(); - case MatrixProfile.showedClickMessage: + case MatrixProfileEnum.showedClickMessage: return InstructionsEnum.clickMessage.toString(); - case MatrixProfile.showedBlurMeansTranslate: + case MatrixProfileEnum.showedBlurMeansTranslate: return InstructionsEnum.blurMeansTranslate.toString(); - case MatrixProfile.showedTooltipInstructions: + case MatrixProfileEnum.showedTooltipInstructions: return InstructionsEnum.tooltipInstructions.toString(); - case MatrixProfile.createdAt: + case MatrixProfileEnum.createdAt: return ModelKey.userCreatedAt; - case MatrixProfile.targetLanguage: + case MatrixProfileEnum.targetLanguage: return ModelKey.l2LanguageKey; - case MatrixProfile.sourceLanguage: + case MatrixProfileEnum.sourceLanguage: return ModelKey.l1LanguageKey; - case MatrixProfile.country: + case MatrixProfileEnum.country: return ModelKey.userCountry; - case MatrixProfile.publicProfile: + case MatrixProfileEnum.publicProfile: return ModelKey.publicProfile; } } + + ToolSetting? get asToolSetting { + switch (this) { + case MatrixProfileEnum.interactiveTranslator: + return ToolSetting.interactiveTranslator; + case MatrixProfileEnum.interactiveGrammar: + return ToolSetting.interactiveGrammar; + case MatrixProfileEnum.immersionMode: + return ToolSetting.immersionMode; + case MatrixProfileEnum.definitions: + return ToolSetting.definitions; + case MatrixProfileEnum.autoIGC: + return ToolSetting.autoIGC; + default: + return null; + } + } +} + +/// A wrapper around the matrix account data for the user profile. +/// Enables easy access to the profile data and saving new data. +/// The matrix profile doesn't function exactly the same as a 'model', +/// since all the data here is already stored in the client as account +/// data, and duplicating that data could lead to some inconsistenies. +/// So this class is more of a helper class to make it easier to +/// access and save the data. +class MatrixProfile { + /// Returns the profile of the user. + /// + /// The profile is retrieved from the `MatrixState.pangeaController.matrixState.client.accountData` + /// using the key `ModelKey.userProfile`. It returns a `Map` object + /// representing the user's profile information. + static Map? get profile => MatrixState.pangeaController + .matrixState.client.accountData[ModelKey.userProfile]?.content; + + static dynamic getProfileData(MatrixProfileEnum key) => profile?[key.title]; + + /// Saves the profile data by updating the current user's profile with the provided updates. + /// + /// The [updates] parameter is a map containing the key-value pairs of the profile fields to be updated. + /// Only non-null values in the [updates] map will be applied to the current profile. + /// + /// If the updated profile is equal to the current profile, no changes will be made. + /// + /// If [waitForDataInSync] is true, the function will wait for the updated data in a sync update + /// If this is set to false, after this function completes there may be a gap where the + /// data has been sent but is not in the client's account data, as the sync update has not yet been received. + static Future saveProfileData( + Map updates, { + waitForDataInSync = false, + }) async { + final currentProfile = toJson(); + for (final entry in updates.entries) { + if (entry.value == null) continue; + currentProfile[entry.key] = entry.value; + } + if (mapEquals(MatrixProfile.toJson(), currentProfile)) return; + + final PangeaController pangeaController = MatrixState.pangeaController; + final Client client = pangeaController.matrixState.client; + + final List profileKeys = + MatrixProfileEnum.values.map((e) => e.title).toList(); + + Future? waitForUpdate; + if (waitForDataInSync) { + waitForUpdate = client.onSync.stream.firstWhere( + (sync) => + sync.accountData != null && + sync.accountData!.any( + (event) => event.content.keys.any((k) => profileKeys.contains(k)), + ), + ); + } + await client.setAccountData( + client.userID!, + ModelKey.userProfile, + currentProfile, + ); + if (waitForDataInSync) await waitForUpdate; + } + + /// Converts the Matrix Profile to a JSON representation. + static Map toJson() { + final Map json = {}; + for (final value in MatrixProfileEnum.values) { + json[value.title] = getProfileData(value); + } + return json; + } + + // below are some convenience methods for accessing the profile data + // getProfileData could be used directly, but these methods reduce the + // need for repeating the same code (like parsing DateTimes or + // assigning default values to null booleans) when accessing specific values. + + static DateTime? get dateOfBirth { + final dob = getProfileData(MatrixProfileEnum.dateOfBirth); + return dob != null ? DateTime.parse(dob) : null; + } + + static bool get autoPlayMessages => + getProfileData(MatrixProfileEnum.autoPlayMessages) ?? false; + static bool get itAutoPlay => + getProfileData(MatrixProfileEnum.itAutoPlay) ?? false; + static bool get activatedFreeTrial => + getProfileData(MatrixProfileEnum.activatedFreeTrial) ?? false; + static bool get interactiveTranslator => + getProfileData(MatrixProfileEnum.interactiveTranslator) ?? true; + static bool get interactiveGrammar => + getProfileData(MatrixProfileEnum.interactiveGrammar) ?? true; + static bool get immersionMode => + getProfileData(MatrixProfileEnum.immersionMode) ?? false; + static bool get definitions => + getProfileData(MatrixProfileEnum.definitions) ?? true; + static bool get autoIGC => getProfileData(MatrixProfileEnum.autoIGC) ?? false; + + /// A list of all the fields in MatrixProfileEnum that correspond to tool settings + static List get toolSettings => [ + MatrixProfileEnum.interactiveTranslator, + MatrixProfileEnum.interactiveGrammar, + MatrixProfileEnum.immersionMode, + MatrixProfileEnum.definitions, + MatrixProfileEnum.autoIGC, + ]; + + /// A list of all the fields in MatrixProfileEnum that correspond to pangea profile values + static List pangeaProfileFields = [ + MatrixProfileEnum.dateOfBirth, + MatrixProfileEnum.createdAt, + MatrixProfileEnum.targetLanguage, + MatrixProfileEnum.sourceLanguage, + MatrixProfileEnum.country, + MatrixProfileEnum.publicProfile, + ]; } class Profile { diff --git a/lib/pangea/pages/find_partner/find_partner.dart b/lib/pangea/pages/find_partner/find_partner.dart index f2bd7e8c1..44274a608 100644 --- a/lib/pangea/pages/find_partner/find_partner.dart +++ b/lib/pangea/pages/find_partner/find_partner.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:country_picker/country_picker.dart'; import 'package:fluffychat/pangea/models/language_model.dart'; import 'package:fluffychat/pangea/models/user_model.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/material.dart'; import '../../../widgets/matrix.dart'; @@ -91,9 +92,19 @@ class FindPartnerController extends State { if (loading || nextUrl == null) return; setState(() => loading = true); + final String? accessToken = + await pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in find partner controller", + s: StackTrace.current, + ); + return; + } + final UserProfileSearchResponse response = await PUserRepo.searchUserProfiles( - accessToken: await pangeaController.userController.accessToken, + accessToken: accessToken, targetLanguage: targetLanguageSearch.langCode, sourceLanguage: sourceLanguageSearch.langCode, country: countrySearch, diff --git a/lib/pangea/pages/p_user_age/p_user_age.dart b/lib/pangea/pages/p_user_age/p_user_age.dart index 54e98ab94..adb060e38 100644 --- a/lib/pangea/pages/p_user_age/p_user_age.dart +++ b/lib/pangea/pages/p_user_age/p_user_age.dart @@ -88,7 +88,7 @@ class PUserAgeController extends State { final String date = DateFormat('yyyy-MM-dd').format(selectedDate!); if (pangeaController.userController.userModel?.access == null) { - await pangeaController.userController.createPangeaUser(dob: date); + await pangeaController.userController.createProfile(dob: date); } else { await pangeaController.userController.updateUserProfile( dateOfBirth: date, diff --git a/lib/pangea/pages/settings_learning/settings_learning_view.dart b/lib/pangea/pages/settings_learning/settings_learning_view.dart index 191bd76db..8198e70dc 100644 --- a/lib/pangea/pages/settings_learning/settings_learning_view.dart +++ b/lib/pangea/pages/settings_learning/settings_learning_view.dart @@ -1,5 +1,5 @@ -import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/widgets/user_settings/country_picker_tile.dart'; @@ -52,34 +52,30 @@ class SettingsLearningView extends StatelessWidget { ListTile( subtitle: Text(L10n.of(context)!.toggleToolSettingsDescription), ), - for (final setting in ToolSetting.values) - PSettingsSwitchListTile.adaptive( - defaultValue: controller.pangeaController.localSettings - .userLanguageToolSetting(setting), - title: setting.toolName(context), - subtitle: setting.toolDescription(context), - pStoreKey: setting.toString(), - local: false, - ), - PSettingsSwitchListTile.adaptive( - defaultValue: controller.pangeaController.pStoreService.read( - PLocalKey.itAutoPlay, - ) ?? - false, - title: L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader, + for (final setting in MatrixProfile.toolSettings) + setting.asToolSetting != null + ? ProfileSettingsSwitchListTile.adaptive( + defaultValue: controller + .pangeaController.permissionsController + .userToolSetting(setting), + title: setting.asToolSetting!.toolName(context), + subtitle: + setting.asToolSetting!.toolDescription(context), + profileKey: setting.asToolSetting!.asMatrixProfileField, + ) + : const SizedBox(), + ProfileSettingsSwitchListTile.adaptive( + defaultValue: MatrixProfile.itAutoPlay, + title: + L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader, subtitle: L10n.of(context)!.interactiveTranslatorAutoPlayDesc, - pStoreKey: PLocalKey.itAutoPlay, - local: false, + profileKey: MatrixProfileEnum.itAutoPlay, ), - PSettingsSwitchListTile.adaptive( - defaultValue: controller.pangeaController.pStoreService.read( - PLocalKey.autoPlayMessages, - ) ?? - false, + ProfileSettingsSwitchListTile.adaptive( + defaultValue: MatrixProfile.autoPlayMessages, title: L10n.of(context)!.autoPlayTitle, subtitle: L10n.of(context)!.autoPlayDesc, - pStoreKey: PLocalKey.autoPlayMessages, - local: false, + profileKey: MatrixProfileEnum.autoPlayMessages, ), ], ), diff --git a/lib/pangea/utils/p_store.dart b/lib/pangea/utils/p_store.dart index f92e41176..0dfe0d5cd 100644 --- a/lib/pangea/utils/p_store.dart +++ b/lib/pangea/utils/p_store.dart @@ -1,133 +1,71 @@ import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:get_storage/get_storage.dart'; -import 'package:matrix/matrix.dart'; -class PLocalStore { +/// Utility to save and read data both in the matrix profile (this is the default +/// behavior) and in the local storage (local needs to be specificied). An +/// instance of this class is created in the PangeaController. +class PStore { final GetStorage _box = GetStorage(); final PangeaController pangeaController; - PLocalStore({required this.pangeaController}); + PStore({required this.pangeaController}); - /// save data in local + /// Saves the provided [data] with the specified [key] in the local storage. + /// + /// By default, the [data] is considered as account data, but you can set + /// [isAccountData] to false if it's not account-related data. + /// + /// Example usage: + /// ```dart + /// await save('user', {'name': 'John Doe', 'age': 25}); + /// ``` Future save( String key, dynamic data, { - bool addClientIdToKey = true, - bool local = false, + bool isAccountData = true, }) async { - local - ? await saveLocal( - key, - data, - addClientIdToKey: addClientIdToKey, - ) - : await saveProfile(key, data); + await _box.write(_key(key, isAccountData: isAccountData), data); } - /// fetch data from local - dynamic read( - String key, { - bool addClientIdToKey = true, - local = false, - }) { - return local - ? readLocal( - key, - addClientIdToKey: addClientIdToKey, - ) - : readProfile(key); - } - - /// delete data from local - Future delete( - String key, { - bool addClientIdToKey = true, - local = false, - }) async { - return local - ? deleteLocal( - key, - addClientIdToKey: addClientIdToKey, - ) - : deleteProfile(key); - } - - /// save data in local - Future saveLocal( - String key, - dynamic data, { - bool addClientIdToKey = true, - }) async { - await _box.write(_key(key, addClientIdToKey: addClientIdToKey), data); - } - - Future saveProfile( - String key, - dynamic data, - ) async { - final waitForAccountSync = - pangeaController.matrixState.client.onSync.stream.firstWhere( - (sync) => - sync.accountData != null && - sync.accountData!.any( - (event) => event.content.keys.any( - (k) => k == key, - ), - ), - ); - await pangeaController.matrixState.client.setAccountData( - pangeaController.matrixState.client.userID!, - key, - {key: data}, - ); - await waitForAccountSync; - await pangeaController.matrixState.client.onSyncStatus.stream.firstWhere( - (syncStatus) => syncStatus.status == SyncStatus.finished, - ); - } - - /// fetch data from local - dynamic readLocal(String key, {bool addClientIdToKey = true}) { + /// Reads the value associated with the given [key] from the local store. + /// + /// If [isAccountData] is true, tries to find key assosiated with the logged in user. + /// Otherwise, it is read from the general store. + /// + /// Returns the value associated with the [key], or + /// null if the user ID is null or value hasn't been set. + dynamic read(String key, {bool isAccountData = true}) { return pangeaController.matrixState.client.userID != null - ? _box.read(_key(key, addClientIdToKey: addClientIdToKey)) + ? _box.read(_key(key, isAccountData: isAccountData)) : null; } - dynamic readProfile(String key) { - try { - return pangeaController.matrixState.client.accountData[key]?.content[key]; - } catch (err) { - ErrorHandler.logError(e: err); - return null; - } - } - - /// delete data from local - Future deleteLocal(String key, {bool addClientIdToKey = true}) async { + /// Deletes the value associated with the given [key] from the local store. + /// + /// If [isAccountData] is true (default), will try to use key assosiated with the logged in user's ID + /// + /// Returns a [Future] that completes when the value is successfully deleted. + /// If the user is not logged in, the value will not be deleted and the [Future] will complete with null. + Future delete(String key, {bool isAccountData = true}) async { return pangeaController.matrixState.client.userID != null - ? _box.remove(_key(key, addClientIdToKey: addClientIdToKey)) + ? _box.remove(_key(key, isAccountData: isAccountData)) : null; } - Future deleteProfile(key) async { - return pangeaController.matrixState.client.userID != null - ? pangeaController.matrixState.client.setAccountData( - pangeaController.matrixState.client.userID!, - key, - {key: null}, - ) - : null; - } - - _key(String key, {bool addClientIdToKey = true}) { - return addClientIdToKey + /// Returns the key for storing data in the pangea store. + /// + /// The [key] parameter represents the base key for the data. + /// The [isAccountData] parameter indicates whether the data is account-specific. + /// If [isAccountData] is true, the account-specific key is returned by appending the user ID to the base key. + /// If [isAccountData] is false, the base key is returned as is. + String _key(String key, {bool isAccountData = true}) { + return isAccountData ? pangeaController.matrixState.client.userID! + key : key; } - /// clear all local storage - clearStorage() { + /// Clears the storage by erasing all data in the box. + void clearStorage() { _box.erase(); } } diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 8d2d66b7d..ad38e688d 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -3,9 +3,9 @@ import 'dart:developer'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/overlay.dart'; @@ -329,17 +329,12 @@ class MessageToolbarState extends State { }); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - final bool autoplay = MatrixState.pangeaController.pStoreService.read( - PLocalKey.autoPlayMessages, - ) ?? - false; - if (widget.pangeaMessageEvent.isAudioMessage) { updateMode(MessageMode.speechToText); return; } - autoplay + MatrixProfile.autoPlayMessages ? updateMode(MessageMode.textToSpeech) : updateMode(MessageMode.translation); }); diff --git a/lib/pangea/widgets/chat/message_translation_card.dart b/lib/pangea/widgets/chat/message_translation_card.dart index fd80df134..9ee15faf7 100644 --- a/lib/pangea/widgets/chat/message_translation_card.dart +++ b/lib/pangea/widgets/chat/message_translation_card.dart @@ -58,9 +58,17 @@ class MessageTranslationCardState extends State { } oldSelectedText = widget.selection.selectedText; - final String accessToken = + final String? accessToken = await MatrixState.pangeaController.userController.accessToken; + if (accessToken == null) { + ErrorHandler.logError( + e: "null accessToken in translateSelection", + s: StackTrace.current, + ); + return; + } + final resp = await FullTextTranslationRepo.translate( accessToken: accessToken, request: FullTextTranslationRequestModel( diff --git a/lib/pangea/widgets/class/join_with_link.dart b/lib/pangea/widgets/class/join_with_link.dart index 804c9b991..a20f7f1e1 100644 --- a/lib/pangea/widgets/class/join_with_link.dart +++ b/lib/pangea/widgets/class/join_with_link.dart @@ -43,8 +43,7 @@ class _JoinClassWithLinkState extends State { await _pangeaController.pStoreService.save( PLocalKey.cachedClassCodeToJoin, classCode, - addClientIdToKey: false, - local: true, + isAccountData: false, ); context.go("/home"); }); diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index 2960d229c..823de6914 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -1,9 +1,9 @@ import 'dart:developer'; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/enum/span_data_type.dart'; import 'package:fluffychat/pangea/models/span_data.dart'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/bot_style.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/match_copy.dart'; @@ -154,21 +154,18 @@ class WordMatchContent extends StatelessWidget { .selected = true; controller.setState( - () => ( - controller.currentExpression = - controller - .widget - .scm - .choreographer - .igc - .igcTextData - !.matches[controller.widget.scm.matchIndex] - .match - .choices![index] - .isBestCorrection + () => (controller.currentExpression = controller + .widget + .scm + .choreographer + .igc + .igcTextData! + .matches[controller.widget.scm.matchIndex] + .match + .choices![index] + .isBestCorrection ? BotExpression.gold - : BotExpression.surprised - ), + : BotExpression.surprised), ); // if (controller.widget.scm.pangeaMatch.match.choices![index].type == // SpanChoiceType.distractor) { @@ -510,12 +507,11 @@ class DontShowSwitchListTileState extends State { activeColor: AppConfig.activeToggleColor, title: Text(L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader), value: switchValue, - onChanged: (value) => { - widget.controller.pStoreService.save( - PLocalKey.itAutoPlay.toString(), - value, - ), - setState(() => switchValue = value), + onChanged: (value) { + MatrixProfile.saveProfileData( + {MatrixProfileEnum.itAutoPlay.title: value}, + ); + setState(() => switchValue = value); }, ); } diff --git a/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart b/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart index 4c3e77599..85dbb7e36 100644 --- a/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart +++ b/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart @@ -1,37 +1,34 @@ import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; -class PSettingsSwitchListTile extends StatefulWidget { +class ProfileSettingsSwitchListTile extends StatefulWidget { final bool defaultValue; - final String pStoreKey; + final MatrixProfileEnum profileKey; final String title; final String? subtitle; - final bool local; - const PSettingsSwitchListTile.adaptive({ + const ProfileSettingsSwitchListTile.adaptive({ super.key, this.defaultValue = false, - required this.pStoreKey, + required this.profileKey, required this.title, this.subtitle, - this.local = false, }); @override PSettingsSwitchListTileState createState() => PSettingsSwitchListTileState(); } -class PSettingsSwitchListTileState extends State { +class PSettingsSwitchListTileState + extends State { bool currentValue = true; @override void initState() { - currentValue = MatrixState.pangeaController.pStoreService.read( - widget.pStoreKey, - local: widget.local, + currentValue = MatrixProfile.getProfileData( + widget.profileKey, ) ?? widget.defaultValue; super.initState(); @@ -39,7 +36,6 @@ class PSettingsSwitchListTileState extends State { @override Widget build(BuildContext context) { - final PangeaController pangeaController = MatrixState.pangeaController; return SwitchListTile.adaptive( value: currentValue, title: Text(widget.title), @@ -47,20 +43,17 @@ class PSettingsSwitchListTileState extends State { subtitle: widget.subtitle != null ? Text(widget.subtitle!) : null, onChanged: (bool newValue) async { try { - await pangeaController.pStoreService.save( - widget.pStoreKey, - newValue, - local: widget.local, - ); - currentValue = newValue; + MatrixProfile.saveProfileData({ + widget.profileKey.title: newValue, + }); + setState(() => currentValue = newValue); } catch (err, s) { ErrorHandler.logError( e: err, - m: "Failed to updates user setting ${widget.pStoreKey}", + m: "Failed to updates user setting ${widget.profileKey.title}", s: s, ); } - setState(() {}); }, ); } diff --git a/lib/widgets/matrix.dart b/lib/widgets/matrix.dart index 5c11d17ba..0f85950ff 100644 --- a/lib/widgets/matrix.dart +++ b/lib/widgets/matrix.dart @@ -342,8 +342,11 @@ class MatrixState extends State with WidgetsBindingObserver { } else { // #Pangea if (state == LoginState.loggedIn) { - await (await pangeaController.userController.completer).future; - await pangeaController.subscriptionController.reinitialize(); + final futures = [ + pangeaController.userController.reinitialize(), + pangeaController.subscriptionController.reinitialize(), + ]; + await Future.wait(futures); } String routeDestination; if (state == LoginState.loggedIn) { From 6ab62e2effa7a371b6e88acdaf86f0edd4cd5df8 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 11 Jul 2024 10:02:53 -0400 Subject: [PATCH 24/51] added functionality to look for data in the only matrix format if it's missing in the new format, make matrixProfile an instance member of userController --- .../controllers/choreographer.dart | 4 +- .../controllers/permissions_controller.dart | 15 +++-- .../controllers/subscription_controller.dart | 4 +- lib/pangea/controllers/user_controller.dart | 21 ++++--- lib/pangea/models/user_model.dart | 56 +++++++++++++------ .../settings_learning_view.dart | 6 +- lib/pangea/widgets/chat/message_toolbar.dart | 3 +- lib/pangea/widgets/igc/span_card.dart | 3 +- .../p_settings_switch_list_tile.dart | 7 ++- 9 files changed, 75 insertions(+), 44 deletions(-) diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index db317cd4e..973a89747 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -14,7 +14,6 @@ import 'package:fluffychat/pangea/models/it_step.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; import 'package:fluffychat/pangea/models/tokens_event_content_model.dart'; -import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/overlay.dart'; @@ -515,7 +514,8 @@ class Choreographer { chatController.room, ); - bool get itAutoPlayEnabled => MatrixProfile.itAutoPlay; + bool get itAutoPlayEnabled => + pangeaController.userController.matrixProfile.itAutoPlay; bool get definitionsEnabled => pangeaController.permissionsController.isToolEnabled( diff --git a/lib/pangea/controllers/permissions_controller.dart b/lib/pangea/controllers/permissions_controller.dart index 3932eb7d2..463a8c9a3 100644 --- a/lib/pangea/controllers/permissions_controller.dart +++ b/lib/pangea/controllers/permissions_controller.dart @@ -32,7 +32,8 @@ class PermissionsController extends BaseController { /// Returns false if user is null bool isUser18() => - MatrixProfile.dateOfBirth?.isAtLeastYearsOld( + _pangeaController.userController.matrixProfile.dateOfBirth + ?.isAtLeastYearsOld( AgeLimits.toAccessFeatures, ) ?? false; @@ -99,15 +100,17 @@ class PermissionsController extends BaseController { bool userToolSetting(MatrixProfileEnum setting) { switch (setting.asToolSetting) { case ToolSetting.interactiveTranslator: - return MatrixProfile.interactiveTranslator; + return _pangeaController + .userController.matrixProfile.interactiveTranslator; case ToolSetting.interactiveGrammar: - return MatrixProfile.interactiveGrammar; + return _pangeaController + .userController.matrixProfile.interactiveGrammar; case ToolSetting.immersionMode: - return MatrixProfile.immersionMode; + return _pangeaController.userController.matrixProfile.immersionMode; case ToolSetting.definitions: - return MatrixProfile.definitions; + return _pangeaController.userController.matrixProfile.definitions; case ToolSetting.autoIGC: - return MatrixProfile.autoIGC; + return _pangeaController.userController.matrixProfile.autoIGC; default: return false; } diff --git a/lib/pangea/controllers/subscription_controller.dart b/lib/pangea/controllers/subscription_controller.dart index 9e5d59b05..e825a7126 100644 --- a/lib/pangea/controllers/subscription_controller.dart +++ b/lib/pangea/controllers/subscription_controller.dart @@ -181,10 +181,10 @@ class SubscriptionController extends BaseController { bool get _activatedNewUserTrial => _pangeaController.userController.inTrialWindow && - MatrixProfile.activatedFreeTrial; + _pangeaController.userController.matrixProfile.activatedFreeTrial; void activateNewUserTrial() { - MatrixProfile.saveProfileData({ + _pangeaController.userController.matrixProfile.saveProfileData({ MatrixProfileEnum.activatedFreeTrial.title: true, }).then((_) { setNewUserTrial(); diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index fa72e6b58..541371e34 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -27,6 +27,11 @@ class UserController extends BaseController { String? get _matrixAccessToken => _pangeaController.matrixState.client.accessToken; + /// An instance of matrix profile. Used to update and access info from the user's matrix profile. + /// No information needs to be passing in the constructor as the matrix + /// profile get all of it's internal data the accountData stored in the client. + MatrixProfile matrixProfile = MatrixProfile(); + /// Returns the [PUserModel] object representing the current user. /// /// This method retrieves the user data from the local storage using the [PLocalKey.user] key. @@ -58,7 +63,7 @@ class UserController extends BaseController { matrixAccessToken: _matrixAccessToken!, ); newUserModel.save(_pangeaController); - await MatrixProfile.saveProfileData( + await matrixProfile.saveProfileData( {MatrixProfileEnum.dateOfBirth.title: dob}, waitForDataInSync: true, ); @@ -161,7 +166,7 @@ class UserController extends BaseController { final Profile? pangeaProfile = userModel?.profile; for (final field in MatrixProfile.pangeaProfileFields) { - final dynamic matrixValue = MatrixProfile.getProfileData(field); + final dynamic matrixValue = matrixProfile.getProfileData(field); dynamic pangeaValue; switch (field) { case MatrixProfileEnum.dateOfBirth: @@ -194,7 +199,7 @@ class UserController extends BaseController { if (profileUpdates.containsKey(value.title)) continue; final dynamic localValue = _pangeaController.pStoreService.read(value.title); - final dynamic matrixValue = MatrixProfile.getProfileData(value); + final dynamic matrixValue = matrixProfile.getProfileData(value); final dynamic unmigratedValue = localValue != null && matrixValue == null ? localValue : null; if (unmigratedValue != null) { @@ -202,7 +207,7 @@ class UserController extends BaseController { } } - await MatrixProfile.saveProfileData( + await matrixProfile.saveProfileData( profileUpdates, waitForDataInSync: true, ); @@ -271,7 +276,7 @@ class UserController extends BaseController { profile: updatedUserProfile, ).save(_pangeaController); - MatrixProfile.saveProfileData({ + matrixProfile.saveProfileData({ MatrixProfileEnum.dateOfBirth.title: dateOfBirth, MatrixProfileEnum.targetLanguage.title: targetLanguage, MatrixProfileEnum.sourceLanguage.title: sourceLanguage, @@ -340,7 +345,7 @@ class UserController extends BaseController { // the function fetchUserModel() uses a completer, so it shouldn't // re-call the endpoint if it has already been called await fetchUserModel(); - return MatrixProfile.dateOfBirth != null; + return matrixProfile.dateOfBirth != null; } catch (err, s) { ErrorHandler.logError(e: err, s: s); return false; @@ -387,9 +392,7 @@ class UserController extends BaseController { } /// Returns a boolean value indicating whether the user's profile is public. - bool get isPublic => - _pangeaController.userController.userModel?.profile?.publicProfile ?? - false; + bool get isPublic => userModel?.profile?.publicProfile ?? false; /// Retrieves the user's email address. /// diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index af7de1ee5..af085bdc5 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -136,15 +136,37 @@ extension MatrixProfileEnumExtension on MatrixProfileEnum { /// So this class is more of a helper class to make it easier to /// access and save the data. class MatrixProfile { + /// Convenience function get get user's account data from the client + Map get accountData => + MatrixState.pangeaController.matrixState.client.accountData; + /// Returns the profile of the user. /// - /// The profile is retrieved from the `MatrixState.pangeaController.matrixState.client.accountData` + /// The profile is retrieved from the user's account data /// using the key `ModelKey.userProfile`. It returns a `Map` object /// representing the user's profile information. - static Map? get profile => MatrixState.pangeaController - .matrixState.client.accountData[ModelKey.userProfile]?.content; + Map? get profile => + accountData[ModelKey.userProfile]?.content; - static dynamic getProfileData(MatrixProfileEnum key) => profile?[key.title]; + /// Retrieves the profile data for the given [key]. + /// + /// This method first tries to get the data from the new profile format. If the data is found, + /// it is returned. If not, it checks if the data is stored in the old format. If it is, the data + /// is saved to the new format and returned. + dynamic getProfileData(MatrixProfileEnum key) { + // try to get the data from the new profile format + if (profile?[key.title] != null) { + return profile?[key.title]; + } + + // check if the data is stored in the old format + // and if so, save it to the new format + final prevFormatData = accountData[key.title]?.content[key.title]; + if (prevFormatData != null) { + saveProfileData({key.title: prevFormatData}); + return prevFormatData; + } + } /// Saves the profile data by updating the current user's profile with the provided updates. /// @@ -156,7 +178,7 @@ class MatrixProfile { /// If [waitForDataInSync] is true, the function will wait for the updated data in a sync update /// If this is set to false, after this function completes there may be a gap where the /// data has been sent but is not in the client's account data, as the sync update has not yet been received. - static Future saveProfileData( + Future saveProfileData( Map updates, { waitForDataInSync = false, }) async { @@ -165,7 +187,7 @@ class MatrixProfile { if (entry.value == null) continue; currentProfile[entry.key] = entry.value; } - if (mapEquals(MatrixProfile.toJson(), currentProfile)) return; + if (mapEquals(toJson(), currentProfile)) return; final PangeaController pangeaController = MatrixState.pangeaController; final Client client = pangeaController.matrixState.client; @@ -192,7 +214,7 @@ class MatrixProfile { } /// Converts the Matrix Profile to a JSON representation. - static Map toJson() { + Map toJson() { final Map json = {}; for (final value in MatrixProfileEnum.values) { json[value.title] = getProfileData(value); @@ -205,26 +227,24 @@ class MatrixProfile { // need for repeating the same code (like parsing DateTimes or // assigning default values to null booleans) when accessing specific values. - static DateTime? get dateOfBirth { + DateTime? get dateOfBirth { final dob = getProfileData(MatrixProfileEnum.dateOfBirth); return dob != null ? DateTime.parse(dob) : null; } - static bool get autoPlayMessages => + bool get autoPlayMessages => getProfileData(MatrixProfileEnum.autoPlayMessages) ?? false; - static bool get itAutoPlay => - getProfileData(MatrixProfileEnum.itAutoPlay) ?? false; - static bool get activatedFreeTrial => + bool get itAutoPlay => getProfileData(MatrixProfileEnum.itAutoPlay) ?? false; + bool get activatedFreeTrial => getProfileData(MatrixProfileEnum.activatedFreeTrial) ?? false; - static bool get interactiveTranslator => + bool get interactiveTranslator => getProfileData(MatrixProfileEnum.interactiveTranslator) ?? true; - static bool get interactiveGrammar => + bool get interactiveGrammar => getProfileData(MatrixProfileEnum.interactiveGrammar) ?? true; - static bool get immersionMode => + bool get immersionMode => getProfileData(MatrixProfileEnum.immersionMode) ?? false; - static bool get definitions => - getProfileData(MatrixProfileEnum.definitions) ?? true; - static bool get autoIGC => getProfileData(MatrixProfileEnum.autoIGC) ?? false; + bool get definitions => getProfileData(MatrixProfileEnum.definitions) ?? true; + bool get autoIGC => getProfileData(MatrixProfileEnum.autoIGC) ?? false; /// A list of all the fields in MatrixProfileEnum that correspond to tool settings static List get toolSettings => [ diff --git a/lib/pangea/pages/settings_learning/settings_learning_view.dart b/lib/pangea/pages/settings_learning/settings_learning_view.dart index 8198e70dc..06136cd7e 100644 --- a/lib/pangea/pages/settings_learning/settings_learning_view.dart +++ b/lib/pangea/pages/settings_learning/settings_learning_view.dart @@ -65,14 +65,16 @@ class SettingsLearningView extends StatelessWidget { ) : const SizedBox(), ProfileSettingsSwitchListTile.adaptive( - defaultValue: MatrixProfile.itAutoPlay, + defaultValue: controller + .pangeaController.userController.matrixProfile.itAutoPlay, title: L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader, subtitle: L10n.of(context)!.interactiveTranslatorAutoPlayDesc, profileKey: MatrixProfileEnum.itAutoPlay, ), ProfileSettingsSwitchListTile.adaptive( - defaultValue: MatrixProfile.autoPlayMessages, + defaultValue: controller.pangeaController.userController + .matrixProfile.autoPlayMessages, title: L10n.of(context)!.autoPlayTitle, subtitle: L10n.of(context)!.autoPlayDesc, profileKey: MatrixProfileEnum.autoPlayMessages, diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index ad38e688d..b547efa7a 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -5,7 +5,6 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/overlay.dart'; @@ -334,7 +333,7 @@ class MessageToolbarState extends State { return; } - MatrixProfile.autoPlayMessages + MatrixState.pangeaController.userController.matrixProfile.autoPlayMessages ? updateMode(MessageMode.textToSpeech) : updateMode(MessageMode.translation); }); diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index 823de6914..c758ea213 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -508,7 +508,8 @@ class DontShowSwitchListTileState extends State { title: Text(L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader), value: switchValue, onChanged: (value) { - MatrixProfile.saveProfileData( + MatrixState.pangeaController.userController.matrixProfile + .saveProfileData( {MatrixProfileEnum.itAutoPlay.title: value}, ); setState(() => switchValue = value); diff --git a/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart b/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart index 85dbb7e36..3f04c61fd 100644 --- a/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart +++ b/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart @@ -1,6 +1,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; class ProfileSettingsSwitchListTile extends StatefulWidget { @@ -27,7 +28,8 @@ class PSettingsSwitchListTileState @override void initState() { - currentValue = MatrixProfile.getProfileData( + currentValue = MatrixState.pangeaController.userController.matrixProfile + .getProfileData( widget.profileKey, ) ?? widget.defaultValue; @@ -43,7 +45,8 @@ class PSettingsSwitchListTileState subtitle: widget.subtitle != null ? Text(widget.subtitle!) : null, onChanged: (bool newValue) async { try { - MatrixProfile.saveProfileData({ + MatrixState.pangeaController.userController.matrixProfile + .saveProfileData({ widget.profileKey.title: newValue, }); setState(() => currentValue = newValue); From 6c2d90f8f8bf4885007620aef81e432e2c7b398a Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 11 Jul 2024 11:42:00 -0400 Subject: [PATCH 25/51] prevent recursion when migrating from old matrix account data format --- lib/pangea/models/user_model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index af085bdc5..d87e125fa 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -217,7 +217,7 @@ class MatrixProfile { Map toJson() { final Map json = {}; for (final value in MatrixProfileEnum.values) { - json[value.title] = getProfileData(value); + json[value.title] = profile?[value.title]; } return json; } From 91e2aa2dfd5f662dc98a85cc5561020bc2b78986 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 11 Jul 2024 12:39:03 -0400 Subject: [PATCH 26/51] remove updatePermissions function that is not called --- lib/pangea/models/space_model.dart | 40 ------------------------------ 1 file changed, 40 deletions(-) diff --git a/lib/pangea/models/space_model.dart b/lib/pangea/models/space_model.dart index fae097ee0..81306372c 100644 --- a/lib/pangea/models/space_model.dart +++ b/lib/pangea/models/space_model.dart @@ -110,46 +110,6 @@ class PangeaRoomRules { this.autoIGC = ClassDefaultValues.languageToolPermissions, }); - updatePermission(String key, bool value) { - switch (key) { - case 'isPublic': - isPublic = value; - break; - case 'isOpenEnrollment': - isOpenEnrollment = value; - break; - case 'oneToOneChatClass': - oneToOneChatClass = value; - break; - case 'isCreateRooms': - isCreateRooms = value; - break; - case 'isShareVideo': - isShareVideo = value; - break; - case 'isSharePhoto': - isSharePhoto = value; - break; - case 'isShareFiles': - isShareFiles = value; - break; - case 'isShareLocation': - isShareLocation = value; - break; - case 'isCreateStories': - isCreateStories = value; - break; - case 'isVoiceNotes': - isVoiceNotes = value; - break; - case 'isInviteOnlyStudents': - isInviteOnlyStudents = value; - break; - default: - throw Exception('Invalid key for setting permissions - $key'); - } - } - setLanguageToolSetting(ToolSetting setting, int value) { switch (setting) { case ToolSetting.interactiveTranslator: From 303d2b1e17ac9db88c513950da2262bb5ffdf74e Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 11 Jul 2024 13:11:20 -0400 Subject: [PATCH 27/51] switched accessToken back to be non-nullable --- .../controllers/choreographer.dart | 3 +- .../widgets/it_feedback_card.dart | 10 +---- .../contextual_definition_controller.dart | 7 --- .../controllers/it_feedback_controller.dart | 9 +--- .../language_detection_controller.dart | 10 +---- .../controllers/message_data_controller.dart | 18 +------- .../speech_to_text_controller.dart | 9 +--- .../text_to_speech_controller.dart | 10 +---- lib/pangea/controllers/user_controller.dart | 21 ++++----- .../controllers/word_net_controller.dart | 11 +---- .../pages/find_partner/find_partner.dart | 44 ++++++++++++------- lib/pangea/repo/span_data_repo.dart | 2 +- lib/pangea/repo/topic_data_repo.dart | 3 +- .../widgets/chat/message_audio_card.dart | 1 - .../chat/message_translation_card.dart | 10 +---- 15 files changed, 47 insertions(+), 121 deletions(-) diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 973a89747..9ccb197ee 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -406,8 +406,7 @@ class Choreographer { PangeaTextController get textController => _textController; - Future get accessToken => - pangeaController.userController.accessToken; + Future get accessToken => pangeaController.userController.accessToken; clear() { choreoMode = ChoreoMode.igc; diff --git a/lib/pangea/choreographer/widgets/it_feedback_card.dart b/lib/pangea/choreographer/widgets/it_feedback_card.dart index 840843ed5..4285f4dca 100644 --- a/lib/pangea/choreographer/widgets/it_feedback_card.dart +++ b/lib/pangea/choreographer/widgets/it_feedback_card.dart @@ -73,15 +73,7 @@ class ITFeedbackCardController extends State { isTranslating = true; }); - final String? accessToken = await controller.userController.accessToken; - if (accessToken == null) { - ErrorHandler.logError( - m: "Cannot translate feedback because accessToken is null", - ); - error = "Cannot translate feedback because accessToken is null"; - return; - } - + final String accessToken = await controller.userController.accessToken; FullTextTranslationRepo.translate( accessToken: accessToken, request: FullTextTranslationRequestModel( diff --git a/lib/pangea/controllers/contextual_definition_controller.dart b/lib/pangea/controllers/contextual_definition_controller.dart index ecc08a234..68e075d97 100644 --- a/lib/pangea/controllers/contextual_definition_controller.dart +++ b/lib/pangea/controllers/contextual_definition_controller.dart @@ -50,13 +50,6 @@ class ContextualDefinitionController { ) async { try { final accessToken = await _pangeaController.userController.accessToken; - if (accessToken == null) { - ErrorHandler.logError( - e: "null accessToken in contextual definition controller", - s: StackTrace.current, - ); - return null; - } final ContextualDefinitionResponseModel res = await _ContextualDefinitionRepo.define( accessToken, diff --git a/lib/pangea/controllers/it_feedback_controller.dart b/lib/pangea/controllers/it_feedback_controller.dart index c2cd96cad..74aee21de 100644 --- a/lib/pangea/controllers/it_feedback_controller.dart +++ b/lib/pangea/controllers/it_feedback_controller.dart @@ -51,15 +51,8 @@ class ITFeedbackController { ITFeedbackRequestModel request, ) async { try { - final String? accessToken = + final String accessToken = await _pangeaController.userController.accessToken; - if (accessToken == null) { - ErrorHandler.logError( - e: "null accessToken in it feedback controller", - s: StackTrace.current, - ); - return null; - } final ITFeedbackResponseModel res = await _ITFeedbackRepo.get( accessToken, request, diff --git a/lib/pangea/controllers/language_detection_controller.dart b/lib/pangea/controllers/language_detection_controller.dart index 5ba0f8879..ecbdea47d 100644 --- a/lib/pangea/controllers/language_detection_controller.dart +++ b/lib/pangea/controllers/language_detection_controller.dart @@ -6,7 +6,6 @@ import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/models/language_detection_model.dart'; import 'package:fluffychat/pangea/network/urls.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:http/http.dart' as http; import '../network/requests.dart'; @@ -145,15 +144,8 @@ class LanguageDetectionController { if (_cache.containsKey(params)) { return _cache[params]!.data; } else { - final String? accessToken = + final String accessToken = await _pangeaController.userController.accessToken; - if (accessToken == null) { - ErrorHandler.logError( - e: "null accessToken in language detection controller", - s: StackTrace.current, - ); - return null; - } final Future response = _fetchResponse( accessToken, params, diff --git a/lib/pangea/controllers/message_data_controller.dart b/lib/pangea/controllers/message_data_controller.dart index 7ad3a748f..aa0c77b9d 100644 --- a/lib/pangea/controllers/message_data_controller.dart +++ b/lib/pangea/controllers/message_data_controller.dart @@ -43,15 +43,6 @@ class MessageDataController extends BaseController { TokensRequestModel req, ) async { final accessToken = await _pangeaController.userController.accessToken; - - if (accessToken == null) { - ErrorHandler.logError( - e: "null accessToken in _getTokens", - s: StackTrace.current, - ); - return null; - } - final TokensResponseModel igcTextData = await TokensRepo.tokenize(accessToken, req); @@ -201,15 +192,8 @@ class MessageDataController extends BaseController { ); try { - final String? accessToken = + final String accessToken = await _pangeaController.userController.accessToken; - if (accessToken == null) { - ErrorHandler.logError( - e: "null accessToken in _getPangeaRepresentation", - s: StackTrace.current, - ); - return null; - } final FullTextTranslationResponseModel res = await FullTextTranslationRepo.translate( diff --git a/lib/pangea/controllers/speech_to_text_controller.dart b/lib/pangea/controllers/speech_to_text_controller.dart index 3daeada07..1b151aca0 100644 --- a/lib/pangea/controllers/speech_to_text_controller.dart +++ b/lib/pangea/controllers/speech_to_text_controller.dart @@ -52,15 +52,8 @@ class SpeechToTextController { if (_cache.containsKey(cacheKey)) { return _cache[cacheKey]!.data; } else { - final String? accessToken = + final String accessToken = await _pangeaController.userController.accessToken; - if (accessToken == null) { - ErrorHandler.logError( - e: 'null accessToken in speech to text controller', - s: StackTrace.current, - ); - return null; - } final Future response = _fetchResponse( accessToken: accessToken, diff --git a/lib/pangea/controllers/text_to_speech_controller.dart b/lib/pangea/controllers/text_to_speech_controller.dart index b00fa23a4..25b888e05 100644 --- a/lib/pangea/controllers/text_to_speech_controller.dart +++ b/lib/pangea/controllers/text_to_speech_controller.dart @@ -6,7 +6,6 @@ import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/network/urls.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:http/http.dart'; import '../network/requests.dart'; @@ -100,15 +99,8 @@ class TextToSpeechController { if (_cache.containsKey(params)) { return _cache[params]!.data; } else { - final String? accessToken = + final String accessToken = await _pangeaController.userController.accessToken; - if (accessToken == null) { - ErrorHandler.logError( - e: "null accessToken in text to speech controller", - s: StackTrace.current, - ); - return null; - } final Future response = _fetchResponse( accessToken, params, diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index 541371e34..a4fd08c4d 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -222,8 +222,6 @@ class UserController extends BaseController { /// The [interests] parameter is a list of new interests for the user. /// The [speaks] parameter is a list of new languages the user speaks. /// The [publicProfile] parameter indicates whether the user's profile should be public or not. - /// - /// Throws an error if [userModel] or [accessToken] is null. Future updateUserProfile({ String? dateOfBirth, String? targetLanguage, @@ -233,10 +231,9 @@ class UserController extends BaseController { List? speaks, bool? publicProfile, }) async { - final String? accessToken = await this.accessToken; - if (userModel == null || accessToken == null) { + if (userModel == null) { ErrorHandler.logError( - e: "calling updateUserProfile with userModel == null or accessToken == null", + e: "calling updateUserProfile with userModel == null", ); return; } @@ -267,11 +264,11 @@ class UserController extends BaseController { final Profile updatedUserProfile = await PUserRepo.updateUserProfile( Profile.fromJson(profileJson), - accessToken, + await accessToken, ); PUserModel( - access: accessToken, + access: await accessToken, refresh: userModel!.refresh, profile: updatedUserProfile, ).save(_pangeaController); @@ -295,19 +292,17 @@ class UserController extends BaseController { /// /// If the locally stored user model is null or the access token has /// expired, it fetches the user model. - /// If the user model is still null after fetching, an error is logged. + /// If the user model is still null after fetching, an error thrown. /// /// Returns the access token as a string, or null if the user model is null. - Future get accessToken async { + Future get accessToken async { final PUserModel? useThisOne = needNewJWT ? await fetchUserModel() : userModel; if (useThisOne == null) { - ErrorHandler.logError( - e: "trying to get accessToken with userModel = null", - ); + throw ("Trying to get accessToken with null userModel"); } - return useThisOne?.access; + return useThisOne.access; } /// Returns the full name of the user. diff --git a/lib/pangea/controllers/word_net_controller.dart b/lib/pangea/controllers/word_net_controller.dart index 43d1a66b0..18c3290a9 100644 --- a/lib/pangea/controllers/word_net_controller.dart +++ b/lib/pangea/controllers/word_net_controller.dart @@ -1,7 +1,6 @@ import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/repo/word_repo.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:http/http.dart' as http; import '../models/word_data_model.dart'; @@ -54,16 +53,8 @@ class WordController extends BaseController { if (local != null) return local; - final String? accessToken = + final String accessToken = await _pangeaController.userController.accessToken; - if (accessToken == null) { - ErrorHandler.logError( - e: "null accessToken in word controller", - s: StackTrace.current, - ); - return null; - } - final WordData remote = await WordRepo.getWordNetData( accessToken: accessToken, fullText: fullText, diff --git a/lib/pangea/pages/find_partner/find_partner.dart b/lib/pangea/pages/find_partner/find_partner.dart index 44274a608..46eb365ae 100644 --- a/lib/pangea/pages/find_partner/find_partner.dart +++ b/lib/pangea/pages/find_partner/find_partner.dart @@ -5,6 +5,7 @@ import 'package:fluffychat/pangea/models/language_model.dart'; import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../../../widgets/matrix.dart'; import '../../controllers/pangea_controller.dart'; @@ -39,6 +40,7 @@ class FindPartnerController extends State { final List _userProfilesCache = []; final scrollController = ScrollController(); + String? error; @override void initState() { @@ -67,6 +69,17 @@ class FindPartnerController extends State { @override Widget build(BuildContext context) { + if (error != null && error!.isNotEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(L10n.of(context)!.oopsSomethingWentWrong), + Text(L10n.of(context)!.errorPleaseRefresh), + ], + ), + ); + } return FindPartnerView(this); } @@ -92,26 +105,25 @@ class FindPartnerController extends State { if (loading || nextUrl == null) return; setState(() => loading = true); - final String? accessToken = - await pangeaController.userController.accessToken; - if (accessToken == null) { - ErrorHandler.logError( - e: "null accessToken in find partner controller", - s: StackTrace.current, + UserProfileSearchResponse response; + try { + final String accessToken = + await pangeaController.userController.accessToken; + response = await PUserRepo.searchUserProfiles( + accessToken: accessToken, + targetLanguage: targetLanguageSearch.langCode, + sourceLanguage: sourceLanguageSearch.langCode, + country: countrySearch, + limit: 15, + pageNumber: nextPage.toString(), ); + } catch (err, s) { + error = err.toString(); + setState(() => loading = false); + ErrorHandler.logError(e: err, s: s); return; } - final UserProfileSearchResponse response = - await PUserRepo.searchUserProfiles( - accessToken: accessToken, - targetLanguage: targetLanguageSearch.langCode, - sourceLanguage: sourceLanguageSearch.langCode, - country: countrySearch, - limit: 15, - pageNumber: nextPage.toString(), - ); - nextUrl = response.next; nextPage++; diff --git a/lib/pangea/repo/span_data_repo.dart b/lib/pangea/repo/span_data_repo.dart index 3c2eb2ab3..cc5c0640a 100644 --- a/lib/pangea/repo/span_data_repo.dart +++ b/lib/pangea/repo/span_data_repo.dart @@ -13,7 +13,7 @@ import '../network/urls.dart'; class SpanDataRepo { static Future getSpanDetails( - String? accessToken, { + String accessToken, { required SpanDetailsRepoReqAndRes request, }) async { final Requests req = Requests( diff --git a/lib/pangea/repo/topic_data_repo.dart b/lib/pangea/repo/topic_data_repo.dart index 9a5def03f..7a4ce049f 100644 --- a/lib/pangea/repo/topic_data_repo.dart +++ b/lib/pangea/repo/topic_data_repo.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:flutter/services.dart'; - import 'package:http/http.dart'; import '../config/environment.dart'; @@ -12,7 +11,7 @@ import '../network/urls.dart'; /// accepts ChatTopic and calls an API for a list of Lemma class TopicDataRepo { static Future generate( - String? accessToken, { + String accessToken, { required TopicDataRequest request, }) async { final Requests req = Requests( diff --git a/lib/pangea/widgets/chat/message_audio_card.dart b/lib/pangea/widgets/chat/message_audio_card.dart index 5c1f8e67b..cfae4282a 100644 --- a/lib/pangea/widgets/chat/message_audio_card.dart +++ b/lib/pangea/widgets/chat/message_audio_card.dart @@ -46,7 +46,6 @@ class MessageAudioCardState extends State { await widget.messageEvent.getMatrixAudioFile(langCode, context); if (mounted) setState(() => _isLoading = false); } catch (e, _) { - debugPrint(StackTrace.current.toString()); if (!mounted) return; setState(() => _isLoading = false); ScaffoldMessenger.of(context).showSnackBar( diff --git a/lib/pangea/widgets/chat/message_translation_card.dart b/lib/pangea/widgets/chat/message_translation_card.dart index 9ee15faf7..fd80df134 100644 --- a/lib/pangea/widgets/chat/message_translation_card.dart +++ b/lib/pangea/widgets/chat/message_translation_card.dart @@ -58,17 +58,9 @@ class MessageTranslationCardState extends State { } oldSelectedText = widget.selection.selectedText; - final String? accessToken = + final String accessToken = await MatrixState.pangeaController.userController.accessToken; - if (accessToken == null) { - ErrorHandler.logError( - e: "null accessToken in translateSelection", - s: StackTrace.current, - ); - return; - } - final resp = await FullTextTranslationRepo.translate( accessToken: accessToken, request: FullTextTranslationRequestModel( From ff468cb4bddb8b0a02549a1da9c63e81bc880bc8 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 11 Jul 2024 13:17:30 -0400 Subject: [PATCH 28/51] remove isFetching flag from user controller and replaced that functionality by making the completer nullable --- lib/pangea/controllers/user_controller.dart | 31 +++++++++------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index a4fd08c4d..1fd13acfc 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -69,11 +69,8 @@ class UserController extends BaseController { ); } - /// A boolean flag indicating whether the profile data is currently being fetched. - bool _isFetching = false; - /// A completer for the profile model of a user. - Completer _profileCompleter = Completer(); + Completer? _profileCompleter; /// Fetches the user model. /// @@ -85,25 +82,27 @@ class UserController extends BaseController { /// /// Returns the future value of the user model completer. Future fetchUserModel() async { - if (_profileCompleter.isCompleted) return _profileCompleter.future; - if (_isFetching) { - await _profileCompleter.future; - return _profileCompleter.future; + if (_profileCompleter?.isCompleted ?? false) { + return _profileCompleter!.future; } - _isFetching = true; + if (_profileCompleter != null) { + await _profileCompleter!.future; + return _profileCompleter!.future; + } + + _profileCompleter = Completer(); PUserModel? fetchedUserModel; try { fetchedUserModel = await _fetchUserModel(); } catch (err, s) { ErrorHandler.logError(e: err, s: s); - return null; + } finally { + _profileCompleter!.complete(fetchedUserModel); } - _isFetching = false; - _profileCompleter.complete(fetchedUserModel); - return _profileCompleter.future; + return _profileCompleter!.future; } /// Fetches the user model asynchronously. @@ -133,13 +132,9 @@ class UserController extends BaseController { } /// Reinitializes the user's profile - /// - /// This method sets up the necessary variables and fetches the user model. - /// It completes the [_profileCompleter] with the fetched user model. /// This method should be called whenever the user's login status changes Future reinitialize() async { - _profileCompleter = Completer(); - _isFetching = false; + _profileCompleter = null; await fetchUserModel(); } From 35434b09d1b389f8f4bcf7cd2d458f832aa96769 Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Fri, 12 Jul 2024 11:14:00 -0400 Subject: [PATCH 29/51] Add mounted check to prevent updates on unmounted widgets --- lib/pangea/widgets/chat/message_toolbar.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 8d2d66b7d..e10d637e0 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -194,6 +194,8 @@ class MessageToolbarState extends State { late StreamSubscription toolbarModeStream; void updateMode(MessageMode newMode) { + //Early exit from the function if the widget has been unmounted to prevent updates on an inactive widget. + if (!mounted) return; if (updatingMode) return; debugPrint("updating toolbar mode"); final bool subscribed = @@ -347,6 +349,7 @@ class MessageToolbarState extends State { Timer? timer; selectionStream = widget.textSelection.selectionStream.stream.listen((value) { + //talk about this timer?.cancel(); timer = Timer(const Duration(milliseconds: 500), () { if (value != null && value.isNotEmpty) { From 5f59a39c72c5f5378c44391daf3439e0df217003 Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Fri, 12 Jul 2024 11:16:37 -0400 Subject: [PATCH 30/51] removed comment --- lib/pangea/widgets/chat/message_toolbar.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index e10d637e0..f06b9d1b4 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -349,7 +349,6 @@ class MessageToolbarState extends State { Timer? timer; selectionStream = widget.textSelection.selectionStream.stream.listen((value) { - //talk about this timer?.cancel(); timer = Timer(const Duration(milliseconds: 500), () { if (value != null && value.isNotEmpty) { From bb32f584f4295c6f2b1baf0759017a6e3c9044f7 Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Fri, 12 Jul 2024 12:48:12 -0400 Subject: [PATCH 31/51] Fixed background for login view --- .../pages/p_user_age/p_user_age_view.dart | 51 +++++++------------ 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/lib/pangea/pages/p_user_age/p_user_age_view.dart b/lib/pangea/pages/p_user_age/p_user_age_view.dart index 1438cf0fe..693099994 100644 --- a/lib/pangea/pages/p_user_age/p_user_age_view.dart +++ b/lib/pangea/pages/p_user_age/p_user_age_view.dart @@ -17,32 +17,25 @@ class PUserAgeView extends StatelessWidget { ), body: ListView( children: [ - Container( - margin: const EdgeInsets.only(top: 10), - padding: const EdgeInsets.all(15), - child: Text( - L10n.of(context)!.yourBirthdayPlease, - textAlign: TextAlign.justify, - style: const TextStyle( - color: Colors.black, - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - ), - const SizedBox( - height: 10, - ), Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), - color: Theme.of(context) - .colorScheme - .onSecondaryContainer - .withAlpha(50), + color: Theme.of(context).colorScheme.onSecondaryContainer.withAlpha(50), ), child: Column( children: [ + Padding( + padding: const EdgeInsets.all(15), + child: Text( + L10n.of(context)!.yourBirthdayPlease, + textAlign: TextAlign.justify, + style: const TextStyle( + color: Colors.black, + fontSize: 14, + fontWeight: FontWeight.normal, + ), + ), + ), ListTile( title: Text( L10n.of(context)!.certifyAge(13), @@ -70,23 +63,16 @@ class PUserAgeView extends StatelessWidget { ], ), ), - const SizedBox( - height: 10, - ), - if (controller.error != null) - Padding( - padding: const EdgeInsets.all(12), - child: Text( - controller.error!, - style: const TextStyle(color: Colors.white), - ), - ), + const SizedBox(height: 20), Hero( tag: 'loginButton', child: Padding( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.symmetric(horizontal: 12), child: ElevatedButton( onPressed: controller.createUserInPangea, + style: ElevatedButton.styleFrom( + minimumSize: const Size.fromHeight(50), + ), child: controller.loading ? const LinearProgressIndicator() : Text(L10n.of(context)!.getStarted), @@ -95,7 +81,6 @@ class PUserAgeView extends StatelessWidget { ), ], ), - // ), ); } } From ca9dcba4e747f952e778a417321597f477f64e50 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 12 Jul 2024 13:52:46 -0400 Subject: [PATCH 32/51] removed any functionality attached to pangea profile, only use it to migrate. Made matrix profile a class with instance members. --- .../controllers/choreographer.dart | 5 +- lib/pangea/constants/local.key.dart | 2 +- lib/pangea/constants/model_keys.dart | 3 + .../controllers/language_controller.dart | 17 +- .../controllers/my_analytics_controller.dart | 36 +- .../controllers/permissions_controller.dart | 32 +- .../controllers/subscription_controller.dart | 35 +- lib/pangea/controllers/user_controller.dart | 341 ++---- lib/pangea/models/space_model.dart | 16 - lib/pangea/models/user_model.dart | 1037 +++++------------ .../models/user_profile_search_model.dart | 6 +- .../pages/find_partner/find_partner.dart | 7 +- .../pages/find_partner/find_partner_view.dart | 5 +- lib/pangea/pages/p_user_age/p_user_age.dart | 29 +- .../settings_learning/settings_learning.dart | 62 +- .../settings_learning_view.dart | 153 ++- lib/pangea/repo/user_repo.dart | 54 +- lib/pangea/utils/country_display.dart | 515 ++++++++ lib/pangea/widgets/chat/message_toolbar.dart | 3 +- lib/pangea/widgets/igc/span_card.dart | 14 +- .../user_settings/country_picker_tile.dart | 8 +- .../user_settings/p_language_dialog.dart | 15 +- .../p_settings_switch_list_tile.dart | 21 +- 23 files changed, 1171 insertions(+), 1245 deletions(-) create mode 100644 lib/pangea/utils/country_display.dart diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 9ccb197ee..e5108ae4d 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -513,8 +513,9 @@ class Choreographer { chatController.room, ); - bool get itAutoPlayEnabled => - pangeaController.userController.matrixProfile.itAutoPlay; + bool get itAutoPlayEnabled { + return pangeaController.userController.profile.userSettings.itAutoPlay; + } bool get definitionsEnabled => pangeaController.permissionsController.isToolEnabled( diff --git a/lib/pangea/constants/local.key.dart b/lib/pangea/constants/local.key.dart index b8c1a4f5d..08446ace4 100644 --- a/lib/pangea/constants/local.key.dart +++ b/lib/pangea/constants/local.key.dart @@ -1,5 +1,5 @@ class PLocalKey { - static const String user = 'user'; + static const String access = "access"; static const String cachedClassCodeToJoin = "cachedclasscodetojoin"; static const String beganWebPayment = "beganWebPayment"; static const String dismissedPaywall = 'dismissedPaywall'; diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart index 08164d9f6..7c60b98d7 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -16,6 +16,9 @@ class ModelKey { static const String l1LanguageKey = 'source_language'; static const String publicProfile = 'public'; static const String userId = 'user_id'; + static const String toolSettings = 'tool_settings'; + static const String userSettings = 'user_settings'; + static const String instructionsSettings = 'instructions_settings'; // matrix profile keys // making this a random string so that it's harder to guess diff --git a/lib/pangea/controllers/language_controller.dart b/lib/pangea/controllers/language_controller.dart index b1bc02380..ccd33761f 100644 --- a/lib/pangea/controllers/language_controller.dart +++ b/lib/pangea/controllers/language_controller.dart @@ -1,12 +1,8 @@ -import 'dart:developer'; - import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/models/language_model.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; import '../widgets/user_settings/p_language_dialog.dart'; @@ -18,15 +14,6 @@ class LanguageController { } //show diloag when user does not have languages selected showDialogOnEmptyLanguage(BuildContext dialogContext, Function callback) { - if (_pangeaController.userController.userModel?.profile == null) { - debugger(when: kDebugMode); - Sentry.addBreadcrumb( - Breadcrumb( - message: 'calling showDialogOnEmptyLanguagae with empty user', - ), - ); - return; - } if (!languagesSet) { pLanguageDialog(dialogContext, callback); } @@ -42,13 +29,13 @@ class LanguageController { String? get _userL1Code { final source = - _pangeaController.userController.userModel?.profile?.sourceLanguage; + _pangeaController.userController.profile.userSettings.sourceLanguage; return source == null || source.isEmpty ? null : source; } String? get _userL2Code { final target = - _pangeaController.userController.userModel?.profile?.targetLanguage; + _pangeaController.userController.profile.userSettings.targetLanguage; return target == null || target.isEmpty ? null : target; } diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 058bad359..8fa8989b1 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -204,24 +204,24 @@ class MyAnalyticsController { Completer? _updateCompleter; Future updateAnalytics() async { - if (!(_updateCompleter?.isCompleted ?? true)) { - await _updateCompleter!.future; - return; - } - _updateCompleter = Completer(); - try { - await _updateAnalytics(); - clearMessagesSinceUpdate(); - } catch (err, s) { - ErrorHandler.logError( - e: err, - m: "Failed to update analytics", - s: s, - ); - } finally { - _updateCompleter?.complete(); - _updateCompleter = null; - } + // if (!(_updateCompleter?.isCompleted ?? true)) { + // await _updateCompleter!.future; + // return; + // } + // _updateCompleter = Completer(); + // try { + // await _updateAnalytics(); + // clearMessagesSinceUpdate(); + // } catch (err, s) { + // ErrorHandler.logError( + // e: err, + // m: "Failed to update analytics", + // s: s, + // ); + // } finally { + // _updateCompleter?.complete(); + // _updateCompleter = null; + // } } String? get userL2 => _pangeaController.languageController.activeL2Code(); diff --git a/lib/pangea/controllers/permissions_controller.dart b/lib/pangea/controllers/permissions_controller.dart index 463a8c9a3..ed84e13c3 100644 --- a/lib/pangea/controllers/permissions_controller.dart +++ b/lib/pangea/controllers/permissions_controller.dart @@ -4,7 +4,6 @@ import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; -import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/p_extension.dart'; import 'package:matrix/matrix.dart'; @@ -31,12 +30,11 @@ class PermissionsController extends BaseController { } /// Returns false if user is null - bool isUser18() => - _pangeaController.userController.matrixProfile.dateOfBirth - ?.isAtLeastYearsOld( - AgeLimits.toAccessFeatures, - ) ?? - false; + bool isUser18() { + final DateTime? dob = + _pangeaController.userController.profile.userSettings.dateOfBirth; + return dob?.isAtLeastYearsOld(AgeLimits.toAccessFeatures) ?? false; + } /// A user can private chat if /// 1) they are 18 and outside a class context or @@ -97,20 +95,22 @@ class PermissionsController extends BaseController { return classPermission == 0; } - bool userToolSetting(MatrixProfileEnum setting) { - switch (setting.asToolSetting) { + bool userToolSetting(ToolSetting setting) { + switch (setting) { case ToolSetting.interactiveTranslator: return _pangeaController - .userController.matrixProfile.interactiveTranslator; + .userController.profile.toolSettings.interactiveTranslator; case ToolSetting.interactiveGrammar: return _pangeaController - .userController.matrixProfile.interactiveGrammar; + .userController.profile.toolSettings.interactiveGrammar; case ToolSetting.immersionMode: - return _pangeaController.userController.matrixProfile.immersionMode; + return _pangeaController + .userController.profile.toolSettings.immersionMode; case ToolSetting.definitions: - return _pangeaController.userController.matrixProfile.definitions; + return _pangeaController + .userController.profile.toolSettings.definitions; case ToolSetting.autoIGC: - return _pangeaController.userController.matrixProfile.autoIGC; + return _pangeaController.userController.profile.toolSettings.autoIGC; default: return false; } @@ -118,13 +118,13 @@ class PermissionsController extends BaseController { bool isToolEnabled(ToolSetting setting, Room? room) { if (room?.isSpaceAdmin ?? false) { - return userToolSetting(setting.asMatrixProfileField); + return userToolSetting(setting); } final int? classPermission = room != null ? classLanguageToolPermission(room, setting) : 1; if (classPermission == 0) return false; if (classPermission == 2) return true; - return userToolSetting(setting.asMatrixProfileField); + return userToolSetting(setting); } bool isWritingAssistanceEnabled(Room? room) { diff --git a/lib/pangea/controllers/subscription_controller.dart b/lib/pangea/controllers/subscription_controller.dart index e825a7126..d5a04a82d 100644 --- a/lib/pangea/controllers/subscription_controller.dart +++ b/lib/pangea/controllers/subscription_controller.dart @@ -8,7 +8,6 @@ import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/models/base_subscription_info.dart'; import 'package:fluffychat/pangea/models/mobile_subscriptions.dart'; -import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/models/web_subscriptions.dart'; import 'package:fluffychat/pangea/network/requests.dart'; import 'package:fluffychat/pangea/network/urls.dart'; @@ -179,31 +178,35 @@ class SubscriptionController extends BaseController { } } - bool get _activatedNewUserTrial => - _pangeaController.userController.inTrialWindow && - _pangeaController.userController.matrixProfile.activatedFreeTrial; + bool get _activatedNewUserTrial { + final bool activated = _pangeaController + .userController.profile.userSettings.activatedFreeTrial; + return _pangeaController.userController.inTrialWindow && activated; + } void activateNewUserTrial() { - _pangeaController.userController.matrixProfile.saveProfileData({ - MatrixProfileEnum.activatedFreeTrial.title: true, - }).then((_) { - setNewUserTrial(); - trialActivationStream.add(true); - }); + _pangeaController.userController.updateProfile( + (profile) { + profile.userSettings.activatedFreeTrial = true; + return profile; + }, + ); + setNewUserTrial(); + trialActivationStream.add(true); } void setNewUserTrial() { - if (_pangeaController.userController.userModel?.profile == null) { + final DateTime? createdAt = + _pangeaController.userController.profile.userSettings.createdAt; + if (createdAt == null) { ErrorHandler.logError( - m: "Null user profile in subscription settings", + m: "Null user profile createAt in subscription settings", s: StackTrace.current, ); return; } - final String profileCreatedAt = - _pangeaController.userController.userModel!.profile!.createdAt; - final DateTime creationTimestamp = DateTime.parse(profileCreatedAt); - final DateTime expirationDate = creationTimestamp.add( + + final DateTime expirationDate = createdAt.add( const Duration(days: 7), ); subscription?.setTrial(expirationDate); diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index 1fd13acfc..a8c6ced14 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/language_constants.dart'; -import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; @@ -27,61 +26,62 @@ class UserController extends BaseController { String? get _matrixAccessToken => _pangeaController.matrixState.client.accessToken; - /// An instance of matrix profile. Used to update and access info from the user's matrix profile. - /// No information needs to be passing in the constructor as the matrix - /// profile get all of it's internal data the accountData stored in the client. - MatrixProfile matrixProfile = MatrixProfile(); + Profile? _cachedProfile; - /// Returns the [PUserModel] object representing the current user. - /// - /// This method retrieves the user data from the local storage using the [PLocalKey.user] key. - /// If the data exists, it is converted to a [PUserModel] object using the [PUserModel.fromJson] method. - /// If the data is null, indicating that the user is not logged in (or that - /// profile fetching has not yet completed, or had an error), null is returned. - PUserModel? get userModel { - final data = _pangeaController.pStoreService.read(PLocalKey.user); - return data != null ? PUserModel.fromJson(data) : null; + /// Listen for updates to account data in syncs and update the cached profile + void addProfileListener() { + _pangeaController.matrixState.client.onSync.stream + .where((sync) => sync.accountData != null) + .listen((sync) { + final Profile? fromAccountData = Profile.fromAccountData(); + if (fromAccountData != null) { + _cachedProfile = fromAccountData; + } + }); } - /// Creates a user pangea chat profile, saves the user's profile information - /// locally, and set the user's DOB in their matrix profile. - /// - /// The [dob] parameter is required and represents the date of birth of the user. - /// This method creates a new [PUserModel] using the [PUserRepo.repoCreatePangeaUser] method, - /// and saves the user model in local storage. - /// It also updates the user's matrix profile using the [updateMatrixProfile] method. - Future createProfile({required String dob}) async { - if (userId == null || _matrixAccessToken == null) { - ErrorHandler.logError( - e: "calling createProfile with userId == null or matrixAccessToken == null", - ); + /// The user's profile. Will be empty if the client's accountData hasn't + /// been loaded yet (if the first sync hasn't gone through yet) + /// or if the user hasn't yer set their date of birth. + Profile get profile { + /// if the profile is cached, return it + if (_cachedProfile != null) return _cachedProfile!; + + /// if account data is empty, return an empty profile + if (_pangeaController.matrixState.client.accountData.isEmpty) { + return Profile.emptyProfile; } - final PUserModel newUserModel = await PUserRepo.repoCreatePangeaUser( - userID: userId!, - fullName: fullname, - dob: dob, - matrixAccessToken: _matrixAccessToken!, - ); - newUserModel.save(_pangeaController); - await matrixProfile.saveProfileData( - {MatrixProfileEnum.dateOfBirth.title: dob}, - waitForDataInSync: true, + + /// try to get the account data in the up-to-date format + final Profile? fromAccountData = Profile.fromAccountData(); + if (fromAccountData != null) { + _cachedProfile = fromAccountData; + return fromAccountData; + } + + _cachedProfile = Profile.migrateFromAccountData(); + _cachedProfile?.saveProfileData(); + return _cachedProfile ?? Profile.emptyProfile; + } + + void updateProfile(Profile Function(Profile) update) { + final Profile updatedProfile = update(profile); + updatedProfile.saveProfileData(); + } + + Future createProfile({required DateTime dob}) async { + final userSettings = UserSettings( + dateOfBirth: dob, + createdAt: DateTime.now(), ); + final newProfile = Profile(userSettings: userSettings); + await newProfile.saveProfileData(waitForDataInSync: true); } /// A completer for the profile model of a user. - Completer? _profileCompleter; + Completer? _profileCompleter; - /// Fetches the user model. - /// - /// This method retrieves the user model asynchronously. If the profile completer is already completed, - /// it returns the future value of the completer. If the user model is currently being fetched, - /// it waits for the completion of the completer and returns the future value. Otherwise, it sets - /// the fetching flag, fetches the user model, completes the profile completer with the fetched user model, - /// and returns the future value of the completer. - /// - /// Returns the future value of the user model completer. - Future fetchUserModel() async { + Future initialize() async { if (_profileCompleter?.isCompleted ?? false) { return _profileCompleter!.future; } @@ -91,213 +91,79 @@ class UserController extends BaseController { return _profileCompleter!.future; } - _profileCompleter = Completer(); - PUserModel? fetchedUserModel; + _profileCompleter = Completer(); try { - fetchedUserModel = await _fetchUserModel(); + await _initialize(); + addProfileListener(); } catch (err, s) { ErrorHandler.logError(e: err, s: s); } finally { - _profileCompleter!.complete(fetchedUserModel); + _profileCompleter!.complete(); } return _profileCompleter!.future; } - /// Fetches the user model asynchronously. - /// - /// This method fetches the user model by calling the [fetchPangeaUserInfo] method - /// from the [PUserRepo] class. It requires the [_matrixAccessToken] and [userId] - /// to be non-null. If either of them is null, an error is logged. - /// - /// The fetched [newUserModel] is then saved locally. - /// The [migrateMatrixProfile] method is called, to migrate any information that is - /// already saved in the user's pangea profile but is not yet saved in the - /// user's matrix profile. Finally, the [newUserModel] is returned. - Future _fetchUserModel() async { - if (_matrixAccessToken == null || userId == null) { - ErrorHandler.logError( - e: "calling fetchUserModel with userId == null or matrixAccessToken == null", - ); - return null; + Future _initialize() async { + await waitForAccountData(); + if (profile.userSettings.dateOfBirth != null) { + return; } - final PUserModel? newUserModel = await PUserRepo.fetchPangeaUserInfo( + final PangeaProfileResponse? resp = await PUserRepo.fetchPangeaUserInfo( userID: userId!, matrixAccessToken: _matrixAccessToken!, ); - newUserModel?.save(_pangeaController); - await migrateMatrixProfile(); - return newUserModel; + if (resp?.profile == null) { + return; + } + final userSetting = UserSettings.fromJson(resp!.profile.toJson()); + final newProfile = Profile(userSettings: userSetting); + await newProfile.saveProfileData(waitForDataInSync: true); } /// Reinitializes the user's profile /// This method should be called whenever the user's login status changes Future reinitialize() async { _profileCompleter = null; - await fetchUserModel(); + _cachedProfile = null; + await initialize(); } - /// Migrates the user's profile from Pangea to Matrix. - /// - /// This method retrieves the user's profile / local settings information from Pangea and checks for corresponding information stored in Matrix. - /// If any of the profile fields in Pangea have information, but the corresponding fields in Matrix are null, the values are updated in Matrix. - /// The profile fields that are checked for migration include date of birth, creation date, target language, source language, country, and public profile. - /// Additionally, several profile settings related to auto play, trial activation, interactive features, and instructional messages are also checked for migration. - /// - /// This method calls the [updateMatrixProfile] method to update the user's profile in Matrix with the migrated values. - /// - /// Note: This method assumes that the [userModel] and [_pangeaController] instances are properly initialized before calling this method. - Future migrateMatrixProfile() async { - // This function relies on the client's account data being loaded. - // The account data is loaded during - // the first sync, so wait for that to complete. + /// Account data comes through in the first sync, so wait for that + Future waitForAccountData() async { final client = _pangeaController.matrixState.client; if (client.prevBatch == null) { await client.onSync.stream.first; } - - final Map profileUpdates = {}; - final Profile? pangeaProfile = userModel?.profile; - - for (final field in MatrixProfile.pangeaProfileFields) { - final dynamic matrixValue = matrixProfile.getProfileData(field); - dynamic pangeaValue; - switch (field) { - case MatrixProfileEnum.dateOfBirth: - pangeaValue = pangeaProfile?.dateOfBirth; - break; - case MatrixProfileEnum.createdAt: - pangeaValue = pangeaProfile?.createdAt; - break; - case MatrixProfileEnum.targetLanguage: - pangeaValue = pangeaProfile?.targetLanguage; - break; - case MatrixProfileEnum.sourceLanguage: - pangeaValue = pangeaProfile?.sourceLanguage; - break; - case MatrixProfileEnum.country: - pangeaValue = pangeaProfile?.country; - break; - case MatrixProfileEnum.publicProfile: - pangeaValue = pangeaProfile?.publicProfile; - break; - default: - break; - } - if (pangeaValue != null && matrixValue == null) { - profileUpdates[field.title] = pangeaValue; - } - } - - for (final value in MatrixProfileEnum.values) { - if (profileUpdates.containsKey(value.title)) continue; - final dynamic localValue = - _pangeaController.pStoreService.read(value.title); - final dynamic matrixValue = matrixProfile.getProfileData(value); - final dynamic unmigratedValue = - localValue != null && matrixValue == null ? localValue : null; - if (unmigratedValue != null) { - profileUpdates[value.title] = unmigratedValue; - } - } - - await matrixProfile.saveProfileData( - profileUpdates, - waitForDataInSync: true, - ); - } - - /// Updates the user's profile with the provided information. - /// - /// The [dateOfBirth] parameter is the new date of birth for the user. - /// The [targetLanguage] parameter is the new target language for the user. - /// The [sourceLanguage] parameter is the new source language for the user. - /// The [country] parameter is the new country for the user. - /// The [interests] parameter is a list of new interests for the user. - /// The [speaks] parameter is a list of new languages the user speaks. - /// The [publicProfile] parameter indicates whether the user's profile should be public or not. - Future updateUserProfile({ - String? dateOfBirth, - String? targetLanguage, - String? sourceLanguage, - String? country, - List? interests, - List? speaks, - bool? publicProfile, - }) async { - if (userModel == null) { - ErrorHandler.logError( - e: "calling updateUserProfile with userModel == null", - ); - return; - } - - final profileJson = userModel!.profile!.toJson(); - - if (dateOfBirth != null) { - profileJson[ModelKey.userDateOfBirth] = dateOfBirth; - } - if (targetLanguage != null) { - profileJson[ModelKey.userTargetLanguage] = targetLanguage; - } - if (sourceLanguage != null) { - profileJson[ModelKey.userSourceLanguage] = sourceLanguage; - } - if (interests != null) { - profileJson[ModelKey.userInterests] = interests.toString(); - } - if (speaks != null) { - profileJson[ModelKey.userSpeaks] = speaks.toString(); - } - if (country != null) { - profileJson[ModelKey.userCountry] = country; - } - if (publicProfile != null) { - profileJson[ModelKey.publicProfile] = publicProfile; - } - - final Profile updatedUserProfile = await PUserRepo.updateUserProfile( - Profile.fromJson(profileJson), - await accessToken, - ); - - PUserModel( - access: await accessToken, - refresh: userModel!.refresh, - profile: updatedUserProfile, - ).save(_pangeaController); - - matrixProfile.saveProfileData({ - MatrixProfileEnum.dateOfBirth.title: dateOfBirth, - MatrixProfileEnum.targetLanguage.title: targetLanguage, - MatrixProfileEnum.sourceLanguage.title: sourceLanguage, - MatrixProfileEnum.country.title: country, - MatrixProfileEnum.publicProfile.title: publicProfile, - }); } /// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed. - /// It checks if the `userModel` has a non-null `access` token and if the token is expired using the `Jwt.isExpired()` method. - /// If the `userModel` is null or the `access` token is null, it returns true indicating that a new JWT is needed. - bool get needNewJWT => - userModel?.access != null ? Jwt.isExpired(userModel!.access) : true; + bool needNewJWT(String token) => Jwt.isExpired(token); - /// Retrieves the access token for the user. - /// - /// If the locally stored user model is null or the access token has - /// expired, it fetches the user model. - /// If the user model is still null after fetching, an error thrown. - /// - /// Returns the access token as a string, or null if the user model is null. + /// Retrieves the access token for the user. Looks for it locally, + /// and if it's not found or expired, fetches it from the server. Future get accessToken async { - final PUserModel? useThisOne = - needNewJWT ? await fetchUserModel() : userModel; + final localAccessToken = + _pangeaController.pStoreService.read(PLocalKey.access); - if (useThisOne == null) { - throw ("Trying to get accessToken with null userModel"); + if (localAccessToken == null || needNewJWT(localAccessToken)) { + final PangeaProfileResponse? userModel = + await PUserRepo.fetchPangeaUserInfo( + userID: userId!, + matrixAccessToken: _matrixAccessToken!, + ); + if (userModel?.access == null) { + throw ("Trying to get accessToken with null userModel"); + } + _pangeaController.pStoreService.save( + PLocalKey.access, + userModel!.access, + ); + return userModel.access; } - return useThisOne.access; + + return localAccessToken; } /// Returns the full name of the user. @@ -314,19 +180,6 @@ class UserController extends BaseController { return userId!.substring(0, userId!.indexOf(":")).replaceAll("@", ""); } - /// Checks if the user data is available. - /// Returns a [Future] that completes with a [bool] value - /// indicating whether the user data is available or not. - Future get isPUserDataAvailable async { - try { - final PUserModel? toCheck = userModel ?? (await fetchUserModel()); - return toCheck != null; - } catch (err, s) { - ErrorHandler.logError(e: err, s: s); - return false; - } - } - /// Checks if user data is available and the date of birth is set. /// Returns a [Future] that completes with a [bool] value indicating /// whether the user data is available and the date of birth is set. @@ -334,8 +187,8 @@ class UserController extends BaseController { try { // the function fetchUserModel() uses a completer, so it shouldn't // re-call the endpoint if it has already been called - await fetchUserModel(); - return matrixProfile.dateOfBirth != null; + await initialize(); + return profile.userSettings.dateOfBirth != null; } catch (err, s) { ErrorHandler.logError(e: err, s: s); return false; @@ -344,11 +197,11 @@ class UserController extends BaseController { /// Returns a boolean value indicating whether the user is currently in the trial window. bool get inTrialWindow { - final String? createdAt = userModel?.profile?.createdAt; + final DateTime? createdAt = profile.userSettings.createdAt; if (createdAt == null) { return false; } - return DateTime.parse(createdAt).isAfter( + return createdAt.isAfter( DateTime.now().subtract(const Duration(days: 7)), ); } @@ -363,12 +216,8 @@ class UserController extends BaseController { /// If an error occurs during the process, it logs the error and returns `false`. Future get areUserLanguagesSet async { try { - final PUserModel? toCheck = userModel ?? (await fetchUserModel()); - if (toCheck?.profile == null) { - return false; - } - final String? srcLang = toCheck!.profile!.sourceLanguage; - final String? tgtLang = toCheck.profile!.targetLanguage; + final String? srcLang = profile.userSettings.sourceLanguage; + final String? tgtLang = profile.userSettings.targetLanguage; return srcLang != null && tgtLang != null && srcLang.isNotEmpty && @@ -382,7 +231,9 @@ class UserController extends BaseController { } /// Returns a boolean value indicating whether the user's profile is public. - bool get isPublic => userModel?.profile?.publicProfile ?? false; + bool get isPublic { + return profile.userSettings.publicProfile; + } /// Retrieves the user's email address. /// diff --git a/lib/pangea/models/space_model.dart b/lib/pangea/models/space_model.dart index 81306372c..232899bb8 100644 --- a/lib/pangea/models/space_model.dart +++ b/lib/pangea/models/space_model.dart @@ -1,6 +1,5 @@ import 'dart:developer'; -import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -270,19 +269,4 @@ extension SettingCopy on ToolSetting { return L10n.of(context)!.autoIGCToolDescription; } } - - MatrixProfileEnum get asMatrixProfileField { - switch (this) { - case ToolSetting.interactiveTranslator: - return MatrixProfileEnum.interactiveTranslator; - case ToolSetting.interactiveGrammar: - return MatrixProfileEnum.interactiveGrammar; - case ToolSetting.immersionMode: - return MatrixProfileEnum.immersionMode; - case ToolSetting.definitions: - return MatrixProfileEnum.definitions; - case ToolSetting.autoIGC: - return MatrixProfileEnum.autoIGC; - } - } } diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index d87e125fa..51aa746d9 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -1,199 +1,320 @@ -import 'package:country_picker/country_picker.dart'; -import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; -import '../constants/language_constants.dart'; import 'language_model.dart'; -class PUserModel { - String access; - String refresh; - Profile? profile; +class UserSettings { + DateTime? dateOfBirth; + DateTime? createdAt; + bool autoPlayMessages; + bool itAutoPlay; + bool activatedFreeTrial; + bool publicProfile; + String? targetLanguage; + String? sourceLanguage; + String? country; - PUserModel({required this.access, required this.refresh, this.profile}); + UserSettings({ + this.dateOfBirth, + this.createdAt, + this.autoPlayMessages = false, + this.itAutoPlay = false, + this.activatedFreeTrial = false, + this.publicProfile = false, + this.targetLanguage, + this.sourceLanguage, + this.country, + }); - factory PUserModel.fromJson(Map json) => PUserModel( - access: json[ModelKey.userAccess], - refresh: json[ModelKey.userRefresh], - profile: json[ModelKey.userProfile] != null - ? Profile.fromJson(json[ModelKey.userProfile]) + factory UserSettings.fromJson(Map json) => UserSettings( + dateOfBirth: DateTime.parse(json[ModelKey.userDateOfBirth]), + createdAt: json[ModelKey.userCreatedAt] != null + ? DateTime.parse(json[ModelKey.userCreatedAt]) : null, + autoPlayMessages: json[ModelKey.autoPlayMessages] ?? false, + itAutoPlay: json[ModelKey.itAutoPlay] ?? false, + activatedFreeTrial: json[ModelKey.activatedTrialKey] ?? false, + publicProfile: json[ModelKey.publicProfile] ?? false, + targetLanguage: json[ModelKey.l2LanguageKey], + sourceLanguage: json[ModelKey.l1LanguageKey], + country: json[ModelKey.userCountry], ); Map toJson() { final Map data = {}; - data[ModelKey.userAccess] = access; - data[ModelKey.userRefresh] = refresh; - if (profile != null) { - data[ModelKey.userProfile] = profile!.toJson(); - } + data[ModelKey.userDateOfBirth] = dateOfBirth?.toIso8601String(); + data[ModelKey.userCreatedAt] = createdAt?.toIso8601String(); + data[ModelKey.autoPlayMessages] = autoPlayMessages; + data[ModelKey.itAutoPlay] = itAutoPlay; + data[ModelKey.activatedTrialKey] = activatedFreeTrial; + data[ModelKey.publicProfile] = publicProfile; + data[ModelKey.l2LanguageKey] = targetLanguage; + data[ModelKey.l1LanguageKey] = sourceLanguage; + data[ModelKey.userCountry] = country; return data; } - Future save(PangeaController pangeaController) async { - await pangeaController.pStoreService.save( - PLocalKey.user, - toJson(), + static UserSettings? migrateFromAccountData() { + final accountData = + MatrixState.pangeaController.matrixState.client.accountData; + + if (!accountData.containsKey(ModelKey.userDateOfBirth)) return null; + final dobContent = accountData[ModelKey.userDateOfBirth]! + .content[ModelKey.userDateOfBirth]; + + String? dobString; + if (dobContent != null) { + dobString = dobContent as String; + } + + DateTime dob; + try { + dob = DateTime.parse(dobString!); + } catch (_) { + return null; + } + + final createdAtContent = + accountData[ModelKey.userCreatedAt]?.content[ModelKey.userCreatedAt]; + DateTime? createdAt; + if (createdAtContent != null) { + try { + createdAt = DateTime.parse(createdAtContent as String); + } catch (_) { + createdAt = null; + } + } + + return UserSettings( + dateOfBirth: dob, + createdAt: createdAt, + autoPlayMessages: (accountData[ModelKey.autoPlayMessages] + ?.content[ModelKey.autoPlayMessages] as bool?) ?? + false, + itAutoPlay: (accountData[ModelKey.itAutoPlay] + ?.content[ModelKey.itAutoPlay] as bool?) ?? + false, + activatedFreeTrial: (accountData[ModelKey.activatedTrialKey] + ?.content[ModelKey.activatedTrialKey] as bool?) ?? + false, + publicProfile: (accountData[ModelKey.publicProfile] + ?.content[ModelKey.publicProfile] as bool?) ?? + false, + targetLanguage: accountData[ModelKey.l2LanguageKey] + ?.content[ModelKey.l2LanguageKey] as String?, + sourceLanguage: accountData[ModelKey.l1LanguageKey] + ?.content[ModelKey.l1LanguageKey] as String?, + country: accountData[ModelKey.userCountry]?.content[ModelKey.userCountry] + as String?, ); } } -/// A list of all the fields in the user profile saved to matrix -enum MatrixProfileEnum { - dateOfBirth, - autoPlayMessages, - itAutoPlay, - activatedFreeTrial, - interactiveTranslator, - interactiveGrammar, - immersionMode, - definitions, - showedItInstructions, - showedClickMessage, - showedBlurMeansTranslate, - showedTooltipInstructions, - createdAt, - targetLanguage, - sourceLanguage, - country, - publicProfile, - autoIGC, -} +class UserToolSettings { + bool interactiveTranslator; + bool interactiveGrammar; + bool immersionMode; + bool definitions; + bool autoIGC; -extension MatrixProfileEnumExtension on MatrixProfileEnum { - String get title { - switch (this) { - case MatrixProfileEnum.dateOfBirth: - return ModelKey.userDateOfBirth; - case MatrixProfileEnum.autoPlayMessages: - return ModelKey.autoPlayMessages; - case MatrixProfileEnum.itAutoPlay: - return ModelKey.itAutoPlay; - case MatrixProfileEnum.activatedFreeTrial: - return ModelKey.activatedTrialKey; - case MatrixProfileEnum.interactiveTranslator: - return ToolSetting.interactiveTranslator.toString(); - case MatrixProfileEnum.interactiveGrammar: - return ToolSetting.interactiveGrammar.toString(); - case MatrixProfileEnum.immersionMode: - return ToolSetting.immersionMode.toString(); - case MatrixProfileEnum.definitions: - return ToolSetting.definitions.toString(); - case MatrixProfileEnum.autoIGC: - return ToolSetting.autoIGC.toString(); - case MatrixProfileEnum.showedItInstructions: - return InstructionsEnum.itInstructions.toString(); - case MatrixProfileEnum.showedClickMessage: - return InstructionsEnum.clickMessage.toString(); - case MatrixProfileEnum.showedBlurMeansTranslate: - return InstructionsEnum.blurMeansTranslate.toString(); - case MatrixProfileEnum.showedTooltipInstructions: - return InstructionsEnum.tooltipInstructions.toString(); - case MatrixProfileEnum.createdAt: - return ModelKey.userCreatedAt; - case MatrixProfileEnum.targetLanguage: - return ModelKey.l2LanguageKey; - case MatrixProfileEnum.sourceLanguage: - return ModelKey.l1LanguageKey; - case MatrixProfileEnum.country: - return ModelKey.userCountry; - case MatrixProfileEnum.publicProfile: - return ModelKey.publicProfile; - } + UserToolSettings({ + this.interactiveTranslator = true, + this.interactiveGrammar = true, + this.immersionMode = false, + this.definitions = true, + this.autoIGC = false, + }); + + factory UserToolSettings.fromJson(Map json) => + UserToolSettings( + interactiveTranslator: + json[ToolSetting.interactiveTranslator.toString()] ?? true, + interactiveGrammar: + json[ToolSetting.interactiveGrammar.toString()] ?? true, + immersionMode: json[ToolSetting.immersionMode.toString()] ?? false, + definitions: json[ToolSetting.definitions.toString()] ?? true, + autoIGC: json[ToolSetting.autoIGC.toString()] ?? false, + ); + + Map toJson() { + final Map data = {}; + data[ToolSetting.interactiveTranslator.toString()] = interactiveTranslator; + data[ToolSetting.interactiveGrammar.toString()] = interactiveGrammar; + data[ToolSetting.immersionMode.toString()] = immersionMode; + data[ToolSetting.definitions.toString()] = definitions; + data[ToolSetting.autoIGC.toString()] = autoIGC; + return data; } - ToolSetting? get asToolSetting { - switch (this) { - case MatrixProfileEnum.interactiveTranslator: - return ToolSetting.interactiveTranslator; - case MatrixProfileEnum.interactiveGrammar: - return ToolSetting.interactiveGrammar; - case MatrixProfileEnum.immersionMode: - return ToolSetting.immersionMode; - case MatrixProfileEnum.definitions: - return ToolSetting.definitions; - case MatrixProfileEnum.autoIGC: - return ToolSetting.autoIGC; - default: - return null; - } + factory UserToolSettings.migrateFromAccountData() { + final accountData = + MatrixState.pangeaController.matrixState.client.accountData; + return UserToolSettings( + interactiveTranslator: + (accountData[ToolSetting.interactiveTranslator.toString()] + ?.content[ToolSetting.interactiveTranslator.toString()] + as bool?) ?? + true, + interactiveGrammar: + (accountData[ToolSetting.interactiveGrammar.toString()] + ?.content[ToolSetting.interactiveGrammar.toString()] + as bool?) ?? + true, + immersionMode: (accountData[ToolSetting.immersionMode.toString()] + ?.content[ToolSetting.immersionMode.toString()] as bool?) ?? + false, + definitions: (accountData[ToolSetting.definitions.toString()] + ?.content[ToolSetting.definitions.toString()] as bool?) ?? + true, + autoIGC: (accountData[ToolSetting.autoIGC.toString()] + ?.content[ToolSetting.autoIGC.toString()] as bool?) ?? + false, + ); + } +} + +class UserInstructions { + bool showedItInstructions; + bool showedClickMessage; + bool showedBlurMeansTranslate; + bool showedTooltipInstructions; + + UserInstructions({ + this.showedItInstructions = false, + this.showedClickMessage = false, + this.showedBlurMeansTranslate = false, + this.showedTooltipInstructions = false, + }); + + factory UserInstructions.fromJson(Map json) => + UserInstructions( + showedItInstructions: + json[InstructionsEnum.itInstructions.toString()] ?? false, + showedClickMessage: + json[InstructionsEnum.clickMessage.toString()] ?? false, + showedBlurMeansTranslate: + json[InstructionsEnum.blurMeansTranslate.toString()] ?? false, + showedTooltipInstructions: + json[InstructionsEnum.tooltipInstructions.toString()] ?? false, + ); + + Map toJson() { + final Map data = {}; + data[InstructionsEnum.itInstructions.toString()] = showedItInstructions; + data[InstructionsEnum.clickMessage.toString()] = showedClickMessage; + data[InstructionsEnum.blurMeansTranslate.toString()] = + showedBlurMeansTranslate; + data[InstructionsEnum.tooltipInstructions.toString()] = + showedTooltipInstructions; + return data; + } + + factory UserInstructions.migrateFromAccountData() { + final accountData = + MatrixState.pangeaController.matrixState.client.accountData; + return UserInstructions( + showedItInstructions: + (accountData[InstructionsEnum.itInstructions.toString()] + ?.content[InstructionsEnum.itInstructions.toString()] + as bool?) ?? + false, + showedClickMessage: (accountData[InstructionsEnum.clickMessage.toString()] + ?.content[InstructionsEnum.clickMessage.toString()] as bool?) ?? + false, + showedBlurMeansTranslate: + (accountData[InstructionsEnum.blurMeansTranslate.toString()] + ?.content[InstructionsEnum.blurMeansTranslate.toString()] + as bool?) ?? + false, + showedTooltipInstructions: + (accountData[InstructionsEnum.tooltipInstructions.toString()] + ?.content[InstructionsEnum.tooltipInstructions.toString()] + as bool?) ?? + false, + ); } } /// A wrapper around the matrix account data for the user profile. /// Enables easy access to the profile data and saving new data. -/// The matrix profile doesn't function exactly the same as a 'model', -/// since all the data here is already stored in the client as account -/// data, and duplicating that data could lead to some inconsistenies. -/// So this class is more of a helper class to make it easier to -/// access and save the data. -class MatrixProfile { - /// Convenience function get get user's account data from the client - Map get accountData => - MatrixState.pangeaController.matrixState.client.accountData; +class Profile { + late UserSettings userSettings; + late UserToolSettings toolSettings; + late UserInstructions instructionSettings; - /// Returns the profile of the user. - /// - /// The profile is retrieved from the user's account data - /// using the key `ModelKey.userProfile`. It returns a `Map` object - /// representing the user's profile information. - Map? get profile => - accountData[ModelKey.userProfile]?.content; - - /// Retrieves the profile data for the given [key]. - /// - /// This method first tries to get the data from the new profile format. If the data is found, - /// it is returned. If not, it checks if the data is stored in the old format. If it is, the data - /// is saved to the new format and returned. - dynamic getProfileData(MatrixProfileEnum key) { - // try to get the data from the new profile format - if (profile?[key.title] != null) { - return profile?[key.title]; - } - - // check if the data is stored in the old format - // and if so, save it to the new format - final prevFormatData = accountData[key.title]?.content[key.title]; - if (prevFormatData != null) { - saveProfileData({key.title: prevFormatData}); - return prevFormatData; - } + Profile({ + required this.userSettings, + UserToolSettings? toolSettings, + UserInstructions? instructionSettings, + }) { + this.toolSettings = toolSettings ?? UserToolSettings(); + this.instructionSettings = instructionSettings ?? UserInstructions(); } - /// Saves the profile data by updating the current user's profile with the provided updates. - /// - /// The [updates] parameter is a map containing the key-value pairs of the profile fields to be updated. - /// Only non-null values in the [updates] map will be applied to the current profile. - /// - /// If the updated profile is equal to the current profile, no changes will be made. - /// - /// If [waitForDataInSync] is true, the function will wait for the updated data in a sync update - /// If this is set to false, after this function completes there may be a gap where the - /// data has been sent but is not in the client's account data, as the sync update has not yet been received. - Future saveProfileData( - Map updates, { + static Profile? fromAccountData() { + final profileData = MatrixState.pangeaController.matrixState.client + .accountData[ModelKey.userProfile]?.content; + if (profileData == null) return null; + + final userSettingsContent = profileData[ModelKey.userSettings]; + if (userSettingsContent == null) return null; + + final toolSettingsContent = profileData[ModelKey.toolSettings]; + final instructionSettingsContent = + profileData[ModelKey.instructionsSettings]; + + return Profile( + userSettings: + UserSettings.fromJson(userSettingsContent as Map), + toolSettings: toolSettingsContent != null + ? UserToolSettings.fromJson( + toolSettingsContent as Map, + ) + : UserToolSettings(), + instructionSettings: instructionSettingsContent != null + ? UserInstructions.fromJson( + instructionSettingsContent as Map, + ) + : UserInstructions(), + ); + } + + Map toJson() { + final Map json = { + ModelKey.userSettings: userSettings.toJson(), + ModelKey.toolSettings: toolSettings.toJson(), + ModelKey.instructionsSettings: instructionSettings.toJson(), + }; + return json; + } + + static Profile? migrateFromAccountData() { + final userSettings = UserSettings.migrateFromAccountData(); + if (userSettings == null) return null; + + final toolSettings = UserToolSettings.migrateFromAccountData(); + final instructionSettings = UserInstructions.migrateFromAccountData(); + return Profile( + userSettings: userSettings, + toolSettings: toolSettings, + instructionSettings: instructionSettings, + ); + } + + Future saveProfileData({ waitForDataInSync = false, }) async { - final currentProfile = toJson(); - for (final entry in updates.entries) { - if (entry.value == null) continue; - currentProfile[entry.key] = entry.value; - } - if (mapEquals(toJson(), currentProfile)) return; - final PangeaController pangeaController = MatrixState.pangeaController; final Client client = pangeaController.matrixState.client; - - final List profileKeys = - MatrixProfileEnum.values.map((e) => e.title).toList(); + final List profileKeys = [ + ModelKey.userSettings, + ModelKey.toolSettings, + ModelKey.instructionsSettings, + ]; Future? waitForUpdate; if (waitForDataInSync) { @@ -208,68 +329,26 @@ class MatrixProfile { await client.setAccountData( client.userID!, ModelKey.userProfile, - currentProfile, + toJson(), ); - if (waitForDataInSync) await waitForUpdate; - } - /// Converts the Matrix Profile to a JSON representation. - Map toJson() { - final Map json = {}; - for (final value in MatrixProfileEnum.values) { - json[value.title] = profile?[value.title]; + if (waitForDataInSync) { + await waitForUpdate; } - return json; } - // below are some convenience methods for accessing the profile data - // getProfileData could be used directly, but these methods reduce the - // need for repeating the same code (like parsing DateTimes or - // assigning default values to null booleans) when accessing specific values. - - DateTime? get dateOfBirth { - final dob = getProfileData(MatrixProfileEnum.dateOfBirth); - return dob != null ? DateTime.parse(dob) : null; + static Profile get emptyProfile { + return Profile( + userSettings: UserSettings(), + toolSettings: UserToolSettings(), + instructionSettings: UserInstructions(), + ); } - - bool get autoPlayMessages => - getProfileData(MatrixProfileEnum.autoPlayMessages) ?? false; - bool get itAutoPlay => getProfileData(MatrixProfileEnum.itAutoPlay) ?? false; - bool get activatedFreeTrial => - getProfileData(MatrixProfileEnum.activatedFreeTrial) ?? false; - bool get interactiveTranslator => - getProfileData(MatrixProfileEnum.interactiveTranslator) ?? true; - bool get interactiveGrammar => - getProfileData(MatrixProfileEnum.interactiveGrammar) ?? true; - bool get immersionMode => - getProfileData(MatrixProfileEnum.immersionMode) ?? false; - bool get definitions => getProfileData(MatrixProfileEnum.definitions) ?? true; - bool get autoIGC => getProfileData(MatrixProfileEnum.autoIGC) ?? false; - - /// A list of all the fields in MatrixProfileEnum that correspond to tool settings - static List get toolSettings => [ - MatrixProfileEnum.interactiveTranslator, - MatrixProfileEnum.interactiveGrammar, - MatrixProfileEnum.immersionMode, - MatrixProfileEnum.definitions, - MatrixProfileEnum.autoIGC, - ]; - - /// A list of all the fields in MatrixProfileEnum that correspond to pangea profile values - static List pangeaProfileFields = [ - MatrixProfileEnum.dateOfBirth, - MatrixProfileEnum.createdAt, - MatrixProfileEnum.targetLanguage, - MatrixProfileEnum.sourceLanguage, - MatrixProfileEnum.country, - MatrixProfileEnum.publicProfile, - ]; } -class Profile { - // i'm considering removing this field because it's duplicating info in the - // matrix database - // String? fullName; +/// Model of data from pangea chat server. Not used anymore, in favor of matrix account data. +/// This class if used to read in data from the server to be migrated to matrix account data. +class PangeaProfile { final String createdAt; final String pangeaUserId; String? dateOfBirth; @@ -279,8 +358,7 @@ class Profile { String? country; bool publicProfile; - Profile({ - // this.fullName, + PangeaProfile({ required this.createdAt, required this.pangeaUserId, this.dateOfBirth, @@ -290,16 +368,15 @@ class Profile { this.publicProfile = false, }); - factory Profile.fromJson(Map json) { + factory PangeaProfile.fromJson(Map json) { final l2 = LanguageModel.codeFromNameOrCode( - json[ModelKey.l2LanguageKey] ?? LanguageKeys.unknownLanguage, + json[ModelKey.l2LanguageKey], ); final l1 = LanguageModel.codeFromNameOrCode( - json[ModelKey.l1LanguageKey] ?? LanguageKeys.unknownLanguage, + json[ModelKey.l1LanguageKey], ); - return Profile( - // fullName: json[ModelKey.userFullName], + return PangeaProfile( createdAt: json[ModelKey.userCreatedAt], pangeaUserId: json[ModelKey.userPangeaUserId], dateOfBirth: json[ModelKey.userDateOfBirth], @@ -312,7 +389,6 @@ class Profile { Map toJson() { final Map data = {}; - // data[ModelKey.userFullName] = fullName; data[ModelKey.userCreatedAt] = createdAt; data[ModelKey.userPangeaUserId] = pangeaUserId; data[ModelKey.userDateOfBirth] = dateOfBirth; @@ -322,514 +398,21 @@ class Profile { data[ModelKey.userCountry] = country; return data; } +} - /// used in find a partner page for display partner's country - String get flagEmoji { - final String? countryName = this.country?.split(' (')[0]; - final Country? country = CountryService().findByName(countryName); - return country?.flagEmoji ?? ""; - } +class PangeaProfileResponse { + final PangeaProfile profile; + final String access; - String? countryDisplayName(BuildContext context) { - final String? countryName = this.country?.split(' (')[0]; - final Country? country = CountryService().findByName(countryName); - if (country?.countryCode == null) return null; - switch (country!.countryCode) { - case 'WW': - return L10n.of(context)!.wwCountryDisplayName; - case 'AF': - return L10n.of(context)!.afCountryDisplayName; - case 'AX': - return L10n.of(context)!.axCountryDisplayName; - case 'AL': - return L10n.of(context)!.alCountryDisplayName; - case 'DZ': - return L10n.of(context)!.dzCountryDisplayName; - case 'AS': - return L10n.of(context)!.asCountryDisplayName; - case 'AD': - return L10n.of(context)!.adCountryDisplayName; - case 'AO': - return L10n.of(context)!.aoCountryDisplayName; - case 'AI': - return L10n.of(context)!.aiCountryDisplayName; - case 'AG': - return L10n.of(context)!.agCountryDisplayName; - case 'AR': - return L10n.of(context)!.arCountryDisplayName; - case 'AM': - return L10n.of(context)!.amCountryDisplayName; - case 'AW': - return L10n.of(context)!.awCountryDisplayName; - case 'AC': - return L10n.of(context)!.acCountryDisplayName; - case 'AU': - return L10n.of(context)!.auCountryDisplayName; - case 'AT': - return L10n.of(context)!.atCountryDisplayName; - case 'AZ': - return L10n.of(context)!.azCountryDisplayName; - case 'BS': - return L10n.of(context)!.bsCountryDisplayName; - case 'BH': - return L10n.of(context)!.bhCountryDisplayName; - case 'BD': - return L10n.of(context)!.bdCountryDisplayName; - case 'BB': - return L10n.of(context)!.bbCountryDisplayName; - case 'BY': - return L10n.of(context)!.byCountryDisplayName; - case 'BE': - return L10n.of(context)!.beCountryDisplayName; - case 'BZ': - return L10n.of(context)!.bzCountryDisplayName; - case 'BJ': - return L10n.of(context)!.bjCountryDisplayName; - case 'BM': - return L10n.of(context)!.bmCountryDisplayName; - case 'BT': - return L10n.of(context)!.btCountryDisplayName; - case 'BO': - return L10n.of(context)!.boCountryDisplayName; - case 'BA': - return L10n.of(context)!.baCountryDisplayName; - case 'BW': - return L10n.of(context)!.bwCountryDisplayName; - case 'BR': - return L10n.of(context)!.brCountryDisplayName; - case 'IO': - return L10n.of(context)!.ioCountryDisplayName; - case 'VG': - return L10n.of(context)!.vgCountryDisplayName; - case 'BN': - return L10n.of(context)!.bnCountryDisplayName; - case 'BG': - return L10n.of(context)!.bgCountryDisplayName; - case 'BF': - return L10n.of(context)!.bfCountryDisplayName; - case 'BI': - return L10n.of(context)!.biCountryDisplayName; - case 'KH': - return L10n.of(context)!.khCountryDisplayName; - case 'CM': - return L10n.of(context)!.cmCountryDisplayName; - case 'CA': - return L10n.of(context)!.caCountryDisplayName; - case 'CV': - return L10n.of(context)!.cvCountryDisplayName; - case 'BQ': - return L10n.of(context)!.bqCountryDisplayName; - case 'KY': - return L10n.of(context)!.kyCountryDisplayName; - case 'CF': - return L10n.of(context)!.cfCountryDisplayName; - case 'TD': - return L10n.of(context)!.tdCountryDisplayName; - case 'CL': - return L10n.of(context)!.clCountryDisplayName; - case 'CN': - return L10n.of(context)!.cnCountryDisplayName; - case 'CX': - return L10n.of(context)!.cxCountryDisplayName; - case 'CC': - return L10n.of(context)!.ccCountryDisplayName; - case 'CO': - return L10n.of(context)!.coCountryDisplayName; - case 'KM': - return L10n.of(context)!.kmCountryDisplayName; - case 'CD': - return L10n.of(context)!.cdCountryDisplayName; - case 'CG': - return L10n.of(context)!.cgCountryDisplayName; - case 'CK': - return L10n.of(context)!.ckCountryDisplayName; - case 'CR': - return L10n.of(context)!.crCountryDisplayName; - case 'CI': - return L10n.of(context)!.ciCountryDisplayName; - case 'HR': - return L10n.of(context)!.hrCountryDisplayName; - case 'CU': - return L10n.of(context)!.cuCountryDisplayName; - case 'CW': - return L10n.of(context)!.cwCountryDisplayName; - case 'CY': - return L10n.of(context)!.cyCountryDisplayName; - case 'CZ': - return L10n.of(context)!.czCountryDisplayName; - case 'DK': - return L10n.of(context)!.dkCountryDisplayName; - case 'DJ': - return L10n.of(context)!.djCountryDisplayName; - case 'DM': - return L10n.of(context)!.dmCountryDisplayName; - case 'DO': - return L10n.of(context)!.doCountryDisplayName; - case 'TL': - return L10n.of(context)!.tlCountryDisplayName; - case 'EC': - return L10n.of(context)!.ecCountryDisplayName; - case 'EG': - return L10n.of(context)!.egCountryDisplayName; - case 'SV': - return L10n.of(context)!.svCountryDisplayName; - case 'GQ': - return L10n.of(context)!.gqCountryDisplayName; - case 'ER': - return L10n.of(context)!.erCountryDisplayName; - case 'EE': - return L10n.of(context)!.eeCountryDisplayName; - case 'SZ': - return L10n.of(context)!.szCountryDisplayName; - case 'ET': - return L10n.of(context)!.etCountryDisplayName; - case 'FK': - return L10n.of(context)!.fkCountryDisplayName; - case 'FO': - return L10n.of(context)!.foCountryDisplayName; - case 'FJ': - return L10n.of(context)!.fjCountryDisplayName; - case 'FI': - return L10n.of(context)!.fiCountryDisplayName; - case 'FR': - return L10n.of(context)!.frCountryDisplayName; - case 'GF': - return L10n.of(context)!.gfCountryDisplayName; - case 'PF': - return L10n.of(context)!.pfCountryDisplayName; - case 'GA': - return L10n.of(context)!.gaCountryDisplayName; - case 'GM': - return L10n.of(context)!.gmCountryDisplayName; - case 'GE': - return L10n.of(context)!.geCountryDisplayName; - case 'DE': - return L10n.of(context)!.deCountryDisplayName; - case 'GH': - return L10n.of(context)!.ghCountryDisplayName; - case 'GI': - return L10n.of(context)!.giCountryDisplayName; - case 'GR': - return L10n.of(context)!.grCountryDisplayName; - case 'GL': - return L10n.of(context)!.glCountryDisplayName; - case 'GD': - return L10n.of(context)!.gdCountryDisplayName; - case 'GP': - return L10n.of(context)!.gpCountryDisplayName; - case 'GU': - return L10n.of(context)!.guCountryDisplayName; - case 'GT': - return L10n.of(context)!.gtCountryDisplayName; - case 'GG': - return L10n.of(context)!.ggCountryDisplayName; - case 'GN': - return L10n.of(context)!.gnCountryDisplayName; - case 'GW': - return L10n.of(context)!.gwCountryDisplayName; - case 'GY': - return L10n.of(context)!.gyCountryDisplayName; - case 'HT': - return L10n.of(context)!.htCountryDisplayName; - case 'HM': - return L10n.of(context)!.hmCountryDisplayName; - case 'HN': - return L10n.of(context)!.hnCountryDisplayName; - case 'HK': - return L10n.of(context)!.hkCountryDisplayName; - case 'HU': - return L10n.of(context)!.huCountryDisplayName; - case 'IS': - return L10n.of(context)!.isCountryDisplayName; - case 'IN': - return L10n.of(context)!.inCountryDisplayName; - case 'ID': - return L10n.of(context)!.idCountryDisplayName; - case 'IR': - return L10n.of(context)!.irCountryDisplayName; - case 'IQ': - return L10n.of(context)!.iqCountryDisplayName; - case 'IE': - return L10n.of(context)!.ieCountryDisplayName; - case 'IM': - return L10n.of(context)!.imCountryDisplayName; - case 'IL': - return L10n.of(context)!.ilCountryDisplayName; - case 'IT': - return L10n.of(context)!.itCountryDisplayName; - case 'JM': - return L10n.of(context)!.jmCountryDisplayName; - case 'JP': - return L10n.of(context)!.jpCountryDisplayName; - case 'JE': - return L10n.of(context)!.jeCountryDisplayName; - case 'JO': - return L10n.of(context)!.joCountryDisplayName; - case 'KZ': - return L10n.of(context)!.kzCountryDisplayName; - case 'KE': - return L10n.of(context)!.keCountryDisplayName; - case 'KI': - return L10n.of(context)!.kiCountryDisplayName; - case 'XK': - return L10n.of(context)!.xkCountryDisplayName; - case 'KW': - return L10n.of(context)!.kwCountryDisplayName; - case 'KG': - return L10n.of(context)!.kgCountryDisplayName; - case 'LA': - return L10n.of(context)!.laCountryDisplayName; - case 'LV': - return L10n.of(context)!.lvCountryDisplayName; - case 'LB': - return L10n.of(context)!.lbCountryDisplayName; - case 'LS': - return L10n.of(context)!.lsCountryDisplayName; - case 'LR': - return L10n.of(context)!.lrCountryDisplayName; - case 'LY': - return L10n.of(context)!.lyCountryDisplayName; - case 'LI': - return L10n.of(context)!.liCountryDisplayName; - case 'LT': - return L10n.of(context)!.ltCountryDisplayName; - case 'LU': - return L10n.of(context)!.luCountryDisplayName; - case 'MO': - return L10n.of(context)!.moCountryDisplayName; - case 'MK': - return L10n.of(context)!.mkCountryDisplayName; - case 'MG': - return L10n.of(context)!.mgCountryDisplayName; - case 'MW': - return L10n.of(context)!.mwCountryDisplayName; - case 'MY': - return L10n.of(context)!.myCountryDisplayName; - case 'MV': - return L10n.of(context)!.mvCountryDisplayName; - case 'ML': - return L10n.of(context)!.mlCountryDisplayName; - case 'MT': - return L10n.of(context)!.mtCountryDisplayName; - case 'MH': - return L10n.of(context)!.mhCountryDisplayName; - case 'MQ': - return L10n.of(context)!.mqCountryDisplayName; - case 'MR': - return L10n.of(context)!.mrCountryDisplayName; - case 'MU': - return L10n.of(context)!.muCountryDisplayName; - case 'YT': - return L10n.of(context)!.ytCountryDisplayName; - case 'MX': - return L10n.of(context)!.mxCountryDisplayName; - case 'FM': - return L10n.of(context)!.fmCountryDisplayName; - case 'MD': - return L10n.of(context)!.mdCountryDisplayName; - case 'MC': - return L10n.of(context)!.mcCountryDisplayName; - case 'MN': - return L10n.of(context)!.mnCountryDisplayName; - case 'ME': - return L10n.of(context)!.meCountryDisplayName; - case 'MS': - return L10n.of(context)!.msCountryDisplayName; - case 'MA': - return L10n.of(context)!.maCountryDisplayName; - case 'MZ': - return L10n.of(context)!.mzCountryDisplayName; - case 'MM': - return L10n.of(context)!.mmCountryDisplayName; - case 'NA': - return L10n.of(context)!.naCountryDisplayName; - case 'NR': - return L10n.of(context)!.nrCountryDisplayName; - case 'NP': - return L10n.of(context)!.npCountryDisplayName; - case 'NL': - return L10n.of(context)!.nlCountryDisplayName; - case 'NC': - return L10n.of(context)!.ncCountryDisplayName; - case 'NZ': - return L10n.of(context)!.nzCountryDisplayName; - case 'NI': - return L10n.of(context)!.niCountryDisplayName; - case 'NE': - return L10n.of(context)!.neCountryDisplayName; - case 'NG': - return L10n.of(context)!.ngCountryDisplayName; - case 'NU': - return L10n.of(context)!.nuCountryDisplayName; - case 'NF': - return L10n.of(context)!.nfCountryDisplayName; - case 'KP': - return L10n.of(context)!.kpCountryDisplayName; - case 'MP': - return L10n.of(context)!.mpCountryDisplayName; - case 'NO': - return L10n.of(context)!.noCountryDisplayName; - case 'OM': - return L10n.of(context)!.omCountryDisplayName; - case 'PK': - return L10n.of(context)!.pkCountryDisplayName; - case 'PW': - return L10n.of(context)!.pwCountryDisplayName; - case 'PS': - return L10n.of(context)!.psCountryDisplayName; - case 'PA': - return L10n.of(context)!.paCountryDisplayName; - case 'PG': - return L10n.of(context)!.pgCountryDisplayName; - case 'PY': - return L10n.of(context)!.pyCountryDisplayName; - case 'PE': - return L10n.of(context)!.peCountryDisplayName; - case 'PH': - return L10n.of(context)!.phCountryDisplayName; - case 'PL': - return L10n.of(context)!.plCountryDisplayName; - case 'PT': - return L10n.of(context)!.ptCountryDisplayName; - case 'PR': - return L10n.of(context)!.prCountryDisplayName; - case 'QA': - return L10n.of(context)!.qaCountryDisplayName; - case 'RE': - return L10n.of(context)!.reCountryDisplayName; - case 'RO': - return L10n.of(context)!.roCountryDisplayName; - case 'RU': - return L10n.of(context)!.ruCountryDisplayName; - case 'RW': - return L10n.of(context)!.rwCountryDisplayName; - case 'BL': - return L10n.of(context)!.blCountryDisplayName; - case 'SH': - return L10n.of(context)!.shCountryDisplayName; - case 'KN': - return L10n.of(context)!.knCountryDisplayName; - case 'LC': - return L10n.of(context)!.lcCountryDisplayName; - case 'MF': - return L10n.of(context)!.mfCountryDisplayName; - case 'PM': - return L10n.of(context)!.pmCountryDisplayName; - case 'VC': - return L10n.of(context)!.vcCountryDisplayName; - case 'WS': - return L10n.of(context)!.wsCountryDisplayName; - case 'SM': - return L10n.of(context)!.smCountryDisplayName; - case 'ST': - return L10n.of(context)!.stCountryDisplayName; - case 'SA': - return L10n.of(context)!.saCountryDisplayName; - case 'SN': - return L10n.of(context)!.snCountryDisplayName; - case 'RS': - return L10n.of(context)!.rsCountryDisplayName; - case 'SC': - return L10n.of(context)!.scCountryDisplayName; - case 'SL': - return L10n.of(context)!.slCountryDisplayName; - case 'SG': - return L10n.of(context)!.sgCountryDisplayName; - case 'SX': - return L10n.of(context)!.sxCountryDisplayName; - case 'SK': - return L10n.of(context)!.skCountryDisplayName; - case 'SI': - return L10n.of(context)!.siCountryDisplayName; - case 'SB': - return L10n.of(context)!.sbCountryDisplayName; - case 'SO': - return L10n.of(context)!.soCountryDisplayName; - case 'ZA': - return L10n.of(context)!.zaCountryDisplayName; - case 'GS': - return L10n.of(context)!.gsCountryDisplayName; - case 'KR': - return L10n.of(context)!.krCountryDisplayName; - case 'SS': - return L10n.of(context)!.ssCountryDisplayName; - case 'ES': - return L10n.of(context)!.esCountryDisplayName; - case 'LK': - return L10n.of(context)!.lkCountryDisplayName; - case 'SD': - return L10n.of(context)!.sdCountryDisplayName; - case 'SR': - return L10n.of(context)!.srCountryDisplayName; - case 'SJ': - return L10n.of(context)!.sjCountryDisplayName; - case 'SE': - return L10n.of(context)!.seCountryDisplayName; - case 'CH': - return L10n.of(context)!.chCountryDisplayName; - case 'SY': - return L10n.of(context)!.syCountryDisplayName; - case 'TW': - return L10n.of(context)!.twCountryDisplayName; - case 'TJ': - return L10n.of(context)!.tjCountryDisplayName; - case 'TZ': - return L10n.of(context)!.tzCountryDisplayName; - case 'TH': - return L10n.of(context)!.thCountryDisplayName; - case 'TG': - return L10n.of(context)!.tgCountryDisplayName; - case 'TK': - return L10n.of(context)!.tkCountryDisplayName; - case 'TO': - return L10n.of(context)!.toCountryDisplayName; - case 'TT': - return L10n.of(context)!.ttCountryDisplayName; - case 'TN': - return L10n.of(context)!.tnCountryDisplayName; - case 'TR': - return L10n.of(context)!.trCountryDisplayName; - case 'TM': - return L10n.of(context)!.tmCountryDisplayName; - case 'TC': - return L10n.of(context)!.tcCountryDisplayName; - case 'TV': - return L10n.of(context)!.tvCountryDisplayName; - case 'VI': - return L10n.of(context)!.viCountryDisplayName; - case 'UG': - return L10n.of(context)!.ugCountryDisplayName; - case 'UA': - return L10n.of(context)!.uaCountryDisplayName; - case 'AE': - return L10n.of(context)!.aeCountryDisplayName; - case 'GB': - return L10n.of(context)!.gbCountryDisplayName; - case 'US': - return L10n.of(context)!.usCountryDisplayName; - case 'UY': - return L10n.of(context)!.uyCountryDisplayName; - case 'UZ': - return L10n.of(context)!.uzCountryDisplayName; - case 'VU': - return L10n.of(context)!.vuCountryDisplayName; - case 'VA': - return L10n.of(context)!.vaCountryDisplayName; - case 'VE': - return L10n.of(context)!.veCountryDisplayName; - case 'VN': - return L10n.of(context)!.vnCountryDisplayName; - case 'WF': - return L10n.of(context)!.wfCountryDisplayName; - case 'EH': - return L10n.of(context)!.ehCountryDisplayName; - case 'YE': - return L10n.of(context)!.yeCountryDisplayName; - case 'ZM': - return L10n.of(context)!.zmCountryDisplayName; - case 'ZW': - return L10n.of(context)!.zwCountryDisplayName; - } - return null; + PangeaProfileResponse({ + required this.profile, + required this.access, + }); + + factory PangeaProfileResponse.fromJson(Map json) { + return PangeaProfileResponse( + profile: PangeaProfile.fromJson(json), + access: json[ModelKey.userAccess], + ); } } diff --git a/lib/pangea/models/user_profile_search_model.dart b/lib/pangea/models/user_profile_search_model.dart index de8822b04..212e8e392 100644 --- a/lib/pangea/models/user_profile_search_model.dart +++ b/lib/pangea/models/user_profile_search_model.dart @@ -4,7 +4,7 @@ class UserProfileSearchResponse { int count; String? next; String? previous; - List results; + List results; UserProfileSearchResponse({ required this.count, @@ -19,9 +19,9 @@ class UserProfileSearchResponse { next: json["next"], previous: json["previous"], results: json["results"] - .map((p) => Profile.fromJson(p)) + .map((p) => PangeaProfile.fromJson(p)) .toList() - .cast(), + .cast(), ); } } diff --git a/lib/pangea/pages/find_partner/find_partner.dart b/lib/pangea/pages/find_partner/find_partner.dart index 46eb365ae..a953c7f19 100644 --- a/lib/pangea/pages/find_partner/find_partner.dart +++ b/lib/pangea/pages/find_partner/find_partner.dart @@ -37,7 +37,7 @@ class FindPartnerController extends State { Timer? coolDown; - final List _userProfilesCache = []; + final List _userProfilesCache = []; final scrollController = ScrollController(); String? error; @@ -83,7 +83,7 @@ class FindPartnerController extends State { return FindPartnerView(this); } - List get userProfiles => _userProfilesCache.where((p) { + List get userProfiles => _userProfilesCache.where((p) { return (p.targetLanguage != null && targetLanguageSearch.langCode == p.targetLanguage) && (p.sourceLanguage != null && @@ -127,8 +127,7 @@ class FindPartnerController extends State { nextUrl = response.next; nextPage++; - final String? currentUserId = - pangeaController.userController.userModel?.profile?.pangeaUserId; + final String? currentUserId = pangeaController.matrixState.client.userID; _userProfilesCache.addAll( response.results.where( (p) => diff --git a/lib/pangea/pages/find_partner/find_partner_view.dart b/lib/pangea/pages/find_partner/find_partner_view.dart index db6afa2d1..032ed7daf 100644 --- a/lib/pangea/pages/find_partner/find_partner_view.dart +++ b/lib/pangea/pages/find_partner/find_partner_view.dart @@ -2,6 +2,7 @@ import 'package:country_picker/country_picker.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/models/user_model.dart'; +import 'package:fluffychat/pangea/utils/country_display.dart'; import 'package:fluffychat/pangea/widgets/common/list_placeholder.dart'; import 'package:fluffychat/pangea/widgets/common/pangea_logo_svg.dart'; import 'package:fluffychat/pangea/widgets/user_settings/p_language_dropdown.dart'; @@ -244,7 +245,7 @@ class LanguageSelectionRow extends StatelessWidget { } class UserProfileEntry extends StatelessWidget { - final Profile pangeaProfile; + final PangeaProfile pangeaProfile; final FindPartnerController controller; const UserProfileEntry({ @@ -287,7 +288,7 @@ class UserProfileEntry extends StatelessWidget { const SizedBox(width: 20), RichText( text: TextSpan( - text: pangeaProfile.flagEmoji, + text: CountryDisplayUtil.flagEmoji(pangeaProfile.country), style: const TextStyle(fontSize: 15), ), ), diff --git a/lib/pangea/pages/p_user_age/p_user_age.dart b/lib/pangea/pages/p_user_age/p_user_age.dart index adb060e38..5fe489485 100644 --- a/lib/pangea/pages/p_user_age/p_user_age.dart +++ b/lib/pangea/pages/p_user_age/p_user_age.dart @@ -10,7 +10,6 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:intl/intl.dart'; import '../../utils/bot_name.dart'; import '../../utils/error_handler.dart'; @@ -73,26 +72,24 @@ class PUserAgeController extends State { } //Note: used linear progress bar (also used in fluffychat signup button) for consistency - createUserInPangea() async { + Future createUserInPangea() async { try { - setState(() { - error = dobValidator(); - }); - + setState(() => error = dobValidator()); if (error?.isNotEmpty == true) return; + setState(() => loading = true); - setState(() { - loading = true; - }); + final DateTime? dob = + pangeaController.userController.profile.userSettings.dateOfBirth; - final String date = DateFormat('yyyy-MM-dd').format(selectedDate!); - - if (pangeaController.userController.userModel?.access == null) { - await pangeaController.userController.createProfile(dob: date); - } else { - await pangeaController.userController.updateUserProfile( - dateOfBirth: date, + if (dob == null) { + await pangeaController.userController.createProfile( + dob: selectedDate!, ); + } else { + pangeaController.userController.updateProfile((profile) { + profile.userSettings.dateOfBirth = selectedDate!; + return profile; + }); } FluffyChatApp.router.go('/rooms'); } catch (err, s) { diff --git a/lib/pangea/pages/settings_learning/settings_learning.dart b/lib/pangea/pages/settings_learning/settings_learning.dart index 4c4fe4d50..03e5f635a 100644 --- a/lib/pangea/pages/settings_learning/settings_learning.dart +++ b/lib/pangea/pages/settings_learning/settings_learning.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'package:country_picker/country_picker.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/models/space_model.dart'; +import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning_view.dart'; import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -18,31 +20,55 @@ class SettingsLearningController extends State { late StreamSubscription _userSubscription; PangeaController pangeaController = MatrixState.pangeaController; - setPublicProfile(bool b) async { - await pangeaController.userController.updateUserProfile(publicProfile: b); - setState(() {}); + Future changeLanguage() async { + await pLanguageDialog(context, () {}); } - @override - void initState() { - super.initState(); - - _userSubscription = - pangeaController.userController.stateStream.listen((event) { - setState(() {}); + Future setPublicProfile(bool isPublic) async { + pangeaController.userController.updateProfile((profile) { + profile.userSettings.publicProfile = isPublic; + return profile; }); } - Future changeLanguage() async { - await pLanguageDialog(context, () {}); - setState(() {}); + Future changeCountry(Country country) async { + pangeaController.userController.updateProfile((profile) { + profile.userSettings.country = country.displayNameNoCountryCode; + return profile; + }); } - Future changeCountry(Country country) async { - await pangeaController.userController.updateUserProfile( - country: country.displayNameNoCountryCode, - ); - setState(() {}); + void updateToolSetting(ToolSetting toolSetting, bool value) { + pangeaController.userController.updateProfile((Profile profile) { + switch (toolSetting) { + case ToolSetting.interactiveTranslator: + return profile..toolSettings.interactiveTranslator = value; + case ToolSetting.interactiveGrammar: + return profile..toolSettings.interactiveGrammar = value; + case ToolSetting.immersionMode: + return profile..toolSettings.immersionMode = value; + case ToolSetting.definitions: + return profile..toolSettings.definitions = value; + case ToolSetting.autoIGC: + return profile..toolSettings.autoIGC = value; + } + }); + } + + bool getToolSetting(ToolSetting toolSetting) { + final toolSettings = pangeaController.userController.profile.toolSettings; + switch (toolSetting) { + case ToolSetting.interactiveTranslator: + return toolSettings.interactiveTranslator; + case ToolSetting.interactiveGrammar: + return toolSettings.interactiveGrammar; + case ToolSetting.immersionMode: + return toolSettings.immersionMode; + case ToolSetting.definitions: + return toolSettings.definitions; + case ToolSetting.autoIGC: + return toolSettings.autoIGC; + } } @override diff --git a/lib/pangea/pages/settings_learning/settings_learning_view.dart b/lib/pangea/pages/settings_learning/settings_learning_view.dart index 06136cd7e..5422ebbe5 100644 --- a/lib/pangea/pages/settings_learning/settings_learning_view.dart +++ b/lib/pangea/pages/settings_learning/settings_learning_view.dart @@ -1,5 +1,4 @@ import 'package:fluffychat/pangea/models/space_model.dart'; -import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/widgets/user_settings/country_picker_tile.dart'; @@ -18,71 +17,97 @@ class SettingsLearningView extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: Text( - L10n.of(context)!.learningSettings, - ), + // rebuild this page each time a sync comes through with new account data + // this prevents having to call setState each time an individual setting is changed + return StreamBuilder( + stream: + controller.pangeaController.matrixState.client.onSync.stream.where( + (update) => update.accountData != null, ), - body: ListTileTheme( - iconColor: Theme.of(context).textTheme.bodyLarge!.color, - child: MaxWidthBody( - withScrolling: true, - child: Column( - children: [ - LanguageTile(controller), - CountryPickerTile(controller), - const SizedBox(height: 8), - const Divider(height: 1), - const SizedBox(height: 8), - if (controller.pangeaController.permissionsController.isUser18()) - SwitchListTile.adaptive( - activeColor: AppConfig.activeToggleColor, - title: Text(L10n.of(context)!.publicProfileTitle), - subtitle: Text(L10n.of(context)!.publicProfileDesc), - value: controller.pangeaController.userController.isPublic, - onChanged: (bool isPublicProfile) => showFutureLoadingDialog( - context: context, - future: () => controller.setPublicProfile(isPublicProfile), - onError: (err) => - ErrorHandler.logError(e: err, s: StackTrace.current), - ), - ), - ListTile( - subtitle: Text(L10n.of(context)!.toggleToolSettingsDescription), - ), - for (final setting in MatrixProfile.toolSettings) - setting.asToolSetting != null - ? ProfileSettingsSwitchListTile.adaptive( - defaultValue: controller - .pangeaController.permissionsController - .userToolSetting(setting), - title: setting.asToolSetting!.toolName(context), - subtitle: - setting.asToolSetting!.toolDescription(context), - profileKey: setting.asToolSetting!.asMatrixProfileField, - ) - : const SizedBox(), - ProfileSettingsSwitchListTile.adaptive( - defaultValue: controller - .pangeaController.userController.matrixProfile.itAutoPlay, - title: - L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader, - subtitle: L10n.of(context)!.interactiveTranslatorAutoPlayDesc, - profileKey: MatrixProfileEnum.itAutoPlay, - ), - ProfileSettingsSwitchListTile.adaptive( - defaultValue: controller.pangeaController.userController - .matrixProfile.autoPlayMessages, - title: L10n.of(context)!.autoPlayTitle, - subtitle: L10n.of(context)!.autoPlayDesc, - profileKey: MatrixProfileEnum.autoPlayMessages, - ), - ], + builder: (context, snapshot) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + L10n.of(context)!.learningSettings, + ), ), - ), - ), + body: ListTileTheme( + iconColor: Theme.of(context).textTheme.bodyLarge!.color, + child: MaxWidthBody( + withScrolling: true, + child: Column( + children: [ + LanguageTile(controller), + CountryPickerTile(controller), + const SizedBox(height: 8), + const Divider(height: 1), + const SizedBox(height: 8), + if (controller.pangeaController.permissionsController + .isUser18()) + SwitchListTile.adaptive( + activeColor: AppConfig.activeToggleColor, + title: Text(L10n.of(context)!.publicProfileTitle), + subtitle: Text(L10n.of(context)!.publicProfileDesc), + value: + controller.pangeaController.userController.isPublic, + onChanged: (bool isPublicProfile) => + showFutureLoadingDialog( + context: context, + future: () => + controller.setPublicProfile(isPublicProfile), + onError: (err) => ErrorHandler.logError( + e: err, + s: StackTrace.current, + ), + ), + ), + ListTile( + subtitle: + Text(L10n.of(context)!.toggleToolSettingsDescription), + ), + for (final toolSetting in ToolSetting.values) + ProfileSettingsSwitchListTile.adaptive( + defaultValue: controller.getToolSetting(toolSetting), + title: toolSetting.toolName(context), + subtitle: toolSetting.toolDescription(context), + onChange: (bool value) => controller.updateToolSetting( + toolSetting, + value, + ), + ), + ProfileSettingsSwitchListTile.adaptive( + defaultValue: controller.pangeaController.userController + .profile.userSettings.itAutoPlay, + title: L10n.of(context)! + .interactiveTranslatorAutoPlaySliderHeader, + subtitle: + L10n.of(context)!.interactiveTranslatorAutoPlayDesc, + onChange: (bool value) => controller + .pangeaController.userController + .updateProfile((profile) { + profile.userSettings.itAutoPlay = value; + return profile; + }), + ), + ProfileSettingsSwitchListTile.adaptive( + defaultValue: controller.pangeaController.userController + .profile.userSettings.autoPlayMessages, + title: L10n.of(context)!.autoPlayTitle, + subtitle: L10n.of(context)!.autoPlayDesc, + onChange: (bool value) => controller + .pangeaController.userController + .updateProfile((profile) { + profile.userSettings.autoPlayMessages = value; + return profile; + }), + ), + ], + ), + ), + ), + ); + }, ); } } diff --git a/lib/pangea/repo/user_repo.dart b/lib/pangea/repo/user_repo.dart index 8f3b5a67f..47caaab0b 100644 --- a/lib/pangea/repo/user_repo.dart +++ b/lib/pangea/repo/user_repo.dart @@ -4,37 +4,13 @@ import 'dart:developer'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:http/http.dart'; -import '../../widgets/matrix.dart'; import '../models/user_model.dart'; import '../models/user_profile_search_model.dart'; import '../network/requests.dart'; import '../network/urls.dart'; class PUserRepo { - static Future repoCreatePangeaUser({ - required String userID, - required String dob, - required fullName, - required String matrixAccessToken, - }) async { - final Requests req = Requests( - baseUrl: PApiUrls.baseAPI, - matrixAccessToken: matrixAccessToken, - ); - - final Map body = { - ModelKey.userFullName: fullName, - ModelKey.userPangeaUserId: userID, - ModelKey.userDateOfBirth: dob, - }; - final Response res = await req.post( - url: PApiUrls.createUser, - body: body, - ); - return PUserModel.fromJson(jsonDecode(res.body)); - } - - static Future fetchPangeaUserInfo({ + static Future fetchPangeaUserInfo({ required String userID, required String matrixAccessToken, }) async { @@ -49,7 +25,7 @@ class PUserRepo { objectId: userID, ); - return PUserModel.fromJson(jsonDecode(res.body)); + return PangeaProfileResponse.fromJson(jsonDecode(res.body)); } catch (err) { //status code should be 400 - PTODO - check ffor this. log("Most likely a first signup and needs to make an account"); @@ -57,32 +33,6 @@ class PUserRepo { } } - //notes for jordan - only replace non-null fields, return whole profile - //Jordan - should return pangeaUserId as well - static Future updateUserProfile( - Profile userProfile, - String accessToken, - ) async { - final Requests req = Requests( - baseUrl: PApiUrls.baseAPI, - accessToken: accessToken, - ); - final Response res = await req.put( - url: PApiUrls.updateUserProfile, - body: userProfile.toJson(), - ); - - //temp fix - final content = jsonDecode(res.body); - //PTODO - try taking this out and see where bug occurs - if (content[ModelKey.userPangeaUserId] == null) { - content[ModelKey.userPangeaUserId] = - MatrixState.pangeaController.matrixState.client.userID; - } - - return Profile.fromJson(content); - } - static Future searchUserProfiles({ // List? interests, String? targetLanguage, diff --git a/lib/pangea/utils/country_display.dart b/lib/pangea/utils/country_display.dart new file mode 100644 index 000000000..caba48442 --- /dev/null +++ b/lib/pangea/utils/country_display.dart @@ -0,0 +1,515 @@ +import 'package:country_picker/country_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class CountryDisplayUtil { + /// used in find a partner page for display partner's country + static String flagEmoji(String? countryName) { + countryName = countryName?.split(' (')[0]; + final Country? country = CountryService().findByName(countryName); + return country?.flagEmoji ?? ""; + } + + static String? countryDisplayName(String? countryName, BuildContext context) { + countryName = countryName?.split(' (')[0]; + final Country? country = CountryService().findByName(countryName); + if (country?.countryCode == null) return null; + switch (country!.countryCode) { + case 'WW': + return L10n.of(context)!.wwCountryDisplayName; + case 'AF': + return L10n.of(context)!.afCountryDisplayName; + case 'AX': + return L10n.of(context)!.axCountryDisplayName; + case 'AL': + return L10n.of(context)!.alCountryDisplayName; + case 'DZ': + return L10n.of(context)!.dzCountryDisplayName; + case 'AS': + return L10n.of(context)!.asCountryDisplayName; + case 'AD': + return L10n.of(context)!.adCountryDisplayName; + case 'AO': + return L10n.of(context)!.aoCountryDisplayName; + case 'AI': + return L10n.of(context)!.aiCountryDisplayName; + case 'AG': + return L10n.of(context)!.agCountryDisplayName; + case 'AR': + return L10n.of(context)!.arCountryDisplayName; + case 'AM': + return L10n.of(context)!.amCountryDisplayName; + case 'AW': + return L10n.of(context)!.awCountryDisplayName; + case 'AC': + return L10n.of(context)!.acCountryDisplayName; + case 'AU': + return L10n.of(context)!.auCountryDisplayName; + case 'AT': + return L10n.of(context)!.atCountryDisplayName; + case 'AZ': + return L10n.of(context)!.azCountryDisplayName; + case 'BS': + return L10n.of(context)!.bsCountryDisplayName; + case 'BH': + return L10n.of(context)!.bhCountryDisplayName; + case 'BD': + return L10n.of(context)!.bdCountryDisplayName; + case 'BB': + return L10n.of(context)!.bbCountryDisplayName; + case 'BY': + return L10n.of(context)!.byCountryDisplayName; + case 'BE': + return L10n.of(context)!.beCountryDisplayName; + case 'BZ': + return L10n.of(context)!.bzCountryDisplayName; + case 'BJ': + return L10n.of(context)!.bjCountryDisplayName; + case 'BM': + return L10n.of(context)!.bmCountryDisplayName; + case 'BT': + return L10n.of(context)!.btCountryDisplayName; + case 'BO': + return L10n.of(context)!.boCountryDisplayName; + case 'BA': + return L10n.of(context)!.baCountryDisplayName; + case 'BW': + return L10n.of(context)!.bwCountryDisplayName; + case 'BR': + return L10n.of(context)!.brCountryDisplayName; + case 'IO': + return L10n.of(context)!.ioCountryDisplayName; + case 'VG': + return L10n.of(context)!.vgCountryDisplayName; + case 'BN': + return L10n.of(context)!.bnCountryDisplayName; + case 'BG': + return L10n.of(context)!.bgCountryDisplayName; + case 'BF': + return L10n.of(context)!.bfCountryDisplayName; + case 'BI': + return L10n.of(context)!.biCountryDisplayName; + case 'KH': + return L10n.of(context)!.khCountryDisplayName; + case 'CM': + return L10n.of(context)!.cmCountryDisplayName; + case 'CA': + return L10n.of(context)!.caCountryDisplayName; + case 'CV': + return L10n.of(context)!.cvCountryDisplayName; + case 'BQ': + return L10n.of(context)!.bqCountryDisplayName; + case 'KY': + return L10n.of(context)!.kyCountryDisplayName; + case 'CF': + return L10n.of(context)!.cfCountryDisplayName; + case 'TD': + return L10n.of(context)!.tdCountryDisplayName; + case 'CL': + return L10n.of(context)!.clCountryDisplayName; + case 'CN': + return L10n.of(context)!.cnCountryDisplayName; + case 'CX': + return L10n.of(context)!.cxCountryDisplayName; + case 'CC': + return L10n.of(context)!.ccCountryDisplayName; + case 'CO': + return L10n.of(context)!.coCountryDisplayName; + case 'KM': + return L10n.of(context)!.kmCountryDisplayName; + case 'CD': + return L10n.of(context)!.cdCountryDisplayName; + case 'CG': + return L10n.of(context)!.cgCountryDisplayName; + case 'CK': + return L10n.of(context)!.ckCountryDisplayName; + case 'CR': + return L10n.of(context)!.crCountryDisplayName; + case 'CI': + return L10n.of(context)!.ciCountryDisplayName; + case 'HR': + return L10n.of(context)!.hrCountryDisplayName; + case 'CU': + return L10n.of(context)!.cuCountryDisplayName; + case 'CW': + return L10n.of(context)!.cwCountryDisplayName; + case 'CY': + return L10n.of(context)!.cyCountryDisplayName; + case 'CZ': + return L10n.of(context)!.czCountryDisplayName; + case 'DK': + return L10n.of(context)!.dkCountryDisplayName; + case 'DJ': + return L10n.of(context)!.djCountryDisplayName; + case 'DM': + return L10n.of(context)!.dmCountryDisplayName; + case 'DO': + return L10n.of(context)!.doCountryDisplayName; + case 'TL': + return L10n.of(context)!.tlCountryDisplayName; + case 'EC': + return L10n.of(context)!.ecCountryDisplayName; + case 'EG': + return L10n.of(context)!.egCountryDisplayName; + case 'SV': + return L10n.of(context)!.svCountryDisplayName; + case 'GQ': + return L10n.of(context)!.gqCountryDisplayName; + case 'ER': + return L10n.of(context)!.erCountryDisplayName; + case 'EE': + return L10n.of(context)!.eeCountryDisplayName; + case 'SZ': + return L10n.of(context)!.szCountryDisplayName; + case 'ET': + return L10n.of(context)!.etCountryDisplayName; + case 'FK': + return L10n.of(context)!.fkCountryDisplayName; + case 'FO': + return L10n.of(context)!.foCountryDisplayName; + case 'FJ': + return L10n.of(context)!.fjCountryDisplayName; + case 'FI': + return L10n.of(context)!.fiCountryDisplayName; + case 'FR': + return L10n.of(context)!.frCountryDisplayName; + case 'GF': + return L10n.of(context)!.gfCountryDisplayName; + case 'PF': + return L10n.of(context)!.pfCountryDisplayName; + case 'GA': + return L10n.of(context)!.gaCountryDisplayName; + case 'GM': + return L10n.of(context)!.gmCountryDisplayName; + case 'GE': + return L10n.of(context)!.geCountryDisplayName; + case 'DE': + return L10n.of(context)!.deCountryDisplayName; + case 'GH': + return L10n.of(context)!.ghCountryDisplayName; + case 'GI': + return L10n.of(context)!.giCountryDisplayName; + case 'GR': + return L10n.of(context)!.grCountryDisplayName; + case 'GL': + return L10n.of(context)!.glCountryDisplayName; + case 'GD': + return L10n.of(context)!.gdCountryDisplayName; + case 'GP': + return L10n.of(context)!.gpCountryDisplayName; + case 'GU': + return L10n.of(context)!.guCountryDisplayName; + case 'GT': + return L10n.of(context)!.gtCountryDisplayName; + case 'GG': + return L10n.of(context)!.ggCountryDisplayName; + case 'GN': + return L10n.of(context)!.gnCountryDisplayName; + case 'GW': + return L10n.of(context)!.gwCountryDisplayName; + case 'GY': + return L10n.of(context)!.gyCountryDisplayName; + case 'HT': + return L10n.of(context)!.htCountryDisplayName; + case 'HM': + return L10n.of(context)!.hmCountryDisplayName; + case 'HN': + return L10n.of(context)!.hnCountryDisplayName; + case 'HK': + return L10n.of(context)!.hkCountryDisplayName; + case 'HU': + return L10n.of(context)!.huCountryDisplayName; + case 'IS': + return L10n.of(context)!.isCountryDisplayName; + case 'IN': + return L10n.of(context)!.inCountryDisplayName; + case 'ID': + return L10n.of(context)!.idCountryDisplayName; + case 'IR': + return L10n.of(context)!.irCountryDisplayName; + case 'IQ': + return L10n.of(context)!.iqCountryDisplayName; + case 'IE': + return L10n.of(context)!.ieCountryDisplayName; + case 'IM': + return L10n.of(context)!.imCountryDisplayName; + case 'IL': + return L10n.of(context)!.ilCountryDisplayName; + case 'IT': + return L10n.of(context)!.itCountryDisplayName; + case 'JM': + return L10n.of(context)!.jmCountryDisplayName; + case 'JP': + return L10n.of(context)!.jpCountryDisplayName; + case 'JE': + return L10n.of(context)!.jeCountryDisplayName; + case 'JO': + return L10n.of(context)!.joCountryDisplayName; + case 'KZ': + return L10n.of(context)!.kzCountryDisplayName; + case 'KE': + return L10n.of(context)!.keCountryDisplayName; + case 'KI': + return L10n.of(context)!.kiCountryDisplayName; + case 'XK': + return L10n.of(context)!.xkCountryDisplayName; + case 'KW': + return L10n.of(context)!.kwCountryDisplayName; + case 'KG': + return L10n.of(context)!.kgCountryDisplayName; + case 'LA': + return L10n.of(context)!.laCountryDisplayName; + case 'LV': + return L10n.of(context)!.lvCountryDisplayName; + case 'LB': + return L10n.of(context)!.lbCountryDisplayName; + case 'LS': + return L10n.of(context)!.lsCountryDisplayName; + case 'LR': + return L10n.of(context)!.lrCountryDisplayName; + case 'LY': + return L10n.of(context)!.lyCountryDisplayName; + case 'LI': + return L10n.of(context)!.liCountryDisplayName; + case 'LT': + return L10n.of(context)!.ltCountryDisplayName; + case 'LU': + return L10n.of(context)!.luCountryDisplayName; + case 'MO': + return L10n.of(context)!.moCountryDisplayName; + case 'MK': + return L10n.of(context)!.mkCountryDisplayName; + case 'MG': + return L10n.of(context)!.mgCountryDisplayName; + case 'MW': + return L10n.of(context)!.mwCountryDisplayName; + case 'MY': + return L10n.of(context)!.myCountryDisplayName; + case 'MV': + return L10n.of(context)!.mvCountryDisplayName; + case 'ML': + return L10n.of(context)!.mlCountryDisplayName; + case 'MT': + return L10n.of(context)!.mtCountryDisplayName; + case 'MH': + return L10n.of(context)!.mhCountryDisplayName; + case 'MQ': + return L10n.of(context)!.mqCountryDisplayName; + case 'MR': + return L10n.of(context)!.mrCountryDisplayName; + case 'MU': + return L10n.of(context)!.muCountryDisplayName; + case 'YT': + return L10n.of(context)!.ytCountryDisplayName; + case 'MX': + return L10n.of(context)!.mxCountryDisplayName; + case 'FM': + return L10n.of(context)!.fmCountryDisplayName; + case 'MD': + return L10n.of(context)!.mdCountryDisplayName; + case 'MC': + return L10n.of(context)!.mcCountryDisplayName; + case 'MN': + return L10n.of(context)!.mnCountryDisplayName; + case 'ME': + return L10n.of(context)!.meCountryDisplayName; + case 'MS': + return L10n.of(context)!.msCountryDisplayName; + case 'MA': + return L10n.of(context)!.maCountryDisplayName; + case 'MZ': + return L10n.of(context)!.mzCountryDisplayName; + case 'MM': + return L10n.of(context)!.mmCountryDisplayName; + case 'NA': + return L10n.of(context)!.naCountryDisplayName; + case 'NR': + return L10n.of(context)!.nrCountryDisplayName; + case 'NP': + return L10n.of(context)!.npCountryDisplayName; + case 'NL': + return L10n.of(context)!.nlCountryDisplayName; + case 'NC': + return L10n.of(context)!.ncCountryDisplayName; + case 'NZ': + return L10n.of(context)!.nzCountryDisplayName; + case 'NI': + return L10n.of(context)!.niCountryDisplayName; + case 'NE': + return L10n.of(context)!.neCountryDisplayName; + case 'NG': + return L10n.of(context)!.ngCountryDisplayName; + case 'NU': + return L10n.of(context)!.nuCountryDisplayName; + case 'NF': + return L10n.of(context)!.nfCountryDisplayName; + case 'KP': + return L10n.of(context)!.kpCountryDisplayName; + case 'MP': + return L10n.of(context)!.mpCountryDisplayName; + case 'NO': + return L10n.of(context)!.noCountryDisplayName; + case 'OM': + return L10n.of(context)!.omCountryDisplayName; + case 'PK': + return L10n.of(context)!.pkCountryDisplayName; + case 'PW': + return L10n.of(context)!.pwCountryDisplayName; + case 'PS': + return L10n.of(context)!.psCountryDisplayName; + case 'PA': + return L10n.of(context)!.paCountryDisplayName; + case 'PG': + return L10n.of(context)!.pgCountryDisplayName; + case 'PY': + return L10n.of(context)!.pyCountryDisplayName; + case 'PE': + return L10n.of(context)!.peCountryDisplayName; + case 'PH': + return L10n.of(context)!.phCountryDisplayName; + case 'PL': + return L10n.of(context)!.plCountryDisplayName; + case 'PT': + return L10n.of(context)!.ptCountryDisplayName; + case 'PR': + return L10n.of(context)!.prCountryDisplayName; + case 'QA': + return L10n.of(context)!.qaCountryDisplayName; + case 'RE': + return L10n.of(context)!.reCountryDisplayName; + case 'RO': + return L10n.of(context)!.roCountryDisplayName; + case 'RU': + return L10n.of(context)!.ruCountryDisplayName; + case 'RW': + return L10n.of(context)!.rwCountryDisplayName; + case 'BL': + return L10n.of(context)!.blCountryDisplayName; + case 'SH': + return L10n.of(context)!.shCountryDisplayName; + case 'KN': + return L10n.of(context)!.knCountryDisplayName; + case 'LC': + return L10n.of(context)!.lcCountryDisplayName; + case 'MF': + return L10n.of(context)!.mfCountryDisplayName; + case 'PM': + return L10n.of(context)!.pmCountryDisplayName; + case 'VC': + return L10n.of(context)!.vcCountryDisplayName; + case 'WS': + return L10n.of(context)!.wsCountryDisplayName; + case 'SM': + return L10n.of(context)!.smCountryDisplayName; + case 'ST': + return L10n.of(context)!.stCountryDisplayName; + case 'SA': + return L10n.of(context)!.saCountryDisplayName; + case 'SN': + return L10n.of(context)!.snCountryDisplayName; + case 'RS': + return L10n.of(context)!.rsCountryDisplayName; + case 'SC': + return L10n.of(context)!.scCountryDisplayName; + case 'SL': + return L10n.of(context)!.slCountryDisplayName; + case 'SG': + return L10n.of(context)!.sgCountryDisplayName; + case 'SX': + return L10n.of(context)!.sxCountryDisplayName; + case 'SK': + return L10n.of(context)!.skCountryDisplayName; + case 'SI': + return L10n.of(context)!.siCountryDisplayName; + case 'SB': + return L10n.of(context)!.sbCountryDisplayName; + case 'SO': + return L10n.of(context)!.soCountryDisplayName; + case 'ZA': + return L10n.of(context)!.zaCountryDisplayName; + case 'GS': + return L10n.of(context)!.gsCountryDisplayName; + case 'KR': + return L10n.of(context)!.krCountryDisplayName; + case 'SS': + return L10n.of(context)!.ssCountryDisplayName; + case 'ES': + return L10n.of(context)!.esCountryDisplayName; + case 'LK': + return L10n.of(context)!.lkCountryDisplayName; + case 'SD': + return L10n.of(context)!.sdCountryDisplayName; + case 'SR': + return L10n.of(context)!.srCountryDisplayName; + case 'SJ': + return L10n.of(context)!.sjCountryDisplayName; + case 'SE': + return L10n.of(context)!.seCountryDisplayName; + case 'CH': + return L10n.of(context)!.chCountryDisplayName; + case 'SY': + return L10n.of(context)!.syCountryDisplayName; + case 'TW': + return L10n.of(context)!.twCountryDisplayName; + case 'TJ': + return L10n.of(context)!.tjCountryDisplayName; + case 'TZ': + return L10n.of(context)!.tzCountryDisplayName; + case 'TH': + return L10n.of(context)!.thCountryDisplayName; + case 'TG': + return L10n.of(context)!.tgCountryDisplayName; + case 'TK': + return L10n.of(context)!.tkCountryDisplayName; + case 'TO': + return L10n.of(context)!.toCountryDisplayName; + case 'TT': + return L10n.of(context)!.ttCountryDisplayName; + case 'TN': + return L10n.of(context)!.tnCountryDisplayName; + case 'TR': + return L10n.of(context)!.trCountryDisplayName; + case 'TM': + return L10n.of(context)!.tmCountryDisplayName; + case 'TC': + return L10n.of(context)!.tcCountryDisplayName; + case 'TV': + return L10n.of(context)!.tvCountryDisplayName; + case 'VI': + return L10n.of(context)!.viCountryDisplayName; + case 'UG': + return L10n.of(context)!.ugCountryDisplayName; + case 'UA': + return L10n.of(context)!.uaCountryDisplayName; + case 'AE': + return L10n.of(context)!.aeCountryDisplayName; + case 'GB': + return L10n.of(context)!.gbCountryDisplayName; + case 'US': + return L10n.of(context)!.usCountryDisplayName; + case 'UY': + return L10n.of(context)!.uyCountryDisplayName; + case 'UZ': + return L10n.of(context)!.uzCountryDisplayName; + case 'VU': + return L10n.of(context)!.vuCountryDisplayName; + case 'VA': + return L10n.of(context)!.vaCountryDisplayName; + case 'VE': + return L10n.of(context)!.veCountryDisplayName; + case 'VN': + return L10n.of(context)!.vnCountryDisplayName; + case 'WF': + return L10n.of(context)!.wfCountryDisplayName; + case 'EH': + return L10n.of(context)!.ehCountryDisplayName; + case 'YE': + return L10n.of(context)!.yeCountryDisplayName; + case 'ZM': + return L10n.of(context)!.zmCountryDisplayName; + case 'ZW': + return L10n.of(context)!.zwCountryDisplayName; + } + return null; + } +} diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index b547efa7a..07b0ad0d5 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -333,7 +333,8 @@ class MessageToolbarState extends State { return; } - MatrixState.pangeaController.userController.matrixProfile.autoPlayMessages + MatrixState.pangeaController.userController.profile.userSettings + .autoPlayMessages ? updateMode(MessageMode.textToSpeech) : updateMode(MessageMode.translation); }); diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index c758ea213..26046f8d0 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -3,7 +3,6 @@ import 'dart:developer'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/enum/span_data_type.dart'; import 'package:fluffychat/pangea/models/span_data.dart'; -import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/bot_style.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/match_copy.dart'; @@ -341,6 +340,12 @@ class WordMatchContent extends StatelessWidget { if (controller.widget.scm.pangeaMatch!.isITStart) DontShowSwitchListTile( controller: pangeaController, + onSwitch: (bool value) { + pangeaController.userController.updateProfile((profile) { + profile.userSettings.itAutoPlay = value; + return profile; + }); + }, ), ], ); @@ -483,10 +488,12 @@ class StartITButton extends StatelessWidget { class DontShowSwitchListTile extends StatefulWidget { final PangeaController controller; + final Function(bool) onSwitch; const DontShowSwitchListTile({ super.key, required this.controller, + required this.onSwitch, }); @override @@ -508,10 +515,7 @@ class DontShowSwitchListTileState extends State { title: Text(L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader), value: switchValue, onChanged: (value) { - MatrixState.pangeaController.userController.matrixProfile - .saveProfileData( - {MatrixProfileEnum.itAutoPlay.title: value}, - ); + widget.onSwitch(value); setState(() => switchValue = value); }, ); diff --git a/lib/pangea/widgets/user_settings/country_picker_tile.dart b/lib/pangea/widgets/user_settings/country_picker_tile.dart index 63677cc68..df292a837 100644 --- a/lib/pangea/widgets/user_settings/country_picker_tile.dart +++ b/lib/pangea/widgets/user_settings/country_picker_tile.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:country_picker/country_picker.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; +import 'package:fluffychat/pangea/utils/country_display.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -19,10 +20,13 @@ class CountryPickerTile extends StatelessWidget { @override Widget build(BuildContext context) { - final Profile? profile = pangeaController.userController.userModel?.profile; + final Profile profile = pangeaController.userController.profile; return ListTile( title: Text( - "${L10n.of(context)!.countryInformation}: ${profile?.countryDisplayName(context) ?? ''} ${profile?.flagEmoji}", + "${L10n.of(context)!.countryInformation}: ${CountryDisplayUtil.countryDisplayName( + profile.userSettings.country, + context, + ) ?? ''} ${CountryDisplayUtil.flagEmoji(profile.userSettings.country)}", ), trailing: const Icon(Icons.edit_outlined), onTap: () => showCountryPicker( diff --git a/lib/pangea/widgets/user_settings/p_language_dialog.dart b/lib/pangea/widgets/user_settings/p_language_dialog.dart index 8b7ae33b5..8635170a0 100644 --- a/lib/pangea/widgets/user_settings/p_language_dialog.dart +++ b/lib/pangea/widgets/user_settings/p_language_dialog.dart @@ -88,13 +88,14 @@ pLanguageDialog(BuildContext parentContext, Function callback) async { context: context, future: () async { try { - await pangeaController.userController - .updateUserProfile( - sourceLanguage: - selectedSourceLanguage.langCode, - targetLanguage: - selectedTargetLanguage.langCode, - ); + pangeaController.userController + .updateProfile((profile) { + profile.userSettings.sourceLanguage = + selectedSourceLanguage.langCode; + profile.userSettings.targetLanguage = + selectedTargetLanguage.langCode; + return profile; + }); Navigator.pop(context); } catch (err, s) { debugger(when: kDebugMode); diff --git a/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart b/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart index 3f04c61fd..2649b28e2 100644 --- a/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart +++ b/lib/pangea/widgets/user_settings/p_settings_switch_list_tile.dart @@ -1,20 +1,18 @@ import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/models/user_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; class ProfileSettingsSwitchListTile extends StatefulWidget { final bool defaultValue; - final MatrixProfileEnum profileKey; final String title; final String? subtitle; + final Function(bool) onChange; const ProfileSettingsSwitchListTile.adaptive({ super.key, - this.defaultValue = false, - required this.profileKey, + required this.defaultValue, required this.title, + required this.onChange, this.subtitle, }); @@ -28,11 +26,7 @@ class PSettingsSwitchListTileState @override void initState() { - currentValue = MatrixState.pangeaController.userController.matrixProfile - .getProfileData( - widget.profileKey, - ) ?? - widget.defaultValue; + currentValue = widget.defaultValue; super.initState(); } @@ -45,15 +39,12 @@ class PSettingsSwitchListTileState subtitle: widget.subtitle != null ? Text(widget.subtitle!) : null, onChanged: (bool newValue) async { try { - MatrixState.pangeaController.userController.matrixProfile - .saveProfileData({ - widget.profileKey.title: newValue, - }); + widget.onChange(newValue); setState(() => currentValue = newValue); } catch (err, s) { ErrorHandler.logError( e: err, - m: "Failed to updates user setting ${widget.profileKey.title}", + m: "Failed to updates user setting", s: s, ); } From 3759731d4813a0e9d07f5ed8e1068e99d784fdea Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 12 Jul 2024 14:06:21 -0400 Subject: [PATCH 33/51] code cleanup --- .../controllers/alternative_translator.dart | 14 ++------ .../widgets/it_feedback_card.dart | 4 +-- .../contextual_definition_controller.dart | 3 +- .../controllers/it_feedback_controller.dart | 4 +-- .../language_detection_controller.dart | 8 ++--- .../controllers/message_data_controller.dart | 6 ++-- .../controllers/my_analytics_controller.dart | 36 +++++++++---------- .../speech_to_text_controller.dart | 7 ++-- .../text_to_speech_controller.dart | 6 ++-- .../controllers/word_net_controller.dart | 6 ++-- .../pangea_message_event.dart | 19 +++------- lib/pangea/repo/span_data_repo.dart | 4 +-- lib/pangea/repo/topic_data_repo.dart | 2 +- .../widgets/chat/message_audio_card.dart | 1 + 14 files changed, 42 insertions(+), 78 deletions(-) diff --git a/lib/pangea/choreographer/controllers/alternative_translator.dart b/lib/pangea/choreographer/controllers/alternative_translator.dart index ccba733e2..5771d7f90 100644 --- a/lib/pangea/choreographer/controllers/alternative_translator.dart +++ b/lib/pangea/choreographer/controllers/alternative_translator.dart @@ -89,19 +89,9 @@ class AlternativeTranslator { final String? goldRouteTranslation = choreographer.itController.goldRouteTracker.fullTranslation; - final accessToken = await choreographer.accessToken; - if (accessToken == null) { - ErrorHandler.logError( - m: "accessToken null in setTranslationFeedback", - s: StackTrace.current, - ); - translationFeedbackKey = FeedbackKey.loadingPleaseWait; - return; - } - final FullTextTranslationResponseModel results = await FullTextTranslationRepo.translate( - accessToken: accessToken, + accessToken: await choreographer.accessToken, request: FullTextTranslationRequestModel( text: choreographer.itController.sourceText!, tgtLang: choreographer.l2LangCode!, @@ -127,7 +117,7 @@ class AlternativeTranslator { } similarityResponse = await SimilarityRepo.get( - accessToken: accessToken, + accessToken: await choreographer.accessToken, request: SimilarityRequestModel( benchmark: results.bestTranslation, toCompare: [userTranslation!], diff --git a/lib/pangea/choreographer/widgets/it_feedback_card.dart b/lib/pangea/choreographer/widgets/it_feedback_card.dart index 4285f4dca..9cada0548 100644 --- a/lib/pangea/choreographer/widgets/it_feedback_card.dart +++ b/lib/pangea/choreographer/widgets/it_feedback_card.dart @@ -72,10 +72,8 @@ class ITFeedbackCardController extends State { setState(() { isTranslating = true; }); - - final String accessToken = await controller.userController.accessToken; FullTextTranslationRepo.translate( - accessToken: accessToken, + accessToken: await controller.userController.accessToken, request: FullTextTranslationRequestModel( text: res!.text, tgtLang: controller.languageController.userL1?.langCode ?? diff --git a/lib/pangea/controllers/contextual_definition_controller.dart b/lib/pangea/controllers/contextual_definition_controller.dart index 68e075d97..d423c9b38 100644 --- a/lib/pangea/controllers/contextual_definition_controller.dart +++ b/lib/pangea/controllers/contextual_definition_controller.dart @@ -49,10 +49,9 @@ class ContextualDefinitionController { ContextualDefinitionRequestModel request, ) async { try { - final accessToken = await _pangeaController.userController.accessToken; final ContextualDefinitionResponseModel res = await _ContextualDefinitionRepo.define( - accessToken, + await _pangeaController.userController.accessToken, request, ); return res; diff --git a/lib/pangea/controllers/it_feedback_controller.dart b/lib/pangea/controllers/it_feedback_controller.dart index 74aee21de..ffef9123f 100644 --- a/lib/pangea/controllers/it_feedback_controller.dart +++ b/lib/pangea/controllers/it_feedback_controller.dart @@ -51,10 +51,8 @@ class ITFeedbackController { ITFeedbackRequestModel request, ) async { try { - final String accessToken = - await _pangeaController.userController.accessToken; final ITFeedbackResponseModel res = await _ITFeedbackRepo.get( - accessToken, + await _pangeaController.userController.accessToken, request, ); return res; diff --git a/lib/pangea/controllers/language_detection_controller.dart b/lib/pangea/controllers/language_detection_controller.dart index ecbdea47d..a3e07b0a3 100644 --- a/lib/pangea/controllers/language_detection_controller.dart +++ b/lib/pangea/controllers/language_detection_controller.dart @@ -125,7 +125,7 @@ class LanguageDetectionController { _cacheClearTimer?.cancel(); } - Future detectLanguage( + Future detectLanguage( String fullText, String? userL2, String? userL1, @@ -138,16 +138,14 @@ class LanguageDetectionController { return get(params); } - Future get( + Future get( LanguageDetectionRequest params, ) async { if (_cache.containsKey(params)) { return _cache[params]!.data; } else { - final String accessToken = - await _pangeaController.userController.accessToken; final Future response = _fetchResponse( - accessToken, + await _pangeaController.userController.accessToken, params, ); _cache[params] = _LanguageDetectionCacheItem(data: response); diff --git a/lib/pangea/controllers/message_data_controller.dart b/lib/pangea/controllers/message_data_controller.dart index aa0c77b9d..7b3a3e6d2 100644 --- a/lib/pangea/controllers/message_data_controller.dart +++ b/lib/pangea/controllers/message_data_controller.dart @@ -43,6 +43,7 @@ class MessageDataController extends BaseController { TokensRequestModel req, ) async { final accessToken = await _pangeaController.userController.accessToken; + final TokensResponseModel igcTextData = await TokensRepo.tokenize(accessToken, req); @@ -192,12 +193,9 @@ class MessageDataController extends BaseController { ); try { - final String accessToken = - await _pangeaController.userController.accessToken; - final FullTextTranslationResponseModel res = await FullTextTranslationRepo.translate( - accessToken: accessToken, + accessToken: await _pangeaController.userController.accessToken, request: req, ); diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 8fa8989b1..058bad359 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -204,24 +204,24 @@ class MyAnalyticsController { Completer? _updateCompleter; Future updateAnalytics() async { - // if (!(_updateCompleter?.isCompleted ?? true)) { - // await _updateCompleter!.future; - // return; - // } - // _updateCompleter = Completer(); - // try { - // await _updateAnalytics(); - // clearMessagesSinceUpdate(); - // } catch (err, s) { - // ErrorHandler.logError( - // e: err, - // m: "Failed to update analytics", - // s: s, - // ); - // } finally { - // _updateCompleter?.complete(); - // _updateCompleter = null; - // } + if (!(_updateCompleter?.isCompleted ?? true)) { + await _updateCompleter!.future; + return; + } + _updateCompleter = Completer(); + try { + await _updateAnalytics(); + clearMessagesSinceUpdate(); + } catch (err, s) { + ErrorHandler.logError( + e: err, + m: "Failed to update analytics", + s: s, + ); + } finally { + _updateCompleter?.complete(); + _updateCompleter = null; + } } String? get userL2 => _pangeaController.languageController.activeL2Code(); diff --git a/lib/pangea/controllers/speech_to_text_controller.dart b/lib/pangea/controllers/speech_to_text_controller.dart index 1b151aca0..67462bcef 100644 --- a/lib/pangea/controllers/speech_to_text_controller.dart +++ b/lib/pangea/controllers/speech_to_text_controller.dart @@ -44,7 +44,7 @@ class SpeechToTextController { _cacheClearTimer?.cancel(); } - Future get( + Future get( SpeechToTextRequestModel requestModel, ) async { final int cacheKey = requestModel.hashCode; @@ -52,11 +52,8 @@ class SpeechToTextController { if (_cache.containsKey(cacheKey)) { return _cache[cacheKey]!.data; } else { - final String accessToken = - await _pangeaController.userController.accessToken; - final Future response = _fetchResponse( - accessToken: accessToken, + accessToken: await _pangeaController.userController.accessToken, requestModel: requestModel, ); _cache[cacheKey] = _SpeechToTextCacheItem(data: response); diff --git a/lib/pangea/controllers/text_to_speech_controller.dart b/lib/pangea/controllers/text_to_speech_controller.dart index 25b888e05..b34092618 100644 --- a/lib/pangea/controllers/text_to_speech_controller.dart +++ b/lib/pangea/controllers/text_to_speech_controller.dart @@ -93,16 +93,14 @@ class TextToSpeechController { _cacheClearTimer?.cancel(); } - Future get( + Future get( TextToSpeechRequest params, ) async { if (_cache.containsKey(params)) { return _cache[params]!.data; } else { - final String accessToken = - await _pangeaController.userController.accessToken; final Future response = _fetchResponse( - accessToken, + await _pangeaController.userController.accessToken, params, ); _cache[params] = _TextToSpeechCacheItem(data: response); diff --git a/lib/pangea/controllers/word_net_controller.dart b/lib/pangea/controllers/word_net_controller.dart index 18c3290a9..b7a8d287e 100644 --- a/lib/pangea/controllers/word_net_controller.dart +++ b/lib/pangea/controllers/word_net_controller.dart @@ -31,7 +31,7 @@ class WordController extends BaseController { ), ); - Future getWordDataGlobal({ + Future getWordDataGlobal({ required String word, required String fullText, required String? userL1, @@ -53,10 +53,8 @@ class WordController extends BaseController { if (local != null) return local; - final String accessToken = - await _pangeaController.userController.accessToken; final WordData remote = await WordRepo.getWordNetData( - accessToken: accessToken, + accessToken: await _pangeaController.userController.accessToken, fullText: fullText, word: word, userL1: userL1, diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index 06f34cf1f..334c0fa78 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -88,7 +88,7 @@ class PangeaMessageEvent { return _latestEdit; } - Future getMatrixAudioFile( + Future getMatrixAudioFile( String langCode, BuildContext context, ) async { @@ -102,15 +102,11 @@ class PangeaMessageEvent { langCode: langCode, ); - final TextToSpeechResponse? response = + final TextToSpeechResponse response = await MatrixState.pangeaController.textToSpeech.get( params, ); - if (response == null) { - return null; - } - final audioBytes = base64.decode(response.audioContent); final eventIdParam = _event.eventId; final fileName = @@ -181,13 +177,10 @@ class PangeaMessageEvent { langCode: langCode, ); - final TextToSpeechResponse? response = + final TextToSpeechResponse response = await MatrixState.pangeaController.textToSpeech.get( params, ); - if (response == null) { - return null; - } final audioBytes = base64.decode(response.audioContent); @@ -330,7 +323,7 @@ class PangeaMessageEvent { debugPrint("mimeType ${matrixFile.mimeType}"); debugPrint("encoding ${mimeTypeToAudioEncoding(matrixFile.mimeType)}"); - final SpeechToTextModel? response = + final SpeechToTextModel response = await MatrixState.pangeaController.speechToText.get( SpeechToTextRequestModel( audioContent: matrixFile.bytes, @@ -346,10 +339,6 @@ class PangeaMessageEvent { ), ); - if (response == null) { - return null; - } - _representations?.add( RepresentationEvent( timeline: timeline, diff --git a/lib/pangea/repo/span_data_repo.dart b/lib/pangea/repo/span_data_repo.dart index cc5c0640a..7073581a1 100644 --- a/lib/pangea/repo/span_data_repo.dart +++ b/lib/pangea/repo/span_data_repo.dart @@ -13,7 +13,7 @@ import '../network/urls.dart'; class SpanDataRepo { static Future getSpanDetails( - String accessToken, { + String? accessToken, { required SpanDetailsRepoReqAndRes request, }) async { final Requests req = Requests( @@ -150,4 +150,4 @@ SpanDetailsRepoReqAndRes get mockReponseWithChoices { // res.span.choices![1].selected = true; // res.span.message = "Conjugation error"; // return res; -// } +// } \ No newline at end of file diff --git a/lib/pangea/repo/topic_data_repo.dart b/lib/pangea/repo/topic_data_repo.dart index 7a4ce049f..06abf6929 100644 --- a/lib/pangea/repo/topic_data_repo.dart +++ b/lib/pangea/repo/topic_data_repo.dart @@ -11,7 +11,7 @@ import '../network/urls.dart'; /// accepts ChatTopic and calls an API for a list of Lemma class TopicDataRepo { static Future generate( - String accessToken, { + String? accessToken, { required TopicDataRequest request, }) async { final Requests req = Requests( diff --git a/lib/pangea/widgets/chat/message_audio_card.dart b/lib/pangea/widgets/chat/message_audio_card.dart index cfae4282a..5c1f8e67b 100644 --- a/lib/pangea/widgets/chat/message_audio_card.dart +++ b/lib/pangea/widgets/chat/message_audio_card.dart @@ -46,6 +46,7 @@ class MessageAudioCardState extends State { await widget.messageEvent.getMatrixAudioFile(langCode, context); if (mounted) setState(() => _isLoading = false); } catch (e, _) { + debugPrint(StackTrace.current.toString()); if (!mounted) return; setState(() => _isLoading = false); ScaffoldMessenger.of(context).showSnackBar( From 8acc793f0c6d9913f5512cdec38f1672aab53f19 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 12 Jul 2024 14:26:44 -0400 Subject: [PATCH 34/51] added documentation to user controller and user model --- lib/pangea/controllers/user_controller.dart | 27 ++++++++++++------- .../client_extension/client_extension.dart | 2 ++ .../general_info_extension.dart | 7 +++++ lib/pangea/models/user_model.dart | 9 +++++++ 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index a8c6ced14..2ef0227e5 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -4,6 +4,7 @@ import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:jwt_decode/jwt_decode.dart'; import 'package:matrix/matrix.dart' as matrix; @@ -26,11 +27,16 @@ class UserController extends BaseController { String? get _matrixAccessToken => _pangeaController.matrixState.client.accessToken; + /// Cached version of the user profile, so it doesn't have + /// to be read in from client's account data each time it is accessed. Profile? _cachedProfile; + /// Listens for account updates and updates the cached profile + StreamSubscription? _profileListener; + /// Listen for updates to account data in syncs and update the cached profile void addProfileListener() { - _pangeaController.matrixState.client.onSync.stream + _profileListener ??= _pangeaController.matrixState.client.onSync.stream .where((sync) => sync.accountData != null) .listen((sync) { final Profile? fromAccountData = Profile.fromAccountData(); @@ -64,11 +70,13 @@ class UserController extends BaseController { return _cachedProfile ?? Profile.emptyProfile; } + /// Updates the user's profile with the given [update] function and saves it. void updateProfile(Profile Function(Profile) update) { final Profile updatedProfile = update(profile); updatedProfile.saveProfileData(); } + /// Creates a new profile for the user with the given date of birth. Future createProfile({required DateTime dob}) async { final userSettings = UserSettings( dateOfBirth: dob, @@ -81,6 +89,9 @@ class UserController extends BaseController { /// A completer for the profile model of a user. Completer? _profileCompleter; + /// Initializes the user's profile. Runs a function to wait for account data to load, + /// read account data into profile, and migrate any missing info from the pangea profile. + /// Finally, it adds a listen to update the profile data when new account data comes in. Future initialize() async { if (_profileCompleter?.isCompleted ?? false) { return _profileCompleter!.future; @@ -105,11 +116,14 @@ class UserController extends BaseController { return _profileCompleter!.future; } + /// Initializes the user's profile by waiting for account data to load, reading in account + /// data to profile, and migrating from the pangea profile if the account data is not present. Future _initialize() async { - await waitForAccountData(); + await _pangeaController.matrixState.client.waitForAccountData(); if (profile.userSettings.dateOfBirth != null) { return; } + final PangeaProfileResponse? resp = await PUserRepo.fetchPangeaUserInfo( userID: userId!, matrixAccessToken: _matrixAccessToken!, @@ -117,6 +131,7 @@ class UserController extends BaseController { if (resp?.profile == null) { return; } + final userSetting = UserSettings.fromJson(resp!.profile.toJson()); final newProfile = Profile(userSettings: userSetting); await newProfile.saveProfileData(waitForDataInSync: true); @@ -130,14 +145,6 @@ class UserController extends BaseController { await initialize(); } - /// Account data comes through in the first sync, so wait for that - Future waitForAccountData() async { - final client = _pangeaController.matrixState.client; - if (client.prevBatch == null) { - await client.onSync.stream.first; - } - } - /// Returns a boolean value indicating whether a new JWT (JSON Web Token) is needed. bool needNewJWT(String token) => Jwt.isExpired(token); diff --git a/lib/pangea/extensions/client_extension/client_extension.dart b/lib/pangea/extensions/client_extension/client_extension.dart index addcfd0ed..bef384f6a 100644 --- a/lib/pangea/extensions/client_extension/client_extension.dart +++ b/lib/pangea/extensions/client_extension/client_extension.dart @@ -80,4 +80,6 @@ extension PangeaClient on Client { String? powerLevelName(int powerLevel, L10n l10n) => _powerLevelName(powerLevel, l10n); + + Future waitForAccountData() async => await _waitForAccountData(); } diff --git a/lib/pangea/extensions/client_extension/general_info_extension.dart b/lib/pangea/extensions/client_extension/general_info_extension.dart index aada940d7..ca5df40cc 100644 --- a/lib/pangea/extensions/client_extension/general_info_extension.dart +++ b/lib/pangea/extensions/client_extension/general_info_extension.dart @@ -78,4 +78,11 @@ extension GeneralInfoClientExtension on Client { 50: l10n.moderator, 100: l10n.admin, }[powerLevel]; + + /// Account data comes through in the first sync, so wait for that + Future _waitForAccountData() async { + if (prevBatch == null) { + await onSync.stream.first; + } + } } diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index 51aa746d9..972d7bbba 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -7,6 +7,7 @@ import 'package:matrix/matrix.dart'; import 'language_model.dart'; +/// The user's settings learning settings. class UserSettings { DateTime? dateOfBirth; DateTime? createdAt; @@ -114,6 +115,7 @@ class UserSettings { } } +/// The user's language tool settings. class UserToolSettings { bool interactiveTranslator; bool interactiveGrammar; @@ -177,6 +179,7 @@ class UserToolSettings { } } +/// The user's settings for whether or not to show instuction messages. class UserInstructions { bool showedItInstructions; bool showedClickMessage; @@ -255,6 +258,7 @@ class Profile { this.instructionSettings = instructionSettings ?? UserInstructions(); } + /// Load an instance of profile from the client's account data. static Profile? fromAccountData() { final profileData = MatrixState.pangeaController.matrixState.client .accountData[ModelKey.userProfile]?.content; @@ -292,6 +296,8 @@ class Profile { return json; } + /// Migrate data from the old matrix account data + /// format to the new matrix account data format. static Profile? migrateFromAccountData() { final userSettings = UserSettings.migrateFromAccountData(); if (userSettings == null) return null; @@ -305,6 +311,9 @@ class Profile { ); } + /// Saves the current configuration of the profile to the client's account data. + /// If [waitForDataInSync] is true, the function will wait for the updated account + /// data to come through in a sync, indicating that it has been set on the matrix server. Future saveProfileData({ waitForDataInSync = false, }) async { From 0ac4b664cee36b701f2b469d3d67cda908a5aef3 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 12 Jul 2024 17:03:00 -0400 Subject: [PATCH 35/51] changed from string to Map --- lib/pangea/models/pangea_token_model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/models/pangea_token_model.dart b/lib/pangea/models/pangea_token_model.dart index 608d5b9d1..ba0772740 100644 --- a/lib/pangea/models/pangea_token_model.dart +++ b/lib/pangea/models/pangea_token_model.dart @@ -53,7 +53,7 @@ class PangeaToken { text: text, lemma: _getLemmas(text.content, json[_lemmaKey]), pos: json['pos'] ?? '', - morph: json['morph'] ?? '{}', + morph: json['morph'] ?? {}, ); } From 7985743382467843ff604ad46fbd5655f57f28b6 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 15 Jul 2024 10:02:05 -0400 Subject: [PATCH 36/51] Fix loading issue when space is selected? --- lib/pages/chat_list/space_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index f3f9749a1..cf5913a25 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -126,7 +126,7 @@ class _SpaceViewState extends State { try { final response = await client.getSpaceHierarchy( activeSpaceId, - maxDepth: 1, + maxDepth: 2, from: prevBatch, ); From d15d149b87e991333736f7b5af9ac551f03a2677 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 15 Jul 2024 11:19:19 -0400 Subject: [PATCH 37/51] removed calls to unsafeGetUserFromMemoryOrFallback that was causing stack overflow error --- .../invitation_selection.dart | 24 +++++++++++++++---- .../utils/get_chat_list_item_subtitle.dart | 12 ++++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/pages/invitation_selection/invitation_selection.dart b/lib/pages/invitation_selection/invitation_selection.dart index 5b69e7049..e59e03819 100644 --- a/lib/pages/invitation_selection/invitation_selection.dart +++ b/lib/pages/invitation_selection/invitation_selection.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:collection/collection.dart'; import 'package:fluffychat/pages/invitation_selection/invitation_selection_view.dart'; import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; @@ -48,16 +49,29 @@ class InvitationSelectionController extends State { ); final contacts = client.rooms .where((r) => r.isDirectChat) - .map((r) => r.unsafeGetUserFromMemoryOrFallback(r.directChatMatrixID!)) + // #Pangea + // .map((r) => r.unsafeGetUserFromMemoryOrFallback(r.directChatMatrixID!)) + .map( + (r) => r + .getParticipants() + .firstWhereOrNull((u) => u.id != client.userID), + ) + // Pangea# .toList(); + // #Pangea + contacts.removeWhere((u) => u == null || u.id != BotName.byEnvironment); contacts.sort( - (a, b) => a.calcDisplayname().toLowerCase().compareTo( - b.calcDisplayname().toLowerCase(), + (a, b) => a!.calcDisplayname().toLowerCase().compareTo( + b!.calcDisplayname().toLowerCase(), ), ); - //#Pangea + return contacts.cast(); + // contacts.sort( + // (a, b) => a.calcDisplayname().toLowerCase().compareTo( + // b.calcDisplayname().toLowerCase(), + // ), + // ); // return contacts; - return contacts.where((u) => u.id != BotName.byEnvironment).toList(); //Pangea# } diff --git a/lib/pangea/utils/get_chat_list_item_subtitle.dart b/lib/pangea/utils/get_chat_list_item_subtitle.dart index bd8d064f5..76bd453b6 100644 --- a/lib/pangea/utils/get_chat_list_item_subtitle.dart +++ b/lib/pangea/utils/get_chat_list_item_subtitle.dart @@ -82,15 +82,19 @@ class GetChatListItemSubtitle { final i18n = MatrixLocals(l10n); - if (text == null) return l10n.emptyChat; + if (text == null || event.room.lastEvent == null) { + return l10n.emptyChat; + } if (!event.room.isDirectChat || - event.room.directChatMatrixID != event.room.lastEvent?.senderId) { + event.room.directChatMatrixID != event.room.lastEvent!.senderId) { final senderNameOrYou = event.senderId == event.room.client.userID ? i18n.you : event.room - .unsafeGetUserFromMemoryOrFallback(event.senderId) - .calcDisplayname(i18n: i18n); + .getParticipants() + .firstWhereOrNull((u) => u.id != event!.room.client.userID) + ?.calcDisplayname(i18n: i18n) ?? + event.room.lastEvent!.senderId; return "$senderNameOrYou: $text"; } From c0cd10a0e252c9be3c3fa7b9766b64d7afb469ac Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 15 Jul 2024 13:58:19 -0400 Subject: [PATCH 38/51] filter out analytics events from syncs. hide space analytics --- lib/config/routes.dart | 45 +++++++++---------- lib/pages/chat_details/chat_details_view.dart | 22 --------- .../chat_list/client_chooser_button.dart | 39 ++++++++-------- lib/utils/client_manager.dart | 13 ++++++ .../filtered_timeline_extension.dart | 1 + 5 files changed, 54 insertions(+), 66 deletions(-) diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 51c631ff3..d9c7a70f7 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -42,9 +42,6 @@ import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import '../pangea/pages/analytics/space_analytics/space_analytics.dart'; -import '../pangea/pages/analytics/space_list/space_list.dart'; - abstract class AppRoutes { static FutureOr loggedInRedirect( BuildContext context, @@ -176,27 +173,27 @@ abstract class AppRoutes { ), redirect: loggedOutRedirect, ), - GoRoute( - path: 'analytics', - pageBuilder: (context, state) => defaultPageBuilder( - context, - state, - const AnalyticsSpaceList(), - ), - redirect: loggedOutRedirect, - routes: [ - GoRoute( - path: ':spaceid', - pageBuilder: (context, state) => defaultPageBuilder( - context, - state, - const SpaceAnalyticsPage( - selectedView: BarChartViewSelection.messages, - ), - ), - ), - ], - ), + // GoRoute( + // path: 'analytics', + // pageBuilder: (context, state) => defaultPageBuilder( + // context, + // state, + // const AnalyticsSpaceList(), + // ), + // redirect: loggedOutRedirect, + // routes: [ + // GoRoute( + // path: ':spaceid', + // pageBuilder: (context, state) => defaultPageBuilder( + // context, + // state, + // const SpaceAnalyticsPage( + // selectedView: BarChartViewSelection.messages, + // ), + // ), + // ), + // ], + // ), // Pangea# GoRoute( path: 'archive', diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 4f4e091d0..a51304b39 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -263,28 +263,6 @@ class ChatDetailsView extends StatelessWidget { controller: controller, ), // Pangea# - if (room.isSpace && room.isRoomAdmin) - ListTile( - title: Text( - L10n.of(context)!.spaceAnalytics, - style: TextStyle( - color: - Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - leading: CircleAvatar( - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, - foregroundColor: iconColor, - child: const Icon( - Icons.analytics_outlined, - ), - ), - onTap: () => context.go( - '/rooms/analytics/${room.id}', - ), - ), // commenting out language settings in spaces for now // if (room.languageSettings != null && room.isRoomAdmin) // LanguageSettings( diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 4ad107f6b..567b8a5db 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -1,5 +1,4 @@ import 'package:adaptive_dialog/adaptive_dialog.dart'; -import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/utils/find_conversation_partner_dialog.dart'; import 'package:fluffychat/pangea/utils/logout.dart'; @@ -53,21 +52,21 @@ class ClientChooserButton extends StatelessWidget { ], ), ), - PopupMenuItem( - enabled: matrix.client.rooms.any( - (room) => - room.isSpace && - room.ownPowerLevel >= ClassDefaultValues.powerLevelOfAdmin, - ), - value: SettingsAction.spaceAnalytics, - child: Row( - children: [ - const Icon(Icons.analytics_outlined), - const SizedBox(width: 18), - Expanded(child: Text(L10n.of(context)!.spaceAnalytics)), - ], - ), - ), + // PopupMenuItem( + // enabled: matrix.client.rooms.any( + // (room) => + // room.isSpace && + // room.ownPowerLevel >= ClassDefaultValues.powerLevelOfAdmin, + // ), + // value: SettingsAction.spaceAnalytics, + // child: Row( + // children: [ + // const Icon(Icons.analytics_outlined), + // const SizedBox(width: 18), + // Expanded(child: Text(L10n.of(context)!.spaceAnalytics)), + // ], + // ), + // ), PopupMenuItem( enabled: matrix.client.rooms.any( (room) => !room.isSpace && !room.isArchived && !room.isAnalyticsRoom, @@ -402,9 +401,9 @@ class ClientChooserButton extends StatelessWidget { controller.pangeaController, ); break; - case SettingsAction.spaceAnalytics: - context.go('/rooms/analytics'); - break; + // case SettingsAction.spaceAnalytics: + // context.go('/rooms/analytics'); + // break; case SettingsAction.myAnalytics: context.go('/rooms/mylearning'); break; @@ -497,7 +496,7 @@ enum SettingsAction { // #Pangea learning, joinWithClassCode, - spaceAnalytics, + // spaceAnalytics, myAnalytics, findAConversationPartner, logout, diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 1d5e1fe66..6058eecf9 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -132,6 +132,19 @@ abstract class ClientManager { customImageResizer: PlatformInfos.isMobile ? customImageResizer : null, defaultNetworkRequestTimeout: const Duration(minutes: 30), enableDehydratedDevices: true, + // #Pangea + syncFilter: Filter( + room: RoomFilter( + state: StateFilter(lazyLoadMembers: true), + timeline: StateFilter( + notTypes: [ + PangeaEventTypes.construct, + PangeaEventTypes.summaryAnalytics, + ], + ), + ), + ), + // Pangea# ); } diff --git a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart index 0ee52ceae..612f18f57 100644 --- a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart +++ b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart @@ -40,6 +40,7 @@ extension IsStateExtension on Event { }.contains(type); // #Pangea + // we're filtering out some state events that we don't want to render static const Set importantStateEvents = { EventTypes.Encryption, EventTypes.RoomCreate, From e6fa69df3910d9f77c671a4159e5dc753ed3634e Mon Sep 17 00:00:00 2001 From: bluearevalo <90929912+bluearevalo@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:28:16 -0400 Subject: [PATCH 39/51] add back #pangea comment tags --- lib/pages/chat/input_bar.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index 04d1be9db..9337b488a 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -490,10 +490,13 @@ class InputBar extends StatelessWidget { keyboardType: keyboardType!, textInputAction: textInputAction, autofocus: autofocus!, - //setting max character count to 1000 - //after max, nothing else can be typed inputFormatters: [ + //#Pangea + //LengthLimitingTextInputFormatter((maxPDUSize / 3).floor()), + //setting max character count to 1000 + //after max, nothing else can be typed LengthLimitingTextInputFormatter(1000), + //Pangea# ], onSubmitted: (text) { // fix for library for now From c4ef72857888adc4842d2b09d6123e31f4c484c4 Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Tue, 16 Jul 2024 09:06:58 -0400 Subject: [PATCH 40/51] client will consume lemma as single object but be capable of handling json lemma as list --- lib/pangea/models/pangea_token_model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/models/pangea_token_model.dart b/lib/pangea/models/pangea_token_model.dart index ba0772740..089521e1a 100644 --- a/lib/pangea/models/pangea_token_model.dart +++ b/lib/pangea/models/pangea_token_model.dart @@ -62,7 +62,7 @@ class PangeaToken { Map toJson() => { _textKey: text.toJson(), - _lemmaKey: lemma.toJson(), + _lemmaKey: [lemma.toJson()], 'pos': pos, 'morph': morph, }; From 7e87c3154c3d1943a1cd59f0fddcea9ea174e8df Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 16 Jul 2024 09:38:54 -0400 Subject: [PATCH 41/51] fix for orange box error in learning settings, updated instructions to base their toggle status on matrix profile --- lib/pangea/enum/instructions_enum.dart | 18 +- .../settings_learning/settings_learning.dart | 25 +-- .../settings_learning_view.dart | 159 ++++++++---------- lib/pangea/utils/instructions.dart | 37 ++-- .../user_settings/country_picker_tile.dart | 36 ++-- .../user_settings/p_language_dialog.dart | 5 +- 6 files changed, 137 insertions(+), 143 deletions(-) diff --git a/lib/pangea/enum/instructions_enum.dart b/lib/pangea/enum/instructions_enum.dart index 376d004b5..4e12c12b8 100644 --- a/lib/pangea/enum/instructions_enum.dart +++ b/lib/pangea/enum/instructions_enum.dart @@ -1,4 +1,5 @@ import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -9,7 +10,7 @@ enum InstructionsEnum { tooltipInstructions, } -extension Copy on InstructionsEnum { +extension InstructionsEnumExtension on InstructionsEnum { String title(BuildContext context) { switch (this) { case InstructionsEnum.itInstructions: @@ -37,6 +38,21 @@ extension Copy on InstructionsEnum { : L10n.of(context)!.tooltipInstructionsBrowserBody; } } + + bool get toggledOff { + final instructionSettings = + MatrixState.pangeaController.userController.profile.instructionSettings; + switch (this) { + case InstructionsEnum.itInstructions: + return instructionSettings.showedItInstructions; + case InstructionsEnum.clickMessage: + return instructionSettings.showedClickMessage; + case InstructionsEnum.blurMeansTranslate: + return instructionSettings.showedBlurMeansTranslate; + case InstructionsEnum.tooltipInstructions: + return instructionSettings.showedTooltipInstructions; + } + } } enum InlineInstructions { diff --git a/lib/pangea/pages/settings_learning/settings_learning.dart b/lib/pangea/pages/settings_learning/settings_learning.dart index 03e5f635a..3e0a11e4c 100644 --- a/lib/pangea/pages/settings_learning/settings_learning.dart +++ b/lib/pangea/pages/settings_learning/settings_learning.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:country_picker/country_picker.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; @@ -17,25 +15,26 @@ class SettingsLearning extends StatefulWidget { } class SettingsLearningController extends State { - late StreamSubscription _userSubscription; PangeaController pangeaController = MatrixState.pangeaController; - Future changeLanguage() async { - await pLanguageDialog(context, () {}); - } - - Future setPublicProfile(bool isPublic) async { + setPublicProfile(bool isPublic) { pangeaController.userController.updateProfile((profile) { profile.userSettings.publicProfile = isPublic; return profile; }); + setState(() {}); } - Future changeCountry(Country country) async { - pangeaController.userController.updateProfile((profile) { + void changeLanguage() { + pLanguageDialog(context, () {}).then((_) => setState(() {})); + } + + void changeCountry(Country country) { + pangeaController.userController.updateProfile((Profile profile) { profile.userSettings.country = country.displayNameNoCountryCode; return profile; }); + setState(() {}); } void updateToolSetting(ToolSetting toolSetting, bool value) { @@ -71,12 +70,6 @@ class SettingsLearningController extends State { } } - @override - void dispose() { - super.dispose(); - _userSubscription.cancel(); - } - @override Widget build(BuildContext context) { return SettingsLearningView(this); diff --git a/lib/pangea/pages/settings_learning/settings_learning_view.dart b/lib/pangea/pages/settings_learning/settings_learning_view.dart index 5422ebbe5..4182ab2d6 100644 --- a/lib/pangea/pages/settings_learning/settings_learning_view.dart +++ b/lib/pangea/pages/settings_learning/settings_learning_view.dart @@ -1,15 +1,12 @@ +import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/widgets/user_settings/country_picker_tile.dart'; import 'package:fluffychat/pangea/widgets/user_settings/language_tile.dart'; import 'package:fluffychat/pangea/widgets/user_settings/p_settings_switch_list_tile.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:future_loading_dialog/future_loading_dialog.dart'; - -import '../../../config/app_config.dart'; class SettingsLearningView extends StatelessWidget { final SettingsLearningController controller; @@ -17,97 +14,75 @@ class SettingsLearningView extends StatelessWidget { @override Widget build(BuildContext context) { - // rebuild this page each time a sync comes through with new account data - // this prevents having to call setState each time an individual setting is changed - return StreamBuilder( - stream: - controller.pangeaController.matrixState.client.onSync.stream.where( - (update) => update.accountData != null, + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + L10n.of(context)!.learningSettings, + ), ), - builder: (context, snapshot) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: Text( - L10n.of(context)!.learningSettings, - ), - ), - body: ListTileTheme( - iconColor: Theme.of(context).textTheme.bodyLarge!.color, - child: MaxWidthBody( - withScrolling: true, - child: Column( - children: [ - LanguageTile(controller), - CountryPickerTile(controller), - const SizedBox(height: 8), - const Divider(height: 1), - const SizedBox(height: 8), - if (controller.pangeaController.permissionsController - .isUser18()) - SwitchListTile.adaptive( - activeColor: AppConfig.activeToggleColor, - title: Text(L10n.of(context)!.publicProfileTitle), - subtitle: Text(L10n.of(context)!.publicProfileDesc), - value: - controller.pangeaController.userController.isPublic, - onChanged: (bool isPublicProfile) => - showFutureLoadingDialog( - context: context, - future: () => - controller.setPublicProfile(isPublicProfile), - onError: (err) => ErrorHandler.logError( - e: err, - s: StackTrace.current, - ), - ), - ), - ListTile( - subtitle: - Text(L10n.of(context)!.toggleToolSettingsDescription), - ), - for (final toolSetting in ToolSetting.values) - ProfileSettingsSwitchListTile.adaptive( - defaultValue: controller.getToolSetting(toolSetting), - title: toolSetting.toolName(context), - subtitle: toolSetting.toolDescription(context), - onChange: (bool value) => controller.updateToolSetting( - toolSetting, - value, - ), - ), - ProfileSettingsSwitchListTile.adaptive( - defaultValue: controller.pangeaController.userController - .profile.userSettings.itAutoPlay, - title: L10n.of(context)! - .interactiveTranslatorAutoPlaySliderHeader, - subtitle: - L10n.of(context)!.interactiveTranslatorAutoPlayDesc, - onChange: (bool value) => controller - .pangeaController.userController - .updateProfile((profile) { - profile.userSettings.itAutoPlay = value; - return profile; - }), - ), - ProfileSettingsSwitchListTile.adaptive( - defaultValue: controller.pangeaController.userController - .profile.userSettings.autoPlayMessages, - title: L10n.of(context)!.autoPlayTitle, - subtitle: L10n.of(context)!.autoPlayDesc, - onChange: (bool value) => controller - .pangeaController.userController - .updateProfile((profile) { - profile.userSettings.autoPlayMessages = value; - return profile; - }), - ), - ], + body: ListTileTheme( + iconColor: Theme.of(context).textTheme.bodyLarge!.color, + child: MaxWidthBody( + withScrolling: true, + child: Column( + children: [ + LanguageTile(controller), + CountryPickerTile(controller), + const SizedBox(height: 8), + const Divider(height: 1), + const SizedBox(height: 8), + if (controller.pangeaController.permissionsController.isUser18()) + SwitchListTile.adaptive( + activeColor: AppConfig.activeToggleColor, + title: Text(L10n.of(context)!.publicProfileTitle), + subtitle: Text(L10n.of(context)!.publicProfileDesc), + value: controller.pangeaController.userController.isPublic, + onChanged: (bool isPublicProfile) => + controller.setPublicProfile(isPublicProfile), + ), + ListTile( + subtitle: Text(L10n.of(context)!.toggleToolSettingsDescription), ), - ), + for (final toolSetting in ToolSetting.values) + ProfileSettingsSwitchListTile.adaptive( + defaultValue: controller.getToolSetting(toolSetting), + title: toolSetting.toolName(context), + subtitle: toolSetting.toolDescription(context), + onChange: (bool value) => controller.updateToolSetting( + toolSetting, + value, + ), + ), + ProfileSettingsSwitchListTile.adaptive( + defaultValue: controller.pangeaController.userController.profile + .userSettings.itAutoPlay, + title: + L10n.of(context)!.interactiveTranslatorAutoPlaySliderHeader, + subtitle: L10n.of(context)!.interactiveTranslatorAutoPlayDesc, + onChange: (bool value) => controller + .pangeaController.userController + .updateProfile((profile) { + profile.userSettings.itAutoPlay = value; + return profile; + }), + ), + ProfileSettingsSwitchListTile.adaptive( + defaultValue: controller.pangeaController.userController.profile + .userSettings.autoPlayMessages, + title: L10n.of(context)!.autoPlayTitle, + subtitle: L10n.of(context)!.autoPlayDesc, + onChange: (bool value) => controller + .pangeaController.userController + .updateProfile((profile) { + profile.userSettings.autoPlayMessages = value; + return profile; + }), + ), + ], ), - ); - }, + ), + ), ); } } diff --git a/lib/pangea/utils/instructions.dart b/lib/pangea/utils/instructions.dart index 948d6a1e4..165bfa460 100644 --- a/lib/pangea/utils/instructions.dart +++ b/lib/pangea/utils/instructions.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/utils/inline_tooltip.dart'; import 'package:flutter/material.dart'; @@ -24,8 +25,9 @@ class InstructionsController { final Map _instructionsShown = {}; /// Returns true if the user requested this popup not be shown again - bool? toggledOff(String key) => - _pangeaController.pStoreService.read(key.toString()); + bool? toggledOff(String key) => InstructionsEnum.values + .firstWhereOrNull((value) => value.toString() == key) + ?.toggledOff; InstructionsController(PangeaController pangeaController) { _pangeaController = pangeaController; @@ -33,19 +35,32 @@ class InstructionsController { /// Returns true if the instructions were closed /// or turned off by the user via the toggle switch - bool wereInstructionsTurnedOff(String key) => - toggledOff(key) ?? _instructionsClosed[key] ?? false; + bool wereInstructionsTurnedOff(String key) { + return toggledOff(key) ?? _instructionsClosed[key] ?? false; + } void turnOffInstruction(String key) => _instructionsClosed[key] = true; - Future updateEnableInstructions( + void updateEnableInstructions( String key, bool value, - ) async => - await _pangeaController.pStoreService.save( - key, - value, - ); + ) { + _pangeaController.userController.updateProfile((profile) { + if (key == InstructionsEnum.itInstructions.toString()) { + profile.instructionSettings.showedItInstructions = value; + } + if (key == InstructionsEnum.clickMessage.toString()) { + profile.instructionSettings.showedClickMessage = value; + } + if (key == InstructionsEnum.blurMeansTranslate.toString()) { + profile.instructionSettings.showedBlurMeansTranslate = value; + } + if (key == InstructionsEnum.tooltipInstructions.toString()) { + profile.instructionSettings.showedTooltipInstructions = value; + } + return profile; + }); + } /// Instruction Card gives users tips on /// how to use Pangea Chat's features @@ -170,7 +185,7 @@ class InstructionsToggleState extends State { widget.instructionsKey.toString(), ), onChanged: ((value) async { - await pangeaController.instructions.updateEnableInstructions( + pangeaController.instructions.updateEnableInstructions( widget.instructionsKey.toString(), value, ); diff --git a/lib/pangea/widgets/user_settings/country_picker_tile.dart b/lib/pangea/widgets/user_settings/country_picker_tile.dart index df292a837..c4441573d 100644 --- a/lib/pangea/widgets/user_settings/country_picker_tile.dart +++ b/lib/pangea/widgets/user_settings/country_picker_tile.dart @@ -1,14 +1,10 @@ -import 'dart:developer'; - import 'package:country_picker/country_picker.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; import 'package:fluffychat/pangea/utils/country_display.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:future_loading_dialog/future_loading_dialog.dart'; import '../../models/user_model.dart'; @@ -21,30 +17,26 @@ class CountryPickerTile extends StatelessWidget { @override Widget build(BuildContext context) { final Profile profile = pangeaController.userController.profile; + + final String displayName = CountryDisplayUtil.countryDisplayName( + profile.userSettings.country, + context, + ) ?? + ''; + + final String flag = CountryDisplayUtil.flagEmoji( + profile.userSettings.country, + ); + return ListTile( title: Text( - "${L10n.of(context)!.countryInformation}: ${CountryDisplayUtil.countryDisplayName( - profile.userSettings.country, - context, - ) ?? ''} ${CountryDisplayUtil.flagEmoji(profile.userSettings.country)}", + "${L10n.of(context)!.countryInformation}: $displayName $flag", ), trailing: const Icon(Icons.edit_outlined), onTap: () => showCountryPicker( context: context, - showPhoneCode: - false, // optional. Shows phone code before the country name. - onSelect: (Country country) async { - showFutureLoadingDialog( - context: context, - future: () async { - try { - learningController.changeCountry(country); - } catch (err) { - debugger(when: kDebugMode); - } - }, - ); - }, + showPhoneCode: false, + onSelect: learningController.changeCountry, ), ); } diff --git a/lib/pangea/widgets/user_settings/p_language_dialog.dart b/lib/pangea/widgets/user_settings/p_language_dialog.dart index 8635170a0..bfc85528b 100644 --- a/lib/pangea/widgets/user_settings/p_language_dialog.dart +++ b/lib/pangea/widgets/user_settings/p_language_dialog.dart @@ -15,7 +15,10 @@ import '../../../widgets/matrix.dart'; import 'p_language_dropdown.dart'; import 'p_question_container.dart'; -pLanguageDialog(BuildContext parentContext, Function callback) async { +Future pLanguageDialog( + BuildContext parentContext, + Function callback, +) async { final PangeaController pangeaController = MatrixState.pangeaController; //PTODO: if source language not set by user, default to languge from device settings final LanguageModel? userL1 = pangeaController.languageController.userL1; From 4e3f00f9b86f4b4fa702e11cc61ec1e0d6e76d47 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 16 Jul 2024 09:41:12 -0400 Subject: [PATCH 42/51] Remove Find Conversation Partner button from menu --- .../chat_list/client_chooser_button.dart | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 567b8a5db..15f52ebe2 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -100,17 +100,17 @@ class ClientChooserButton extends StatelessWidget { // ], // ), // ), - if (controller.pangeaController.permissionsController.isUser18()) - PopupMenuItem( - value: SettingsAction.findAConversationPartner, - child: Row( - children: [ - const Icon(Icons.add_circle_outline), - const SizedBox(width: 18), - Expanded(child: Text(L10n.of(context)!.findALanguagePartner)), - ], - ), - ), + // if (controller.pangeaController.permissionsController.isUser18()) + // PopupMenuItem( + // value: SettingsAction.findAConversationPartner, + // child: Row( + // children: [ + // const Icon(Icons.add_circle_outline), + // const SizedBox(width: 18), + // Expanded(child: Text(L10n.of(context)!.findALanguagePartner)), + // ], + // ), + // ), // PopupMenuItem( // value: SettingsAction.setStatus, // child: Row( From 08faf399cc0a0932eeb3981365d8dee17a8cb8ea Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 16 Jul 2024 09:53:19 -0400 Subject: [PATCH 43/51] added fix for pangea profile toJson method --- lib/pangea/models/user_model.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index 972d7bbba..180422576 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -420,7 +420,7 @@ class PangeaProfileResponse { factory PangeaProfileResponse.fromJson(Map json) { return PangeaProfileResponse( - profile: PangeaProfile.fromJson(json), + profile: PangeaProfile.fromJson(json[ModelKey.userProfile]), access: json[ModelKey.userAccess], ); } From 057f07d2104d8d9e003644fb67d517a23dd29848 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 16 Jul 2024 10:04:34 -0400 Subject: [PATCH 44/51] removed debugger statement from my_analytics_controller update function - not an error state --- lib/pangea/controllers/my_analytics_controller.dart | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 058bad359..12baeb689 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -229,11 +229,8 @@ class MyAnalyticsController { /// top level analytics sending function. Gather recent messages and activity records, /// convert them into the correct formats, and send them to the analytics room Future _updateAnalytics() async { - // if missing important info, don't send analytics - if (userL2 == null || _client.userID == null) { - debugger(when: kDebugMode); - return; - } + // if missing important info, don't send analytics. Could happen if user just signed up. + if (userL2 == null || _client.userID == null) return; // analytics room for the user and current target language final Room analyticsRoom = await _client.getMyAnalyticsRoom(userL2!); From 899a128118cbb6b46ab612515988c6a7d8277083 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 16 Jul 2024 12:23:40 -0400 Subject: [PATCH 45/51] Fix loading issue for realsies --- lib/pages/chat_list/space_view.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index cf5913a25..ef938c235 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -126,8 +126,11 @@ class _SpaceViewState extends State { try { final response = await client.getSpaceHierarchy( activeSpaceId, - maxDepth: 2, + maxDepth: 1, from: prevBatch, + // #Pangea + limit: 100, + // Pangea# ); if (prevBatch != null) { From 280915fc96d8a09db12b342e140641b6eabf2fdb Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 16 Jul 2024 13:43:11 -0400 Subject: [PATCH 46/51] removed excessive calls to setState in chat.dart and replaced them with smaller, stateful widgets --- lib/pages/chat/chat.dart | 97 ++---- lib/pages/chat/chat_event_list.dart | 2 - lib/pages/chat/chat_input_row.dart | 7 +- lib/pages/chat/chat_view.dart | 282 +++++++----------- lib/pages/chat/events/message.dart | 7 +- .../controllers/choreographer.dart | 3 - .../controllers/message_options.dart | 46 --- lib/pangea/choreographer/widgets/it_bar.dart | 232 +++++++------- .../widgets/language_display_toggle.dart | 56 ---- .../choreographer/widgets/send_button.dart | 41 ++- .../pangea_message_event.dart | 3 +- .../pages/class_analytics/measure_able.dart | 57 ---- .../chat/chat_floating_action_button.dart | 94 ++++++ .../widgets/chat/input_bar_wrapper.dart | 82 +++++ lib/pangea/widgets/chat/message_actions.dart | 29 -- .../widgets/chat/text_to_speech_button.dart | 138 --------- 16 files changed, 472 insertions(+), 704 deletions(-) delete mode 100644 lib/pangea/choreographer/controllers/message_options.dart delete mode 100644 lib/pangea/choreographer/widgets/language_display_toggle.dart delete mode 100644 lib/pangea/pages/class_analytics/measure_able.dart create mode 100644 lib/pangea/widgets/chat/chat_floating_action_button.dart create mode 100644 lib/pangea/widgets/chat/input_bar_wrapper.dart delete mode 100644 lib/pangea/widgets/chat/message_actions.dart delete mode 100644 lib/pangea/widgets/chat/text_to_speech_button.dart diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index f998b62b3..8107993da 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -20,7 +20,6 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_e import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/choreo_record.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; -import 'package:fluffychat/pangea/models/space_model.dart'; import 'package:fluffychat/pangea/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; @@ -294,10 +293,6 @@ class ChatController extends State } } - // #Pangea - bool showPermissionsError = false; - // #Pangea - @override void initState() { scrollController.addListener(_updateScrollController); @@ -327,31 +322,12 @@ class ChatController extends State context, () => Future.delayed( Duration.zero, - () => setState( - () {}, - ), + () => setState(() {}), ), ); } await Matrix.of(context).client.roomsLoading; - choreographer.setRoomId(roomId); - choreographer.messageOptions.resetSelectedDisplayLang(); - choreographer.stateListener.stream.listen((event) { - debugPrint("chat.dart choreo event $event"); - setState(() {}); - }); - showPermissionsError = !pangeaController.permissionsController - .isToolEnabled(ToolSetting.interactiveTranslator, room) || - !pangeaController.permissionsController - .isToolEnabled(ToolSetting.interactiveGrammar, room); }); - - Future.delayed( - const Duration(seconds: 5), - () { - if (mounted) setState(() => showPermissionsError = false); - }, - ); // Pangea# _tryLoadTimeline(); if (kIsWeb) { @@ -602,10 +578,9 @@ class ChatController extends State }); // #Pangea - final List edittingEvents = []; - void clearEdittingEvent(String eventId) { - edittingEvents.remove(eventId); - setState(() {}); + Event? pangeaEditingEvent; + void clearEditingEvent() { + pangeaEditingEvent = null; } // Future send() async { @@ -665,11 +640,9 @@ class ChatController extends State .then( (String? msgEventId) async { // #Pangea - setState(() { - if (previousEdit != null) { - edittingEvents.add(previousEdit.eventId); - } - }); + if (previousEdit != null) { + pangeaEditingEvent = previousEdit; + } GoogleAnalytics.sendMessage( room.id, @@ -1262,9 +1235,6 @@ class ChatController extends State void clearSelectedEvents() => setState(() { selectedEvents.clear(); showEmojiPicker = false; - //#Pangea - choreographer.messageOptions.resetSelectedDisplayLang(); - //Pangea# }); void clearSingleSelectedEvent() { @@ -1336,19 +1306,19 @@ class ChatController extends State // Pangea# if (!event.redacted) { // #Pangea - // If previous selectedEvent has same eventId, delete previous selectedEvent - final matches = - selectedEvents.where((e) => e.eventId == event.eventId).toList(); + // if (selectedEvents.contains(event)) { + // setState( + // () => selectedEvents.remove(event), + // ); + // } + + // If delete first selected event with the selected eventID + final matches = selectedEvents.where((e) => e.eventId == event.eventId); if (matches.isNotEmpty) { - // if (selectedEvents.contains(event)) { - // Pangea# - setState( - // #Pangea - () => selectedEvents.remove(matches.first), - // () => selectedEvents.remove(event), - // Pangea# - ); - } else { + setState(() => selectedEvents.remove(matches.first)); + } + // Pangea# + else { setState( () => selectedEvents.add(event), ); @@ -1557,35 +1527,6 @@ class ChatController extends State }); // #Pangea - double? availableSpace; - double? inputRowSize; - bool? lastState; - bool get isRowScrollable { - if (availableSpace == null || inputRowSize == null) { - if (lastState == null) { - lastState = false; - Future.delayed(Duration.zero, () { - setState(() {}); - }); - } - return false; - } - const double offSetValue = 10; - final bool currentState = inputRowSize! > (availableSpace! - offSetValue); - if (!lastState! && currentState) { - Future.delayed(Duration.zero, () { - setState(() {}); - }); - } - if (lastState! && !currentState) { - Future.delayed(Duration.zero, () { - setState(() {}); - }); - } - lastState = currentState; - return currentState; - } - final Map _pangeaMessageEvents = {}; final Map _toolbarDisplayControllers = {}; diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 24508bb62..9bca32169 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -170,8 +170,6 @@ class ChatEventList extends StatelessWidget { controller.scrollToEventId(eventId), longPressSelect: controller.selectedEvents.isNotEmpty, // #Pangea - selectedDisplayLang: - controller.choreographer.messageOptions.selectedDisplayLang, immersionMode: controller.choreographer.immersionMode, definitions: controller.choreographer.definitionsEnabled, controller: controller, diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index 2ba6a0957..b1174fd2c 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -3,6 +3,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; import 'package:fluffychat/pangea/choreographer/widgets/send_button.dart'; import 'package:fluffychat/pangea/constants/language_constants.dart'; +import 'package:fluffychat/pangea/widgets/chat/input_bar_wrapper.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -12,7 +13,6 @@ import 'package:matrix/matrix.dart'; import '../../config/themes.dart'; import 'chat.dart'; -import 'input_bar.dart'; class ChatInputRow extends StatelessWidget { final ChatController controller; @@ -322,7 +322,10 @@ class ChatInputRow extends StatelessWidget { Expanded( child: Padding( padding: const EdgeInsets.symmetric(vertical: 0.0), - child: InputBar( + // #Pangea + // child: InputBar( + child: InputBarWrapper( + // Pangea# room: controller.room, minLines: 1, maxLines: 8, diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 3c819f412..36e6d69dc 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -7,11 +7,9 @@ import 'package:fluffychat/pages/chat/chat_event_list.dart'; import 'package:fluffychat/pages/chat/pinned_events.dart'; import 'package:fluffychat/pages/chat/reactions_picker.dart'; import 'package:fluffychat/pages/chat/reply_display.dart'; -import 'package:fluffychat/pangea/choreographer/widgets/has_error_button.dart'; -import 'package:fluffychat/pangea/choreographer/widgets/language_permissions_warning_buttons.dart'; import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/pages/class_analytics/measure_able.dart'; +import 'package:fluffychat/pangea/widgets/chat/chat_floating_action_button.dart'; import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/widgets/chat_settings_popup_menu.dart'; import 'package:fluffychat/widgets/connection_status_header.dart'; @@ -266,32 +264,20 @@ class ChatView extends StatelessWidget { // #Pangea // floatingActionButton: controller.showScrollDownButton && // controller.selectedEvents.isEmpty - floatingActionButton: controller.selectedEvents.isEmpty - ? (controller.showScrollDownButton - // Pangea# - ? Padding( - padding: const EdgeInsets.only(bottom: 56.0), - child: FloatingActionButton( - onPressed: controller.scrollDown, - heroTag: null, - mini: true, - child: const Icon(Icons.arrow_downward_outlined), - ), - ) - // #Pangea - : controller.choreographer.errorService.error != null - ? ChoreographerHasErrorButton( - controller.pangeaController, - controller.choreographer.errorService.error!, - ) - : controller.showPermissionsError - ? LanguagePermissionsButtons( - choreographer: controller.choreographer, - roomID: controller.roomId, - ) - : null) - // #Pangea - : null, + // ? Padding( + // padding: const EdgeInsets.only(bottom: 56.0), + // child: FloatingActionButton( + // onPressed: controller.scrollDown, + // heroTag: null, + // mini: true, + // child: const Icon(Icons.arrow_downward_outlined), + // ), + // ) + // : null, + floatingActionButton: ChatFloatingActionButton( + controller: controller, + ), + // Pangea# body: // #Pangea // DropTarget( @@ -338,120 +324,100 @@ class ChatView extends StatelessWidget { ), if (controller.room.canSendDefaultMessages && controller.room.membership == Membership.join) - // #Pangea - // Container( - ConditionalFlexible( - isScroll: controller.isRowScrollable, - child: ConditionalScroll( - isScroll: controller.isRowScrollable, - child: MeasurableWidget( - onChange: (size, position) { - controller.inputRowSize = size!.height; - }, - child: Container( - // Pangea# - margin: EdgeInsets.only( - bottom: bottomSheetPadding, - left: bottomSheetPadding, - right: bottomSheetPadding, - ), - constraints: const BoxConstraints( - maxWidth: FluffyThemes.columnWidth * 2.5, - ), - alignment: Alignment.center, - child: Material( - clipBehavior: Clip.hardEdge, - color: Theme.of(context) - .colorScheme - // ignore: deprecated_member_use - .surfaceVariant, - borderRadius: const BorderRadius.all( - Radius.circular(24), - ), - child: controller.room.isAbandonedDMRoom == - true - ? Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, - children: [ - // #Pangea - if (controller.room.isRoomAdmin) - TextButton.icon( - style: TextButton.styleFrom( - padding: - const EdgeInsets.all( - 16, - ), - foregroundColor: - Theme.of(context) - .colorScheme - .error, - ), - icon: const Icon( - Icons.archive_outlined, - ), - onPressed: - controller.archiveChat, - label: Text( - L10n.of(context)!.archive, - ), - ), - // Pangea# - TextButton.icon( - style: TextButton.styleFrom( - padding: const EdgeInsets.all( - 16, - ), - foregroundColor: - Theme.of(context) - .colorScheme - .error, - ), - icon: const Icon( - // #Pangea - // Icons.archive_outlined, - Icons.arrow_forward, - // Pangea# - ), - onPressed: controller.leaveChat, - label: Text( - L10n.of(context)!.leave, - ), - ), - TextButton.icon( - style: TextButton.styleFrom( - padding: const EdgeInsets.all( - 16, - ), - ), - icon: const Icon( - Icons.forum_outlined, - ), - onPressed: - controller.recreateChat, - label: Text( - L10n.of(context)!.reopenChat, - ), - ), - ], - ) - : Column( - mainAxisSize: MainAxisSize.min, - children: [ - const ConnectionStatusHeader(), - ITBar( - choreographer: - controller.choreographer, - ), - ReactionsPicker(controller), - ReplyDisplay(controller), - ChatInputRow(controller), - ChatEmojiPicker(controller), - ], - ), - ), - ), + Container( + margin: EdgeInsets.only( + bottom: bottomSheetPadding, + left: bottomSheetPadding, + right: bottomSheetPadding, + ), + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 2.5, + ), + alignment: Alignment.center, + child: Material( + clipBehavior: Clip.hardEdge, + color: Theme.of(context) + .colorScheme + // ignore: deprecated_member_use + .surfaceVariant, + borderRadius: const BorderRadius.all( + Radius.circular(24), ), + child: controller.room.isAbandonedDMRoom == true + ? Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + // #Pangea + if (controller.room.isRoomAdmin) + TextButton.icon( + style: TextButton.styleFrom( + padding: const EdgeInsets.all( + 16, + ), + foregroundColor: Theme.of(context) + .colorScheme + .error, + ), + icon: const Icon( + Icons.archive_outlined, + ), + onPressed: controller.archiveChat, + label: Text( + L10n.of(context)!.archive, + ), + ), + // Pangea# + TextButton.icon( + style: TextButton.styleFrom( + padding: const EdgeInsets.all( + 16, + ), + foregroundColor: Theme.of(context) + .colorScheme + .error, + ), + icon: const Icon( + // #Pangea + // Icons.archive_outlined, + Icons.arrow_forward, + // Pangea# + ), + onPressed: controller.leaveChat, + label: Text( + L10n.of(context)!.leave, + ), + ), + TextButton.icon( + style: TextButton.styleFrom( + padding: const EdgeInsets.all( + 16, + ), + ), + icon: const Icon( + Icons.forum_outlined, + ), + onPressed: controller.recreateChat, + label: Text( + L10n.of(context)!.reopenChat, + ), + ), + ], + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + const ConnectionStatusHeader(), + ITBar( + choreographer: + controller.choreographer, + ), + ReactionsPicker(controller), + ReplyDisplay(controller), + ChatInputRow(controller), + ChatEmojiPicker(controller), + ], + ), ), ), ], @@ -484,35 +450,3 @@ class ChatView extends StatelessWidget { ); } } - -// #Pangea -Widget ConditionalFlexible({required bool isScroll, required Widget child}) { - if (isScroll) { - return Flexible( - flex: 9999999, - child: child, - ); - } - return child; -} - -class ConditionalScroll extends StatelessWidget { - final bool isScroll; - final Widget child; - const ConditionalScroll({ - super.key, - required this.isScroll, - required this.child, - }); - - @override - Widget build(BuildContext context) { - if (isScroll) { - return SingleChildScrollView( - child: child, - ); - } - return child; - } -} -// Pangea# diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 3b2c1b2fb..b0fc6842f 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -2,7 +2,6 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/enum/use_type.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; import 'package:fluffychat/pangea/widgets/chat/message_buttons.dart'; import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; @@ -39,7 +38,6 @@ class Message extends StatelessWidget { final bool animateIn; final void Function()? resetAnimateIn; // #Pangea - final LanguageModel? selectedDisplayLang; final bool immersionMode; final bool definitions; final ChatController controller; @@ -64,7 +62,6 @@ class Message extends StatelessWidget { this.resetAnimateIn, this.avatarPresenceBackgroundColor, // #Pangea - required this.selectedDisplayLang, required this.immersionMode, required this.definitions, required this.controller, @@ -82,9 +79,9 @@ class Message extends StatelessWidget { // #Pangea debugPrint('Message.build()'); WidgetsBinding.instance.addPostFrameCallback((_) { - if (controller.edittingEvents.contains(event.eventId)) { + if (controller.pangeaEditingEvent?.eventId == event.eventId) { pangeaMessageEvent?.updateLatestEdit(); - controller.clearEdittingEvent(event.eventId); + controller.clearEditingEvent(); } }); // Pangea# diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index e5108ae4d..dd2078fa2 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -4,7 +4,6 @@ import 'dart:developer'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/choreographer/controllers/alternative_translator.dart'; import 'package:fluffychat/pangea/choreographer/controllers/igc_controller.dart'; -import 'package:fluffychat/pangea/choreographer/controllers/message_options.dart'; import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/subscription_controller.dart'; @@ -38,7 +37,6 @@ class Choreographer { late PangeaTextController _textController; late ITController itController; late IgcController igc; - late MessageOptions messageOptions; late AlternativeTranslator altTranslator; late ErrorService errorService; @@ -59,7 +57,6 @@ class Choreographer { _textController = PangeaTextController(choreographer: this); itController = ITController(this); igc = IgcController(this); - messageOptions = MessageOptions(this); errorService = ErrorService(this); altTranslator = AlternativeTranslator(this); _textController.addListener(_onChangeListener); diff --git a/lib/pangea/choreographer/controllers/message_options.dart b/lib/pangea/choreographer/controllers/message_options.dart deleted file mode 100644 index 96df5d921..000000000 --- a/lib/pangea/choreographer/controllers/message_options.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/cupertino.dart'; - -import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; -import 'package:fluffychat/pangea/constants/language_constants.dart'; -import 'package:fluffychat/pangea/models/language_model.dart'; -import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; - -class MessageOptions { - Choreographer choreographer; - LanguageModel? _selectedDisplayLang; - - MessageOptions(this.choreographer); - - LanguageModel? get selectedDisplayLang { - if (_selectedDisplayLang != null && - _selectedDisplayLang!.langCode != LanguageKeys.unknownLanguage) { - return _selectedDisplayLang; - } - _selectedDisplayLang = choreographer.l2Lang; - return _selectedDisplayLang; - } - - bool get isTranslationOn => - _selectedDisplayLang?.langCode != choreographer.l2LangCode; - - // void setSelectedDisplayLang(LanguageModel? newLang) { - // _selectedDisplayLang = newLang; - // choreographer.setState(); - // } - - void toggleSelectedDisplayLang() { - if (_selectedDisplayLang?.langCode == choreographer.l2LangCode) { - _selectedDisplayLang = choreographer.l1Lang; - } else { - _selectedDisplayLang = choreographer.l2Lang; - } - debugPrint('toggleSelectedDisplayLang: ${_selectedDisplayLang?.langCode}'); - choreographer.setState(); - GoogleAnalytics.messageTranslate(); - } - - void resetSelectedDisplayLang() { - _selectedDisplayLang = choreographer.l2Lang; - choreographer.setState(); - } -} diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 2ba630900..28b0f8bd8 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:developer'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; @@ -7,7 +8,6 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_feedback_card.dart'; import 'package:fluffychat/pangea/choreographer/widgets/translation_finished_flow.dart'; import 'package:fluffychat/pangea/constants/choreo_constants.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -18,112 +18,140 @@ import '../../utils/overlay.dart'; import '../../widgets/igc/word_data_card.dart'; import 'choice_array.dart'; -class ITBar extends StatelessWidget { +class ITBar extends StatefulWidget { final Choreographer choreographer; const ITBar({super.key, required this.choreographer}); - ITController get itController => choreographer.itController; + @override + ITBarState createState() => ITBarState(); +} + +class ITBarState extends State { + ITController get itController => widget.choreographer.itController; + StreamSubscription? _choreoSub; + + @override + void initState() { + // Rebuild the widget each time there's an update from choreo. + _choreoSub = widget.choreographer.stateListener.stream.listen((_) { + setState(() {}); + }); + super.initState(); + } + + @override + void dispose() { + _choreoSub?.cancel(); + super.dispose(); + } @override Widget build(BuildContext context) { - return AnimatedSize( duration: itController.willOpen - ? const Duration(milliseconds: 2000) - : const Duration(milliseconds: 500), + ? const Duration(milliseconds: 2000) + : const Duration(milliseconds: 500), curve: Curves.fastOutSlowIn, clipBehavior: Clip.none, child: !itController.willOpen - ? const SizedBox() - : CompositedTransformTarget( - link: choreographer.itBarLinkAndKey.link, - child: AnimatedOpacity( - duration: itController.willOpen - ? const Duration(milliseconds: 2000) - : const Duration(milliseconds: 500), - opacity: itController.willOpen ? 1.0 : 0.0, - child: Container( - key: choreographer.itBarLinkAndKey.key, - decoration: BoxDecoration( - color: Theme.of(context).brightness == Brightness.light - ? Colors.white - : Colors.black, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(AppConfig.borderRadius), - topRight: Radius.circular(AppConfig.borderRadius), - ), - ), - width: double.infinity, - padding: const EdgeInsets.fromLTRB(0, 3, 3, 3), - child: Stack( - children: [ - SingleChildScrollView( - child: Column( - children: [ - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // // Row( - // // mainAxisAlignment: MainAxisAlignment.start, - // // crossAxisAlignment: CrossAxisAlignment.start, - // // children: [ - // // CounterDisplay( - // // correct: controller.correctChoices, - // // custom: controller.customChoices, - // // incorrect: controller.incorrectChoices, - // // yellow: controller.wildcardChoices, - // // ), - // // CompositedTransformTarget( - // // link: choreographer.itBotLayerLinkAndKey.link, - // // child: ITBotButton( - // // key: choreographer.itBotLayerLinkAndKey.key, - // // choreographer: choreographer, - // // ), - // // ), - // // ], - // // ), - // ITCloseButton(choreographer: choreographer), - // ], - // ), - // const SizedBox(height: 40.0), - OriginalText(controller: itController), - const SizedBox(height: 7.0), - IntrinsicHeight( - child: Container( - constraints: const BoxConstraints(minHeight: 80), - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: Center( - child: itController.choreographer.errorService.isError - ? ITError( - error: itController - .choreographer.errorService.error!, - controller: itController, - ) - : itController.showChoiceFeedback - ? ChoiceFeedbackText(controller: itController) - : itController.isTranslationDone - ? TranslationFeedback( - controller: itController, - ) - : ITChoices(controller: itController), - ), - ), - ), - ], + ? const SizedBox() + : CompositedTransformTarget( + link: widget.choreographer.itBarLinkAndKey.link, + child: AnimatedOpacity( + duration: itController.willOpen + ? const Duration(milliseconds: 2000) + : const Duration(milliseconds: 500), + opacity: itController.willOpen ? 1.0 : 0.0, + child: Container( + key: widget.choreographer.itBarLinkAndKey.key, + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.light + ? Colors.white + : Colors.black, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(AppConfig.borderRadius), + topRight: Radius.circular(AppConfig.borderRadius), ), ), - Positioned( - top: 0.0, - right: 0.0, - child: ITCloseButton(choreographer: choreographer), + width: double.infinity, + padding: const EdgeInsets.fromLTRB(0, 3, 3, 3), + child: Stack( + children: [ + SingleChildScrollView( + child: Column( + children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // // Row( + // // mainAxisAlignment: MainAxisAlignment.start, + // // crossAxisAlignment: CrossAxisAlignment.start, + // // children: [ + // // CounterDisplay( + // // correct: controller.correctChoices, + // // custom: controller.customChoices, + // // incorrect: controller.incorrectChoices, + // // yellow: controller.wildcardChoices, + // // ), + // // CompositedTransformTarget( + // // link: choreographer.itBotLayerLinkAndKey.link, + // // child: ITBotButton( + // // key: choreographer.itBotLayerLinkAndKey.key, + // // choreographer: choreographer, + // // ), + // // ), + // // ], + // // ), + // ITCloseButton(choreographer: choreographer), + // ], + // ), + // const SizedBox(height: 40.0), + OriginalText(controller: itController), + const SizedBox(height: 7.0), + IntrinsicHeight( + child: Container( + constraints: + const BoxConstraints(minHeight: 80), + width: double.infinity, + padding: + const EdgeInsets.symmetric(horizontal: 4.0), + child: Center( + child: itController + .choreographer.errorService.isError + ? ITError( + error: itController.choreographer + .errorService.error!, + controller: itController, + ) + : itController.showChoiceFeedback + ? ChoiceFeedbackText( + controller: itController, + ) + : itController.isTranslationDone + ? TranslationFeedback( + controller: itController, + ) + : ITChoices( + controller: itController, + ), + ), + ), + ), + ], + ), + ), + Positioned( + top: 0.0, + right: 0.0, + child: + ITCloseButton(choreographer: widget.choreographer), + ), + ], ), - ], + ), ), ), - ), - ), ); } } @@ -199,20 +227,16 @@ class OriginalText extends StatelessWidget { ), ), ), - if ( - !controller.isEditingSourceText - && controller.sourceText != null - ) + if (!controller.isEditingSourceText && controller.sourceText != null) AnimatedOpacity( duration: const Duration(milliseconds: 500), - opacity: controller.nextITStep != null - ? 1.0 - : 0.0, + opacity: controller.nextITStep != null ? 1.0 : 0.0, child: IconButton( onPressed: () => { - if (controller.nextITStep != null) { - controller.setIsEditingSourceText(true), - }, + if (controller.nextITStep != null) + { + controller.setIsEditingSourceText(true), + }, }, icon: const Icon(Icons.edit_outlined), ), @@ -309,9 +333,9 @@ class ITChoices extends StatelessWidget { choices: controller.currentITStep!.continuances.map((e) { try { return Choice( - text: e.text.trim(), - color: e.color, - isGold: e.description == "best", + text: e.text.trim(), + color: e.color, + isGold: e.description == "best", ); } catch (e) { debugger(when: kDebugMode); diff --git a/lib/pangea/choreographer/widgets/language_display_toggle.dart b/lib/pangea/choreographer/widgets/language_display_toggle.dart deleted file mode 100644 index bc0efd492..000000000 --- a/lib/pangea/choreographer/widgets/language_display_toggle.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -import '../../../config/app_config.dart'; -import '../../../pages/chat/chat.dart'; - -class LanguageDisplayToggle extends StatelessWidget { - const LanguageDisplayToggle({ - super.key, - required this.controller, - }); - - final ChatController controller; - - get onPressed => - controller.choreographer.messageOptions.toggleSelectedDisplayLang; - - @override - Widget build(BuildContext context) { - // if (!controller.choreographer.translationEnabled) { - // return const SizedBox(); - // } - return Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: controller.choreographer.messageOptions.isTranslationOn - ? AppConfig.primaryColor - : null, - ), - child: IconButton( - tooltip: L10n.of(context)!.toggleLanguages, - onPressed: onPressed, - icon: const Icon(Icons.translate_outlined), - selectedIcon: const Icon(Icons.translate), - isSelected: controller.choreographer.messageOptions.isTranslationOn, - ), - ); - // return Tooltip( - // message: L10n.of(context)!.toggleLanguages, - // waitDuration: const Duration(milliseconds: 1000), - // child: FloatingActionButton( - // onPressed: onPressed, - // backgroundColor: Colors.white, - // mini: false, - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(200), // <-- Radius - // ), - // child: LanguageFlag( - // flagUrl: controller - // .choreographer.messageOptions.displayLang?.languageFlag, - // size: 50, - // ), - // ), - // ); - } -} diff --git a/lib/pangea/choreographer/widgets/send_button.dart b/lib/pangea/choreographer/widgets/send_button.dart index 3f034b736..f5e358a31 100644 --- a/lib/pangea/choreographer/widgets/send_button.dart +++ b/lib/pangea/choreographer/widgets/send_button.dart @@ -1,22 +1,47 @@ +import 'dart:async'; + import 'package:fluffychat/pangea/enum/assistance_state_enum.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../../../pages/chat/chat.dart'; -class ChoreographerSendButton extends StatelessWidget { +class ChoreographerSendButton extends StatefulWidget { const ChoreographerSendButton({ super.key, required this.controller, }); - final ChatController controller; + @override + State createState() => + ChoreographerSendButtonState(); +} + +class ChoreographerSendButtonState extends State { + StreamSubscription? _choreoSub; + + @override + void initState() { + // Rebuild the widget each time there's an update from + // choreo. This keeps the spin up-to-date. + _choreoSub = + widget.controller.choreographer.stateListener.stream.listen((_) { + setState(() {}); + }); + super.initState(); + } + + @override + void dispose() { + _choreoSub?.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { - // commit for cicd - return controller.choreographer.isFetching && - controller.choreographer.isAutoIGCEnabled + return widget.controller.choreographer.isFetching && + widget.controller.choreographer.isAutoIGCEnabled ? Container( height: 56, width: 56, @@ -28,10 +53,10 @@ class ChoreographerSendButton extends StatelessWidget { alignment: Alignment.center, child: IconButton( icon: const Icon(Icons.send_outlined), - color: - controller.choreographer.assistanceState.stateColor(context), + color: widget.controller.choreographer.assistanceState + .stateColor(context), onPressed: () { - controller.choreographer.send(context); + widget.controller.choreographer.send(context); }, tooltip: L10n.of(context)!.send, ), diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index 530a7a547..f1c9e5082 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -82,10 +82,9 @@ class PangeaMessageEvent { .firstOrNull ?? _event; - Event updateLatestEdit() { + void updateLatestEdit() { _latestEditCache = null; _representations = null; - return _latestEdit; } Future getMatrixAudioFile( diff --git a/lib/pangea/pages/class_analytics/measure_able.dart b/lib/pangea/pages/class_analytics/measure_able.dart deleted file mode 100644 index a569893fa..000000000 --- a/lib/pangea/pages/class_analytics/measure_able.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; - -class MeasurableWidget extends StatefulWidget { - final Widget child; - final Function(Size? size, Offset? position) onChange; - - const MeasurableWidget({ - super.key, - required this.onChange, - required this.child, - }); - - @override - _WidgetSizeState createState() => _WidgetSizeState(); -} - -class _WidgetSizeState extends State { - var widgetKey = GlobalKey(); - Offset? oldPosition; - @override - void initState() { - // TODO: implement initState - super.initState(); - } - - void postFrameCallback(_) { - final context = widgetKey.currentContext; - if (context == null) return; - - final RenderBox? box = - widgetKey.currentContext?.findRenderObject() as RenderBox?; - - if (box != null && box.hasSize) { - final Offset position = box.localToGlobal(Offset.zero); - - if (oldPosition != null) { - if (oldPosition!.dx == position.dx && oldPosition!.dy == position.dy) { - return; - } - } - oldPosition = position; - - final newSize = context.size; - widget.onChange(newSize, position); - } - } - - @override - Widget build(BuildContext context) { - SchedulerBinding.instance.addPostFrameCallback(postFrameCallback); - return Container( - key: widgetKey, - child: widget.child, - ); - } -} diff --git a/lib/pangea/widgets/chat/chat_floating_action_button.dart b/lib/pangea/widgets/chat/chat_floating_action_button.dart new file mode 100644 index 000000000..735b51b36 --- /dev/null +++ b/lib/pangea/widgets/chat/chat_floating_action_button.dart @@ -0,0 +1,94 @@ +import 'dart:async'; + +import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pangea/choreographer/widgets/has_error_button.dart'; +import 'package:fluffychat/pangea/choreographer/widgets/language_permissions_warning_buttons.dart'; +import 'package:fluffychat/pangea/models/space_model.dart'; +import 'package:flutter/material.dart'; + +class ChatFloatingActionButton extends StatefulWidget { + final ChatController controller; + const ChatFloatingActionButton({ + super.key, + required this.controller, + }); + + @override + ChatFloatingActionButtonState createState() => + ChatFloatingActionButtonState(); +} + +class ChatFloatingActionButtonState extends State { + bool showPermissionsError = false; + StreamSubscription? _choreoSub; + + @override + void initState() { + final permissionsController = + widget.controller.pangeaController.permissionsController; + final itEnabled = permissionsController.isToolEnabled( + ToolSetting.interactiveTranslator, + widget.controller.room, + ); + final igcEnabled = permissionsController.isToolEnabled( + ToolSetting.interactiveGrammar, + widget.controller.room, + ); + showPermissionsError = !itEnabled || !igcEnabled; + debugPrint("showPermissionsError: $showPermissionsError"); + + if (showPermissionsError) { + Future.delayed( + const Duration(seconds: 5), + () { + if (mounted) setState(() => showPermissionsError = false); + }, + ); + } + + // Rebuild the widget each time there's an update from choreo (i.e., an error). + _choreoSub = + widget.controller.choreographer.stateListener.stream.listen((_) { + setState(() {}); + }); + + super.initState(); + } + + @override + void dispose() { + _choreoSub?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (widget.controller.selectedEvents.isNotEmpty) { + return const SizedBox.shrink(); + } + if (widget.controller.showScrollDownButton) { + return Padding( + padding: const EdgeInsets.only(bottom: 56.0), + child: FloatingActionButton( + onPressed: widget.controller.scrollDown, + heroTag: null, + mini: true, + child: const Icon(Icons.arrow_downward_outlined), + ), + ); + } + if (widget.controller.choreographer.errorService.error != null) { + return ChoreographerHasErrorButton( + widget.controller.pangeaController, + widget.controller.choreographer.errorService.error!, + ); + } + + return showPermissionsError + ? LanguagePermissionsButtons( + choreographer: widget.controller.choreographer, + roomID: widget.controller.roomId, + ) + : const SizedBox.shrink(); + } +} diff --git a/lib/pangea/widgets/chat/input_bar_wrapper.dart b/lib/pangea/widgets/chat/input_bar_wrapper.dart new file mode 100644 index 000000000..374f60a80 --- /dev/null +++ b/lib/pangea/widgets/chat/input_bar_wrapper.dart @@ -0,0 +1,82 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:fluffychat/pages/chat/input_bar.dart'; +import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; + +class InputBarWrapper extends StatefulWidget { + final Room room; + final int? minLines; + final int? maxLines; + final TextInputType? keyboardType; + final TextInputAction? textInputAction; + final ValueChanged? onSubmitted; + final ValueChanged? onSubmitImage; + final FocusNode? focusNode; + final PangeaTextController? controller; + final InputDecoration? decoration; + final ValueChanged? onChanged; + final bool? autofocus; + final bool readOnly; + + const InputBarWrapper({ + required this.room, + this.minLines, + this.maxLines, + this.keyboardType, + this.onSubmitted, + this.onSubmitImage, + this.focusNode, + this.controller, + this.decoration, + this.onChanged, + this.autofocus, + this.textInputAction, + this.readOnly = false, + super.key, + }); + + @override + State createState() => InputBarWrapperState(); +} + +class InputBarWrapperState extends State { + StreamSubscription? _choreoSub; + + @override + void initState() { + // Rebuild the widget each time there's an update from choreo + _choreoSub = + widget.controller?.choreographer.stateListener.stream.listen((_) { + setState(() {}); + }); + super.initState(); + } + + @override + void dispose() { + _choreoSub?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return InputBar( + room: widget.room, + minLines: widget.minLines, + maxLines: widget.maxLines, + keyboardType: widget.keyboardType, + onSubmitted: widget.onSubmitted, + onSubmitImage: widget.onSubmitImage, + focusNode: widget.focusNode, + controller: widget.controller, + decoration: widget.decoration, + onChanged: widget.onChanged, + autofocus: widget.autofocus, + textInputAction: widget.textInputAction, + readOnly: widget.readOnly, + ); + } +} diff --git a/lib/pangea/widgets/chat/message_actions.dart b/lib/pangea/widgets/chat/message_actions.dart deleted file mode 100644 index 05731f234..000000000 --- a/lib/pangea/widgets/chat/message_actions.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/widgets/chat/text_to_speech_button.dart'; -import 'package:flutter/material.dart'; - -class PangeaMessageActions extends StatelessWidget { - final ChatController chatController; - - const PangeaMessageActions({super.key, required this.chatController}); - - @override - Widget build(BuildContext context) { - return chatController.selectedEvents.length == 1 - ? Row( - children: [ - // LanguageToggleSwitch(controller: chatController), - TextToSpeechButton( - controller: chatController, - selectedEvent: chatController.selectedEvents.first, - ), - // IconButton( - // icon: Icon(Icons.mic), - // onPressed: chatController.onMicTap, - // ), - // Add more IconButton widgets here - ], - ) - : const SizedBox(); - } -} diff --git a/lib/pangea/widgets/chat/text_to_speech_button.dart b/lib/pangea/widgets/chat/text_to_speech_button.dart deleted file mode 100644 index 6e9e8e443..000000000 --- a/lib/pangea/widgets/chat/text_to_speech_button.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'dart:developer'; - -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pages/chat/events/audio_player.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:just_audio/just_audio.dart'; -import 'package:matrix/matrix.dart'; - -class TextToSpeechButton extends StatefulWidget { - final ChatController controller; - final Event selectedEvent; - - const TextToSpeechButton({ - super.key, - required this.controller, - required this.selectedEvent, - }); - - @override - _TextToSpeechButtonState createState() => _TextToSpeechButtonState(); -} - -class _TextToSpeechButtonState extends State { - final AudioPlayer _audioPlayer = AudioPlayer(); - late PangeaMessageEvent _pangeaMessageEvent; - bool _isLoading = false; - - @override - void dispose() { - _audioPlayer.dispose(); - super.dispose(); - } - - @override - void initState() { - super.initState(); - _pangeaMessageEvent = PangeaMessageEvent( - event: widget.selectedEvent, - timeline: widget.controller.timeline!, - ownMessage: - widget.selectedEvent.senderId == Matrix.of(context).client.userID, - ); - } - - Event? get localAudioEvent => - langCode != null && text != null && text!.isNotEmpty - ? _pangeaMessageEvent.getTextToSpeechLocal(langCode!, text!) - : null; - - String? get langCode => - widget.controller.choreographer.messageOptions.selectedDisplayLang - ?.langCode ?? - widget.controller.choreographer.l2LangCode; - - String? get text => langCode != null - ? _pangeaMessageEvent.representationByLanguage(langCode!)?.text - : null; - - Future _getAudio() async { - try { - if (!mounted) return; - if (text == null || text!.isEmpty) return; - if (langCode == null || langCode!.isEmpty) return; - - setState(() => _isLoading = true); - await _pangeaMessageEvent.getTextToSpeechGlobal(langCode!); - setState(() => _isLoading = false); - } catch (e) { - setState(() => _isLoading = false); - debugger(when: kDebugMode); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context)!.errorGettingAudio), - ), - ); - ErrorHandler.logError( - e: Exception(), - s: StackTrace.current, - m: 'text is null or empty in text_to_speech_button.dart', - data: {'selectedEvent': widget.selectedEvent, 'langCode': langCode}, - ); - } - } - - @override - Widget build(BuildContext context) { - if (_isLoading) { - return const Center(child: CircularProgressIndicator()); - } - - final playButton = InkWell( - borderRadius: BorderRadius.circular(64), - onTap: text == null || text!.isEmpty ? null : _getAudio, - child: Material( - color: AppConfig.primaryColor.withAlpha(64), - borderRadius: BorderRadius.circular(64), - child: const Icon( - // Change the icon based on some condition. If you have an audio player state, use it here. - Icons.play_arrow_outlined, - color: AppConfig.primaryColor, - ), - ), - ); - - return localAudioEvent == null - ? Opacity( - opacity: text == null || text!.isEmpty ? 0.5 : 1, - child: SizedBox( - width: 44, // Match the size of the button in AudioPlayerState - height: 36, - child: Padding( - //only left side of the button is padded to match the padding of the AudioPlayerState - padding: const EdgeInsets.only(left: 8), - child: playButton, - ), - ), - ) - : Container( - constraints: const BoxConstraints( - maxWidth: 250, - ), - child: Column( - children: [ - AudioPlayerWidget( - localAudioEvent!, - color: Theme.of(context).colorScheme.onPrimaryContainer, - ), - ], - ), - ); - } -} From 2fbc4e2016496e7db9fee05c9d1a5c9877ee652b Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 16 Jul 2024 13:59:45 -0400 Subject: [PATCH 47/51] Doesn't add welcome chat to subspace --- lib/pages/login/login_view.dart | 2 +- lib/pages/new_space/new_space.dart | 32 ++++++++++++++++++------------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/pages/login/login_view.dart b/lib/pages/login/login_view.dart index a50567eea..818dd06b4 100644 --- a/lib/pages/login/login_view.dart +++ b/lib/pages/login/login_view.dart @@ -147,7 +147,7 @@ class LoginView extends StatelessWidget { controller.showPassword ? Icons.visibility_off_outlined : Icons.visibility_outlined, - color: Colors.black, + // color: Colors.black, ), ), ), diff --git a/lib/pages/new_space/new_space.dart b/lib/pages/new_space/new_space.dart index 1903a6200..d5e41410d 100644 --- a/lib/pages/new_space/new_space.dart +++ b/lib/pages/new_space/new_space.dart @@ -198,14 +198,18 @@ class NewSpaceController extends State { if (capacity != null && space != null) { space.updateRoomCapacity(capacity); } - final newChatRoomId = await Matrix.of(context).client.createGroupChat( - enableEncryption: false, - preset: sdk.CreateRoomPreset.publicChat, - // Welcome chat name is '[space name acronym]: Welcome Chat' - groupName: - '${nameController.text.trim().split(RegExp(r"\s+")).map((s) => s[0]).join()}: ${L10n.of(context)!.classWelcomeChat}', - ); - GoogleAnalytics.createChat(newChatRoomId); + // If space has no parents, add welcome chat + String? newChatRoomId; + if (space?.pangeaSpaceParents.isEmpty ?? false) { + newChatRoomId = await Matrix.of(context).client.createGroupChat( + enableEncryption: false, + preset: sdk.CreateRoomPreset.publicChat, + // Welcome chat name is '[space name acronym]: Welcome Chat' + groupName: + '${nameController.text.trim().split(RegExp(r"\s+")).map((s) => s[0]).join()}: ${L10n.of(context)!.classWelcomeChat}', + ); + GoogleAnalytics.createChat(newChatRoomId); + } final Room? room = Matrix.of(context).client.getRoomById(spaceId); if (room == null) { @@ -217,11 +221,13 @@ class NewSpaceController extends State { return; } - room.setSpaceChild(newChatRoomId, suggested: true); - GoogleAnalytics.addParent( - newChatRoomId, - room.classCode, - ); + if (newChatRoomId != null) { + room.setSpaceChild(newChatRoomId, suggested: true); + GoogleAnalytics.addParent( + newChatRoomId, + room.classCode, + ); + } GoogleAnalytics.createClass(room.name, room.classCode); try { From 5a06e708020671945d60bafb626abd9ee35fc32d Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 16 Jul 2024 14:36:15 -0400 Subject: [PATCH 48/51] Do not create welcome chats under any circumstance --- lib/pages/new_space/new_space.dart | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/lib/pages/new_space/new_space.dart b/lib/pages/new_space/new_space.dart index d5e41410d..57462a698 100644 --- a/lib/pages/new_space/new_space.dart +++ b/lib/pages/new_space/new_space.dart @@ -198,18 +198,6 @@ class NewSpaceController extends State { if (capacity != null && space != null) { space.updateRoomCapacity(capacity); } - // If space has no parents, add welcome chat - String? newChatRoomId; - if (space?.pangeaSpaceParents.isEmpty ?? false) { - newChatRoomId = await Matrix.of(context).client.createGroupChat( - enableEncryption: false, - preset: sdk.CreateRoomPreset.publicChat, - // Welcome chat name is '[space name acronym]: Welcome Chat' - groupName: - '${nameController.text.trim().split(RegExp(r"\s+")).map((s) => s[0]).join()}: ${L10n.of(context)!.classWelcomeChat}', - ); - GoogleAnalytics.createChat(newChatRoomId); - } final Room? room = Matrix.of(context).client.getRoomById(spaceId); if (room == null) { @@ -221,14 +209,6 @@ class NewSpaceController extends State { return; } - if (newChatRoomId != null) { - room.setSpaceChild(newChatRoomId, suggested: true); - GoogleAnalytics.addParent( - newChatRoomId, - room.classCode, - ); - } - GoogleAnalytics.createClass(room.name, room.classCode); try { await room.invite(BotName.byEnvironment); From c9e023e684a628fc51418ffdc24971dc2f2d50be Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 16 Jul 2024 15:28:56 -0400 Subject: [PATCH 49/51] rate limited builds for unread room badge, don't call to load chat counts until it's needed for the UI --- lib/pages/chat_list/space_view.dart | 20 +++++++++++++++++--- lib/widgets/unread_rooms_badge.dart | 9 ++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index b57e4bdd6..954a19c14 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -451,9 +451,22 @@ class _SpaceViewState extends State { // #Pangea Future loadChatCounts() async { - for (final Room room in Matrix.of(context).client.rooms) { - if (room.isSpace && !chatCounts.containsKey(room.id)) { - await loadHierarchy(null, room.id); + // if not in the call spaces view, don't load chat count yet + if (widget.controller.activeSpaceId != null) return; + + final List allSpaces = + Matrix.of(context).client.rooms.where((room) => room.isSpace).toList(); + + for (final Room space in allSpaces) { + // check if the space is visible in the all spaces list + final bool isRootSpace = !allSpaces.any( + (parentSpace) => + parentSpace.spaceChildren.any((child) => child.roomId == space.id), + ); + + // if it's visible, and it hasn't been loaded yet, load chat count + if (isRootSpace && !chatCounts.containsKey(space.id)) { + await loadHierarchy(null, space.id); } } } @@ -479,6 +492,7 @@ class _SpaceViewState extends State { event.isSpaceChildUpdate( widget.controller.activeSpaceId!, )) { + debugPrint("refresh on update"); await loadHierarchy(); } setState(() => refreshing = false); diff --git a/lib/widgets/unread_rooms_badge.dart b/lib/widgets/unread_rooms_badge.dart index 332fa42a1..9104b0f07 100644 --- a/lib/widgets/unread_rooms_badge.dart +++ b/lib/widgets/unread_rooms_badge.dart @@ -1,6 +1,6 @@ -import 'package:flutter/material.dart'; - import 'package:badges/badges.dart' as b; +import 'package:fluffychat/utils/stream_extension.dart'; +import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; import 'matrix.dart'; @@ -24,7 +24,10 @@ class UnreadRoomsBadge extends StatelessWidget { .client .onSync .stream - .where((syncUpdate) => syncUpdate.hasRoomUpdate), + .where((syncUpdate) => syncUpdate.hasRoomUpdate) + // #Pangea + .rateLimit(const Duration(seconds: 1)), + // Pangea# builder: (context, _) { // #Pangea // final unreadCount = Matrix.of(context) From 4f1445d975fb4c0e116eee8f733dbe623e1cca8c Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 17 Jul 2024 09:21:55 -0400 Subject: [PATCH 50/51] fixes for null check / disposed widget errors --- lib/pages/chat/chat.dart | 2 +- lib/pages/chat_list/space_view.dart | 33 +++++++++++++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 8107993da..4f25d8a70 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -416,7 +416,7 @@ class ChatController extends State onInsert: onInsert, ); // #Pangea - if (visibleEvents.length < 10) { + if (visibleEvents.length < 10 && timeline != null) { int prevNumEvents = timeline!.events.length; await requestHistory(); int numRequests = 0; diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 954a19c14..e625289c5 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -130,19 +130,27 @@ class _SpaceViewState extends State { if (prevBatch != null) { response.rooms.insertAll(0, _lastResponse[activeSpaceId]?.rooms ?? []); } - setState(() { - _lastResponse[activeSpaceId] = response; - }); + // #Pangea + if (mounted) { + // Pangea# + setState(() { + _lastResponse[activeSpaceId] = response; + }); + } return _lastResponse[activeSpaceId]!; } catch (e) { - setState(() { - error = e; - }); + // #Pangea + if (mounted) { + // Pangea# + setState(() { + error = e; + }); + } rethrow; } finally { // #Pangea if (activeSpace != null) { - await setChatCount( + setChatCount( activeSpace, _lastResponse[activeSpaceId] ?? GetSpaceHierarchyResponse( @@ -150,10 +158,12 @@ class _SpaceViewState extends State { ), ); } - // Pangea# - setState(() { - loading = false; - }); + if (mounted) { + // Pangea# + setState(() { + loading = false; + }); + } } } @@ -499,6 +509,7 @@ class _SpaceViewState extends State { } bool includeSpaceChild(sc, matchingSpaceChildren) { + if (!mounted) return false; final bool isAnalyticsRoom = sc.roomType == PangeaRoomTypes.analytics; final bool isMember = [Membership.join, Membership.invite] .contains(Matrix.of(context).client.getRoomById(sc.roomId)?.membership); From 4adbb1b3357088bb8306dd2cfa3aa7df5f1b3b15 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 17 Jul 2024 09:27:58 -0400 Subject: [PATCH 51/51] commented out pangea room rules editor in new space and chat details view --- lib/pages/chat_details/chat_details_view.dart | 14 ++-- lib/pages/new_space/new_space_view.dart | 66 +++++++++---------- 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index a51304b39..8b496afab 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -9,7 +9,6 @@ import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_det import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_invitation_buttons.dart'; import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_name_button.dart'; import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart'; -import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_rules_editor.dart'; import 'package:fluffychat/pangea/utils/lock_room.dart'; import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart'; import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart'; @@ -269,11 +268,14 @@ class ChatDetailsView extends StatelessWidget { // roomId: controller.roomId, // startOpen: false, // ), - if (room.pangeaRoomRules != null) - RoomRulesEditor( - roomId: controller.roomId, - startOpen: false, - ), + + // Commenting out pangea room rules for now + // if (room.pangeaRoomRules != null) + // RoomRulesEditor( + // roomId: controller.roomId, + // startOpen: false, + // ), + // if (!room.canChangeStateEvent(EventTypes.RoomTopic)) // ListTile( // title: Text( diff --git a/lib/pages/new_space/new_space_view.dart b/lib/pages/new_space/new_space_view.dart index 22751a787..9ef9c9849 100644 --- a/lib/pages/new_space/new_space_view.dart +++ b/lib/pages/new_space/new_space_view.dart @@ -1,12 +1,8 @@ import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; -import 'package:fluffychat/pangea/models/space_model.dart'; import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart'; -import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_rules_editor.dart'; import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -133,35 +129,39 @@ class NewSpaceView extends StatelessWidget { startOpen: true, spaceMode: true, ), - if (controller.rulesEditorKey.currentState != null) - RoomRulesEditor( - key: controller.rulesEditorKey, - roomId: null, - startOpen: false, - initialRules: controller.rulesEditorKey.currentState!.rules, - ), - if (controller.rulesEditorKey.currentState == null) - FutureBuilder( - future: Matrix.of(context).client.lastUpdatedRoomRules, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - return RoomRulesEditor( - key: controller.rulesEditorKey, - roomId: null, - startOpen: false, - initialRules: snapshot.data, - ); - } else { - return const Padding( - padding: EdgeInsets.all(16.0), - child: Center( - child: - CircularProgressIndicator.adaptive(strokeWidth: 2), - ), - ); - } - }, - ), + // Commenting out pangea room rules for now + // if (controller.rulesEditorKey.currentState != null) + // RoomRulesEditor( + // key: controller.rulesEditorKey, + // roomId: null, + // startOpen: false, + // initialRules: controller.rulesEditorKey.currentState!.rules, + // ), + + // Commenting out pangea room rules for now + // if (controller.rulesEditorKey.currentState == null) + // FutureBuilder( + // future: Matrix.of(context).client.lastUpdatedRoomRules, + // builder: (context, snapshot) { + // if (snapshot.connectionState == ConnectionState.done) { + // return RoomRulesEditor( + // key: controller.rulesEditorKey, + // roomId: null, + // startOpen: false, + // initialRules: snapshot.data, + // ); + // } else { + // return const Padding( + // padding: EdgeInsets.all(16.0), + // child: Center( + // child: + // CircularProgressIndicator.adaptive(strokeWidth: 2), + // ), + // ); + // } + // }, + // ), + // SwitchListTile.adaptive( // title: Text(L10n.of(context)!.spaceIsPublic), // value: controller.publicGroup,