diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart index 50d46c08e..ea81c842c 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart @@ -1,12 +1,10 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/pages/chat_permissions_settings/chat_permissions_settings.dart'; import 'package:fluffychat/pages/chat_permissions_settings/permission_list_tile.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'; +import 'package:matrix/matrix.dart'; class ChatPermissionsSettingsView extends StatelessWidget { final ChatPermissionsSettingsController controller; @@ -35,7 +33,10 @@ class ChatPermissionsSettingsView extends StatelessWidget { room.getState(EventTypes.RoomPowerLevels)?.content ?? {}, ); final powerLevels = Map.from(powerLevelsContent) - ..removeWhere((k, v) => v is! int); + // #Pangea + // ..removeWhere((k, v) => v is! int); + ..removeWhere((k, v) => v is! int || k.equals("m.call.invite")); + // Pangea# final eventsPowerLevels = Map.from( powerLevelsContent.tryGetMap('events') ?? {}, )..removeWhere((k, v) => v is! int); diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index 2b88e7195..b882e4087 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -3,9 +3,9 @@ import 'dart:developer'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; +import 'package:fluffychat/pangea/controllers/span_data_controller.dart'; import 'package:fluffychat/pangea/models/igc_text_data_model.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; -import 'package:fluffychat/pangea/models/span_data.dart'; import 'package:fluffychat/pangea/repo/igc_repo.dart'; import 'package:fluffychat/pangea/widgets/igc/span_card.dart'; import 'package:flutter/foundation.dart'; @@ -14,41 +14,19 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import '../../models/language_detection_model.dart'; import '../../models/span_card_model.dart'; -import '../../repo/span_data_repo.dart'; import '../../repo/tokens_repo.dart'; import '../../utils/error_handler.dart'; import '../../utils/overlay.dart'; -class _SpanDetailsCacheItem { - Future data; - - _SpanDetailsCacheItem({required this.data}); -} - class IgcController { Choreographer choreographer; IGCTextData? igcTextData; Object? igcError; - Completer igcCompleter = Completer(); - final Map _cache = {}; - Timer? _cacheClearTimer; + late SpanDataController spanDataController; IgcController(this.choreographer) { - _initializeCacheClearing(); - } - - void _initializeCacheClearing() { - const duration = Duration(minutes: 2); - _cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache()); - } - - void _clearCache() { - _cache.clear(); - } - - void dispose() { - _cacheClearTimer?.cancel(); + spanDataController = SpanDataController(choreographer); } Future getIGCTextData({required bool tokensOnly}) async { @@ -56,10 +34,8 @@ class IgcController { if (choreographer.currentText.isEmpty) return clear(); // the error spans are going to be reloaded, so clear the cache - _clearCache(); - + spanDataController.clearCache(); debugPrint('getIGCTextData called with ${choreographer.currentText}'); - debugPrint('getIGCTextData called with tokensOnly = $tokensOnly'); final IGCRequestBody reqBody = IGCRequestBody( @@ -110,7 +86,7 @@ class IgcController { // This will make the loading of span details faster for the user if (igcTextData?.matches.isNotEmpty ?? false) { for (int i = 0; i < igcTextData!.matches.length; i++) { - getSpanDetails(i); + spanDataController.getSpanDetails(i); } } @@ -125,56 +101,6 @@ class IgcController { } } - Future getSpanDetails(int matchIndex) async { - if (igcTextData == null || - igcTextData!.matches.isEmpty || - matchIndex < 0 || - matchIndex >= igcTextData!.matches.length) { - debugger(when: kDebugMode); - return; - } - - /// Retrieves the span data from the `igcTextData` matches at the specified `matchIndex`. - /// Creates a `SpanDetailsRepoReqAndRes` object with the retrieved span data and other parameters. - /// Generates a cache key based on the created `SpanDetailsRepoReqAndRes` object. - final SpanData span = igcTextData!.matches[matchIndex].match; - final req = SpanDetailsRepoReqAndRes( - userL1: choreographer.l1LangCode!, - userL2: choreographer.l2LangCode!, - enableIGC: choreographer.igcEnabled, - enableIT: choreographer.itEnabled, - span: span, - ); - final int cacheKey = req.hashCode; - - /// Retrieves the [SpanDetailsRepoReqAndRes] response from the cache if it exists, - /// otherwise makes an API call to get the response and stores it in the cache. - Future response; - if (_cache.containsKey(cacheKey)) { - response = _cache[cacheKey]!.data; - } else { - response = SpanDataRepo.getSpanDetails( - await choreographer.accessToken, - request: SpanDetailsRepoReqAndRes( - userL1: choreographer.l1LangCode!, - userL2: choreographer.l2LangCode!, - enableIGC: choreographer.igcEnabled, - enableIT: choreographer.itEnabled, - span: span, - ), - ); - _cache[cacheKey] = _SpanDetailsCacheItem(data: response); - } - - try { - igcTextData!.matches[matchIndex].match = (await response).span; - } catch (err, s) { - ErrorHandler.logError(e: err, s: s); - } - - choreographer.setState(); - } - Future justGetTokensAndAddThemToIGCTextData() async { try { if (igcTextData == null) { @@ -290,7 +216,7 @@ class IgcController { clear() { igcTextData = null; - _clearCache(); + spanDataController.clearCache(); // Not sure why this is here // MatrixState.pAnyState.closeOverlay(); } diff --git a/lib/pangea/controllers/span_data_controller.dart b/lib/pangea/controllers/span_data_controller.dart new file mode 100644 index 000000000..5f83f1a53 --- /dev/null +++ b/lib/pangea/controllers/span_data_controller.dart @@ -0,0 +1,89 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; +import 'package:fluffychat/pangea/models/span_data.dart'; +import 'package:fluffychat/pangea/repo/span_data_repo.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:flutter/foundation.dart'; + +class _SpanDetailsCacheItem { + Future data; + + _SpanDetailsCacheItem({required this.data}); +} + +class SpanDataController { + late Choreographer choreographer; + final Map _cache = {}; + Timer? _cacheClearTimer; + + SpanDataController(this.choreographer) { + _initializeCacheClearing(); + } + + void _initializeCacheClearing() { + const duration = Duration(minutes: 2); + _cacheClearTimer = Timer.periodic(duration, (Timer t) => clearCache()); + } + + void clearCache() { + _cache.clear(); + } + + void dispose() { + _cacheClearTimer?.cancel(); + } + + Future getSpanDetails(int matchIndex) async { + if (choreographer.igc.igcTextData == null || + choreographer.igc.igcTextData!.matches.isEmpty || + matchIndex < 0 || + matchIndex >= choreographer.igc.igcTextData!.matches.length) { + debugger(when: kDebugMode); + return; + } + + /// Retrieves the span data from the `igcTextData` matches at the specified `matchIndex`. + /// Creates a `SpanDetailsRepoReqAndRes` object with the retrieved span data and other parameters. + /// Generates a cache key based on the created `SpanDetailsRepoReqAndRes` object. + final SpanData span = + choreographer.igc.igcTextData!.matches[matchIndex].match; + final req = SpanDetailsRepoReqAndRes( + userL1: choreographer.l1LangCode!, + userL2: choreographer.l2LangCode!, + enableIGC: choreographer.igcEnabled, + enableIT: choreographer.itEnabled, + span: span, + ); + final int cacheKey = req.hashCode; + + /// Retrieves the [SpanDetailsRepoReqAndRes] response from the cache if it exists, + /// otherwise makes an API call to get the response and stores it in the cache. + Future response; + if (_cache.containsKey(cacheKey)) { + response = _cache[cacheKey]!.data; + } else { + response = SpanDataRepo.getSpanDetails( + await choreographer.accessToken, + request: SpanDetailsRepoReqAndRes( + userL1: choreographer.l1LangCode!, + userL2: choreographer.l2LangCode!, + enableIGC: choreographer.igcEnabled, + enableIT: choreographer.itEnabled, + span: span, + ), + ); + _cache[cacheKey] = _SpanDetailsCacheItem(data: response); + } + + try { + choreographer.igc.igcTextData!.matches[matchIndex].match = + (await response).span; + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); + } + + choreographer.setState(); + } +} diff --git a/lib/pangea/models/span_data.dart b/lib/pangea/models/span_data.dart index d420e1ffd..bf8ab8eca 100644 --- a/lib/pangea/models/span_data.dart +++ b/lib/pangea/models/span_data.dart @@ -148,6 +148,30 @@ class SpanChoice { bool get isBestCorrection => type == SpanChoiceType.bestCorrection; Color get color => type.color; + + // override == operator and hashcode + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is SpanChoice && + other.value == value && + other.type.toString() == type.toString() && + other.selected == selected && + other.feedback == feedback && + other.timestamp?.toIso8601String() == timestamp?.toIso8601String(); + } + + @override + int get hashCode { + return Object.hashAll([ + value.hashCode, + type.toString().hashCode, + selected.hashCode, + feedback.hashCode, + timestamp?.toIso8601String().hashCode, + ]); + } } class Rule { diff --git a/lib/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart b/lib/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart index ca876fae8..7be6e8ec3 100644 --- a/lib/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart +++ b/lib/pangea/pages/class_settings/p_class_widgets/room_capacity_button.dart @@ -72,8 +72,7 @@ class RoomCapacityButtonState extends State { return Column( children: [ ListTile( - onTap: () => - ((widget.room?.isRoomAdmin ?? true) ? (setRoomCapacity()) : null), + onTap: (widget.room?.isRoomAdmin ?? true) ? setRoomCapacity : null, leading: CircleAvatar( backgroundColor: Theme.of(context).scaffoldBackgroundColor, foregroundColor: iconColor, diff --git a/lib/pangea/pages/settings_learning/settings_learning.dart b/lib/pangea/pages/settings_learning/settings_learning.dart index c560fef57..4c4fe4d50 100644 --- a/lib/pangea/pages/settings_learning/settings_learning.dart +++ b/lib/pangea/pages/settings_learning/settings_learning.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:country_picker/country_picker.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning_view.dart'; import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart'; @@ -37,6 +38,13 @@ class SettingsLearningController extends State { setState(() {}); } + Future changeCountry(Country country) async { + await pangeaController.userController.updateUserProfile( + country: country.displayNameNoCountryCode, + ); + setState(() {}); + } + @override void dispose() { super.dispose(); diff --git a/lib/pangea/pages/settings_learning/settings_learning_view.dart b/lib/pangea/pages/settings_learning/settings_learning_view.dart index 910cc611f..ca337222a 100644 --- a/lib/pangea/pages/settings_learning/settings_learning_view.dart +++ b/lib/pangea/pages/settings_learning/settings_learning_view.dart @@ -32,7 +32,7 @@ class SettingsLearningView extends StatelessWidget { child: Column( children: [ LanguageTile(controller), - CountryPickerTile(), + CountryPickerTile(controller), const SizedBox(height: 8), const Divider(height: 1), const SizedBox(height: 8), diff --git a/lib/pangea/repo/span_data_repo.dart b/lib/pangea/repo/span_data_repo.dart index f06363fd1..3c2eb2ab3 100644 --- a/lib/pangea/repo/span_data_repo.dart +++ b/lib/pangea/repo/span_data_repo.dart @@ -85,16 +85,13 @@ class SpanDetailsRepoReqAndRes { if (other.userL2 != userL2) return false; if (other.enableIT != enableIT) return false; if (other.enableIGC != enableIGC) return false; - if (other.span.choices - ?.firstWhere( - (choice) => choice.type == SpanChoiceType.bestCorrection, - ) - .value != - span.choices - ?.firstWhere( - (choice) => choice.type == SpanChoiceType.bestCorrection, - ) - .value) return false; + if (const ListEquality().equals( + other.span.choices?.sorted((a, b) => b.value.compareTo(a.value)), + span.choices?.sorted((a, b) => b.value.compareTo(a.value)), + ) == + false) { + return false; + } return true; } @@ -107,12 +104,12 @@ class SpanDetailsRepoReqAndRes { userL2.hashCode, enableIT.hashCode, enableIGC.hashCode, - span.choices - ?.firstWhereOrNull( - (choice) => choice.type == SpanChoiceType.bestCorrection, - ) - ?.value - .hashCode, + if (span.choices != null) + Object.hashAll( + span.choices! + .sorted((a, b) => b.value.compareTo(a.value)) + .map((choice) => choice.hashCode), + ), ]); } } diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index 0d149eaec..6fa4e17b3 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -94,7 +94,8 @@ class SpanCardState extends State { fetchingData = true; }); - await widget.scm.choreographer.igc.getSpanDetails(widget.scm.matchIndex); + await widget.scm.choreographer.igc.spanDataController + .getSpanDetails(widget.scm.matchIndex); if (mounted) { setState(() => fetchingData = false); diff --git a/lib/pangea/widgets/user_settings/country_picker_tile.dart b/lib/pangea/widgets/user_settings/country_picker_tile.dart index ec62374e9..63677cc68 100644 --- a/lib/pangea/widgets/user_settings/country_picker_tile.dart +++ b/lib/pangea/widgets/user_settings/country_picker_tile.dart @@ -1,20 +1,21 @@ 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/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; - -import 'package:country_picker/country_picker.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import '../../models/user_model.dart'; class CountryPickerTile extends StatelessWidget { + final SettingsLearningController learningController; final PangeaController pangeaController = MatrixState.pangeaController; - CountryPickerTile({super.key}); + CountryPickerTile(this.learningController, {super.key}); @override Widget build(BuildContext context) { @@ -33,9 +34,7 @@ class CountryPickerTile extends StatelessWidget { context: context, future: () async { try { - await pangeaController.userController.updateUserProfile( - country: country.displayNameNoCountryCode, - ); + learningController.changeCountry(country); } catch (err) { debugger(when: kDebugMode); }