refactor: app customization

This commit is contained in:
cogwheel0
2025-09-07 11:29:29 +05:30
parent a16fb86e27
commit 0116a5be7b
12 changed files with 524 additions and 247 deletions

View File

@@ -43,13 +43,11 @@ class EnhancedImageAttachment extends ConsumerStatefulWidget {
class _EnhancedImageAttachmentState
extends ConsumerState<EnhancedImageAttachment>
with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
with AutomaticKeepAliveClientMixin {
String? _cachedImageData;
bool _isLoading = true;
String? _errorMessage;
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
bool _hasShownContent = false;
// Removed unused animation and state flags
@override
bool get wantKeepAlive => true;
@@ -57,14 +55,6 @@ class _EnhancedImageAttachmentState
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
_fadeAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
// Defer loading until after first frame to avoid accessing inherited widgets
// (e.g., Localizations) during initState
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -75,7 +65,6 @@ class _EnhancedImageAttachmentState
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@@ -87,11 +76,7 @@ class _EnhancedImageAttachmentState
setState(() {
_cachedImageData = _globalImageCache[widget.attachmentId];
_isLoading = false;
_hasShownContent = true;
});
if (!widget.disableAnimation) {
_animationController.forward();
}
}
return;
}
@@ -119,11 +104,7 @@ class _EnhancedImageAttachmentState
setState(() {
_cachedImageData = widget.attachmentId;
_isLoading = false;
_hasShownContent = true;
});
if (!widget.disableAnimation) {
_animationController.forward();
}
}
return;
}
@@ -140,11 +121,7 @@ class _EnhancedImageAttachmentState
setState(() {
_cachedImageData = fullUrl;
_isLoading = false;
_hasShownContent = true;
});
if (!widget.disableAnimation) {
_animationController.forward();
}
}
return;
} else {
@@ -214,11 +191,7 @@ class _EnhancedImageAttachmentState
setState(() {
_cachedImageData = fileContent;
_isLoading = false;
_hasShownContent = true;
});
if (!widget.disableAnimation) {
_animationController.forward();
}
}
} catch (e) {
final error = l10n.failedToLoadImage(e.toString());

View File

@@ -8,6 +8,8 @@ import '../../../core/services/settings_service.dart';
import '../../../shared/theme/theme_extensions.dart';
import '../../../shared/widgets/conduit_components.dart';
import '../../../shared/utils/ui_utils.dart';
import '../../../core/providers/app_providers.dart';
import '../../../l10n/app_localizations.dart';
class AppCustomizationPage extends ConsumerWidget {
const AppCustomizationPage({super.key});
@@ -15,6 +17,13 @@ class AppCustomizationPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final settings = ref.watch(appSettingsProvider);
final themeMode = ref.watch(themeModeProvider);
final platformBrightness = MediaQuery.platformBrightnessOf(context);
final bool isDarkEffective =
themeMode == ThemeMode.dark ||
(themeMode == ThemeMode.system &&
platformBrightness == Brightness.dark);
final locale = ref.watch(localeProvider);
return Scaffold(
backgroundColor: context.conduitTheme.surfaceBackground,
@@ -30,10 +39,10 @@ class AppCustomizationPage extends ConsumerWidget {
color: context.conduitTheme.textPrimary,
),
onPressed: () => Navigator.of(context).maybePop(),
tooltip: 'Back',
tooltip: AppLocalizations.of(context)!.back,
),
title: Text(
'App Customization',
AppLocalizations.of(context)!.appCustomization,
style: AppTypography.headlineSmallStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
@@ -47,7 +56,7 @@ class AppCustomizationPage extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Display',
AppLocalizations.of(context)!.display,
style: context.conduitTheme.headingSmall?.copyWith(
color: context.conduitTheme.textPrimary,
),
@@ -57,6 +66,150 @@ class AppCustomizationPage extends ConsumerWidget {
padding: EdgeInsets.zero,
child: Column(
children: [
// Dark mode toggle
ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.listItemPadding,
vertical: Spacing.sm,
),
leading: Container(
padding: const EdgeInsets.all(Spacing.sm),
decoration: BoxDecoration(
color: context.conduitTheme.buttonPrimary
.withValues(alpha: Alpha.highlight),
borderRadius:
BorderRadius.circular(AppBorderRadius.small),
),
child: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.moon_stars,
android: Icons.dark_mode,
),
color: context.conduitTheme.buttonPrimary,
size: IconSize.medium,
),
),
title: Text(
AppLocalizations.of(context)!.darkMode,
style: context.conduitTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
themeMode == ThemeMode.system
? AppLocalizations.of(context)!.followingSystem(
platformBrightness == Brightness.dark
? AppLocalizations.of(context)!.themeDark
: AppLocalizations.of(context)!.themeLight,
)
: (isDarkEffective
? AppLocalizations.of(context)!
.currentlyUsingDarkTheme
: AppLocalizations.of(context)!
.currentlyUsingLightTheme),
style: context.conduitTheme.bodySmall?.copyWith(
color: context.conduitTheme.textSecondary,
),
),
trailing: Switch.adaptive(
value: isDarkEffective,
onChanged: (value) {
ref
.read(themeModeProvider.notifier)
.setTheme(value ? ThemeMode.dark : ThemeMode.light);
},
),
onTap: () {
final newValue = !isDarkEffective;
ref
.read(themeModeProvider.notifier)
.setTheme(
newValue ? ThemeMode.dark : ThemeMode.light);
},
),
Divider(color: context.conduitTheme.dividerColor, height: 1),
// App language selector
Builder(builder: (context) {
final currentCode = locale?.languageCode ?? 'system';
final label = () {
switch (currentCode) {
case 'en':
return 'English';
case 'de':
return 'Deutsch';
case 'fr':
return 'Français';
case 'it':
return 'Italiano';
default:
return 'System';
}
}();
return ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.listItemPadding,
vertical: Spacing.sm,
),
leading: Container(
padding: const EdgeInsets.all(Spacing.sm),
decoration: BoxDecoration(
color: context.conduitTheme.buttonPrimary
.withValues(alpha: Alpha.highlight),
borderRadius:
BorderRadius.circular(AppBorderRadius.small),
),
child: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.globe,
android: Icons.language,
),
color: context.conduitTheme.buttonPrimary,
size: IconSize.medium,
),
),
title: Text(
AppLocalizations.of(context)!.appLanguage,
style: context.conduitTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
label,
style: context.conduitTheme.bodySmall?.copyWith(
color: context.conduitTheme.textSecondary,
),
),
trailing: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.chevron_right,
android: Icons.chevron_right,
),
color: context.conduitTheme.iconSecondary,
size: IconSize.small,
),
onTap: () async {
final selected = await _showLanguageSelector(
context, currentCode);
if (selected != null) {
if (selected == 'system') {
await ref
.read(localeProvider.notifier)
.setLocale(null);
} else {
await ref
.read(localeProvider.notifier)
.setLocale(Locale(selected));
}
}
},
);
}),
Divider(color: context.conduitTheme.dividerColor, height: 1),
SwitchListTile.adaptive(
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.listItemPadding,
@@ -65,14 +218,15 @@ class AppCustomizationPage extends ConsumerWidget {
// Use platform defaults for switch colors to match theme
value: settings.omitProviderInModelName,
title: Text(
'Hide provider in model names',
AppLocalizations.of(context)!.hideProviderInModelNames,
style: context.conduitTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
'Show names like "gpt-4o" instead of "openai/gpt-4o".',
AppLocalizations.of(context)!
.hideProviderInModelNamesDescription,
style: context.conduitTheme.bodySmall?.copyWith(
color: context.conduitTheme.textSecondary,
),
@@ -105,7 +259,7 @@ class AppCustomizationPage extends ConsumerWidget {
const SizedBox(height: Spacing.lg),
Text(
'Realtime',
AppLocalizations.of(context)!.realtime,
style: context.conduitTheme.headingSmall?.copyWith(
color: context.conduitTheme.textPrimary,
),
@@ -138,14 +292,14 @@ class AppCustomizationPage extends ConsumerWidget {
),
),
title: Text(
'Transport mode',
AppLocalizations.of(context)!.transportMode,
style: context.conduitTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
),
),
subtitle: Text(
'Choose how the app connects for realtime updates.',
AppLocalizations.of(context)!.transportModeDescription,
style: context.conduitTheme.bodySmall?.copyWith(
color: context.conduitTheme.textSecondary,
),
@@ -166,21 +320,23 @@ class AppCustomizationPage extends ConsumerWidget {
.read(appSettingsProvider.notifier)
.setSocketTransportMode(v);
},
items: const [
items: [
DropdownMenuItem(
value: 'auto',
child: Text('Auto (Polling + WebSocket)'),
child: Text(AppLocalizations.of(context)!
.transportModeAuto),
),
DropdownMenuItem(
value: 'ws',
child: Text('WebSocket only'),
child: Text(AppLocalizations.of(context)!
.transportModeWs),
),
],
decoration: const InputDecoration(
labelText: 'Mode',
border: OutlineInputBorder(),
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.mode,
border: const OutlineInputBorder(),
isDense: true,
contentPadding: EdgeInsets.symmetric(
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
@@ -196,8 +352,10 @@ class AppCustomizationPage extends ConsumerWidget {
),
child: Text(
settings.socketTransportMode == 'auto'
? 'More robust on restrictive networks. Upgrades to WebSocket when possible.'
: 'Lower overhead, but may fail behind strict proxies/firewalls.',
? AppLocalizations.of(context)!
.transportModeAutoInfo
: AppLocalizations.of(context)!
.transportModeWsInfo,
style: context.conduitTheme.caption?.copyWith(
color: context.conduitTheme.textSecondary,
),
@@ -211,4 +369,56 @@ class AppCustomizationPage extends ConsumerWidget {
),
);
}
Future<String?> _showLanguageSelector(BuildContext context, String current) {
return showModalBottomSheet<String>(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (context) => Container(
decoration: BoxDecoration(
color: context.conduitTheme.surfaceBackground,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(AppBorderRadius.modal),
),
boxShadow: ConduitShadows.modal,
),
child: SafeArea(
top: false,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: Spacing.sm),
ListTile(
title: Text(AppLocalizations.of(context)!.system),
trailing: current == 'system' ? const Icon(Icons.check) : null,
onTap: () => Navigator.pop(context, 'system'),
),
ListTile(
title: Text(AppLocalizations.of(context)!.english),
trailing: current == 'en' ? const Icon(Icons.check) : null,
onTap: () => Navigator.pop(context, 'en'),
),
ListTile(
title: Text(AppLocalizations.of(context)!.deutsch),
trailing: current == 'de' ? const Icon(Icons.check) : null,
onTap: () => Navigator.pop(context, 'de'),
),
ListTile(
title: Text(AppLocalizations.of(context)!.francais),
trailing: current == 'fr' ? const Icon(Icons.check) : null,
onTap: () => Navigator.pop(context, 'fr'),
),
ListTile(
title: Text(AppLocalizations.of(context)!.italiano),
trailing: current == 'it' ? const Icon(Icons.check) : null,
onTap: () => Navigator.pop(context, 'it'),
),
const SizedBox(height: Spacing.sm),
],
),
),
),
);
}
}

View File

@@ -229,17 +229,13 @@ class ProfilePage extends ConsumerWidget {
children: [
_buildDefaultModelTile(context, ref),
Divider(color: context.conduitTheme.dividerColor, height: 1),
_buildThemeToggleTile(context, ref),
Divider(color: context.conduitTheme.dividerColor, height: 1),
_buildLanguageTile(context, ref),
Divider(color: context.conduitTheme.dividerColor, height: 1),
_buildAccountOption(
icon: UiUtils.platformIcon(
ios: CupertinoIcons.slider_horizontal_3,
android: Icons.tune,
),
title: 'App Customization',
subtitle: 'Personalize how names and UI display',
title: AppLocalizations.of(context)!.appCustomization,
subtitle: AppLocalizations.of(context)!.appCustomizationSubtitle,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
@@ -463,199 +459,7 @@ class ProfilePage extends ConsumerWidget {
);
}
Widget _buildLanguageTile(BuildContext context, WidgetRef ref) {
final locale = ref.watch(localeProvider);
final currentCode = locale?.languageCode ?? 'system';
final label = () {
switch (currentCode) {
case 'en':
return 'English';
case 'de':
return 'Deutsch';
case 'fr':
return 'Français';
case 'it':
return 'Italiano';
default:
return 'System';
}
}();
return ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.listItemPadding,
vertical: Spacing.sm,
),
leading: Container(
padding: const EdgeInsets.all(Spacing.sm),
decoration: BoxDecoration(
color: context.conduitTheme.buttonPrimary.withValues(
alpha: Alpha.highlight,
),
borderRadius: BorderRadius.circular(AppBorderRadius.small),
),
child: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.globe,
android: Icons.language,
),
color: context.conduitTheme.buttonPrimary,
size: IconSize.medium,
),
),
title: Text(
AppLocalizations.of(context)!.appLanguage,
style: context.conduitTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
label,
style: context.conduitTheme.bodySmall?.copyWith(
color: context.conduitTheme.textSecondary,
),
),
trailing: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.chevron_right,
android: Icons.chevron_right,
),
color: context.conduitTheme.iconSecondary,
size: IconSize.small,
),
onTap: () async {
final selected = await _showLanguageSelector(context, currentCode);
if (selected != null) {
if (selected == 'system') {
await ref.read(localeProvider.notifier).setLocale(null);
} else {
await ref.read(localeProvider.notifier).setLocale(Locale(selected));
}
}
},
);
}
Future<String?> _showLanguageSelector(BuildContext context, String current) {
return showModalBottomSheet<String>(
context: context,
backgroundColor: Colors.transparent,
isScrollControlled: true,
builder: (context) => Container(
decoration: BoxDecoration(
color: context.conduitTheme.surfaceBackground,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(AppBorderRadius.modal),
),
boxShadow: ConduitShadows.modal,
),
child: SafeArea(
top: false,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: Spacing.sm),
ListTile(
title: Text(AppLocalizations.of(context)!.system),
trailing: current == 'system' ? const Icon(Icons.check) : null,
onTap: () => Navigator.pop(context, 'system'),
),
ListTile(
title: Text(AppLocalizations.of(context)!.english),
trailing: current == 'en' ? const Icon(Icons.check) : null,
onTap: () => Navigator.pop(context, 'en'),
),
ListTile(
title: Text(AppLocalizations.of(context)!.deutsch),
trailing: current == 'de' ? const Icon(Icons.check) : null,
onTap: () => Navigator.pop(context, 'de'),
),
ListTile(
title: Text(AppLocalizations.of(context)!.francais),
trailing: current == 'fr' ? const Icon(Icons.check) : null,
onTap: () => Navigator.pop(context, 'fr'),
),
ListTile(
title: Text(AppLocalizations.of(context)!.italiano),
trailing: current == 'it' ? const Icon(Icons.check) : null,
onTap: () => Navigator.pop(context, 'it'),
),
const SizedBox(height: Spacing.sm),
],
),
),
),
);
}
Widget _buildThemeToggleTile(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeModeProvider);
final platformBrightness = MediaQuery.platformBrightnessOf(context);
final bool isDarkEffective =
themeMode == ThemeMode.dark ||
(themeMode == ThemeMode.system &&
platformBrightness == Brightness.dark);
return ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.listItemPadding,
vertical: Spacing.sm,
),
leading: Container(
padding: const EdgeInsets.all(Spacing.sm),
decoration: BoxDecoration(
color: context.conduitTheme.buttonPrimary.withValues(
alpha: Alpha.highlight,
),
borderRadius: BorderRadius.circular(AppBorderRadius.small),
),
child: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.moon_stars,
android: Icons.dark_mode,
),
color: context.conduitTheme.buttonPrimary,
size: IconSize.medium,
),
),
title: Text(
AppLocalizations.of(context)!.darkMode,
style: context.conduitTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
themeMode == ThemeMode.system
? AppLocalizations.of(context)!.followingSystem(
platformBrightness == Brightness.dark
? AppLocalizations.of(context)!.themeDark
: AppLocalizations.of(context)!.themeLight,
)
: (isDarkEffective
? AppLocalizations.of(context)!.currentlyUsingDarkTheme
: AppLocalizations.of(context)!.currentlyUsingLightTheme),
style: context.conduitTheme.bodySmall?.copyWith(
color: context.conduitTheme.textSecondary,
),
),
trailing: Switch.adaptive(
value: isDarkEffective,
onChanged: (value) {
ref
.read(themeModeProvider.notifier)
.setTheme(value ? ThemeMode.dark : ThemeMode.light);
},
),
onTap: () {
final newValue = !isDarkEffective;
ref
.read(themeModeProvider.notifier)
.setTheme(newValue ? ThemeMode.dark : ThemeMode.light);
},
);
}
// Theme and language controls moved to AppCustomizationPage.
Widget _buildAboutTile(BuildContext context) {
return _buildAccountOption(

View File

@@ -259,4 +259,18 @@
"description": "Zeigt an, wie lange der Assistent nachgedacht hat.",
"placeholders": {"duration": {"type": "String", "example": "3 s"}}
}
,
"appCustomization": "App-Anpassung",
"appCustomizationSubtitle": "Personalisieren, wie Namen und UI angezeigt werden",
"display": "Anzeige",
"realtime": "Echtzeit",
"hideProviderInModelNames": "Anbieter in Modellnamen ausblenden",
"hideProviderInModelNamesDescription": "Zeige Namen wie \"gpt-4o\" statt \"openai/gpt-4o\".",
"transportMode": "Transportmodus",
"transportModeDescription": "Wähle, wie die App für Echtzeit-Updates verbindet.",
"mode": "Modus",
"transportModeAuto": "Auto (Polling + WebSocket)",
"transportModeWs": "Nur WebSocket",
"transportModeAutoInfo": "Robuster in restriktiven Netzwerken. Wechselt nach Möglichkeit zu WebSocket.",
"transportModeWsInfo": "Geringerer Overhead, kann jedoch hinter strikten Proxys/Firewalls fehlschlagen."
}

