refactor: improve app start time
This commit is contained in:
@@ -99,10 +99,9 @@ class AuthStateManager extends StateNotifier<AuthState> {
|
||||
|
||||
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<AuthState> {
|
||||
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,
|
||||
|
||||
@@ -682,137 +682,104 @@ final defaultModelProvider = FutureProvider<Model?>((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<void>((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');
|
||||
|
||||
|
||||
@@ -279,9 +279,27 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
}
|
||||
|
||||
void _handleMessageSend(String text, dynamic selectedModel) async {
|
||||
// Resolve model on-demand if none selected yet
|
||||
if (selectedModel == null) {
|
||||
try {
|
||||
// Prefer already-loaded models
|
||||
List<Model> 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);
|
||||
final isReviewerMode = ref.read(reviewerModeProvider);
|
||||
@@ -884,16 +902,6 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
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<ChatPage> {
|
||||
});
|
||||
}
|
||||
|
||||
// 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<ChatPage> {
|
||||
},
|
||||
child: ModernChatInput(
|
||||
enabled:
|
||||
selectedModel != null &&
|
||||
(isOnline || ref.watch(reviewerModeProvider)),
|
||||
onSendMessage: (text) =>
|
||||
_handleMessageSend(text, selectedModel),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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é",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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<ConduitApp> {
|
||||
@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<ConduitApp> {
|
||||
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(),
|
||||
|
||||
Reference in New Issue
Block a user