From ac12eca6b56411f4e85c84b421c9a4b85d7d068a Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Tue, 16 Sep 2025 20:10:53 +0530 Subject: [PATCH] refactor: improve app start time --- lib/core/auth/auth_state_manager.dart | 26 +-- lib/core/providers/app_providers.dart | 209 +++++++++++-------------- lib/features/chat/views/chat_page.dart | 35 +++-- lib/l10n/app_de.arb | 4 +- lib/l10n/app_en.arb | 4 +- lib/l10n/app_fr.arb | 4 +- lib/l10n/app_it.arb | 4 +- lib/l10n/app_localizations.dart | 8 +- lib/l10n/app_localizations_de.dart | 7 +- lib/l10n/app_localizations_en.dart | 5 +- lib/l10n/app_localizations_fr.dart | 7 +- lib/l10n/app_localizations_it.dart | 7 +- lib/main.dart | 18 +-- 13 files changed, 150 insertions(+), 188 deletions(-) diff --git a/lib/core/auth/auth_state_manager.dart b/lib/core/auth/auth_state_manager.dart index afa5423..662dfe5 100644 --- a/lib/core/auth/auth_state_manager.dart +++ b/lib/core/auth/auth_state_manager.dart @@ -99,10 +99,9 @@ class AuthStateManager extends StateNotifier { if (token != null && token.isNotEmpty) { DebugLogger.auth('Found stored token during initialization'); - // Validate token before setting authenticated state - final isValid = await _validateToken(token); - DebugLogger.auth('Token validation result: $isValid'); - if (isValid) { + // Fast path: trust token format to avoid blocking startup on network + final formatOk = _isValidTokenFormat(token); + if (formatOk) { state = state.copyWith( status: AuthStatus.authenticated, token: token, @@ -110,14 +109,23 @@ class AuthStateManager extends StateNotifier { clearError: true, ); - // Update API service with token + // Update API service with token and load user data in background _updateApiServiceToken(token); - - // Load user data in background _loadUserData(); + + // Background server validation; if it fails, invalidate token gracefully + Future.microtask(() async { + try { + final ok = await _validateToken(token); + DebugLogger.auth('Deferred token validation result: $ok'); + if (!ok) { + await onTokenInvalidated(); + } + } catch (_) {} + }); } else { - // Token is invalid, clear it - DebugLogger.auth('Token validation failed, deleting token'); + // Token format invalid; clear and require login + DebugLogger.auth('Token format invalid, deleting token'); await storage.deleteAuthToken(); state = state.copyWith( status: AuthStatus.unauthenticated, diff --git a/lib/core/providers/app_providers.dart b/lib/core/providers/app_providers.dart index ac6c74e..0fdfd16 100644 --- a/lib/core/providers/app_providers.dart +++ b/lib/core/providers/app_providers.dart @@ -682,137 +682,104 @@ final defaultModelProvider = FutureProvider((ref) async { if (api == null) return null; try { - // Get all available models first + // Respect manual selection if present + if (ref.read(isManualModelSelectionProvider)) { + final current = ref.read(selectedModelProvider); + if (current != null) return current; + } + + // 1) Fast path: read stored default model ID directly and select optimistically + try { + final storedDefaultId = await SettingsService.getDefaultModel(); + if (storedDefaultId != null && storedDefaultId.isNotEmpty) { + if (!ref.read(isManualModelSelectionProvider)) { + final placeholder = Model( + id: storedDefaultId, + name: storedDefaultId, + supportsStreaming: true, + ); + ref.read(selectedModelProvider.notifier).state = placeholder; + } + // Reconcile against real models in background + Future.microtask(() async { + try { + final models = await ref.read(modelsProvider.future); + Model? resolved; + try { + resolved = models.firstWhere((m) => m.id == storedDefaultId); + } catch (_) { + final byName = models + .where((m) => m.name == storedDefaultId) + .toList(); + if (byName.length == 1) resolved = byName.first; + } + resolved ??= models.isNotEmpty ? models.first : null; + if (resolved != null && !ref.read(isManualModelSelectionProvider)) { + ref.read(selectedModelProvider.notifier).state = resolved; + foundation.debugPrint( + 'DEBUG: Reconciled default model to ${resolved.name}', + ); + } + } catch (_) {} + }); + return ref.read(selectedModelProvider); + } + } catch (_) {} + + // 2) Fast server path: query server default ID without listing all models + try { + final serverDefault = await api.getDefaultModel(); + if (serverDefault != null && serverDefault.isNotEmpty) { + if (!ref.read(isManualModelSelectionProvider)) { + final placeholder = Model( + id: serverDefault, + name: serverDefault, + supportsStreaming: true, + ); + ref.read(selectedModelProvider.notifier).state = placeholder; + } + // Reconcile against real models in background + Future.microtask(() async { + try { + final models = await ref.read(modelsProvider.future); + Model? resolved; + try { + resolved = models.firstWhere((m) => m.id == serverDefault); + } catch (_) { + final byName = models + .where((m) => m.name == serverDefault) + .toList(); + if (byName.length == 1) resolved = byName.first; + } + resolved ??= models.isNotEmpty ? models.first : null; + if (resolved != null && !ref.read(isManualModelSelectionProvider)) { + ref.read(selectedModelProvider.notifier).state = resolved; + foundation.debugPrint( + 'DEBUG: Reconciled server default to ${resolved.name}', + ); + } + } catch (_) {} + }); + return ref.read(selectedModelProvider); + } + } catch (_) {} + + // 3) Fallback: fetch models and pick first available final models = await ref.read(modelsProvider.future); if (models.isEmpty) { foundation.debugPrint('DEBUG: No models available'); return null; } - - Model? selectedModel; - - // First check user's preferred default model (ID only). If an older - // name-based value is found, migrate it once to the correct ID. - final userSettings = ref.read(appSettingsProvider); - final userDefaultModelId = userSettings.defaultModel; - - if (userDefaultModelId != null && userDefaultModelId.isNotEmpty) { - try { - // Exact ID match only - selectedModel = models.firstWhere( - (model) => model.id == userDefaultModelId, - ); - foundation.debugPrint( - 'DEBUG: Found user default model by ID: ${selectedModel.name}', - ); - } catch (e) { - // Attempt a one-time migration if the stored value was a model name - // from older versions. Only migrate on exact, unique name match. - final nameMatches = models - .where((m) => m.name == userDefaultModelId) - .toList(); - if (nameMatches.length == 1) { - selectedModel = nameMatches.first; - foundation.debugPrint( - 'DEBUG: Migrating user default model name to ID: ' - '${nameMatches.first.name} -> ${nameMatches.first.id}', - ); - // Persist the migrated ID - await ref - .read(appSettingsProvider.notifier) - .setDefaultModel(nameMatches.first.id); - } else { - foundation.debugPrint( - 'DEBUG: User default model "$userDefaultModelId" not found by ID and ' - 'no unique name match. Ignoring.', - ); - selectedModel = - null; // Will fall back to server default or first model - } - } - } - - // If no user default or user default not found, try server's default model - if (selectedModel == null) { - try { - final defaultModelId = await api.getDefaultModel(); - - if (defaultModelId != null && defaultModelId.isNotEmpty) { - // Find the model that matches the default model ID (ID only) - try { - selectedModel = models.firstWhere( - (model) => model.id == defaultModelId, - ); - foundation.debugPrint( - 'DEBUG: Found server default model by ID: ${selectedModel.name}', - ); - } catch (e) { - // If server returned a name instead of ID, attempt exact name match. - final byName = models - .where((m) => m.name == defaultModelId) - .toList(); - if (byName.length == 1) { - selectedModel = byName.first; - foundation.debugPrint( - 'DEBUG: Server default "$defaultModelId" matched by name; ' - 'selected ${selectedModel.name} (${selectedModel.id})', - ); - } else { - foundation.debugPrint( - 'DEBUG: Server default model "$defaultModelId" not found by ID; ' - 'falling back to first available', - ); - selectedModel = models.first; - } - } - } else { - // No server default, use first available model - selectedModel = models.first; - foundation.debugPrint( - 'DEBUG: No server default model, using first available: ${selectedModel.name}', - ); - } - } catch (apiError) { - foundation.debugPrint( - 'DEBUG: Failed to get default model from server: $apiError', - ); - // Use first available model as fallback - selectedModel = models.first; - foundation.debugPrint( - 'DEBUG: Using first available model as fallback: ${selectedModel.name}', - ); - } - } - - // Update selection immediately inside provider context + final selectedModel = models.first; if (!ref.read(isManualModelSelectionProvider)) { ref.read(selectedModelProvider.notifier).state = selectedModel; - foundation.debugPrint('DEBUG: Set default model: ${selectedModel.name}'); + foundation.debugPrint( + 'DEBUG: Set default model (fallback): ${selectedModel.name}', + ); } - return selectedModel; } catch (e) { foundation.debugPrint('DEBUG: Error setting default model: $e'); - - // Final fallback: try to select any available model - try { - final models = await ref.read(modelsProvider.future); - if (models.isNotEmpty) { - final fallbackModel = models.first; - if (!ref.read(isManualModelSelectionProvider)) { - ref.read(selectedModelProvider.notifier).state = fallbackModel; - foundation.debugPrint( - 'DEBUG: Fallback to first available model: ${fallbackModel.name}', - ); - } - return fallbackModel; - } - } catch (fallbackError) { - foundation.debugPrint( - 'DEBUG: Error in fallback model selection: $fallbackError', - ); - } - return null; } }); @@ -826,7 +793,7 @@ final backgroundModelLoadProvider = Provider((ref) { // Schedule background loading without blocking Future.microtask(() async { // Wait a bit to ensure auth is complete - await Future.delayed(const Duration(milliseconds: 1500)); + await Future.delayed(const Duration(milliseconds: 200)); foundation.debugPrint('DEBUG: Starting background model loading'); diff --git a/lib/features/chat/views/chat_page.dart b/lib/features/chat/views/chat_page.dart index 67fca0f..dd47a96 100644 --- a/lib/features/chat/views/chat_page.dart +++ b/lib/features/chat/views/chat_page.dart @@ -279,8 +279,26 @@ class _ChatPageState extends ConsumerState { } void _handleMessageSend(String text, dynamic selectedModel) async { + // Resolve model on-demand if none selected yet if (selectedModel == null) { - return; + try { + // Prefer already-loaded models + List models; + final modelsAsync = ref.read(modelsProvider); + if (modelsAsync.hasValue) { + models = modelsAsync.value!; + } else { + models = await ref.read(modelsProvider.future); + } + if (models.isNotEmpty) { + selectedModel = models.first; + ref.read(selectedModelProvider.notifier).state = selectedModel; + } + } catch (_) { + // If models cannot be resolved, bail out without sending + return; + } + if (selectedModel == null) return; } final isOnline = ref.read(isOnlineProvider); @@ -884,16 +902,6 @@ class _ChatPageState extends ConsumerState { textAlign: TextAlign.center, ).animate().fadeIn(delay: const Duration(milliseconds: 150)), - const SizedBox(height: Spacing.sm), - - Text( - l10n.typeBelowToBegin, - style: theme.textTheme.bodyLarge?.copyWith( - color: context.conduitTheme.textSecondary, - fontWeight: FontWeight.w400, - ), - textAlign: TextAlign.center, - ).animate().fadeIn(delay: const Duration(milliseconds: 300)), ], ), ), @@ -945,8 +953,8 @@ class _ChatPageState extends ConsumerState { }); } - // Focus composer on app startup once, when a model is selected - if (!_didStartupFocus && selectedModel != null) { + // Focus composer on app startup once + if (!_didStartupFocus) { WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; final current = ref.read(inputFocusTriggerProvider); @@ -1425,7 +1433,6 @@ class _ChatPageState extends ConsumerState { }, child: ModernChatInput( enabled: - selectedModel != null && (isOnline || ref.watch(reviewerModeProvider)), onSendMessage: (text) => _handleMessageSend(text, selectedModel), diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index a24e8d5..336c0c9 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -34,6 +34,7 @@ "noFilesYet": "Noch keine Dateien", "uploadDocsPrompt": "Lade Dokumente hoch, um sie in deinen Unterhaltungen mit Conduit zu verwenden", "uploadFirstFile": "Erste Datei hochladen", + "attachments": "Anhänge", "knowledgeBaseEmpty": "Wissensdatenbank ist leer", "createCollectionsPrompt": "Erstelle Sammlungen verwandter Dokumente zur einfachen Referenz", "chooseSourcePhoto": "Quelle auswählen", @@ -91,7 +92,7 @@ "next": "Weiter", "done": "Fertig", "onboardStartTitle": "Hallo, {username}", - "onboardStartSubtitle": "Wähle ein Modell und tippe los. Tippe jederzeit auf Neuer Chat.", + "onboardStartSubtitle": "Wähle ein Modell, um loszulegen. Tippe jederzeit auf Neuer Chat.", "onboardStartBullet1": "Modellname oben antippen, um zu wechseln", "onboardStartBullet2": "Mit Neuer Chat den Kontext zurücksetzen", "onboardAttachTitle": "Kontext hinzufügen", @@ -204,7 +205,6 @@ "failedToDeleteFolder": "Ordner konnte nicht gelöscht werden", "aboutApp": "Über die App", "aboutAppSubtitle": "Conduit Informationen und Links", - "typeBelowToBegin": "Unten tippen, um zu beginnen", "web": "Web", "imageGen": "Bild-Gen", "pinned": "Angeheftet", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e78cfde..b6dc61f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -204,7 +204,7 @@ "@done": {"description": "Onboarding: finish the flow."}, "onboardStartTitle": "Hello, {username}", "@onboardStartTitle": {"description": "Onboarding card: start chatting title.", "placeholders": {"username": {"type": "String", "example": "Alex"}}}, - "onboardStartSubtitle": "Choose a model, then type below to begin. Tap New Chat anytime.", + "onboardStartSubtitle": "Choose a model to get started. Tap New Chat anytime.", "@onboardStartSubtitle": {"description": "Onboarding card: brief guidance to begin a chat."}, "onboardStartBullet1": "Tap the model name in the top bar to switch models", "@onboardStartBullet1": {"description": "Bullet: how to switch models."}, @@ -417,8 +417,6 @@ "@aboutApp": {"description": "Settings tile title to view app information."}, "aboutAppSubtitle": "Conduit information and links", "@aboutAppSubtitle": {"description": "Subtitle/description for the About section."}, - "typeBelowToBegin": "Type below to begin", - "@typeBelowToBegin": {"description": "Hint shown in empty chat input area."}, "web": "Web", "@web": {"description": "Tab/section label for web features."}, "imageGen": "Image Gen", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 3e9f70b..51da75a 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -34,6 +34,7 @@ "noFilesYet": "Pas encore de fichiers", "uploadDocsPrompt": "Importez des documents à utiliser dans vos conversations avec Conduit", "uploadFirstFile": "Importer votre premier fichier", + "attachments": "Pièces jointes", "knowledgeBaseEmpty": "La base de connaissances est vide", "createCollectionsPrompt": "Créez des collections de documents liés pour une référence facile", "chooseSourcePhoto": "Choisir la source", @@ -91,7 +92,7 @@ "next": "Suivant", "done": "Terminé", "onboardStartTitle": "Bonjour, {username}", - "onboardStartSubtitle": "Choisissez un modèle puis commencez à écrire. Touchez Nouveau chat à tout moment.", + "onboardStartSubtitle": "Choisissez un modèle pour commencer. Touchez Nouveau chat à tout moment.", "onboardStartBullet1": "Touchez le nom du modèle en haut pour changer", "onboardStartBullet2": "Utilisez Nouveau chat pour réinitialiser le contexte", "onboardAttachTitle": "Ajouter du contexte", @@ -204,7 +205,6 @@ "failedToDeleteFolder": "Échec de la suppression du dossier", "aboutApp": "À propos de l’application", "aboutAppSubtitle": "Informations et liens Conduit", - "typeBelowToBegin": "Saisissez ci‑dessous pour commencer", "web": "Web", "imageGen": "Gén. image", "pinned": "Épinglé", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 7036061..5858287 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -34,6 +34,7 @@ "noFilesYet": "Ancora nessun file", "uploadDocsPrompt": "Carica documenti da usare nelle conversazioni con Conduit", "uploadFirstFile": "Carica il tuo primo file", + "attachments": "Allegati", "knowledgeBaseEmpty": "La base di conoscenza è vuota", "createCollectionsPrompt": "Crea raccolte di documenti correlati per un rapido riferimento", "chooseSourcePhoto": "Scegli origine", @@ -91,7 +92,7 @@ "next": "Avanti", "done": "Fatto", "onboardStartTitle": "Ciao, {username}", - "onboardStartSubtitle": "Scegli un modello e inizia a scrivere. Tocca Nuova chat in qualsiasi momento.", + "onboardStartSubtitle": "Scegli un modello per iniziare. Tocca Nuova chat in qualsiasi momento.", "onboardStartBullet1": "Tocca il nome del modello in alto per cambiare", "onboardStartBullet2": "Usa Nuova chat per azzerare il contesto", "onboardAttachTitle": "Aggiungi contesto", @@ -204,7 +205,6 @@ "failedToDeleteFolder": "Impossibile eliminare la cartella", "aboutApp": "Informazioni sull’app", "aboutAppSubtitle": "Informazioni e link di Conduit", - "typeBelowToBegin": "Scrivi qui sotto per iniziare", "web": "Web", "imageGen": "Gen. immagini", "pinned": "Fissati", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 4e3400e..5094099 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -657,7 +657,7 @@ abstract class AppLocalizations { /// Onboarding card: brief guidance to begin a chat. /// /// In en, this message translates to: - /// **'Choose a model, then type below to begin. Tap New Chat anytime.'** + /// **'Choose a model to get started. Tap New Chat anytime.'** String get onboardStartSubtitle; /// Bullet: how to switch models. @@ -1218,12 +1218,6 @@ abstract class AppLocalizations { /// **'Conduit information and links'** String get aboutAppSubtitle; - /// Hint shown in empty chat input area. - /// - /// In en, this message translates to: - /// **'Type below to begin'** - String get typeBelowToBegin; - /// Tab/section label for web features. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 3444fd4..b3c8359 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -125,7 +125,7 @@ class AppLocalizationsDe extends AppLocalizations { String get uploadFirstFile => 'Erste Datei hochladen'; @override - String get attachments => 'Attachments'; + String get attachments => 'Anhänge'; @override String get knowledgeBaseEmpty => 'Wissensdatenbank ist leer'; @@ -320,7 +320,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get onboardStartSubtitle => - 'Wähle ein Modell und tippe los. Tippe jederzeit auf Neuer Chat.'; + 'Wähle ein Modell, um loszulegen. Tippe jederzeit auf Neuer Chat.'; @override String get onboardStartBullet1 => 'Modellname oben antippen, um zu wechseln'; @@ -621,9 +621,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get aboutAppSubtitle => 'Conduit Informationen und Links'; - @override - String get typeBelowToBegin => 'Unten tippen, um zu beginnen'; - @override String get web => 'Web'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 2424d23..10677d4 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -316,7 +316,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get onboardStartSubtitle => - 'Choose a model, then type below to begin. Tap New Chat anytime.'; + 'Choose a model to get started. Tap New Chat anytime.'; @override String get onboardStartBullet1 => @@ -616,9 +616,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get aboutAppSubtitle => 'Conduit information and links'; - @override - String get typeBelowToBegin => 'Type below to begin'; - @override String get web => 'Web'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 7dee4ca..6be1774 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -124,7 +124,7 @@ class AppLocalizationsFr extends AppLocalizations { String get uploadFirstFile => 'Importer votre premier fichier'; @override - String get attachments => 'Attachments'; + String get attachments => 'Pièces jointes'; @override String get knowledgeBaseEmpty => 'La base de connaissances est vide'; @@ -321,7 +321,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String get onboardStartSubtitle => - 'Choisissez un modèle puis commencez à écrire. Touchez Nouveau chat à tout moment.'; + 'Choisissez un modèle pour commencer. Touchez Nouveau chat à tout moment.'; @override String get onboardStartBullet1 => @@ -626,9 +626,6 @@ class AppLocalizationsFr extends AppLocalizations { @override String get aboutAppSubtitle => 'Informations et liens Conduit'; - @override - String get typeBelowToBegin => 'Saisissez ci‑dessous pour commencer'; - @override String get web => 'Web'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index a255f13..3abe97b 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -123,7 +123,7 @@ class AppLocalizationsIt extends AppLocalizations { String get uploadFirstFile => 'Carica il tuo primo file'; @override - String get attachments => 'Attachments'; + String get attachments => 'Allegati'; @override String get knowledgeBaseEmpty => 'La base di conoscenza è vuota'; @@ -316,7 +316,7 @@ class AppLocalizationsIt extends AppLocalizations { @override String get onboardStartSubtitle => - 'Scegli un modello e inizia a scrivere. Tocca Nuova chat in qualsiasi momento.'; + 'Scegli un modello per iniziare. Tocca Nuova chat in qualsiasi momento.'; @override String get onboardStartBullet1 => @@ -619,9 +619,6 @@ class AppLocalizationsIt extends AppLocalizations { @override String get aboutAppSubtitle => 'Informazioni e link di Conduit'; - @override - String get typeBelowToBegin => 'Scrivi qui sotto per iniziare'; - @override String get web => 'Web'; diff --git a/lib/main.dart b/lib/main.dart index 4931487..d719313 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,7 +26,9 @@ void main() async { // Enable edge-to-edge globally (back-compat on pre-Android 15) // Pairs with Activity's EdgeToEdge.enable and our SafeArea usage. - await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + // Do not block first frame on system UI mode; apply shortly after startup + // ignore: discarded_futures + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); final sharedPrefs = await SharedPreferences.getInstance(); const secureStorage = FlutterSecureStorage( @@ -65,7 +67,8 @@ class _ConduitAppState extends ConsumerState { @override void initState() { super.initState(); - _initializeAppState(); + // Defer heavy provider initialization to after first frame to render UI sooner + WidgetsBinding.instance.addPostFrameCallback((_) => _initializeAppState()); } Widget _buildInitialLoadingSkeleton(BuildContext context) { @@ -142,14 +145,11 @@ class _ConduitAppState extends ConsumerState { return MediaQuery( data: MediaQuery.of(context).copyWith( // Ensure proper text scaling for edge-to-edge - textScaler: MediaQuery.of(context).textScaler.clamp( - minScaleFactor: 0.8, - maxScaleFactor: 1.3, - ), - ), - child: OfflineIndicator( - child: child ?? const SizedBox.shrink(), + textScaler: MediaQuery.of( + context, + ).textScaler.clamp(minScaleFactor: 0.8, maxScaleFactor: 1.3), ), + child: OfflineIndicator(child: child ?? const SizedBox.shrink()), ); }, home: _getInitialPageWithReactiveState(),