From 77fb4bcf4e5311a837e2e7e33d032cef0099f6b2 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:40:41 -0400 Subject: [PATCH 01/23] Revert "base timer off of game state event" --- lib/pages/chat/chat.dart | 44 +++-- lib/pages/chat/chat_event_list.dart | 9 +- lib/pages/chat/chat_view.dart | 3 +- lib/pangea/constants/game_constants.dart | 3 - lib/pangea/constants/model_keys.dart | 4 - lib/pangea/constants/pangea_event_types.dart | 2 - lib/pangea/models/game_state_model.dart | 48 ------ .../pages/games/story_game/round_model.dart | 123 +++++-------- lib/pangea/widgets/chat/round_timer.dart | 161 ++++++++---------- 9 files changed, 131 insertions(+), 266 deletions(-) delete mode 100644 lib/pangea/constants/game_constants.dart delete mode 100644 lib/pangea/models/game_state_model.dart diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 79824d662..6e6a7487f 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -15,12 +15,10 @@ import 'package:fluffychat/pages/chat/event_info_dialog.dart'; import 'package:fluffychat/pages/chat/recording_dialog.dart'; import 'package:fluffychat/pages/chat_details/chat_details.dart'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; -import 'package:fluffychat/pangea/constants/pangea_event_types.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/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/choreo_record.dart'; -import 'package:fluffychat/pangea/models/game_state_model.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/pages/games/story_game/round_model.dart'; @@ -28,6 +26,7 @@ import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/utils/report_message.dart'; import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; +import 'package:fluffychat/pangea/widgets/chat/round_timer.dart'; import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart'; import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; @@ -116,9 +115,17 @@ class ChatController extends State // #Pangea final PangeaController pangeaController = MatrixState.pangeaController; late Choreographer choreographer = Choreographer(pangeaController, this); + final GlobalKey roundTimerStateKey = + GlobalKey(); + RoundTimer? timer; - /// Model of the current story game round - GameRoundModel? currentRound; + final List gameRounds = []; + + List get completedRoundEventIds => gameRounds + .where((round) => round.isCompleted) + .map((round) => round.userMessageIDs) + .expand((x) => x) + .toList(); // Pangea# Room get room => sendingClient.getRoomById(roomId) ?? widget.room; @@ -301,22 +308,12 @@ class ChatController extends State } // #Pangea - /// Recursive function that sets the current round, waits for it to - /// finish, sets it, etc. until the chat view is no longer mounted. - void setRound() { - currentRound?.dispose(); - currentRound = GameRoundModel(room: room); - room.client.onRoomState.stream.firstWhere((update) { - if (update.roomId != roomId) return false; - if (update.state is! Event) return false; - if ((update.state as Event).type != PangeaEventTypes.storyGame) { - return false; - } - - final game = GameModel.fromJson((update.state as Event).content); - return game.previousRoundEndTime != null; - }).then((_) { - if (mounted) setRound(); + void addRound() { + debugPrint("ADDING A ROUND. Rounds so far: ${gameRounds.length}"); + final newRound = GameRoundModel(controller: this, timer: timer!); + gameRounds.add(newRound); + newRound.roundCompleter.future.then((_) { + if (mounted) addRound(); }); } // Pangea# @@ -336,7 +333,8 @@ class ChatController extends State sendingClient = Matrix.of(context).client; WidgetsBinding.instance.addObserver(this); // #Pangea - setRound(); + timer = RoundTimer(key: roundTimerStateKey); + addRound(); if (!mounted) return; Future.delayed(const Duration(seconds: 1), () async { if (!mounted) return; @@ -423,7 +421,8 @@ class ChatController extends State List get visibleEvents => timeline?.events .where( - (x) => x.isVisibleInGui, + (x) => + x.isVisibleInGui && !completedRoundEventIds.contains(x.eventId), ) .toList() ?? []; @@ -561,7 +560,6 @@ class ChatController extends State //#Pangea choreographer.stateListener.close(); choreographer.dispose(); - currentRound?.dispose(); //Pangea# super.dispose(); } diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 1e216094a..7b9003379 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -6,7 +6,6 @@ import 'package:fluffychat/pages/chat/typing_indicators.dart'; import 'package:fluffychat/pages/user_bottom_sheet/user_bottom_sheet.dart'; import 'package:fluffychat/pangea/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/utils/bot_name.dart'; import 'package:fluffychat/pangea/widgets/chat/locked_chat_message.dart'; import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; @@ -33,13 +32,7 @@ class ChatEventList extends StatelessWidget { event.isVisibleInGui // #Pangea && - // In story game, hide messages sent by non-bot users in previous round - (event.type != EventTypes.Message || - event.senderId == BotName.byEnvironment || - controller.currentRound?.previousRoundEnd == null || - event.originServerTs.isAfter( - controller.currentRound!.previousRoundEnd!, - )) + !controller.completedRoundEventIds.contains(event.eventId) // Pangea# , ) diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 876db6e68..ce45043eb 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -13,7 +13,6 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_bar.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/widgets/chat/chat_floating_action_button.dart'; -import 'package:fluffychat/pangea/widgets/chat/round_timer.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'; @@ -120,7 +119,7 @@ class ChatView extends StatelessWidget { // #Pangea } else { return [ - RoundTimer(controller: controller), + controller.timer ?? const SizedBox(), const SizedBox( width: 10, ), diff --git a/lib/pangea/constants/game_constants.dart b/lib/pangea/constants/game_constants.dart deleted file mode 100644 index 6b0b22fbb..000000000 --- a/lib/pangea/constants/game_constants.dart +++ /dev/null @@ -1,3 +0,0 @@ -class GameConstants { - static const int timerMaxSeconds = 120; -} diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart index e427cf098..b42061446 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -119,8 +119,4 @@ class ModelKey { static const String prevEventId = "prev_event_id"; static const String prevLastUpdated = "prev_last_updated"; - - static const String gameState = "game_state"; - static const String currentRoundStartTime = "start_time"; - static const String previousRoundEndTime = "message_visible_from"; } diff --git a/lib/pangea/constants/pangea_event_types.dart b/lib/pangea/constants/pangea_event_types.dart index ab5d655a7..9ca975dc0 100644 --- a/lib/pangea/constants/pangea_event_types.dart +++ b/lib/pangea/constants/pangea_event_types.dart @@ -35,6 +35,4 @@ class PangeaEventTypes { /// A record of completion of an activity. There /// can be one per user per activity. static const activityRecord = "pangea.activity_completion"; - - static const storyGame = "p.game.story"; } diff --git a/lib/pangea/models/game_state_model.dart b/lib/pangea/models/game_state_model.dart deleted file mode 100644 index 12e1bb695..000000000 --- a/lib/pangea/models/game_state_model.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'dart:developer'; - -import 'package:fluffychat/pangea/constants/model_keys.dart'; -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:flutter/foundation.dart'; -import 'package:matrix/matrix_api_lite/generated/model.dart'; - -class GameModel { - DateTime? currentRoundStartTime; - DateTime? previousRoundEndTime; - - GameModel({ - this.currentRoundStartTime, - this.previousRoundEndTime, - }); - - factory GameModel.fromJson(json) { - return GameModel( - currentRoundStartTime: json[ModelKey.currentRoundStartTime] != null - ? DateTime.parse(json[ModelKey.currentRoundStartTime]) - : null, - previousRoundEndTime: json[ModelKey.previousRoundEndTime] != null - ? DateTime.parse(json[ModelKey.previousRoundEndTime]) - : null, - ); - } - - Map toJson() { - final data = {}; - try { - data[ModelKey.currentRoundStartTime] = - currentRoundStartTime?.toIso8601String(); - data[ModelKey.previousRoundEndTime] = - previousRoundEndTime?.toIso8601String(); - return data; - } catch (e, s) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: e, s: s); - return data; - } - } - - StateEvent get toStateEvent => StateEvent( - content: toJson(), - type: PangeaEventTypes.storyGame, - ); -} diff --git a/lib/pangea/pages/games/story_game/round_model.dart b/lib/pangea/pages/games/story_game/round_model.dart index 3b43b86b5..c192c36a6 100644 --- a/lib/pangea/pages/games/story_game/round_model.dart +++ b/lib/pangea/pages/games/story_game/round_model.dart @@ -1,84 +1,62 @@ import 'dart:async'; -import 'package:fluffychat/pangea/constants/game_constants.dart'; -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; +import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/extensions/sync_update_extension.dart'; -import 'package:fluffychat/pangea/models/game_state_model.dart'; import 'package:fluffychat/pangea/utils/bot_name.dart'; +import 'package:fluffychat/pangea/widgets/chat/round_timer.dart'; +import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; -/// A model of a game round. Manages the round's state and duration. +enum RoundState { notStarted, inProgress, completed } + class GameRoundModel { - final Duration roundDuration = const Duration( - seconds: GameConstants.timerMaxSeconds, - ); + static const int timerMaxSeconds = 180; + final String adminName = BotName.byEnvironment; - final Room room; - - // All the below state variables are used for sending and managing - // round start and end times. Once the bot starts doing that, they should be removed. + final ChatController controller; + final Completer roundCompleter = Completer(); late DateTime createdAt; - Timer? timer; + RoundTimer timer; + DateTime? startTime; + DateTime? endTime; + RoundState state = RoundState.notStarted; StreamSubscription? syncSubscription; final List userMessageIDs = []; final List botMessageIDs = []; GameRoundModel({ - required this.room, + required this.controller, + required this.timer, }) { createdAt = DateTime.now(); - - // if, on creation, the current round is already ongoing, - // start the timer (or reset it if the round went over) - if (currentRoundStart != null) { - final currentRoundDuration = DateTime.now().difference( - currentRoundStart!, - ); - final roundFinished = currentRoundDuration > roundDuration; - - if (roundFinished) { - endRound(); - } - } - - // listen to syncs for new bot messages to start and stop rounds - syncSubscription ??= room.client.onSync.stream.listen(_handleSync); + syncSubscription ??= client.onSync.stream.listen(_handleSync); } - GameModel get gameState => GameModel.fromJson( - room.getState(PangeaEventTypes.storyGame)?.content ?? {}, - ); - - DateTime? get currentRoundStart => gameState.currentRoundStartTime; - DateTime? get previousRoundEnd => gameState.previousRoundEndTime; - void _handleSync(SyncUpdate update) { final newMessages = update - .messages(room) + .messages(controller.room) .where((msg) => msg.originServerTs.isAfter(createdAt)) .toList(); - final botMessages = newMessages - .where((msg) => msg.senderId == BotName.byEnvironment) - .toList(); - final userMessages = newMessages - .where((msg) => msg.senderId != BotName.byEnvironment) - .toList(); + final botMessages = + newMessages.where((msg) => msg.senderId == adminName).toList(); + final userMessages = + newMessages.where((msg) => msg.senderId != adminName).toList(); final hasNewBotMessage = botMessages.any( (msg) => !botMessageIDs.contains(msg.eventId), ); if (hasNewBotMessage) { - if (currentRoundStart == null) { + if (state == RoundState.notStarted) { startRound(); - } else { + } else if (state == RoundState.inProgress) { endRound(); return; } } - if (currentRoundStart != null) { + if (state == RoundState.inProgress) { for (final message in botMessages) { if (!botMessageIDs.contains(message.eventId)) { botMessageIDs.add(message.eventId); @@ -93,53 +71,32 @@ class GameRoundModel { } } - /// Set the start and end times of the current and previous rounds. - Future setRoundTimes({ - DateTime? currentRoundStart, - DateTime? previousRoundEnd, - }) async { - final game = GameModel.fromJson( - room.getState(PangeaEventTypes.storyGame)?.content ?? {}, - ); + Client get client => controller.pangeaController.matrixState.client; - game.currentRoundStartTime = currentRoundStart; - game.previousRoundEndTime = previousRoundEnd; + bool get isCompleted => roundCompleter.isCompleted; - await room.client.setRoomStateWithKey( - room.id, - PangeaEventTypes.storyGame, - '', - game.toJson(), - ); - } - - /// Start a new round. void startRound() { - setRoundTimes( - currentRoundStart: DateTime.now(), - previousRoundEnd: null, - ).then((_) => timer = Timer(roundDuration, endRound)); + debugPrint("starting round"); + state = RoundState.inProgress; + startTime = DateTime.now(); + controller.roundTimerStateKey.currentState?.resetTimer( + roundLength: timerMaxSeconds, + ); + controller.roundTimerStateKey.currentState?.startTimer(); } - /// End and cleanup after the current round. void endRound() { - syncSubscription?.cancel(); - syncSubscription = null; - - timer?.cancel(); - timer = null; - - setRoundTimes( - currentRoundStart: null, - previousRoundEnd: DateTime.now(), + debugPrint( + "ending round, user message IDs: $userMessageIDs, bot message IDs: $botMessageIDs", ); + endTime = DateTime.now(); + state = RoundState.completed; + controller.roundTimerStateKey.currentState?.resetTimer(); + syncSubscription?.cancel(); + roundCompleter.complete(); } void dispose() { syncSubscription?.cancel(); - syncSubscription = null; - - timer?.cancel(); - timer = null; } } diff --git a/lib/pangea/widgets/chat/round_timer.dart b/lib/pangea/widgets/chat/round_timer.dart index 5153fee12..83641983d 100644 --- a/lib/pangea/widgets/chat/round_timer.dart +++ b/lib/pangea/widgets/chat/round_timer.dart @@ -1,20 +1,17 @@ import 'dart:async'; -import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/constants/game_constants.dart'; -import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/models/game_state_model.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; /// Create a timer that counts down to the given time /// Default duration is 180 seconds class RoundTimer extends StatefulWidget { - final ChatController controller; + final int timerMaxSeconds; + final Duration roundDuration; + const RoundTimer({ super.key, - required this.controller, + this.timerMaxSeconds = 180, + this.roundDuration = const Duration(seconds: 1), }); @override @@ -23,112 +20,90 @@ class RoundTimer extends StatefulWidget { class RoundTimerState extends State { int currentSeconds = 0; - Timer? timer; - StreamSubscription? stateSubscription; + Timer? _timer; + bool isTiming = false; + Duration? duration; + int timerMaxSeconds = 180; + + void resetTimer({Duration? roundDuration, int? roundLength}) { + if (_timer != null) { + _timer!.cancel(); + isTiming = false; + } + if (roundDuration != null) { + duration = roundDuration; + } + if (roundLength != null) { + timerMaxSeconds = roundLength; + } + setState(() { + currentSeconds = 0; + }); + } + + int get remainingTime => timerMaxSeconds - currentSeconds; + + String get timerText => + '${(remainingTime ~/ 60).toString().padLeft(2, '0')}: ${(remainingTime % 60).toString().padLeft(2, '0')}'; + + startTimer() { + _timer = Timer.periodic(duration ?? widget.roundDuration, (timer) { + setState(() { + currentSeconds++; + if (currentSeconds >= timerMaxSeconds) timer.cancel(); + }); + }); + setState(() { + isTiming = true; + }); + } + + stopTimer() { + if (_timer != null) { + _timer!.cancel(); + } + setState(() { + isTiming = false; + }); + } @override void initState() { + duration = widget.roundDuration; + timerMaxSeconds = widget.timerMaxSeconds; super.initState(); - - final roundStartTime = widget.controller.currentRound?.currentRoundStart; - if (roundStartTime != null) { - final roundDuration = DateTime.now().difference(roundStartTime).inSeconds; - if (roundDuration > GameConstants.timerMaxSeconds) return; - - currentSeconds = roundDuration; - timer = Timer.periodic(const Duration(seconds: 1), (Timer t) { - currentSeconds++; - if (currentSeconds >= GameConstants.timerMaxSeconds) { - t.cancel(); - } - setState(() {}); - }); - } - - stateSubscription = Matrix.of(context) - .client - .onRoomState - .stream - .where(isRoundUpdate) - .listen(onRoundUpdate); - } - - bool isRoundUpdate(update) { - return update.roomId == widget.controller.room.id && - update.state is Event && - (update.state as Event).type == PangeaEventTypes.storyGame; - } - - void onRoundUpdate(update) { - final GameModel gameState = GameModel.fromJson( - (update.state as Event).content, - ); - final startTime = gameState.currentRoundStartTime; - final endTime = gameState.previousRoundEndTime; - - if (startTime == null && endTime == null) return; - timer?.cancel(); - timer = null; - - // if this update is the start of a round - if (startTime != null) { - timer = Timer.periodic(const Duration(seconds: 1), (Timer t) { - currentSeconds++; - if (currentSeconds >= GameConstants.timerMaxSeconds) { - t.cancel(); - } - setState(() {}); - }); - return; - } - - // if this update is the end of a round - currentSeconds = 0; - setState(() {}); } @override void dispose() { + if (_timer != null) { + _timer!.cancel(); + } super.dispose(); - - stateSubscription?.cancel(); - stateSubscription = null; - - timer?.cancel(); - timer = null; } - int get remainingTime => GameConstants.timerMaxSeconds - currentSeconds; - - String get timerText => - '${(remainingTime ~/ 60).toString().padLeft(2, '0')}: ${(remainingTime % 60).toString().padLeft(2, '0')}'; - @override Widget build(BuildContext context) { return Material( color: const Color.fromARGB(255, 126, 22, 14), child: Padding( - padding: const EdgeInsets.all(5), + padding: const EdgeInsets.all( + 5, + ), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(timerText), - const Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // IconButton( - // onPressed: widget.currentRound.timer == null - // ? widget.currentRound.startRound - // : null, - // icon: Icon( - // widget.currentRound.timer != null - // ? Icons.pause_circle - // : Icons.play_circle, - // ), - // ), - ], - ), + // Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // IconButton( + // onPressed: isTiming ? stopTimeout : startTimeout, + // icon: Icon(isTiming ? Icons.pause_circle : Icons.play_circle), + // ), + // ], + // ), ], ), ), From a49105d298139a2c169e5a7c4e0e97f833b4e10e Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:42:35 -0400 Subject: [PATCH 02/23] =?UTF-8?q?Revert=20"moved=20timer=20sync=20logic=20?= =?UTF-8?q?to=20seperate=20function,=20don't=20hide=20bot=20messages=20?= =?UTF-8?q?=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/chat/chat.dart | 2 +- lib/pages/chat/chat_view.dart | 1 + .../extensions/sync_update_extension.dart | 4 +- .../pages/games/story_game/round_model.dart | 65 +++++++------------ 4 files changed, 28 insertions(+), 44 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 6e6a7487f..cbc5c8bf7 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -123,7 +123,7 @@ class ChatController extends State List get completedRoundEventIds => gameRounds .where((round) => round.isCompleted) - .map((round) => round.userMessageIDs) + .map((round) => round.messageIDs) .expand((x) => x) .toList(); // Pangea# diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index ce45043eb..c72866c86 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -13,6 +13,7 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_bar.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/widgets/chat/chat_floating_action_button.dart'; +import 'package:fluffychat/pangea/widgets/chat/round_timer.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'; diff --git a/lib/pangea/extensions/sync_update_extension.dart b/lib/pangea/extensions/sync_update_extension.dart index 68c1f684a..412ca2b8f 100644 --- a/lib/pangea/extensions/sync_update_extension.dart +++ b/lib/pangea/extensions/sync_update_extension.dart @@ -10,9 +10,7 @@ extension MembershipUpdate on SyncUpdate { return rooms!.join![chat.id]!.timeline!.events! .where( - (event) => - event.type == EventTypes.Message && - !event.eventId.startsWith("Pangea Chat"), + (event) => event.type == EventTypes.Message, ) .map((event) => Event.fromMatrixEvent(event, chat)) .toList(); diff --git a/lib/pangea/pages/games/story_game/round_model.dart b/lib/pangea/pages/games/story_game/round_model.dart index c192c36a6..61beb6242 100644 --- a/lib/pangea/pages/games/story_game/round_model.dart +++ b/lib/pangea/pages/games/story_game/round_model.dart @@ -11,7 +11,6 @@ enum RoundState { notStarted, inProgress, completed } class GameRoundModel { static const int timerMaxSeconds = 180; - final String adminName = BotName.byEnvironment; final ChatController controller; final Completer roundCompleter = Completer(); @@ -21,54 +20,42 @@ class GameRoundModel { DateTime? endTime; RoundState state = RoundState.notStarted; StreamSubscription? syncSubscription; - final List userMessageIDs = []; - final List botMessageIDs = []; + final Set messageIDs = {}; GameRoundModel({ required this.controller, required this.timer, }) { createdAt = DateTime.now(); - syncSubscription ??= client.onSync.stream.listen(_handleSync); - } + debugPrint("timeline: ${controller.room.timeline}"); + syncSubscription ??= client.onSync.stream.listen((update) { + final newMessages = update.messages(controller.room); + final botMessages = newMessages + .where((msg) => msg.senderId == BotName.byEnvironment) + .toList(); - void _handleSync(SyncUpdate update) { - final newMessages = update - .messages(controller.room) - .where((msg) => msg.originServerTs.isAfter(createdAt)) - .toList(); - - final botMessages = - newMessages.where((msg) => msg.senderId == adminName).toList(); - final userMessages = - newMessages.where((msg) => msg.senderId != adminName).toList(); - - final hasNewBotMessage = botMessages.any( - (msg) => !botMessageIDs.contains(msg.eventId), - ); - - if (hasNewBotMessage) { - if (state == RoundState.notStarted) { - startRound(); - } else if (state == RoundState.inProgress) { - endRound(); - return; - } - } - - if (state == RoundState.inProgress) { - for (final message in botMessages) { - if (!botMessageIDs.contains(message.eventId)) { - botMessageIDs.add(message.eventId); + if (botMessages.isNotEmpty && + botMessages.any( + (msg) => + msg.originServerTs.isAfter(createdAt) && + !messageIDs.contains(msg.eventId), + )) { + if (state == RoundState.notStarted) { + startRound(); + } else if (state == RoundState.inProgress) { + endRound(); + return; } } - for (final message in userMessages) { - if (!userMessageIDs.contains(message.eventId)) { - userMessageIDs.add(message.eventId); + for (final message in newMessages) { + if (message.originServerTs.isAfter(createdAt) && + !messageIDs.contains(message.eventId) && + !message.eventId.startsWith("Pangea Chat")) { + messageIDs.add(message.eventId); } } - } + }); } Client get client => controller.pangeaController.matrixState.client; @@ -86,9 +73,7 @@ class GameRoundModel { } void endRound() { - debugPrint( - "ending round, user message IDs: $userMessageIDs, bot message IDs: $botMessageIDs", - ); + debugPrint("ending round, message IDs: $messageIDs"); endTime = DateTime.now(); state = RoundState.completed; controller.roundTimerStateKey.currentState?.resetTimer(); From 14783d8536229fc7a3f9ff96b34a1090528e6531 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Fri, 23 Aug 2024 10:44:21 -0400 Subject: [PATCH 03/23] Revert "Story game" --- lib/pages/chat/chat.dart | 31 +---- lib/pages/chat/chat_event_list.dart | 10 +- lib/pages/chat/chat_view.dart | 5 - lib/pages/chat/event_info_dialog.dart | 21 ++-- lib/pages/chat/events/message.dart | 16 ++- lib/pages/chat/events/message_content.dart | 24 ++-- lib/pages/chat/events/reply_content.dart | 3 +- lib/pages/chat/seen_by_row.dart | 7 +- lib/pages/chat/typing_indicators.dart | 17 ++- .../chat_details/participant_list_item.dart | 10 +- lib/pages/chat_list/chat_list_item.dart | 93 +++++++------- .../chat_search/chat_search_message_tab.dart | 17 +-- .../user_bottom_sheet_view.dart | 11 +- .../extensions/sync_update_extension.dart | 89 ++++++++++++-- .../pages/games/story_game/round_model.dart | 87 -------------- lib/pangea/widgets/chat/round_timer.dart | 113 ------------------ .../matrix_sdk_extensions/matrix_locals.dart | 9 +- lib/utils/room_status_extension.dart | 15 +-- 18 files changed, 195 insertions(+), 383 deletions(-) delete mode 100644 lib/pangea/pages/games/story_game/round_model.dart delete mode 100644 lib/pangea/widgets/chat/round_timer.dart diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index cbc5c8bf7..3e0413230 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -21,12 +21,10 @@ import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dar import 'package:fluffychat/pangea/models/choreo_record.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; import 'package:fluffychat/pangea/models/tokens_event_content_model.dart'; -import 'package:fluffychat/pangea/pages/games/story_game/round_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/utils/report_message.dart'; import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; -import 'package:fluffychat/pangea/widgets/chat/round_timer.dart'; import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart'; import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; @@ -114,20 +112,9 @@ class ChatController extends State with WidgetsBindingObserver { // #Pangea final PangeaController pangeaController = MatrixState.pangeaController; + late Choreographer choreographer = Choreographer(pangeaController, this); - final GlobalKey roundTimerStateKey = - GlobalKey(); - RoundTimer? timer; - - final List gameRounds = []; - - List get completedRoundEventIds => gameRounds - .where((round) => round.isCompleted) - .map((round) => round.messageIDs) - .expand((x) => x) - .toList(); // Pangea# - Room get room => sendingClient.getRoomById(roomId) ?? widget.room; late Client sendingClient; @@ -307,17 +294,6 @@ class ChatController extends State } } - // #Pangea - void addRound() { - debugPrint("ADDING A ROUND. Rounds so far: ${gameRounds.length}"); - final newRound = GameRoundModel(controller: this, timer: timer!); - gameRounds.add(newRound); - newRound.roundCompleter.future.then((_) { - if (mounted) addRound(); - }); - } - // Pangea# - @override void initState() { scrollController.addListener(_updateScrollController); @@ -333,8 +309,6 @@ class ChatController extends State sendingClient = Matrix.of(context).client; WidgetsBinding.instance.addObserver(this); // #Pangea - timer = RoundTimer(key: roundTimerStateKey); - addRound(); if (!mounted) return; Future.delayed(const Duration(seconds: 1), () async { if (!mounted) return; @@ -421,8 +395,7 @@ class ChatController extends State List get visibleEvents => timeline?.events .where( - (x) => - x.isVisibleInGui && !completedRoundEventIds.contains(x.eventId), + (x) => x.isVisibleInGui, ) .toList() ?? []; diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 7b9003379..9bca32169 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -27,15 +27,7 @@ class ChatEventList extends StatelessWidget { final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0; final events = controller.timeline!.events - .where( - (event) => - event.isVisibleInGui - // #Pangea - && - !controller.completedRoundEventIds.contains(event.eventId) - // Pangea# - , - ) + .where((event) => event.isVisibleInGui) .toList(); final animateInEventIndex = controller.animateInEventIndex; diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index c72866c86..405e16418 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -13,7 +13,6 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_bar.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/widgets/chat/chat_floating_action_button.dart'; -import 'package:fluffychat/pangea/widgets/chat/round_timer.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'; @@ -120,10 +119,6 @@ class ChatView extends StatelessWidget { // #Pangea } else { return [ - controller.timer ?? const SizedBox(), - const SizedBox( - width: 10, - ), ChatSettingsPopupMenu( controller.room, (!controller.room.isDirectChat && !controller.room.isArchived), diff --git a/lib/pages/chat/event_info_dialog.dart b/lib/pages/chat/event_info_dialog.dart index 8b8f1e703..38acdc84c 100644 --- a/lib/pages/chat/event_info_dialog.dart +++ b/lib/pages/chat/event_info_dialog.dart @@ -1,12 +1,14 @@ import 'dart:convert'; +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'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; extension EventInfoDialogExtension on Event { void showInfoDialog(BuildContext context) => showAdaptiveBottomSheet( @@ -47,16 +49,15 @@ class EventInfoDialog extends StatelessWidget { children: [ ListTile( leading: Avatar( - // mxContent: event.senderFromMemoryOrFallback.avatarUrl, - // name: event.senderFromMemoryOrFallback.calcDisplayname(), - name: "?", + mxContent: event.senderFromMemoryOrFallback.avatarUrl, + name: event.senderFromMemoryOrFallback.calcDisplayname(), client: event.room.client, - // presenceUserId: event.senderId, + presenceUserId: event.senderId, ), title: Text(L10n.of(context)!.sender), - // subtitle: Text( - // '${event.senderFromMemoryOrFallback.calcDisplayname()} [${event.senderId}]', - // ), + subtitle: Text( + '${event.senderFromMemoryOrFallback.calcDisplayname()} [${event.senderId}]', + ), ), ListTile( title: Text(L10n.of(context)!.time), diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 5c0a3ae29..3a6b7030c 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -263,10 +263,9 @@ class Message extends StatelessWidget { final user = snapshot.data ?? event.senderFromMemoryOrFallback; return Avatar( - // mxContent: user.avatarUrl, - // name: user.calcDisplayname(), - // presenceUserId: user.stateKey, - name: "?", + mxContent: user.avatarUrl, + name: user.calcDisplayname(), + presenceUserId: user.stateKey, presenceBackgroundColor: avatarPresenceBackgroundColor, onTap: () => onAvatarTab(event), @@ -289,11 +288,10 @@ class Message extends StatelessWidget { : FutureBuilder( future: event.fetchSenderUser(), builder: (context, snapshot) { - // final displayname = snapshot.data - // ?.calcDisplayname() ?? - // event.senderFromMemoryOrFallback - // .calcDisplayname(); - const displayname = "?"; + final displayname = snapshot.data + ?.calcDisplayname() ?? + event.senderFromMemoryOrFallback + .calcDisplayname(); return Text( displayname, style: TextStyle( diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 2010e47f2..5468b59e8 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -96,16 +96,12 @@ class MessageContent extends StatelessWidget { ListTile( contentPadding: EdgeInsets.zero, leading: Avatar( - // mxContent: sender.avatarUrl, - // name: sender.calcDisplayname(), - // presenceUserId: sender.stateKey, - name: "?", + mxContent: sender.avatarUrl, + name: sender.calcDisplayname(), + presenceUserId: sender.stateKey, client: event.room.client, ), - title: const Text( - // sender.calcDisplayname(), - "?", - ), + title: Text(sender.calcDisplayname()), subtitle: Text(event.originServerTs.localizedTime(context)), trailing: const Icon(Icons.lock_outlined), ), @@ -269,10 +265,9 @@ class MessageContent extends StatelessWidget { builder: (context, snapshot) { final reason = event.redactedBecause?.content.tryGet('reason'); - // final redactedBy = snapshot.data?.calcDisplayname() ?? - // event.redactedBecause?.senderId.localpart ?? - // L10n.of(context)!.user; - const redactedBy = "?"; + final redactedBy = snapshot.data?.calcDisplayname() ?? + event.redactedBecause?.senderId.localpart ?? + L10n.of(context)!.user; return _ButtonContent( label: reason == null ? L10n.of(context)!.redactedBy(redactedBy) @@ -390,9 +385,8 @@ class MessageContent extends StatelessWidget { builder: (context, snapshot) { return _ButtonContent( label: L10n.of(context)!.userSentUnknownEvent( - // snapshot.data?.calcDisplayname() ?? - // event.senderFromMemoryOrFallback.calcDisplayname(), - "?", + snapshot.data?.calcDisplayname() ?? + event.senderFromMemoryOrFallback.calcDisplayname(), event.type, ), icon: 'ℹ️', diff --git a/lib/pages/chat/events/reply_content.dart b/lib/pages/chat/events/reply_content.dart index a31765d38..b48f16cd9 100644 --- a/lib/pages/chat/events/reply_content.dart +++ b/lib/pages/chat/events/reply_content.dart @@ -59,8 +59,7 @@ class ReplyContent extends StatelessWidget { future: displayEvent.fetchSenderUser(), builder: (context, snapshot) { return Text( - // '${snapshot.data?.calcDisplayname() ?? displayEvent.senderFromMemoryOrFallback.calcDisplayname()}:', - '?:', + '${snapshot.data?.calcDisplayname() ?? displayEvent.senderFromMemoryOrFallback.calcDisplayname()}:', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( diff --git a/lib/pages/chat/seen_by_row.dart b/lib/pages/chat/seen_by_row.dart index 21fd6794e..af8b82069 100644 --- a/lib/pages/chat/seen_by_row.dart +++ b/lib/pages/chat/seen_by_row.dart @@ -37,10 +37,9 @@ class SeenByRow extends StatelessWidget { ? seenByUsers.sublist(0, maxAvatars) : seenByUsers) .map( - (user) => const Avatar( - // mxContent: user.avatarUrl, - // name: user.calcDisplayname(), - name: "?", + (user) => Avatar( + mxContent: user.avatarUrl, + name: user.calcDisplayname(), size: 16, ), ), diff --git a/lib/pages/chat/typing_indicators.dart b/lib/pages/chat/typing_indicators.dart index e02d59d1e..35fbf5d25 100644 --- a/lib/pages/chat/typing_indicators.dart +++ b/lib/pages/chat/typing_indicators.dart @@ -1,11 +1,12 @@ import 'dart:async'; +import 'package:flutter/material.dart'; + import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; class TypingIndicators extends StatelessWidget { final ChatController controller; @@ -55,11 +56,10 @@ class TypingIndicators extends StatelessWidget { child: Stack( children: [ if (typingUsers.isNotEmpty) - const Avatar( + Avatar( size: avatarSize, - // mxContent: typingUsers.first.avatarUrl, - // name: typingUsers.first.calcDisplayname(), - name: "?", + mxContent: typingUsers.first.avatarUrl, + name: typingUsers.first.calcDisplayname(), ), if (typingUsers.length == 2) Padding( @@ -69,10 +69,9 @@ class TypingIndicators extends StatelessWidget { mxContent: typingUsers.length == 2 ? typingUsers.last.avatarUrl : null, - // name: typingUsers.length == 2 - // ? typingUsers.last.calcDisplayname() - // : '+${typingUsers.length - 1}', - name: "?", + name: typingUsers.length == 2 + ? typingUsers.last.calcDisplayname() + : '+${typingUsers.length - 1}', ), ), ], diff --git a/lib/pages/chat_details/participant_list_item.dart b/lib/pages/chat_details/participant_list_item.dart index 4f63a6238..9b5580692 100644 --- a/lib/pages/chat_details/participant_list_item.dart +++ b/lib/pages/chat_details/participant_list_item.dart @@ -39,10 +39,9 @@ class ParticipantListItem extends StatelessWidget { ), title: Row( children: [ - const Expanded( + Expanded( child: Text( - // user.calcDisplayname(), - "?", + user.calcDisplayname(), overflow: TextOverflow.ellipsis, ), ), @@ -89,9 +88,8 @@ class ParticipantListItem extends StatelessWidget { subtitle: Text(user.id), leading: Avatar( mxContent: user.avatarUrl, - // name: user.calcDisplayname(), - // presenceUserId: user.stateKey, - name: "?", + name: user.calcDisplayname(), + presenceUserId: user.stateKey, ), ), ); diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 54995d31d..76c75e83b 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -1,9 +1,11 @@ import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/utils/get_chat_list_item_subtitle.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/room_status_extension.dart'; import 'package:fluffychat/widgets/hover_builder.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'; @@ -238,52 +240,51 @@ class ChatListItem extends StatelessWidget { softWrap: false, ) // #Pangea - : const SizedBox(), - // FutureBuilder( - // future: room.lastEvent != null - // ? GetChatListItemSubtitle().getSubtitle( - // L10n.of(context)!, - // room.lastEvent, - // MatrixState.pangeaController, - // ) - // : Future.value(L10n.of(context)!.emptyChat), - // builder: (context, snapshot) { - // // Pangea# - // return Text( - // room.membership == Membership.invite - // ? isDirectChat - // ? L10n.of(context)!.invitePrivateChat - // : L10n.of(context)!.inviteGroupChat - // // #Pangea - // : snapshot.data ?? - // // Pangea# - // room.lastEvent - // ?.calcLocalizedBodyFallback( - // MatrixLocals(L10n.of(context)!), - // hideReply: true, - // hideEdit: true, - // plaintextBody: true, - // removeMarkdown: true, - // withSenderNamePrefix: !isDirectChat || - // directChatMatrixId != - // room.lastEvent?.senderId, - // ) ?? - // L10n.of(context)!.emptyChat, - // softWrap: false, - // maxLines: 1, - // overflow: TextOverflow.ellipsis, - // style: TextStyle( - // fontWeight: unread || room.hasNewMessages - // ? FontWeight.bold - // : null, - // color: theme.colorScheme.onSurfaceVariant, - // decoration: room.lastEvent?.redacted == true - // ? TextDecoration.lineThrough - // : null, - // ), - // ); - // }, - // ), + : FutureBuilder( + future: room.lastEvent != null + ? GetChatListItemSubtitle().getSubtitle( + L10n.of(context)!, + room.lastEvent, + MatrixState.pangeaController, + ) + : Future.value(L10n.of(context)!.emptyChat), + builder: (context, snapshot) { + // Pangea# + return Text( + room.membership == Membership.invite + ? isDirectChat + ? L10n.of(context)!.invitePrivateChat + : L10n.of(context)!.inviteGroupChat + // #Pangea + : snapshot.data ?? + // Pangea# + room.lastEvent + ?.calcLocalizedBodyFallback( + MatrixLocals(L10n.of(context)!), + hideReply: true, + hideEdit: true, + plaintextBody: true, + removeMarkdown: true, + withSenderNamePrefix: !isDirectChat || + directChatMatrixId != + room.lastEvent?.senderId, + ) ?? + L10n.of(context)!.emptyChat, + softWrap: false, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontWeight: unread || room.hasNewMessages + ? FontWeight.bold + : null, + color: theme.colorScheme.onSurfaceVariant, + decoration: room.lastEvent?.redacted == true + ? TextDecoration.lineThrough + : null, + ), + ); + }, + ), ), const SizedBox(width: 8), // #Pangea diff --git a/lib/pages/chat_search/chat_search_message_tab.dart b/lib/pages/chat_search/chat_search_message_tab.dart index 68e627282..7542d6ae9 100644 --- a/lib/pages/chat_search/chat_search_message_tab.dart +++ b/lib/pages/chat_search/chat_search_message_tab.dart @@ -1,13 +1,15 @@ -import 'package:fluffychat/utils/date_time_extension.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; -import 'package:fluffychat/utils/url_launcher.dart'; -import 'package:fluffychat/widgets/avatar.dart'; import 'package:flutter/material.dart'; + import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; +import 'package:fluffychat/utils/date_time_extension.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; +import 'package:fluffychat/utils/url_launcher.dart'; +import 'package:fluffychat/widgets/avatar.dart'; + class ChatSearchMessageTab extends StatelessWidget { final String searchQuery; final Room room; @@ -96,10 +98,9 @@ class ChatSearchMessageTab extends StatelessWidget { } final event = events[i]; final sender = event.senderFromMemoryOrFallback; - // final displayname = sender.calcDisplayname( - // i18n: MatrixLocals(L10n.of(context)!), - // ); - const displayname = "?"; + final displayname = sender.calcDisplayname( + i18n: MatrixLocals(L10n.of(context)!), + ); return _MessageSearchResultListTile( sender: sender, displayname: displayname, 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 7bd189288..ef79c0e31 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -22,10 +22,9 @@ class UserBottomSheetView extends StatelessWidget { Widget build(BuildContext context) { final user = controller.widget.user; final userId = (user?.id ?? controller.widget.profile?.userId)!; - // final displayname = (user?.calcDisplayname() ?? - // controller.widget.profile?.displayName ?? - // controller.widget.profile?.userId.localpart)!; - const displayname = "?"; + final displayname = (user?.calcDisplayname() ?? + controller.widget.profile?.displayName ?? + controller.widget.profile?.userId.localpart)!; final avatarUrl = user?.avatarUrl ?? controller.widget.profile?.avatarUrl; final client = Matrix.of(controller.widget.outerContext).client; @@ -40,7 +39,7 @@ class UserBottomSheetView extends StatelessWidget { title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text(displayname), + Text(displayname), PresenceBuilder( userId: userId, client: client, @@ -214,7 +213,7 @@ class UserBottomSheetView extends StatelessWidget { foregroundColor: Theme.of(context).colorScheme.onSurface, ), - label: const Text( + label: Text( displayname, maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/pangea/extensions/sync_update_extension.dart b/lib/pangea/extensions/sync_update_extension.dart index 412ca2b8f..6adb55c69 100644 --- a/lib/pangea/extensions/sync_update_extension.dart +++ b/lib/pangea/extensions/sync_update_extension.dart @@ -1,18 +1,85 @@ import 'package:matrix/matrix.dart'; extension MembershipUpdate on SyncUpdate { - List messages(Room chat) { - if (rooms?.join == null || - !rooms!.join!.containsKey(chat.id) || - rooms!.join![chat.id]!.timeline?.events == null) { - return []; + bool isMembershipUpdate(String userId) { + return isMembershipUpdateByType(Membership.join, userId) || + isMembershipUpdateByType(Membership.leave, userId) || + isMembershipUpdateByType(Membership.invite, userId); + } + + bool isMembershipUpdateByType(Membership type, String userId) { + final List? updates = getRoomUpdates(type); + if (updates?.isEmpty ?? true) { + return false; } - return rooms!.join![chat.id]!.timeline!.events! - .where( - (event) => event.type == EventTypes.Message, - ) - .map((event) => Event.fromMatrixEvent(event, chat)) - .toList(); + for (final SyncRoomUpdate update in updates!) { + final List? events = getRoomUpdateEvents(type, update); + if (hasMembershipUpdate( + events, + type.name, + userId, + )) { + return true; + } + } + return false; + } + + List? getRoomUpdates(Membership type) { + switch (type) { + case Membership.join: + return rooms?.join?.values.toList(); + case Membership.leave: + return rooms?.leave?.values.toList(); + case Membership.invite: + return rooms?.invite?.values.toList(); + default: + return null; + } + } + + bool isSpaceChildUpdate(String activeSpaceId) { + if (rooms?.join?.isEmpty ?? true) { + return false; + } + for (final update in rooms!.join!.entries) { + final String spaceId = update.key; + final List? timelineEvents = update.value.timeline?.events; + final bool isUpdate = timelineEvents != null && + spaceId == activeSpaceId && + timelineEvents.any((event) => event.type == EventTypes.SpaceChild); + if (isUpdate) return true; + } + return false; } } + +List? getRoomUpdateEvents(Membership type, SyncRoomUpdate update) { + switch (type) { + case Membership.join: + return (update as JoinedRoomUpdate).timeline?.events; + case Membership.leave: + return (update as LeftRoomUpdate).timeline?.events; + case Membership.invite: + return (update as InvitedRoomUpdate).inviteState; + default: + return null; + } +} + +bool hasMembershipUpdate( + List? events, + String membershipType, + String userId, +) { + if (events == null) { + return false; + } + return events.any( + (event) => + event.type == EventTypes.RoomMember && + event.stateKey == userId && + event.content['membership'] == membershipType, + ); +} diff --git a/lib/pangea/pages/games/story_game/round_model.dart b/lib/pangea/pages/games/story_game/round_model.dart deleted file mode 100644 index 61beb6242..000000000 --- a/lib/pangea/pages/games/story_game/round_model.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'dart:async'; - -import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pangea/extensions/sync_update_extension.dart'; -import 'package:fluffychat/pangea/utils/bot_name.dart'; -import 'package:fluffychat/pangea/widgets/chat/round_timer.dart'; -import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; - -enum RoundState { notStarted, inProgress, completed } - -class GameRoundModel { - static const int timerMaxSeconds = 180; - - final ChatController controller; - final Completer roundCompleter = Completer(); - late DateTime createdAt; - RoundTimer timer; - DateTime? startTime; - DateTime? endTime; - RoundState state = RoundState.notStarted; - StreamSubscription? syncSubscription; - final Set messageIDs = {}; - - GameRoundModel({ - required this.controller, - required this.timer, - }) { - createdAt = DateTime.now(); - debugPrint("timeline: ${controller.room.timeline}"); - syncSubscription ??= client.onSync.stream.listen((update) { - final newMessages = update.messages(controller.room); - final botMessages = newMessages - .where((msg) => msg.senderId == BotName.byEnvironment) - .toList(); - - if (botMessages.isNotEmpty && - botMessages.any( - (msg) => - msg.originServerTs.isAfter(createdAt) && - !messageIDs.contains(msg.eventId), - )) { - if (state == RoundState.notStarted) { - startRound(); - } else if (state == RoundState.inProgress) { - endRound(); - return; - } - } - - for (final message in newMessages) { - if (message.originServerTs.isAfter(createdAt) && - !messageIDs.contains(message.eventId) && - !message.eventId.startsWith("Pangea Chat")) { - messageIDs.add(message.eventId); - } - } - }); - } - - Client get client => controller.pangeaController.matrixState.client; - - bool get isCompleted => roundCompleter.isCompleted; - - void startRound() { - debugPrint("starting round"); - state = RoundState.inProgress; - startTime = DateTime.now(); - controller.roundTimerStateKey.currentState?.resetTimer( - roundLength: timerMaxSeconds, - ); - controller.roundTimerStateKey.currentState?.startTimer(); - } - - void endRound() { - debugPrint("ending round, message IDs: $messageIDs"); - endTime = DateTime.now(); - state = RoundState.completed; - controller.roundTimerStateKey.currentState?.resetTimer(); - syncSubscription?.cancel(); - roundCompleter.complete(); - } - - void dispose() { - syncSubscription?.cancel(); - } -} diff --git a/lib/pangea/widgets/chat/round_timer.dart b/lib/pangea/widgets/chat/round_timer.dart deleted file mode 100644 index 83641983d..000000000 --- a/lib/pangea/widgets/chat/round_timer.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -/// Create a timer that counts down to the given time -/// Default duration is 180 seconds -class RoundTimer extends StatefulWidget { - final int timerMaxSeconds; - final Duration roundDuration; - - const RoundTimer({ - super.key, - this.timerMaxSeconds = 180, - this.roundDuration = const Duration(seconds: 1), - }); - - @override - RoundTimerState createState() => RoundTimerState(); -} - -class RoundTimerState extends State { - int currentSeconds = 0; - Timer? _timer; - bool isTiming = false; - Duration? duration; - int timerMaxSeconds = 180; - - void resetTimer({Duration? roundDuration, int? roundLength}) { - if (_timer != null) { - _timer!.cancel(); - isTiming = false; - } - if (roundDuration != null) { - duration = roundDuration; - } - if (roundLength != null) { - timerMaxSeconds = roundLength; - } - setState(() { - currentSeconds = 0; - }); - } - - int get remainingTime => timerMaxSeconds - currentSeconds; - - String get timerText => - '${(remainingTime ~/ 60).toString().padLeft(2, '0')}: ${(remainingTime % 60).toString().padLeft(2, '0')}'; - - startTimer() { - _timer = Timer.periodic(duration ?? widget.roundDuration, (timer) { - setState(() { - currentSeconds++; - if (currentSeconds >= timerMaxSeconds) timer.cancel(); - }); - }); - setState(() { - isTiming = true; - }); - } - - stopTimer() { - if (_timer != null) { - _timer!.cancel(); - } - setState(() { - isTiming = false; - }); - } - - @override - void initState() { - duration = widget.roundDuration; - timerMaxSeconds = widget.timerMaxSeconds; - super.initState(); - } - - @override - void dispose() { - if (_timer != null) { - _timer!.cancel(); - } - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Material( - color: const Color.fromARGB(255, 126, 22, 14), - child: Padding( - padding: const EdgeInsets.all( - 5, - ), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(timerText), - // Row( - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // IconButton( - // onPressed: isTiming ? stopTimeout : startTimeout, - // icon: Icon(isTiming ? Icons.pause_circle : Icons.play_circle), - // ), - // ], - // ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/utils/matrix_sdk_extensions/matrix_locals.dart b/lib/utils/matrix_sdk_extensions/matrix_locals.dart index f959898ed..b4536b6db 100644 --- a/lib/utils/matrix_sdk_extensions/matrix_locals.dart +++ b/lib/utils/matrix_sdk_extensions/matrix_locals.dart @@ -184,9 +184,9 @@ class MatrixLocals extends MatrixLocalizations { @override String redactedAnEvent(Event redactedEvent) { return l10n.redactedAnEvent( - // redactedEvent.redactedBecause?.senderFromMemoryOrFallback - // .calcDisplayname() ?? - l10n.user, + redactedEvent.redactedBecause?.senderFromMemoryOrFallback + .calcDisplayname() ?? + l10n.user, ); } @@ -198,8 +198,7 @@ class MatrixLocals extends MatrixLocalizations { @override String removedBy(Event redactedEvent) { return l10n.redactedBy( - // redactedEvent.senderFromMemoryOrFallback.calcDisplayname(), - "?", + redactedEvent.senderFromMemoryOrFallback.calcDisplayname(), ); } diff --git a/lib/utils/room_status_extension.dart b/lib/utils/room_status_extension.dart index 277894473..02c70b088 100644 --- a/lib/utils/room_status_extension.dart +++ b/lib/utils/room_status_extension.dart @@ -1,4 +1,5 @@ import 'package:flutter/widgets.dart'; + import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; @@ -19,21 +20,17 @@ extension RoomStatusExtension on Room { } else if (typingUsers.length == 1) { typingText = L10n.of(context)!.isTyping; if (typingUsers.first.id != directChatMatrixID) { - typingText = L10n.of(context)!.userIsTyping( - // typingUsers.first.calcDisplayname(), - "?", - ); + typingText = + L10n.of(context)!.userIsTyping(typingUsers.first.calcDisplayname()); } } else if (typingUsers.length == 2) { typingText = L10n.of(context)!.userAndUserAreTyping( - // typingUsers.first.calcDisplayname(), - // typingUsers[1].calcDisplayname(), - "?", "?", + typingUsers.first.calcDisplayname(), + typingUsers[1].calcDisplayname(), ); } else if (typingUsers.length > 2) { typingText = L10n.of(context)!.userAndOthersAreTyping( - // typingUsers.first.calcDisplayname(), - "?", + typingUsers.first.calcDisplayname(), (typingUsers.length - 1).toString(), ); } From e6f90af011913d42deab6e86e4e73cb9c0db5be6 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 23 Aug 2024 11:56:34 -0400 Subject: [PATCH 04/23] added potential fix and better logging for flutter_secure_storage returning null for database cipher on iOS --- .../cipher.dart | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/cipher.dart b/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/cipher.dart index bfe1251dc..0134b50b3 100644 --- a/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/cipher.dart +++ b/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/cipher.dart @@ -14,9 +14,25 @@ Future getDatabaseCipher() async { String? password; try { - const secureStorage = FlutterSecureStorage(); + // #Pangea + // mogol/flutter_secure_storage#532 + // mogol/flutter_secure_storage#524 + // Pangea# + const secureStorage = FlutterSecureStorage( + // #Pangea + iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock), + // Pangea# + ); + // #Pangea + await secureStorage.read(key: _passwordStorageKey); + // Pangea# final containsEncryptionKey = await secureStorage.read(key: _passwordStorageKey) != null; + // #Pangea + Sentry.addBreadcrumb( + Breadcrumb(message: 'containsEncryptionKey: $containsEncryptionKey'), + ); + // Pangea# if (!containsEncryptionKey) { final rng = Random.secure(); final list = Uint8List(32); @@ -29,18 +45,41 @@ Future getDatabaseCipher() async { } // workaround for if we just wrote to the key and it still doesn't exist password = await secureStorage.read(key: _passwordStorageKey); - if (password == null) throw MissingPluginException(); + if (password == null) { + throw MissingPluginException( + // #Pangea + "password is null after storing new password", + // Pangea# + ); + } } on MissingPluginException catch (e) { const FlutterSecureStorage() .delete(key: _passwordStorageKey) .catchError((_) {}); Logs().w('Database encryption is not supported on this platform', e); + // #Pangea + Sentry.addBreadcrumb( + Breadcrumb( + message: + 'Database encryption is not supported on this platform. Error message: ${e.message}', + data: {'exception': e}, + ), + ); + // Pangea# _sendNoEncryptionWarning(e); } catch (e, s) { const FlutterSecureStorage() .delete(key: _passwordStorageKey) .catchError((_) {}); Logs().w('Unable to init database encryption', e, s); + // #Pangea + Sentry.addBreadcrumb( + Breadcrumb( + message: 'Unable to init database encryption', + data: {'exception': e, 'stackTrace': s}, + ), + ); + // Pangea# _sendNoEncryptionWarning(e); } From abb1e3a56f2d3b77d77c00befaf141440e52c34e Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 23 Aug 2024 12:04:38 -0400 Subject: [PATCH 05/23] added missing matrix localization for invitedBy --- assets/l10n/intl_en.arb | 8 +++++++- lib/utils/matrix_sdk_extensions/matrix_locals.dart | 5 +---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 5fcac8b57..490f6c91d 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2303,6 +2303,12 @@ "user": {} } }, + "invitedBy": "📩 Invited by {user}", + "@invitedBy": { + "placeholders": { + "user": {} + } + }, "youInvitedUser": "📩 You invited {user}", "@youInvitedUser": { "placeholders": { @@ -4017,7 +4023,7 @@ "conversationBotTextAdventureZone_title": "Text Adventure", "conversationBotTextAdventureZone_instructionLabel": "Game Master Instructions", "conversationBotTextAdventureZone_instructionPlaceholder": "Set game master instructions", - "conversationBotCustomZone_instructionSystemPromptEmptyError": "Missing game master instructions", + "conversationBotCustomZone_instructionSystemPromptEmptyError": "Missing game master instructions", "studentAnalyticsNotAvailable": "Student data not currently available", "roomDataMissing": "Some data may be missing from rooms in which you are not a member.", "updatePhoneOS": "You may need to update your device's OS version.", diff --git a/lib/utils/matrix_sdk_extensions/matrix_locals.dart b/lib/utils/matrix_sdk_extensions/matrix_locals.dart index b4536b6db..cbba6b630 100644 --- a/lib/utils/matrix_sdk_extensions/matrix_locals.dart +++ b/lib/utils/matrix_sdk_extensions/matrix_locals.dart @@ -346,8 +346,5 @@ class MatrixLocals extends MatrixLocalizations { l10n.startedKeyVerification(senderName); @override - String invitedBy(String senderName) { - // TODO: implement invitedBy - throw UnimplementedError(); - } + String invitedBy(String senderName) => l10n.invitedBy(senderName); } From db21eaac7affd3c46eee2909e17248cd3f86a2ab Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 27 Aug 2024 14:42:11 -0400 Subject: [PATCH 06/23] check if igc button is mounted before settings state --- .../widgets/start_igc_button.dart | 5 +- lib/pangea/widgets/common/aligned_dialog.dart | 206 ------------------ lib/utils/error_reporter.dart | 17 +- 3 files changed, 15 insertions(+), 213 deletions(-) delete mode 100644 lib/pangea/widgets/common/aligned_dialog.dart diff --git a/lib/pangea/choreographer/widgets/start_igc_button.dart b/lib/pangea/choreographer/widgets/start_igc_button.dart index e8625da95..d1d45bb6b 100644 --- a/lib/pangea/choreographer/widgets/start_igc_button.dart +++ b/lib/pangea/choreographer/widgets/start_igc_button.dart @@ -50,7 +50,10 @@ class StartIGCButtonState extends State _controller?.stop(); _controller?.reverse(); } - setState(() => prevState = assistanceState); + + if (mounted) { + setState(() => prevState = assistanceState); + } } bool get itEnabled => widget.controller.choreographer.itEnabled; diff --git a/lib/pangea/widgets/common/aligned_dialog.dart b/lib/pangea/widgets/common/aligned_dialog.dart deleted file mode 100644 index c7bc5da59..000000000 --- a/lib/pangea/widgets/common/aligned_dialog.dart +++ /dev/null @@ -1,206 +0,0 @@ -import 'package:flutter/material.dart'; - -Future showAlignedDialog({ - required BuildContext context, - required WidgetBuilder builder, - bool barrierDismissible = true, - Color? barrierColor = Colors.black54, - String? barrierLabel, - bool useRootNavigator = true, - RouteSettings? routeSettings, - Alignment followerAnchor = Alignment.center, - Alignment targetAnchor = Alignment.center, - Size? refChildSize, - Offset offset = Offset.zero, - bool avoidOverflow = false, - bool isGlobal = false, - RouteTransitionsBuilder? transitionsBuilder, - Duration? duration, -}) { - assert(debugCheckHasMaterialLocalizations(context)); - - final CapturedThemes themes = InheritedTheme.capture( - from: context, - to: Navigator.of( - context, - rootNavigator: useRootNavigator, - ).context, - ); - - final RenderBox targetBox = context.findRenderObject()! as RenderBox; - final RenderBox overlay = - Navigator.of(context).overlay!.context.findRenderObject()! as RenderBox; - Offset position = targetBox - .localToGlobal(targetAnchor.alongSize(targetBox.size), ancestor: overlay); - - if (isGlobal) { - position = overlay.localToGlobal(followerAnchor.alongSize(overlay.size)); - } - - return Navigator.of(context, rootNavigator: useRootNavigator).push( - AlignedDialogRoute( - followerAlignment: followerAnchor, - position: position, - context: context, - builder: builder, - barrierColor: barrierColor, - barrierDismissible: barrierDismissible, - barrierLabel: barrierLabel, - useSafeArea: isGlobal == true, - settings: routeSettings, - themes: themes, - transitionsBuilder: transitionsBuilder, - duration: duration, - avoidOverflow: avoidOverflow, - offset: offset, - ), - ); -} - -class AlignedDialogRoute extends RawDialogRoute { - /// A dialog route with Material entrance and exit animations, - /// modal barrier color, and modal barrier behavior (dialog is dismissible - /// with a tap on the barrier). - AlignedDialogRoute({ - required BuildContext context, - required WidgetBuilder builder, - required Alignment followerAlignment, - required Offset position, - CapturedThemes? themes, - super.barrierColor = Colors.transparent, - super.barrierDismissible, - String? barrierLabel, - bool useSafeArea = false, - super.settings, - RouteTransitionsBuilder? transitionsBuilder, - Duration? duration, - bool avoidOverflow = false, - Offset offset = Offset.zero, - }) : super( - pageBuilder: ( - BuildContext buildContext, - Animation animation, - Animation secondaryAnimation, - ) { - final Widget pageChild = Builder(builder: builder); - Widget dialog = Builder( - builder: (BuildContext context) { - final MediaQueryData mediaQuery = MediaQuery.of(context); - return CustomSingleChildLayout( - delegate: _FollowerDialogRouteLayout( - followerAlignment, - position, - Directionality.of(context), - mediaQuery.padding.top, - mediaQuery.padding.bottom, - offset, - avoidOverflow, - ), - child: pageChild, - ); - }, - ); - dialog = themes?.wrap(dialog) ?? dialog; - if (useSafeArea) { - dialog = SafeArea(child: dialog); - } - return dialog; - }, - barrierLabel: barrierLabel ?? - MaterialLocalizations.of(context).modalBarrierDismissLabel, - transitionDuration: duration ?? const Duration(milliseconds: 200), - transitionBuilder: - transitionsBuilder ?? _buildMaterialDialogTransitions, - ); -} - -// Positioning of the menu on the screen. -class _FollowerDialogRouteLayout extends SingleChildLayoutDelegate { - _FollowerDialogRouteLayout( - this.followerAnchor, - this.position, - this.textDirection, - this.topPadding, - this.bottomPadding, - this.offset, - this.avoidOverflow, - ); - - final Alignment followerAnchor; - - final Offset position; - - final TextDirection textDirection; - - final double topPadding; - - final double bottomPadding; - - final Offset offset; - final bool avoidOverflow; - - @override - BoxConstraints getConstraintsForChild(BoxConstraints constraints) { - // The menu can be at most the size of the overlay minus 8.0 pixels in each - // direction. - return BoxConstraints.loose(constraints.biggest) - .deflate(EdgeInsets.only(top: topPadding, bottom: bottomPadding)); - } - - @override - Offset getPositionForChild(Size size, Size childSize) { - Offset rst = followerAnchor.alongSize(childSize); - rst = position - rst; - rst += offset; - if (avoidOverflow) { - if (rst.dx < 0) rst = Offset(0, rst.dy); - if (rst.dy < 0) rst = Offset(rst.dx, 0); - if (rst.dx + childSize.width > size.width) { - rst = Offset(size.width - childSize.width, rst.dy); - } - if (rst.dy + childSize.height > size.height) { - rst = Offset(rst.dx, size.height - childSize.height); - } - } - return rst; - } - - @override - bool shouldRelayout(_FollowerDialogRouteLayout oldDelegate) { - return followerAnchor != oldDelegate.followerAnchor || - position != oldDelegate.position || - offset != oldDelegate.offset || - avoidOverflow != oldDelegate.avoidOverflow || - textDirection != oldDelegate.textDirection || - topPadding != oldDelegate.topPadding || - bottomPadding != oldDelegate.bottomPadding; - } -} - -Widget _buildMaterialDialogTransitions( - BuildContext context, - Animation animation, - Animation secondaryAnimation, - Widget child, -) { - return FadeTransition( - opacity: CurvedAnimation( - parent: animation, - curve: Curves.easeOut, - ), - child: child, - ); -} - -Offset _buildOffSet( - BuildContext context, { - required Size refChildSize, - required Offset offset, -}) { - final Size screenSize = MediaQuery.of(context).size; - final Size maxAvilableArea = Size( - screenSize.width - refChildSize.width, - screenSize.height - refChildSize.height, - ); - return const Offset(0, 0); -} diff --git a/lib/utils/error_reporter.dart b/lib/utils/error_reporter.dart index d44b48dcb..650277bf1 100644 --- a/lib/utils/error_reporter.dart +++ b/lib/utils/error_reporter.dart @@ -21,13 +21,18 @@ class ErrorReporter { // 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 + + try { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + l10n.oopsSomethingWentWrong, // Use the non-null L10n instance to get the error message + ), ), - ), - ); + ); + } catch (err) { + debugPrint("Failed to show error snackbar."); + } } // final text = '$error\n${stackTrace ?? ''}'; // await showAdaptiveDialog( From 9d0bf230e1fa7b16eacbf1cce0804eb364a193d9 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 5 Aug 2024 15:22:20 -0400 Subject: [PATCH 07/23] Re-add mention functionality --- lib/config/app_config.dart | 4 +- lib/pages/chat/input_bar.dart | 203 +++++++++++++++++----------------- 2 files changed, 104 insertions(+), 103 deletions(-) diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 16637925e..ba9908240 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -63,13 +63,11 @@ abstract class AppConfig { path: '/krille-chan/fluffychat/issues/new', ); // #Pangea - // static bool renderHtml = true; static const bool enableSentry = true; static const String sentryDns = 'https://8591d0d863b646feb4f3dda7e5dcab38@o256755.ingest.sentry.io/5243143'; - static bool renderHtml = false; - // static bool renderHtml = true; // Pangea# + static bool renderHtml = true; static bool hideRedactedEvents = false; static bool hideUnknownEvents = true; static bool hideUnimportantStateEvents = true; diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index 61ce641fc..4a524bf5f 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -1,9 +1,13 @@ import 'package:emojis/emoji.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pages/chat/command_hints.dart'; import 'package:fluffychat/pangea/widgets/igc/pangea_text_controller.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/widgets/avatar.dart'; +import 'package:fluffychat/widgets/mxc_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:matrix/matrix.dart'; import 'package:pasteboard/pasteboard.dart'; @@ -217,6 +221,7 @@ class InputBar extends StatelessWidget { } } } + print("ret length: ${ret.length}"); return ret; } @@ -225,106 +230,104 @@ class InputBar extends StatelessWidget { Map suggestion, Client? client, ) { - // #Pangea - // const size = 30.0; - // const padding = EdgeInsets.all(4.0); - // if (suggestion['type'] == 'command') { - // final command = suggestion['name']!; - // final hint = commandHint(L10n.of(context)!, command); - // return Tooltip( - // message: hint, - // waitDuration: const Duration(days: 1), // don't show on hover - // child: Container( - // padding: padding, - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // commandExample(command), - // style: const TextStyle(fontFamily: 'monospace'), - // ), - // Text( - // hint, - // maxLines: 1, - // overflow: TextOverflow.ellipsis, - // style: Theme.of(context).textTheme.bodySmall, - // ), - // ], - // ), - // ), - // ); - // } - // if (suggestion['type'] == 'emoji') { - // final label = suggestion['label']!; - // return Tooltip( - // message: label, - // waitDuration: const Duration(days: 1), // don't show on hover - // child: Container( - // padding: padding, - // child: Text(label, style: const TextStyle(fontFamily: 'monospace')), - // ), - // ); - // } - // if (suggestion['type'] == 'emote') { - // return Container( - // padding: padding, - // child: Row( - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // MxcImage( - // // ensure proper ordering ... - // key: ValueKey(suggestion['name']), - // uri: suggestion['mxc'] is String - // ? Uri.parse(suggestion['mxc'] ?? '') - // : null, - // width: size, - // height: size, - // ), - // const SizedBox(width: 6), - // Text(suggestion['name']!), - // Expanded( - // child: Align( - // alignment: Alignment.centerRight, - // child: Opacity( - // opacity: suggestion['pack_avatar_url'] != null ? 0.8 : 0.5, - // child: suggestion['pack_avatar_url'] != null - // ? Avatar( - // mxContent: Uri.tryParse( - // suggestion.tryGet('pack_avatar_url') ?? '', - // ), - // name: suggestion.tryGet('pack_display_name'), - // size: size * 0.9, - // client: client, - // ) - // : Text(suggestion['pack_display_name']!), - // ), - // ), - // ), - // ], - // ), - // ); - // } - // if (suggestion['type'] == 'user' || suggestion['type'] == 'room') { - // final url = Uri.parse(suggestion['avatar_url'] ?? ''); - // return Container( - // padding: padding, - // child: Row( - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // Avatar( - // mxContent: url, - // name: suggestion.tryGet('displayname') ?? - // suggestion.tryGet('mxid'), - // size: size, - // client: client, - // ), - // const SizedBox(width: 6), - // Text(suggestion['displayname'] ?? suggestion['mxid']!), - // ], - // ), - // ); - // } - // Pangea# + const size = 30.0; + const padding = EdgeInsets.all(4.0); + if (suggestion['type'] == 'command') { + final command = suggestion['name']!; + final hint = commandHint(L10n.of(context)!, command); + return Tooltip( + message: hint, + waitDuration: const Duration(days: 1), // don't show on hover + child: Container( + padding: padding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + commandExample(command), + style: const TextStyle(fontFamily: 'monospace'), + ), + Text( + hint, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ), + ); + } + if (suggestion['type'] == 'emoji') { + final label = suggestion['label']!; + return Tooltip( + message: label, + waitDuration: const Duration(days: 1), // don't show on hover + child: Container( + padding: padding, + child: Text(label, style: const TextStyle(fontFamily: 'monospace')), + ), + ); + } + if (suggestion['type'] == 'emote') { + return Container( + padding: padding, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + MxcImage( + // ensure proper ordering ... + key: ValueKey(suggestion['name']), + uri: suggestion['mxc'] is String + ? Uri.parse(suggestion['mxc'] ?? '') + : null, + width: size, + height: size, + ), + const SizedBox(width: 6), + Text(suggestion['name']!), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Opacity( + opacity: suggestion['pack_avatar_url'] != null ? 0.8 : 0.5, + child: suggestion['pack_avatar_url'] != null + ? Avatar( + mxContent: Uri.tryParse( + suggestion.tryGet('pack_avatar_url') ?? '', + ), + name: suggestion.tryGet('pack_display_name'), + size: size * 0.9, + client: client, + ) + : Text(suggestion['pack_display_name']!), + ), + ), + ), + ], + ), + ); + } + if (suggestion['type'] == 'user' || suggestion['type'] == 'room') { + final url = Uri.parse(suggestion['avatar_url'] ?? ''); + return Container( + padding: padding, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Avatar( + mxContent: url, + name: suggestion.tryGet('displayname') ?? + suggestion.tryGet('mxid'), + size: size, + client: client, + ), + const SizedBox(width: 6), + Text(suggestion['displayname'] ?? suggestion['mxid']!), + ], + ), + ); + } return const SizedBox.shrink(); } From 87255ecdc07f40632d73c18b9f58c311a31d8d9a Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 6 Aug 2024 10:05:16 -0400 Subject: [PATCH 08/23] Fix mention suggestion overflow --- lib/pages/chat/input_bar.dart | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index 4a524bf5f..aea82a886 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -221,7 +221,6 @@ class InputBar extends StatelessWidget { } } } - print("ret length: ${ret.length}"); return ret; } @@ -231,7 +230,10 @@ class InputBar extends StatelessWidget { Client? client, ) { const size = 30.0; - const padding = EdgeInsets.all(4.0); + // #Pangea + // const padding = EdgeInsets.all(4.0); + const padding = EdgeInsets.all(8.0); + // Pangea# if (suggestion['type'] == 'command') { final command = suggestion['name']!; final hint = commandHint(L10n.of(context)!, command); @@ -323,7 +325,17 @@ class InputBar extends StatelessWidget { client: client, ), const SizedBox(width: 6), - Text(suggestion['displayname'] ?? suggestion['mxid']!), + // #Pangea + Flexible( + child: + // Pangea# + Text( + suggestion['displayname'] ?? suggestion['mxid']!, + // #Pangea + overflow: TextOverflow.ellipsis, + // Pangea# + ), + ), ], ), ); @@ -536,6 +548,15 @@ class InputBar extends StatelessWidget { // fix loading briefly flickering a dark box emptyBuilder: (BuildContext context) => const SizedBox .shrink(), // fix loading briefly showing no suggestions + // #Pangea + // If we ever want to change the suggestion background color + // here is the code for it + // decorationBuilder: (context, child) => Material( + // borderRadius: BorderRadius.circular(AppConfig.borderRadius), + // color: Theme.of(context).colorScheme.surfaceContainerHigh, + // child: child, + // ), + // Pangea# ), ), ), From 291dcfc192397c394d4a29433bc1b543170e22ff Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 12 Aug 2024 14:58:11 -0400 Subject: [PATCH 09/23] Toolbar can show downward --- lib/pangea/widgets/chat/message_toolbar.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 3c63c81fc..7c5682933 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -59,8 +59,12 @@ class ToolbarDisplayController { } void showToolbar(BuildContext context, {MessageMode? mode}) { - // Close keyboard, if open - FocusManager.instance.primaryFocus?.unfocus(); + // Don't show toolbar if keyboard open + if (controller.inputFocus.hasFocus) { + FocusManager.instance.primaryFocus?.unfocus(); + return; + } + bool toolbarUp = true; if (highlighted) return; if (controller.selectMode) { @@ -86,13 +90,12 @@ class ToolbarDisplayController { if (targetOffset.dy < 320) { final spaceBeneath = MediaQuery.of(context).size.height - (targetOffset.dy + transformTargetSize.height); - // If toolbar is open, opening toolbar beneath without scrolling can cause issues - // if (spaceBeneath >= 320) { - // toolbarUp = false; - // } + if (spaceBeneath >= 320) { + toolbarUp = false; + } // See if it's possible to scroll up to make space - if (controller.scrollController.offset - targetOffset.dy + 320 >= + else if (controller.scrollController.offset - targetOffset.dy + 320 >= controller.scrollController.position.minScrollExtent && controller.scrollController.offset - targetOffset.dy + 320 <= controller.scrollController.position.maxScrollExtent) { From 20ffe7ba37cc9f8398a42044d20c3c72b94fb49b Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 29 Jul 2024 14:49:12 -0400 Subject: [PATCH 10/23] Pass previous X messages to IGC --- .../controllers/choreographer.dart | 33 +++++++++++++++++++ .../controllers/igc_controller.dart | 1 + .../pangea_message_event.dart | 15 +++++++++ lib/pangea/repo/igc_repo.dart | 16 +++++++++ 4 files changed, 65 insertions(+) diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 1a11de0e3..eab917f4d 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -9,16 +9,19 @@ import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/subscription_controller.dart'; import 'package:fluffychat/pangea/enum/assistance_state_enum.dart'; import 'package:fluffychat/pangea/enum/edit_type.dart'; +import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; 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/repo/igc_repo.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'; import 'package:fluffychat/pangea/widgets/igc/paywall_card.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import '../../../widgets/matrix.dart'; @@ -66,6 +69,36 @@ class Choreographer { clear(); } + List prevMessages() { + const int howFarBack = 5; + final List events = chatController.timeline?.events ?? []; + final List messages = []; + for (final Event event in events) { + if (event.messageType == MessageTypes.Text || + event.messageType == MessageTypes.Audio) { + final Map? content = + (event.messageType == MessageTypes.Text) + ? event.content + : (event as PangeaMessageEvent) + .getSpeechToTextLocalOnly(l1LangCode, l2LangCode) + ?.toJson(); + if (content != null) { + messages.add( + PreviousMessage( + event.content, + event.senderId, + event.originServerTs, + ), + ); + if (messages.length >= howFarBack) { + return messages; + } + } + } + } + return messages; + } + void send(BuildContext context) { if (isFetching) return; diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index ab0e48669..89941ccec 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -43,6 +43,7 @@ class IgcController { userL2: choreographer.l2LangCode!, enableIGC: choreographer.igcEnabled && !onlyTokensAndLanguageDetection, enableIT: choreographer.itEnabled && !onlyTokensAndLanguageDetection, + prevMessages: choreographer.prevMessages(), ); final IGCTextData igcTextDataResponse = await IgcRepo.getIGC( diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index f1c9e5082..edc4abf0e 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -269,6 +269,21 @@ class PangeaMessageEvent { null; }).toSet(); + SpeechToTextModel? getSpeechToTextLocalOnly( + String? l1Code, + String? l2Code, + ) { + if (l1Code == null || l2Code == null) { + return null; + } + return representations + .firstWhereOrNull( + (element) => element.content.speechToText != null, + ) + ?.content + .speechToText; + } + Future getSpeechToText( String l1Code, String l2Code, diff --git a/lib/pangea/repo/igc_repo.dart b/lib/pangea/repo/igc_repo.dart index e32f6cab7..ce50367cd 100644 --- a/lib/pangea/repo/igc_repo.dart +++ b/lib/pangea/repo/igc_repo.dart @@ -94,12 +94,27 @@ class IgcRepo { } } +/// Previous text/audio messages sent in chat +/// Contain message content, sender, and timestamp +class PreviousMessage { + Map content; + String sender; + DateTime timestamp; + + PreviousMessage( + this.content, + this.sender, + this.timestamp, + ); +} + class IGCRequestBody { String fullText; String userL1; String userL2; bool enableIT; bool enableIGC; + List prevMessages; IGCRequestBody({ required this.fullText, @@ -107,6 +122,7 @@ class IGCRequestBody { required this.userL2, required this.enableIGC, required this.enableIT, + required this.prevMessages, }); Map toJson() => { From 61d20f8b3732227c92b4584b088499634befc98c Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 30 Jul 2024 11:03:21 -0400 Subject: [PATCH 11/23] Add prevMessages to more places --- .../choreographer/controllers/span_data_controller.dart | 2 ++ lib/pangea/constants/model_keys.dart | 1 + lib/pangea/models/igc_text_data_model.dart | 4 ++++ lib/pangea/repo/igc_repo.dart | 2 ++ lib/pangea/repo/span_data_repo.dart | 6 ++++++ 5 files changed, 15 insertions(+) diff --git a/lib/pangea/choreographer/controllers/span_data_controller.dart b/lib/pangea/choreographer/controllers/span_data_controller.dart index 24470454c..910246abf 100644 --- a/lib/pangea/choreographer/controllers/span_data_controller.dart +++ b/lib/pangea/choreographer/controllers/span_data_controller.dart @@ -54,6 +54,7 @@ class SpanDataController { userL2: choreographer.l2LangCode!, enableIGC: choreographer.igcEnabled, enableIT: choreographer.itEnabled, + prevMessages: choreographer.prevMessages(), span: span, ); final int cacheKey = req.hashCode; @@ -71,6 +72,7 @@ class SpanDataController { userL2: choreographer.l2LangCode!, enableIGC: choreographer.igcEnabled, enableIT: choreographer.itEnabled, + prevMessages: choreographer.prevMessages(), span: span, ), ); diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart index b42061446..d252a3c7c 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -69,6 +69,7 @@ class ModelKey { static const String permissions = "permissions"; static const String enableIGC = "enable_igc"; static const String enableIT = "enable_it"; + static const String prevMessages = "prev_messages"; static const String originalSent = "original_sent"; static const String originalWritten = "original_written"; diff --git a/lib/pangea/models/igc_text_data_model.dart b/lib/pangea/models/igc_text_data_model.dart index 5f32f92d1..588318011 100644 --- a/lib/pangea/models/igc_text_data_model.dart +++ b/lib/pangea/models/igc_text_data_model.dart @@ -26,6 +26,7 @@ class IGCTextData { bool enableIT; bool enableIGC; bool loading = false; + Map prevMessages; IGCTextData({ required this.detections, @@ -37,6 +38,7 @@ class IGCTextData { required this.userL2, required this.enableIT, required this.enableIGC, + required this.prevMessages, }); factory IGCTextData.fromJson(Map json) { @@ -76,6 +78,7 @@ class IGCTextData { userL2: json[ModelKey.userL2], enableIT: json["enable_it"], enableIGC: json["enable_igc"], + prevMessages: json["prev_messages"], ); } @@ -93,6 +96,7 @@ class IGCTextData { ModelKey.userL2: userL2, "enable_it": enableIT, "enable_igc": enableIGC, + "prev_messages": prevMessages, }; /// if we haven't run IGC or IT or there are no matches, we use the highest validated detection diff --git a/lib/pangea/repo/igc_repo.dart b/lib/pangea/repo/igc_repo.dart index ce50367cd..0cb3e2e46 100644 --- a/lib/pangea/repo/igc_repo.dart +++ b/lib/pangea/repo/igc_repo.dart @@ -88,6 +88,7 @@ class IgcRepo { userL2: "en", enableIT: true, enableIGC: true, + prevMessages: {}, ); return igcTextData; @@ -131,5 +132,6 @@ class IGCRequestBody { ModelKey.userL2: userL2, "enable_it": enableIT, "enable_igc": enableIGC, + "prev_messages": prevMessages, }; } diff --git a/lib/pangea/repo/span_data_repo.dart b/lib/pangea/repo/span_data_repo.dart index 7073581a1..df6b26c76 100644 --- a/lib/pangea/repo/span_data_repo.dart +++ b/lib/pangea/repo/span_data_repo.dart @@ -5,6 +5,7 @@ import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/enum/span_choice_type.dart'; import 'package:fluffychat/pangea/enum/span_data_type.dart'; import 'package:fluffychat/pangea/models/span_data.dart'; +import 'package:fluffychat/pangea/repo/igc_repo.dart'; import 'package:http/http.dart'; import '../constants/model_keys.dart'; @@ -47,6 +48,7 @@ class SpanDetailsRepoReqAndRes { String userL2; bool enableIT; bool enableIGC; + List prevMessages; SpanData span; SpanDetailsRepoReqAndRes({ @@ -54,6 +56,7 @@ class SpanDetailsRepoReqAndRes { required this.userL2, required this.enableIGC, required this.enableIT, + required this.prevMessages, required this.span, }); @@ -62,6 +65,7 @@ class SpanDetailsRepoReqAndRes { ModelKey.userL2: userL2, "enable_it": enableIT, "enable_igc": enableIGC, + "prev_messages": prevMessages, 'span': span.toJson(), }; @@ -71,6 +75,7 @@ class SpanDetailsRepoReqAndRes { userL2: json['user_l2'] as String, enableIT: json['enable_it'] as bool, enableIGC: json['enable_igc'] as bool, + prevMessages: json['prev_messages'], span: SpanData.fromJson(json['span']), ); @@ -132,6 +137,7 @@ final mockRequest = SpanDetailsRepoReqAndRes( userL2: "en", enableIGC: true, enableIT: true, + prevMessages: [], span: spanDataRepomockSpan, ); From 55d75efa061e9f33aa1f89418f8bd758077fe380 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 30 Jul 2024 11:05:51 -0400 Subject: [PATCH 12/23] Forgot to save earlier --- lib/pangea/models/igc_text_data_model.dart | 3 ++- lib/pangea/repo/igc_repo.dart | 2 +- lib/pangea/repo/span_data_repo.dart | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/pangea/models/igc_text_data_model.dart b/lib/pangea/models/igc_text_data_model.dart index 588318011..eba1c1476 100644 --- a/lib/pangea/models/igc_text_data_model.dart +++ b/lib/pangea/models/igc_text_data_model.dart @@ -5,6 +5,7 @@ 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/repo/igc_repo.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -26,7 +27,7 @@ class IGCTextData { bool enableIT; bool enableIGC; bool loading = false; - Map prevMessages; + List prevMessages; IGCTextData({ required this.detections, diff --git a/lib/pangea/repo/igc_repo.dart b/lib/pangea/repo/igc_repo.dart index 0cb3e2e46..552895068 100644 --- a/lib/pangea/repo/igc_repo.dart +++ b/lib/pangea/repo/igc_repo.dart @@ -88,7 +88,7 @@ class IgcRepo { userL2: "en", enableIT: true, enableIGC: true, - prevMessages: {}, + prevMessages: [], ); return igcTextData; diff --git a/lib/pangea/repo/span_data_repo.dart b/lib/pangea/repo/span_data_repo.dart index df6b26c76..360ddd421 100644 --- a/lib/pangea/repo/span_data_repo.dart +++ b/lib/pangea/repo/span_data_repo.dart @@ -75,7 +75,7 @@ class SpanDetailsRepoReqAndRes { userL2: json['user_l2'] as String, enableIT: json['enable_it'] as bool, enableIGC: json['enable_igc'] as bool, - prevMessages: json['prev_messages'], + prevMessages: json['prev_messages'] as List, span: SpanData.fromJson(json['span']), ); @@ -90,6 +90,7 @@ class SpanDetailsRepoReqAndRes { if (other.userL2 != userL2) return false; if (other.enableIT != enableIT) return false; if (other.enableIGC != enableIGC) return false; + if (!other.prevMessages.equals(prevMessages)) 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)), @@ -109,6 +110,7 @@ class SpanDetailsRepoReqAndRes { userL2.hashCode, enableIT.hashCode, enableIGC.hashCode, + prevMessages.hashCode, if (span.choices != null) Object.hashAll( span.choices! From 2ca6d102aa96b8369454a62027f7d0eff4d1bcc4 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 31 Jul 2024 11:28:26 -0400 Subject: [PATCH 13/23] Add json methods to PreviousMessage --- .../controllers/choreographer.dart | 48 +++++++++++-------- lib/pangea/constants/model_keys.dart | 3 ++ lib/pangea/repo/igc_repo.dart | 30 +++++++++--- 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index eab917f4d..8768ed4ff 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -71,28 +71,36 @@ class Choreographer { List prevMessages() { const int howFarBack = 5; - final List events = chatController.timeline?.events ?? []; + final List events = chatController.timeline?.events + .where( + (e) => + e.messageType == MessageTypes.Text || + e.messageType == MessageTypes.Audio, + ) + .toList() ?? + []; + // Sort from most recent to least + events.sort( + (a, b) => b.originServerTs.compareTo(a.originServerTs), + ); final List messages = []; for (final Event event in events) { - if (event.messageType == MessageTypes.Text || - event.messageType == MessageTypes.Audio) { - final Map? content = - (event.messageType == MessageTypes.Text) - ? event.content - : (event as PangeaMessageEvent) - .getSpeechToTextLocalOnly(l1LangCode, l2LangCode) - ?.toJson(); - if (content != null) { - messages.add( - PreviousMessage( - event.content, - event.senderId, - event.originServerTs, - ), - ); - if (messages.length >= howFarBack) { - return messages; - } + final Map? content = + (event.messageType == MessageTypes.Text) + ? event.content + : (event as PangeaMessageEvent) + .getSpeechToTextLocalOnly(l1LangCode, l2LangCode) + ?.toJson(); + if (content != null) { + messages.add( + PreviousMessage( + event.content, + event.senderId, + event.originServerTs, + ), + ); + if (messages.length >= howFarBack) { + return messages; } } } diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart index d252a3c7c..195e919b4 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -70,6 +70,9 @@ class ModelKey { static const String enableIGC = "enable_igc"; static const String enableIT = "enable_it"; static const String prevMessages = "prev_messages"; + static const String prevContent = "prev_content"; + static const String prevSender = "prev_sender"; + static const String prevTimestamp = "prev_timestamp"; static const String originalSent = "original_sent"; static const String originalWritten = "original_written"; diff --git a/lib/pangea/repo/igc_repo.dart b/lib/pangea/repo/igc_repo.dart index 552895068..50dba9c2a 100644 --- a/lib/pangea/repo/igc_repo.dart +++ b/lib/pangea/repo/igc_repo.dart @@ -95,18 +95,33 @@ class IgcRepo { } } -/// Previous text/audio messages sent in chat +/// Previous text/audio message sent in chat /// Contain message content, sender, and timestamp class PreviousMessage { Map content; String sender; DateTime timestamp; - PreviousMessage( - this.content, - this.sender, - this.timestamp, - ); + PreviousMessage({ + required this.content, + required this.sender, + required this.timestamp, + }); + + factory PreviousMessage.fromJson(Map json) => + PreviousMessage( + content: jsonDecode(json[ModelKey.prevContent]) ?? {}, + sender: json[ModelKey.prevSender] ?? "", + timestamp: json[ModelKey.prevTimestamp] == null + ? DateTime.now() + : DateTime.parse(json[ModelKey.prevTimestamp]), + ); + + Map toJson() => { + ModelKey.prevContent: jsonEncode(content), + ModelKey.prevSender: sender, + ModelKey.prevTimestamp: timestamp.toIso8601String(), + }; } class IGCRequestBody { @@ -132,6 +147,7 @@ class IGCRequestBody { ModelKey.userL2: userL2, "enable_it": enableIT, "enable_igc": enableIGC, - "prev_messages": prevMessages, + ModelKey.prevMessages: + jsonEncode(prevMessages.map((x) => x.toJson()).toList()), }; } From 4f924656e0fb49b37ce6da5c7f91bba45009e793 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 5 Aug 2024 11:37:24 -0400 Subject: [PATCH 14/23] Fix minor error --- lib/pangea/choreographer/controllers/choreographer.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 8768ed4ff..c462ff3b4 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -94,9 +94,9 @@ class Choreographer { if (content != null) { messages.add( PreviousMessage( - event.content, - event.senderId, - event.originServerTs, + content: event.content, + sender: event.senderId, + timestamp: event.originServerTs, ), ); if (messages.length >= howFarBack) { From 34d02bb95c13f5bf8956e8f8e0c55d3f3d08642d Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 13 Aug 2024 09:04:44 -0400 Subject: [PATCH 15/23] Remove prev message from span --- .../choreographer/controllers/span_data_controller.dart | 2 -- lib/pangea/repo/span_data_repo.dart | 8 -------- 2 files changed, 10 deletions(-) diff --git a/lib/pangea/choreographer/controllers/span_data_controller.dart b/lib/pangea/choreographer/controllers/span_data_controller.dart index 910246abf..24470454c 100644 --- a/lib/pangea/choreographer/controllers/span_data_controller.dart +++ b/lib/pangea/choreographer/controllers/span_data_controller.dart @@ -54,7 +54,6 @@ class SpanDataController { userL2: choreographer.l2LangCode!, enableIGC: choreographer.igcEnabled, enableIT: choreographer.itEnabled, - prevMessages: choreographer.prevMessages(), span: span, ); final int cacheKey = req.hashCode; @@ -72,7 +71,6 @@ class SpanDataController { userL2: choreographer.l2LangCode!, enableIGC: choreographer.igcEnabled, enableIT: choreographer.itEnabled, - prevMessages: choreographer.prevMessages(), span: span, ), ); diff --git a/lib/pangea/repo/span_data_repo.dart b/lib/pangea/repo/span_data_repo.dart index 360ddd421..7073581a1 100644 --- a/lib/pangea/repo/span_data_repo.dart +++ b/lib/pangea/repo/span_data_repo.dart @@ -5,7 +5,6 @@ import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/enum/span_choice_type.dart'; import 'package:fluffychat/pangea/enum/span_data_type.dart'; import 'package:fluffychat/pangea/models/span_data.dart'; -import 'package:fluffychat/pangea/repo/igc_repo.dart'; import 'package:http/http.dart'; import '../constants/model_keys.dart'; @@ -48,7 +47,6 @@ class SpanDetailsRepoReqAndRes { String userL2; bool enableIT; bool enableIGC; - List prevMessages; SpanData span; SpanDetailsRepoReqAndRes({ @@ -56,7 +54,6 @@ class SpanDetailsRepoReqAndRes { required this.userL2, required this.enableIGC, required this.enableIT, - required this.prevMessages, required this.span, }); @@ -65,7 +62,6 @@ class SpanDetailsRepoReqAndRes { ModelKey.userL2: userL2, "enable_it": enableIT, "enable_igc": enableIGC, - "prev_messages": prevMessages, 'span': span.toJson(), }; @@ -75,7 +71,6 @@ class SpanDetailsRepoReqAndRes { userL2: json['user_l2'] as String, enableIT: json['enable_it'] as bool, enableIGC: json['enable_igc'] as bool, - prevMessages: json['prev_messages'] as List, span: SpanData.fromJson(json['span']), ); @@ -90,7 +85,6 @@ class SpanDetailsRepoReqAndRes { if (other.userL2 != userL2) return false; if (other.enableIT != enableIT) return false; if (other.enableIGC != enableIGC) return false; - if (!other.prevMessages.equals(prevMessages)) 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)), @@ -110,7 +104,6 @@ class SpanDetailsRepoReqAndRes { userL2.hashCode, enableIT.hashCode, enableIGC.hashCode, - prevMessages.hashCode, if (span.choices != null) Object.hashAll( span.choices! @@ -139,7 +132,6 @@ final mockRequest = SpanDetailsRepoReqAndRes( userL2: "en", enableIGC: true, enableIT: true, - prevMessages: [], span: spanDataRepomockSpan, ); From 54d63b1a4f133c3fb5acf7bd52e47d82774456ad Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 13 Aug 2024 10:39:30 -0400 Subject: [PATCH 16/23] Fix audio errors --- .../controllers/choreographer.dart | 58 +++++++++++-------- lib/pangea/repo/igc_repo.dart | 6 +- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index c462ff3b4..bff53890a 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -19,6 +19,7 @@ import 'package:fluffychat/pangea/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/overlay.dart'; import 'package:fluffychat/pangea/widgets/igc/paywall_card.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; @@ -71,38 +72,47 @@ class Choreographer { List prevMessages() { const int howFarBack = 5; - final List events = chatController.timeline?.events + final List eventList = chatController.timeline?.events .where( (e) => - e.messageType == MessageTypes.Text || - e.messageType == MessageTypes.Audio, + e.isVisibleInGui && + (e.messageType == MessageTypes.Text || + e.messageType == MessageTypes.Audio) && + e.status.isSent && + e.type.equals('m.room.message'), ) .toList() ?? []; - // Sort from most recent to least - events.sort( - (a, b) => b.originServerTs.compareTo(a.originServerTs), - ); final List messages = []; - for (final Event event in events) { - final Map? content = - (event.messageType == MessageTypes.Text) - ? event.content - : (event as PangeaMessageEvent) - .getSpeechToTextLocalOnly(l1LangCode, l2LangCode) - ?.toJson(); - if (content != null) { - messages.add( - PreviousMessage( - content: event.content, - sender: event.senderId, - timestamp: event.originServerTs, - ), - ); - if (messages.length >= howFarBack) { - return messages; + try { + for (final Event event in eventList) { + final String? content = (event.messageType == MessageTypes.Text) + ? event.content.toString() + : PangeaMessageEvent( + event: event, + // eventList will be empty if the timeline is null + timeline: chatController.timeline!, + ownMessage: event.senderId == + pangeaController.matrixState.client.userID, + ) + .getSpeechToTextLocalOnly(l1LangCode, l2LangCode) + ?.transcript + .text; + if (content != null) { + messages.add( + PreviousMessage( + content: content, + sender: event.senderId, + timestamp: event.originServerTs, + ), + ); + if (messages.length >= howFarBack) { + return messages; + } } } + } catch (err, stack) { + ErrorHandler.logError(e: err, s: stack); } return messages; } diff --git a/lib/pangea/repo/igc_repo.dart b/lib/pangea/repo/igc_repo.dart index 50dba9c2a..861932812 100644 --- a/lib/pangea/repo/igc_repo.dart +++ b/lib/pangea/repo/igc_repo.dart @@ -98,7 +98,7 @@ class IgcRepo { /// Previous text/audio message sent in chat /// Contain message content, sender, and timestamp class PreviousMessage { - Map content; + String content; String sender; DateTime timestamp; @@ -110,7 +110,7 @@ class PreviousMessage { factory PreviousMessage.fromJson(Map json) => PreviousMessage( - content: jsonDecode(json[ModelKey.prevContent]) ?? {}, + content: json[ModelKey.prevContent] ?? "", sender: json[ModelKey.prevSender] ?? "", timestamp: json[ModelKey.prevTimestamp] == null ? DateTime.now() @@ -118,7 +118,7 @@ class PreviousMessage { ); Map toJson() => { - ModelKey.prevContent: jsonEncode(content), + ModelKey.prevContent: content, ModelKey.prevSender: sender, ModelKey.prevTimestamp: timestamp.toIso8601String(), }; From b5abef7d1aa37f5eb4c956adb8f2d71eae32e2e2 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 20 Aug 2024 15:13:46 -0400 Subject: [PATCH 17/23] moved previous message function to igc controller --- .../controllers/choreographer.dart | 51 ------------------- .../controllers/igc_controller.dart | 47 ++++++++++++++++- .../pangea_message_event.dart | 2 +- lib/pangea/models/igc_text_data_model.dart | 5 -- lib/pangea/repo/igc_repo.dart | 1 - 5 files changed, 47 insertions(+), 59 deletions(-) diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index bff53890a..1a11de0e3 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -9,20 +9,16 @@ import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/subscription_controller.dart'; import 'package:fluffychat/pangea/enum/assistance_state_enum.dart'; import 'package:fluffychat/pangea/enum/edit_type.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; 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/repo/igc_repo.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'; import 'package:fluffychat/pangea/widgets/igc/paywall_card.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import '../../../widgets/matrix.dart'; @@ -70,53 +66,6 @@ class Choreographer { clear(); } - List prevMessages() { - const int howFarBack = 5; - final List eventList = chatController.timeline?.events - .where( - (e) => - e.isVisibleInGui && - (e.messageType == MessageTypes.Text || - e.messageType == MessageTypes.Audio) && - e.status.isSent && - e.type.equals('m.room.message'), - ) - .toList() ?? - []; - final List messages = []; - try { - for (final Event event in eventList) { - final String? content = (event.messageType == MessageTypes.Text) - ? event.content.toString() - : PangeaMessageEvent( - event: event, - // eventList will be empty if the timeline is null - timeline: chatController.timeline!, - ownMessage: event.senderId == - pangeaController.matrixState.client.userID, - ) - .getSpeechToTextLocalOnly(l1LangCode, l2LangCode) - ?.transcript - .text; - if (content != null) { - messages.add( - PreviousMessage( - content: content, - sender: event.senderId, - timestamp: event.originServerTs, - ), - ); - if (messages.length >= howFarBack) { - return messages; - } - } - } - } catch (err, stack) { - ErrorHandler.logError(e: err, s: stack); - } - return messages; - } - void send(BuildContext context) { if (isFetching) return; diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index 89941ccec..237697b40 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -4,12 +4,14 @@ import 'dart:developer'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; import 'package:fluffychat/pangea/choreographer/controllers/span_data_controller.dart'; +import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; 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/widgets/igc/span_card.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; import '../../models/span_card_model.dart'; import '../../utils/error_handler.dart'; @@ -43,7 +45,7 @@ class IgcController { userL2: choreographer.l2LangCode!, enableIGC: choreographer.igcEnabled && !onlyTokensAndLanguageDetection, enableIT: choreographer.itEnabled && !onlyTokensAndLanguageDetection, - prevMessages: choreographer.prevMessages(), + prevMessages: prevMessages(), ); final IGCTextData igcTextDataResponse = await IgcRepo.getIGC( @@ -126,6 +128,49 @@ class IgcController { ); } + /// Get the content of previous text and audio messages in chat. + /// Passed to IGC request to add context. + List prevMessages({int numMessages = 5}) { + final List events = choreographer.chatController.visibleEvents + .where( + (e) => + e.type == EventTypes.Message && + (e.messageType == MessageTypes.Text || + e.messageType == MessageTypes.Audio), + ) + .toList(); + + final List messages = []; + for (final Event event in events) { + final String? content = event.messageType == MessageTypes.Text + ? event.content.toString() + : PangeaMessageEvent( + event: event, + timeline: choreographer.chatController.timeline!, + ownMessage: event.senderId == + choreographer.pangeaController.matrixState.client.userID, + ) + .getSpeechToTextLocal( + choreographer.l1LangCode, + choreographer.l2LangCode, + ) + ?.transcript + .text; + if (content == null) continue; + messages.add( + PreviousMessage( + content: content, + sender: event.senderId, + timestamp: event.originServerTs, + ), + ); + if (messages.length >= numMessages) { + return messages; + } + } + return messages; + } + bool get hasRelevantIGCTextData { if (igcTextData == null) return false; diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index edc4abf0e..cd4ce5e76 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -269,7 +269,7 @@ class PangeaMessageEvent { null; }).toSet(); - SpeechToTextModel? getSpeechToTextLocalOnly( + SpeechToTextModel? getSpeechToTextLocal( String? l1Code, String? l2Code, ) { diff --git a/lib/pangea/models/igc_text_data_model.dart b/lib/pangea/models/igc_text_data_model.dart index eba1c1476..5f32f92d1 100644 --- a/lib/pangea/models/igc_text_data_model.dart +++ b/lib/pangea/models/igc_text_data_model.dart @@ -5,7 +5,6 @@ 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/repo/igc_repo.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -27,7 +26,6 @@ class IGCTextData { bool enableIT; bool enableIGC; bool loading = false; - List prevMessages; IGCTextData({ required this.detections, @@ -39,7 +37,6 @@ class IGCTextData { required this.userL2, required this.enableIT, required this.enableIGC, - required this.prevMessages, }); factory IGCTextData.fromJson(Map json) { @@ -79,7 +76,6 @@ class IGCTextData { userL2: json[ModelKey.userL2], enableIT: json["enable_it"], enableIGC: json["enable_igc"], - prevMessages: json["prev_messages"], ); } @@ -97,7 +93,6 @@ class IGCTextData { ModelKey.userL2: userL2, "enable_it": enableIT, "enable_igc": enableIGC, - "prev_messages": prevMessages, }; /// if we haven't run IGC or IT or there are no matches, we use the highest validated detection diff --git a/lib/pangea/repo/igc_repo.dart b/lib/pangea/repo/igc_repo.dart index 861932812..33abb4deb 100644 --- a/lib/pangea/repo/igc_repo.dart +++ b/lib/pangea/repo/igc_repo.dart @@ -88,7 +88,6 @@ class IgcRepo { userL2: "en", enableIT: true, enableIGC: true, - prevMessages: [], ); return igcTextData; From 41ce21355360d3bb68b971e6b1ebaebb8d1db034 Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Thu, 15 Aug 2024 11:43:56 -0400 Subject: [PATCH 18/23] show feedback in user l1 --- lib/pangea/choreographer/widgets/it_bar.dart | 6 +++++- lib/pangea/choreographer/widgets/it_feedback_card.dart | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 28b0f8bd8..fc398cf03 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -305,7 +305,11 @@ class ITChoices extends StatelessWidget { chosenContinuance: controller.currentITStep!.continuances[index].text, bestContinuance: controller.currentITStep!.best.text, - feedbackLang: controller.targetLangCode, + // TODO: we want this to eventually switch between target and source lang, + // based on the learner's proficiency - maybe with the words involved in the translation + // maybe overall. For now, we'll just use the source lang. + feedbackLang: controller.choreographer.l1Lang?.langCode ?? + controller.sourceLangCode, sourceTextLang: controller.sourceLangCode, targetLang: controller.targetLangCode, ), diff --git a/lib/pangea/choreographer/widgets/it_feedback_card.dart b/lib/pangea/choreographer/widgets/it_feedback_card.dart index f959715ce..06f2493d1 100644 --- a/lib/pangea/choreographer/widgets/it_feedback_card.dart +++ b/lib/pangea/choreographer/widgets/it_feedback_card.dart @@ -146,8 +146,11 @@ class ITFeedbackCardView extends StatelessWidget { controller.res!.text, style: BotStyle.text(context), ), - // if res is not null, show a button to translate the text - if (controller.res != null && controller.translatedFeedback == null) + // if res is not null and feedback not in the userL1, show a button to translate the text + if (controller.res != null && + controller.translatedFeedback == null && + controller.widget.req.feedbackLang != + controller.controller.languageController.userL1?.langCode) Column( children: [ const SizedBox(height: 10), From 8a947e83d97f1a316ca24307196bf5817223c396 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 15 Aug 2024 16:55:53 -0400 Subject: [PATCH 19/23] Adds inline tooltip to IT bar --- assets/l10n/intl_en.arb | 3 ++- .../controllers/it_controller.dart | 13 ++++++++++++ lib/pangea/choreographer/widgets/it_bar.dart | 20 +++++++++++++++++++ lib/pangea/enum/instructions_enum.dart | 3 +++ lib/pangea/utils/inline_tooltip.dart | 2 +- 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 490f6c91d..9921f8c28 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4127,5 +4127,6 @@ "createSpace": "Create space", "createChat": "Create chat", "error520Title": "Please try again.", - "error520Desc": "Sorry, we could not understand your message..." + "error520Desc": "Sorry, we could not understand your message...", + "translationChoicesBody": "Click and hold an option for a hint." } \ No newline at end of file diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index 3187d4a6a..6739db2d9 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -3,7 +3,9 @@ import 'dart:developer'; import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; import 'package:fluffychat/pangea/constants/choreo_constants.dart'; +import 'package:fluffychat/pangea/enum/instructions_enum.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:http/http.dart' as http; @@ -54,6 +56,17 @@ class ITController { choreographer.setState(); } + void closeHint() { + MatrixState.pangeaController.instructions.turnOffInstruction( + InlineInstructions.translationChoices.toString(), + ); + MatrixState.pangeaController.instructions.updateEnableInstructions( + InlineInstructions.translationChoices.toString(), + true, + ); + choreographer.setState(); + } + Future initializeIT(ITStartData itStartData) async { _willOpen = true; Future.delayed(const Duration(microseconds: 100), () { diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index fc398cf03..793519e23 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -7,7 +7,9 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_bar_buttons.dart'; 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/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:fluffychat/pangea/utils/inline_tooltip.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -138,6 +140,24 @@ class ITBarState extends State { ), ), ), + if (!widget + .choreographer.pangeaController.instructions + .wereInstructionsTurnedOff( + InlineInstructions.translationChoices.toString(), + )) + Container( + constraints: const BoxConstraints( + maxWidth: 180, + ), + child: InlineTooltip( + body: InlineInstructions.translationChoices + .body(context), + onClose: itController.closeHint, + ), + ), + const SizedBox( + height: 10, + ), ], ), ), diff --git a/lib/pangea/enum/instructions_enum.dart b/lib/pangea/enum/instructions_enum.dart index 4e12c12b8..007f60f37 100644 --- a/lib/pangea/enum/instructions_enum.dart +++ b/lib/pangea/enum/instructions_enum.dart @@ -58,6 +58,7 @@ extension InstructionsEnumExtension on InstructionsEnum { enum InlineInstructions { speechToText, l1Translation, + translationChoices, } extension InlineInstructionsExtension on InlineInstructions { @@ -67,6 +68,8 @@ extension InlineInstructionsExtension on InlineInstructions { return L10n.of(context)!.speechToTextBody; case InlineInstructions.l1Translation: return L10n.of(context)!.l1TranslationBody; + case InlineInstructions.translationChoices: + return L10n.of(context)!.translationChoicesBody; } } } diff --git a/lib/pangea/utils/inline_tooltip.dart b/lib/pangea/utils/inline_tooltip.dart index 21fc7321a..2dc1bfddb 100644 --- a/lib/pangea/utils/inline_tooltip.dart +++ b/lib/pangea/utils/inline_tooltip.dart @@ -29,7 +29,7 @@ class InlineTooltip extends StatelessWidget { child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), - color: Theme.of(context).colorScheme.primary.withAlpha(20), + color: Theme.of(context).colorScheme.primary.withAlpha(40), ), child: Padding( padding: const EdgeInsets.all(10), From 104750732bc6ded3cbdc8a22d3e7e321a418fb63 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 20 Aug 2024 14:36:01 -0400 Subject: [PATCH 20/23] made inline tooltip take up only one line, sped up animation when closing inline tooltip --- .../controllers/it_controller.dart | 21 ++++++---- lib/pangea/choreographer/widgets/it_bar.dart | 39 +++++++------------ lib/pangea/utils/inline_tooltip.dart | 4 +- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index 6739db2d9..74bce2f92 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -5,7 +5,6 @@ import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; import 'package:fluffychat/pangea/constants/choreo_constants.dart'; import 'package:fluffychat/pangea/enum/instructions_enum.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:http/http.dart' as http; @@ -56,15 +55,21 @@ class ITController { choreographer.setState(); } + bool _closingHint = false; + Duration get animationSpeed => (_closingHint || !_willOpen) + ? const Duration(milliseconds: 500) + : const Duration(milliseconds: 2000); + void closeHint() { - MatrixState.pangeaController.instructions.turnOffInstruction( - InlineInstructions.translationChoices.toString(), - ); - MatrixState.pangeaController.instructions.updateEnableInstructions( - InlineInstructions.translationChoices.toString(), - true, - ); + _closingHint = true; + final String hintKey = InlineInstructions.translationChoices.toString(); + final instructionsController = choreographer.pangeaController.instructions; + instructionsController.turnOffInstruction(hintKey); + instructionsController.updateEnableInstructions(hintKey, true); choreographer.setState(); + Future.delayed(const Duration(milliseconds: 500), () { + _closingHint = false; + }); } Future initializeIT(ITStartData itStartData) async { diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 793519e23..f7d928f0c 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -47,12 +47,16 @@ class ITBarState extends State { super.dispose(); } + bool get instructionsTurnedOff => + widget.choreographer.pangeaController.instructions + .wereInstructionsTurnedOff( + InlineInstructions.translationChoices.toString(), + ); + @override Widget build(BuildContext context) { return AnimatedSize( - duration: itController.willOpen - ? const Duration(milliseconds: 2000) - : const Duration(milliseconds: 500), + duration: itController.animationSpeed, curve: Curves.fastOutSlowIn, clipBehavior: Clip.none, child: !itController.willOpen @@ -60,9 +64,7 @@ class ITBarState extends State { : CompositedTransformTarget( link: widget.choreographer.itBarLinkAndKey.link, child: AnimatedOpacity( - duration: itController.willOpen - ? const Duration(milliseconds: 2000) - : const Duration(milliseconds: 500), + duration: itController.animationSpeed, opacity: itController.willOpen ? 1.0 : 0.0, child: Container( key: widget.choreographer.itBarLinkAndKey.key, @@ -111,6 +113,12 @@ class ITBarState extends State { // const SizedBox(height: 40.0), OriginalText(controller: itController), const SizedBox(height: 7.0), + if (!instructionsTurnedOff) + InlineTooltip( + body: InlineInstructions.translationChoices + .body(context), + onClose: itController.closeHint, + ), IntrinsicHeight( child: Container( constraints: @@ -140,24 +148,6 @@ class ITBarState extends State { ), ), ), - if (!widget - .choreographer.pangeaController.instructions - .wereInstructionsTurnedOff( - InlineInstructions.translationChoices.toString(), - )) - Container( - constraints: const BoxConstraints( - maxWidth: 180, - ), - child: InlineTooltip( - body: InlineInstructions.translationChoices - .body(context), - onClose: itController.closeHint, - ), - ), - const SizedBox( - height: 10, - ), ], ), ), @@ -171,6 +161,7 @@ class ITBarState extends State { ), ), ), + // ), ), ); } diff --git a/lib/pangea/utils/inline_tooltip.dart b/lib/pangea/utils/inline_tooltip.dart index 2dc1bfddb..f82a11682 100644 --- a/lib/pangea/utils/inline_tooltip.dart +++ b/lib/pangea/utils/inline_tooltip.dart @@ -26,10 +26,10 @@ class InlineTooltip extends StatelessWidget { onPressed: onClose, ), ), - child: Container( + child: DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), - color: Theme.of(context).colorScheme.primary.withAlpha(40), + color: Theme.of(context).colorScheme.primary.withAlpha(20), ), child: Padding( padding: const EdgeInsets.all(10), From 5c7a1f554b113df86c6b9b1b09e2eb89a2c04f87 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 20 Aug 2024 14:48:44 -0400 Subject: [PATCH 21/23] added tooltip toggle status to matrix profile to prevent showing again after it's been dismissed --- lib/pangea/enum/instructions_enum.dart | 13 ++++++++++ lib/pangea/models/user_model.dart | 34 ++++++++++++++++++++++++++ lib/pangea/utils/instructions.dart | 21 +++++++++++++--- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/lib/pangea/enum/instructions_enum.dart b/lib/pangea/enum/instructions_enum.dart index 007f60f37..48544925e 100644 --- a/lib/pangea/enum/instructions_enum.dart +++ b/lib/pangea/enum/instructions_enum.dart @@ -72,4 +72,17 @@ extension InlineInstructionsExtension on InlineInstructions { return L10n.of(context)!.translationChoicesBody; } } + + bool get toggledOff { + final instructionSettings = + MatrixState.pangeaController.userController.profile.instructionSettings; + switch (this) { + case InlineInstructions.speechToText: + return instructionSettings.showedSpeechToTextTooltip; + case InlineInstructions.l1Translation: + return instructionSettings.showedL1TranslationTooltip; + case InlineInstructions.translationChoices: + return instructionSettings.showedTranslationChoicesTooltip; + } + } } diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index 180422576..24db180dc 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -186,11 +186,18 @@ class UserInstructions { bool showedBlurMeansTranslate; bool showedTooltipInstructions; + bool showedSpeechToTextTooltip; + bool showedL1TranslationTooltip; + bool showedTranslationChoicesTooltip; + UserInstructions({ this.showedItInstructions = false, this.showedClickMessage = false, this.showedBlurMeansTranslate = false, this.showedTooltipInstructions = false, + this.showedSpeechToTextTooltip = false, + this.showedL1TranslationTooltip = false, + this.showedTranslationChoicesTooltip = false, }); factory UserInstructions.fromJson(Map json) => @@ -203,6 +210,12 @@ class UserInstructions { json[InstructionsEnum.blurMeansTranslate.toString()] ?? false, showedTooltipInstructions: json[InstructionsEnum.tooltipInstructions.toString()] ?? false, + showedL1TranslationTooltip: + json[InlineInstructions.l1Translation.toString()] ?? false, + showedTranslationChoicesTooltip: + json[InlineInstructions.translationChoices.toString()] ?? false, + showedSpeechToTextTooltip: + json[InlineInstructions.speechToText.toString()] ?? false, ); Map toJson() { @@ -213,6 +226,12 @@ class UserInstructions { showedBlurMeansTranslate; data[InstructionsEnum.tooltipInstructions.toString()] = showedTooltipInstructions; + data[InlineInstructions.l1Translation.toString()] = + showedL1TranslationTooltip; + data[InlineInstructions.translationChoices.toString()] = + showedTranslationChoicesTooltip; + data[InlineInstructions.speechToText.toString()] = + showedSpeechToTextTooltip; return data; } @@ -238,6 +257,21 @@ class UserInstructions { ?.content[InstructionsEnum.tooltipInstructions.toString()] as bool?) ?? false, + showedL1TranslationTooltip: + (accountData[InlineInstructions.l1Translation.toString()] + ?.content[InlineInstructions.l1Translation.toString()] + as bool?) ?? + false, + showedTranslationChoicesTooltip: (accountData[ + InlineInstructions.translationChoices.toString()] + ?.content[InlineInstructions.translationChoices.toString()] + as bool?) ?? + false, + showedSpeechToTextTooltip: + (accountData[InlineInstructions.speechToText.toString()] + ?.content[InlineInstructions.speechToText.toString()] + as bool?) ?? + false, ); } } diff --git a/lib/pangea/utils/instructions.dart b/lib/pangea/utils/instructions.dart index 165bfa460..78dab6f6c 100644 --- a/lib/pangea/utils/instructions.dart +++ b/lib/pangea/utils/instructions.dart @@ -25,9 +25,15 @@ class InstructionsController { final Map _instructionsShown = {}; /// Returns true if the user requested this popup not be shown again - bool? toggledOff(String key) => InstructionsEnum.values - .firstWhereOrNull((value) => value.toString() == key) - ?.toggledOff; + bool? toggledOff(String key) { + final bool? instruction = InstructionsEnum.values + .firstWhereOrNull((value) => value.toString() == key) + ?.toggledOff; + final bool? tooltip = InlineInstructions.values + .firstWhereOrNull((value) => value.toString() == key) + ?.toggledOff; + return instruction ?? tooltip; + } InstructionsController(PangeaController pangeaController) { _pangeaController = pangeaController; @@ -58,6 +64,15 @@ class InstructionsController { if (key == InstructionsEnum.tooltipInstructions.toString()) { profile.instructionSettings.showedTooltipInstructions = value; } + if (key == InlineInstructions.speechToText.toString()) { + profile.instructionSettings.showedSpeechToTextTooltip = value; + } + if (key == InlineInstructions.l1Translation.toString()) { + profile.instructionSettings.showedL1TranslationTooltip = value; + } + if (key == InlineInstructions.translationChoices.toString()) { + profile.instructionSettings.showedTranslationChoicesTooltip = value; + } return profile; }); } From cace96545624cd5c16e9544812631b2d4add6032 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 20 Aug 2024 15:37:20 -0400 Subject: [PATCH 22/23] wrap chat input row in stateful widget and rebuild on choreo update to keep hint text and send button up-to-date --- lib/pages/chat/chat_input_row.dart | 10 +-- lib/pages/chat/chat_view.dart | 6 +- .../widgets/chat/input_bar_wrapper.dart | 65 +++---------------- 3 files changed, 17 insertions(+), 64 deletions(-) diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index b1174fd2c..bee8a1af8 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -1,9 +1,8 @@ import 'package:animations/animations.dart'; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; +import 'package:fluffychat/pages/chat/input_bar.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'; @@ -34,7 +33,7 @@ class ChatInputRow extends StatelessWidget { controller.pangeaController.languageController.activeL2Model(); String hintText() { - if (controller.choreographer.choreoMode == ChoreoMode.it) { + if (controller.choreographer.itController.willOpen) { return L10n.of(context)!.buildTranslation; } return activel1 != null && @@ -322,10 +321,7 @@ class ChatInputRow extends StatelessWidget { Expanded( child: Padding( padding: const EdgeInsets.symmetric(vertical: 0.0), - // #Pangea - // child: InputBar( - child: InputBarWrapper( - // Pangea# + child: InputBar( room: controller.room, minLines: 1, maxLines: 8, diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 405e16418..4d04d3051 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -5,7 +5,6 @@ import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart'; import 'package:fluffychat/pages/chat/chat_app_bar_title.dart'; import 'package:fluffychat/pages/chat/chat_emoji_picker.dart'; import 'package:fluffychat/pages/chat/chat_event_list.dart'; -import 'package:fluffychat/pages/chat/chat_input_row.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'; @@ -13,6 +12,7 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_bar.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/widgets/chat/chat_floating_action_button.dart'; +import 'package:fluffychat/pangea/widgets/chat/input_bar_wrapper.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'; @@ -498,7 +498,9 @@ class ChatView extends StatelessWidget { ), ReactionsPicker(controller), ReplyDisplay(controller), - ChatInputRow(controller), + ChatInputRowWrapper( + controller: controller, + ), ChatEmojiPicker(controller), ], ), diff --git a/lib/pangea/widgets/chat/input_bar_wrapper.dart b/lib/pangea/widgets/chat/input_bar_wrapper.dart index 9441312bd..6c6f80218 100644 --- a/lib/pangea/widgets/chat/input_bar_wrapper.dart +++ b/lib/pangea/widgets/chat/input_bar_wrapper.dart @@ -1,48 +1,23 @@ import 'dart:async'; -import 'dart:typed_data'; -import 'package:fluffychat/pages/chat/input_bar.dart'; +import 'package:fluffychat/pages/chat/chat.dart'; +import 'package:fluffychat/pages/chat/chat_input_row.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; +class ChatInputRowWrapper extends StatefulWidget { + final ChatController controller; - 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, + const ChatInputRowWrapper({ + required this.controller, super.key, }); @override - State createState() => InputBarWrapperState(); + State createState() => ChatInputRowWrapperState(); } -class InputBarWrapperState extends State { +class ChatInputRowWrapperState extends State { StreamSubscription? _choreoSub; String _currentText = ''; @@ -50,7 +25,7 @@ class InputBarWrapperState extends State { void initState() { // Rebuild the widget each time there's an update from choreo _choreoSub = - widget.controller?.choreographer.stateListener.stream.listen((_) { + widget.controller.choreographer.stateListener.stream.listen((_) { setState(() {}); }); super.initState(); @@ -63,10 +38,6 @@ class InputBarWrapperState extends State { } void refreshOnChange(String text) { - if (widget.onChanged != null) { - widget.onChanged!(text); - } - final bool decreasedFromMaxLength = _currentText.length >= PangeaTextController.maxLength && text.length < PangeaTextController.maxLength; @@ -81,21 +52,5 @@ class InputBarWrapperState extends State { } @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: refreshOnChange, - autofocus: widget.autofocus, - textInputAction: widget.textInputAction, - readOnly: widget.readOnly, - ); - } + Widget build(BuildContext context) => ChatInputRow(widget.controller); } From 381769a6e43743bcdcd349bac10b1338503307fb Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Tue, 27 Aug 2024 13:59:13 -0400 Subject: [PATCH 23/23] adding user id to igc request --- lib/pangea/choreographer/controllers/igc_controller.dart | 1 + lib/pangea/repo/igc_repo.dart | 3 +++ 2 files changed, 4 insertions(+) diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index 237697b40..aae7104d4 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -41,6 +41,7 @@ class IgcController { final IGCRequestBody reqBody = IGCRequestBody( fullText: choreographer.currentText, + userId: choreographer.pangeaController.userController.userId!, userL1: choreographer.l1LangCode!, userL2: choreographer.l2LangCode!, enableIGC: choreographer.igcEnabled && !onlyTokensAndLanguageDetection, diff --git a/lib/pangea/repo/igc_repo.dart b/lib/pangea/repo/igc_repo.dart index 33abb4deb..bb5c27062 100644 --- a/lib/pangea/repo/igc_repo.dart +++ b/lib/pangea/repo/igc_repo.dart @@ -129,6 +129,7 @@ class IGCRequestBody { String userL2; bool enableIT; bool enableIGC; + String userId; List prevMessages; IGCRequestBody({ @@ -137,6 +138,7 @@ class IGCRequestBody { required this.userL2, required this.enableIGC, required this.enableIT, + required this.userId, required this.prevMessages, }); @@ -146,6 +148,7 @@ class IGCRequestBody { ModelKey.userL2: userL2, "enable_it": enableIT, "enable_igc": enableIGC, + ModelKey.userId: userId, ModelKey.prevMessages: jsonEncode(prevMessages.map((x) => x.toJson()).toList()), };