From aa01dc3440417d906537529134cb51479a4b5835 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 24 Jun 2025 09:53:25 -0400 Subject: [PATCH 01/39] chore: allow audio transcript to exapnd outside of original message bounds --- lib/pangea/toolbar/widgets/overlay_message.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index e83e856d3..66173f038 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/events/message_content.dart'; @@ -149,9 +150,9 @@ class OverlayMessage extends StatelessWidget { final transcription = showTranscription ? Container( - width: messageWidth, - constraints: const BoxConstraints( - maxHeight: AppConfig.audioTranscriptionMaxHeight, + constraints: BoxConstraints( + maxHeight: maxHeight - (messageHeight ?? 0), + maxWidth: FluffyThemes.columnWidth * 1.5, ), child: Padding( padding: const EdgeInsets.all(12.0), @@ -178,6 +179,7 @@ class OverlayMessage extends StatelessWidget { child: Column( spacing: 8.0, crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ SttTranscriptTokens( model: overlayController.transcription!, From 8c4d6379b5f333747428df5bfe644a71ee6544d9 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 24 Jun 2025 14:14:57 -0400 Subject: [PATCH 02/39] Adds default avatars to spaces in find your people --- .../find_your_people/public_space_tile.dart | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/pangea/find_your_people/public_space_tile.dart b/lib/pangea/find_your_people/public_space_tile.dart index 7880937a0..ef19865a1 100644 --- a/lib/pangea/find_your_people/public_space_tile.dart +++ b/lib/pangea/find_your_people/public_space_tile.dart @@ -1,11 +1,15 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicator_button.dart'; import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart'; +import 'package:fluffychat/pangea/spaces/constants/space_constants.dart'; import 'package:fluffychat/widgets/avatar.dart'; class PublicSpaceTile extends StatelessWidget { @@ -35,14 +39,29 @@ class PublicSpaceTile extends StatelessWidget { height: isColumnMode ? 80.0 : 58.0, child: Row( children: [ - Avatar( - mxContent: space.avatarUrl, - name: space.name, - size: isColumnMode ? 80.0 : 58.0, - borderRadius: BorderRadius.circular( - 10, - ), - ), + (space.avatarUrl != null) + ? Avatar( + mxContent: space.avatarUrl, + name: space.name, + size: isColumnMode ? 80.0 : 58.0, + borderRadius: BorderRadius.circular( + 10, + ), + ) + : ClipRRect( + borderRadius: BorderRadius.circular( + 10, + ), + child: CachedNetworkImage( + imageUrl: SpaceConstants + .publicSpaceIcons[Random().nextInt( + SpaceConstants.publicSpaceIcons.length, + )], + width: isColumnMode ? 80.0 : 58.0, + height: isColumnMode ? 80.0 : 58.0, + fit: BoxFit.cover, + ), + ), Flexible( child: Padding( padding: const EdgeInsets.all(8.0), From 7bc67174563235729d9cbb7799d51421376a97eb Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 24 Jun 2025 14:20:35 -0400 Subject: [PATCH 03/39] chore: fix overflow in word card text --- .../widgets/word_zoom/word_zoom_widget.dart | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart index a3e9481cb..dcb86b6eb 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart @@ -73,15 +73,18 @@ class WordZoomWidget extends StatelessWidget { ), ), ), - Text( - token.text.content, - style: TextStyle( - fontSize: 32.0, - fontWeight: FontWeight.w600, - height: 1.2, - color: Theme.of(context).brightness == Brightness.light - ? AppConfig.yellowDark - : AppConfig.yellowLight, + Flexible( + child: Text( + token.text.content, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 32.0, + fontWeight: FontWeight.w600, + height: 1.2, + color: Theme.of(context).brightness == Brightness.light + ? AppConfig.yellowDark + : AppConfig.yellowLight, + ), ), ), ConstructXpWidget( From 889741c083fb80531ae774c0b8abaae38fb4be86 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 24 Jun 2025 14:31:56 -0400 Subject: [PATCH 04/39] Make word zoom header flexible, so it doesn't overflow --- .../widgets/word_zoom/word_zoom_widget.dart | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart index a3e9481cb..813d7235c 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart @@ -73,15 +73,17 @@ class WordZoomWidget extends StatelessWidget { ), ), ), - Text( - token.text.content, - style: TextStyle( - fontSize: 32.0, - fontWeight: FontWeight.w600, - height: 1.2, - color: Theme.of(context).brightness == Brightness.light - ? AppConfig.yellowDark - : AppConfig.yellowLight, + Flexible( + child: Text( + token.text.content, + style: TextStyle( + fontSize: 32.0, + fontWeight: FontWeight.w600, + height: 1.2, + color: Theme.of(context).brightness == Brightness.light + ? AppConfig.yellowDark + : AppConfig.yellowLight, + ), ), ), ConstructXpWidget( From d76cb4752da29673f9053109b3b8e0bc84043369 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 24 Jun 2025 14:44:48 -0400 Subject: [PATCH 05/39] chore: allow user to set playback speed before audio starts playing --- lib/pages/chat/events/audio_player.dart | 28 +++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index 11bbfba52..6728e69c0 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -77,6 +77,8 @@ class AudioPlayerState extends State { // #Pangea StreamSubscription? _onAudioPositionChanged; StreamSubscription? _onAudioStateChanged; + + double playbackSpeed = 1.0; // Pangea# @override @@ -175,6 +177,9 @@ class AudioPlayerState extends State { : matrix.audioPlayer; if (currentPlayer != null) { + // #Pangea + currentPlayer.setSpeed(playbackSpeed); + // Pangea# if (currentPlayer.isAtEndPosition) { currentPlayer.seek(Duration.zero); } else if (currentPlayer.playing) { @@ -250,6 +255,7 @@ class AudioPlayerState extends State { final audioPlayer = matrix.audioPlayer = AudioPlayer(); // #Pangea + audioPlayer.setSpeed(playbackSpeed); _onAudioPositionChanged?.cancel(); _onAudioPositionChanged = matrix.audioPlayer!.positionStream.listen((state) { @@ -306,7 +312,25 @@ class AudioPlayerState extends State { void _toggleSpeed() async { final audioPlayer = matrix.audioPlayer; - if (audioPlayer == null) return; + // #Pangea + // if (audioPlayer == null) return; + if (audioPlayer == null || + matrix.voiceMessageEventId.value != widget.eventId) { + switch (playbackSpeed) { + case 1.0: + setState(() => playbackSpeed = 0.75); + case 0.75: + setState(() => playbackSpeed = 0.5); + case 0.5: + setState(() => playbackSpeed = 1.25); + case 1.25: + setState(() => playbackSpeed = 1.5); + default: + setState(() => playbackSpeed = 1.0); + } + return; + } + // Pangea# switch (audioPlayer.speed) { // #Pangea // case 1.0: @@ -599,7 +623,7 @@ class AudioPlayerState extends State { height: 20, child: Center( child: Text( - '${audioPlayer?.speed.toString() ?? 1}x', + '${audioPlayer?.speed.toString() ?? playbackSpeed}x', style: TextStyle( color: widget.color, fontSize: 9, From 646723d7cab3fa5c511a789c4e095ce44083ddcc Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 24 Jun 2025 15:04:03 -0400 Subject: [PATCH 06/39] chore: update pin button after pinning / unpinning a message --- lib/pages/chat/chat.dart | 13 +++++++++++-- lib/pangea/toolbar/widgets/overlay_header.dart | 7 ++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index e2cd8b515..3b2a0dedf 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1856,7 +1856,10 @@ class ChatController extends State } } - void pinEvent() { + // #Pangea + // void pinEvent() { + Future pinEvent() async { + // Pangea# final pinnedEventIds = room.pinnedEventIds; final selectedEventIds = selectedEvents.map((e) => e.eventId).toSet(); final unpin = selectedEventIds.length == 1 && @@ -1866,10 +1869,16 @@ class ChatController extends State } else { pinnedEventIds.addAll(selectedEventIds); } - showFutureLoadingDialog( + // #Pangea + // showFutureLoadingDialog( + // context: context, + // future: () => room.setPinnedEvents(pinnedEventIds), + // ); + await showFutureLoadingDialog( context: context, future: () => room.setPinnedEvents(pinnedEventIds), ); + // Pangea# } Timer? _storeInputTimeoutTimer; diff --git a/lib/pangea/toolbar/widgets/overlay_header.dart b/lib/pangea/toolbar/widgets/overlay_header.dart index a13e1c2a4..81bb4aba4 100644 --- a/lib/pangea/toolbar/widgets/overlay_header.dart +++ b/lib/pangea/toolbar/widgets/overlay_header.dart @@ -109,10 +109,15 @@ class OverlayHeaderState extends State { icon: pinned ? const Icon(Icons.push_pin) : const Icon(Icons.push_pin_outlined), - onPressed: controller.pinEvent, + onPressed: () { + controller + .pinEvent() + .then((_) => setState(() {})); + }, tooltip: pinned ? l10n.unpin : l10n.pinMessage, color: theme.colorScheme.primary, ), + if (controller.canEditSelectedEvents && !controller.selectedEvents.first.isActivityMessage) IconButton( From a688e1c38b1feecc9e45fbbd4012c938d0686098 Mon Sep 17 00:00:00 2001 From: WilsonLe Date: Tue, 24 Jun 2025 15:23:20 -0400 Subject: [PATCH 07/39] setup well-known dir for staging --- .github/workflows/main_deploy.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/main_deploy.yaml b/.github/workflows/main_deploy.yaml index d60208739..51dfaa1fe 100644 --- a/.github/workflows/main_deploy.yaml +++ b/.github/workflows/main_deploy.yaml @@ -50,6 +50,11 @@ jobs: cp public/.env public/assets/.env touch public/assets/envs.json echo "$ENV_OVERRIDES" >> public/assets/envs.json + mkdir -p public/.well-known + curl https://app.pangea.chat/.well-known/apple-app-site-association \ + -o public/.well-known/apple-app-site-association + curl https://app.pangea.chat/.well-known/assetlinks.json \ + -o public/.well-known/assetlinks.json - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v4 with: From 4c11a2a079c23d7bfbefee0c9615d72309f33e3e Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 24 Jun 2025 16:05:41 -0400 Subject: [PATCH 08/39] chore: update phonetic transcription when text changes --- .../phonetic_transcription_widget.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index 8486a9991..83aaf2275 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -51,6 +51,17 @@ class _PhoneticTranscriptionWidgetState _fetchTranscription(); } + @override + void didUpdateWidget( + covariant PhoneticTranscriptionWidget oldWidget, + ) { + super.didUpdateWidget(oldWidget); + if (oldWidget.text != widget.text || + oldWidget.textLanguage != widget.textLanguage) { + _fetchTranscription(); + } + } + Future _fetchTranscription() async { try { setState(() { From f86608bb0a69a45840da0952e86c43a038f8183d Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:21:35 -0400 Subject: [PATCH 09/39] Revert "chore: add fake event to show when activity ended" --- lib/pages/chat/chat_event_list.dart | 27 -- lib/pages/chat/events/message.dart | 4 - .../activities/activity_state_event.dart | 333 +++++++++--------- .../events/constants/pangea_event_types.dart | 1 - .../filtered_timeline_extension.dart | 31 +- 5 files changed, 190 insertions(+), 206 deletions(-) diff --git a/lib/pages/chat/chat_event_list.dart b/lib/pages/chat/chat_event_list.dart index 030f81422..948c0578b 100644 --- a/lib/pages/chat/chat_event_list.dart +++ b/lib/pages/chat/chat_event_list.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:fluffychat/config/app_config.dart'; @@ -11,9 +10,7 @@ import 'package:fluffychat/pages/chat/events/message.dart'; import 'package:fluffychat/pages/chat/seen_by_row.dart'; import 'package:fluffychat/pages/chat/typing_indicators.dart'; import 'package:fluffychat/pangea/activity_planner/activity_plan_message.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; @@ -43,30 +40,6 @@ class ChatEventList extends StatelessWidget { final horizontalPadding = FluffyThemes.isColumnMode(context) ? 8.0 : 0.0; final events = timeline.events.filterByVisibleInGui(); - // #Pangea - if (timeline.room.activityPlan?.endAt != null && - timeline.room.activityPlan!.endAt!.isBefore(DateTime.now())) { - final eventIndex = events.indexWhere( - (e) => e.originServerTs.isBefore( - timeline.room.activityPlan!.endAt!, - ), - ); - - if (eventIndex != -1) { - events.insert( - eventIndex, - Event( - type: PangeaEventTypes.activityPlanEnd, - eventId: timeline.room.client.generateUniqueTransactionId(), - senderId: timeline.room.client.userID!, - originServerTs: timeline.room.activityPlan!.endAt!, - room: timeline.room, - content: {}, - ), - ); - } - } - // Pangea# final animateInEventIndex = controller.animateInEventIndex; // create a map of eventId --> index to greatly improve performance of diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 30d787343..0fd7e79bc 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -134,10 +134,6 @@ class Message extends StatelessWidget { ? ActivityStateEvent(event: event) : const SizedBox(); } - - if (event.type == PangeaEventTypes.activityPlanEnd) { - return const ActivityFinishedEvent(); - } // Pangea# return StateMessage(event); } diff --git a/lib/pangea/activities/activity_state_event.dart b/lib/pangea/activities/activity_state_event.dart index b59b8c233..1f7a250e5 100644 --- a/lib/pangea/activities/activity_state_event.dart +++ b/lib/pangea/activities/activity_state_event.dart @@ -57,6 +57,11 @@ class ActivityStateEventState extends State { } } + bool get _activityIsOver { + return activityPlan?.endAt != null && + DateTime.now().isAfter(activityPlan!.endAt!); + } + @override Widget build(BuildContext context) { if (activityPlan == null) { @@ -78,151 +83,186 @@ class ActivityStateEventState extends State { spacing: 12.0, children: [ Container( - padding: const EdgeInsets.all(16.0), + padding: EdgeInsets.all(_activityIsOver ? 24.0 : 16.0), decoration: BoxDecoration( color: theme.colorScheme.primaryContainer, borderRadius: BorderRadius.circular(18), ), child: AnimatedSize( duration: FluffyThemes.animationDuration, - child: Text( - activityPlan!.markdown, - style: TextStyle( - color: theme.colorScheme.onPrimaryContainer, - fontSize: - AppConfig.fontSizeFactor * AppConfig.messageFontSize, - ), - ), + child: _activityIsOver + ? Column( + spacing: 12.0, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + L10n.of(context).activityEnded, + style: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontSize: 16.0, + ), + ), + CachedNetworkImage( + width: 120.0, + imageUrl: + "${AppConfig.assetsBaseURL}/${ActivityConstants.activityFinishedAsset}", + fit: BoxFit.cover, + placeholder: (context, url) => const Center( + child: CircularProgressIndicator(), + ), + errorWidget: (context, url, error) => + const SizedBox(), + ), + ], + ) + : Text( + activityPlan!.markdown, + style: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontSize: AppConfig.fontSizeFactor * + AppConfig.messageFontSize, + ), + ), ), ), AnimatedSize( duration: FluffyThemes.animationDuration, - child: IntrinsicHeight( - child: Row( - spacing: 12.0, - children: [ - Container( - height: imageWidth, - width: imageWidth, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: activityPlan!.imageURL != null - ? activityPlan!.imageURL!.startsWith("mxc") - ? MxcImage( - uri: Uri.parse( - activityPlan!.imageURL!, - ), - width: imageWidth, - height: imageWidth, - cacheKey: activityPlan!.bookmarkId, - fit: BoxFit.cover, - ) - : CachedNetworkImage( - imageUrl: activityPlan!.imageURL!, - fit: BoxFit.cover, - placeholder: (context, url) => const Center( - child: CircularProgressIndicator(), - ), - errorWidget: ( - context, - url, - error, - ) => - const SizedBox(), - ) - : const SizedBox(), - ), - ), - Expanded( - child: Column( - spacing: 9.0, - mainAxisSize: MainAxisSize.max, + child: _activityIsOver + ? const SizedBox() + : IntrinsicHeight( + child: Row( + spacing: 12.0, children: [ - Expanded( - child: SizedBox.expand( - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - backgroundColor: - theme.colorScheme.primaryContainer, - foregroundColor: - theme.colorScheme.onPrimaryContainer, - ), - onPressed: () async { - final Duration? duration = await showDialog( - context: context, - builder: (context) { - return ActivityDurationPopup( - initialValue: activityPlan?.duration ?? - const Duration(days: 1), - ); - }, - ); - - if (duration == null) return; - - showFutureLoadingDialog( - context: context, - future: () => - widget.event.room.sendActivityPlan( - activityPlan!.copyWith( - endAt: DateTime.now().add(duration), - duration: duration, - ), - ), - ); - }, - child: CountDown( - deadline: activityPlan!.endAt, - iconSize: 20.0, - textSize: 16.0, - ), - ), + Container( + height: imageWidth, + width: imageWidth, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), ), - ), // Optional spacing between buttons + child: ClipRRect( + borderRadius: BorderRadius.circular(16), + child: activityPlan!.imageURL != null + ? activityPlan!.imageURL!.startsWith("mxc") + ? MxcImage( + uri: Uri.parse( + activityPlan!.imageURL!, + ), + width: imageWidth, + height: imageWidth, + cacheKey: activityPlan!.bookmarkId, + fit: BoxFit.cover, + ) + : CachedNetworkImage( + imageUrl: activityPlan!.imageURL!, + fit: BoxFit.cover, + placeholder: (context, url) => + const Center( + child: CircularProgressIndicator(), + ), + errorWidget: ( + context, + url, + error, + ) => + const SizedBox(), + ) + : const SizedBox(), + ), + ), Expanded( - child: SizedBox.expand( - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - backgroundColor: theme.colorScheme.error, - foregroundColor: theme.colorScheme.onPrimary, - ), - onPressed: () { - showFutureLoadingDialog( - context: context, - future: () => - widget.event.room.sendActivityPlan( - activityPlan!.copyWith( - endAt: DateTime.now(), - duration: Duration.zero, + child: Column( + spacing: 9.0, + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: SizedBox.expand( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(20), + ), + backgroundColor: + theme.colorScheme.primaryContainer, + foregroundColor: theme + .colorScheme.onPrimaryContainer, + ), + onPressed: () async { + final Duration? duration = + await showDialog( + context: context, + builder: (context) { + return ActivityDurationPopup( + initialValue: + activityPlan?.duration ?? + const Duration(days: 1), + ); + }, + ); + + if (duration == null) return; + + showFutureLoadingDialog( + context: context, + future: () => widget.event.room + .sendActivityPlan( + activityPlan!.copyWith( + endAt: + DateTime.now().add(duration), + duration: duration, + ), + ), + ); + }, + child: CountDown( + deadline: activityPlan!.endAt, + iconSize: 20.0, + textSize: 16.0, + ), + ), + ), + ), // Optional spacing between buttons + Expanded( + child: SizedBox.expand( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(20), + ), + backgroundColor: + theme.colorScheme.error, + foregroundColor: + theme.colorScheme.onPrimary, + ), + onPressed: () { + showFutureLoadingDialog( + context: context, + future: () => widget.event.room + .sendActivityPlan( + activityPlan!.copyWith( + endAt: DateTime.now(), + duration: Duration.zero, + ), + ), + ); + }, + child: Text( + L10n.of(context).endNow, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), ), ), - ); - }, - child: Text( - L10n.of(context).endNow, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, ), ), - ), + ], ), ), ], ), ), - ], - ), - ), ), ], ), @@ -230,50 +270,3 @@ class ActivityStateEventState extends State { ); } } - -class ActivityFinishedEvent extends StatelessWidget { - const ActivityFinishedEvent({super.key}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Center( - child: Container( - constraints: const BoxConstraints( - maxWidth: 400.0, - ), - margin: const EdgeInsets.all(18.0), - child: Container( - padding: const EdgeInsets.all(24.0), - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(18), - ), - child: Column( - spacing: 12.0, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - L10n.of(context).activityEnded, - style: TextStyle( - color: theme.colorScheme.onPrimaryContainer, - fontSize: 16.0, - ), - ), - CachedNetworkImage( - width: 120.0, - imageUrl: - "${AppConfig.assetsBaseURL}/${ActivityConstants.activityFinishedAsset}", - fit: BoxFit.cover, - placeholder: (context, url) => const Center( - child: CircularProgressIndicator(), - ), - errorWidget: (context, url, error) => const SizedBox(), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/pangea/events/constants/pangea_event_types.dart b/lib/pangea/events/constants/pangea_event_types.dart index a385fe97d..fe2a47e18 100644 --- a/lib/pangea/events/constants/pangea_event_types.dart +++ b/lib/pangea/events/constants/pangea_event_types.dart @@ -26,7 +26,6 @@ class PangeaEventTypes { static const capacity = "pangea.capacity"; static const activityPlan = "pangea.activity_plan"; - static const activityPlanEnd = "pangea.activity.end"; static const userAge = "pangea.user_age"; diff --git a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart index d762a4dcc..f074a43f1 100644 --- a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart +++ b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart @@ -5,9 +5,33 @@ import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import '../../config/app_config.dart'; extension VisibleInGuiExtension on List { - List filterByVisibleInGui({String? exceptionEventId}) => where( - (event) => event.isVisibleInGui || event.eventId == exceptionEventId, - ).toList(); + List filterByVisibleInGui({String? exceptionEventId}) { + final visibleEvents = + where((e) => e.isVisibleInGui || e.eventId == exceptionEventId) + .toList(); + + // Hide creation state events: + if (visibleEvents.isNotEmpty && + visibleEvents.last.type == EventTypes.RoomCreate) { + var i = visibleEvents.length - 2; + while (i > 0) { + final event = visibleEvents[i]; + if (!event.isState) break; + if (event.type == EventTypes.Encryption) { + i--; + continue; + } + if (event.type == EventTypes.RoomMember && + event.roomMemberChangeType == RoomMemberChangeType.acceptInvite) { + i--; + continue; + } + visibleEvents.removeAt(i); + i--; + } + } + return visibleEvents; + } } extension IsStateExtension on Event { @@ -65,7 +89,6 @@ extension IsStateExtension on Event { EventTypes.RoomTombstone, EventTypes.CallInvite, PangeaEventTypes.activityPlan, - PangeaEventTypes.activityPlanEnd, }; // Pangea# } From 57a8c09da254dc7621a6c5a5428035509a48818b Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 24 Jun 2025 16:31:20 -0400 Subject: [PATCH 10/39] chore: revert changes to activity plan message --- lib/l10n/intl_en.arb | 3 - lib/pages/chat/chat_view.dart | 12 - lib/pages/chat/events/message.dart | 18 -- .../activities/activity_aware_builder.dart | 62 ---- lib/pangea/activities/activity_constants.dart | 3 - .../activities/activity_duration_popup.dart | 282 ------------------ .../activities/activity_state_event.dart | 279 ----------------- lib/pangea/activities/countdown.dart | 98 ------ .../activities/pinned_activity_message.dart | 100 ------- .../extensions/pangea_room_extension.dart | 1 + .../extensions/room_events_extension.dart | 55 ++++ .../filtered_timeline_extension.dart | 10 +- 12 files changed, 57 insertions(+), 866 deletions(-) delete mode 100644 lib/pangea/activities/activity_aware_builder.dart delete mode 100644 lib/pangea/activities/activity_constants.dart delete mode 100644 lib/pangea/activities/activity_duration_popup.dart delete mode 100644 lib/pangea/activities/activity_state_event.dart delete mode 100644 lib/pangea/activities/countdown.dart delete mode 100644 lib/pangea/activities/pinned_activity_message.dart diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 0f6b2899e..c5c74d682 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5017,9 +5017,6 @@ "newDirectMessage": "New direct message", "speakingExercisesTooltip": "Speaking practice", "noChatsFoundHereYet": "No chats found here yet", - "endNow": "End now", - "setDuration": "Set duration", - "activityEnded": "That’s a wrap for this activity! Big thanks to everyone for chatting, learning, and making this space so lively. Language grows with conversation, and every word exchanged brings us closer to confidence and fluency.\n\nKeep practicing, stay curious, and don’t be shy to keep the conversation going!", "duration": "Duration", "transcriptionFailed": "Failed to transcribe audio", "aUserIsKnocking": "1 user is requesting to join your space", diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 2f90e7f59..1436cb00b 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -13,11 +13,9 @@ 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_event_list.dart'; import 'package:fluffychat/pages/chat/pinned_events.dart'; -import 'package:fluffychat/pangea/activities/pinned_activity_message.dart'; import 'package:fluffychat/pangea/chat/widgets/chat_input_bar.dart'; import 'package:fluffychat/pangea/chat/widgets/chat_input_bar_header.dart'; import 'package:fluffychat/pangea/chat/widgets/chat_view_background.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/utils/account_config.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; @@ -190,13 +188,6 @@ class ChatView extends StatelessWidget { if (scrollUpBannerEventId != null) { appbarBottomHeight += ChatAppBarListTile.fixedHeight; } - // #Pangea - if (controller.room.activityPlan != null && - controller.room.activityPlan!.endAt != null && - controller.room.activityPlan!.endAt!.isAfter(DateTime.now())) { - appbarBottomHeight += ChatAppBarListTile.fixedHeight; - } - // Pangea# return Scaffold( appBar: AppBar( actionsIconTheme: IconThemeData( @@ -235,9 +226,6 @@ class ChatView extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ PinnedEvents(controller), - // #Pangea - PinnedActivityMessage(controller), - // Pangea# if (scrollUpBannerEventId != null) ChatAppBarListTile( leading: IconButton( diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 30d787343..ada6cceb4 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -9,9 +9,7 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pages/chat/events/room_creation_state_event.dart'; -import 'package:fluffychat/pangea/activities/activity_state_event.dart'; import 'package:fluffychat/pangea/common/widgets/pressable_button.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/file_description.dart'; @@ -123,22 +121,6 @@ class Message extends StatelessWidget { if (event.type == EventTypes.RoomCreate) { return RoomCreationStateEvent(event: event); } - // #Pangea - if (event.type == PangeaEventTypes.activityPlan) { - final state = event.room.getState(PangeaEventTypes.activityPlan); - if (state == null || state is! Event) { - return const SizedBox.shrink(); - } - - return state.originServerTs == event.originServerTs - ? ActivityStateEvent(event: event) - : const SizedBox(); - } - - if (event.type == PangeaEventTypes.activityPlanEnd) { - return const ActivityFinishedEvent(); - } - // Pangea# return StateMessage(event); } diff --git a/lib/pangea/activities/activity_aware_builder.dart b/lib/pangea/activities/activity_aware_builder.dart deleted file mode 100644 index 68eea54af..000000000 --- a/lib/pangea/activities/activity_aware_builder.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -class ActivityAwareBuilder extends StatefulWidget { - final DateTime? deadline; - final Widget Function(bool) builder; - - const ActivityAwareBuilder({ - super.key, - required this.builder, - this.deadline, - }); - - @override - State createState() => ActivityAwareBuilderState(); -} - -class ActivityAwareBuilderState extends State { - Timer? _timer; - - @override - void initState() { - super.initState(); - _setTimer(); - } - - @override - void didUpdateWidget(covariant ActivityAwareBuilder oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.deadline != widget.deadline) { - _setTimer(); - } - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - void _setTimer() { - final now = DateTime.now(); - final delay = widget.deadline?.difference(now); - - if (delay != null && delay > Duration.zero) { - _timer?.cancel(); - _timer = Timer(delay, () { - _timer?.cancel(); - _timer = null; - if (mounted) setState(() {}); - }); - } - } - - @override - Widget build(BuildContext context) { - return widget.builder( - widget.deadline != null && widget.deadline!.isAfter(DateTime.now()), - ); - } -} diff --git a/lib/pangea/activities/activity_constants.dart b/lib/pangea/activities/activity_constants.dart deleted file mode 100644 index 41858d53a..000000000 --- a/lib/pangea/activities/activity_constants.dart +++ /dev/null @@ -1,3 +0,0 @@ -class ActivityConstants { - static const String activityFinishedAsset = "EndActivityMsg.png"; -} diff --git a/lib/pangea/activities/activity_duration_popup.dart b/lib/pangea/activities/activity_duration_popup.dart deleted file mode 100644 index c6cbb56ab..000000000 --- a/lib/pangea/activities/activity_duration_popup.dart +++ /dev/null @@ -1,282 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/l10n/l10n.dart'; - -class ActivityDurationPopup extends StatefulWidget { - final Duration initialValue; - const ActivityDurationPopup({ - super.key, - required this.initialValue, - }); - - @override - State createState() => ActivityDurationPopupState(); -} - -class ActivityDurationPopupState extends State { - final TextEditingController _daysController = TextEditingController(); - final TextEditingController _hoursController = TextEditingController(); - final TextEditingController _minutesController = TextEditingController(); - - String? error; - - final List _durations = [ - const Duration(minutes: 15), - const Duration(minutes: 30), - const Duration(minutes: 45), - const Duration(minutes: 60), - const Duration(hours: 1, minutes: 30), - const Duration(hours: 2), - const Duration(hours: 24), - const Duration(days: 2), - const Duration(days: 7), - ]; - - @override - void initState() { - super.initState(); - _daysController.text = widget.initialValue.inDays.toString(); - _hoursController.text = - widget.initialValue.inHours.remainder(24).toString(); - _minutesController.text = - widget.initialValue.inMinutes.remainder(60).toString(); - - _daysController.addListener(() => setState(() => error = null)); - _hoursController.addListener(() => setState(() => error = null)); - _minutesController.addListener(() => setState(() => error = null)); - } - - @override - void dispose() { - _daysController.dispose(); - _hoursController.dispose(); - _minutesController.dispose(); - super.dispose(); - } - - void _setDuration({int? days, int? hours, int? minutes}) { - setState(() { - if (days != null) _daysController.text = days.toString(); - if (hours != null) _hoursController.text = hours.toString(); - if (minutes != null) _minutesController.text = minutes.toString(); - }); - } - - String _formatDuration(Duration duration) { - final days = duration.inDays; - final hours = duration.inHours.remainder(24); - final minutes = duration.inMinutes.remainder(60); - - final List parts = []; - if (days > 0) parts.add("${days}d"); - if (hours > 0) parts.add("${hours}h"); - if (minutes > 0) parts.add("${minutes}m"); - if (parts.isEmpty) return "0m"; - - return parts.join(" "); - } - - Duration get _duration { - final days = int.tryParse(_daysController.text) ?? 0; - final hours = int.tryParse(_hoursController.text) ?? 0; - final minutes = int.tryParse(_minutesController.text) ?? 0; - return Duration(days: days, hours: hours, minutes: minutes); - } - - void _submit() { - final days = int.tryParse(_daysController.text); - final hours = int.tryParse(_hoursController.text); - final minutes = int.tryParse(_minutesController.text); - - if (days == null || hours == null || minutes == null) { - setState(() { - error = "Invalid duration"; - }); - return; - } - - Navigator.of(context).pop(_duration); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Dialog( - child: ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 350.0, - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(20), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - spacing: 12.0, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - L10n.of(context).setDuration, - style: const TextStyle(fontSize: 20.0, height: 1.2), - ), - Column( - children: [ - Container( - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - side: BorderSide( - width: 2, - color: theme.colorScheme.primary.withAlpha(100), - ), - borderRadius: BorderRadius.circular(20), - ), - ), - padding: const EdgeInsets.only( - top: 12.0, - bottom: 12.0, - right: 24.0, - left: 8.0, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SelectionArea( - child: Row( - spacing: 12.0, - children: [ - _DatePickerInput( - type: "d", - controller: _daysController, - ), - _DatePickerInput( - type: "h", - controller: _hoursController, - ), - _DatePickerInput( - type: "m", - controller: _minutesController, - ), - ], - ), - ), - const Icon( - Icons.alarm, - size: 24, - ), - ], - ), - ), - AnimatedSize( - duration: FluffyThemes.animationDuration, - child: error != null - ? Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - error!, - style: TextStyle( - color: theme.colorScheme.error, - fontSize: 14.0, - ), - ), - ) - : const SizedBox.shrink(), - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 12.0, - horizontal: 24.0, - ), - child: Wrap( - spacing: 10.0, - runSpacing: 10.0, - children: _durations - .map( - (d) => InkWell( - borderRadius: BorderRadius.circular(12), - onTap: () { - _setDuration( - days: d.inDays, - hours: d.inHours.remainder(24), - minutes: d.inMinutes.remainder(60), - ); - }, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 0.0, - ), - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer - .withAlpha(_duration == d ? 200 : 100), - borderRadius: BorderRadius.circular(12), - ), - child: Text(_formatDuration(d)), - ), - ), - ) - .toList(), - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - TextButton( - onPressed: _submit, - child: Text(L10n.of(context).confirm), - ), - ], - ), - ], - ), - ), - ), - ), - ); - } -} - -class _DatePickerInput extends StatelessWidget { - final String type; - final TextEditingController controller; - - const _DatePickerInput({ - required this.type, - required this.controller, - }); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - SizedBox( - width: 35.0, - child: TextField( - controller: controller, - textAlign: TextAlign.end, - decoration: InputDecoration( - isDense: true, - border: InputBorder.none, - contentPadding: const EdgeInsets.all(0.0), - hintText: "0", - hintStyle: TextStyle( - fontSize: 20.0, - color: theme.colorScheme.onSurfaceVariant.withAlpha(100), - ), - ), - style: const TextStyle( - fontSize: 20.0, - ), - keyboardType: TextInputType.number, - ), - ), - Text(type, style: const TextStyle(fontSize: 20.0)), - ], - ); - } -} diff --git a/lib/pangea/activities/activity_state_event.dart b/lib/pangea/activities/activity_state_event.dart deleted file mode 100644 index b59b8c233..000000000 --- a/lib/pangea/activities/activity_state_event.dart +++ /dev/null @@ -1,279 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/activities/activity_constants.dart'; -import 'package:fluffychat/pangea/activities/activity_duration_popup.dart'; -import 'package:fluffychat/pangea/activities/countdown.dart'; -import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; -import 'package:fluffychat/widgets/future_loading_dialog.dart'; -import 'package:fluffychat/widgets/mxc_image.dart'; - -class ActivityStateEvent extends StatefulWidget { - final Event event; - - const ActivityStateEvent({required this.event, super.key}); - - @override - State createState() => ActivityStateEventState(); -} - -class ActivityStateEventState extends State { - Timer? _timer; - - @override - void initState() { - super.initState(); - final now = DateTime.now(); - final delay = activityPlan?.endAt != null - ? activityPlan!.endAt!.difference(now) - : null; - - if (delay != null && delay > Duration.zero) { - _timer = Timer(delay, () { - setState(() {}); - }); - } - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - ActivityPlanModel? get activityPlan { - try { - return ActivityPlanModel.fromJson(widget.event.content); - } catch (e) { - return null; - } - } - - @override - Widget build(BuildContext context) { - if (activityPlan == null) { - return const SizedBox.shrink(); - } - - final theme = Theme.of(context); - final isColumnMode = FluffyThemes.isColumnMode(context); - - final double imageWidth = isColumnMode ? 240.0 : 175.0; - - return Center( - child: Container( - constraints: const BoxConstraints( - maxWidth: 400.0, - ), - margin: const EdgeInsets.all(18.0), - child: Column( - spacing: 12.0, - children: [ - Container( - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(18), - ), - child: AnimatedSize( - duration: FluffyThemes.animationDuration, - child: Text( - activityPlan!.markdown, - style: TextStyle( - color: theme.colorScheme.onPrimaryContainer, - fontSize: - AppConfig.fontSizeFactor * AppConfig.messageFontSize, - ), - ), - ), - ), - AnimatedSize( - duration: FluffyThemes.animationDuration, - child: IntrinsicHeight( - child: Row( - spacing: 12.0, - children: [ - Container( - height: imageWidth, - width: imageWidth, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(16), - child: activityPlan!.imageURL != null - ? activityPlan!.imageURL!.startsWith("mxc") - ? MxcImage( - uri: Uri.parse( - activityPlan!.imageURL!, - ), - width: imageWidth, - height: imageWidth, - cacheKey: activityPlan!.bookmarkId, - fit: BoxFit.cover, - ) - : CachedNetworkImage( - imageUrl: activityPlan!.imageURL!, - fit: BoxFit.cover, - placeholder: (context, url) => const Center( - child: CircularProgressIndicator(), - ), - errorWidget: ( - context, - url, - error, - ) => - const SizedBox(), - ) - : const SizedBox(), - ), - ), - Expanded( - child: Column( - spacing: 9.0, - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: SizedBox.expand( - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - backgroundColor: - theme.colorScheme.primaryContainer, - foregroundColor: - theme.colorScheme.onPrimaryContainer, - ), - onPressed: () async { - final Duration? duration = await showDialog( - context: context, - builder: (context) { - return ActivityDurationPopup( - initialValue: activityPlan?.duration ?? - const Duration(days: 1), - ); - }, - ); - - if (duration == null) return; - - showFutureLoadingDialog( - context: context, - future: () => - widget.event.room.sendActivityPlan( - activityPlan!.copyWith( - endAt: DateTime.now().add(duration), - duration: duration, - ), - ), - ); - }, - child: CountDown( - deadline: activityPlan!.endAt, - iconSize: 20.0, - textSize: 16.0, - ), - ), - ), - ), // Optional spacing between buttons - Expanded( - child: SizedBox.expand( - child: ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - backgroundColor: theme.colorScheme.error, - foregroundColor: theme.colorScheme.onPrimary, - ), - onPressed: () { - showFutureLoadingDialog( - context: context, - future: () => - widget.event.room.sendActivityPlan( - activityPlan!.copyWith( - endAt: DateTime.now(), - duration: Duration.zero, - ), - ), - ); - }, - child: Text( - L10n.of(context).endNow, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ], - ), - ), - ], - ), - ), - ), - ], - ), - ), - ); - } -} - -class ActivityFinishedEvent extends StatelessWidget { - const ActivityFinishedEvent({super.key}); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Center( - child: Container( - constraints: const BoxConstraints( - maxWidth: 400.0, - ), - margin: const EdgeInsets.all(18.0), - child: Container( - padding: const EdgeInsets.all(24.0), - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(18), - ), - child: Column( - spacing: 12.0, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - L10n.of(context).activityEnded, - style: TextStyle( - color: theme.colorScheme.onPrimaryContainer, - fontSize: 16.0, - ), - ), - CachedNetworkImage( - width: 120.0, - imageUrl: - "${AppConfig.assetsBaseURL}/${ActivityConstants.activityFinishedAsset}", - fit: BoxFit.cover, - placeholder: (context, url) => const Center( - child: CircularProgressIndicator(), - ), - errorWidget: (context, url, error) => const SizedBox(), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/pangea/activities/countdown.dart b/lib/pangea/activities/countdown.dart deleted file mode 100644 index 17516cb23..000000000 --- a/lib/pangea/activities/countdown.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:fluffychat/l10n/l10n.dart'; - -class CountDown extends StatefulWidget { - final DateTime? deadline; - - final double? iconSize; - final double? textSize; - - const CountDown({ - super.key, - required this.deadline, - this.iconSize, - this.textSize, - }); - - @override - State createState() => CountDownState(); -} - -class CountDownState extends State { - Timer? _timer; - - @override - void initState() { - super.initState(); - _timer = Timer.periodic(const Duration(seconds: 1), (_) { - setState(() {}); - }); - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - String? _formatDuration(Duration duration) { - final days = duration.inDays; - final hours = duration.inHours.remainder(24); - final minutes = duration.inMinutes.remainder(60); - final seconds = duration.inSeconds.remainder(60); - - final List parts = []; - if (days > 0) parts.add("${days}d"); - if (hours > 0) parts.add("${hours}h"); - if (minutes > 0) parts.add("${minutes}m"); - if (seconds > 0 && minutes <= 0) parts.add("${seconds}s"); - if (parts.isEmpty) return null; - - return parts.join(" "); - } - - Duration? get _remainingTime { - if (widget.deadline == null) { - return null; - } - - final now = DateTime.now(); - return widget.deadline!.difference(now); - } - - @override - Widget build(BuildContext context) { - final remainingTime = _remainingTime; - final durationString = _formatDuration(remainingTime ?? Duration.zero); - - return ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 250.0, - ), - child: Row( - spacing: 4.0, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.timer_outlined, - size: widget.iconSize ?? 28.0, - ), - Flexible( - child: Text( - remainingTime != null && - remainingTime >= Duration.zero && - durationString != null - ? durationString - : L10n.of(context).duration, - style: TextStyle(fontSize: widget.textSize ?? 20), - ), - ), - ], - ), - ); - } -} diff --git a/lib/pangea/activities/pinned_activity_message.dart b/lib/pangea/activities/pinned_activity_message.dart deleted file mode 100644 index 8be5a492f..000000000 --- a/lib/pangea/activities/pinned_activity_message.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:collection/collection.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/pages/chat/chat.dart'; -import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart'; -import 'package:fluffychat/pangea/activities/activity_aware_builder.dart'; -import 'package:fluffychat/pangea/activities/activity_duration_popup.dart'; -import 'package:fluffychat/pangea/activities/countdown.dart'; -import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; -import 'package:fluffychat/widgets/future_loading_dialog.dart'; - -class PinnedActivityMessage extends StatelessWidget { - final ChatController controller; - - const PinnedActivityMessage(this.controller, {super.key}); - - Future _scrollToEvent() async { - final eventId = _activityPlanEvent?.eventId; - if (eventId != null) controller.scrollToEventId(eventId); - } - - Event? get _activityPlanEvent => controller.timeline?.events.firstWhereOrNull( - (event) => event.type == PangeaEventTypes.activityPlan, - ); - - ActivityPlanModel? get _activityPlan => controller.room.activityPlan; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - - return ActivityAwareBuilder( - deadline: _activityPlan?.endAt, - builder: (isActive) { - if (!isActive || _activityPlan == null) { - return const SizedBox.shrink(); - } - - return ChatAppBarListTile( - title: _activityPlan!.title, - leading: IconButton( - splashRadius: 18, - iconSize: 18, - color: theme.colorScheme.onSurfaceVariant, - icon: const Icon(Icons.push_pin), - onPressed: () {}, - ), - trailing: Padding( - padding: const EdgeInsets.only(right: 16.0), - child: InkWell( - borderRadius: BorderRadius.circular(20), - onTap: () async { - final Duration? duration = await showDialog( - context: context, - builder: (context) { - return ActivityDurationPopup( - initialValue: - _activityPlan?.duration ?? const Duration(days: 1), - ); - }, - ); - - if (duration == null) return; - - showFutureLoadingDialog( - context: context, - future: () => controller.room.sendActivityPlan( - _activityPlan!.copyWith( - endAt: DateTime.now().add(duration), - duration: duration, - ), - ), - ); - }, - child: Container( - padding: const EdgeInsets.all(4.0), - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(20), - ), - child: CountDown( - deadline: _activityPlan!.endAt, - iconSize: 16.0, - textSize: 14.0, - ), - ), - ), - ), - onTap: _scrollToEvent, - ); - }, - ); - } -} diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index 58616de36..316ef7199 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; import 'package:html_unescape/html_unescape.dart'; +import 'package:http/http.dart' as http; import 'package:matrix/matrix.dart'; import 'package:matrix/src/utils/markdown.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; diff --git a/lib/pangea/extensions/room_events_extension.dart b/lib/pangea/extensions/room_events_extension.dart index e24427ef3..799ebdb66 100644 --- a/lib/pangea/extensions/room_events_extension.dart +++ b/lib/pangea/extensions/room_events_extension.dart @@ -277,6 +277,57 @@ extension EventsRoomExtension on Room { }) async { BookmarkedActivitiesRepo.save(activity); + String? imageURL = activity.imageURL; + final eventId = await pangeaSendTextEvent( + activity.markdown, + messageTag: ModelKey.messageTagActivityPlan, + ); + + Uint8List? bytes = avatar; + if (imageURL != null && bytes == null) { + try { + final resp = await http + .get(Uri.parse(imageURL)) + .timeout(const Duration(seconds: 5)); + bytes = resp.bodyBytes; + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: { + "avatarURL": imageURL, + }, + ); + } + } + + if (bytes != null && imageURL == null) { + final url = await client.uploadContent( + bytes, + filename: filename, + ); + imageURL = url.toString(); + } + + MatrixFile? file; + if (filename != null && bytes != null) { + file = MatrixFile( + bytes: bytes, + name: filename, + ); + } + + if (file != null) { + final content = { + 'msgtype': file.msgType, + 'body': file.name, + 'filename': file.name, + 'url': imageURL, + ModelKey.messageTags: ModelKey.messageTagActivityPlan, + }; + await sendEvent(content); + } + if (canChangeStateEvent(PangeaEventTypes.activityPlan)) { await client.setRoomStateWithKey( id, @@ -284,6 +335,10 @@ extension EventsRoomExtension on Room { "", activity.toJson(), ); + + if (eventId != null && canChangeStateEvent(EventTypes.RoomPinnedEvents)) { + await setPinnedEvents([eventId]); + } } } diff --git a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart index d762a4dcc..a350316b5 100644 --- a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart +++ b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart @@ -1,7 +1,6 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/common/constants/model_keys.dart'; -import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import '../../config/app_config.dart'; extension VisibleInGuiExtension on List { @@ -23,12 +22,7 @@ extension IsStateExtension on Event { // if we enabled to hide all redacted events, don't show those (!AppConfig.hideRedactedEvents || !redacted) && // if we enabled to hide all unknown events, don't show those - // #Pangea - // (!AppConfig.hideUnknownEvents || isEventTypeKnown) && - (!AppConfig.hideUnknownEvents || - isEventTypeKnown || - importantStateEvents.contains(type)) && - // Pangea# + (!AppConfig.hideUnknownEvents || isEventTypeKnown) && // remove state events that we don't want to render (isState || !AppConfig.hideAllStateEvents) && // #Pangea @@ -64,8 +58,6 @@ extension IsStateExtension on Event { EventTypes.RoomMember, EventTypes.RoomTombstone, EventTypes.CallInvite, - PangeaEventTypes.activityPlan, - PangeaEventTypes.activityPlanEnd, }; // Pangea# } From 8bd8e1d9ba6683b749d4b0ec69c0239d66e9327f Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 25 Jun 2025 10:32:10 -0400 Subject: [PATCH 11/39] chore: adjust message offset / size when bubble content changes --- lib/config/app_config.dart | 1 - .../phonetic_transcription_widget.dart | 10 +- .../widgets/message_selection_overlay.dart | 29 ++++- .../widgets/message_selection_positioner.dart | 115 +++++++++++------- .../widgets/overlay_center_content.dart | 4 + .../toolbar/widgets/overlay_message.dart | 54 ++++---- 6 files changed, 135 insertions(+), 78 deletions(-) diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 734869ecc..2106db95a 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -44,7 +44,6 @@ abstract class AppConfig { toolbarButtonsHeight + (chatInputRowOverlayPadding * 2) + toolbarSpacing; - static const double audioTranscriptionMaxHeight = 150.0; static TextStyle messageTextStyle( Event? event, diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index 83aaf2275..ee9f64546 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -22,6 +22,8 @@ class PhoneticTranscriptionWidget extends StatefulWidget { final bool enabled; + final VoidCallback? onTranscriptionFetched; + const PhoneticTranscriptionWidget({ super.key, required this.text, @@ -30,6 +32,7 @@ class PhoneticTranscriptionWidget extends StatefulWidget { this.iconSize, this.iconColor, this.enabled = true, + this.onTranscriptionFetched, }); @override @@ -103,7 +106,12 @@ class _PhoneticTranscriptionWidgetState }, ); } finally { - if (mounted) setState(() => _isLoading = false); + if (mounted) { + setState(() { + _isLoading = false; + widget.onTranscriptionFetched?.call(); + }); + } } } diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index daaecd2ae..14da26467 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -101,6 +101,8 @@ class MessageOverlayController extends State bool showSpeechTranslation = false; String? speechTranslation; + final StreamController contentChangedStream = StreamController.broadcast(); + double maxWidth = AppConfig.toolbarMinWidth; ///////////////////////////////////// @@ -121,6 +123,7 @@ class MessageOverlayController extends State WidgetsBinding.instance.addPostFrameCallback( (_) => widget.chatController.clearSelectedEvents(), ); + contentChangedStream.close(); super.dispose(); } @@ -587,7 +590,10 @@ class MessageOverlayController extends State void setTranslation(String value) { if (mounted) { - setState(() => translation = value); + setState(() { + translation = value; + contentChangedStream.add(true); + }); } } @@ -598,12 +604,18 @@ class MessageOverlayController extends State } if (showTranslation == show) return; - setState(() => showTranslation = show); + setState(() { + showTranslation = show; + contentChangedStream.add(true); + }); } void setSpeechTranslation(String value) { if (mounted) { - setState(() => speechTranslation = value); + setState(() { + speechTranslation = value; + contentChangedStream.add(true); + }); } } @@ -614,7 +626,10 @@ class MessageOverlayController extends State } if (showSpeechTranslation == show) return; - setState(() => showSpeechTranslation = show); + setState(() { + showSpeechTranslation = show; + contentChangedStream.add(true); + }); } void setTranscription(SpeechToTextModel value) { @@ -622,13 +637,17 @@ class MessageOverlayController extends State setState(() { transcriptionError = null; transcription = value; + contentChangedStream.add(true); }); } } void setTranscriptionError(String value) { if (mounted) { - setState(() => transcriptionError = value); + setState(() { + transcriptionError = value; + contentChangedStream.add(true); + }); } } diff --git a/lib/pangea/toolbar/widgets/message_selection_positioner.dart b/lib/pangea/toolbar/widgets/message_selection_positioner.dart index f32f4d3ba..bbfad506d 100644 --- a/lib/pangea/toolbar/widgets/message_selection_positioner.dart +++ b/lib/pangea/toolbar/widgets/message_selection_positioner.dart @@ -71,6 +71,7 @@ class MessageSelectionPositionerState extends State Offset? _currentOffset; StreamSubscription? _reactionSubscription; + StreamSubscription? _contentChangedSubscription; final _animationDuration = const Duration( milliseconds: AppConfig.overlayAnimationDuration, @@ -106,6 +107,10 @@ class MessageSelectionPositionerState extends State }, ).listen((_) => setState(() {})); + _contentChangedSubscription = widget + .overlayController.contentChangedStream.stream + .listen(_onContentSizeChanged); + WidgetsBinding.instance.addPostFrameCallback((_) async { await _centeredMessageCompleter.future; if (!mounted) return; @@ -138,6 +143,7 @@ class MessageSelectionPositionerState extends State void dispose() { _animationController.dispose(); _reactionSubscription?.cancel(); + _contentChangedSubscription?.cancel(); MatrixState.pangeaController.matrixState.audioPlayer ?..stop() ..dispose(); @@ -196,34 +202,9 @@ class MessageSelectionPositionerState extends State } if (mode == ReadingAssistanceMode.selectMode) { - _overlayOffsetAnimation = Tween( - begin: _currentOffset, - end: _adjustedOriginalMessageOffset, - ).animate( - CurvedAnimation( - parent: _animationController, - curve: FluffyThemes.animationCurve, - ), - )..addListener(() { - if (mounted) { - setState(() => _currentOffset = _overlayOffsetAnimation?.value); - } - }); + _resetOffsetAnimation(_adjustedOriginalMessageOffset); } else if (mode == ReadingAssistanceMode.practiceMode) { - _overlayOffsetAnimation = Tween( - begin: _currentOffset, - end: _centeredMessageOffset!, - ).animate( - CurvedAnimation( - parent: _animationController, - curve: FluffyThemes.animationCurve, - ), - )..addListener(() { - if (mounted) { - setState(() => _currentOffset = _overlayOffsetAnimation?.value); - } - }); - + _resetOffsetAnimation(_centeredMessageOffset!); _messageSizeAnimation = Tween( begin: Size( _originalMessageSize.width, @@ -244,6 +225,40 @@ class MessageSelectionPositionerState extends State } } + void _onContentSizeChanged(_) { + Future.delayed(FluffyThemes.animationDuration, () { + final offset = _overlayMessageRenderBox?.localToGlobal(Offset.zero); + if (offset == null || !_overlayMessageRenderBox!.hasSize) { + return null; + } + + final newOffset = _adjustedMessageOffset( + _overlayMessageRenderBox!.size, + offset, + ); + + if (newOffset == _currentOffset) return; + _resetOffsetAnimation(newOffset); + _animationController.forward(from: 0); + }); + } + + void _resetOffsetAnimation(Offset offset) { + _overlayOffsetAnimation = Tween( + begin: _currentOffset, + end: offset, + ).animate( + CurvedAnimation( + parent: _animationController, + curve: FluffyThemes.animationCurve, + ), + )..addListener(() { + if (mounted) { + setState(() => _currentOffset = _overlayOffsetAnimation?.value); + } + }); + } + T _runWithLogging( Function runner, String errorMessage, @@ -326,6 +341,14 @@ class MessageSelectionPositionerState extends State null, ); + RenderBox? get _overlayMessageRenderBox => _runWithLogging( + () => MatrixState.pAnyState.getRenderBox( + 'overlay_message_${widget.event.eventId}', + ), + "Error getting overlay message render box", + null, + ); + Size get _defaultMessageSize => const Size(FluffyThemes.columnWidth / 2, 100); /// The size of the message in the chat list (as opposed to the expanded size in the center overlay) @@ -394,17 +417,28 @@ class MessageSelectionPositionerState extends State } Offset get _adjustedOriginalMessageOffset { + return _adjustedMessageOffset( + _originalMessageSize, + _originalMessageOffset, + ); + } + + Offset _adjustedMessageOffset( + Size messageSize, + Offset messageOffset, + ) { if (_messageRenderBox == null || !_messageRenderBox!.hasSize) { return _defaultMessageOffset; } - final topOffset = _originalMessageOffset.dy; - final bottomOffset = _originalMessageBottomOffset - - _reactionsHeight - - _selectionButtonsHeight; + final topOffset = messageOffset.dy; + final bottomOffset = + (_mediaQuery!.size.height - topOffset - messageSize.height) - + _reactionsHeight - + _selectionButtonsHeight; - final hasHeaderOverflow = topOffset < - (_headerHeight + AppConfig.toolbarSpacing + _audioTranscriptionHeight); + final hasHeaderOverflow = + topOffset < (_headerHeight + AppConfig.toolbarSpacing); final hasFooterOverflow = bottomOffset < (_footerHeight + AppConfig.toolbarSpacing); @@ -416,15 +450,12 @@ class MessageSelectionPositionerState extends State } if (hasHeaderOverflow) { - final difference = topOffset - - (_headerHeight + - AppConfig.toolbarSpacing + - _audioTranscriptionHeight); + final difference = topOffset - (_headerHeight + AppConfig.toolbarSpacing); double newBottomOffset = _mediaQuery!.size.height - - _originalMessageOffset.dy + + topOffset + difference - - _originalMessageSize.height - + messageSize.height - _selectionButtonsHeight; if (newBottomOffset < _footerHeight + AppConfig.toolbarSpacing) { @@ -524,12 +555,6 @@ class MessageSelectionPositionerState extends State return showSelectionButtons ? AppConfig.toolbarButtonsHeight : 0; } - double get _audioTranscriptionHeight { - return widget.pangeaMessageEvent?.isAudioMessage ?? false - ? AppConfig.audioTranscriptionMaxHeight - : 0; - } - bool get _hasReactions { final reactionsEvents = widget.event.aggregatedEvents( widget.chatController.timeline!, diff --git a/lib/pangea/toolbar/widgets/overlay_center_content.dart b/lib/pangea/toolbar/widgets/overlay_center_content.dart index 835177fc5..c4d2810d9 100644 --- a/lib/pangea/toolbar/widgets/overlay_center_content.dart +++ b/lib/pangea/toolbar/widgets/overlay_center_content.dart @@ -9,6 +9,7 @@ import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dar import 'package:fluffychat/pangea/toolbar/widgets/measure_render_box.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/pangea/toolbar/widgets/overlay_message.dart'; +import 'package:fluffychat/widgets/matrix.dart'; class OverlayCenterContent extends StatelessWidget { final Event event; @@ -69,6 +70,9 @@ class OverlayCenterContent extends StatelessWidget { MeasureRenderBox( onChange: onChangeMessageSize, child: OverlayMessage( + key: MatrixState.pAnyState + .layerLinkAndKey('overlay_message_${event.eventId}') + .key, event, pangeaMessageEvent: pangeaMessageEvent, immersionMode: chatController.choreographer.immersionMode, diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index 66173f038..a5dda244c 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -150,8 +150,7 @@ class OverlayMessage extends StatelessWidget { final transcription = showTranscription ? Container( - constraints: BoxConstraints( - maxHeight: maxHeight - (messageHeight ?? 0), + constraints: const BoxConstraints( maxWidth: FluffyThemes.columnWidth * 1.5, ), child: Padding( @@ -210,6 +209,9 @@ class OverlayMessage extends StatelessWidget { iconColor: textColor, enabled: event.senderId != BotName.byEnvironment, + onTranscriptionFetched: () => + overlayController.contentChangedStream + .add(true), ), ], ), @@ -228,9 +230,8 @@ class OverlayMessage extends StatelessWidget { final translation = showTranslation || showSpeechTranslation ? Container( - width: messageWidth, constraints: const BoxConstraints( - maxHeight: AppConfig.audioTranscriptionMaxHeight, + maxWidth: FluffyThemes.columnWidth * 1.5, ), child: Padding( padding: const EdgeInsets.fromLTRB( @@ -273,8 +274,6 @@ class OverlayMessage extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (readingAssistanceMode == ReadingAssistanceMode.transitionMode) - transcription, if (event.relationshipType == RelationshipTypes.reply) FutureBuilder( future: event.getReplyEvent( @@ -373,8 +372,6 @@ class OverlayMessage extends StatelessWidget { ], ), ), - if (readingAssistanceMode == ReadingAssistanceMode.transitionMode) - translation, ], ), ), @@ -388,26 +385,31 @@ class OverlayMessage extends StatelessWidget { color: noBubble ? Colors.transparent : color, borderRadius: borderRadius, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (readingAssistanceMode != ReadingAssistanceMode.transitionMode) + constraints: BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 1.5, + maxHeight: maxHeight, + ), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ transcription, - sizeAnimation != null - ? AnimatedBuilder( - animation: sizeAnimation!, - builder: (context, child) { - return SizedBox( - height: sizeAnimation!.value.height, - width: sizeAnimation!.value.width, - child: content, - ); - }, - ) - : content, - if (readingAssistanceMode != ReadingAssistanceMode.transitionMode) + sizeAnimation != null + ? AnimatedBuilder( + animation: sizeAnimation!, + builder: (context, child) { + return SizedBox( + height: sizeAnimation!.value.height, + width: sizeAnimation!.value.width, + child: content, + ); + }, + ) + : content, translation, - ], + ], + ), ), ), ); From b64c66c74ba1b0dfdf631e9abb29f99b8dfcfe1b Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 25 Jun 2025 10:53:13 -0400 Subject: [PATCH 12/39] Use seed to make random avatar consistent --- .../extensions/pangea_rooms_chunk_extension.dart | 16 ++++++++++++++++ .../find_your_people/public_space_tile.dart | 9 ++------- .../public_spaces/public_room_bottom_sheet.dart | 9 ++------- lib/pangea/public_spaces/public_space_card.dart | 9 ++------- 4 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 lib/pangea/extensions/pangea_rooms_chunk_extension.dart diff --git a/lib/pangea/extensions/pangea_rooms_chunk_extension.dart b/lib/pangea/extensions/pangea_rooms_chunk_extension.dart new file mode 100644 index 000000000..78785948f --- /dev/null +++ b/lib/pangea/extensions/pangea_rooms_chunk_extension.dart @@ -0,0 +1,16 @@ +import 'dart:math'; + +import 'package:matrix/matrix_api_lite/generated/model.dart'; + +import 'package:fluffychat/pangea/spaces/constants/space_constants.dart'; + +extension PangeaRoomsChunk on PublicRoomsChunk { + /// Use Random with a seed to get the default + /// avatar associated with this space + String defaultAvatar() { + final int seed = roomId.hashCode; + return SpaceConstants.publicSpaceIcons[Random(seed).nextInt( + SpaceConstants.publicSpaceIcons.length, + )]; + } +} diff --git a/lib/pangea/find_your_people/public_space_tile.dart b/lib/pangea/find_your_people/public_space_tile.dart index ef19865a1..5dd19f68f 100644 --- a/lib/pangea/find_your_people/public_space_tile.dart +++ b/lib/pangea/find_your_people/public_space_tile.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; @@ -8,8 +6,8 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicator_button.dart'; +import 'package:fluffychat/pangea/extensions/pangea_rooms_chunk_extension.dart'; import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart'; -import 'package:fluffychat/pangea/spaces/constants/space_constants.dart'; import 'package:fluffychat/widgets/avatar.dart'; class PublicSpaceTile extends StatelessWidget { @@ -53,10 +51,7 @@ class PublicSpaceTile extends StatelessWidget { 10, ), child: CachedNetworkImage( - imageUrl: SpaceConstants - .publicSpaceIcons[Random().nextInt( - SpaceConstants.publicSpaceIcons.length, - )], + imageUrl: space.defaultAvatar(), width: isColumnMode ? 80.0 : 58.0, height: isColumnMode ? 80.0 : 58.0, fit: BoxFit.cover, diff --git a/lib/pangea/public_spaces/public_room_bottom_sheet.dart b/lib/pangea/public_spaces/public_room_bottom_sheet.dart index 89ed09700..a634dc39b 100644 --- a/lib/pangea/public_spaces/public_room_bottom_sheet.dart +++ b/lib/pangea/public_spaces/public_room_bottom_sheet.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; @@ -9,7 +7,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; -import 'package:fluffychat/pangea/spaces/constants/space_constants.dart'; +import 'package:fluffychat/pangea/extensions/pangea_rooms_chunk_extension.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/widgets/avatar.dart'; @@ -212,10 +210,7 @@ class PublicRoomBottomSheetState extends State { : ClipRRect( borderRadius: BorderRadius.circular(24.0), child: CachedNetworkImage( - imageUrl: SpaceConstants - .publicSpaceIcons[Random().nextInt( - SpaceConstants.publicSpaceIcons.length, - )], + imageUrl: chunk!.defaultAvatar(), width: 160.0, height: 160.0, fit: BoxFit.cover, diff --git a/lib/pangea/public_spaces/public_space_card.dart b/lib/pangea/public_spaces/public_space_card.dart index 5b8063225..29521f67b 100644 --- a/lib/pangea/public_spaces/public_space_card.dart +++ b/lib/pangea/public_spaces/public_space_card.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; @@ -7,8 +5,8 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/common/widgets/pressable_button.dart'; +import 'package:fluffychat/pangea/extensions/pangea_rooms_chunk_extension.dart'; import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart'; -import 'package:fluffychat/pangea/spaces/constants/space_constants.dart'; import 'package:fluffychat/widgets/mxc_image.dart'; class PublicSpaceCard extends StatelessWidget { @@ -71,10 +69,7 @@ class PublicSpaceCard extends StatelessWidget { fit: BoxFit.cover, ) : CachedNetworkImage( - imageUrl: SpaceConstants - .publicSpaceIcons[Random().nextInt( - SpaceConstants.publicSpaceIcons.length, - )], + imageUrl: space.defaultAvatar(), width: width, height: width, fit: BoxFit.cover, From 13adb07935737485800bd20048df018bfd6ea2df Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 25 Jun 2025 10:56:31 -0400 Subject: [PATCH 13/39] chore: exclude punctuation tokens in HTML message rendering --- lib/pages/chat/events/html_message.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 877ddf428..d322ffcae 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -155,7 +155,9 @@ class HtmlMessage extends StatelessWidget { // #Pangea List? get tokens => - pangeaMessageEvent?.messageDisplayRepresentation?.tokens; + pangeaMessageEvent?.messageDisplayRepresentation?.tokens + ?.where((t) => t.pos != "PUNCT") + .toList(); PangeaToken? getToken( String text, From 1d20ba02dbc255cc59abe131f9f2091d3f225516 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 25 Jun 2025 15:05:38 -0400 Subject: [PATCH 14/39] chore: don't expand delete space dialog if empty --- lib/pangea/chat_settings/widgets/delete_space_dialog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/chat_settings/widgets/delete_space_dialog.dart b/lib/pangea/chat_settings/widgets/delete_space_dialog.dart index 6afb8ab26..4adce8e62 100644 --- a/lib/pangea/chat_settings/widgets/delete_space_dialog.dart +++ b/lib/pangea/chat_settings/widgets/delete_space_dialog.dart @@ -146,7 +146,7 @@ class DeleteSpaceDialogState extends State { style: TextStyle(color: Theme.of(context).colorScheme.error), ), ), - Expanded( + Flexible( child: SingleChildScrollView( child: Builder( builder: (context) { From bb99cd9d6124681abcdba71889ee0df7516be2c1 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 25 Jun 2025 15:26:00 -0400 Subject: [PATCH 15/39] chore: fix duplicate key error --- lib/pangea/toolbar/widgets/overlay_center_content.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pangea/toolbar/widgets/overlay_center_content.dart b/lib/pangea/toolbar/widgets/overlay_center_content.dart index c4d2810d9..6b5396b65 100644 --- a/lib/pangea/toolbar/widgets/overlay_center_content.dart +++ b/lib/pangea/toolbar/widgets/overlay_center_content.dart @@ -70,9 +70,11 @@ class OverlayCenterContent extends StatelessWidget { MeasureRenderBox( onChange: onChangeMessageSize, child: OverlayMessage( - key: MatrixState.pAnyState - .layerLinkAndKey('overlay_message_${event.eventId}') - .key, + key: isTransitionAnimation + ? MatrixState.pAnyState + .layerLinkAndKey('overlay_message_${event.eventId}') + .key + : null, event, pangeaMessageEvent: pangeaMessageEvent, immersionMode: chatController.choreographer.immersionMode, From f1387eb662a543dbf85566f2a4a0d74a73ae59f6 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 25 Jun 2025 16:01:17 -0400 Subject: [PATCH 16/39] chore: remove wait for sync when updating learning settings --- lib/pangea/learning_settings/pages/settings_learning.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pangea/learning_settings/pages/settings_learning.dart b/lib/pangea/learning_settings/pages/settings_learning.dart index 76e4fd764..340b79597 100644 --- a/lib/pangea/learning_settings/pages/settings_learning.dart +++ b/lib/pangea/learning_settings/pages/settings_learning.dart @@ -127,7 +127,6 @@ class SettingsLearningController extends State { context: context, future: () async => pangeaController.userController.updateProfile( (_) => _profile, - waitForDataInSync: true, ), ); Navigator.of(context).pop(); From 0f2a02447f57617dfd6067e0d1f8a8278240baa0 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 25 Jun 2025 16:42:05 -0400 Subject: [PATCH 17/39] chore: if user is in trial window, always treat them as subscribed --- .../subscription/controllers/subscription_controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/subscription/controllers/subscription_controller.dart b/lib/pangea/subscription/controllers/subscription_controller.dart index b3fb0e991..91027f88c 100644 --- a/lib/pangea/subscription/controllers/subscription_controller.dart +++ b/lib/pangea/subscription/controllers/subscription_controller.dart @@ -58,7 +58,7 @@ class SubscriptionController extends BaseController { final bool hasSubscription = currentSubscriptionInfo?.currentSubscriptionId != null; - return hasSubscription; + return hasSubscription || _userController.inTrialWindow(); } bool _isInitializing = false; From c545ddf02c10d14579157a284977e0b3ddf84a44 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 25 Jun 2025 17:30:06 -0400 Subject: [PATCH 18/39] chore: on click space in nav rail, if invited, show invite popup --- lib/pages/chat_list/chat_list.dart | 1 - .../utils/chat_list_handle_space_tap.dart | 20 +++++++------------ lib/widgets/navigation_rail.dart | 16 ++++++++++++++- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index eccc19960..19006d83b 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -587,7 +587,6 @@ class ChatListController extends State if (space != null) { chatListHandleSpaceTap( context, - this, space, ); } diff --git a/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart b/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart index 4950c40c3..549d39440 100644 --- a/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart +++ b/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart @@ -3,9 +3,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; @@ -23,7 +21,7 @@ Future _showInviteDialog(Room room, BuildContext context) async { cancelLabel: L10n.of(context).decline, ); - await showFutureLoadingDialog( + final resp = await showFutureLoadingDialog( context: context, future: () async { if (acceptInvite == OkCancelResult.ok) { @@ -31,28 +29,24 @@ Future _showInviteDialog(Room room, BuildContext context) async { context.go( room.isSpace ? "/rooms?spaceId=${room.id}" : "/rooms/${room.id}", ); - return; + return room.id; } await room.leave(); }, ); + + if (!resp.isError && resp.result is String) { + context.go("/rooms?spaceId=${resp.result}"); + } } // ignore: curly_braces_in_flow_control_structures void chatListHandleSpaceTap( BuildContext context, - ChatListController controller, Room space, ) { void setActiveSpaceAndCloseChat() { - controller.setActiveSpace(space.id); - - if (FluffyThemes.isColumnMode(context)) { - context.go('/rooms/${space.id}'); - } else if (controller.activeChat != null && - !space.isFirstOrSecondChild(controller.activeChat!)) { - context.go("/rooms"); - } + context.go("/rooms?spaceId=${space.id}"); } void autoJoin(Room space) { diff --git a/lib/widgets/navigation_rail.dart b/lib/widgets/navigation_rail.dart index ab0983345..ac14abdb9 100644 --- a/lib/widgets/navigation_rail.dart +++ b/lib/widgets/navigation_rail.dart @@ -7,6 +7,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat_list/navi_rail_item.dart'; +import 'package:fluffychat/pangea/chat_list/utils/chat_list_handle_space_tap.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; @@ -175,7 +176,20 @@ class SpacesNavigationRail extends StatelessWidget { return NaviRailItem( toolTip: displayname, isSelected: activeSpaceId == space.id, - onTap: () => onGoToSpaceId(rootSpaces[i].id), + // #Pangea + // onTap: () => onGoToSpaceId(rootSpaces[i].id), + onTap: () { + final room = client.getRoomById(rootSpaces[i].id); + if (room != null) { + chatListHandleSpaceTap( + context, + room, + ); + } else { + onGoToSpaceId(rootSpaces[i].id); + } + }, + // Pangea# unreadBadgeFilter: (room) => spaceChildrenIds.contains(room.id), icon: Avatar( From 39b9aac80ea6b7ff8386a3ed7146ab5d6c582235 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 26 Jun 2025 09:29:00 -0400 Subject: [PATCH 19/39] chore: replace call to startRoom with call to startDirectChat for bot chat --- lib/pangea/onboarding/onboarding.dart | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/pangea/onboarding/onboarding.dart b/lib/pangea/onboarding/onboarding.dart index bf2f0979c..53d45bd87 100644 --- a/lib/pangea/onboarding/onboarding.dart +++ b/lib/pangea/onboarding/onboarding.dart @@ -80,17 +80,16 @@ class OnboardingController extends State { Future startChatWithBot() async { final resp = await showFutureLoadingDialog( context: context, - future: () => Matrix.of(context).client.createRoom( - invite: [BotName.byEnvironment], - isDirect: true, - preset: CreateRoomPreset.trustedPrivateChat, - initialState: [ - BotOptionsModel(mode: BotMode.directChat).toStateEvent, - RoomDefaults.defaultPowerLevels( - Matrix.of(context).client.userID!, - ), - ], + future: () => Matrix.of(context).client.startDirectChat( + BotName.byEnvironment, + preset: CreateRoomPreset.trustedPrivateChat, + initialState: [ + BotOptionsModel(mode: BotMode.directChat).toStateEvent, + RoomDefaults.defaultPowerLevels( + Matrix.of(context).client.userID!, ), + ], + ), ); if (resp.isError) return; context.go("/rooms/${resp.result}"); From f3281d87d2b9a341a2977c48e077ce90605a6688 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 26 Jun 2025 11:25:38 -0400 Subject: [PATCH 20/39] chore: if originalSent doesn't have tokens, don't include it in representations list because related token events cannot be sent to it --- .../event_wrappers/pangea_message_event.dart | 10 ++++++- .../widgets/message_selection_overlay.dart | 26 ++++--------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/lib/pangea/events/event_wrappers/pangea_message_event.dart b/lib/pangea/events/event_wrappers/pangea_message_event.dart index 0f01f2655..d0fff2281 100644 --- a/lib/pangea/events/event_wrappers/pangea_message_event.dart +++ b/lib/pangea/events/event_wrappers/pangea_message_event.dart @@ -396,7 +396,15 @@ class PangeaMessageEvent { ), ); } - _representations!.add(sent); + + // If originalSent has no tokens, there is not way to generate a tokens event + // and send it as a related event, since original sent has not eventID to set + // as parentEventId. In this case, it's better to generate a new representation + // with an eventID and send the related tokens event to that representation. + // This is a rare situation, and has only been seen with some bot messages. + if (sent.tokens != null) { + _representations!.add(sent); + } } catch (err, s) { ErrorHandler.logError( m: "error parsing originalSent", diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index 14da26467..d95fee56a 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -18,8 +18,6 @@ import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dar import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; -import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart'; @@ -135,7 +133,11 @@ class MessageOverlayController extends State RepresentationEvent? repEvent = pangeaMessageEvent?.messageDisplayRepresentation; - repEvent ??= await _fetchNewRepEvent(); + + if (repEvent == null || + (repEvent.event == null && repEvent.tokens == null)) { + repEvent = await _fetchNewRepEvent(); + } if (repEvent?.event != null) { await repEvent!.sendTokensEvent( @@ -145,24 +147,6 @@ class MessageOverlayController extends State MatrixState.pangeaController.languageController.userL2!.langCode, ); } - // If repEvent is originalSent but it's missing tokens, then fetch tokens. - // An edge case, but has happened with some bot message. - else if (repEvent != null && - repEvent.tokens == null && - repEvent.content.originalSent) { - final tokens = await repEvent.tokensGlobal( - pangeaMessageEvent!.senderId, - pangeaMessageEvent!.event.originServerTs, - ); - await pangeaMessageEvent!.room.pangeaSendTextEvent( - pangeaMessageEvent!.messageDisplayText, - editEventId: pangeaMessageEvent!.eventId, - originalSent: pangeaMessageEvent!.originalSent?.content, - originalWritten: pangeaMessageEvent!.originalWritten?.content, - tokensSent: PangeaMessageTokens(tokens: tokens), - choreo: pangeaMessageEvent!.originalSent?.choreo, - ); - } // Get all the lemma infos final messageVocabConstructIds = pangeaMessageEvent! From e2cd920668d72e40e18042ea7da5d180fdba36b7 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 26 Jun 2025 11:50:08 -0400 Subject: [PATCH 21/39] chore: fix audio player ID discrepancy --- lib/pangea/toolbar/widgets/select_mode_buttons.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index 43759a1a1..238fec5f9 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -240,7 +240,7 @@ class SelectModeButtonsState extends State { matrix?.audioPlayer?.dispose(); matrix?.audioPlayer = AudioPlayer(); matrix?.voiceMessageEventId.value = - widget.overlayController.pangeaMessageEvent?.eventId; + "${widget.overlayController.pangeaMessageEvent?.eventId}_button"; _onPlayerStateChanged = matrix?.audioPlayer?.playerStateStream.listen((state) { From 51f5515fc94ed633ceedc5ff407d930b27b2ff77 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 26 Jun 2025 12:01:40 -0400 Subject: [PATCH 22/39] chore: reduce padding around avatar in settings view --- lib/pages/settings/settings_view.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index 81f488f2e..c273109c9 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -87,7 +87,14 @@ class SettingsView extends StatelessWidget { return Row( children: [ Padding( - padding: const EdgeInsets.all(32.0), + // #Pangea + // padding: const EdgeInsets.all(32.0), + padding: const EdgeInsets.only( + top: 32.0, + bottom: 32.0, + left: 12.0, + ), + // Pangea# child: Stack( children: [ Avatar( From adde62896bf885fd51ab1b82a09b87f291f167f9 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 26 Jun 2025 12:33:30 -0400 Subject: [PATCH 23/39] chore: show invite dialog on chat list init --- lib/pages/chat_list/chat_list.dart | 15 +++++++++++++++ .../utils/chat_list_handle_space_tap.dart | 5 +++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 19006d83b..091c6e8b0 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -46,6 +46,7 @@ import '../../widgets/matrix.dart'; import 'package:fluffychat/utils/tor_stub.dart' if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart'; + enum PopupMenuAction { settings, invite, @@ -668,6 +669,10 @@ class ChatListController extends State _activeSpaceId = widget.activeSpaceId == 'clear' ? null : widget.activeSpaceId; + + WidgetsBinding.instance.addPostFrameCallback((_) { + _joinInvitedSpaces(); + }); // Pangea# super.initState(); @@ -684,6 +689,16 @@ class ChatListController extends State : setActiveSpace(widget.activeSpaceId!); } } + + Future _joinInvitedSpaces() async { + final invitedSpaces = Matrix.of(context).client.rooms.where( + (r) => r.isSpace && r.membership == Membership.invite, + ); + + for (final space in invitedSpaces) { + await showInviteDialog(space, context); + } + } // Pangea# @override diff --git a/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart b/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart index 549d39440..428567a27 100644 --- a/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart +++ b/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart @@ -10,7 +10,8 @@ import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; import '../../common/utils/error_handler.dart'; -Future _showInviteDialog(Room room, BuildContext context) async { +Future showInviteDialog(Room room, BuildContext context) async { + if (room.membership != Membership.invite) return; final acceptInvite = await showOkCancelAlertDialog( context: context, title: L10n.of(context).youreInvited, @@ -79,7 +80,7 @@ void chatListHandleSpaceTap( justInputtedCode == space.classCode) { // do nothing } else { - _showInviteDialog(space, context); + showInviteDialog(space, context); } break; case Membership.leave: From 29c61b2b2d951e92e8c507640764ee60123ae054 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 26 Jun 2025 13:25:14 -0400 Subject: [PATCH 24/39] chore: if user closes invite dialog without declining, don't leave invited space --- lib/pages/chat_list/chat_list.dart | 1 - lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 091c6e8b0..519ff9218 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -46,7 +46,6 @@ import '../../widgets/matrix.dart'; import 'package:fluffychat/utils/tor_stub.dart' if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart'; - enum PopupMenuAction { settings, invite, diff --git a/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart b/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart index 428567a27..72839b716 100644 --- a/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart +++ b/lib/pangea/chat_list/utils/chat_list_handle_space_tap.dart @@ -31,8 +31,9 @@ Future showInviteDialog(Room room, BuildContext context) async { room.isSpace ? "/rooms?spaceId=${room.id}" : "/rooms/${room.id}", ); return room.id; + } else if (acceptInvite == OkCancelResult.cancel) { + await room.leave(); } - await room.leave(); }, ); From 687fd4a03e1dd4d79730ffd15ecd9adaf54a35b8 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 26 Jun 2025 13:48:04 -0400 Subject: [PATCH 25/39] chore: fix overflow in overlay for other user's audio messages on small screens --- .../toolbar/widgets/overlay_message.dart | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index a5dda244c..fc0543395 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -20,6 +22,7 @@ import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart import 'package:fluffychat/pangea/toolbar/widgets/stt_transcript_tokens.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/file_description.dart'; +import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; // @ggurdin be great to explain the need/function of a widget like this @@ -150,8 +153,13 @@ class OverlayMessage extends StatelessWidget { final transcription = showTranscription ? Container( - constraints: const BoxConstraints( - maxWidth: FluffyThemes.columnWidth * 1.5, + constraints: BoxConstraints( + maxWidth: min( + FluffyThemes.columnWidth * 1.5, + MediaQuery.of(context).size.width - + (ownMessage ? 0 : Avatar.defaultSize) - + 24.0, + ), ), child: Padding( padding: const EdgeInsets.all(12.0), @@ -230,8 +238,13 @@ class OverlayMessage extends StatelessWidget { final translation = showTranslation || showSpeechTranslation ? Container( - constraints: const BoxConstraints( - maxWidth: FluffyThemes.columnWidth * 1.5, + constraints: BoxConstraints( + maxWidth: min( + FluffyThemes.columnWidth * 1.5, + MediaQuery.of(context).size.width - + (ownMessage ? 0 : Avatar.defaultSize) - + 24.0, + ), ), child: Padding( padding: const EdgeInsets.fromLTRB( From 3586fb75fd4f6bc85df5d0a7ecd511374721e7c2 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 26 Jun 2025 15:51:13 -0400 Subject: [PATCH 26/39] If there are no activities after loading finishes, display error message --- .../activity_suggestions/activity_suggestions_area.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/pangea/activity_suggestions/activity_suggestions_area.dart b/lib/pangea/activity_suggestions/activity_suggestions_area.dart index b4ce264a0..82d0ca74b 100644 --- a/lib/pangea/activity_suggestions/activity_suggestions_area.dart +++ b/lib/pangea/activity_suggestions/activity_suggestions_area.dart @@ -221,7 +221,7 @@ class ActivitySuggestionsAreaState extends State { ), AnimatedSize( duration: FluffyThemes.animationDuration, - child: _timeout + child: (_timeout || !_loading && cards.isEmpty) ? Padding( padding: const EdgeInsets.all(8.0), child: RichText( @@ -236,8 +236,10 @@ class ActivitySuggestionsAreaState extends State { ), const TextSpan(text: " "), TextSpan( - text: - L10n.of(context).activitySuggestionTimeoutMessage, + text: _timeout + ? L10n.of(context) + .activitySuggestionTimeoutMessage + : L10n.of(context).oopsSomethingWentWrong, ), ], ), From 32b5de675c40d851b559010c61a5f78b632f2924 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 27 Jun 2025 08:48:02 -0400 Subject: [PATCH 27/39] chore: set fetch audio error --- lib/pangea/toolbar/widgets/select_mode_buttons.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index 238fec5f9..f2f561361 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:developer'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -202,12 +201,10 @@ class SelectModeButtonsState extends State { File? file; file = File('${tempDir.path}/${_audioBytes!.name}'); await file.writeAsBytes(_audioBytes!.bytes); - setState(() => _audioFile = file); + _audioFile = file; } - - if (mounted) setState(() => _isLoadingAudio = false); } catch (e, s) { - debugger(when: kDebugMode); + _audioError = e.toString(); ErrorHandler.logError( e: e, s: s, @@ -217,6 +214,7 @@ class SelectModeButtonsState extends State { messageEvent?.messageDisplayLangCode, }, ); + } finally { if (mounted) setState(() => _isLoadingAudio = false); } } @@ -289,7 +287,7 @@ class SelectModeButtonsState extends State { } TtsController.stop(); - matrix?.audioPlayer?.play(); + await matrix?.audioPlayer?.play(); } catch (e, s) { setState(() => _audioError = e.toString()); ErrorHandler.logError( From e6d2a6834871ddf7caa6cda223301a39498a4160 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 27 Jun 2025 10:40:39 -0400 Subject: [PATCH 28/39] chore: don't interupt message audio with button click sound --- lib/pages/chat/events/audio_player.dart | 43 +++++++++++-------- .../toolbar/widgets/overlay_message.dart | 4 +- .../toolbar/widgets/select_mode_buttons.dart | 2 +- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index 6728e69c0..ebecd12c0 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -209,28 +209,37 @@ class AudioPlayerState extends State { // #Pangea // if (!kIsWeb) { - if (!kIsWeb && matrixFile != null) { - // Pangea# - final tempDir = await getTemporaryDirectory(); - final fileName = Uri.encodeComponent( - // #Pangea - // widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last, - widget.event!.attachmentOrThumbnailMxcUrl()!.pathSegments.last, + if (!kIsWeb) { + if (matrixFile != null) { // Pangea# - ); - file = File('${tempDir.path}/${fileName}_${matrixFile.name}'); + final tempDir = await getTemporaryDirectory(); + final fileName = Uri.encodeComponent( + // #Pangea + // widget.event.attachmentOrThumbnailMxcUrl()!.pathSegments.last, + widget.event!.attachmentOrThumbnailMxcUrl()!.pathSegments.last, + // Pangea# + ); + file = File('${tempDir.path}/${fileName}_${matrixFile.name}'); - await file.writeAsBytes(matrixFile.bytes); + await file.writeAsBytes(matrixFile.bytes); - if (Platform.isIOS && - matrixFile.mimeType.toLowerCase() == 'audio/ogg') { - Logs().v('Convert ogg audio file for iOS...'); - final convertedFile = File('${file.path}.caf'); - if (await convertedFile.exists() == false) { - OpusCaf().convertOpusToCaf(file.path, convertedFile.path); + if (Platform.isIOS && + matrixFile.mimeType.toLowerCase() == 'audio/ogg') { + Logs().v('Convert ogg audio file for iOS...'); + final convertedFile = File('${file.path}.caf'); + if (await convertedFile.exists() == false) { + OpusCaf().convertOpusToCaf(file.path, convertedFile.path); + } + file = convertedFile; } - file = convertedFile; + // #Pangea + } else if (widget.matrixFile != null) { + final tempDir = await getTemporaryDirectory(); + + file = File('${tempDir.path}/${widget.matrixFile!.name}'); + await file.writeAsBytes(widget.matrixFile!.bytes); } + // Pangea# } setState(() { diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index fc0543395..e678ac1ad 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -206,8 +206,8 @@ class OverlayMessage extends StatelessWidget { text: overlayController .transcription!.transcript.text, textLanguage: PLanguageStore.byLangCode( - pangeaMessageEvent! - .messageDisplayLangCode, + overlayController + .transcription!.langCode, ) ?? LanguageModel.unknown, style: AppConfig.messageTextStyle( diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index f2f561361..97e334bfb 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -492,7 +492,7 @@ class SelectModeButtonsState extends State { borderRadius: BorderRadius.circular(20), color: Theme.of(context).colorScheme.primaryContainer, onPressed: () => _updateMode(mode), - playSound: true, + playSound: mode != SelectMode.audio, colorFactor: Theme.of(context).brightness == Brightness.light ? 0.55 : 0.3, From 15ae6844ca02e4923c5773eb8f9abc92f441ff81 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 27 Jun 2025 12:03:52 -0400 Subject: [PATCH 29/39] Simplify audio button error behavior, remove audio error reset on clear, remove tooltip on error --- lib/pangea/toolbar/widgets/select_mode_buttons.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index f2f561361..fcc43c81a 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -125,7 +125,8 @@ class SelectModeButtonsState extends State { void _clear() { setState(() { - _audioError = null; + // Audio errors do not go away when I switch modes and back + // Is there any reason to wipe error records on clear? _translationError = null; _speechTranslationError = null; }); @@ -148,8 +149,10 @@ class SelectModeButtonsState extends State { } setState( - () => _selectedMode = - _selectedMode == mode && mode != SelectMode.audio ? null : mode, + () => _selectedMode = _selectedMode == mode && + (mode != SelectMode.audio || _audioError != null) + ? null + : mode, ); if (_selectedMode == SelectMode.audio) { @@ -486,7 +489,7 @@ class SelectModeButtonsState extends State { children: [ for (final mode in modes) Tooltip( - message: mode.tooltip(context), + message: _isError ? null : mode.tooltip(context), child: PressableButton( depressed: mode == _selectedMode, borderRadius: BorderRadius.circular(20), From 9a0f5682ef52306a1e420fe562d1c9c4bdb0681e Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 27 Jun 2025 12:19:47 -0400 Subject: [PATCH 30/39] chore: send all message / constructs from the level to request for construct summary, show loading and error messages when needed --- .../get_analytics_controller.dart | 52 ++- lib/pangea/analytics_misc/level_up.dart | 296 ++++++++++-------- 2 files changed, 203 insertions(+), 145 deletions(-) diff --git a/lib/pangea/analytics_misc/get_analytics_controller.dart b/lib/pangea/analytics_misc/get_analytics_controller.dart index dcdcafd65..5f8997a7d 100644 --- a/lib/pangea/analytics_misc/get_analytics_controller.dart +++ b/lib/pangea/analytics_misc/get_analytics_controller.dart @@ -477,8 +477,8 @@ class GetAnalyticsController extends BaseController { // generate level up analytics as a construct summary ConstructSummary summary; try { - final int maxXP = constructListModel.calculateXpWithLevel(upperLevel); - final int minXP = constructListModel.calculateXpWithLevel(lowerLevel); + final int minXP = constructListModel.calculateXpWithLevel(upperLevel); + final int maxXP = constructListModel.calculateXpWithLevel(lowerLevel); int diffXP = maxXP - minXP; if (diffXP < 0) diffXP = 0; @@ -492,23 +492,41 @@ class GetAnalyticsController extends BaseController { } // extract construct use message bodies for analytics - List? constructUseMessageContentBodies = []; + final Map> useEventIds = {}; for (final use in constructUseOfCurrentLevel) { - try { - final useMessage = await use.getEvent(_client); - final useMessageBody = useMessage?.content["body"]; - if (useMessageBody is String) { - constructUseMessageContentBodies.add(useMessageBody); - } else { - constructUseMessageContentBodies.add(null); - } - } catch (e) { - constructUseMessageContentBodies.add(null); - } + if (use.metadata.roomId == null) continue; + if (use.metadata.eventId == null) continue; + useEventIds[use.metadata.roomId!] ??= {}; + useEventIds[use.metadata.roomId!]!.add(use.metadata.eventId!); } - if (constructUseMessageContentBodies.length != - constructUseOfCurrentLevel.length) { - constructUseMessageContentBodies = null; + + final List constructUseMessageContentBodies = []; + for (final entry in useEventIds.entries) { + final String roomId = entry.key; + final room = _client.getRoomById(roomId); + if (room == null) continue; + final List messageBodies = []; + for (final eventId in entry.value) { + try { + final Event? event = await room.getEventById(eventId); + if (event?.content["body"] is! String) continue; + final String body = event?.content["body"] as String; + if (body.isEmpty) continue; + messageBodies.add(body); + } catch (e, s) { + debugPrint("Error getting event by ID: $e"); + ErrorHandler.logError( + e: e, + s: s, + data: { + 'roomId': roomId, + 'eventId': eventId, + }, + ); + continue; + } + } + constructUseMessageContentBodies.addAll(messageBodies); } final request = ConstructSummaryRequest( diff --git a/lib/pangea/analytics_misc/level_up.dart b/lib/pangea/analytics_misc/level_up.dart index ab57cd03d..6e348aa13 100644 --- a/lib/pangea/analytics_misc/level_up.dart +++ b/lib/pangea/analytics_misc/level_up.dart @@ -91,6 +91,7 @@ class LevelUpBannerState extends State ConstructSummary? _constructSummary; String? _error; + bool _loading = true; @override void initState() { @@ -143,6 +144,7 @@ class LevelUpBannerState extends State Future _setConstructSummary() async { try { + setState(() => _loading = true); _constructSummary = await MatrixState.pangeaController.getAnalytics .generateLevelUpAnalytics( widget.level, @@ -150,6 +152,10 @@ class LevelUpBannerState extends State ); } catch (e) { _error = e.toString(); + } finally { + if (mounted) { + setState(() => _loading = false); + } } } @@ -364,144 +370,178 @@ class LevelUpBannerState extends State borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.all(16), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - spacing: 24.0, - children: [ - Table( - columnWidths: const { - 0: IntrinsicColumnWidth(), - 1: FlexColumnWidth(), - 2: IntrinsicColumnWidth(), - }, - defaultVerticalAlignment: - TableCellVerticalAlignment.middle, - children: [ - ...LearningSkillsEnum.values - .where( - (v) => - v.isVisible && _skillsPoints(v) > -1, + child: _loading + ? const Center( + child: CircularProgressIndicator.adaptive(), + ) + : _error != null + ? Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.error, + color: Theme.of(context) + .colorScheme + .error, + ), + const SizedBox(width: 8.0), + Text( + L10n.of(context) + .oopsSomethingWentWrong, + ), + ], ) - .map((skill) { - return TableRow( + : SingleChildScrollView( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.center, + spacing: 24.0, children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 9.0, - horizontal: 18.0, - ), - child: Icon( - skill.icon, - size: 25, - color: Colors.white, - ), + Table( + columnWidths: const { + 0: IntrinsicColumnWidth(), + 1: FlexColumnWidth(), + 2: IntrinsicColumnWidth(), + }, + defaultVerticalAlignment: + TableCellVerticalAlignment + .middle, + children: [ + ...LearningSkillsEnum.values + .where( + (v) => + v.isVisible && + _skillsPoints(v) > -1, + ) + .map((skill) { + return TableRow( + children: [ + Padding( + padding: const EdgeInsets + .symmetric( + vertical: 9.0, + horizontal: 18.0, + ), + child: Icon( + skill.icon, + size: 25, + color: Colors.white, + ), + ), + Padding( + padding: const EdgeInsets + .symmetric( + vertical: 9.0, + horizontal: 18.0, + ), + child: Text( + skill.tooltip(context), + style: const TextStyle( + fontSize: 16, + fontWeight: + FontWeight.w600, + color: Colors.white, + ), + textAlign: + TextAlign.center, + ), + ), + Padding( + padding: const EdgeInsets + .symmetric( + vertical: 9.0, + horizontal: 18.0, + ), + child: Text( + "+ ${_skillsPoints(skill)} XP", + style: const TextStyle( + fontSize: 16, + fontWeight: + FontWeight.w600, + color: Colors.white, + ), + textAlign: + TextAlign.center, + ), + ), + ], + ); + }), + ], ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 9.0, - horizontal: 18.0, - ), - child: Text( - skill.tooltip(context), - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, + CachedNetworkImage( + imageUrl: + "${AppConfig.assetsBaseURL}/${LevelUpConstants.dinoLevelUPFileName}", + width: 400, + fit: BoxFit.cover, + ), + if (_constructSummary?.textSummary != + null) + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .secondaryContainer, + borderRadius: + BorderRadius.circular(8), ), - textAlign: TextAlign.center, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 9.0, - horizontal: 18.0, - ), - child: Text( - "+ ${_skillsPoints(skill)} XP", - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, + child: Text( + _constructSummary!.textSummary, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context) + .colorScheme + .onSecondaryContainer, + ), + textAlign: TextAlign.center, ), - textAlign: TextAlign.center, ), + const SizedBox( + height: 24, ), + // Share button, currently no functionality + // ElevatedButton( + // onPressed: () { + // // Add share functionality + // }, + // style: ElevatedButton.styleFrom( + // backgroundColor: Colors.white, + // foregroundColor: Colors.black, + // padding: const EdgeInsets.symmetric( + // vertical: 12, + // horizontal: 24, + // ), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(8), + // ), + // ), + // child: const Row( + // mainAxisSize: MainAxisSize + // .min, + // children: [ + // Text( + // "Share with Friends", + // style: TextStyle( + // fontSize: 16, + // fontWeight: FontWeight.bold, + // ), + // ), + // SizedBox( + // width: 8, + // ), + // Icon( + // Icons.ios_share, + // size: 20, + // ), + // ), + // ), + // ), ], - ); - }), - ], - ), - CachedNetworkImage( - imageUrl: - "${AppConfig.assetsBaseURL}/${LevelUpConstants.dinoLevelUPFileName}", - width: 400, - fit: BoxFit.cover, - ), - if (_constructSummary?.textSummary != null) - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .secondaryContainer, - borderRadius: BorderRadius.circular(8), - ), - child: Text( - _constructSummary!.textSummary, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .colorScheme - .onSecondaryContainer, ), - textAlign: TextAlign.center, ), - ), - const SizedBox( - height: 24, - ), - // Share button, currently no functionality - // ElevatedButton( - // onPressed: () { - // // Add share functionality - // }, - // style: ElevatedButton.styleFrom( - // backgroundColor: Colors.white, - // foregroundColor: Colors.black, - // padding: const EdgeInsets.symmetric( - // vertical: 12, - // horizontal: 24, - // ), - // shape: RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(8), - // ), - // ), - // child: const Row( - // mainAxisSize: MainAxisSize - // .min, - // children: [ - // Text( - // "Share with Friends", - // style: TextStyle( - // fontSize: 16, - // fontWeight: FontWeight.bold, - // ), - // ), - // SizedBox( - // width: 8, - // ), - // Icon( - // Icons.ios_share, - // size: 20, - // ), - // ), - // ), - // ), - ], - ), - ), ), ), ], From 678e32a80f2730467071300fee94ee0e2e844799 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 27 Jun 2025 12:32:11 -0400 Subject: [PATCH 31/39] Use tooltipvisibility, don't mess with tooltips for non-selected modes --- .../toolbar/widgets/select_mode_buttons.dart | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index 0025ff800..1a5ff426b 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -488,25 +488,28 @@ class SelectModeButtonsState extends State { spacing: 4.0, children: [ for (final mode in modes) - Tooltip( - message: _isError ? null : mode.tooltip(context), - child: PressableButton( - depressed: mode == _selectedMode, - borderRadius: BorderRadius.circular(20), - color: Theme.of(context).colorScheme.primaryContainer, - onPressed: () => _updateMode(mode), - playSound: mode != SelectMode.audio, - colorFactor: Theme.of(context).brightness == Brightness.light - ? 0.55 - : 0.3, - child: Container( - height: buttonSize, - width: buttonSize, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primaryContainer, - shape: BoxShape.circle, + TooltipVisibility( + visible: (!_isError || mode != _selectedMode), + child: Tooltip( + message: mode.tooltip(context), + child: PressableButton( + depressed: mode == _selectedMode, + borderRadius: BorderRadius.circular(20), + color: Theme.of(context).colorScheme.primaryContainer, + onPressed: () => _updateMode(mode), + playSound: mode != SelectMode.audio, + colorFactor: Theme.of(context).brightness == Brightness.light + ? 0.55 + : 0.3, + child: Container( + height: buttonSize, + width: buttonSize, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + shape: BoxShape.circle, + ), + child: icon(mode), ), - child: icon(mode), ), ), ), From 8714f05aa86888831c20e81531b2d40e0cdeddf2 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 27 Jun 2025 12:38:18 -0400 Subject: [PATCH 32/39] chore: always set playback speech variable when toggling audio speed --- lib/pages/chat/events/audio_player.dart | 27 +++++++++++-------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index ebecd12c0..0831047f4 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -323,22 +323,19 @@ class AudioPlayerState extends State { final audioPlayer = matrix.audioPlayer; // #Pangea // if (audioPlayer == null) return; - if (audioPlayer == null || - matrix.voiceMessageEventId.value != widget.eventId) { - switch (playbackSpeed) { - case 1.0: - setState(() => playbackSpeed = 0.75); - case 0.75: - setState(() => playbackSpeed = 0.5); - case 0.5: - setState(() => playbackSpeed = 1.25); - case 1.25: - setState(() => playbackSpeed = 1.5); - default: - setState(() => playbackSpeed = 1.0); - } - return; + switch (playbackSpeed) { + case 1.0: + setState(() => playbackSpeed = 0.75); + case 0.75: + setState(() => playbackSpeed = 0.5); + case 0.5: + setState(() => playbackSpeed = 1.25); + case 1.25: + setState(() => playbackSpeed = 1.5); + default: + setState(() => playbackSpeed = 1.0); } + if (audioPlayer == null) return; // Pangea# switch (audioPlayer.speed) { // #Pangea From d42347201960db71ac9ad938f4ea5c32b6ab3dbb Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 27 Jun 2025 13:49:45 -0400 Subject: [PATCH 33/39] chore: wait for language change updates in sync --- .../learning_settings/pages/settings_learning.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/pangea/learning_settings/pages/settings_learning.dart b/lib/pangea/learning_settings/pages/settings_learning.dart index 340b79597..304beebe6 100644 --- a/lib/pangea/learning_settings/pages/settings_learning.dart +++ b/lib/pangea/learning_settings/pages/settings_learning.dart @@ -125,9 +125,12 @@ class SettingsLearningController extends State { if (formKey.currentState!.validate()) { await showFutureLoadingDialog( context: context, - future: () async => pangeaController.userController.updateProfile( - (_) => _profile, - ), + future: () async => pangeaController.userController + .updateProfile( + (_) => _profile, + waitForDataInSync: true, + ) + .timeout(const Duration(seconds: 15)), ); Navigator.of(context).pop(); } From 918c8b57148a136a6cda8fd489167fd724446d29 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 27 Jun 2025 16:24:06 -0400 Subject: [PATCH 34/39] chore: fix punctuation font size in centered messages --- lib/pages/chat/events/html_message.dart | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index d322ffcae..af380fc27 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -311,6 +311,8 @@ class HtmlMessage extends StatelessWidget { overlayController: overlayController, isTransitionAnimation: isTransitionAnimation, ); + + final fontSize = renderer.fontSize(context) ?? this.fontSize; // Pangea# switch (node.localName) { @@ -425,10 +427,7 @@ class HtmlMessage extends StatelessWidget { avatar: user.avatarUrl, uri: href, outerContext: context, - // #Pangea - // fontSize: fontSize, - fontSize: renderer.fontSize(context) ?? fontSize, - // Pangea# + fontSize: fontSize, color: linkStyle.color, // #Pangea userId: user.id, @@ -449,10 +448,7 @@ class HtmlMessage extends StatelessWidget { avatar: room?.avatar, uri: href, outerContext: context, - // #Pangea - // fontSize: fontSize, - fontSize: renderer.fontSize(context) ?? fontSize, - // Pangea# + fontSize: fontSize, color: linkStyle.color, ), ); From d209c1c3dbe6e958d4b47e12ab8f029d068cd75d Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 30 Jun 2025 10:44:10 -0400 Subject: [PATCH 35/39] chore: request activity topics/objectives/modes in user l1 --- lib/pangea/activity_generator/activity_generator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/activity_generator/activity_generator.dart b/lib/pangea/activity_generator/activity_generator.dart index 35c517b17..2ed3dcd9b 100644 --- a/lib/pangea/activity_generator/activity_generator.dart +++ b/lib/pangea/activity_generator/activity_generator.dart @@ -73,7 +73,7 @@ class ActivityGeneratorState extends State { ActivitySettingRequestSchema get req => ActivitySettingRequestSchema( langCode: - MatrixState.pangeaController.languageController.userL2?.langCode ?? + MatrixState.pangeaController.languageController.userL1?.langCode ?? LanguageKeys.defaultLanguage, ); From 2efafbe3c19db0a7934050908643f2a7d0e140da Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 30 Jun 2025 14:46:14 -0400 Subject: [PATCH 36/39] chore: add bot speech to text to respresentations list --- .../event_wrappers/pangea_message_event.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/pangea/events/event_wrappers/pangea_message_event.dart b/lib/pangea/events/event_wrappers/pangea_message_event.dart index d0fff2281..f9ef56e2f 100644 --- a/lib/pangea/events/event_wrappers/pangea_message_event.dart +++ b/lib/pangea/events/event_wrappers/pangea_message_event.dart @@ -266,6 +266,21 @@ class PangeaMessageEvent { final botTranscription = SpeechToTextModel.fromJson( Map.from(rawBotTranscription), ); + + _representations?.add( + RepresentationEvent( + timeline: timeline, + parentMessageEvent: _event, + content: PangeaRepresentation( + langCode: botTranscription.langCode, + text: botTranscription.transcript.text, + originalSent: false, + originalWritten: false, + speechToText: botTranscription, + ), + ), + ); + return botTranscription; } From 61d15f1fffd7e07cdb6609576d740b01996643a1 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 30 Jun 2025 15:18:42 -0400 Subject: [PATCH 37/39] chore: simplify space invite page --- .../invitation_selection.dart | 29 -- .../invitation_selection_view.dart | 274 +++++++++--------- .../widgets/space_invite_buttons.dart | 137 --------- 3 files changed, 131 insertions(+), 309 deletions(-) delete mode 100644 lib/pangea/chat_settings/widgets/space_invite_buttons.dart diff --git a/lib/pages/invitation_selection/invitation_selection.dart b/lib/pages/invitation_selection/invitation_selection.dart index 06509fae9..4929e27a9 100644 --- a/lib/pages/invitation_selection/invitation_selection.dart +++ b/lib/pages/invitation_selection/invitation_selection.dart @@ -35,35 +35,6 @@ class InvitationSelectionController extends State { String? get roomId => widget.roomId; // #Pangea - final viewportKey = GlobalKey(); - - final participantListItemHeight = 72.0; - final goToChatButtonHeight = 50.0; - final shareButtonsHeight = 150.0; - final padding = 16.0 * 2; - final fixedParticipantHeight = 72.0; - - double? viewportHeight; - double get availableHeight => - (viewportHeight ?? 0) - - goToChatButtonHeight - - shareButtonsHeight - - padding; - - bool showShareButtons(int numParticipants) => - (fixedParticipantHeight * numParticipants) < availableHeight; - - @override - initState() { - WidgetsBinding.instance.addPostFrameCallback((_) { - final context = viewportKey.currentContext; - if (context == null) return; - final renderBox = context.findRenderObject() as RenderBox; - final size = renderBox.size; - setState(() => viewportHeight = size.height); - }); - super.initState(); - } List? get participants { final room = Matrix.of(context).client.getRoomById(roomId!); diff --git a/lib/pages/invitation_selection/invitation_selection_view.dart b/lib/pages/invitation_selection/invitation_selection_view.dart index 59c6f6ad0..2df3b6a56 100644 --- a/lib/pages/invitation_selection/invitation_selection_view.dart +++ b/lib/pages/invitation_selection/invitation_selection_view.dart @@ -8,12 +8,10 @@ import 'package:matrix/matrix.dart'; import 'package:universal_html/html.dart' as html; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/invitation_selection/invitation_selection.dart'; import 'package:fluffychat/pangea/analytics_misc/level_display_name.dart'; import 'package:fluffychat/pangea/chat_settings/constants/room_settings_constants.dart'; -import 'package:fluffychat/pangea/chat_settings/widgets/space_invite_buttons.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/spaces/constants/space_constants.dart'; @@ -107,165 +105,153 @@ class InvitationSelectionView extends StatelessWidget { // #Pangea withScrolling: false, // Pangea# - child: Stack( - alignment: Alignment.bottomCenter, + child: Column( children: [ Padding( - padding: const EdgeInsets.all(16.0), - child: SizedBox( - width: 450, - child: CachedNetworkImage( - imageUrl: - "${AppConfig.assetsBaseURL}/${RoomSettingsConstants.referFriendAsset}", - errorWidget: (context, url, error) => const SizedBox(), - placeholder: (context, url) => const Center( - child: CircularProgressIndicator.adaptive(), + // #Pangea + // padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.only( + bottom: 16.0, + left: 16.0, + right: 16.0, + ), + // Pangea# + child: TextField( + textInputAction: TextInputAction.search, + decoration: InputDecoration( + filled: true, + fillColor: theme.colorScheme.secondaryContainer, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(99), ), + hintStyle: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontWeight: FontWeight.normal, + ), + // #Pangea + hintText: L10n.of(context).inviteStudentByUserName, + // hintText: L10n.of(context).inviteContactToGroup(groupName), + // Pangea# + prefixIcon: controller.loading + ? const Padding( + padding: EdgeInsets.symmetric( + vertical: 10.0, + horizontal: 12, + ), + child: SizedBox.square( + dimension: 24, + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), + ), + ) + : const Icon(Icons.search_outlined), ), + onChanged: controller.searchUserWithCoolDown, ), ), - Column( - children: [ - Padding( - // #Pangea - // padding: const EdgeInsets.all(16.0), - padding: const EdgeInsets.only( - bottom: 16.0, - left: 16.0, - right: 16.0, - ), - // Pangea# - child: TextField( - textInputAction: TextInputAction.search, - decoration: InputDecoration( - filled: true, - fillColor: theme.colorScheme.secondaryContainer, - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(99), - ), - hintStyle: TextStyle( - color: theme.colorScheme.onPrimaryContainer, - fontWeight: FontWeight.normal, - ), - // #Pangea - hintText: L10n.of(context).inviteStudentByUserName, - // hintText: L10n.of(context).inviteContactToGroup(groupName), - // Pangea# - prefixIcon: controller.loading - ? const Padding( - padding: EdgeInsets.symmetric( - vertical: 10.0, - horizontal: 12, - ), - child: SizedBox.square( - dimension: 24, + // #Pangea + // StreamBuilder( + Expanded( + child: StreamBuilder( + // stream: room.client.onRoomState.stream + // .where((update) => update.roomId == room.id), + stream: room.client.onRoomState.stream + .where((update) => update.roomId == room.id) + .rateLimit(const Duration(seconds: 1)), + // Pangea# + builder: (context, snapshot) { + final participants = + room.getParticipants().map((user) => user.id).toSet(); + return controller.foundProfiles.isNotEmpty + ? ListView.builder( + // #Pangea + // physics: const NeverScrollableScrollPhysics(), + // shrinkWrap: true, + // Pangea# + itemCount: controller.foundProfiles.length, + itemBuilder: (BuildContext context, int i) => + _InviteContactListTile( + profile: controller.foundProfiles[i], + isMember: participants.contains( + controller.foundProfiles[i].userId, + ), + onTap: () => controller.inviteAction( + context, + controller.foundProfiles[i].userId, + controller.foundProfiles[i].displayName ?? + controller + .foundProfiles[i].userId.localpart ?? + L10n.of(context).user, + ), + ), + ) + : FutureBuilder>( + future: controller.getContacts(context), + builder: (BuildContext context, snapshot) { + if (!snapshot.hasData) { + return const Center( child: CircularProgressIndicator.adaptive( strokeWidth: 2, ), - ), - ) - : const Icon(Icons.search_outlined), - ), - onChanged: controller.searchUserWithCoolDown, - ), - ), - // #Pangea - // StreamBuilder( - Expanded( - key: controller.viewportKey, - child: StreamBuilder( - // stream: room.client.onRoomState.stream - // .where((update) => update.roomId == room.id), - stream: room.client.onRoomState.stream - .where((update) => update.roomId == room.id) - .rateLimit(const Duration(seconds: 1)), - // Pangea# - builder: (context, snapshot) { - final participants = - room.getParticipants().map((user) => user.id).toSet(); - return controller.foundProfiles.isNotEmpty - ? ListView.builder( + ); + } + final contacts = snapshot.data!; + return ListView.builder( // #Pangea // physics: const NeverScrollableScrollPhysics(), // shrinkWrap: true, - // Pangea# - itemCount: controller.foundProfiles.length, - itemBuilder: (BuildContext context, int i) => - _InviteContactListTile( - profile: controller.foundProfiles[i], - isMember: participants.contains( - controller.foundProfiles[i].userId, - ), - onTap: () => controller.inviteAction( - context, - controller.foundProfiles[i].userId, - controller.foundProfiles[i].displayName ?? - controller - .foundProfiles[i].userId.localpart ?? - L10n.of(context).user, - ), - ), - ) - : FutureBuilder>( - future: controller.getContacts(context), - builder: (BuildContext context, snapshot) { - if (!snapshot.hasData) { - return const Center( - child: CircularProgressIndicator.adaptive( - strokeWidth: 2, + // itemCount: contacts.length, + // itemBuilder: (BuildContext context, int i) => + // _InviteContactListTile( + itemCount: contacts.length + 1, + itemBuilder: (BuildContext context, int i) { + if (i == contacts.length) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: SizedBox( + width: 450, + child: CachedNetworkImage( + imageUrl: + "${AppConfig.assetsBaseURL}/${RoomSettingsConstants.referFriendAsset}", + errorWidget: (context, url, error) => + const SizedBox(), + placeholder: (context, url) => + const Center( + child: CircularProgressIndicator + .adaptive(), + ), + ), ), ); } - final contacts = snapshot.data!; - return ListView.builder( - // #Pangea - // physics: const NeverScrollableScrollPhysics(), - // shrinkWrap: true, - // itemCount: contacts.length, - // itemBuilder: (BuildContext context, int i) => - // _InviteContactListTile( - itemCount: contacts.length + 1, - itemBuilder: (BuildContext context, int i) { - if (i == contacts.length) { - final showButtons = controller - .showShareButtons(contacts.length); - return AnimatedOpacity( - duration: - FluffyThemes.animationDuration, - opacity: showButtons ? 1.0 : 0.0, - child: SpaceInviteButtons(room: room), - ); - } - - return _InviteContactListTile( - // Pangea# - user: contacts[i], - profile: Profile( - avatarUrl: contacts[i].avatarUrl, - displayName: contacts[i].displayName ?? - contacts[i].id.localpart ?? - L10n.of(context).user, - userId: contacts[i].id, - ), - isMember: - participants.contains(contacts[i].id), - onTap: () => controller.inviteAction( - context, - contacts[i].id, - contacts[i].displayName ?? - contacts[i].id.localpart ?? - L10n.of(context).user, - ), - ); - }, + return _InviteContactListTile( + // Pangea# + user: contacts[i], + profile: Profile( + avatarUrl: contacts[i].avatarUrl, + displayName: contacts[i].displayName ?? + contacts[i].id.localpart ?? + L10n.of(context).user, + userId: contacts[i].id, + ), + isMember: + participants.contains(contacts[i].id), + onTap: () => controller.inviteAction( + context, + contacts[i].id, + contacts[i].displayName ?? + contacts[i].id.localpart ?? + L10n.of(context).user, + ), ); }, ); - }, - ), - ), - ], + }, + ); + }, + ), ), Padding( padding: const EdgeInsets.all(16.0), @@ -355,6 +341,8 @@ class _InviteContactListTile extends StatelessWidget { style: const TextStyle( fontSize: 12.0, ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), LevelDisplayName(userId: profile.userId), ], diff --git a/lib/pangea/chat_settings/widgets/space_invite_buttons.dart b/lib/pangea/chat_settings/widgets/space_invite_buttons.dart deleted file mode 100644 index 5fd3c7347..000000000 --- a/lib/pangea/chat_settings/widgets/space_invite_buttons.dart +++ /dev/null @@ -1,137 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -import 'package:matrix/matrix.dart'; -import 'package:universal_html/html.dart' as html; - -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/common/config/environment.dart'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/spaces/constants/space_constants.dart'; - -class SpaceInviteButtons extends StatefulWidget { - final Room room; - // final ScrollController scrollController; - const SpaceInviteButtons({ - super.key, - required this.room, - // required this.scrollController, - }); - - @override - SpaceInviteButtonsController createState() => SpaceInviteButtonsController(); -} - -class SpaceInviteButtonsController extends State { - // bool get isVisible { - // final context = (widget.key as GlobalKey).currentContext; - // if (context == null) return false; - - // final renderBox = context.findRenderObject() as RenderBox; - // final position = renderBox.localToGlobal(Offset.zero); - - // final size = renderBox.size; - // final screenHeight = MediaQuery.of(context).size.height; - - // debugPrint("position: $position, size: $size, screenHeight: $screenHeight"); - - // // Check if any part of the widget is within the visible range - // return position.dy + size.height > 0 && position.dy < screenHeight; - // } - - @override - void initState() { - // WidgetsBinding.instance.addPostFrameCallback( - // (_) => debugPrint("isVisible: $isVisible"), - // ); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final spaceCode = widget.room.classCode; - if (!widget.room.isSpace || spaceCode == null) { - return const SizedBox.shrink(); - } - - return SizedBox( - height: 150.0, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only( - top: 16.0, - right: 16.0, - left: 16.0, - ), - child: ElevatedButton( - child: Row( - spacing: 8.0, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.share_outlined, - ), - Text(L10n.of(context).shareSpaceLink), - ], - ), - onPressed: () async { - final String initialUrl = - kIsWeb ? html.window.origin! : Environment.frontendURL; - final link = - "$initialUrl/#/join_with_link?${SpaceConstants.classCode}=$spaceCode"; - await Clipboard.setData( - ClipboardData( - text: link, - ), - ); - ScaffoldMessenger.of( - context, - ).showSnackBar( - SnackBar( - content: Text( - L10n.of(context).copiedToClipboard, - ), - ), - ); - }, - ), - ), - Padding( - padding: const EdgeInsets.only( - top: 16.0, - right: 16.0, - left: 16.0, - ), - child: ElevatedButton( - child: Row( - spacing: 8.0, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.share_outlined, - ), - Text(L10n.of(context).shareInviteCode(spaceCode)), - ], - ), - onPressed: () async { - await Clipboard.setData(ClipboardData(text: spaceCode)); - ScaffoldMessenger.of( - context, - ).showSnackBar( - SnackBar( - content: Text( - L10n.of(context).copiedToClipboard, - ), - ), - ); - }, - ), - ), - ], - ), - ); - } -} From e0086c4b333d4b833ec7ffcddd215e21f43458a9 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 1 Jul 2025 09:03:42 -0400 Subject: [PATCH 38/39] build: bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2e67fdca8..08e6bbcfd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ description: Learn a language while texting your friends. # Pangea# publish_to: none # On version bump also increase the build number for F-Droid -version: 4.1.10+2 +version: 4.1.12+1 environment: sdk: ">=3.0.0 <4.0.0" From 114b14406ea42563588f2840664a9e691048b88c Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 1 Jul 2025 09:09:44 -0400 Subject: [PATCH 39/39] Hides listening practice error widget --- lib/pangea/toolbar/widgets/message_audio_card.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/pangea/toolbar/widgets/message_audio_card.dart b/lib/pangea/toolbar/widgets/message_audio_card.dart index 2cc529422..3b854b5bf 100644 --- a/lib/pangea/toolbar/widgets/message_audio_card.dart +++ b/lib/pangea/toolbar/widgets/message_audio_card.dart @@ -8,7 +8,6 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pages/chat/events/audio_player.dart'; import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart'; -import 'package:fluffychat/pangea/choreographer/widgets/igc/card_error_widget.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; @@ -96,9 +95,7 @@ class MessageAudioCardState extends State { ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onPrimary, ) - : const CardErrorWidget( - error: "Null audio file in message_audio_card", - ); + : const SizedBox(); } }