View File

@@ -283,4 +283,18 @@
"description": "Shows how long the assistant thought before replying.",
"placeholders": {"duration": {"type": "String", "example": "3s"}}
}
,
"appCustomization": "App Customization",
"appCustomizationSubtitle": "Personalize how names and UI display",
"display": "Display",
"realtime": "Realtime",
"hideProviderInModelNames": "Hide provider in model names",
"hideProviderInModelNamesDescription": "Show names like \"gpt-4o\" instead of \"openai/gpt-4o\".",
"transportMode": "Transport mode",
"transportModeDescription": "Choose how the app connects for realtime updates.",
"mode": "Mode",
"transportModeAuto": "Auto (Polling + WebSocket)",
"transportModeWs": "WebSocket only",
"transportModeAutoInfo": "More robust on restrictive networks. Upgrades to WebSocket when possible.",
"transportModeWsInfo": "Lower overhead, but may fail behind strict proxies/firewalls."
}

View File

@@ -259,4 +259,18 @@
"description": "Indique la durée de réflexion de l'assistant.",
"placeholders": {"duration": {"type": "String", "example": "3 s"}}
}
,
"appCustomization": "Personnalisation de l'app",
"appCustomizationSubtitle": "Personnalisez l'affichage des noms et de l'UI",
"display": "Affichage",
"realtime": "Temps réel",
"hideProviderInModelNames": "Masquer le fournisseur dans les noms de modèles",
"hideProviderInModelNamesDescription": "Afficher des noms comme \"gpt-4o\" au lieu de \"openai/gpt-4o\".",
"transportMode": "Mode de transport",
"transportModeDescription": "Choisissez comment l'app se connecte pour les mises à jour en temps réel.",
"mode": "Mode",
"transportModeAuto": "Auto (Polling + WebSocket)",
"transportModeWs": "WebSocket uniquement",
"transportModeAutoInfo": "Plus robuste sur les réseaux restrictifs. Passe à WebSocket lorsque possible.",
"transportModeWsInfo": "Moins de surcharge, mais peut échouer derrière des proxys/firewalls stricts."
}

