From d09617aa817b14cb6943971afa038639701a000f Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:09:06 -0400 Subject: [PATCH] Activity loading tweaks (#3636) * Added activity message when generation is partially successful * Fix problem with missing intl entries * Fix error message on timeout, add try again button to partial timeout * Revert _activityItems.isNotEmpty check on _loading * Make reversion match previous format * Fix _setActivityItems failure from empty activity_plans * Set timeout after first timeout, throw timeout exception for empty activities * Only show Try again buttons when not currently loading * fix text align --------- Co-authored-by: ggurdin --- lib/l10n/intl_en.arb | 3 +- .../activity_plan_search_repo.dart | 3 +- .../activity_suggestions_area.dart | 147 +++++++++++++----- 3 files changed, 110 insertions(+), 43 deletions(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 7cb763d1f..5910f525c 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -4999,7 +4999,7 @@ "access": "Access", "addSubspace": "Add subspace", "botSettings": "Bot settings", - "activitySuggestionTimeoutMessage": "We are working hard to generate activities for you, please check back in a minute", + "activitySuggestionTimeoutMessage": "We are working hard to generate more activities for you, please check back in a minute", "accessSettingsWarning": "Oops! It looks like you don't have permission to set the Access rules of this room. You should check these to make sure they're what you need and talk to a room admin if you need to change them", "howSpaceCanBeFound": "How this space can be found", "private": "Private", @@ -5156,6 +5156,7 @@ "activitySummaryError": "Activity summaries unavailable", "requestSummaries": "Request summaries", "loadingActivitySummary": "Loading activity summary...", + "generatingNewActivities": "You're the first user of this language pair! Please wait a moment, we're preparing activities just for you.", "requestAccessTitle": "Request to analytics view access?", "requestAccessDesc": "Would you like to request access to view participant analytics?\n\nIf participants agree, admin of this space will be able to view their:\n • total vocabulary\n • total grammar concepts\n • total activity sessions completed\n • the specific grammar concepts used, correctly and incorrectly\n\n • They will not be able to view their:\n • messages in chats outside the space\n • vocabulary list", "requestAccess": "Request access ({count})", diff --git a/lib/pangea/activity_suggestions/activity_plan_search_repo.dart b/lib/pangea/activity_suggestions/activity_plan_search_repo.dart index 5006c4617..b2fb18abf 100644 --- a/lib/pangea/activity_suggestions/activity_plan_search_repo.dart +++ b/lib/pangea/activity_suggestions/activity_plan_search_repo.dart @@ -20,7 +20,8 @@ class ActivitySearchRepo { static Future get(ActivityPlanRequest request) async { final cachedJson = _activityPlanStorage.read(request.storageKey); - if (cachedJson != null) { + if (cachedJson != null && + (cachedJson['activity_plans'] as List).isNotEmpty) { final cached = ActivityPlanResponse.fromJson(cachedJson); return cached; diff --git a/lib/pangea/activity_suggestions/activity_suggestions_area.dart b/lib/pangea/activity_suggestions/activity_suggestions_area.dart index 1071f3368..c97dcd81b 100644 --- a/lib/pangea/activity_suggestions/activity_suggestions_area.dart +++ b/lib/pangea/activity_suggestions/activity_suggestions_area.dart @@ -63,7 +63,10 @@ class ActivitySuggestionsAreaState extends State { super.dispose(); } + // _loading is true when _setActivityItems is currently requesting activities bool _loading = true; + // _timeout is true if 1+ round of _setActivityItems + // has occurred and no activities retrieved bool _timeout = false; bool get _isColumnMode => FluffyThemes.isColumnMode(context); @@ -105,10 +108,13 @@ class ActivitySuggestionsAreaState extends State { } try { - setState(() { - _activityItems.clear(); - _loading = true; - }); + if (retries == 0) { + setState(() { + _activityItems.clear(); + _loading = true; + _timeout = false; + }); + } final resp = await ActivitySearchRepo.get(_request).timeout( const Duration(seconds: 5), @@ -116,7 +122,6 @@ class ActivitySuggestionsAreaState extends State { if (mounted) { setState(() { _timeout = true; - _loading = false; }); } @@ -132,7 +137,23 @@ class ActivitySuggestionsAreaState extends State { }, ); _activityItems.addAll(resp.activityPlans); - _timeout = false; + + // If activities are not successfully retrieved, try again + if (_activityItems.isEmpty) { + if (mounted) { + setState(() { + _timeout = true; + }); + } + + Future.delayed(const Duration(seconds: 5), () { + if (mounted) _setActivityItems(retries: retries + 1); + }); + + throw TimeoutException( + L10n.of(context).activitySuggestionTimeoutMessage, + ); + } } catch (e, s) { if (e is! TimeoutException) rethrow; ErrorHandler.logError( @@ -145,7 +166,15 @@ class ActivitySuggestionsAreaState extends State { level: SentryLevel.warning, ); } finally { - if (mounted) setState(() => _loading = false); + // If activities are successfully retrieved, set timeout and loading to false + if (mounted && _activityItems.isNotEmpty) { + setState( + () { + _loading = false; + _timeout = false; + }, + ); + } } } @@ -214,59 +243,95 @@ class ActivitySuggestionsAreaState extends State { children: [ AnimatedSize( duration: FluffyThemes.animationDuration, - child: (_timeout || !_loading && cards.isEmpty) + child: _timeout ? Padding( padding: const EdgeInsets.all(8.0), child: Column( spacing: 16.0, mainAxisSize: MainAxisSize.min, children: [ - ErrorIndicator( - message: _timeout - ? L10n.of(context).activitySuggestionTimeoutMessage - : L10n.of(context).errorFetchingActivitiesMessage, - ), - ElevatedButton( - onPressed: _setActivityItems, - style: ElevatedButton.styleFrom( - backgroundColor: theme.colorScheme.primaryContainer, - foregroundColor: theme.colorScheme.onPrimaryContainer, - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - ), + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 300), + child: Text( + L10n.of(context).generatingNewActivities, + textAlign: TextAlign.center, ), - child: Text(L10n.of(context).tryAgain), ), + if (_loading) const CircularProgressIndicator(), + if (!_loading) + ElevatedButton( + onPressed: _setActivityItems, + style: ElevatedButton.styleFrom( + backgroundColor: theme.colorScheme.primaryContainer, + foregroundColor: + theme.colorScheme.onPrimaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), + child: Text(L10n.of(context).tryAgain), + ), ], ), ) : Container( decoration: const BoxDecoration(), - child: scrollDirection == Axis.horizontal - ? Scrollbar( - thumbVisibility: true, - controller: _scrollController, - child: Padding( - padding: const EdgeInsets.only(bottom: 16.0), - child: SingleChildScrollView( + child: Column( + children: [ + scrollDirection == Axis.horizontal + ? Scrollbar( + thumbVisibility: true, controller: _scrollController, - scrollDirection: Axis.horizontal, - child: Row( - spacing: 8.0, + child: Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: SingleChildScrollView( + controller: _scrollController, + scrollDirection: Axis.horizontal, + child: Row( + spacing: 8.0, + children: cards, + ), + ), + ), + ) + : SizedBox( + width: MediaQuery.of(context).size.width, + child: Wrap( + alignment: WrapAlignment.spaceEvenly, + runSpacing: 16.0, + spacing: 4.0, children: cards, ), ), - ), - ) - : SizedBox( - width: MediaQuery.of(context).size.width, - child: Wrap( - alignment: WrapAlignment.spaceEvenly, - runSpacing: 16.0, - spacing: 4.0, - children: cards, + if (cards.length < 5) + Padding( + padding: const EdgeInsetsGeometry.all(16.0), + child: ErrorIndicator( + message: L10n.of(context) + .activitySuggestionTimeoutMessage, ), ), + if (cards.length < 5 && _loading) + const CircularProgressIndicator(), + if (cards.length < 5 && !_loading) + Padding( + padding: const EdgeInsetsGeometry.only(bottom: 16), + child: ElevatedButton( + onPressed: _setActivityItems, + style: ElevatedButton.styleFrom( + backgroundColor: + theme.colorScheme.primaryContainer, + foregroundColor: + theme.colorScheme.onPrimaryContainer, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + ), + ), + child: Text(L10n.of(context).tryAgain), + ), + ), + ], + ), ), ), ],