From c4349babcfdbb75feb27e90157e2dc18a9d3099c Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 13 Jun 2025 13:04:06 -0400 Subject: [PATCH 01/57] Fix duplicate language warning overflow --- .../pages/settings_learning_view.dart | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/pangea/learning_settings/pages/settings_learning_view.dart b/lib/pangea/learning_settings/pages/settings_learning_view.dart index 74c8cb0c6..c521e9717 100644 --- a/lib/pangea/learning_settings/pages/settings_learning_view.dart +++ b/lib/pangea/learning_settings/pages/settings_learning_view.dart @@ -192,13 +192,15 @@ class SettingsLearningView extends StatelessWidget { .colorScheme .error, ), - Text( - L10n.of(context) - .noIdenticalLanguages, - style: TextStyle( - color: Theme.of(context) - .colorScheme - .error, + Flexible( + child: Text( + L10n.of(context) + .noIdenticalLanguages, + style: TextStyle( + color: Theme.of(context) + .colorScheme + .error, + ), ), ), ], From 01b8a4ff383cc6ba0724e058bc12fa2864005503 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 13 Jun 2025 13:35:26 -0400 Subject: [PATCH 02/57] Stop overflow in language selection entries --- .../widgets/p_language_dropdown.dart | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/pangea/learning_settings/widgets/p_language_dropdown.dart b/lib/pangea/learning_settings/widgets/p_language_dropdown.dart index 462eaf650..c71fea714 100644 --- a/lib/pangea/learning_settings/widgets/p_language_dropdown.dart +++ b/lib/pangea/learning_settings/widgets/p_language_dropdown.dart @@ -239,15 +239,17 @@ class LanguageDropDownEntry extends StatelessWidget { Expanded( child: Row( children: [ - Text( - languageModel.getDisplayName(context) ?? "", - style: const TextStyle().copyWith( - color: enabled - ? Theme.of(context).textTheme.bodyLarge!.color - : Theme.of(context).disabledColor, - fontSize: 14, + Flexible( + child: Text( + languageModel.getDisplayName(context) ?? "", + style: const TextStyle().copyWith( + color: enabled + ? Theme.of(context).textTheme.bodyLarge!.color + : Theme.of(context).disabledColor, + fontSize: 14, + ), + overflow: TextOverflow.ellipsis, ), - overflow: TextOverflow.ellipsis, ), const SizedBox(width: 10), if (isL2List && languageModel.l2Support != L2SupportEnum.full) From 68b1382ba205fa2795a9f444d704201ed5a17999 Mon Sep 17 00:00:00 2001 From: wcjord <32568597+wcjord@users.noreply.github.com> Date: Fri, 13 Jun 2025 14:57:45 -0400 Subject: [PATCH 03/57] feat(phonetic_transcription): repo set up --- lib/pangea/common/network/urls.dart | 3 + .../phonetic_transcription_repo.dart | 66 +++++++++++++++++++ .../phonetic_transcription_request.dart | 33 ++++++++++ .../phonetic_transcription_response.dart | 55 ++++++++++++++++ .../test_phonetic_transcription_api.dart | 0 5 files changed, 157 insertions(+) create mode 100644 lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart create mode 100644 lib/pangea/phonetic_transcription/phonetic_transcription_request.dart create mode 100644 lib/pangea/phonetic_transcription/phonetic_transcription_response.dart create mode 100644 lib/pangea/phonetic_transcription/test_phonetic_transcription_api.dart diff --git a/lib/pangea/common/network/urls.dart b/lib/pangea/common/network/urls.dart index a667066c8..5bb4f2c86 100644 --- a/lib/pangea/common/network/urls.dart +++ b/lib/pangea/common/network/urls.dart @@ -86,4 +86,7 @@ class PApiUrls { static String rcProductsTrial = "${PApiUrls.subscriptionEndpoint}/free_trial"; static String rcSubscription = PApiUrls.subscriptionEndpoint; + + static String phoneticTranscription = + "${PApiUrls.choreoEndpoint}/phonetic_transcription"; } diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart new file mode 100644 index 000000000..30eac8302 --- /dev/null +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:fluffychat/pangea/common/config/environment.dart'; +import 'package:fluffychat/pangea/common/network/requests.dart'; +import 'package:fluffychat/pangea/common/network/urls.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_response.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:http/http.dart'; + +class PhoneticTranscriptionRepo { + static final GetStorage _storage = + GetStorage('phonetic_transcription_storage'); + + static void set(PhoneticTranscriptionRequest request, + PhoneticTranscriptionResponse response) { + response.expireAt ??= DateTime.now().add(const Duration(days: 100)); + _storage.write(request.storageKey, response.toJson()); + } + + static Future _fetch( + PhoneticTranscriptionRequest request) async { + final cachedJson = _storage.read(request.storageKey); + final cached = cachedJson == null + ? null + : PhoneticTranscriptionResponse.fromJson(cachedJson); + + if (cached != null) { + if (DateTime.now().isBefore(cached.expireAt!)) { + return cached; + } else { + _storage.remove(request.storageKey); + } + } + + final Requests req = Requests( + choreoApiKey: Environment.choreoApiKey, + accessToken: MatrixState.pangeaController.userController.accessToken, + ); + + final Response res = await req.post( + url: PApiUrls.phoneticTranscription, + body: request.toJson(), + ); + + final decodedBody = jsonDecode(utf8.decode(res.bodyBytes)); + final response = PhoneticTranscriptionResponse.fromJson(decodedBody); + set(request, response); + return response; + } + + static Future get( + PhoneticTranscriptionRequest request) async { + try { + return await _fetch(request); + } catch (e) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: e, data: request.toJson()); + rethrow; + } + } +} diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_request.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_request.dart new file mode 100644 index 000000000..02dfcb7c7 --- /dev/null +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_request.dart @@ -0,0 +1,33 @@ +class PhoneticTranscriptionRequest { + final String l1; + final String l2; + final String content; + final bool requiresTokenization; + + PhoneticTranscriptionRequest({ + required this.l1, + required this.l2, + required this.content, + this.requiresTokenization = true, + }); + + factory PhoneticTranscriptionRequest.fromJson(Map json) { + return PhoneticTranscriptionRequest( + l1: json['l1'] as String, + l2: json['l2'] as String, + content: json['content'] as String, + requiresTokenization: json['requires_tokenization'] ?? true, + ); + } + + Map toJson() { + return { + 'l1': l1, + 'l2': l2, + 'content': content, + 'requires_tokenization': requiresTokenization, + }; + } + + String get storageKey => 'l1:$l1,l2:$l2,content:$content'; +} diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_response.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_response.dart new file mode 100644 index 000000000..9efce60d4 --- /dev/null +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_response.dart @@ -0,0 +1,55 @@ +class PhoneticTranscriptionResponse { + final Map arc; + final Map content; + final Map tokenization; + final Map phoneticTranscriptionResult; + DateTime? expireAt; + + PhoneticTranscriptionResponse({ + required this.arc, + required this.content, + required this.tokenization, + required this.phoneticTranscriptionResult, + this.expireAt, + }); + + factory PhoneticTranscriptionResponse.fromJson(Map json) { + return PhoneticTranscriptionResponse( + arc: Map.from(json['arc'] as Map), + content: Map.from(json['content'] as Map), + tokenization: Map.from(json['tokenization'] as Map), + phoneticTranscriptionResult: Map.from( + json['phonetic_transcription_result'] as Map), + expireAt: json['expireAt'] == null + ? null + : DateTime.parse(json['expireAt'] as String), + ); + } + + Map toJson() { + return { + 'arc': arc, + 'content': content, + 'tokenization': tokenization, + 'phonetic_transcription_result': phoneticTranscriptionResult, + 'expireAt': expireAt?.toIso8601String(), + }; + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PhoneticTranscriptionResponse && + runtimeType == other.runtimeType && + arc == other.arc && + content == other.content && + tokenization == other.tokenization && + phoneticTranscriptionResult == other.phoneticTranscriptionResult; + + @override + int get hashCode => + arc.hashCode ^ + content.hashCode ^ + tokenization.hashCode ^ + phoneticTranscriptionResult.hashCode; +} diff --git a/lib/pangea/phonetic_transcription/test_phonetic_transcription_api.dart b/lib/pangea/phonetic_transcription/test_phonetic_transcription_api.dart new file mode 100644 index 000000000..e69de29bb From 742bc0d8decfccad572c39a29b2fcd13806a014f Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 16 Jun 2025 11:01:34 -0400 Subject: [PATCH 04/57] Make get started background grayer and more opaque --- lib/pangea/onboarding/onboarding_complete.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pangea/onboarding/onboarding_complete.dart b/lib/pangea/onboarding/onboarding_complete.dart index cbc3c821b..796602d28 100644 --- a/lib/pangea/onboarding/onboarding_complete.dart +++ b/lib/pangea/onboarding/onboarding_complete.dart @@ -19,7 +19,10 @@ class OnboardingComplete extends StatelessWidget { children: [ Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.onSurface.withAlpha(20), + color: Theme.of(context) + .colorScheme + .surfaceContainerHigh + .withAlpha(170), borderRadius: BorderRadius.circular( 10.0, ), From bf1b4397df0abc5b6aa6c886df7c91c14076311a Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 16 Jun 2025 11:15:08 -0400 Subject: [PATCH 05/57] Does not show option to start a conversation for the user's own profile popup --- .../member_actions_popup_menu_button.dart | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/widgets/member_actions_popup_menu_button.dart b/lib/widgets/member_actions_popup_menu_button.dart index 6502beedd..115ddb072 100644 --- a/lib/widgets/member_actions_popup_menu_button.dart +++ b/lib/widgets/member_actions_popup_menu_button.dart @@ -7,6 +7,7 @@ import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_misc/level_display_name.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; import 'package:fluffychat/widgets/avatar.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/permission_slider_dialog.dart'; import 'adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; import 'adaptive_dialogs/user_dialog.dart'; @@ -90,20 +91,21 @@ void showMemberActionsPopupMenu({ ), const PopupMenuDivider(), // #Pangea - PopupMenuItem( - value: _MemberActions.chat, - child: Row( - children: [ - const Icon(Icons.forum_outlined), - const SizedBox(width: 18), - Text( - dmRoomId == null - ? L10n.of(context).startConversation - : L10n.of(context).sendAMessage, - ), - ], + if (Matrix.of(context).client.userID != user.id) + PopupMenuItem( + value: _MemberActions.chat, + child: Row( + children: [ + const Icon(Icons.forum_outlined), + const SizedBox(width: 18), + Text( + dmRoomId == null + ? L10n.of(context).startConversation + : L10n.of(context).sendAMessage, + ), + ], + ), ), - ), // Pangea# if (onMention != null) PopupMenuItem( From e50e1db16a5686d32ccedbfd9dd8eb02e86d8688 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 16 Jun 2025 11:50:21 -0400 Subject: [PATCH 06/57] feat: initial work for add duration to in-chat activities --- lib/l10n/intl_en.arb | 6 +- lib/pages/chat/chat_view.dart | 10 + lib/pages/chat/events/message.dart | 7 + lib/pangea/activities/activity_constants.dart | 3 + .../activities/activity_duration_popup.dart | 280 ++++++++++++++++++ .../activities/activity_state_event.dart | 272 +++++++++++++++++ lib/pangea/activities/countdown.dart | 112 +++++++ .../activities/pinned_activity_message.dart | 95 ++++++ .../activity_planner/activity_plan_model.dart | 51 +++- lib/pangea/common/constants/model_keys.dart | 2 + .../extensions/pangea_room_extension.dart | 1 - .../extensions/room_events_extension.dart | 55 ---- .../filtered_timeline_extension.dart | 9 +- 13 files changed, 840 insertions(+), 63 deletions(-) create mode 100644 lib/pangea/activities/activity_constants.dart create mode 100644 lib/pangea/activities/activity_duration_popup.dart create mode 100644 lib/pangea/activities/activity_state_event.dart create mode 100644 lib/pangea/activities/countdown.dart create mode 100644 lib/pangea/activities/pinned_activity_message.dart diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 50166f59f..2d44b7524 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5016,5 +5016,9 @@ "directMessage": "Direct Message", "newDirectMessage": "New direct message", "speakingExercisesTooltip": "Speaking practice", - "noChatsFoundHereYet": "No chats found here yet" + "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" } \ No newline at end of file diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 1436cb00b..8840a2bf4 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -13,9 +13,11 @@ 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'; @@ -188,6 +190,11 @@ class ChatView extends StatelessWidget { if (scrollUpBannerEventId != null) { appbarBottomHeight += ChatAppBarListTile.fixedHeight; } + // #Pangea + if (controller.room.activityPlan != null) { + appbarBottomHeight += ChatAppBarListTile.fixedHeight; + } + // Pangea# return Scaffold( appBar: AppBar( actionsIconTheme: IconThemeData( @@ -226,6 +233,9 @@ 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 ada6cceb4..e98bb7554 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -9,7 +9,9 @@ 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'; @@ -121,6 +123,11 @@ class Message extends StatelessWidget { if (event.type == EventTypes.RoomCreate) { return RoomCreationStateEvent(event: event); } + // #Pangea + if (event.type == PangeaEventTypes.activityPlan) { + return ActivityStateEvent(event: event); + } + // Pangea# return StateMessage(event); } diff --git a/lib/pangea/activities/activity_constants.dart b/lib/pangea/activities/activity_constants.dart new file mode 100644 index 000000000..41858d53a --- /dev/null +++ b/lib/pangea/activities/activity_constants.dart @@ -0,0 +1,3 @@ +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 new file mode 100644 index 000000000..4ce8a40a6 --- /dev/null +++ b/lib/pangea/activities/activity_duration_popup.dart @@ -0,0 +1,280 @@ +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: [ + 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 new file mode 100644 index 000000000..97715632e --- /dev/null +++ b/lib/pangea/activities/activity_state_event.dart @@ -0,0 +1,272 @@ +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 { + late final 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; + } + } + + bool get _activityIsOver { + return activityPlan?.endAt != null && + DateTime.now().isAfter(activityPlan!.endAt!); + } + + @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: EdgeInsets.all(_activityIsOver ? 24.0 : 16.0), + decoration: BoxDecoration( + color: theme.colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(18), + ), + child: AnimatedSize( + duration: FluffyThemes.animationDuration, + 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: _activityIsOver + ? const SizedBox() + : 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, + ), + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pangea/activities/countdown.dart b/lib/pangea/activities/countdown.dart new file mode 100644 index 000000000..f0ed5e48d --- /dev/null +++ b/lib/pangea/activities/countdown.dart @@ -0,0 +1,112 @@ +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 { + late Timer _timer; + + @override + void initState() { + super.initState(); + _timer = Timer.periodic(const Duration(seconds: 1), (_) { + setState(() {}); + }); + + // final now = DateTime.now(); + // final delay = widget.deadline?.difference(now); + + // if (delay != null && delay > Duration.zero) { + // _endTimer = Timer(delay, () { + // setState( + // () => setState(() {}), + // ); + // }); + // } + } + + @override + void dispose() { + _timer.cancel(); + // _endTimer.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!.isAfter(now) + ? widget.deadline!.difference(now) + : Duration.zero; + } + + @override + Widget build(BuildContext context) { + final remainingTime = this.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 new file mode 100644 index 000000000..32554058e --- /dev/null +++ b/lib/pangea/activities/pinned_activity_message.dart @@ -0,0 +1,95 @@ +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_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); + + if (_activityPlan?.endAt == null || + _activityPlan!.endAt!.isBefore(DateTime.now())) { + 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/activity_planner/activity_plan_model.dart b/lib/pangea/activity_planner/activity_plan_model.dart index bd37039fb..7ea09a68a 100644 --- a/lib/pangea/activity_planner/activity_plan_model.dart +++ b/lib/pangea/activity_planner/activity_plan_model.dart @@ -11,6 +11,8 @@ class ActivityPlanModel { final String instructions; final List vocab; final String? imageURL; + final DateTime? endAt; + final Duration? duration; ActivityPlanModel({ required this.req, @@ -19,31 +21,70 @@ class ActivityPlanModel { required this.instructions, required this.vocab, this.imageURL, + this.endAt, + this.duration, }) : bookmarkId = "${title.hashCode ^ learningObjective.hashCode ^ instructions.hashCode ^ imageURL.hashCode ^ vocab.map((v) => v.hashCode).reduce((a, b) => a ^ b)}"; + ActivityPlanModel copyWith({ + String? title, + String? learningObjective, + String? instructions, + List? vocab, + String? imageURL, + DateTime? endAt, + Duration? duration, + }) { + return ActivityPlanModel( + req: req, + title: title ?? this.title, + learningObjective: learningObjective ?? this.learningObjective, + instructions: instructions ?? this.instructions, + vocab: vocab ?? this.vocab, + imageURL: imageURL ?? this.imageURL, + endAt: endAt ?? this.endAt, + duration: duration ?? this.duration, + ); + } + factory ActivityPlanModel.fromJson(Map json) { return ActivityPlanModel( + imageURL: json[ModelKey.activityPlanImageURL], + instructions: json[ModelKey.activityPlanInstructions], req: ActivityPlanRequest.fromJson(json[ModelKey.activityPlanRequest]), title: json[ModelKey.activityPlanTitle], learningObjective: json[ModelKey.activityPlanLearningObjective], - instructions: json[ModelKey.activityPlanInstructions], vocab: List.from( json[ModelKey.activityPlanVocab].map((vocab) => Vocab.fromJson(vocab)), ), - imageURL: json[ModelKey.activityPlanImageURL], + endAt: json[ModelKey.activityPlanEndAt] != null + ? DateTime.parse(json[ModelKey.activityPlanEndAt]) + : null, + duration: json[ModelKey.activityPlanDuration] != null + ? Duration( + days: json[ModelKey.activityPlanDuration]['days'] ?? 0, + hours: json[ModelKey.activityPlanDuration]['hours'] ?? 0, + minutes: json[ModelKey.activityPlanDuration]['minutes'] ?? 0, + ) + : null, ); } Map toJson() { return { + ModelKey.activityPlanBookmarkId: bookmarkId, + ModelKey.activityPlanImageURL: imageURL, + ModelKey.activityPlanInstructions: instructions, ModelKey.activityPlanRequest: req.toJson(), ModelKey.activityPlanTitle: title, ModelKey.activityPlanLearningObjective: learningObjective, - ModelKey.activityPlanInstructions: instructions, ModelKey.activityPlanVocab: vocab.map((vocab) => vocab.toJson()).toList(), - ModelKey.activityPlanImageURL: imageURL, - ModelKey.activityPlanBookmarkId: bookmarkId, + ModelKey.activityPlanEndAt: endAt?.toIso8601String(), + ModelKey.activityPlanDuration: { + 'days': duration?.inDays ?? 0, + 'hours': duration?.inHours.remainder(24) ?? 0, + 'minutes': duration?.inMinutes.remainder(60) ?? 0, + }, }; } diff --git a/lib/pangea/common/constants/model_keys.dart b/lib/pangea/common/constants/model_keys.dart index 781667c34..22b360c8d 100644 --- a/lib/pangea/common/constants/model_keys.dart +++ b/lib/pangea/common/constants/model_keys.dart @@ -163,6 +163,8 @@ class ModelKey { static const String activityPlanVocab = "vocab"; static const String activityPlanImageURL = "image_url"; static const String activityPlanBookmarkId = "bookmark_id"; + static const String activityPlanEndAt = "end_at"; + static const String activityPlanDuration = "duration"; static const String activityRequestTopic = "topic"; static const String activityRequestMode = "mode"; diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index 316ef7199..58616de36 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -9,7 +9,6 @@ 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 3da8c1272..7c5676bc3 100644 --- a/lib/pangea/extensions/room_events_extension.dart +++ b/lib/pangea/extensions/room_events_extension.dart @@ -277,57 +277,6 @@ 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 (canSendDefaultStates) { await client.setRoomStateWithKey( id, @@ -335,10 +284,6 @@ extension EventsRoomExtension on Room { "", activity.toJson(), ); - - if (eventId != null) { - 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 fee1b3c62..f074a43f1 100644 --- a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart +++ b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart @@ -1,6 +1,7 @@ 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 { @@ -46,7 +47,12 @@ 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 - (!AppConfig.hideUnknownEvents || isEventTypeKnown) && + // #Pangea + // (!AppConfig.hideUnknownEvents || isEventTypeKnown) && + (!AppConfig.hideUnknownEvents || + isEventTypeKnown || + importantStateEvents.contains(type)) && + // Pangea# // remove state events that we don't want to render (isState || !AppConfig.hideAllStateEvents) && // #Pangea @@ -82,6 +88,7 @@ extension IsStateExtension on Event { EventTypes.RoomMember, EventTypes.RoomTombstone, EventTypes.CallInvite, + PangeaEventTypes.activityPlan, }; // Pangea# } From 007162ea0265b53507d4a943e8172467bb8483fa Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 16 Jun 2025 11:53:22 -0400 Subject: [PATCH 07/57] chore: update how client instance is accessed --- lib/widgets/member_actions_popup_menu_button.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/widgets/member_actions_popup_menu_button.dart b/lib/widgets/member_actions_popup_menu_button.dart index 115ddb072..f4ff7c3ca 100644 --- a/lib/widgets/member_actions_popup_menu_button.dart +++ b/lib/widgets/member_actions_popup_menu_button.dart @@ -7,7 +7,6 @@ import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_misc/level_display_name.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; import 'package:fluffychat/widgets/avatar.dart'; -import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/permission_slider_dialog.dart'; import 'adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; import 'adaptive_dialogs/user_dialog.dart'; @@ -91,7 +90,7 @@ void showMemberActionsPopupMenu({ ), const PopupMenuDivider(), // #Pangea - if (Matrix.of(context).client.userID != user.id) + if (user.room.client.userID != user.id) PopupMenuItem( value: _MemberActions.chat, child: Row( From 9139c992acd46975cf78d8229371a549b21f50f7 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 16 Jun 2025 12:00:53 -0400 Subject: [PATCH 08/57] chore: update emojis when switching between different tokens --- lib/pangea/lemmas/lemma_reaction_picker.dart | 32 +++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/lib/pangea/lemmas/lemma_reaction_picker.dart b/lib/pangea/lemmas/lemma_reaction_picker.dart index b229f2396..92a3a70ef 100644 --- a/lib/pangea/lemmas/lemma_reaction_picker.dart +++ b/lib/pangea/lemmas/lemma_reaction_picker.dart @@ -28,17 +28,35 @@ class LemmaReactionPickerState extends State { @override void initState() { super.initState(); - widget.cId.getLemmaInfo().then((info) { - loading = false; - setState(() => displayEmoji = info.emoji); - }).catchError((e, s) { - ErrorHandler.logError(data: widget.cId.toJson(), e: e, s: s); - setState(() => loading = false); - }); + _refresh(); + } + + @override + void didUpdateWidget(LemmaReactionPicker oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.cId != widget.cId) { + _refresh(); + } } void setEmoji(String emoji) => widget.controller.sendEmojiAction(emoji); + Future _refresh() async { + setState(() { + loading = true; + displayEmoji = []; + }); + + try { + final info = await widget.cId.getLemmaInfo(); + displayEmoji = info.emoji; + } catch (e, s) { + ErrorHandler.logError(data: widget.cId.toJson(), e: e, s: s); + } finally { + setState(() => loading = false); + } + } + @override Widget build(BuildContext context) { return Container( From 40a6e5a10bb67fc05561cc8b37c3e380a8bd64e1 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 16 Jun 2025 13:52:32 -0400 Subject: [PATCH 09/57] chore: started adding widget to show phonetic transcription --- lib/l10n/intl_en.arb | 3 +- .../vocab_analytics_details_view.dart | 10 ++ .../phonetic_transcription_repo.dart | 20 ++-- .../phonetic_transcription_widget.dart | 106 ++++++++++++++++++ .../test_phonetic_transcription_api.dart | 0 5 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart delete mode 100644 lib/pangea/phonetic_transcription/test_phonetic_transcription_api.dart diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 50166f59f..a7e110b90 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5016,5 +5016,6 @@ "directMessage": "Direct Message", "newDirectMessage": "New direct message", "speakingExercisesTooltip": "Speaking practice", - "noChatsFoundHereYet": "No chats found here yet" + "noChatsFoundHereYet": "No chats found here yet", + "phoneticTranscriptionError": "Phonetic transcription failed" } \ No newline at end of file diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart index 56fabe42c..4f65589cc 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/lemmas/lemma_emoji_row.dart'; import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_icon.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_widget.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -26,6 +27,9 @@ class VocabDetailsView extends StatelessWidget { ConstructUses get _construct => constructId.constructUses; + String? get _userL1 => + MatrixState.pangeaController.languageController.userL1?.langCode; + /// Get the language code for the current lemma String? get _userL2 => MatrixState.pangeaController.languageController.userL2?.langCode; @@ -60,6 +64,12 @@ class VocabDetailsView extends StatelessWidget { ), subtitle: Column( children: [ + if (_userL1 != null && _userL2 != null) + PhoneticTranscription( + text: _construct.lemma, + l1: _userL1!, + l2: _userL2!, + ), Row( mainAxisSize: MainAxisSize.min, spacing: 8.0, diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart index 30eac8302..124389eed 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_repo.dart @@ -1,6 +1,11 @@ import 'dart:convert'; import 'dart:developer'; +import 'package:flutter/foundation.dart'; + +import 'package:get_storage/get_storage.dart'; +import 'package:http/http.dart'; + import 'package:fluffychat/pangea/common/config/environment.dart'; import 'package:fluffychat/pangea/common/network/requests.dart'; import 'package:fluffychat/pangea/common/network/urls.dart'; @@ -8,22 +13,22 @@ import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart'; import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_response.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/foundation.dart'; -import 'package:get_storage/get_storage.dart'; -import 'package:http/http.dart'; class PhoneticTranscriptionRepo { static final GetStorage _storage = GetStorage('phonetic_transcription_storage'); - static void set(PhoneticTranscriptionRequest request, - PhoneticTranscriptionResponse response) { + static void set( + PhoneticTranscriptionRequest request, + PhoneticTranscriptionResponse response, + ) { response.expireAt ??= DateTime.now().add(const Duration(days: 100)); _storage.write(request.storageKey, response.toJson()); } static Future _fetch( - PhoneticTranscriptionRequest request) async { + PhoneticTranscriptionRequest request, + ) async { final cachedJson = _storage.read(request.storageKey); final cached = cachedJson == null ? null @@ -54,7 +59,8 @@ class PhoneticTranscriptionRepo { } static Future get( - PhoneticTranscriptionRequest request) async { + PhoneticTranscriptionRequest request, + ) async { try { return await _fetch(request); } catch (e) { diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart new file mode 100644 index 000000000..b8d7f0d77 --- /dev/null +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; + +import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_repo.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_response.dart'; + +class PhoneticTranscription extends StatefulWidget { + final String text; + final String l1; + final String l2; + + const PhoneticTranscription({ + super.key, + required this.text, + required this.l1, + required this.l2, + }); + + @override + State createState() => PhoneticTranscriptionState(); +} + +class PhoneticTranscriptionState extends State { + bool _loading = false; + String? error; + + PhoneticTranscriptionResponse? _response; + + @override + void initState() { + super.initState(); + _fetchPhoneticTranscription(); + } + + @override + void didUpdateWidget(covariant PhoneticTranscription oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.text != widget.text || + oldWidget.l1 != widget.l1 || + oldWidget.l2 != widget.l2) { + _fetchPhoneticTranscription(); + } + } + + Future _fetchPhoneticTranscription() async { + final PhoneticTranscriptionRequest request = PhoneticTranscriptionRequest( + l1: widget.l1, + l2: widget.l2, + content: widget.text, + ); + + try { + setState(() { + _loading = true; + error = null; + }); + + _response = await PhoneticTranscriptionRepo.get(request); + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: request.toJson(), + ); + error = e.toString(); + } finally { + if (mounted) { + setState(() { + _loading = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + if (error != null) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.error_outline, + color: Theme.of(context).colorScheme.error, + size: 16.0, + ), + const SizedBox(width: 8), + Text( + L10n.of(context).phoneticTranscriptionError, + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ); + } + + if (_loading || _response == null) { + return const Center(child: CircularProgressIndicator.adaptive()); + } + + return Text( + 'Phonetic transcription for "${widget.text}" in ${widget.l2}', + style: Theme.of(context).textTheme.bodyLarge, + ); + } +} diff --git a/lib/pangea/phonetic_transcription/test_phonetic_transcription_api.dart b/lib/pangea/phonetic_transcription/test_phonetic_transcription_api.dart deleted file mode 100644 index e69de29bb..000000000 From a92237fc8bfb4816fd70540ebbde03b2eccf5377 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 16 Jun 2025 14:12:49 -0400 Subject: [PATCH 10/57] chore: update transcription error message --- lib/l10n/intl_en.arb | 3 ++- lib/pangea/toolbar/widgets/overlay_message.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 50166f59f..09c40f998 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5016,5 +5016,6 @@ "directMessage": "Direct Message", "newDirectMessage": "New direct message", "speakingExercisesTooltip": "Speaking practice", - "noChatsFoundHereYet": "No chats found here yet" + "noChatsFoundHereYet": "No chats found here yet", + "transcriptionFailed": "Failed to transcribe audio" } \ No newline at end of file diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index ed2f8b71e..b6633b0c6 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -160,7 +160,7 @@ class OverlayMessage extends StatelessWidget { ), const SizedBox(width: 8), Text( - L10n.of(context).oopsSomethingWentWrong, + L10n.of(context).transcriptionFailed, style: AppConfig.messageTextStyle( event, textColor, From 1a0061b35accffbd80b60a57f5d957df5ece4af0 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 16 Jun 2025 14:50:02 -0400 Subject: [PATCH 11/57] Use translate tooltip for speech translation button --- lib/pangea/toolbar/widgets/select_mode_buttons.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index 0c353c3f4..07f5d3722 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -37,11 +37,10 @@ enum SelectMode { case SelectMode.audio: return l10n.playAudio; case SelectMode.translate: + case SelectMode.speechTranslation: return l10n.translationTooltip; case SelectMode.practice: return l10n.practice; - case SelectMode.speechTranslation: - return l10n.speechToTextTooltip; } } } From 20fab6a5d2d08b8088f506bf17a2d0eeadbdf150 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 16 Jun 2025 15:04:37 -0400 Subject: [PATCH 12/57] Don't use space default image/description for joining chats in space --- .../public_spaces/public_room_bottom_sheet.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/pangea/public_spaces/public_room_bottom_sheet.dart b/lib/pangea/public_spaces/public_room_bottom_sheet.dart index ba0b71fc7..89ed09700 100644 --- a/lib/pangea/public_spaces/public_room_bottom_sheet.dart +++ b/lib/pangea/public_spaces/public_room_bottom_sheet.dart @@ -200,12 +200,14 @@ class PublicRoomBottomSheetState extends State { Row( spacing: 16.0, children: [ - (chunk?.avatarUrl != null) + (chunk?.avatarUrl != null || chunk?.roomType != 'm.space') ? Avatar( mxContent: chunk?.avatarUrl, name: chunk?.name, size: 160.0, - borderRadius: BorderRadius.circular(24.0), + borderRadius: BorderRadius.circular( + chunk?.roomType != 'm.space' ? 80 : 24.0, + ), ) : ClipRRect( borderRadius: BorderRadius.circular(24.0), @@ -242,7 +244,11 @@ class PublicRoomBottomSheetState extends State { child: SingleChildScrollView( child: Text( chunk?.topic ?? - L10n.of(context).noSpaceDescriptionYet, + (chunk?.roomType != 'm.space' + ? L10n.of(context) + .noChatDescriptionYet + : L10n.of(context) + .noSpaceDescriptionYet), softWrap: true, textAlign: TextAlign.start, maxLines: null, From daeaf900f3da5eae705e87eb8f6b3255fecf1a31 Mon Sep 17 00:00:00 2001 From: wcjord <32568597+wcjord@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:13:40 -0400 Subject: [PATCH 13/57] fix(phonetic_transcription): fixed models --- lib/main.dart | 17 +- .../vocab_analytics_details_view.dart | 37 +++-- .../models/pangea_token_text_model.dart | 8 + .../models/language_model.dart | 27 +++- .../phonetic_transcription_request.dart | 26 +-- .../phonetic_transcription_response.dart | 123 ++++++++++++-- .../phonetic_transcription_widget.dart | 153 ++++++++++++++++++ 7 files changed, 346 insertions(+), 45 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d33e62b41..b7df5a61c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,13 +1,4 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:collection/collection.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:get_storage/get_storage.dart'; -import 'package:matrix/matrix.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; @@ -15,6 +6,14 @@ import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:matrix/matrix.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + import 'config/setting_keys.dart'; import 'utils/background_push.dart'; import 'widgets/fluffy_chat_app.dart'; diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart index 4f65589cc..c00fb1176 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart @@ -1,7 +1,4 @@ -import 'package:flutter/material.dart'; - import 'package:collection/collection.dart'; - import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup_content.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; @@ -15,6 +12,7 @@ import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_ import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; /// Displays information about selected lemma, and its usage class VocabDetailsView extends StatelessWidget { @@ -53,14 +51,33 @@ class VocabDetailsView extends StatelessWidget { : _construct.lemmaCategory.darkColor(context)); return AnalyticsDetailsViewContent( - title: WordTextWithAudioButton( - text: _construct.lemma, - style: Theme.of(context).textTheme.headlineLarge?.copyWith( - color: textColor, + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + WordTextWithAudioButton( + text: _construct.lemma, + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + color: textColor, + ), + iconSize: _iconSize, + uniqueID: "${_construct.lemma}-${_construct.category}", + langCode: _userL2!, + ), + if (MatrixState.pangeaController.languageController.userL2 != null) + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: PhoneticTranscriptionWidget( + text: _construct.lemma, + textLanguage: + MatrixState.pangeaController.languageController.userL2!, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: textColor.withAlpha((0.7 * 255).toInt()), + fontSize: 18, + ), + iconSize: _iconSize * 0.8, + ), ), - iconSize: _iconSize, - uniqueID: "${_construct.lemma}-${_construct.category}", - langCode: _userL2!, + ], ), subtitle: Column( children: [ diff --git a/lib/pangea/events/models/pangea_token_text_model.dart b/lib/pangea/events/models/pangea_token_text_model.dart index 7f7323cf6..efef9a712 100644 --- a/lib/pangea/events/models/pangea_token_text_model.dart +++ b/lib/pangea/events/models/pangea_token_text_model.dart @@ -22,6 +22,14 @@ class PangeaTokenText { ); } + static PangeaTokenText fromString(String content) { + return PangeaTokenText( + offset: 0, + content: content, + length: content.length, + ); + } + static const String _offsetKey = "offset"; static const String _contentKey = "content"; static const String _lengthKey = "length"; diff --git a/lib/pangea/learning_settings/models/language_model.dart b/lib/pangea/learning_settings/models/language_model.dart index 61f021e91..6d6a03380 100644 --- a/lib/pangea/learning_settings/models/language_model.dart +++ b/lib/pangea/learning_settings/models/language_model.dart @@ -1,8 +1,7 @@ -import 'package:flutter/material.dart'; - import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/learning_settings/enums/l2_support_enum.dart'; +import 'package:flutter/material.dart'; class LanguageModel { final String langCode; @@ -80,3 +79,27 @@ class LanguageModel { @override int get hashCode => langCode.hashCode; } + +class LanguageArc { + final LanguageModel l1; + final LanguageModel l2; + + LanguageArc({ + required this.l1, + required this.l2, + }); + + factory LanguageArc.fromJson(Map json) { + return LanguageArc( + l1: LanguageModel.fromJson(json['l1'] as Map), + l2: LanguageModel.fromJson(json['l2'] as Map), + ); + } + + Map toJson() { + return { + 'l1': l1.toJson(), + 'l2': l2.toJson(), + }; + } +} diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_request.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_request.dart index 02dfcb7c7..cbd22fc22 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_request.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_request.dart @@ -1,33 +1,33 @@ +import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; +import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; + class PhoneticTranscriptionRequest { - final String l1; - final String l2; - final String content; + final LanguageArc arc; + final PangeaTokenText content; final bool requiresTokenization; PhoneticTranscriptionRequest({ - required this.l1, - required this.l2, + required this.arc, required this.content, - this.requiresTokenization = true, + this.requiresTokenization = false, }); factory PhoneticTranscriptionRequest.fromJson(Map json) { return PhoneticTranscriptionRequest( - l1: json['l1'] as String, - l2: json['l2'] as String, - content: json['content'] as String, + arc: LanguageArc.fromJson(json['arc'] as Map), + content: + PangeaTokenText.fromJson(json['content'] as Map), requiresTokenization: json['requires_tokenization'] ?? true, ); } Map toJson() { return { - 'l1': l1, - 'l2': l2, - 'content': content, + 'arc': arc.toJson(), + 'content': content.toJson(), 'requires_tokenization': requiresTokenization, }; } - String get storageKey => 'l1:$l1,l2:$l2,content:$content'; + String get storageKey => '${arc.l1}-${arc.l2}-${content.hashCode}'; } diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_response.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_response.dart index 9efce60d4..a4cd2b3a3 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_response.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_response.dart @@ -1,8 +1,107 @@ +import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; +import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; + +enum PhoneticTranscriptionDelimEnum { sp, noSp } + +extension PhoneticTranscriptionDelimEnumExt on PhoneticTranscriptionDelimEnum { + String get value { + switch (this) { + case PhoneticTranscriptionDelimEnum.sp: + return " "; + case PhoneticTranscriptionDelimEnum.noSp: + return ""; + } + } + + static PhoneticTranscriptionDelimEnum fromString(String s) { + switch (s) { + case " ": + return PhoneticTranscriptionDelimEnum.sp; + case "": + return PhoneticTranscriptionDelimEnum.noSp; + default: + return PhoneticTranscriptionDelimEnum.sp; + } + } +} + +class PhoneticTranscriptionToken { + final LanguageArc arc; + final PangeaTokenText tokenL2; + final PangeaTokenText phoneticL1Transcription; + + PhoneticTranscriptionToken({ + required this.arc, + required this.tokenL2, + required this.phoneticL1Transcription, + }); + + factory PhoneticTranscriptionToken.fromJson(Map json) { + return PhoneticTranscriptionToken( + arc: LanguageArc.fromJson(json['arc'] as Map), + tokenL2: + PangeaTokenText.fromJson(json['token_l2'] as Map), + phoneticL1Transcription: PangeaTokenText.fromJson( + json['phonetic_l1_transcription'] as Map, + ), + ); + } + + Map toJson() => { + 'arc': arc.toJson(), + 'token_l2': tokenL2.toJson(), + 'phonetic_l1_transcription': phoneticL1Transcription.toJson(), + }; +} + +class PhoneticTranscription { + final LanguageArc arc; + final PangeaTokenText transcriptionL2; + final List phoneticTranscription; + final PhoneticTranscriptionDelimEnum delim; + + PhoneticTranscription({ + required this.arc, + required this.transcriptionL2, + required this.phoneticTranscription, + this.delim = PhoneticTranscriptionDelimEnum.sp, + }); + + factory PhoneticTranscription.fromJson(Map json) { + return PhoneticTranscription( + arc: LanguageArc.fromJson(json['arc'] as Map), + transcriptionL2: PangeaTokenText.fromJson( + json['transcription_l2'] as Map, + ), + phoneticTranscription: (json['phonetic_transcription'] as List) + .map( + (e) => + PhoneticTranscriptionToken.fromJson(e as Map), + ) + .toList(), + delim: json['delim'] != null + ? PhoneticTranscriptionDelimEnumExt.fromString( + json['delim'] as String, + ) + : PhoneticTranscriptionDelimEnum.sp, + ); + } + + Map toJson() => { + 'arc': arc.toJson(), + 'transcription_l2': transcriptionL2.toJson(), + 'phonetic_transcription': + phoneticTranscription.map((e) => e.toJson()).toList(), + 'delim': delim.value, + }; +} + class PhoneticTranscriptionResponse { - final Map arc; - final Map content; - final Map tokenization; - final Map phoneticTranscriptionResult; + final LanguageArc arc; + final PangeaTokenText content; + final Map + tokenization; // You can define a typesafe model if needed + final PhoneticTranscription phoneticTranscriptionResult; DateTime? expireAt; PhoneticTranscriptionResponse({ @@ -15,11 +114,13 @@ class PhoneticTranscriptionResponse { factory PhoneticTranscriptionResponse.fromJson(Map json) { return PhoneticTranscriptionResponse( - arc: Map.from(json['arc'] as Map), - content: Map.from(json['content'] as Map), + arc: LanguageArc.fromJson(json['arc'] as Map), + content: + PangeaTokenText.fromJson(json['content'] as Map), tokenization: Map.from(json['tokenization'] as Map), - phoneticTranscriptionResult: Map.from( - json['phonetic_transcription_result'] as Map), + phoneticTranscriptionResult: PhoneticTranscription.fromJson( + json['phonetic_transcription_result'] as Map, + ), expireAt: json['expireAt'] == null ? null : DateTime.parse(json['expireAt'] as String), @@ -28,10 +129,10 @@ class PhoneticTranscriptionResponse { Map toJson() { return { - 'arc': arc, - 'content': content, + 'arc': arc.toJson(), + 'content': content.toJson(), 'tokenization': tokenization, - 'phonetic_transcription_result': phoneticTranscriptionResult, + 'phonetic_transcription_result': phoneticTranscriptionResult.toJson(), 'expireAt': expireAt?.toIso8601String(), }; } diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index b8d7f0d77..abf0c8bc2 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -1,3 +1,4 @@ +<<<<<<< Updated upstream import 'package:flutter/material.dart'; import 'package:fluffychat/l10n/l10n.dart'; @@ -27,10 +28,51 @@ class PhoneticTranscriptionState extends State { String? error; PhoneticTranscriptionResponse? _response; +======= +import 'dart:async'; + +import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; +import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_repo.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart'; +import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; + +class PhoneticTranscriptionWidget extends StatefulWidget { + final String text; + final LanguageModel textLanguage; + final TextStyle? style; + final double? iconSize; + + const PhoneticTranscriptionWidget({ + super.key, + required this.text, + required this.textLanguage, + this.style, + this.iconSize, + }); + + @override + State createState() => + _PhoneticTranscriptionWidgetState(); +} + +class _PhoneticTranscriptionWidgetState + extends State { + late Future _transcriptionFuture; + bool _hovering = false; + bool _isPlaying = false; + bool _isLoading = false; + late final StreamSubscription _loadingChoreoSubscription; +>>>>>>> Stashed changes @override void initState() { super.initState(); +<<<<<<< Updated upstream _fetchPhoneticTranscription(); } @@ -71,11 +113,69 @@ class PhoneticTranscriptionState extends State { _loading = false; }); } +======= + _transcriptionFuture = _fetchTranscription(); + _loadingChoreoSubscription = + TtsController.loadingChoreoStream.stream.listen((val) { + if (mounted) setState(() => _isLoading = val); + }); + } + + @override + void dispose() { + TtsController.stop(); + _loadingChoreoSubscription.cancel(); + super.dispose(); + } + + Future _fetchTranscription() async { + if (MatrixState.pangeaController.languageController.userL1 == null) { + ErrorHandler.logError( + e: Exception('User L1 is not set'), + data: { + 'text': widget.text, + 'textLanguageCode': widget.textLanguage.langCode, + }, + ); + return widget.text; // Fallback to original text if no L1 is set + } + final req = PhoneticTranscriptionRequest( + arc: LanguageArc( + l1: MatrixState.pangeaController.languageController.userL1!, + l2: widget.textLanguage, + ), + content: PangeaTokenText.fromString(widget.text), + // arc can be omitted for default empty map + ); + final res = await PhoneticTranscriptionRepo.get(req); + return res.phoneticTranscriptionResult.phoneticTranscription.first + .phoneticL1Transcription.content; + } + + Future _handleAudioTap(BuildContext context) async { + if (_isPlaying) { + await TtsController.stop(); + setState(() => _isPlaying = false); + } else { + await TtsController.tryToSpeak( + widget.text, + context: context, + targetID: 'phonetic-transcription-${widget.text}', + langCode: widget.textLanguage.langCode, + onStart: () { + if (mounted) setState(() => _isPlaying = true); + }, + onStop: () { + if (mounted) setState(() => _isPlaying = false); + }, + ); +>>>>>>> Stashed changes } } @override Widget build(BuildContext context) { +<<<<<<< Updated upstream if (error != null) { return Row( mainAxisSize: MainAxisSize.min, @@ -101,6 +201,59 @@ class PhoneticTranscriptionState extends State { return Text( 'Phonetic transcription for "${widget.text}" in ${widget.l2}', style: Theme.of(context).textTheme.bodyLarge, +======= + return FutureBuilder( + future: _transcriptionFuture, + builder: (context, snapshot) { + final transcription = snapshot.data ?? ''; + return MouseRegion( + onEnter: (_) => setState(() => _hovering = true), + onExit: (_) => setState(() => _hovering = false), + child: GestureDetector( + onTap: () => _handleAudioTap(context), + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + decoration: BoxDecoration( + color: _hovering + ? Colors.grey.withAlpha((0.2 * 255).round()) + : Colors.transparent, + borderRadius: BorderRadius.circular(6), + ), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + transcription.isNotEmpty ? transcription : widget.text, + style: + widget.style ?? Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(width: 8), + Tooltip( + message: _isPlaying + ? L10n.of(context).stop + : L10n.of(context).playAudio, + child: _isLoading + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 3), + ) + : Icon( + _isPlaying ? Icons.pause_outlined : Icons.volume_up, + size: widget.iconSize ?? 24, + color: _isPlaying + ? Theme.of(context).colorScheme.primary + : Theme.of(context).iconTheme.color, + ), + ), + ], + ), + ), + ), + ); + }, +>>>>>>> Stashed changes ); } } From 7756f2fe9fa4f53324471d1a7107746b8b617fda Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 16 Jun 2025 16:32:13 -0400 Subject: [PATCH 14/57] chore: clean up vocab analytics details popup --- lib/main.dart | 17 +-- .../vocab_analytics_details_view.dart | 30 +++-- .../models/language_model.dart | 3 +- .../phonetic_transcription_widget.dart | 108 +----------------- 4 files changed, 28 insertions(+), 130 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index b7df5a61c..d33e62b41 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + import 'package:collection/collection.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:matrix/matrix.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; @@ -6,14 +15,6 @@ import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:get_storage/get_storage.dart'; -import 'package:matrix/matrix.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - import 'config/setting_keys.dart'; import 'utils/background_push.dart'; import 'widgets/fluffy_chat_app.dart'; diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart index c00fb1176..d472a64b1 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart @@ -1,4 +1,7 @@ +import 'package:flutter/material.dart'; + import 'package:collection/collection.dart'; + import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup_content.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; @@ -9,10 +12,10 @@ import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/morphs/morph_icon.dart'; import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_widget.dart'; +import 'package:fluffychat/pangea/toolbar/utils/shrinkable_text.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; /// Displays information about selected lemma, and its usage class VocabDetailsView extends StatelessWidget { @@ -52,16 +55,17 @@ class VocabDetailsView extends StatelessWidget { return AnalyticsDetailsViewContent( title: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - WordTextWithAudioButton( - text: _construct.lemma, - style: Theme.of(context).textTheme.headlineLarge?.copyWith( - color: textColor, - ), - iconSize: _iconSize, - uniqueID: "${_construct.lemma}-${_construct.category}", - langCode: _userL2!, + LayoutBuilder( + builder: (context, constraints) { + return ShrinkableText( + text: _construct.lemma, + maxWidth: constraints.maxWidth - 40.0, + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + color: textColor, + ), + ); + }, ), if (MatrixState.pangeaController.languageController.userL2 != null) Padding( @@ -81,12 +85,6 @@ class VocabDetailsView extends StatelessWidget { ), subtitle: Column( children: [ - if (_userL1 != null && _userL2 != null) - PhoneticTranscription( - text: _construct.lemma, - l1: _userL1!, - l2: _userL2!, - ), Row( mainAxisSize: MainAxisSize.min, spacing: 8.0, diff --git a/lib/pangea/learning_settings/models/language_model.dart b/lib/pangea/learning_settings/models/language_model.dart index 6d6a03380..1e1ef69b9 100644 --- a/lib/pangea/learning_settings/models/language_model.dart +++ b/lib/pangea/learning_settings/models/language_model.dart @@ -1,7 +1,8 @@ +import 'package:flutter/material.dart'; + import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/learning_settings/enums/l2_support_enum.dart'; -import 'package:flutter/material.dart'; class LanguageModel { final String langCode; diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index abf0c8bc2..d55695dc2 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -1,36 +1,7 @@ -<<<<<<< Updated upstream -import 'package:flutter/material.dart'; - -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_repo.dart'; -import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart'; -import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_response.dart'; - -class PhoneticTranscription extends StatefulWidget { - final String text; - final String l1; - final String l2; - - const PhoneticTranscription({ - super.key, - required this.text, - required this.l1, - required this.l2, - }); - - @override - State createState() => PhoneticTranscriptionState(); -} - -class PhoneticTranscriptionState extends State { - bool _loading = false; - String? error; - - PhoneticTranscriptionResponse? _response; -======= import 'dart:async'; +import 'package:flutter/material.dart'; + import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; @@ -39,7 +10,6 @@ import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_ import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart'; import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; class PhoneticTranscriptionWidget extends StatefulWidget { final String text; @@ -67,53 +37,10 @@ class _PhoneticTranscriptionWidgetState bool _isPlaying = false; bool _isLoading = false; late final StreamSubscription _loadingChoreoSubscription; ->>>>>>> Stashed changes @override void initState() { super.initState(); -<<<<<<< Updated upstream - _fetchPhoneticTranscription(); - } - - @override - void didUpdateWidget(covariant PhoneticTranscription oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.text != widget.text || - oldWidget.l1 != widget.l1 || - oldWidget.l2 != widget.l2) { - _fetchPhoneticTranscription(); - } - } - - Future _fetchPhoneticTranscription() async { - final PhoneticTranscriptionRequest request = PhoneticTranscriptionRequest( - l1: widget.l1, - l2: widget.l2, - content: widget.text, - ); - - try { - setState(() { - _loading = true; - error = null; - }); - - _response = await PhoneticTranscriptionRepo.get(request); - } catch (e, s) { - ErrorHandler.logError( - e: e, - s: s, - data: request.toJson(), - ); - error = e.toString(); - } finally { - if (mounted) { - setState(() { - _loading = false; - }); - } -======= _transcriptionFuture = _fetchTranscription(); _loadingChoreoSubscription = TtsController.loadingChoreoStream.stream.listen((val) { @@ -169,39 +96,11 @@ class _PhoneticTranscriptionWidgetState if (mounted) setState(() => _isPlaying = false); }, ); ->>>>>>> Stashed changes } } @override Widget build(BuildContext context) { -<<<<<<< Updated upstream - if (error != null) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.error_outline, - color: Theme.of(context).colorScheme.error, - size: 16.0, - ), - const SizedBox(width: 8), - Text( - L10n.of(context).phoneticTranscriptionError, - style: Theme.of(context).textTheme.bodyLarge, - ), - ], - ); - } - - if (_loading || _response == null) { - return const Center(child: CircularProgressIndicator.adaptive()); - } - - return Text( - 'Phonetic transcription for "${widget.text}" in ${widget.l2}', - style: Theme.of(context).textTheme.bodyLarge, -======= return FutureBuilder( future: _transcriptionFuture, builder: (context, snapshot) { @@ -224,7 +123,7 @@ class _PhoneticTranscriptionWidgetState mainAxisSize: MainAxisSize.min, children: [ Text( - transcription.isNotEmpty ? transcription : widget.text, + "/${transcription.isNotEmpty ? transcription : widget.text}/", style: widget.style ?? Theme.of(context).textTheme.bodyMedium, ), @@ -253,7 +152,6 @@ class _PhoneticTranscriptionWidgetState ), ); }, ->>>>>>> Stashed changes ); } } From c4b582c93f37272c839b45cf08f8c4a36eddc0b5 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 16 Jun 2025 16:51:18 -0400 Subject: [PATCH 15/57] chore: add phonetic transcription to audio message toolbar --- .../phonetic_transcription_widget.dart | 10 +++-- .../toolbar/widgets/overlay_message.dart | 39 +++++++++++++++---- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index d55695dc2..0e7c69f74 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -122,10 +122,12 @@ class _PhoneticTranscriptionWidgetState child: Row( mainAxisSize: MainAxisSize.min, children: [ - Text( - "/${transcription.isNotEmpty ? transcription : widget.text}/", - style: - widget.style ?? Theme.of(context).textTheme.bodyMedium, + Flexible( + child: Text( + "/${transcription.isNotEmpty ? transcription : widget.text}/", + style: widget.style ?? + Theme.of(context).textTheme.bodyMedium, + ), ), const SizedBox(width: 8), Tooltip( diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index b6633b0c6..aa3300f3c 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -10,6 +10,9 @@ import 'package:fluffychat/pages/chat/events/message_content.dart'; import 'package:fluffychat/pages/chat/events/reply_content.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; +import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; +import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; +import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_widget.dart'; import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; @@ -170,14 +173,34 @@ class OverlayMessage extends StatelessWidget { ) : overlayController.transcription != null ? SingleChildScrollView( - child: Text( - overlayController.transcription!.transcript.text, - style: AppConfig.messageTextStyle( - event, - textColor, - ).copyWith( - fontStyle: FontStyle.italic, - ), + child: Column( + spacing: 8.0, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + overlayController + .transcription!.transcript.text, + style: AppConfig.messageTextStyle( + event, + textColor, + ).copyWith( + fontStyle: FontStyle.italic, + ), + ), + PhoneticTranscriptionWidget( + text: overlayController + .transcription!.transcript.text, + textLanguage: PLanguageStore.byLangCode( + pangeaMessageEvent! + .messageDisplayLangCode, + ) ?? + LanguageModel.unknown, + style: AppConfig.messageTextStyle( + event, + textColor, + ), + ), + ], ), ) : Row( From 5f535a9d95d9fc8c3e56bbdbbd53e1628074b369 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 16 Jun 2025 16:56:29 -0400 Subject: [PATCH 16/57] chore: remove background color from emoji reactions --- lib/pages/chat/events/message_reactions.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pages/chat/events/message_reactions.dart b/lib/pages/chat/events/message_reactions.dart index e34dad9a0..b3afbef60 100644 --- a/lib/pages/chat/events/message_reactions.dart +++ b/lib/pages/chat/events/message_reactions.dart @@ -158,7 +158,9 @@ class _Reaction extends StatelessWidget { borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), child: Container( decoration: BoxDecoration( - color: color, + // #Pangea + // color: color, + // Pangea# borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), ), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), From fdf30b3462d73e9f31f2ae073a53f2652b073308 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 17 Jun 2025 09:32:47 -0400 Subject: [PATCH 17/57] chore: update presence avatar when user ID changes, go right to knocking members filter from knocking users indicator --- lib/config/routes.dart | 3 ++ lib/l10n/intl_en.arb | 12 +++++- lib/pages/chat_members/chat_members.dart | 37 ++++++++++++++++++- .../widgets/knocking_users_indicator.dart | 8 ++-- .../widgets/leaderboard_participant_list.dart | 5 ++- lib/widgets/presence_builder.dart | 18 +++++++++ 6 files changed, 77 insertions(+), 6 deletions(-) diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 1b647b2ae..e7fb1d376 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -653,6 +653,9 @@ abstract class AppRoutes { state, ChatMembersPage( roomId: state.pathParameters['roomid']!, + // #Pangea + filter: state.uri.queryParameters['filter'], + // Pangea# ), ), redirect: loggedOutRedirect, diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 09c40f998..813eb9c76 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5017,5 +5017,15 @@ "newDirectMessage": "New direct message", "speakingExercisesTooltip": "Speaking practice", "noChatsFoundHereYet": "No chats found here yet", - "transcriptionFailed": "Failed to transcribe audio" + "transcriptionFailed": "Failed to transcribe audio", + "aUserIsKnocking": "1 user is requesting to join your space", + "usersAreKnocking": "{users} users are requesting to join your space", + "@usersAreKnocking": { + "type": "int", + "placeholders": { + "users": { + "type": "int" + } + } + } } \ No newline at end of file diff --git a/lib/pages/chat_members/chat_members.dart b/lib/pages/chat_members/chat_members.dart index 6376a9b2c..8fd3df8be 100644 --- a/lib/pages/chat_members/chat_members.dart +++ b/lib/pages/chat_members/chat_members.dart @@ -9,8 +9,18 @@ import 'chat_members_view.dart'; class ChatMembersPage extends StatefulWidget { final String roomId; + // #Pangea + final String? filter; + // Pangea# - const ChatMembersPage({required this.roomId, super.key}); + // #Pangea + // const ChatMembersPage({required this.roomId, super.key}); + const ChatMembersPage({ + required this.roomId, + this.filter, + super.key, + }); + // Pangea# @override State createState() => ChatMembersController(); @@ -24,6 +34,22 @@ class ChatMembersController extends State { final TextEditingController filterController = TextEditingController(); + // #Pangea + @override + void didUpdateWidget(ChatMembersPage oldWidget) { + super.didUpdateWidget(oldWidget); + // Update the membership filter if the widget's filter changes + if (oldWidget.filter != widget.filter) { + setState(() { + membershipFilter = Membership.values.firstWhere( + (membership) => membership.name == widget.filter, + orElse: () => Membership.join, + ); + }); + } + } + // Pangea# + void setMembershipFilter(Membership membership) { membershipFilter = membership; setFilter(); @@ -110,6 +136,15 @@ class ChatMembersController extends State { false, ) .listen(refreshMembers); + + // #Pangea + if (widget.filter != null) { + membershipFilter = Membership.values.firstWhere( + (membership) => membership.name == widget.filter, + orElse: () => Membership.join, + ); + } + // Pangea# } @override diff --git a/lib/pangea/spaces/widgets/knocking_users_indicator.dart b/lib/pangea/spaces/widgets/knocking_users_indicator.dart index 76cc30959..b40519584 100644 --- a/lib/pangea/spaces/widgets/knocking_users_indicator.dart +++ b/lib/pangea/spaces/widgets/knocking_users_indicator.dart @@ -7,6 +7,7 @@ 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/extensions/pangea_room_extension.dart'; import 'package:fluffychat/utils/stream_extension.dart'; @@ -89,15 +90,16 @@ class KnockingUsersIndicatorState extends State { Expanded( child: Text( _knockingUsers.length == 1 - ? "1 user is requesting to join your space" - : "${_knockingUsers.length} users are requesting to join your space", + ? L10n.of(context).aUserIsKnocking + : L10n.of(context) + .usersAreKnocking(_knockingUsers.length), style: Theme.of(context).textTheme.bodyMedium, ), ), ], ), onTap: () => context.push( - "/rooms/${widget.room.id}/details/members", + "/rooms/${widget.room.id}/details/members?filter=knock", ), ), ), diff --git a/lib/pangea/spaces/widgets/leaderboard_participant_list.dart b/lib/pangea/spaces/widgets/leaderboard_participant_list.dart index 347d12cd2..c9906cea9 100644 --- a/lib/pangea/spaces/widgets/leaderboard_participant_list.dart +++ b/lib/pangea/spaces/widgets/leaderboard_participant_list.dart @@ -48,7 +48,10 @@ class LeaderboardParticipantListState return LoadParticipantsUtil( space: widget.space, builder: (participantsLoader) { - final participants = participantsLoader.filteredParticipants(""); + final participants = participantsLoader + .filteredParticipants("") + .where((p) => p.membership == Membership.join) + .toList(); return AnimatedSize( duration: FluffyThemes.animationDuration, diff --git a/lib/widgets/presence_builder.dart b/lib/widgets/presence_builder.dart index 2f1a66d77..4332485ab 100644 --- a/lib/widgets/presence_builder.dart +++ b/lib/widgets/presence_builder.dart @@ -48,6 +48,24 @@ class _PresenceBuilderState extends State { } } + // #Pangea + @override + void didUpdateWidget(PresenceBuilder oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.userId == widget.userId) return; + + final client = widget.client ?? Matrix.of(context).client; + final userId = widget.userId; + if (userId != null) { + client.fetchCurrentPresence(userId).then(_updatePresence); + _sub?.cancel(); + _sub = client.onPresenceChanged.stream + .where((presence) => presence.userid == userId) + .listen(_updatePresence); + } + } + // Pangea# + @override void dispose() { _sub?.cancel(); From 9e9d9d3b66e669eb6bda9c1c9fead78e997da167 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 17 Jun 2025 10:21:57 -0400 Subject: [PATCH 18/57] chore: prevent getting stuck in chat members with no filters --- lib/pages/chat_members/chat_members.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/pages/chat_members/chat_members.dart b/lib/pages/chat_members/chat_members.dart index 8fd3df8be..971b538f4 100644 --- a/lib/pages/chat_members/chat_members.dart +++ b/lib/pages/chat_members/chat_members.dart @@ -105,6 +105,19 @@ class ChatMembersController extends State { if (!mounted) return; + // #Pangea + final availableFilters = (participants ?? []) + .map( + (p) => p.membership, + ) + .toSet(); + + if (availableFilters.length == 1 && + membershipFilter != availableFilters.first) { + membershipFilter = availableFilters.first; + } + // Pangea# + setState(() { members = participants; }); From 161fd570050154944dc8aa825f649879552e2b62 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 17 Jun 2025 10:41:00 -0400 Subject: [PATCH 19/57] chore: fix position of unread label in nav rail --- lib/pages/chat_list/navi_rail_item.dart | 68 ++++++++++++++----------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/lib/pages/chat_list/navi_rail_item.dart b/lib/pages/chat_list/navi_rail_item.dart index a68112cf8..492804381 100644 --- a/lib/pages/chat_list/navi_rail_item.dart +++ b/lib/pages/chat_list/navi_rail_item.dart @@ -89,39 +89,45 @@ class NaviRailItem extends StatelessWidget { // color: isSelected // ? theme.colorScheme.primaryContainer // : theme.colorScheme.surfaceContainerHigh, - child: Container( - alignment: Alignment.center, - decoration: BoxDecoration( - color: backgroundColor ?? - (isSelected - ? theme.colorScheme.primaryContainer - : theme.colorScheme.surfaceContainerHigh), - borderRadius: borderRadius, + child: UnreadRoomsBadge( + filter: unreadBadgeFilter ?? (_) => false, + badgePosition: BadgePosition.topEnd( + top: -4, + end: isColumnMode ? 8 : 4, ), - margin: EdgeInsets.symmetric( - horizontal: isColumnMode ? 16.0 : 12.0, - vertical: isColumnMode ? 8.0 : 6.0, - ), - // Pangea# - child: Tooltip( - message: toolTip, - child: InkWell( + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: backgroundColor ?? + (isSelected + ? theme.colorScheme.primaryContainer + : theme.colorScheme.surfaceContainerHigh), borderRadius: borderRadius, - onTap: onTap, - child: unreadBadgeFilter == null - ? icon - : UnreadRoomsBadge( - filter: unreadBadgeFilter, - badgePosition: BadgePosition.topEnd( - // #Pangea - // top: -12, - // end: -8, - top: -20, - end: -16, - // Pangea# - ), - child: icon, - ), + ), + margin: EdgeInsets.symmetric( + horizontal: isColumnMode ? 16.0 : 12.0, + vertical: isColumnMode ? 8.0 : 6.0, + ), + // Pangea# + child: Tooltip( + message: toolTip, + child: InkWell( + borderRadius: borderRadius, + onTap: onTap, + // #Pangea + child: icon, + // child: unreadBadgeFilter == null + // ? icon + // : UnreadRoomsBadge( + // filter: unreadBadgeFilter, + // badgePosition: BadgePosition.topEnd( + // top: -12, + // end: -8, + // ), + // child: icon, + // ), + // Pangea# + ), ), ), ), From 684a9bd16e2dbf231b87568df10b83fad28119eb Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 17 Jun 2025 10:49:32 -0400 Subject: [PATCH 20/57] chore: make text size match for matrix pill widget --- lib/pages/chat/events/html_message.dart | 10 ++++++++-- lib/pangea/toolbar/utils/token_rendering_util.dart | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index d7af1008f..ddfa041b3 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -407,7 +407,10 @@ class HtmlMessage extends StatelessWidget { avatar: user.avatarUrl, uri: href, outerContext: context, - fontSize: fontSize, + // #Pangea + // fontSize: fontSize, + fontSize: renderer.fontSize(context), + // Pangea# color: linkStyle.color, // #Pangea userId: user.id, @@ -428,7 +431,10 @@ class HtmlMessage extends StatelessWidget { avatar: room?.avatar, uri: href, outerContext: context, - fontSize: fontSize, + // #Pangea + // fontSize: fontSize, + fontSize: renderer.fontSize(context), + // Pangea# color: linkStyle.color, ), ); diff --git a/lib/pangea/toolbar/utils/token_rendering_util.dart b/lib/pangea/toolbar/utils/token_rendering_util.dart index d6d2cc27f..8efbeeedc 100644 --- a/lib/pangea/toolbar/utils/token_rendering_util.dart +++ b/lib/pangea/toolbar/utils/token_rendering_util.dart @@ -27,7 +27,7 @@ class TokenRenderingUtil { return readingAssistanceMode == ReadingAssistanceMode.transitionMode; } - double? _fontSize(BuildContext context) => showCenterStyling + double? fontSize(BuildContext context) => showCenterStyling ? overlayController != null && overlayController!.maxWidth > 600 ? Theme.of(context).textTheme.titleLarge?.fontSize : Theme.of(context).textTheme.bodyLarge?.fontSize @@ -38,14 +38,14 @@ class TokenRenderingUtil { Color? color, }) => existingStyle.copyWith( - fontSize: _fontSize(context), + fontSize: fontSize(context), decoration: TextDecoration.underline, decorationThickness: 4, decorationColor: color ?? Colors.white.withAlpha(0), ); double tokenTextWidthForContainer(BuildContext context, String text) { - final tokenSizeKey = "$text-${_fontSize(context)}"; + final tokenSizeKey = "$text-${fontSize(context)}"; if (_tokensWidthCache.containsKey(tokenSizeKey)) { return _tokensWidthCache[tokenSizeKey]!; } From 5447f3464e2ff4d4a8a7914191e3065362b7a645 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 17 Jun 2025 11:25:15 -0400 Subject: [PATCH 21/57] chore: some fixes for the timer --- lib/pages/chat/events/message.dart | 9 +- .../activities/activity_aware_builder.dart | 62 +++++++++++ .../activities/activity_state_event.dart | 4 +- lib/pangea/activities/countdown.dart | 26 +---- .../activities/pinned_activity_message.dart | 103 +++++++++--------- 5 files changed, 132 insertions(+), 72 deletions(-) create mode 100644 lib/pangea/activities/activity_aware_builder.dart diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index e98bb7554..0fd7e79bc 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -125,7 +125,14 @@ class Message extends StatelessWidget { } // #Pangea if (event.type == PangeaEventTypes.activityPlan) { - return ActivityStateEvent(event: event); + 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(); } // Pangea# return StateMessage(event); diff --git a/lib/pangea/activities/activity_aware_builder.dart b/lib/pangea/activities/activity_aware_builder.dart new file mode 100644 index 000000000..68eea54af --- /dev/null +++ b/lib/pangea/activities/activity_aware_builder.dart @@ -0,0 +1,62 @@ +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_state_event.dart b/lib/pangea/activities/activity_state_event.dart index 97715632e..1f7a250e5 100644 --- a/lib/pangea/activities/activity_state_event.dart +++ b/lib/pangea/activities/activity_state_event.dart @@ -26,7 +26,7 @@ class ActivityStateEvent extends StatefulWidget { } class ActivityStateEventState extends State { - late final Timer _timer; + Timer? _timer; @override void initState() { @@ -45,7 +45,7 @@ class ActivityStateEventState extends State { @override void dispose() { - _timer.cancel(); + _timer?.cancel(); super.dispose(); } diff --git a/lib/pangea/activities/countdown.dart b/lib/pangea/activities/countdown.dart index f0ed5e48d..17516cb23 100644 --- a/lib/pangea/activities/countdown.dart +++ b/lib/pangea/activities/countdown.dart @@ -22,7 +22,7 @@ class CountDown extends StatefulWidget { } class CountDownState extends State { - late Timer _timer; + Timer? _timer; @override void initState() { @@ -30,23 +30,11 @@ class CountDownState extends State { _timer = Timer.periodic(const Duration(seconds: 1), (_) { setState(() {}); }); - - // final now = DateTime.now(); - // final delay = widget.deadline?.difference(now); - - // if (delay != null && delay > Duration.zero) { - // _endTimer = Timer(delay, () { - // setState( - // () => setState(() {}), - // ); - // }); - // } } @override void dispose() { - _timer.cancel(); - // _endTimer.cancel(); + _timer?.cancel(); super.dispose(); } @@ -66,20 +54,18 @@ class CountDownState extends State { return parts.join(" "); } - Duration? get remainingTime { + Duration? get _remainingTime { if (widget.deadline == null) { return null; } final now = DateTime.now(); - return widget.deadline!.isAfter(now) - ? widget.deadline!.difference(now) - : Duration.zero; + return widget.deadline!.difference(now); } @override Widget build(BuildContext context) { - final remainingTime = this.remainingTime; + final remainingTime = _remainingTime; final durationString = _formatDuration(remainingTime ?? Duration.zero); return ConstrainedBox( @@ -98,7 +84,7 @@ class CountDownState extends State { Flexible( child: Text( remainingTime != null && - remainingTime > Duration.zero && + remainingTime >= Duration.zero && durationString != null ? durationString : L10n.of(context).duration, diff --git a/lib/pangea/activities/pinned_activity_message.dart b/lib/pangea/activities/pinned_activity_message.dart index 32554058e..8be5a492f 100644 --- a/lib/pangea/activities/pinned_activity_message.dart +++ b/lib/pangea/activities/pinned_activity_message.dart @@ -7,6 +7,7 @@ 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'; @@ -34,62 +35,66 @@ class PinnedActivityMessage extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - if (_activityPlan?.endAt == null || - _activityPlan!.endAt!.isBefore(DateTime.now())) { - return const SizedBox.shrink(); - } + 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), + 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, + ), + ), ); }, - ); - - 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, ), ), - ); - }, - 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, + onTap: _scrollToEvent, + ); + }, ); } } From 0a29e8b44867601df9843626172be337067daab3 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 17 Jun 2025 11:29:11 -0400 Subject: [PATCH 22/57] Applies padding on gold bar to background bar to keep it from sticking out --- .../progress_bar/progress_bar_background.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/pangea/analytics_summary/progress_bar/progress_bar_background.dart b/lib/pangea/analytics_summary/progress_bar/progress_bar_background.dart index 5c9dabef8..a24d8d7e7 100644 --- a/lib/pangea/analytics_summary/progress_bar/progress_bar_background.dart +++ b/lib/pangea/analytics_summary/progress_bar/progress_bar_background.dart @@ -13,14 +13,17 @@ class ProgressBarBackground extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - height: details.height, - width: details.totalWidth, - decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(AppConfig.borderRadius), + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 2), + child: Container( + height: details.height, + width: details.totalWidth, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all( + Radius.circular(AppConfig.borderRadius), + ), + color: details.borderColor.withAlpha(50), ), - color: details.borderColor.withAlpha(50), ), ); } From e9a601884ef02422c8aca304c56e039a5b425826 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 17 Jun 2025 12:28:51 -0400 Subject: [PATCH 23/57] chore: fix permission check in sendActivityPlan --- lib/pangea/extensions/room_events_extension.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/extensions/room_events_extension.dart b/lib/pangea/extensions/room_events_extension.dart index 7c5676bc3..e24427ef3 100644 --- a/lib/pangea/extensions/room_events_extension.dart +++ b/lib/pangea/extensions/room_events_extension.dart @@ -277,7 +277,7 @@ extension EventsRoomExtension on Room { }) async { BookmarkedActivitiesRepo.save(activity); - if (canSendDefaultStates) { + if (canChangeStateEvent(PangeaEventTypes.activityPlan)) { await client.setRoomStateWithKey( id, PangeaEventTypes.activityPlan, From 233e5162d15bd5821bc85aac85e443ef9a75e998 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 17 Jun 2025 13:39:46 -0400 Subject: [PATCH 24/57] chore: don't show match activities for single-word messages --- .../practice_activities/activity_type_enum.dart | 16 ++++++++++++++++ .../practice_activities/practice_selection.dart | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/lib/pangea/practice_activities/activity_type_enum.dart b/lib/pangea/practice_activities/activity_type_enum.dart index 2fe80c418..c58bdd4e2 100644 --- a/lib/pangea/practice_activities/activity_type_enum.dart +++ b/lib/pangea/practice_activities/activity_type_enum.dart @@ -190,4 +190,20 @@ extension ActivityTypeExtension on ActivityTypeEnum { return null; // TODO: Add to L10n } } + + /// The minimum number of tokens in a message for this activity type to be available. + /// Matching activities don't make sense for a single-word message. + int get minTokensForMatchActivity { + switch (this) { + case ActivityTypeEnum.wordMeaning: + case ActivityTypeEnum.lemmaId: + case ActivityTypeEnum.wordFocusListening: + return 2; + case ActivityTypeEnum.hiddenWordListening: + case ActivityTypeEnum.emoji: + case ActivityTypeEnum.morphId: + case ActivityTypeEnum.messageMeaning: + return 1; + } + } } diff --git a/lib/pangea/practice_activities/practice_selection.dart b/lib/pangea/practice_activities/practice_selection.dart index de2aa3709..e7b60be33 100644 --- a/lib/pangea/practice_activities/practice_selection.dart +++ b/lib/pangea/practice_activities/practice_selection.dart @@ -155,6 +155,11 @@ class PracticeSelection { return []; } + if (tokens.length < activityType.minTokensForMatchActivity) { + // if we only have one token, we don't need to do an emoji activity + return []; + } + return [ PracticeTarget( activityType: activityType, From 76b3f29ac270eaddc1652168c3d44deaae305f83 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 17 Jun 2025 14:20:45 -0400 Subject: [PATCH 25/57] chore: remove access toggles and join with code button from new space page --- lib/pages/new_group/new_group.dart | 17 ++--- lib/pages/new_group/new_group_view.dart | 87 ++++++++----------------- 2 files changed, 34 insertions(+), 70 deletions(-) diff --git a/lib/pages/new_group/new_group.dart b/lib/pages/new_group/new_group.dart index b918f532d..395804047 100644 --- a/lib/pages/new_group/new_group.dart +++ b/lib/pages/new_group/new_group.dart @@ -38,6 +38,8 @@ class NewGroupController extends State { TextEditingController nameController = TextEditingController(); // #Pangea + // bool publicGroup = false; + // bool groupCanBeFound = false; ActivityPlanModel? selectedActivity; Uint8List? selectedActivityImage; String? selectedActivityImageFilename; @@ -45,12 +47,8 @@ class NewGroupController extends State { final GlobalKey formKey = GlobalKey(); final FocusNode focusNode = FocusNode(); - bool requiredCodeToJoin = false; - // bool publicGroup = false; - bool get canSubmit => nameController.text.trim().isNotEmpty; // Pangea# - bool groupCanBeFound = false; Uint8List? avatar; @@ -71,7 +69,6 @@ class NewGroupController extends State { // #Pangea // void setPublicGroup(bool b) => // setState(() => publicGroup = groupCanBeFound = b); - void setRequireCode(bool b) => setState(() => requiredCodeToJoin = b); void setSelectedActivity( ActivityPlanModel? activity, @@ -103,7 +100,9 @@ class NewGroupController extends State { } // Pangea# - void setGroupCanBeFound(bool b) => setState(() => groupCanBeFound = b); + // #Pangea + // void setGroupCanBeFound(bool b) => setState(() => groupCanBeFound = b); + // Pangea# void selectPhoto() async { final photo = await selectFiles( @@ -230,10 +229,8 @@ class NewGroupController extends State { name: nameController.text, introChatName: L10n.of(context).introductions, announcementsChatName: L10n.of(context).announcements, - visibility: - groupCanBeFound ? sdk.Visibility.public : sdk.Visibility.private, - joinRules: - requiredCodeToJoin ? sdk.JoinRules.knock : sdk.JoinRules.public, + visibility: sdk.Visibility.private, + joinRules: sdk.JoinRules.knock, avatar: avatar, avatarUrl: avatarUrl, ); diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index c6c825c10..f07747ac2 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -4,7 +4,6 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/new_group/new_group.dart'; import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_carousel.dart'; -import 'package:fluffychat/pangea/spaces/utils/space_code.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; @@ -38,20 +37,6 @@ class NewGroupView extends StatelessWidget { : L10n.of(context).newChat, // Pangea# ), - actions: [ - if (controller.createGroupType == CreateGroupType.space) - TextButton( - onPressed: controller.loading - ? null - : () => SpaceCodeUtil.joinWithSpaceCodeDialog(context), - child: Text( - L10n.of(context).joinByCode, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - ), - ), - ), - ], ), body: MaxWidthBody( // #Pangea @@ -141,51 +126,33 @@ class NewGroupView extends StatelessWidget { ), const SizedBox(height: 16), // #Pangea - if (controller.createGroupType == CreateGroupType.space) - // Pangea# - SwitchListTile.adaptive( - contentPadding: const EdgeInsets.symmetric(horizontal: 32), - secondary: const Icon(Icons.public_outlined), - // #Pangea - // title: Text( - // controller.createGroupType == CreateGroupType.space - // ? L10n.of(context).spaceIsPublic - // : L10n.of(context).groupIsPublic, - // ), - title: Text(L10n.of(context).requireCodeToJoin), - // value: controller.publicGroup, - // onChanged: - // controller.loading ? null : controller.setPublicGroup, - value: controller.requiredCodeToJoin, - onChanged: - controller.loading ? null : controller.setRequireCode, - // Pangea# - ), - // #Pangea - if (controller.createGroupType == CreateGroupType.space) - // Pangea# - AnimatedSize( - duration: FluffyThemes.animationDuration, - curve: FluffyThemes.animationCurve, - child: - // #Pangea - // controller.publicGroup ? - // Pangea# - SwitchListTile.adaptive( - contentPadding: const EdgeInsets.symmetric(horizontal: 32), - secondary: const Icon(Icons.search_outlined), - // #Pangea - // title: Text(L10n.of(context).groupCanBeFoundViaSearch), - title: Text(L10n.of(context).canFindInSearch), - // Pangea# - value: controller.groupCanBeFound, - onChanged: - controller.loading ? null : controller.setGroupCanBeFound, - ), - // #Pangea - // : const SizedBox.shrink(), - // Pangea# - ), + // SwitchListTile.adaptive( + // contentPadding: const EdgeInsets.symmetric(horizontal: 32), + // secondary: const Icon(Icons.public_outlined), + // title: Text( + // controller.createGroupType == CreateGroupType.space + // ? L10n.of(context).spaceIsPublic + // : L10n.of(context).groupIsPublic, + // ), + // value: controller.publicGroup, + // onChanged: controller.loading ? null : controller.setPublicGroup, + // ), + // AnimatedSize( + // duration: FluffyThemes.animationDuration, + // curve: FluffyThemes.animationCurve, + // child: controller.publicGroup + // ? SwitchListTile.adaptive( + // contentPadding: + // const EdgeInsets.symmetric(horizontal: 32), + // secondary: const Icon(Icons.search_outlined), + // title: Text(L10n.of(context).groupCanBeFoundViaSearch), + // value: controller.groupCanBeFound, + // onChanged: controller.loading + // ? null + // : controller.setGroupCanBeFound, + // ) + // : const SizedBox.shrink(), + // ), // AnimatedSize( // duration: FluffyThemes.animationDuration, // curve: FluffyThemes.animationCurve, From d47dd3255e88972d655f74f4f4b1fdb22ca770aa Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 17 Jun 2025 14:41:44 -0400 Subject: [PATCH 26/57] chore: fix can't edit duration on firefox/safari --- .../activities/activity_duration_popup.dart | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/pangea/activities/activity_duration_popup.dart b/lib/pangea/activities/activity_duration_popup.dart index 4ce8a40a6..c6cbb56ab 100644 --- a/lib/pangea/activities/activity_duration_popup.dart +++ b/lib/pangea/activities/activity_duration_popup.dart @@ -141,22 +141,24 @@ class ActivityDurationPopupState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - spacing: 12.0, - children: [ - _DatePickerInput( - type: "d", - controller: _daysController, - ), - _DatePickerInput( - type: "h", - controller: _hoursController, - ), - _DatePickerInput( - type: "m", - controller: _minutesController, - ), - ], + 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, From 171fbbdd0dbcd98d65cf512c42ecf2f37f97fe8d Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 17 Jun 2025 15:01:47 -0400 Subject: [PATCH 27/57] chore: fix message token button vertical alignment --- lib/pages/chat/events/html_message.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index ddfa041b3..c34696a3e 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -313,7 +313,6 @@ class HtmlMessage extends StatelessWidget { ); return WidgetSpan( - alignment: PlaceholderAlignment.middle, child: CompositedTransformTarget( link: token != null && renderer.assignTokenKey ? MatrixState.pAnyState From 2acb30bd7f39c672a21426f0d013332639d97d46 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 10:07:50 -0400 Subject: [PATCH 28/57] chore: make TTS and full message audio stop each other to prevent overlapping audio --- .../toolbar/controllers/tts_controller.dart | 8 ++-- .../toolbar/widgets/select_mode_buttons.dart | 47 ++++++++++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/lib/pangea/toolbar/controllers/tts_controller.dart b/lib/pangea/toolbar/controllers/tts_controller.dart index 57c42ace4..32e575ae1 100644 --- a/lib/pangea/toolbar/controllers/tts_controller.dart +++ b/lib/pangea/toolbar/controllers/tts_controller.dart @@ -35,6 +35,8 @@ class TtsController { static final StreamController loadingChoreoStream = StreamController.broadcast(); + static final audioPlayer = AudioPlayer(); + static bool get _useAlternativeTTS { return PlatformInfos.isWindows; } @@ -120,6 +122,7 @@ class TtsController { // https://pub.dev/packages/flutter_tts final result = await (_useAlternativeTTS ? _alternativeTTS.stop() : _tts.stop()); + audioPlayer.stop(); if (!_useAlternativeTTS && result != 1) { ErrorHandler.logError( @@ -187,6 +190,8 @@ class TtsController { VoidCallback? onStop, }) async { chatController?.stopMediaStream.add(null); + MatrixState.pangeaController.matrixState.audioPlayer?.stop(); + await _setSpeakingLanguage(langCode); final enableTTS = MatrixState @@ -306,7 +311,6 @@ class TtsController { if (ttsRes == null) return; - final audioPlayer = AudioPlayer(); try { Logs().i('Speaking from choreo: $text, langCode: $langCode'); final audioContent = base64Decode(ttsRes.audioContent); @@ -326,8 +330,6 @@ class TtsController { 'text': text, }, ); - } finally { - await audioPlayer.dispose(); } } diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index 07f5d3722..7be70a778 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -18,6 +18,7 @@ import 'package:fluffychat/pangea/common/widgets/pressable_button.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; import 'package:fluffychat/pangea/events/models/representation_content_model.dart'; +import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -76,7 +77,6 @@ class SelectModeButtonsState extends State { SelectMode? _selectedMode; - final AudioPlayer _audioPlayer = AudioPlayer(); bool _isLoadingAudio = false; PangeaAudioFile? _audioBytes; File? _audioFile; @@ -93,17 +93,26 @@ class SelectModeButtonsState extends State { Completer? _transcriptionCompleter; + AudioPlayer? get _audioPlayer => Matrix.of(context).audioPlayer!; + @override void initState() { super.initState(); - _onPlayerStateChanged = _audioPlayer.playerStateStream.listen((state) { + + final matrix = Matrix.of(context); + matrix.audioPlayer?.dispose(); + matrix.audioPlayer = AudioPlayer(); + matrix.voiceMessageEventId.value = + widget.overlayController.pangeaMessageEvent?.eventId; + + _onPlayerStateChanged = _audioPlayer?.playerStateStream.listen((state) { if (state.processingState == ProcessingState.completed) { _updateMode(null); } setState(() {}); }); - _onAudioPositionChanged ??= _audioPlayer.positionStream.listen((state) { + _onAudioPositionChanged ??= _audioPlayer?.positionStream.listen((state) { if (_audioBytes != null) { widget.overlayController.highlightCurrentText( state.inMilliseconds, @@ -119,7 +128,10 @@ class SelectModeButtonsState extends State { @override void dispose() { - _audioPlayer.dispose(); + _audioPlayer?.dispose(); + Matrix.of(context).audioPlayer = null; + Matrix.of(context).voiceMessageEventId.value = null; + _onPlayerStateChanged?.cancel(); _onAudioPositionChanged?.cancel(); super.dispose(); @@ -150,8 +162,8 @@ class SelectModeButtonsState extends State { if (mode == null) { setState(() { - _audioPlayer.stop(); - _audioPlayer.seek(null); + _audioPlayer?.stop(); + _audioPlayer?.seek(null); _selectedMode = null; }); return; @@ -166,8 +178,8 @@ class SelectModeButtonsState extends State { _playAudio(); return; } else { - _audioPlayer.stop(); - _audioPlayer.seek(null); + _audioPlayer?.stop(); + _audioPlayer?.seek(null); } if (_selectedMode == SelectMode.practice) { @@ -232,11 +244,12 @@ class SelectModeButtonsState extends State { Future _playAudio() async { try { - if (_audioPlayer.playerState.playing) { - await _audioPlayer.pause(); + if (_audioPlayer != null && _audioPlayer!.playerState.playing) { + await _audioPlayer?.pause(); return; - } else if (_audioPlayer.position != Duration.zero) { - await _audioPlayer.play(); + } else if (_audioPlayer?.position != Duration.zero) { + TtsController.stop(); + await _audioPlayer?.play(); return; } @@ -247,16 +260,18 @@ class SelectModeButtonsState extends State { if (_audioBytes == null) return; if (_audioFile != null) { - await _audioPlayer.setFilePath(_audioFile!.path); + await _audioPlayer?.setFilePath(_audioFile!.path); } else { - await _audioPlayer.setAudioSource( + await _audioPlayer?.setAudioSource( BytesAudioSource( _audioBytes!.bytes, _audioBytes!.mimeType, ), ); } - _audioPlayer.play(); + + TtsController.stop(); + _audioPlayer?.play(); } catch (e, s) { setState(() => _audioError = e.toString()); ErrorHandler.logError( @@ -426,7 +441,7 @@ class SelectModeButtonsState extends State { if (mode == SelectMode.audio) { return Icon( - _audioPlayer.playerState.playing == true + _audioPlayer?.playerState.playing == true ? Icons.pause_outlined : Icons.volume_up, size: 20, From 27b70f9a769671bc2a7190206512bfee6dd0cdf6 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 10:27:45 -0400 Subject: [PATCH 29/57] chore: if space is selected, only show chats within that space in activity chat selelction dialog --- .../activity_suggestions/activity_room_selection.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pangea/activity_suggestions/activity_room_selection.dart b/lib/pangea/activity_suggestions/activity_room_selection.dart index 4ca587ce5..d0d91a78c 100644 --- a/lib/pangea/activity_suggestions/activity_room_selection.dart +++ b/lib/pangea/activity_suggestions/activity_room_selection.dart @@ -69,6 +69,13 @@ class ActivityRoomSelectionState extends State { return a.name.toLowerCase().compareTo(b.name.toLowerCase()); }); + final room = widget.controller.room; + if (room != null && room.isSpace) { + _launchableRooms = _launchableRooms.where((r) { + return room.spaceChildren.any((child) => child.roomId == r.id); + }).toList(); + } + _hasBotDM = Matrix.of(context).client.rooms.any((room) { if (room.isDirectChat && room.directChatMatrixID == BotName.byEnvironment) { From 16db460ae24e32a1c8a0cf226d3e8cd2a8c3e883 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 10:43:00 -0400 Subject: [PATCH 30/57] chore: in lemma constructs button, don't execute onTap if no points --- lib/pangea/lemmas/construct_xp_widget.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pangea/lemmas/construct_xp_widget.dart b/lib/pangea/lemmas/construct_xp_widget.dart index 34a73a92c..22bb38d18 100644 --- a/lib/pangea/lemmas/construct_xp_widget.dart +++ b/lib/pangea/lemmas/construct_xp_widget.dart @@ -91,7 +91,7 @@ class ConstructXpWidgetState extends State Stream get stream => MatrixState.pangeaController.getAnalytics.analyticsStream.stream; - Widget get svg => constructLemmaCategory?.icon() ?? const SizedBox(); + Widget? get svg => constructLemmaCategory?.icon(); @override void dispose() { @@ -106,9 +106,10 @@ class ConstructXpWidgetState extends State width: widget.size, height: widget.size, child: GestureDetector( - onTap: widget.onTap, + onTap: svg != null ? widget.onTap : null, child: MouseRegion( - cursor: SystemMouseCursors.click, + cursor: + svg != null ? SystemMouseCursors.click : SystemMouseCursors.basic, child: Stack( alignment: Alignment.center, children: [ From ecac8fd48f07fd1b09142eb26806803a163fb5d3 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 18 Jun 2025 11:04:33 -0400 Subject: [PATCH 31/57] Limit size of chat selection popup title --- lib/pages/chat_list/chat_list.dart | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 5756cf402..87a7ec429 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, @@ -753,10 +754,19 @@ class ChatListController extends State // Pangea# ), const SizedBox(width: 12), - Text( - displayname, - style: - TextStyle(color: Theme.of(context).colorScheme.onSurface), + // #Pangea + Flexible( + child: + // Pangea# + Text( + displayname, + style: + TextStyle(color: Theme.of(context).colorScheme.onSurface), + // #Pangea + maxLines: 2, + overflow: TextOverflow.ellipsis, + // Pangea# + ), ), ], ), From 575b928edef3efba86cdd3619f6d5ac51475f1d4 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 11:06:34 -0400 Subject: [PATCH 32/57] chore: fix mention font size --- lib/pages/chat/events/html_message.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index c34696a3e..06b203f0f 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -408,7 +408,7 @@ class HtmlMessage extends StatelessWidget { outerContext: context, // #Pangea // fontSize: fontSize, - fontSize: renderer.fontSize(context), + fontSize: renderer.fontSize(context) ?? fontSize, // Pangea# color: linkStyle.color, // #Pangea @@ -432,7 +432,7 @@ class HtmlMessage extends StatelessWidget { outerContext: context, // #Pangea // fontSize: fontSize, - fontSize: renderer.fontSize(context), + fontSize: renderer.fontSize(context) ?? fontSize, // Pangea# color: linkStyle.color, ), From 7b7a599086aa6e6eac5bbc88d0d2bbd613229f52 Mon Sep 17 00:00:00 2001 From: avashilling <165050625+avashilling@users.noreply.github.com> Date: Wed, 18 Jun 2025 11:20:06 -0400 Subject: [PATCH 33/57] chore: make onboarding text slightly smaller --- lib/pangea/onboarding/onboarding_complete.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/pangea/onboarding/onboarding_complete.dart b/lib/pangea/onboarding/onboarding_complete.dart index 796602d28..901e1161e 100644 --- a/lib/pangea/onboarding/onboarding_complete.dart +++ b/lib/pangea/onboarding/onboarding_complete.dart @@ -1,12 +1,10 @@ -import 'package:flutter/material.dart'; - import 'package:cached_network_image/cached_network_image.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/onboarding/onboarding.dart'; import 'package:fluffychat/pangea/onboarding/onboarding_constants.dart'; +import 'package:flutter/material.dart'; class OnboardingComplete extends StatelessWidget { final OnboardingController controller; @@ -41,7 +39,7 @@ class OnboardingComplete extends StatelessWidget { L10n.of(context).getStartedComplete, textAlign: TextAlign.center, style: const TextStyle( - fontSize: 32.0, + fontSize: 14, ), ) : Column( From 68109aab9c784d1991816232e84a8b9df92651ff Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 11:28:53 -0400 Subject: [PATCH 34/57] small update --- lib/pages/chat_list/chat_list.dart | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 87a7ec429..024b521d6 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -744,28 +744,23 @@ class ChatListController extends State value: ChatContextAction.open, child: Row( mainAxisSize: MainAxisSize.min, + spacing: 12.0, children: [ Avatar( mxContent: room.avatar, - size: Avatar.defaultSize / 2, name: displayname, // #Pangea userId: room.directChatMatrixID, // Pangea# ), - const SizedBox(width: 12), - // #Pangea - Flexible( - child: - // Pangea# - Text( + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 128), + child: Text( displayname, style: TextStyle(color: Theme.of(context).colorScheme.onSurface), - // #Pangea maxLines: 2, overflow: TextOverflow.ellipsis, - // Pangea# ), ), ], From 7000adadc9c425044268494374cd41ba285efe7b Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 11:32:59 -0400 Subject: [PATCH 35/57] formatting --- lib/pangea/onboarding/onboarding_complete.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pangea/onboarding/onboarding_complete.dart b/lib/pangea/onboarding/onboarding_complete.dart index 901e1161e..fcef4d8ab 100644 --- a/lib/pangea/onboarding/onboarding_complete.dart +++ b/lib/pangea/onboarding/onboarding_complete.dart @@ -1,10 +1,12 @@ +import 'package:flutter/material.dart'; + import 'package:cached_network_image/cached_network_image.dart'; + import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/onboarding/onboarding.dart'; import 'package:fluffychat/pangea/onboarding/onboarding_constants.dart'; -import 'package:flutter/material.dart'; class OnboardingComplete extends StatelessWidget { final OnboardingController controller; From 312c1389667f88ff633098b110111f1a1c840d87 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 12:44:47 -0400 Subject: [PATCH 36/57] chore: show full userIds in invitation page --- lib/pages/chat_list/chat_list.dart | 1 - .../invitation_selection_view.dart | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 024b521d6..78c5fecb7 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/pages/invitation_selection/invitation_selection_view.dart b/lib/pages/invitation_selection/invitation_selection_view.dart index c59817b8c..59c6f6ad0 100644 --- a/lib/pages/invitation_selection/invitation_selection_view.dart +++ b/lib/pages/invitation_selection/invitation_selection_view.dart @@ -345,7 +345,20 @@ class _InviteContactListTile extends StatelessWidget { // color: theme.colorScheme.secondary, // ), // ), - subtitle: LevelDisplayName(userId: profile.userId), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // https://github.com/pangeachat/client/issues/3047 + const SizedBox(height: 2.0), + Text( + profile.userId, + style: const TextStyle( + fontSize: 12.0, + ), + ), + LevelDisplayName(userId: profile.userId), + ], + ), // Pangea# trailing: TextButton.icon( onPressed: isMember ? null : onTap, From ab7df42fbf24da0d7e2104bcf67eda841c7d9622 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 13:06:15 -0400 Subject: [PATCH 37/57] chore: visually distinguish between pinned and unpinned events in overlay header --- lib/pages/chat/chat_view.dart | 4 +++- lib/pangea/toolbar/widgets/overlay_header.dart | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 8840a2bf4..2f90e7f59 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -191,7 +191,9 @@ class ChatView extends StatelessWidget { appbarBottomHeight += ChatAppBarListTile.fixedHeight; } // #Pangea - if (controller.room.activityPlan != null) { + if (controller.room.activityPlan != null && + controller.room.activityPlan!.endAt != null && + controller.room.activityPlan!.endAt!.isAfter(DateTime.now())) { appbarBottomHeight += ChatAppBarListTile.fixedHeight; } // Pangea# diff --git a/lib/pangea/toolbar/widgets/overlay_header.dart b/lib/pangea/toolbar/widgets/overlay_header.dart index cb4db5d21..ff85e6b0b 100644 --- a/lib/pangea/toolbar/widgets/overlay_header.dart +++ b/lib/pangea/toolbar/widgets/overlay_header.dart @@ -36,6 +36,9 @@ class OverlayHeaderState extends State { Widget build(BuildContext context) { final l10n = L10n.of(context); final theme = Theme.of(context); + final pinned = controller.room.pinnedEventIds.contains( + controller.selectedEvents.first.eventId, + ); return Container( padding: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( @@ -102,9 +105,11 @@ class OverlayHeaderState extends State { ), if (controller.canPinSelectedEvents) IconButton( - icon: const Icon(Icons.push_pin_outlined), + icon: pinned + ? const Icon(Icons.push_pin) + : const Icon(Icons.push_pin_outlined), onPressed: controller.pinEvent, - tooltip: l10n.pinMessage, + tooltip: pinned ? l10n.unpin : l10n.pinMessage, color: theme.colorScheme.primary, ), if (controller.canEditSelectedEvents && From 17420f28d0b40fc5f5ee662e8e20ef5f589491de Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 13:33:38 -0400 Subject: [PATCH 38/57] chore: show loading indicator and error message in transcription --- lib/l10n/intl_en.arb | 3 +- .../phonetic_transcription_widget.dart | 156 ++++++++++-------- 2 files changed, 91 insertions(+), 68 deletions(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index b85b940fe..00ebd337f 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5031,5 +5031,6 @@ "type": "int" } } - } + }, + "failedToFetchTranscription": "Failed to fetch transcription" } \ No newline at end of file diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index 0e7c69f74..e6c91bad8 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -9,6 +9,7 @@ import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_repo.dart'; import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart'; import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; +import 'package:fluffychat/widgets/hover_builder.dart'; import 'package:fluffychat/widgets/matrix.dart'; class PhoneticTranscriptionWidget extends StatefulWidget { @@ -32,51 +33,61 @@ class PhoneticTranscriptionWidget extends StatefulWidget { class _PhoneticTranscriptionWidgetState extends State { - late Future _transcriptionFuture; - bool _hovering = false; bool _isPlaying = false; bool _isLoading = false; - late final StreamSubscription _loadingChoreoSubscription; + Object? _error; + + String? _transcription; @override void initState() { super.initState(); - _transcriptionFuture = _fetchTranscription(); - _loadingChoreoSubscription = - TtsController.loadingChoreoStream.stream.listen((val) { - if (mounted) setState(() => _isLoading = val); - }); + _fetchTranscription(); } - @override - void dispose() { - TtsController.stop(); - _loadingChoreoSubscription.cancel(); - super.dispose(); - } + Future _fetchTranscription() async { + try { + setState(() { + _isLoading = true; + _error = null; + _transcription = null; + }); - Future _fetchTranscription() async { - if (MatrixState.pangeaController.languageController.userL1 == null) { + if (MatrixState.pangeaController.languageController.userL1 == null) { + ErrorHandler.logError( + e: Exception('User L1 is not set'), + data: { + 'text': widget.text, + 'textLanguageCode': widget.textLanguage.langCode, + }, + ); + _error = Exception('User L1 is not set'); + return; + } + final req = PhoneticTranscriptionRequest( + arc: LanguageArc( + l1: MatrixState.pangeaController.languageController.userL1!, + l2: widget.textLanguage, + ), + content: PangeaTokenText.fromString(widget.text), + // arc can be omitted for default empty map + ); + final res = await PhoneticTranscriptionRepo.get(req); + _transcription = res.phoneticTranscriptionResult.phoneticTranscription + .first.phoneticL1Transcription.content; + } catch (e, s) { + _error = e; ErrorHandler.logError( - e: Exception('User L1 is not set'), + e: e, + s: s, data: { 'text': widget.text, 'textLanguageCode': widget.textLanguage.langCode, }, ); - return widget.text; // Fallback to original text if no L1 is set + } finally { + if (mounted) setState(() => _isLoading = false); } - final req = PhoneticTranscriptionRequest( - arc: LanguageArc( - l1: MatrixState.pangeaController.languageController.userL1!, - l2: widget.textLanguage, - ), - content: PangeaTokenText.fromString(widget.text), - // arc can be omitted for default empty map - ); - final res = await PhoneticTranscriptionRepo.get(req); - return res.phoneticTranscriptionResult.phoneticTranscription.first - .phoneticL1Transcription.content; } Future _handleAudioTap(BuildContext context) async { @@ -101,55 +112,66 @@ class _PhoneticTranscriptionWidgetState @override Widget build(BuildContext context) { - return FutureBuilder( - future: _transcriptionFuture, - builder: (context, snapshot) { - final transcription = snapshot.data ?? ''; - return MouseRegion( - onEnter: (_) => setState(() => _hovering = true), - onExit: (_) => setState(() => _hovering = false), - child: GestureDetector( - onTap: () => _handleAudioTap(context), - child: AnimatedContainer( - duration: const Duration(milliseconds: 150), - decoration: BoxDecoration( - color: _hovering - ? Colors.grey.withAlpha((0.2 * 255).round()) - : Colors.transparent, - borderRadius: BorderRadius.circular(6), - ), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ + return HoverBuilder( + builder: (context, hovering) { + return GestureDetector( + onTap: () => _handleAudioTap(context), + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + decoration: BoxDecoration( + color: hovering + ? Colors.grey.withAlpha((0.2 * 255).round()) + : Colors.transparent, + borderRadius: BorderRadius.circular(6), + ), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (_error != null) + Row( + spacing: 8.0, + children: [ + Icon( + Icons.error_outline, + size: widget.iconSize ?? 24, + color: Theme.of(context).colorScheme.error, + ), + Text( + L10n.of(context).failedToFetchTranscription, + style: widget.style, + ), + ], + ) + else if (_isLoading || _transcription == null) + const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator.adaptive(), + ) + else Flexible( child: Text( - "/${transcription.isNotEmpty ? transcription : widget.text}/", + "/$_transcription/", style: widget.style ?? Theme.of(context).textTheme.bodyMedium, ), ), - const SizedBox(width: 8), + const SizedBox(width: 8), + if (_transcription != null && _error == null) Tooltip( message: _isPlaying ? L10n.of(context).stop : L10n.of(context).playAudio, - child: _isLoading - ? const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 3), - ) - : Icon( - _isPlaying ? Icons.pause_outlined : Icons.volume_up, - size: widget.iconSize ?? 24, - color: _isPlaying - ? Theme.of(context).colorScheme.primary - : Theme.of(context).iconTheme.color, - ), + child: Icon( + _isPlaying ? Icons.pause_outlined : Icons.volume_up, + size: widget.iconSize ?? 24, + color: _isPlaying + ? Theme.of(context).colorScheme.primary + : Theme.of(context).iconTheme.color, + ), ), - ], - ), + ], ), ), ); From 9dce1a856d710909cf67026f3262706a6d2b707c Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 18 Jun 2025 13:45:56 -0400 Subject: [PATCH 39/57] Remove unpin tooltip/action when user does not have permission --- lib/pages/chat/pinned_events.dart | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index ef9dd29a1..fe1421eed 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -79,10 +79,18 @@ class PinnedEvents extends StatelessWidget { iconSize: 18, color: theme.colorScheme.onSurfaceVariant, icon: const Icon(Icons.push_pin), - tooltip: L10n.of(context).unpin, - onPressed: controller.room.canSendEvent(EventTypes.RoomPinnedEvents) - ? () => controller.unpinEvent(event!.eventId) - : null, + // #Pangea + // tooltip: L10n.of(context).unpin, + tooltip: + controller.room.canChangeStateEvent(EventTypes.RoomPinnedEvents) + ? L10n.of(context).unpin + : null, + // onPressed: controller.room.canSendEvent(EventTypes.RoomPinnedEvents) + onPressed: + controller.room.canChangeStateEvent(EventTypes.RoomPinnedEvents) + // Pangea# + ? () => controller.unpinEvent(event!.eventId) + : null, ), onTap: () => _displayPinnedEventsDialog(context), ); From 6c118ba88fe831d389fefca736f2918330fd2073 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 14:06:43 -0400 Subject: [PATCH 40/57] chore: exlude reply content from HTML token search --- lib/pages/chat/events/html_message.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 06b203f0f..268abbcac 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -185,6 +185,23 @@ class HtmlMessage extends StatelessWidget { result.add(html.substring(lastEnd)); // Remaining text after last tag } + final replyTagIndex = result.indexWhere( + (string) => string.contains(''), + ); + if (replyTagIndex != -1) { + final closingReplyTagIndex = result.indexWhere( + (string) => string.contains(''), + replyTagIndex, + ); + if (closingReplyTagIndex != -1) { + result.replaceRange( + replyTagIndex, + closingReplyTagIndex + 1, + [result.sublist(replyTagIndex, closingReplyTagIndex + 1).join()], + ); + } + } + for (final PangeaToken token in tokens ?? []) { final String tokenText = token.text.content; final substringIndex = result.indexWhere( From 531d4e927d3fc6b203bf1549f3765ed05c26fec0 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 14:22:56 -0400 Subject: [PATCH 41/57] build: remove unused github actions --- .github/dependabot.yml | 42 ++--- .github/workflows/auto_pull_request.yaml | 41 ----- .github/workflows/build-ios.yml | 79 -------- .github/workflows/dart_format.yaml | 39 ---- .github/workflows/integrate.yaml | 200 ++++++++++----------- .github/workflows/manual.yml | 11 +- .github/workflows/matrix_notification.yaml | 26 --- .github/workflows/old.yml | 44 ----- .github/workflows/release.yaml | 63 +++---- .github/workflows/upload-release-ios.yml | 39 ---- 10 files changed, 155 insertions(+), 429 deletions(-) delete mode 100644 .github/workflows/auto_pull_request.yaml delete mode 100644 .github/workflows/build-ios.yml delete mode 100644 .github/workflows/dart_format.yaml delete mode 100644 .github/workflows/matrix_notification.yaml delete mode 100644 .github/workflows/old.yml delete mode 100644 .github/workflows/upload-release-ios.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2b993ea05..7e75afa95 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,20 +1,22 @@ -version: 2 -updates: - - package-ecosystem: "pub" - directory: "/" - schedule: - interval: "daily" - allow: - - dependency-name: "*" - commit-message: - prefix: "build: " - include: "scope" - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" - allow: - - dependency-name: "*" - commit-message: - prefix: "build: " - include: "scope" +# #Pangea +# version: 2 +# updates: +# - package-ecosystem: "pub" +# directory: "/" +# schedule: +# interval: "daily" +# allow: +# - dependency-name: "*" +# commit-message: +# prefix: "build: " +# include: "scope" +# - package-ecosystem: "github-actions" +# directory: "/" +# schedule: +# interval: "daily" +# allow: +# - dependency-name: "*" +# commit-message: +# prefix: "build: " +# include: "scope" +# Pangea# diff --git a/.github/workflows/auto_pull_request.yaml b/.github/workflows/auto_pull_request.yaml deleted file mode 100644 index cf6bb228c..000000000 --- a/.github/workflows/auto_pull_request.yaml +++ /dev/null @@ -1,41 +0,0 @@ -name: Auto Pull Request - -#on: -# schedule: -# - cron: '0 0 * * 0' # Run at midnight (00:00) every Sunday - -on: - push: - branches: - - auto-pr # Change this to match your main branch name - -jobs: - auto_pull_request: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Test - run: echo ${{ github.head_ref }}.${{ github.sha }} - - - name: Pull changes from FluffyChat - run: | - git config --global user.email "${{ vars.CI_EMAIL }}" - git config --global user.name "${{ vars.CI_USERNAME }}" - git remote add fluffychat https://github.com/krille-chan/fluffychat - git fetch fluffychat main - git merge --no-edit fluffychat/main --allow-unrelated-histories - - - name: Push changes - run: | - git push origin HEAD:main-update-fluffy-automatic - - - name: Create Pull Request - uses: peter-evans/create-pull-request@v3 - with: - token: ${{ secrets.GH_TOKEN }} - title: Updated fork with new fluffy changes - body: | - This is an automatic PR created by GitHub Actions. - branch: main diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml deleted file mode 100644 index aa7a13d70..000000000 --- a/.github/workflows/build-ios.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: build-ios -on: - workflow_call: - inputs: - screenshot: - type: string - required: true - ipa: - description: 'Run IPA build' - type: string - required: true - workflow_dispatch: - inputs: - screenshot: - description: 'Run screenshot build' - type: choice - options: ['true', 'false'] - required: true - ipa: - description: 'Run IPA build' - type: choice - options: ['true', 'false'] - required: true - - -jobs: - build-ios: - runs-on: macos-latest - timeout-minutes: 20 - defaults: - run: - working-directory: ios - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - run: 'echo "$API_KEY" | base64 --decode > AuthKey.p8' - shell: bash - env: - API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }} - - - run: bundle install - - - run: bundle exec fastlane versioning - - - name: Flutter - uses: subosito/flutter-action@v2 - with: - cache: true - cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" - - run: flutter build ios --simulator --target=integration_test/screenshot_test.dart - if: ${{ inputs.screenshot == 'true' }} - - - name: Archive integration ipa - if: ${{ inputs.screenshot == 'true' }} - uses: actions/upload-artifact@v4 - with: - name: app-simulator-build - path: build/ios/iphonesimulator/Runner.app - if-no-files-found: error - retention-days: 3 - - # Build ios Release - - run: flutter build ios --release --config-only --no-codesign --target=lib/main.dart - if: ${{ inputs.ipa == 'true' }} - - - run: bundle exec fastlane build - if: ${{ inputs.ipa == 'true' }} - - - name: Archive ipa - if: ${{ inputs.ipa == 'true' }} - uses: actions/upload-artifact@v4 - with: - name: Runner.ipa - path: ios/Runner.ipa - if-no-files-found: error - retention-days: 3 \ No newline at end of file diff --git a/.github/workflows/dart_format.yaml b/.github/workflows/dart_format.yaml deleted file mode 100644 index 5e1a142c5..000000000 --- a/.github/workflows/dart_format.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# name: Dart Code Formatter - -# on: -# pull_request: -# push: -# branches: main - -# jobs: -# format: -# runs-on: ubuntu-latest - -# steps: -# - name: Checkout code -# uses: actions/checkout@v3 -# with: -# ref: ${{ github.head_ref }} - -# - run: cat .github/workflows/versions.env >> $GITHUB_ENV -# - uses: subosito/flutter-action@v2 -# with: -# flutter-version: ${{ env.FLUTTER_VERSION }} -# cache: true - -# - name: Auto-format Dart code -# run: | -# dart format lib/ test/ -# dart run import_sorter:main --no-comments -# if ! git diff --exit-code; then -# git config user.name "github-actions[bot]" -# git config user.email "41898282+github-actions[bot]@users.noreply.github.com" -# git add . -# git commit -m "generated" -# git push -# fi - -# - name: Check for unformatted files -# if: ${{ failure() }} -# run: | -# echo "Code was formatted. Please verify the changes in the PR." diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index c4307fadf..dc4df96cf 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -1,107 +1,105 @@ -name: Pull Request Workflow +# #Pangea +# name: Pull Request Workflow -on: - pull_request: - merge_group: +# on: +# pull_request: +# merge_group: -jobs: - code_tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: ./scripts/generate-locale-config.sh - - run: git diff --exit-code - - run: cat .github/workflows/versions.env >> $GITHUB_ENV - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.FLUTTER_VERSION }} - cache: true - - run: flutter pub get - - run: flutter gen-l10n - - name: Check formatting - run: dart format lib/ test/ --set-exit-if-changed - - name: Check import formatting - run: dart run import_sorter:main --no-comments --exit-if-changed - - name: Check license compliance - run: dart run license_checker check-licenses -c licenses.yaml --problematic - - run: flutter analyze - # #Pangea - Commented out the following lines, we already have fcm enabled by default - # - name: Apply google services patch - # run: git apply ./scripts/enable-android-google-services.patch - # Pangea# - - run: flutter analyze - - run: flutter test +# jobs: +# code_tests: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - run: ./scripts/generate-locale-config.sh +# - run: git diff --exit-code +# - run: cat .github/workflows/versions.env >> $GITHUB_ENV +# - uses: subosito/flutter-action@v2 +# with: +# flutter-version: ${{ env.FLUTTER_VERSION }} +# cache: true +# - run: flutter pub get +# - run: flutter gen-l10n +# - name: Check formatting +# run: dart format lib/ test/ --set-exit-if-changed +# - name: Check import formatting +# run: dart run import_sorter:main --no-comments --exit-if-changed +# - name: Check license compliance +# run: dart run license_checker check-licenses -c licenses.yaml --problematic +# - run: flutter analyze +# - name: Apply google services patch +# run: git apply ./scripts/enable-android-google-services.patch +# - run: flutter analyze +# - run: flutter test - build_apk: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: cat .github/workflows/versions.env >> $GITHUB_ENV - - uses: actions/setup-java@v4 - with: - java-version: ${{ env.JAVA_VERSION }} - distribution: "zulu" - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.FLUTTER_VERSION }} - cache: false - - run: flutter pub get - - name: Free Disk Space (Ubuntu) - uses: jlumbroso/free-disk-space@main - with: - # this might remove tools that are actually needed, - # if set to "true" but frees about 6 GB - tool-cache: false - android: false - - run: flutter build apk --debug +# build_apk: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - run: cat .github/workflows/versions.env >> $GITHUB_ENV +# - uses: actions/setup-java@v4 +# with: +# java-version: ${{ env.JAVA_VERSION }} +# distribution: "zulu" +# - uses: subosito/flutter-action@v2 +# with: +# flutter-version: ${{ env.FLUTTER_VERSION }} +# cache: false +# - run: flutter pub get +# - name: Free Disk Space (Ubuntu) +# uses: jlumbroso/free-disk-space@main +# with: +# # this might remove tools that are actually needed, +# # if set to "true" but frees about 6 GB +# tool-cache: false +# android: false +# - run: flutter build apk --debug - build_web: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: cat .github/workflows/versions.env >> $GITHUB_ENV - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.FLUTTER_VERSION }} - cache: false - - run: flutter pub get - - name: Prepare web - run: ./scripts/prepare-web.sh - - run: flutter build web +# build_web: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v4 +# - run: cat .github/workflows/versions.env >> $GITHUB_ENV +# - uses: subosito/flutter-action@v2 +# with: +# flutter-version: ${{ env.FLUTTER_VERSION }} +# cache: false +# - run: flutter pub get +# - name: Prepare web +# run: ./scripts/prepare-web.sh +# - run: flutter build web - # #Pangea - # commented out because we do not build Pangea Chat to linux - # build_debug_linux: - # strategy: - # matrix: - # arch: [ x64, arm64 ] - # runs-on: ${{ matrix.arch == 'arm64' && 'self-hosted' || 'ubuntu-latest'}} - # steps: - # - uses: actions/checkout@v4 - # - run: cat .github/workflows/versions.env >> $GITHUB_ENV - # - name: Install dependencies - # run: sudo apt-get update && sudo apt-get install git wget curl clang cmake ninja-build pkg-config libgtk-3-dev libblkid-dev liblzma-dev libjsoncpp-dev cmake-data libsecret-1-dev libsecret-1-0 librhash0 libssl-dev libwebkit2gtk-4.1-dev -y - # - name: Install Flutter - # run: | - # git clone --branch ${{ env.FLUTTER_VERSION }} https://github.com/flutter/flutter.git - # ./flutter/bin/flutter doctor - # - run: ./flutter/bin/flutter pub get - # - run: ./flutter/bin/flutter build linux --target-platform linux-${{ matrix.arch }} - # Pangea# +# commented out because we do not build Pangea Chat to linux +# build_debug_linux: +# strategy: +# matrix: +# arch: [ x64, arm64 ] +# runs-on: ${{ matrix.arch == 'arm64' && 'self-hosted' || 'ubuntu-latest'}} +# steps: +# - uses: actions/checkout@v4 +# - run: cat .github/workflows/versions.env >> $GITHUB_ENV +# - name: Install dependencies +# run: sudo apt-get update && sudo apt-get install git wget curl clang cmake ninja-build pkg-config libgtk-3-dev libblkid-dev liblzma-dev libjsoncpp-dev cmake-data libsecret-1-dev libsecret-1-0 librhash0 libssl-dev libwebkit2gtk-4.1-dev -y +# - name: Install Flutter +# run: | +# git clone --branch ${{ env.FLUTTER_VERSION }} https://github.com/flutter/flutter.git +# ./flutter/bin/flutter doctor +# - run: ./flutter/bin/flutter pub get +# - run: ./flutter/bin/flutter build linux --target-platform linux-${{ matrix.arch }} - build_debug_ios: - runs-on: macos-15 - steps: - - uses: actions/checkout@v4 - - run: cat .github/workflows/versions.env >> $GITHUB_ENV - - uses: subosito/flutter-action@v2 - with: - flutter-version: ${{ env.FLUTTER_VERSION }} - cache: true - - name: Setup Xcode version - uses: maxim-lobanov/setup-xcode@v1.6.0 - with: - xcode-version: latest - - run: brew install sqlcipher - - run: flutter pub get - - run: flutter build ipa --no-codesign +# build_debug_ios: +# runs-on: macos-15 +# steps: +# - uses: actions/checkout@v4 +# - run: cat .github/workflows/versions.env >> $GITHUB_ENV +# - uses: subosito/flutter-action@v2 +# with: +# flutter-version: ${{ env.FLUTTER_VERSION }} +# cache: true +# - name: Setup Xcode version +# uses: maxim-lobanov/setup-xcode@v1.6.0 +# with: +# xcode-version: latest +# - run: brew install sqlcipher +# - run: flutter pub get +# - run: flutter build ipa --no-codesign +# Pangea# diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml index 1999c49d3..e9d809e60 100644 --- a/.github/workflows/manual.yml +++ b/.github/workflows/manual.yml @@ -77,13 +77,4 @@ jobs: bundle install bundle update fastlane bundle exec fastlane deploy_internal_test - cd .. - - deploy_ios_testflight: # stashed on old.yml - environment: - name: ${{ inputs.environment }} - env: - WEB_APP_ENV: ${{ vars.WEB_APP_ENV }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 \ No newline at end of file + cd .. \ No newline at end of file diff --git a/.github/workflows/matrix_notification.yaml b/.github/workflows/matrix_notification.yaml deleted file mode 100644 index 1d6dd8085..000000000 --- a/.github/workflows/matrix_notification.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# name: Matrix Notification - -# on: -# issues: -# types: [ opened ] -# issue_comment: -# types: [ created ] - -# jobs: -# notify: -# runs-on: ubuntu-latest - -# steps: -# - name: Send Matrix Notification -# env: -# MATRIX_URL: https://matrix.janian.de/_matrix/client/v3/rooms/${{ secrets.MATRIX_MANAGEMENT_ROOM }}/send/m.room.message -# run: | -# if [ "${{ github.event.action }}" == "opened" ]; then -# PAYLOAD="{\"msgtype\": \"m.notice\", \"body\": \"New Issue from ${{ github.event.issue.user.login }}\\n${{ github.event.issue.title }}\\n\\n${{ github.event.issue.body }}\\n\\nURL: ${{ github.event.issue.html_url }}\"}" -# elif [ "${{ github.event.action }}" == "created" ]; then -# PAYLOAD="{\"msgtype\": \"m.notice\", \"body\": \"New Comment from ${{ github.event.comment.user.login }}\\n\\n${{ github.event.comment.body }}\\n\\nURL: ${{ github.event.comment.html_url }}\"}" -# fi -# curl -X POST -H "Authorization: Bearer ${{ secrets.MATRIX_BOT_TOKEN }}" \ -# -H "Content-Type: application/json" \ -# -d "$PAYLOAD" \ -# $MATRIX_URL diff --git a/.github/workflows/old.yml b/.github/workflows/old.yml deleted file mode 100644 index 62d57416b..000000000 --- a/.github/workflows/old.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Old Release Workflow - -on: - push: - branches: - - master-unused - -concurrency: - group: release_workflow - cancel-in-progress: true - -jobs: - deploy_ios_internal: - runs-on: macos-latest - environment: staging - steps: - - uses: actions/checkout@v4 - - run: cat ../.github/workflows/versions.env >> $GITHUB_ENV - - - name: Flutter - uses: subosito/flutter-action@v2 - with: - cache: true - cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:' - cache-path: "${{ runner.tool_cache }}/flutter/:channel:-:version:-:arch:" - - # Build ios Release - - run: flutter build ios --release --config-only --no-codesign --target=lib/main.dart - - - name: Deploy ios - run: | - mkdir -p build/ios - cp build/app/outputs/bundle/release/app-release.aab build/ios/ - cd ios - bundle install - bundle update fastlane - cd .. - - name: Execute fastlane signing - env: - APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} - APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }} - APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_IS_KEY_CONTENT_BASE64 }} - APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }} - run: bundle exec fastlane ios beta \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b6751456d..51705779a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -142,36 +142,39 @@ jobs: asset_path: build/app/outputs/flutter-apk/app-release.apk asset_name: pangeachat.apk asset_content_type: application/vnd.android.package-archive - build_linux: - strategy: - matrix: - arch: [ x64 ] - runs-on: ubuntu-latest - needs: create_release - steps: - - uses: actions/checkout@v4 - - run: cat .github/workflows/versions.env >> $GITHUB_ENV - - name: Install dependencies - run: sudo apt-get update && sudo apt-get install curl clang cmake ninja-build pkg-config libgtk-3-dev libblkid-dev liblzma-dev libjsoncpp-dev cmake-data libsecret-1-dev libsecret-1-0 librhash0 libssl-dev libwebkit2gtk-4.1-dev -y - - name: Install dependencies for audio-player - run: sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev - - name: Install Flutter - run: | - git clone --branch ${{ env.FLUTTER_VERSION }} https://github.com/flutter/flutter.git - ./flutter/bin/flutter doctor - - run: ./flutter/bin/flutter pub get - - run: ./flutter/bin/flutter build linux --target-platform linux-${{ matrix.arch }} - - name: Create archive - run: tar -czf pangeachat-linux-${{ matrix.arch }}.tar.gz -C build/linux/${{ matrix.arch }}/release/bundle/ . - - name: Upload to release - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.PAGES_DEPLOY_TOKEN }} - with: - upload_url: ${{ needs.create_release.outputs.upload_url }} - asset_path: pangeachat-linux-${{ matrix.arch }}.tar.gz - asset_name: pangeachat-linux-${{ matrix.arch }}.tar.gz - asset_content_type: application/gzip + + # #Pangea + # build_linux: + # strategy: + # matrix: + # arch: [ x64 ] + # runs-on: ubuntu-latest + # needs: create_release + # steps: + # - uses: actions/checkout@v4 + # - run: cat .github/workflows/versions.env >> $GITHUB_ENV + # - name: Install dependencies + # run: sudo apt-get update && sudo apt-get install curl clang cmake ninja-build pkg-config libgtk-3-dev libblkid-dev liblzma-dev libjsoncpp-dev cmake-data libsecret-1-dev libsecret-1-0 librhash0 libssl-dev libwebkit2gtk-4.1-dev -y + # - name: Install dependencies for audio-player + # run: sudo apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev + # - name: Install Flutter + # run: | + # git clone --branch ${{ env.FLUTTER_VERSION }} https://github.com/flutter/flutter.git + # ./flutter/bin/flutter doctor + # - run: ./flutter/bin/flutter pub get + # - run: ./flutter/bin/flutter build linux --target-platform linux-${{ matrix.arch }} + # - name: Create archive + # run: tar -czf pangeachat-linux-${{ matrix.arch }}.tar.gz -C build/linux/${{ matrix.arch }}/release/bundle/ . + # - name: Upload to release + # uses: actions/upload-release-asset@v1 + # env: + # GITHUB_TOKEN: ${{ secrets.PAGES_DEPLOY_TOKEN }} + # with: + # upload_url: ${{ needs.create_release.outputs.upload_url }} + # asset_path: pangeachat-linux-${{ matrix.arch }}.tar.gz + # asset_name: pangeachat-linux-${{ matrix.arch }}.tar.gz + # asset_content_type: application/gzip + # Pangea# deploy_web: runs-on: ubuntu-latest diff --git a/.github/workflows/upload-release-ios.yml b/.github/workflows/upload-release-ios.yml deleted file mode 100644 index aa70cd9ca..000000000 --- a/.github/workflows/upload-release-ios.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: upload-release-ios -on: - workflow_call: - inputs: - new_release: - required: true - type: string - description: "The new release version number" - new_release_notes: - required: true - type: string - description: "The release notes for the new release" - -jobs: - build: - runs-on: macos-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Download app - uses: actions/download-artifact@v4 - with: - name: Runner.ipa - path: ios/ - - - run: 'echo "$API_KEY" | base64 --decode > AuthKey.p8' - shell: bash - env: - API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }} - - - run: bundle install - - - run: bundle exec fastlane upload_testflight - env: - RELEASE_NOTES: ${{ inputs.new_release_notes }} - - run: bundle exec fastlane upload_metadata_app_store \ No newline at end of file From 586b5350f4a4ef7931a5e284a8b975d136a3d15d Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 14:55:53 -0400 Subject: [PATCH 42/57] clean up pangea comments --- lib/pages/chat/pinned_events.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index fe1421eed..46c17d815 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -81,16 +81,18 @@ class PinnedEvents extends StatelessWidget { icon: const Icon(Icons.push_pin), // #Pangea // tooltip: L10n.of(context).unpin, + // onPressed: controller.room.canSendEvent(EventTypes.RoomPinnedEvents) + // ? () => controller.unpinEvent(event!.eventId) + // : null, tooltip: controller.room.canChangeStateEvent(EventTypes.RoomPinnedEvents) ? L10n.of(context).unpin : null, - // onPressed: controller.room.canSendEvent(EventTypes.RoomPinnedEvents) onPressed: controller.room.canChangeStateEvent(EventTypes.RoomPinnedEvents) - // Pangea# ? () => controller.unpinEvent(event!.eventId) : null, + // Pangea# ), onTap: () => _displayPinnedEventsDialog(context), ); From 0ff8b047402d5db70758b18e88f0921031447f21 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 18 Jun 2025 14:58:12 -0400 Subject: [PATCH 43/57] Selects icon color for phonetic audio button based on background darkness --- .../phonetic_transcription_widget.dart | 18 +++++++++++++++--- .../toolbar/widgets/overlay_message.dart | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index 0e7c69f74..b1fe93f75 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -16,11 +16,13 @@ class PhoneticTranscriptionWidget extends StatefulWidget { final LanguageModel textLanguage; final TextStyle? style; final double? iconSize; + final bool? lightBackground; const PhoneticTranscriptionWidget({ super.key, required this.text, required this.textLanguage, + this.lightBackground, this.style, this.iconSize, }); @@ -143,9 +145,19 @@ class _PhoneticTranscriptionWidgetState : Icon( _isPlaying ? Icons.pause_outlined : Icons.volume_up, size: widget.iconSize ?? 24, - color: _isPlaying - ? Theme.of(context).colorScheme.primary - : Theme.of(context).iconTheme.color, + color: widget.lightBackground ?? + Theme.of(context).brightness == + Brightness.light + ? _isPlaying + ? Theme.of(context) + .colorScheme + .onPrimaryFixed + : Theme.of(context) + .colorScheme + .onTertiaryFixed + : _isPlaying + ? Theme.of(context).colorScheme.primary + : Theme.of(context).iconTheme.color, ), ), ], diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index aa3300f3c..73a3e6f6b 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -199,6 +199,7 @@ class OverlayMessage extends StatelessWidget { event, textColor, ), + lightBackground: ownMessage ? true : null, ), ], ), From a2d4aa0f16afcb9b62407fb22d7c07b57cfeceff Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 15:07:14 -0400 Subject: [PATCH 44/57] chore: deselect events in message overlay dispose function --- lib/pages/chat/chat.dart | 7 +++++++ .../toolbar/widgets/message_selection_overlay.dart | 13 ++++++++++++- lib/pangea/toolbar/widgets/overlay_header.dart | 7 ++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index b3515294f..6ee5dc1dd 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1651,6 +1651,13 @@ class ChatController extends State showEmojiPicker = false; }); } + + void setSelectedEvent(Event event) { + setState(() { + selectedEvents.clear(); + selectedEvents.add(event); + }); + } // Pangea# void clearSingleSelectedEvent() { diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index 08a756476..cdacaa4bb 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -109,8 +109,19 @@ class MessageOverlayController extends State @override void initState() { - initializeTokensAndMode(); super.initState(); + initializeTokensAndMode(); + WidgetsBinding.instance.addPostFrameCallback( + (_) => widget.chatController.setSelectedEvent(event), + ); + } + + @override + void dispose() { + WidgetsBinding.instance.addPostFrameCallback( + (_) => widget.chatController.clearSelectedEvents(), + ); + super.dispose(); } Future initializeTokensAndMode() async { diff --git a/lib/pangea/toolbar/widgets/overlay_header.dart b/lib/pangea/toolbar/widgets/overlay_header.dart index ff85e6b0b..a13e1c2a4 100644 --- a/lib/pangea/toolbar/widgets/overlay_header.dart +++ b/lib/pangea/toolbar/widgets/overlay_header.dart @@ -36,9 +36,10 @@ class OverlayHeaderState extends State { Widget build(BuildContext context) { final l10n = L10n.of(context); final theme = Theme.of(context); - final pinned = controller.room.pinnedEventIds.contains( - controller.selectedEvents.first.eventId, - ); + final pinned = controller.selectedEvents.length == 1 && + controller.room.pinnedEventIds.contains( + controller.selectedEvents.first.eventId, + ); return Container( padding: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( From 3923ba8bd6d0645b695af14fdd08954319bdcc91 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 15:33:44 -0400 Subject: [PATCH 45/57] chore: fix playing icon in phonestic transcription widget --- .../toolbar/controllers/tts_controller.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/pangea/toolbar/controllers/tts_controller.dart b/lib/pangea/toolbar/controllers/tts_controller.dart index 32e575ae1..80ae1a080 100644 --- a/lib/pangea/toolbar/controllers/tts_controller.dart +++ b/lib/pangea/toolbar/controllers/tts_controller.dart @@ -35,7 +35,7 @@ class TtsController { static final StreamController loadingChoreoStream = StreamController.broadcast(); - static final audioPlayer = AudioPlayer(); + static AudioPlayer? audioPlayer; static bool get _useAlternativeTTS { return PlatformInfos.isWindows; @@ -122,7 +122,7 @@ class TtsController { // https://pub.dev/packages/flutter_tts final result = await (_useAlternativeTTS ? _alternativeTTS.stop() : _tts.stop()); - audioPlayer.stop(); + audioPlayer?.stop(); if (!_useAlternativeTTS && result != 1) { ErrorHandler.logError( @@ -173,8 +173,6 @@ class TtsController { onStart: onStart, onStop: onStop, ); - - onStop?.call(); } /// A safer version of speak, that handles the case of @@ -314,13 +312,15 @@ class TtsController { try { Logs().i('Speaking from choreo: $text, langCode: $langCode'); final audioContent = base64Decode(ttsRes.audioContent); - await audioPlayer.setAudioSource( + audioPlayer?.dispose(); + audioPlayer = AudioPlayer(); + await audioPlayer!.setAudioSource( BytesAudioSource( audioContent, ttsRes.mimeType, ), ); - await audioPlayer.play(); + await audioPlayer!.play(); } catch (e, s) { ErrorHandler.logError( e: 'Error playing audio', @@ -330,6 +330,9 @@ class TtsController { 'text': text, }, ); + } finally { + audioPlayer?.dispose(); + audioPlayer = null; } } From 241a8051cd7e539e1844c99b6a584f3771755856 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 15:57:11 -0400 Subject: [PATCH 46/57] simplify --- .../phonetic_transcription_widget.dart | 14 ++++---------- lib/pangea/toolbar/widgets/overlay_message.dart | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index eb5cc59f5..d343c5855 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -17,15 +17,15 @@ class PhoneticTranscriptionWidget extends StatefulWidget { final LanguageModel textLanguage; final TextStyle? style; final double? iconSize; - final bool? lightBackground; + final Color? iconColor; const PhoneticTranscriptionWidget({ super.key, required this.text, required this.textLanguage, - this.lightBackground, this.style, this.iconSize, + this.iconColor, }); @override @@ -168,14 +168,8 @@ class _PhoneticTranscriptionWidgetState child: Icon( _isPlaying ? Icons.pause_outlined : Icons.volume_up, size: widget.iconSize ?? 24, - color: widget.lightBackground ?? - Theme.of(context).brightness == Brightness.light - ? _isPlaying - ? Theme.of(context).colorScheme.onPrimaryFixed - : Theme.of(context).colorScheme.onTertiaryFixed - : _isPlaying - ? Theme.of(context).colorScheme.primary - : Theme.of(context).iconTheme.color, + color: + widget.iconColor ?? Theme.of(context).iconTheme.color, ), ), ], diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index 73a3e6f6b..71a7c573d 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -199,7 +199,7 @@ class OverlayMessage extends StatelessWidget { event, textColor, ), - lightBackground: ownMessage ? true : null, + iconColor: textColor, ), ], ), From 29df67d2273f8971b57f87300634766c941c0843 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 18 Jun 2025 16:15:33 -0400 Subject: [PATCH 47/57] chore: make room details buttons expand to fill available space --- .../pages/pangea_chat_details.dart | 97 ++++++++----------- 1 file changed, 39 insertions(+), 58 deletions(-) diff --git a/lib/pangea/chat_settings/pages/pangea_chat_details.dart b/lib/pangea/chat_settings/pages/pangea_chat_details.dart index a5c242ba1..6b142d304 100644 --- a/lib/pangea/chat_settings/pages/pangea_chat_details.dart +++ b/lib/pangea/chat_settings/pages/pangea_chat_details.dart @@ -304,7 +304,6 @@ class RoomDetailsButtonRowState extends State { super.dispose(); } - final double _buttonWidth = 125.0; final double _buttonHeight = 84.0; final double _miniButtonWidth = 50.0; @@ -327,6 +326,7 @@ class RoomDetailsButtonRowState extends State { onPressed: () => context.go('/rooms/${room.id}/details/permissions'), visible: (room.isRoomAdmin && !room.isDirectChat) || room.isSpace, enabled: room.isRoomAdmin && !room.isDirectChat, + showInMainView: false, ), ButtonDetails( title: l10n.access, @@ -389,6 +389,7 @@ class RoomDetailsButtonRowState extends State { icon: const Icon(Icons.download_outlined, size: 30.0), onPressed: widget.controller.downloadChatAction, visible: room.ownPowerLevel >= 50 && !room.isSpace, + showInMainView: false, ), ButtonDetails( title: l10n.botSettings, @@ -411,6 +412,7 @@ class RoomDetailsButtonRowState extends State { onPressed: widget.controller.setRoomCapacity, visible: !room.isSpace && !room.isDirectChat && room.canSendDefaultStates, + showInMainView: false, ), ButtonDetails( title: l10n.leave, @@ -492,24 +494,15 @@ class RoomDetailsButtonRowState extends State { child: LayoutBuilder( builder: (context, constraints) { final availableWidth = constraints.maxWidth; - final fullButtonCapacity = - (availableWidth / _buttonWidth).floor() - 1; - final miniButtonCapacity = - (availableWidth / _miniButtonWidth).floor() - 1; + final fullButtonCapacity = (availableWidth / 120.0).floor() - 1; final mini = fullButtonCapacity < 4; - final capacity = mini ? miniButtonCapacity : fullButtonCapacity; - List mainViewButtons = + final List mainViewButtons = buttons.where((button) => button.showInMainView).toList(); final List otherButtons = buttons.where((button) => !button.showInMainView).toList(); - if (capacity < mainViewButtons.length) { - otherButtons.addAll(mainViewButtons.skip(capacity)); - mainViewButtons = mainViewButtons.take(capacity).toList(); - } - return Row( mainAxisAlignment: MainAxisAlignment.center, children: List.generate(mainViewButtons.length + 1, (index) { @@ -518,44 +511,46 @@ class RoomDetailsButtonRowState extends State { return const SizedBox(); } - return PopupMenuButton( - useRootNavigator: true, - onSelected: (button) => button.onPressed?.call(), - itemBuilder: (context) { - return otherButtons - .map( - (button) => PopupMenuItem( - value: button, - child: Row( - children: [ - button.icon, - const SizedBox(width: 8), - Text(button.title), - ], + return Expanded( + child: PopupMenuButton( + useRootNavigator: true, + onSelected: (button) => button.onPressed?.call(), + itemBuilder: (context) { + return otherButtons + .map( + (button) => PopupMenuItem( + value: button, + child: Row( + children: [ + button.icon, + const SizedBox(width: 8), + Text(button.title), + ], + ), ), - ), - ) - .toList(); - }, - child: RoomDetailsButton( - mini: mini, - buttonDetails: ButtonDetails( - title: L10n.of(context).more, - icon: const Icon(Icons.more_horiz_outlined), - visible: true, + ) + .toList(); + }, + child: RoomDetailsButton( + mini: mini, + buttonDetails: ButtonDetails( + title: L10n.of(context).more, + icon: const Icon(Icons.more_horiz_outlined), + visible: true, + ), + height: mini ? _miniButtonWidth : _buttonHeight, ), - width: mini ? _miniButtonWidth : _buttonWidth, - height: mini ? _miniButtonWidth : _buttonHeight, ), ); } final button = buttons[index]; - return RoomDetailsButton( - mini: mini, - buttonDetails: button, - width: mini ? _miniButtonWidth : _buttonWidth, - height: mini ? _miniButtonWidth : _buttonHeight, + return Expanded( + child: RoomDetailsButton( + mini: mini, + buttonDetails: button, + height: mini ? _miniButtonWidth : _buttonHeight, + ), ); }), ); @@ -567,14 +562,6 @@ class RoomDetailsButtonRowState extends State { class RoomDetailsButton extends StatelessWidget { final bool mini; - // final bool visible; - // final bool enabled; - - // final String title; - // final Widget icon; - // final VoidCallback? onPressed; - - final double width; final double height; final ButtonDetails buttonDetails; @@ -582,14 +569,8 @@ class RoomDetailsButton extends StatelessWidget { const RoomDetailsButton({ super.key, required this.buttonDetails, - // required this.visible, - // required this.title, - // required this.icon, required this.mini, - required this.width, required this.height, - // this.enabled = true, - // this.onPressed, }); @override @@ -613,7 +594,7 @@ class RoomDetailsButton extends StatelessWidget { child: Opacity( opacity: buttonDetails.enabled ? 1.0 : 0.5, child: Container( - width: width, + alignment: Alignment.center, height: height, decoration: BoxDecoration( color: hovered From d8a145834c57ee6f02299a276b9b554a300f59a1 Mon Sep 17 00:00:00 2001 From: wcjord <32568597+wcjord@users.noreply.github.com> Date: Thu, 19 Jun 2025 15:09:13 -0400 Subject: [PATCH 48/57] feat(phonetic_transcription): hide transcription for langauges with the same script --- lib/main.dart | 19 +++++----- .../vocab_analytics_details_view.dart | 6 ++-- .../controllers/language_controller.dart | 13 ++++--- .../models/language_model.dart | 7 ++-- .../toolbar/widgets/overlay_message.dart | 36 +++++++++---------- 5 files changed, 43 insertions(+), 38 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d33e62b41..601e5a77d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,13 +1,4 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:collection/collection.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:get_storage/get_storage.dart'; -import 'package:matrix/matrix.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; @@ -15,6 +6,14 @@ import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:matrix/matrix.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + import 'config/setting_keys.dart'; import 'utils/background_push.dart'; import 'widgets/fluffy_chat_app.dart'; @@ -24,7 +23,7 @@ void main() async { // #Pangea try { - await dotenv.load(fileName: ".env"); + await dotenv.load(fileName: ".env.local_choreo"); } catch (e) { Logs().e('Failed to load .env file', e); } diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart index d472a64b1..8e6dba805 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart @@ -1,7 +1,4 @@ -import 'package:flutter/material.dart'; - import 'package:collection/collection.dart'; - import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup_content.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; @@ -16,6 +13,7 @@ import 'package:fluffychat/pangea/toolbar/utils/shrinkable_text.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; /// Displays information about selected lemma, and its usage class VocabDetailsView extends StatelessWidget { @@ -67,7 +65,7 @@ class VocabDetailsView extends StatelessWidget { ); }, ), - if (MatrixState.pangeaController.languageController.userL2 != null) + if (MatrixState.pangeaController.languageController.showTrancription) Padding( padding: const EdgeInsets.only(top: 4.0), child: PhoneticTranscriptionWidget( diff --git a/lib/pangea/learning_settings/controllers/language_controller.dart b/lib/pangea/learning_settings/controllers/language_controller.dart index b5cc511c2..90837e6ff 100644 --- a/lib/pangea/learning_settings/controllers/language_controller.dart +++ b/lib/pangea/learning_settings/controllers/language_controller.dart @@ -1,13 +1,12 @@ // ignore_for_file: depend_on_referenced_packages -import 'package:flutter/material.dart'; - -import 'package:universal_io/io.dart'; - import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; +import 'package:flutter/material.dart'; +import 'package:universal_io/io.dart'; + import '../widgets/p_language_dialog.dart'; class LanguageController { @@ -112,4 +111,10 @@ class LanguageController { // final model = activeL2 != null ? PangeaLanguage.byLangCode(activeL2) : null; // return model; } + + bool get showTrancription => + _pangeaController.languageController.userL1 != null && + _pangeaController.languageController.userL2 != null && + _pangeaController.languageController.userL1?.script != + _pangeaController.languageController.userL2?.script; } diff --git a/lib/pangea/learning_settings/models/language_model.dart b/lib/pangea/learning_settings/models/language_model.dart index 1e1ef69b9..ae8ab78b0 100644 --- a/lib/pangea/learning_settings/models/language_model.dart +++ b/lib/pangea/learning_settings/models/language_model.dart @@ -1,17 +1,18 @@ -import 'package:flutter/material.dart'; - import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/learning_settings/enums/l2_support_enum.dart'; +import 'package:flutter/material.dart'; class LanguageModel { final String langCode; final String displayName; + final String script; final L2SupportEnum l2Support; LanguageModel({ required this.langCode, required this.displayName, + this.script = LanguageKeys.unknownLanguage, this.l2Support = L2SupportEnum.na, }); @@ -28,12 +29,14 @@ class LanguageModel { l2Support: json['l2_support'] != null ? L2SupportEnum.na.fromStorageString(json['l2_support']) : L2SupportEnum.na, + script: json['script'] ?? LanguageKeys.unknownLanguage, ); } toJson() => { 'language_code': langCode, 'language_name': displayName, + 'script': script, 'l2_support': l2Support.storageString, }; diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index 71a7c573d..6f8ff388f 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -1,8 +1,3 @@ -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; - -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; @@ -18,6 +13,9 @@ import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/file_description.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; // @ggurdin be great to explain the need/function of a widget like this class OverlayMessage extends StatelessWidget { @@ -187,20 +185,22 @@ class OverlayMessage extends StatelessWidget { fontStyle: FontStyle.italic, ), ), - PhoneticTranscriptionWidget( - text: overlayController - .transcription!.transcript.text, - textLanguage: PLanguageStore.byLangCode( - pangeaMessageEvent! - .messageDisplayLangCode, - ) ?? - LanguageModel.unknown, - style: AppConfig.messageTextStyle( - event, - textColor, + if (MatrixState.pangeaController + .languageController.showTrancription) + PhoneticTranscriptionWidget( + text: overlayController + .transcription!.transcript.text, + textLanguage: PLanguageStore.byLangCode( + pangeaMessageEvent! + .messageDisplayLangCode, + ) ?? + LanguageModel.unknown, + style: AppConfig.messageTextStyle( + event, + textColor, + ), + iconColor: textColor, ), - iconColor: textColor, - ), ], ), ) From 82f729d625450865a671180eadb32650a6123a41 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 20 Jun 2025 08:54:38 -0400 Subject: [PATCH 49/57] fix .env file path --- lib/main.dart | 19 ++++++++++--------- .../vocab_analytics_details_view.dart | 4 +++- .../controllers/language_controller.dart | 7 ++++--- .../models/language_model.dart | 3 ++- .../toolbar/widgets/overlay_message.dart | 8 +++++--- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 601e5a77d..d33e62b41 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + import 'package:collection/collection.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:matrix/matrix.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; @@ -6,14 +15,6 @@ import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:get_storage/get_storage.dart'; -import 'package:matrix/matrix.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - import 'config/setting_keys.dart'; import 'utils/background_push.dart'; import 'widgets/fluffy_chat_app.dart'; @@ -23,7 +24,7 @@ void main() async { // #Pangea try { - await dotenv.load(fileName: ".env.local_choreo"); + await dotenv.load(fileName: ".env"); } catch (e) { Logs().e('Failed to load .env file', e); } diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart index 8e6dba805..9446dfb1b 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart @@ -1,4 +1,7 @@ +import 'package:flutter/material.dart'; + import 'package:collection/collection.dart'; + import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup_content.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; @@ -13,7 +16,6 @@ import 'package:fluffychat/pangea/toolbar/utils/shrinkable_text.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; /// Displays information about selected lemma, and its usage class VocabDetailsView extends StatelessWidget { diff --git a/lib/pangea/learning_settings/controllers/language_controller.dart b/lib/pangea/learning_settings/controllers/language_controller.dart index 90837e6ff..ba22c6eda 100644 --- a/lib/pangea/learning_settings/controllers/language_controller.dart +++ b/lib/pangea/learning_settings/controllers/language_controller.dart @@ -1,12 +1,13 @@ // ignore_for_file: depend_on_referenced_packages +import 'package:flutter/material.dart'; + +import 'package:universal_io/io.dart'; + import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; -import 'package:flutter/material.dart'; -import 'package:universal_io/io.dart'; - import '../widgets/p_language_dialog.dart'; class LanguageController { diff --git a/lib/pangea/learning_settings/models/language_model.dart b/lib/pangea/learning_settings/models/language_model.dart index ae8ab78b0..4029b9507 100644 --- a/lib/pangea/learning_settings/models/language_model.dart +++ b/lib/pangea/learning_settings/models/language_model.dart @@ -1,7 +1,8 @@ +import 'package:flutter/material.dart'; + import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/learning_settings/enums/l2_support_enum.dart'; -import 'package:flutter/material.dart'; class LanguageModel { final String langCode; diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index 6f8ff388f..14dea0f1c 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -1,3 +1,8 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; @@ -13,9 +18,6 @@ import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/file_description.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; // @ggurdin be great to explain the need/function of a widget like this class OverlayMessage extends StatelessWidget { From a396b94becf8ddfe46649eaa343a3de42f08ddf5 Mon Sep 17 00:00:00 2001 From: wcjord <32568597+wcjord@users.noreply.github.com> Date: Fri, 20 Jun 2025 09:18:17 -0400 Subject: [PATCH 50/57] fix(phonetic_transcription): show transcriptions if scripts null in languages --- lib/main.dart | 19 +++++++++---------- .../vocab_analytics_details_view.dart | 4 +--- .../controllers/language_controller.dart | 18 ++++++++++++++---- .../models/language_model.dart | 3 +-- .../toolbar/widgets/overlay_message.dart | 8 +++----- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index d33e62b41..601e5a77d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,13 +1,4 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:collection/collection.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:get_storage/get_storage.dart'; -import 'package:matrix/matrix.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; @@ -15,6 +6,14 @@ import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:matrix/matrix.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + import 'config/setting_keys.dart'; import 'utils/background_push.dart'; import 'widgets/fluffy_chat_app.dart'; @@ -24,7 +23,7 @@ void main() async { // #Pangea try { - await dotenv.load(fileName: ".env"); + await dotenv.load(fileName: ".env.local_choreo"); } catch (e) { Logs().e('Failed to load .env file', e); } diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart index 9446dfb1b..8e6dba805 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart @@ -1,7 +1,4 @@ -import 'package:flutter/material.dart'; - import 'package:collection/collection.dart'; - import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup_content.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; @@ -16,6 +13,7 @@ import 'package:fluffychat/pangea/toolbar/utils/shrinkable_text.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; /// Displays information about selected lemma, and its usage class VocabDetailsView extends StatelessWidget { diff --git a/lib/pangea/learning_settings/controllers/language_controller.dart b/lib/pangea/learning_settings/controllers/language_controller.dart index ba22c6eda..c9a49ced2 100644 --- a/lib/pangea/learning_settings/controllers/language_controller.dart +++ b/lib/pangea/learning_settings/controllers/language_controller.dart @@ -1,13 +1,12 @@ // ignore_for_file: depend_on_referenced_packages -import 'package:flutter/material.dart'; - -import 'package:universal_io/io.dart'; - import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; +import 'package:flutter/material.dart'; +import 'package:universal_io/io.dart'; + import '../widgets/p_language_dialog.dart'; class LanguageController { @@ -114,8 +113,19 @@ class LanguageController { } bool get showTrancription => +<<<<<<< Updated upstream _pangeaController.languageController.userL1 != null && _pangeaController.languageController.userL2 != null && _pangeaController.languageController.userL1?.script != _pangeaController.languageController.userL2?.script; +======= + (_pangeaController.languageController.userL1 != null && + _pangeaController.languageController.userL2 != null && + _pangeaController.languageController.userL1?.script != + _pangeaController.languageController.userL2?.script) || + (_pangeaController.languageController.userL1?.script != + LanguageKeys.unknownLanguage || + _pangeaController.languageController.userL2?.script == + LanguageKeys.unknownLanguage); +>>>>>>> Stashed changes } diff --git a/lib/pangea/learning_settings/models/language_model.dart b/lib/pangea/learning_settings/models/language_model.dart index 4029b9507..ae8ab78b0 100644 --- a/lib/pangea/learning_settings/models/language_model.dart +++ b/lib/pangea/learning_settings/models/language_model.dart @@ -1,8 +1,7 @@ -import 'package:flutter/material.dart'; - import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/learning_settings/enums/l2_support_enum.dart'; +import 'package:flutter/material.dart'; class LanguageModel { final String langCode; diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index 14dea0f1c..6f8ff388f 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -1,8 +1,3 @@ -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; - -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; @@ -18,6 +13,9 @@ import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/file_description.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; // @ggurdin be great to explain the need/function of a widget like this class OverlayMessage extends StatelessWidget { From 4fdf1f970b8c139da35c2ee045729ebaaf545f1c Mon Sep 17 00:00:00 2001 From: wcjord <32568597+wcjord@users.noreply.github.com> Date: Fri, 20 Jun 2025 09:31:14 -0400 Subject: [PATCH 51/57] fix(phonetic_transcription): fixing a merge conflict --- .../learning_settings/controllers/language_controller.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/pangea/learning_settings/controllers/language_controller.dart b/lib/pangea/learning_settings/controllers/language_controller.dart index c9a49ced2..86ea49800 100644 --- a/lib/pangea/learning_settings/controllers/language_controller.dart +++ b/lib/pangea/learning_settings/controllers/language_controller.dart @@ -113,12 +113,6 @@ class LanguageController { } bool get showTrancription => -<<<<<<< Updated upstream - _pangeaController.languageController.userL1 != null && - _pangeaController.languageController.userL2 != null && - _pangeaController.languageController.userL1?.script != - _pangeaController.languageController.userL2?.script; -======= (_pangeaController.languageController.userL1 != null && _pangeaController.languageController.userL2 != null && _pangeaController.languageController.userL1?.script != @@ -127,5 +121,4 @@ class LanguageController { LanguageKeys.unknownLanguage || _pangeaController.languageController.userL2?.script == LanguageKeys.unknownLanguage); ->>>>>>> Stashed changes } From cb2747247b001e8653b4751bc3257fb5f69d0ef6 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 20 Jun 2025 10:03:06 -0400 Subject: [PATCH 52/57] chore: use node.text instead of node.innerhtml --- lib/pages/chat/events/html_message.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 268abbcac..877ddf428 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -326,7 +326,7 @@ class HtmlMessage extends StatelessWidget { final tokenWidth = renderer.tokenTextWidthForContainer( context, - node.innerHtml, + node.text, ); return WidgetSpan( @@ -380,7 +380,7 @@ class HtmlMessage extends StatelessWidget { text: TextSpan( children: [ LinkifySpan( - text: node.innerHtml, + text: node.text, style: renderer.style( context, color: renderer.backgroundColor( From 7d965cebb3d2bcc89078b2b081950f4886b285a2 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 20 Jun 2025 10:05:00 -0400 Subject: [PATCH 53/57] formatting --- lib/main.dart | 17 +++++++++-------- .../vocab_analytics_details_view.dart | 4 +++- .../controllers/language_controller.dart | 7 ++++--- .../models/language_model.dart | 3 ++- lib/pangea/toolbar/widgets/overlay_message.dart | 8 +++++--- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 601e5a77d..20e00e3c5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + import 'package:collection/collection.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:matrix/matrix.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/common/config/environment.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; @@ -6,14 +15,6 @@ import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; import 'package:fluffychat/utils/client_manager.dart'; import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:get_storage/get_storage.dart'; -import 'package:matrix/matrix.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - import 'config/setting_keys.dart'; import 'utils/background_push.dart'; import 'widgets/fluffy_chat_app.dart'; diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart index 8e6dba805..9446dfb1b 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart @@ -1,4 +1,7 @@ +import 'package:flutter/material.dart'; + import 'package:collection/collection.dart'; + import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup_content.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; @@ -13,7 +16,6 @@ import 'package:fluffychat/pangea/toolbar/utils/shrinkable_text.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; /// Displays information about selected lemma, and its usage class VocabDetailsView extends StatelessWidget { diff --git a/lib/pangea/learning_settings/controllers/language_controller.dart b/lib/pangea/learning_settings/controllers/language_controller.dart index 86ea49800..68dd7d021 100644 --- a/lib/pangea/learning_settings/controllers/language_controller.dart +++ b/lib/pangea/learning_settings/controllers/language_controller.dart @@ -1,12 +1,13 @@ // ignore_for_file: depend_on_referenced_packages +import 'package:flutter/material.dart'; + +import 'package:universal_io/io.dart'; + import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; -import 'package:flutter/material.dart'; -import 'package:universal_io/io.dart'; - import '../widgets/p_language_dialog.dart'; class LanguageController { diff --git a/lib/pangea/learning_settings/models/language_model.dart b/lib/pangea/learning_settings/models/language_model.dart index ae8ab78b0..4029b9507 100644 --- a/lib/pangea/learning_settings/models/language_model.dart +++ b/lib/pangea/learning_settings/models/language_model.dart @@ -1,7 +1,8 @@ +import 'package:flutter/material.dart'; + import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/learning_settings/enums/l2_support_enum.dart'; -import 'package:flutter/material.dart'; class LanguageModel { final String langCode; diff --git a/lib/pangea/toolbar/widgets/overlay_message.dart b/lib/pangea/toolbar/widgets/overlay_message.dart index 6f8ff388f..14dea0f1c 100644 --- a/lib/pangea/toolbar/widgets/overlay_message.dart +++ b/lib/pangea/toolbar/widgets/overlay_message.dart @@ -1,3 +1,8 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat.dart'; @@ -13,9 +18,6 @@ import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/file_description.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:matrix/matrix.dart'; // @ggurdin be great to explain the need/function of a widget like this class OverlayMessage extends StatelessWidget { From 2309df6727ac4e9ba9122c1875eb0ff809d39c19 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 20 Jun 2025 10:07:37 -0400 Subject: [PATCH 54/57] fix .env file path --- lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 20e00e3c5..d33e62b41 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -24,7 +24,7 @@ void main() async { // #Pangea try { - await dotenv.load(fileName: ".env.local_choreo"); + await dotenv.load(fileName: ".env"); } catch (e) { Logs().e('Failed to load .env file', e); } From 7a6db851adbf9e4c6251e08a9a4bbcb536c237f6 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 20 Jun 2025 10:18:44 -0400 Subject: [PATCH 55/57] chore: fix audio player unique ID mixup --- lib/pangea/toolbar/widgets/message_audio_card.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/toolbar/widgets/message_audio_card.dart b/lib/pangea/toolbar/widgets/message_audio_card.dart index e50a6458c..b4a4b26eb 100644 --- a/lib/pangea/toolbar/widgets/message_audio_card.dart +++ b/lib/pangea/toolbar/widgets/message_audio_card.dart @@ -84,7 +84,7 @@ class MessageAudioCardState extends State { : audioFile != null ? AudioPlayerWidget( null, - eventId: widget.messageEvent.eventId, + eventId: "${widget.messageEvent.eventId}_practice", roomId: widget.messageEvent.room.id, senderId: widget.messageEvent.senderId, matrixFile: audioFile, From fb52b8b1f0f332e8b0f06b84435c3489d47c04dc Mon Sep 17 00:00:00 2001 From: avashilling <165050625+avashilling@users.noreply.github.com> Date: Fri, 20 Jun 2025 10:22:22 -0400 Subject: [PATCH 56/57] chore: add border around word card for emphasis --- lib/pangea/toolbar/widgets/reading_assistance_content.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pangea/toolbar/widgets/reading_assistance_content.dart b/lib/pangea/toolbar/widgets/reading_assistance_content.dart index c07b15d4c..0edc58849 100644 --- a/lib/pangea/toolbar/widgets/reading_assistance_content.dart +++ b/lib/pangea/toolbar/widgets/reading_assistance_content.dart @@ -138,6 +138,10 @@ class ReadingAssistanceContentState extends State { child: Container( decoration: BoxDecoration( color: Theme.of(context).cardColor, + border: Border.all( + color: Theme.of(context).colorScheme.primary, + width: 4.0, + ), borderRadius: const BorderRadius.all( Radius.circular(AppConfig.borderRadius), ), From 03b092c74d0cff84280dc458440adff643ae7fe1 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 20 Jun 2025 10:53:58 -0400 Subject: [PATCH 57/57] chore: fix emoji reaction centering on iOS --- lib/pages/chat/events/message_reactions.dart | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/pages/chat/events/message_reactions.dart b/lib/pages/chat/events/message_reactions.dart index b3afbef60..7681c90ae 100644 --- a/lib/pages/chat/events/message_reactions.dart +++ b/lib/pages/chat/events/message_reactions.dart @@ -5,6 +5,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -158,12 +159,17 @@ class _Reaction extends StatelessWidget { borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), child: Container( decoration: BoxDecoration( - // #Pangea - // color: color, - // Pangea# + color: color, borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2), ), - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + // #Pangea + // issue: https://github.com/pangeachat/client/issues/3100 + // fix: https://github.com/flutter/flutter/issues/119623#issuecomment-2476719745 + padding: PlatformInfos.isIOS + ? const EdgeInsets.fromLTRB(5.5, 1, 3, 2.5) + : const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + // padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + // Pangea# child: content, ), );