View File

@@ -259,4 +259,18 @@
"description": "Mostra per quanto tempo ha pensato l'assistente.",
"placeholders": {"duration": {"type": "String", "example": "3 s"}}
}
,
"appCustomization": "Personalizzazione app",
"appCustomizationSubtitle": "Personalizza la visualizzazione dei nomi e dell'UI",
"display": "Schermo",
"realtime": "Tempo reale",
"hideProviderInModelNames": "Nascondi provider nei nomi dei modelli",
"hideProviderInModelNamesDescription": "Mostra nomi come \"gpt-4o\" invece di \"openai/gpt-4o\".",
"transportMode": "Modalità di trasporto",
"transportModeDescription": "Scegli come l'app si connette per gli aggiornamenti in tempo reale.",
"mode": "Modalità",
"transportModeAuto": "Auto (Polling + WebSocket)",
"transportModeWs": "Solo WebSocket",
"transportModeAutoInfo": "Più robusto nelle reti restrittive. Passa a WebSocket quando possibile.",
"transportModeWsInfo": "Minore overhead, ma può fallire dietro proxy/firewall restrittivi."
}

View File

@@ -1230,13 +1230,13 @@ abstract class AppLocalizations {
/// No description provided for @appLanguage.
///
/// In en, this message translates to:
/// **'App language'**
/// **'App Language'**
String get appLanguage;
/// No description provided for @darkMode.
///
/// In en, this message translates to:
/// **'Dark mode'**
/// **'Dark Mode'**
String get darkMode;
/// No description provided for @webSearch.
@@ -1472,6 +1472,84 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Thought for {duration}'**
String thoughtForDuration(String duration);
/// No description provided for @appCustomization.
///
/// In en, this message translates to:
/// **'App Customization'**
String get appCustomization;
/// No description provided for @appCustomizationSubtitle.
///
/// In en, this message translates to:
/// **'Personalize how names and UI display'**
String get appCustomizationSubtitle;
/// No description provided for @display.
///
/// In en, this message translates to:
/// **'Display'**
String get display;
/// No description provided for @realtime.
///
/// In en, this message translates to:
/// **'Realtime'**
String get realtime;
/// No description provided for @hideProviderInModelNames.
///
/// In en, this message translates to:
/// **'Hide provider in model names'**
String get hideProviderInModelNames;
/// No description provided for @hideProviderInModelNamesDescription.
///
/// In en, this message translates to:
/// **'Show names like \"gpt-4o\" instead of \"openai/gpt-4o\".'**
String get hideProviderInModelNamesDescription;
/// No description provided for @transportMode.
///
/// In en, this message translates to:
/// **'Transport mode'**
String get transportMode;
/// No description provided for @transportModeDescription.
///
/// In en, this message translates to:
/// **'Choose how the app connects for realtime updates.'**
String get transportModeDescription;
/// No description provided for @mode.
///
/// In en, this message translates to:
/// **'Mode'**
String get mode;
/// No description provided for @transportModeAuto.
///
/// In en, this message translates to:
/// **'Auto (Polling + WebSocket)'**
String get transportModeAuto;
/// No description provided for @transportModeWs.
///
/// In en, this message translates to:
/// **'WebSocket only'**
String get transportModeWs;
/// No description provided for @transportModeAutoInfo.
///
/// In en, this message translates to:
/// **'More robust on restrictive networks. Upgrades to WebSocket when possible.'**
String get transportModeAutoInfo;
/// No description provided for @transportModeWsInfo.
///
/// In en, this message translates to:
/// **'Lower overhead, but may fail behind strict proxies/firewalls.'**
String get transportModeWsInfo;
}
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -727,4 +727,43 @@ class AppLocalizationsDe extends AppLocalizations {
String thoughtForDuration(String duration) {
return 'Gedacht für $duration';
}
@override
String get appCustomization => 'App-Anpassung';
@override
String get appCustomizationSubtitle => 'Personalisieren, wie Namen und UI angezeigt werden';
@override
String get display => 'Anzeige';
@override
String get realtime => 'Echtzeit';
@override
String get hideProviderInModelNames => 'Anbieter in Modellnamen ausblenden';
@override
String get hideProviderInModelNamesDescription => 'Zeige Namen wie \"gpt-4o\" statt \"openai/gpt-4o\".';
@override
String get transportMode => 'Transportmodus';
@override
String get transportModeDescription => 'Wähle, wie die App für Echtzeit-Updates verbindet.';
@override
String get mode => 'Modus';
@override
String get transportModeAuto => 'Auto (Polling + WebSocket)';
@override
String get transportModeWs => 'Nur WebSocket';
@override
String get transportModeAutoInfo => 'Robuster in restriktiven Netzwerken. Wechselt nach Möglichkeit zu WebSocket.';
@override
String get transportModeWsInfo => 'Geringerer Overhead, kann jedoch hinter strikten Proxys/Firewalls fehlschlagen.';
}

