From 7985214670acef8721b62610f9e437ff7b37d667 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 16 Jan 2026 12:20:19 -0500 Subject: [PATCH 1/6] chore: reset font size on logout --- lib/pangea/common/controllers/pangea_controller.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pangea/common/controllers/pangea_controller.dart b/lib/pangea/common/controllers/pangea_controller.dart index 3d80b2204..b1339a3e4 100644 --- a/lib/pangea/common/controllers/pangea_controller.dart +++ b/lib/pangea/common/controllers/pangea_controller.dart @@ -122,7 +122,9 @@ class PangeaController { } Future _clearCache({List exclude = const []}) async { - final List> futures = []; + final List> futures = [ + matrixState.store.setString(SettingKeys.fontSizeFactor, ''), + ]; for (final key in _storageKeys) { if (exclude.contains(key)) continue; futures.add(GetStorage(key).erase()); @@ -140,6 +142,7 @@ class PangeaController { ); } + AppConfig.fontSizeFactor = 1.0; await Future.wait(futures); } From f3b2feac20303c103f9128ddc5aa6e4f700ad3c7 Mon Sep 17 00:00:00 2001 From: Ava Shilling <165050625+avashilling@users.noreply.github.com> Date: Fri, 16 Jan 2026 12:39:04 -0500 Subject: [PATCH 2/6] fix: show alt text after flipping practice choice --- lib/pangea/vocab_practice/choice_cards/game_choice_card.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pangea/vocab_practice/choice_cards/game_choice_card.dart b/lib/pangea/vocab_practice/choice_cards/game_choice_card.dart index c4bed1767..ad2e5c461 100644 --- a/lib/pangea/vocab_practice/choice_cards/game_choice_card.dart +++ b/lib/pangea/vocab_practice/choice_cards/game_choice_card.dart @@ -100,7 +100,6 @@ class _GameChoiceCardState extends State animation: _scaleAnim, builder: (context, _) { final scale = _scaleAnim.value; - final showAlt = scale < 0.1 && widget.altChild != null; final showContent = scale > 0.05; return Transform.scale( @@ -113,7 +112,7 @@ class _GameChoiceCardState extends State : (hovered ? hoverColor : Colors.transparent), child: Opacity( opacity: showContent ? 1 : 0, - child: showAlt ? widget.altChild! : widget.child, + child: _revealed ? widget.altChild! : widget.child, ), ), ); From bc66198bf45675c68d1855b671a3032821d7e58d Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 16 Jan 2026 12:41:06 -0500 Subject: [PATCH 3/6] chore: reset download state on switch download type --- .../analytics_downloads/analytics_dowload_dialog.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/pangea/analytics_downloads/analytics_dowload_dialog.dart b/lib/pangea/analytics_downloads/analytics_dowload_dialog.dart index fe0af6e37..ea0739580 100644 --- a/lib/pangea/analytics_downloads/analytics_dowload_dialog.dart +++ b/lib/pangea/analytics_downloads/analytics_dowload_dialog.dart @@ -50,7 +50,13 @@ class AnalyticsDownloadDialogState extends State { } void _setDownloadType(DownloadType type) { - if (mounted) setState(() => _downloadType = type); + if (mounted) { + setState(() { + _downloadType = type; + _downloaded = false; + _error = null; + }); + } } Future _downloadAnalytics() async { @@ -427,7 +433,8 @@ class AnalyticsDownloadDialogState extends State { padding: const EdgeInsets.all(8.0), child: SegmentedButton( selected: {_downloadType}, - onSelectionChanged: (c) => _setDownloadType(c.first), + onSelectionChanged: + _downloading ? null : (c) => _setDownloadType(c.first), segments: [ ButtonSegment( value: DownloadType.csv, From 971fc5508e89bfd6689908a2728d84fa7b9bd34c Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 16 Jan 2026 12:51:26 -0500 Subject: [PATCH 4/6] fix: account for left rooms in join public course flow --- lib/pangea/course_creation/selected_course_page.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pangea/course_creation/selected_course_page.dart b/lib/pangea/course_creation/selected_course_page.dart index 1b8b3848e..299c784f4 100644 --- a/lib/pangea/course_creation/selected_course_page.dart +++ b/lib/pangea/course_creation/selected_course_page.dart @@ -169,12 +169,12 @@ class SelectedCourseController extends State : await client.joinRoom(widget.roomChunk!.roomId); Room? room = client.getRoomById(roomId); - if (!knock && room == null) { - await client.waitForRoomInSync(roomId); + if (!knock && room?.membership != Membership.join) { + await client.waitForRoomInSync(roomId, join: true); room = client.getRoomById(roomId); } - if (knock && room == null) { + if (knock) { Navigator.of(context).pop(); await showOkAlertDialog( context: context, From dac3c5edf54be11fd71f9cfdd066dc75be394120 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 16 Jan 2026 13:00:19 -0500 Subject: [PATCH 5/6] chore: show course info chips in one column mode --- lib/pangea/chat_settings/pages/space_details_content.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pangea/chat_settings/pages/space_details_content.dart b/lib/pangea/chat_settings/pages/space_details_content.dart index 6a4db73b4..b57233edd 100644 --- a/lib/pangea/chat_settings/pages/space_details_content.dart +++ b/lib/pangea/chat_settings/pages/space_details_content.dart @@ -296,7 +296,7 @@ class SpaceDetailsContent extends StatelessWidget { ], Flexible( child: Column( - spacing: 12.0, + spacing: isColumnMode ? 12.0 : 6.0, mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -311,7 +311,7 @@ class SpaceDetailsContent extends StatelessWidget { : FontWeight.bold, ), ), - if (isColumnMode && room.coursePlan != null) + if (room.coursePlan != null) CourseInfoChips( room.coursePlan!.uuid, fontSize: 12.0, From a25bf26779af4e57e87dac9890de7ad102db4455 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 16 Jan 2026 13:18:26 -0500 Subject: [PATCH 6/6] fix: use room state stream to drive updates to analytics request indicator --- .../analytics_request_indicator.dart | 70 ++++++++++++------- 1 file changed, 43 insertions(+), 27 deletions(-) diff --git a/lib/pangea/space_analytics/analytics_request_indicator.dart b/lib/pangea/space_analytics/analytics_request_indicator.dart index 180a0bc6f..9ba4e910b 100644 --- a/lib/pangea/space_analytics/analytics_request_indicator.dart +++ b/lib/pangea/space_analytics/analytics_request_indicator.dart @@ -10,6 +10,7 @@ import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/space_analytics/space_analytics_requested_dialog.dart'; +import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; class AnalyticsRequestIndicator extends StatefulWidget { @@ -26,51 +27,68 @@ class AnalyticsRequestIndicator extends StatefulWidget { class AnalyticsRequestIndicatorState extends State { AnalyticsRequestIndicatorState(); - - final Map> _knockingAdmins = {}; + StreamSubscription? _analyticsRoomSub; @override void initState() { super.initState(); - _fetchKnockingAdmins(); + _init(); } @override void didUpdateWidget(covariant AnalyticsRequestIndicator oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.room.id != widget.room.id) { - _fetchKnockingAdmins(); + _init(); } } - Future _fetchKnockingAdmins() async { - setState(() => _knockingAdmins.clear()); + @override + void dispose() { + _analyticsRoomSub?.cancel(); + super.dispose(); + } - final admins = (await widget.room.requestParticipants( - [Membership.join, Membership.invite, Membership.knock], - false, - true, - )) - .where((u) => u.powerLevel >= 100); + Future _init() async { + final analyticsRooms = widget.room.client.allMyAnalyticsRooms; + final futures = analyticsRooms.map( + (r) => r.requestParticipants( + [Membership.join, Membership.invite, Membership.knock], + false, + true, + ), + ); + await Future.wait(futures); + final analyicsRoomIds = analyticsRooms.map((r) => r.id).toSet(); + _analyticsRoomSub?.cancel(); + _analyticsRoomSub = widget.room.client.onRoomState.stream + .where( + (event) => + analyicsRoomIds.contains(event.roomId) && + event.state.type == EventTypes.RoomMember, + ) + .rateLimit(const Duration(seconds: 1)) + .listen((_) => setState(() {})); + + if (mounted) setState(() {}); + } + + Map> get _knockingAdmins { + final Map> knockingAdmins = {}; for (final analyticsRoom in widget.room.client.allMyAnalyticsRooms) { - final knocking = await analyticsRoom.requestParticipants( - [Membership.knock], - ); - final knockingSpace = - knocking.where((u) => u.content['reason'] == widget.room.id).toList(); - if (knockingSpace.isEmpty) continue; + final knocking = analyticsRoom + .getParticipants([Membership.knock]) + .where((u) => u.content['reason'] == widget.room.id) + .toList(); - for (final admin in admins) { - if (knockingSpace.any((u) => u.id == admin.id)) { - _knockingAdmins.putIfAbsent(admin, () => []).add(analyticsRoom); - } + if (knocking.isEmpty) continue; + for (final admin in knocking) { + knockingAdmins.putIfAbsent(admin, () => []).add(analyticsRoom); } } - if (mounted) { - setState(() {}); - } + return knockingAdmins; } Future _onTap(BuildContext context) async { @@ -109,8 +127,6 @@ class AnalyticsRequestIndicatorState extends State { } }, ); - - if (mounted) _fetchKnockingAdmins(); } @override