diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 9ad418e36..19eb99149 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -7,7 +7,6 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/send_file_dialog.dart'; import 'package:fluffychat/pages/chat_list/chat_list_view.dart'; 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/chat_list_handle_space_tap.dart'; @@ -1016,7 +1015,7 @@ class ChatListController extends State } // #Pangea - await _initPangeaControllers(client); + _initPangeaControllers(client); // Pangea# if (!mounted) return; setState(() { @@ -1025,22 +1024,12 @@ class ChatListController extends State } // #Pangea - Future _initPangeaControllers(Client client) async { - MatrixState.pangeaController.putAnalytics.initialize(); - MatrixState.pangeaController.getAnalytics.initialize(); + void _initPangeaControllers(Client client) { + GoogleAnalytics.analyticsUserUpdate(client.userID); + client.migrateAnalyticsRooms(); + MatrixState.pangeaController.initControllers(); if (mounted) { - final PangeaController pangeaController = MatrixState.pangeaController; - GoogleAnalytics.analyticsUserUpdate(client.userID); - pangeaController.startChatWithBotIfNotPresent(); - await pangeaController.subscriptionController.initialize(); - pangeaController.afterSyncAndFirstLoginInitialization(context); - await pangeaController.inviteBotToExistingSpaces(); - await pangeaController.setPangeaPushRules(); - client.migrateAnalyticsRooms(); - } else { - ErrorHandler.logError( - m: "didn't run afterSyncAndFirstLoginInitialization because not mounted", - ); + MatrixState.pangeaController.classController.joinCachedSpaceCode(context); } } // Pangea# diff --git a/lib/pangea/controllers/class_controller.dart b/lib/pangea/controllers/class_controller.dart index 0e51e700c..df0a699bc 100644 --- a/lib/pangea/controllers/class_controller.dart +++ b/lib/pangea/controllers/class_controller.dart @@ -42,7 +42,7 @@ class ClassController extends BaseController { ); } - Future checkForClassCodeAndSubscription(BuildContext context) async { + Future joinCachedSpaceCode(BuildContext context) async { final String? classCode = _pangeaController.pStoreService.read( PLocalKey.cachedClassCodeToJoin, isAccountData: false, @@ -53,6 +53,7 @@ class ClassController extends BaseController { context, classCode, ); + await _pangeaController.pStoreService.delete( PLocalKey.cachedClassCodeToJoin, isAccountData: false, diff --git a/lib/pangea/controllers/get_analytics_controller.dart b/lib/pangea/controllers/get_analytics_controller.dart index ed4568b65..e9bb72fdd 100644 --- a/lib/pangea/controllers/get_analytics_controller.dart +++ b/lib/pangea/controllers/get_analytics_controller.dart @@ -25,6 +25,7 @@ class GetAnalyticsController { StreamController.broadcast(); ConstructListModel constructListModel = ConstructListModel(uses: []); + Completer? initCompleter; GetAnalyticsController(PangeaController pangeaController) { _pangeaController = pangeaController; @@ -34,13 +35,19 @@ class GetAnalyticsController { Client get _client => _pangeaController.matrixState.client; // the minimum XP required for a given level - double get _minXPForLevel { - return 12.5 * (2 * pow(constructListModel.level - 1, 2) - 1); + int get _minXPForLevel { + return _calculateMinXpForLevel(constructListModel.level); } // the minimum XP required for the next level - double get _minXPForNextLevel { - return 12.5 * (2 * pow(constructListModel.level, 2) - 1); + int get _minXPForNextLevel { + return _calculateMinXpForLevel(constructListModel.level + 1); + } + + /// Calculates the minimum XP required for a specific level. + int _calculateMinXpForLevel(int level) { + if (level == 1) return 0; // Ensure level 1 starts at 0 XP + return ((100 / 8) * (2 * pow(level - 1, 2))).floor(); } // the progress within the current level as a percentage (0.0 to 1.0) @@ -50,20 +57,23 @@ class GetAnalyticsController { return progress >= 0 ? progress : 0; } - void initialize() { + Future initialize() async { + if (initCompleter != null) return; + initCompleter = Completer(); + _analyticsUpdateSubscription ??= _pangeaController .putAnalytics.analyticsUpdateStream.stream .listen(_onAnalyticsUpdate); - _pangeaController.putAnalytics.lastUpdatedCompleter.future.then((_) { - _getConstructs().then((_) { - constructListModel.updateConstructs([ - ...(_getConstructsLocal() ?? []), - ..._locallyCachedConstructs, - ]); - _updateAnalyticsStream(); - }); - }); + await _pangeaController.putAnalytics.lastUpdatedCompleter.future; + await _getConstructs(); + constructListModel.updateConstructs([ + ...(_getConstructsLocal() ?? []), + ..._locallyCachedConstructs, + ]); + _updateAnalyticsStream(); + + initCompleter!.complete(); } /// Clear all cached analytics data. @@ -71,6 +81,7 @@ class GetAnalyticsController { constructListModel.dispose(); _analyticsUpdateSubscription?.cancel(); _analyticsUpdateSubscription = null; + initCompleter = null; _cache.clear(); } diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index 3e737113d..f09457d34 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -28,7 +28,6 @@ import 'package:fluffychat/pangea/utils/bot_name.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/instructions.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; @@ -80,11 +79,19 @@ class PangeaController { _addRefInObjects(); } - Future afterSyncAndFirstLoginInitialization( - BuildContext context, - ) async { - await classController.checkForClassCodeAndSubscription(context); + /// Initializes various controllers and settings. + /// While many of these functions are asynchronous, they are not awaited here, + /// because of order of execution does not matter, + /// and running them at the same times speeds them up. + void initControllers() { + putAnalytics.initialize(); + getAnalytics.initialize(); + subscriptionController.initialize(); classController.fixClassPowerLevels(); + + startChatWithBotIfNotPresent(); + inviteBotToExistingSpaces(); + setPangeaPushRules(); } /// Initialize controllers @@ -243,13 +250,23 @@ class PangeaController { ], ); - final room = matrixState.client.getRoomById(roomId); + Room? room = matrixState.client.getRoomById(roomId); if (room == null || room.membership != Membership.join) { // Wait for room actually appears in sync await matrixState.client.waitForRoomInSync(roomId, join: true); + room = matrixState.client.getRoomById(roomId); + if (room == null) { + ErrorHandler.logError( + e: "Bot chat null after waiting for room in sync", + data: { + "roomId": roomId, + }, + ); + return null; + } } - final botOptions = room!.getState(PangeaEventTypes.botOptions); + final botOptions = room.getState(PangeaEventTypes.botOptions); if (botOptions == null) { await matrixState.client.setRoomStateWithKey( roomId, @@ -277,7 +294,10 @@ class PangeaController { } final Room botDMWithLatestActivity = botDMs.reduce((a, b) { - if (a.timeline == null || b.timeline == null) { + if (a.timeline == null || + b.timeline == null || + a.timeline!.events.isEmpty || + b.timeline!.events.isEmpty) { return a; } final aLastEvent = a.timeline!.events.last; diff --git a/lib/pangea/controllers/put_analytics_controller.dart b/lib/pangea/controllers/put_analytics_controller.dart index 695c524fd..f484520e8 100644 --- a/lib/pangea/controllers/put_analytics_controller.dart +++ b/lib/pangea/controllers/put_analytics_controller.dart @@ -146,7 +146,7 @@ class PutAnalyticsController extends BaseController { ); } - Future _onUpdateLanguages(String previousL2) async { + Future _onUpdateLanguages(String? previousL2) async { await sendLocalAnalyticsToAnalyticsRoom( l2Override: previousL2, ); diff --git a/lib/pangea/pages/sign_up/signup.dart b/lib/pangea/pages/sign_up/signup.dart index 7bbd055c0..f62108027 100644 --- a/lib/pangea/pages/sign_up/signup.dart +++ b/lib/pangea/pages/sign_up/signup.dart @@ -137,11 +137,11 @@ class SignupPageController extends State { displayname, ); } - } catch (e) { + } catch (e, s) { //#Pangea const cancelledString = "Exception: Request has been canceled"; if (e.toString() != cancelledString) { - ErrorHandler.logError(e: e); + ErrorHandler.logError(e: e, s: s); error = (e).toLocalizedString(context); } // Pangea# diff --git a/lib/pangea/utils/download_chat.dart b/lib/pangea/utils/download_chat.dart index ab0fee8a2..96d941120 100644 --- a/lib/pangea/utils/download_chat.dart +++ b/lib/pangea/utils/download_chat.dart @@ -41,13 +41,8 @@ Future downloadChat( timeline, room, ); - } catch (err) { - ErrorHandler.logError( - e: Exception( - "Failed to fetch messages for chat ${room.id} in while downloading chat", - ), - s: StackTrace.current, - ); + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( @@ -214,12 +209,9 @@ Future downloadFile( directory = await getExternalStorageDirectory(); } } - } catch (err) { + } catch (err, s) { debugPrint("Failed to get download folder path"); - ErrorHandler.logError( - e: Exception("Failed to get download folder path"), - s: StackTrace.current, - ); + ErrorHandler.logError(e: err, s: s); } if (directory != null) { final File f = File("${directory.path}/$filename"); diff --git a/lib/pangea/widgets/chat/overlay_message_text.dart b/lib/pangea/widgets/chat/overlay_message_text.dart index cb85d4c31..6f2b3fb12 100644 --- a/lib/pangea/widgets/chat/overlay_message_text.dart +++ b/lib/pangea/widgets/chat/overlay_message_text.dart @@ -1,5 +1,4 @@ import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart'; @@ -24,26 +23,24 @@ class OverlayMessageText extends StatefulWidget { } class OverlayMessageTextState extends State { - final PangeaController pangeaController = MatrixState.pangeaController; - List? tokens; + List? _tokens; @override void initState() { + super.initState(); + _setTokens(); + } + + Future _setTokens() async { final repEvent = widget.pangeaMessageEvent.messageDisplayRepresentation; if (repEvent != null) { - tokens = repEvent.tokens; - if (tokens == null) { - repEvent - .tokensGlobal( - widget.pangeaMessageEvent.senderId, - widget.pangeaMessageEvent.originServerTs, - ) - .then((tokens) { - setState(() => this.tokens = tokens); - }); - } + _tokens = repEvent.tokens; + _tokens ??= await repEvent.tokensGlobal( + widget.pangeaMessageEvent.senderId, + widget.pangeaMessageEvent.originServerTs, + ); + if (mounted) setState(() {}); } - super.initState(); } @override @@ -60,7 +57,7 @@ class OverlayMessageTextState extends State { fontSize: AppConfig.messageFontSize * AppConfig.fontSizeFactor, ); - if (tokens == null || tokens!.isEmpty) { + if (_tokens == null || _tokens!.isEmpty) { return Text( widget.pangeaMessageEvent.event.calcLocalizedBodyFallback( MatrixLocals(L10n.of(context)!), @@ -83,8 +80,8 @@ class OverlayMessageTextState extends State { final List tokenPositions = []; int globalIndex = 0; - for (int i = 0; i < tokens!.length; i++) { - final token = tokens![i]; + for (int i = 0; i < _tokens!.length; i++) { + final token = _tokens![i]; final start = token.start; final end = token.end; diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index 29ce30078..a96df6f91 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -36,6 +36,13 @@ class LearningProgressIndicatorsState @override void initState() { super.initState(); + + // if getAnalytics has already finished initializing, + // the data is loaded and should be displayed. + if (MatrixState.pangeaController.getAnalytics.initCompleter?.isCompleted ?? + false) { + updateData(null); + } _analyticsSubscription = MatrixState .pangeaController.getAnalytics.analyticsStream.stream .listen(updateData); @@ -48,7 +55,7 @@ class LearningProgressIndicatorsState super.dispose(); } - void updateData(AnalyticsStreamUpdate _) { + void updateData(AnalyticsStreamUpdate? _) { if (_loading) _loading = false; if (mounted) setState(() {}); }