View File

@@ -727,4 +727,43 @@ class AppLocalizationsEn extends AppLocalizations {
String thoughtForDuration(String duration) {
return 'Thought for $duration';
}
@override
String get appCustomization => 'App Customization';
@override
String get appCustomizationSubtitle => 'Personalize how names and UI display';
@override
String get display => 'Display';
@override
String get realtime => 'Realtime';
@override
String get hideProviderInModelNames => 'Hide provider in model names';
@override
String get hideProviderInModelNamesDescription => 'Show names like \"gpt-4o\" instead of \"openai/gpt-4o\".';
@override
String get transportMode => 'Transport mode';
@override
String get transportModeDescription => 'Choose how the app connects for realtime updates.';
@override
String get mode => 'Mode';
@override
String get transportModeAuto => 'Auto (Polling + WebSocket)';
@override
String get transportModeWs => 'WebSocket only';
@override
String get transportModeAutoInfo => 'More robust on restrictive networks. Upgrades to WebSocket when possible.';
@override
String get transportModeWsInfo => 'Lower overhead, but may fail behind strict proxies/firewalls.';
}

View File

@@ -727,4 +727,43 @@ class AppLocalizationsFr extends AppLocalizations {
String thoughtForDuration(String duration) {
return 'A réfléchi pendant $duration';
}
@override
String get appCustomization => 'Personnalisation de l\'app';
@override
String get appCustomizationSubtitle => 'Personnalisez l\'affichage des noms et de l\'UI';
@override
String get display => 'Affichage';
@override
String get realtime => 'Temps réel';
@override
String get hideProviderInModelNames => 'Masquer le fournisseur dans les noms de modèles';
@override
String get hideProviderInModelNamesDescription => 'Afficher des noms comme \"gpt-4o\" au lieu de \"openai/gpt-4o\".';
@override
String get transportMode => 'Mode de transport';
@override
String get transportModeDescription => 'Choisissez comment l\'app se connecte pour les mises à jour en temps réel.';
@override
String get mode => 'Mode';
@override
String get transportModeAuto => 'Auto (Polling + WebSocket)';
@override
String get transportModeWs => 'WebSocket uniquement';
@override
String get transportModeAutoInfo => 'Plus robuste sur les réseaux restrictifs. Passe à WebSocket lorsque possible.';
@override
String get transportModeWsInfo => 'Moins de surcharge, mais peut échouer derrière des proxys/firewalls stricts.';
}

View File

@@ -727,4 +727,43 @@ class AppLocalizationsIt extends AppLocalizations {
String thoughtForDuration(String duration) {
return 'Ha pensato per $duration';
}
@override
String get appCustomization => 'Personalizzazione app';
@override
String get appCustomizationSubtitle => 'Personalizza la visualizzazione dei nomi e dell\'UI';
@override
String get display => 'Schermo';
@override
String get realtime => 'Tempo reale';
@override
String get hideProviderInModelNames => 'Nascondi provider nei nomi dei modelli';
@override
String get hideProviderInModelNamesDescription => 'Mostra nomi come \"gpt-4o\" invece di \"openai/gpt-4o\".';
@override
String get transportMode => 'Modalità di trasporto';
@override
String get transportModeDescription => 'Scegli come l\'app si connette per gli aggiornamenti in tempo reale.';
@override
String get mode => 'Modalità';
@override
String get transportModeAuto => 'Auto (Polling + WebSocket)';
@override
String get transportModeWs => 'Solo WebSocket';
@override
String get transportModeAutoInfo => 'Più robusto nelle reti restrittive. Passa a WebSocket quando possibile.';
@override
String get transportModeWsInfo => 'Minore overhead, ma può fallire dietro proxy/firewall restrittivi.';
}