refactor: settings pages

This commit is contained in:
cogwheel0
2025-09-20 23:02:59 +05:30
parent 8d89fd79b1
commit 3db5a8b760
11 changed files with 998 additions and 895 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -43,129 +43,24 @@ class ProfilePage extends ConsumerWidget {
return ErrorBoundary( return ErrorBoundary(
child: user.when( child: user.when(
data: (userData) => Scaffold( data: (userData) => _buildScaffold(
backgroundColor: context.conduitTheme.surfaceBackground, context,
appBar: AppBar( body: _buildProfileBody(context, ref, userData, api),
backgroundColor: context.conduitTheme.surfaceBackground,
elevation: Elevation.none,
toolbarHeight: kToolbarHeight - 8,
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.back,
android: Icons.arrow_back,
),
color: context.conduitTheme.textPrimary,
),
onPressed: () => Navigator.of(context).maybePop(),
tooltip: AppLocalizations.of(context)!.back,
),
// keep reduced height only once
titleSpacing: 0.0,
title: Text(
AppLocalizations.of(context)!.you,
style: AppTypography.headlineSmallStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
),
),
centerTitle: true,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(Spacing.pagePadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Profile Header - Enhanced with better spacing and animations
_buildProfileHeader(context, userData, api)
.animate()
.fadeIn(duration: AnimationDuration.pageTransition)
.slideY(
begin: 0.1,
end: 0,
curve: AnimationCurves.pageTransition,
),
const SizedBox(height: Spacing.sectionGap),
// Account Section - Enhanced with improved spacing
_buildAccountSection(context, ref)
.animate()
.fadeIn(
delay: AnimationDelay.short,
duration: AnimationDuration.pageTransition,
)
.slideY(
begin: 0.1,
end: 0,
curve: AnimationCurves.pageTransition,
),
],
),
),
), ),
loading: () => Scaffold( loading: () => _buildScaffold(
backgroundColor: context.conduitTheme.surfaceBackground, context,
appBar: AppBar( body: _buildCenteredState(
backgroundColor: context.conduitTheme.surfaceBackground, context,
elevation: Elevation.none, ImprovedLoadingState(
toolbarHeight: kToolbarHeight - 8,
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.back,
android: Icons.arrow_back,
),
color: context.conduitTheme.textPrimary,
),
onPressed: () => Navigator.of(context).maybePop(),
tooltip: AppLocalizations.of(context)!.back,
),
title: Text(
AppLocalizations.of(context)!.you,
style: AppTypography.headlineSmallStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
),
),
centerTitle: true,
),
body: Center(
child: ImprovedLoadingState(
message: AppLocalizations.of(context)!.loadingProfile, message: AppLocalizations.of(context)!.loadingProfile,
), ),
), ),
), ),
error: (error, stack) => Scaffold( error: (error, stack) => _buildScaffold(
backgroundColor: context.conduitTheme.surfaceBackground, context,
appBar: AppBar( body: _buildCenteredState(
backgroundColor: context.conduitTheme.surfaceBackground, context,
elevation: Elevation.none, ImprovedEmptyState(
toolbarHeight: kToolbarHeight - 8,
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.back,
android: Icons.arrow_back,
),
color: context.conduitTheme.textPrimary,
),
onPressed: () => Navigator.of(context).maybePop(),
tooltip: AppLocalizations.of(context)!.back,
),
title: Text(
AppLocalizations.of(context)!.you,
style: AppTypography.headlineSmallStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
),
),
centerTitle: true,
),
body: Center(
child: ImprovedEmptyState(
title: AppLocalizations.of(context)!.unableToLoadProfile, title: AppLocalizations.of(context)!.unableToLoadProfile,
subtitle: AppLocalizations.of(context)!.pleaseCheckConnection, subtitle: AppLocalizations.of(context)!.pleaseCheckConnection,
icon: UiUtils.platformIcon( icon: UiUtils.platformIcon(
@@ -179,6 +74,97 @@ class ProfilePage extends ConsumerWidget {
); );
} }
Scaffold _buildScaffold(BuildContext context, {required Widget body}) {
return Scaffold(
backgroundColor: context.conduitTheme.surfaceBackground,
appBar: _buildAppBar(context),
body: body,
);
}
PreferredSizeWidget _buildAppBar(BuildContext context) {
final canPop = ModalRoute.of(context)?.canPop ?? false;
return AppBar(
backgroundColor: context.conduitTheme.surfaceBackground,
surfaceTintColor: Colors.transparent,
elevation: Elevation.none,
toolbarHeight: kToolbarHeight,
automaticallyImplyLeading: false,
leading: canPop
? IconButton(
icon: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.back,
android: Icons.arrow_back,
),
color: context.conduitTheme.iconPrimary,
),
onPressed: () => Navigator.of(context).maybePop(),
tooltip: AppLocalizations.of(context)!.back,
)
: null,
titleSpacing: 0,
title: Text(
AppLocalizations.of(context)!.you,
style: AppTypography.headlineSmallStyle.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w600,
),
),
centerTitle: true,
);
}
Widget _buildCenteredState(BuildContext context, Widget child) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(Spacing.pagePadding),
child: Center(child: child),
),
);
}
Widget _buildProfileBody(
BuildContext context,
WidgetRef ref,
dynamic userData,
ApiService? api,
) {
return SafeArea(
child: ListView(
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
),
padding: const EdgeInsets.symmetric(
horizontal: Spacing.pagePadding,
vertical: Spacing.pagePadding,
),
children: [
_buildProfileHeader(context, userData, api)
.animate()
.fadeIn(duration: AnimationDuration.pageTransition)
.slideY(
begin: 0.1,
end: 0,
curve: AnimationCurves.pageTransition,
),
const SizedBox(height: Spacing.sectionGap),
_buildAccountSection(context, ref)
.animate()
.fadeIn(
delay: AnimationDelay.short,
duration: AnimationDuration.pageTransition,
)
.slideY(
begin: 0.08,
end: 0,
curve: AnimationCurves.pageTransition,
),
],
),
);
}
Widget _buildProfileHeader( Widget _buildProfileHeader(
BuildContext context, BuildContext context,
dynamic user, dynamic user,
@@ -212,50 +198,128 @@ class ProfilePage extends ConsumerWidget {
} }
final email = extractEmail(user) ?? 'No email'; final email = extractEmail(user) ?? 'No email';
final theme = context.conduitTheme;
final accent = theme.buttonPrimary;
return ConduitCard( return Container(
padding: const EdgeInsets.all(Spacing.cardPadding), padding: const EdgeInsets.all(Spacing.cardPadding),
child: Row( decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
accent.withValues(alpha: 0.22),
accent.withValues(alpha: 0.06),
],
),
borderRadius: BorderRadius.circular(AppBorderRadius.extraLarge),
border: Border.all(
color: accent.withValues(alpha: 0.18),
width: BorderWidth.thin,
),
boxShadow: ConduitShadows.medium,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
decoration: BoxDecoration( padding: const EdgeInsets.symmetric(
borderRadius: BorderRadius.circular(AppBorderRadius.avatar), horizontal: Spacing.sm,
boxShadow: ConduitShadows.card, vertical: Spacing.xs,
), ),
child: UserAvatar( decoration: BoxDecoration(
size: IconSize.avatar, color: theme.surfaceBackground.withValues(alpha: 0.7),
imageUrl: avatarUrl, borderRadius: BorderRadius.circular(AppBorderRadius.pill),
fallbackText: initial, ),
child: Text(
AppLocalizations.of(context)!.account,
style:
theme.caption?.copyWith(
color: theme.textSecondary,
fontWeight: FontWeight.w600,
) ??
TextStyle(
color: theme.textSecondary,
fontWeight: FontWeight.w600,
),
), ),
), ),
const SizedBox(width: Spacing.md), const SizedBox(height: Spacing.lg),
Expanded( Row(
child: Column( crossAxisAlignment: CrossAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Container(
Text( decoration: BoxDecoration(
displayName, borderRadius: BorderRadius.circular(AppBorderRadius.avatar),
style: boxShadow: ConduitShadows.high,
context.conduitTheme.headingMedium?.copyWith( ),
color: context.conduitTheme.textPrimary, child: UserAvatar(
fontWeight: FontWeight.w600, size: IconSize.huge,
) ?? imageUrl: avatarUrl,
TextStyle( fallbackText: initial,
color: context.conduitTheme.textPrimary, ),
fontWeight: FontWeight.w600, ),
const SizedBox(width: Spacing.lg),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
displayName,
style:
theme.headingMedium?.copyWith(
color: theme.textPrimary,
fontWeight: FontWeight.w700,
) ??
TextStyle(
color: theme.textPrimary,
fontWeight: FontWeight.w700,
fontSize: 22,
),
),
const SizedBox(height: Spacing.sm),
Container(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.md,
vertical: Spacing.xs,
), ),
decoration: BoxDecoration(
color: theme.surfaceBackground.withValues(alpha: 0.75),
borderRadius: BorderRadius.circular(
AppBorderRadius.round,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.envelope,
android: Icons.mail_outline,
),
size: IconSize.small,
color: theme.textSecondary,
),
const SizedBox(width: Spacing.xs),
Flexible(
child: Text(
email,
style:
theme.bodySmall?.copyWith(
color: theme.textSecondary,
) ??
TextStyle(color: theme.textSecondary),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
),
],
), ),
const SizedBox(height: Spacing.sm), ),
Text( ],
email,
style:
context.conduitTheme.bodyMedium?.copyWith(
color: context.conduitTheme.textSecondary,
) ??
TextStyle(color: context.conduitTheme.textSecondary),
),
],
),
), ),
], ],
), ),
@@ -263,6 +327,37 @@ class ProfilePage extends ConsumerWidget {
} }
Widget _buildAccountSection(BuildContext context, WidgetRef ref) { Widget _buildAccountSection(BuildContext context, WidgetRef ref) {
final items = [
_buildDefaultModelTile(context, ref),
_buildAccountOption(
context,
icon: UiUtils.platformIcon(
ios: CupertinoIcons.slider_horizontal_3,
android: Icons.tune,
),
title: AppLocalizations.of(context)!.appCustomization,
subtitle: AppLocalizations.of(context)!.appCustomizationSubtitle,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const AppCustomizationPage()),
);
},
),
_buildAboutTile(context),
_buildAccountOption(
context,
icon: UiUtils.platformIcon(
ios: CupertinoIcons.square_arrow_left,
android: Icons.logout,
),
title: AppLocalizations.of(context)!.signOut,
subtitle: AppLocalizations.of(context)!.endYourSession,
onTap: () => _signOut(context, ref),
isDestructive: true,
showChevron: false,
),
];
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -272,106 +367,42 @@ class ProfilePage extends ConsumerWidget {
color: context.conduitTheme.textPrimary, color: context.conduitTheme.textPrimary,
), ),
), ),
const SizedBox(height: Spacing.md), const SizedBox(height: Spacing.sm),
ConduitCard( for (var i = 0; i < items.length; i++) ...[
padding: EdgeInsets.zero, items[i],
child: Column( if (i != items.length - 1) const SizedBox(height: Spacing.md),
children: [ ],
_buildDefaultModelTile(context, ref),
Divider(color: context.conduitTheme.dividerColor, height: 1),
_buildAccountOption(
icon: UiUtils.platformIcon(
ios: CupertinoIcons.slider_horizontal_3,
android: Icons.tune,
),
title: AppLocalizations.of(context)!.appCustomization,
subtitle: AppLocalizations.of(
context,
)!.appCustomizationSubtitle,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const AppCustomizationPage(),
),
);
},
),
Divider(color: context.conduitTheme.dividerColor, height: 1),
_buildAboutTile(context),
Divider(color: context.conduitTheme.dividerColor, height: 1),
_buildAccountOption(
icon: UiUtils.platformIcon(
ios: CupertinoIcons.square_arrow_left,
android: Icons.logout,
),
title: AppLocalizations.of(context)!.signOut,
subtitle: AppLocalizations.of(context)!.endYourSession,
onTap: () => _signOut(context, ref),
isDestructive: true,
),
],
),
),
], ],
); );
} }
Widget _buildAccountOption({ Widget _buildAccountOption(
BuildContext context, {
required IconData icon, required IconData icon,
required String title, required String title,
required String subtitle, required String subtitle,
required VoidCallback onTap, required VoidCallback onTap,
bool isDestructive = false, bool isDestructive = false,
bool showChevron = true,
}) { }) {
return Builder( final theme = context.conduitTheme;
builder: (context) => ListTile( final color = isDestructive ? theme.error : theme.buttonPrimary;
contentPadding: const EdgeInsets.symmetric( return _ProfileSettingTile(
horizontal: Spacing.listItemPadding, onTap: onTap,
vertical: Spacing.sm, isDestructive: isDestructive,
), leading: _buildIconBadge(context, icon, color: color),
leading: Container( title: title,
padding: const EdgeInsets.all(Spacing.sm), subtitle: subtitle,
decoration: BoxDecoration( trailing: showChevron
color: isDestructive ? Icon(
? context.conduitTheme.error.withValues(alpha: Alpha.highlight) UiUtils.platformIcon(
: context.conduitTheme.buttonPrimary.withValues( ios: CupertinoIcons.chevron_right,
alpha: Alpha.highlight, android: Icons.chevron_right,
), ),
borderRadius: BorderRadius.circular(AppBorderRadius.small), color: theme.iconSecondary,
), size: IconSize.small,
child: Icon( )
icon, : null,
color: isDestructive
? context.conduitTheme.error
: context.conduitTheme.buttonPrimary,
size: IconSize.medium,
),
),
title: Text(
title,
style: context.conduitTheme.bodyLarge?.copyWith(
color: isDestructive
? context.conduitTheme.error
: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
subtitle,
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: onTap,
),
); );
} }
@@ -400,142 +431,125 @@ class ProfilePage extends ConsumerWidget {
? currentModel.name ? currentModel.name
: AppLocalizations.of(context)!.autoSelect; : AppLocalizations.of(context)!.autoSelect;
final theme = context.conduitTheme;
Widget leading; Widget leading;
if (selectedModelExplicit) { if (selectedModelExplicit) {
leading = ModelAvatar( leading = Container(
size: 32, width: 48,
imageUrl: modelIconUrl, height: 48,
label: currentModel.name, decoration: BoxDecoration(
color: theme.surfaceBackground.withValues(alpha: 0.85),
borderRadius: BorderRadius.circular(AppBorderRadius.medium),
border: Border.all(
color: theme.cardBorder,
width: BorderWidth.thin,
),
),
alignment: Alignment.center,
child: ModelAvatar(
size: 32,
imageUrl: modelIconUrl,
label: currentModel.name,
),
); );
} else { } else {
leading = Container( leading = _buildIconBadge(
padding: const EdgeInsets.all(Spacing.sm), context,
decoration: BoxDecoration( UiUtils.platformIcon(
color: context.conduitTheme.buttonPrimary.withValues( ios: CupertinoIcons.wand_stars,
alpha: Alpha.highlight, android: Icons.auto_awesome,
),
borderRadius: BorderRadius.circular(AppBorderRadius.small),
),
child: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.wand_stars,
android: Icons.auto_awesome,
),
color: context.conduitTheme.buttonPrimary,
size: IconSize.medium,
), ),
color: theme.buttonPrimary,
); );
} }
return ListTile( return _ProfileSettingTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: Spacing.listItemPadding,
vertical: Spacing.sm,
),
leading: leading, leading: leading,
title: Text( title: AppLocalizations.of(context)!.defaultModel,
AppLocalizations.of(context)!.defaultModel, subtitle: modelLabel,
style: context.conduitTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
modelLabel,
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: () => _showModelSelector(context, ref, models), onTap: () => _showModelSelector(context, ref, models),
); );
}, },
loading: () => ListTile( loading: () => _ProfileSettingTile(
contentPadding: const EdgeInsets.symmetric( leading: _buildIconBadge(
horizontal: Spacing.listItemPadding, context,
vertical: Spacing.sm, UiUtils.platformIcon(
ios: CupertinoIcons.cube_box,
android: Icons.psychology,
),
color: context.conduitTheme.buttonPrimary,
), ),
leading: Container( title: AppLocalizations.of(context)!.defaultModel,
padding: const EdgeInsets.all(Spacing.sm), subtitle: AppLocalizations.of(context)!.loadingModels,
decoration: BoxDecoration( showChevron: false,
color: context.conduitTheme.buttonPrimary.withValues( trailing: SizedBox(
alpha: Alpha.highlight, width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
context.conduitTheme.buttonPrimary,
), ),
borderRadius: BorderRadius.circular(AppBorderRadius.small),
),
child: Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.cube_box,
android: Icons.psychology,
),
color: context.conduitTheme.buttonPrimary,
size: IconSize.medium,
),
),
title: Text(
AppLocalizations.of(context)!.defaultModel,
style: context.conduitTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
AppLocalizations.of(context)!.loadingModels,
style: context.conduitTheme.bodySmall?.copyWith(
color: context.conduitTheme.textSecondary,
), ),
), ),
), ),
error: (error, stack) => ListTile( error: (error, stack) => _ProfileSettingTile(
contentPadding: const EdgeInsets.symmetric( leading: _buildIconBadge(
horizontal: Spacing.listItemPadding, context,
vertical: Spacing.sm, UiUtils.platformIcon(
), ios: CupertinoIcons.exclamationmark_triangle,
leading: Container( android: Icons.error_outline,
padding: const EdgeInsets.all(Spacing.sm),
decoration: BoxDecoration(
color: context.conduitTheme.error.withValues(
alpha: Alpha.highlight,
),
borderRadius: BorderRadius.circular(AppBorderRadius.small),
), ),
child: Icon( color: context.conduitTheme.error,
),
title: AppLocalizations.of(context)!.defaultModel,
subtitle: AppLocalizations.of(context)!.failedToLoadModels,
isDestructive: true,
showChevron: false,
onTap: () => ref.invalidate(modelsProvider),
trailing: IconButton(
onPressed: () => ref.invalidate(modelsProvider),
tooltip: AppLocalizations.of(context)!.retry,
icon: Icon(
UiUtils.platformIcon( UiUtils.platformIcon(
ios: CupertinoIcons.exclamationmark_triangle, ios: CupertinoIcons.refresh,
android: Icons.error_outline, android: Icons.refresh,
), ),
color: context.conduitTheme.error, color: context.conduitTheme.error,
size: IconSize.medium, size: IconSize.small,
),
),
title: Text(
AppLocalizations.of(context)!.defaultModel,
style: context.conduitTheme.bodyLarge?.copyWith(
color: context.conduitTheme.textPrimary,
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
AppLocalizations.of(context)!.failedToLoadModels,
style: context.conduitTheme.bodySmall?.copyWith(
color: context.conduitTheme.error,
), ),
), ),
), ),
); );
} }
Widget _buildIconBadge(
BuildContext context,
IconData icon, {
required Color color,
}) {
return Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: color.withValues(alpha: Alpha.highlight),
borderRadius: BorderRadius.circular(AppBorderRadius.medium),
border: Border.all(
color: color.withValues(alpha: 0.2),
width: BorderWidth.thin,
),
),
alignment: Alignment.center,
child: Icon(icon, color: color, size: IconSize.large),
);
}
// Theme and language controls moved to AppCustomizationPage. // Theme and language controls moved to AppCustomizationPage.
Widget _buildAboutTile(BuildContext context) { Widget _buildAboutTile(BuildContext context) {
return _buildAccountOption( return _buildAccountOption(
context,
icon: UiUtils.platformIcon( icon: UiUtils.platformIcon(
ios: CupertinoIcons.info, ios: CupertinoIcons.info,
android: Icons.info_outline, android: Icons.info_outline,
@@ -666,6 +680,87 @@ class ProfilePage extends ConsumerWidget {
} }
} }
class _ProfileSettingTile extends StatelessWidget {
const _ProfileSettingTile({
required this.leading,
required this.title,
required this.subtitle,
this.onTap,
this.trailing,
this.isDestructive = false,
this.showChevron = true,
});
final Widget leading;
final String title;
final String subtitle;
final VoidCallback? onTap;
final Widget? trailing;
final bool isDestructive;
final bool showChevron;
@override
Widget build(BuildContext context) {
final theme = context.conduitTheme;
final textColor = isDestructive ? theme.error : theme.textPrimary;
final subtitleColor = isDestructive
? theme.error.withValues(alpha: 0.85)
: theme.textSecondary;
return ConduitCard(
padding: const EdgeInsets.symmetric(
horizontal: Spacing.listItemPadding,
vertical: Spacing.md,
),
onTap: onTap,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
leading,
const SizedBox(width: Spacing.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style:
theme.bodyLarge?.copyWith(
color: textColor,
fontWeight: FontWeight.w600,
) ??
TextStyle(color: textColor, fontWeight: FontWeight.w600),
),
const SizedBox(height: Spacing.textSpacing),
Text(
subtitle,
style:
theme.bodySmall?.copyWith(color: subtitleColor) ??
TextStyle(color: subtitleColor),
),
],
),
),
if (trailing != null) ...[
const SizedBox(width: Spacing.md),
trailing!,
] else if (showChevron && onTap != null) ...[
const SizedBox(width: Spacing.md),
Icon(
UiUtils.platformIcon(
ios: CupertinoIcons.chevron_right,
android: Icons.chevron_right,
),
color: theme.iconSecondary,
size: IconSize.small,
),
],
],
),
);
}
}
class _DefaultModelBottomSheet extends ConsumerStatefulWidget { class _DefaultModelBottomSheet extends ConsumerStatefulWidget {
final List<Model> models; final List<Model> models;
final String? currentDefaultModelId; final String? currentDefaultModelId;

View File

@@ -265,6 +265,7 @@
, ,
"appCustomization": "App-Anpassung", "appCustomization": "App-Anpassung",
"appCustomizationSubtitle": "Personalisieren, wie Namen und UI angezeigt werden", "appCustomizationSubtitle": "Personalisieren, wie Namen und UI angezeigt werden",
"quickActionsDescription": "Wähle bis zu zwei Schnellzugriffe, die neben dem Eingabefeld angepinnt werden",
"display": "Anzeige", "display": "Anzeige",
"realtime": "Echtzeit", "realtime": "Echtzeit",
"hideProviderInModelNames": "Anbieter in Modellnamen ausblenden", "hideProviderInModelNames": "Anbieter in Modellnamen ausblenden",

View File

@@ -532,6 +532,8 @@
"@appCustomization": {"description": "Title of the customization settings page."}, "@appCustomization": {"description": "Title of the customization settings page."},
"appCustomizationSubtitle": "Personalize how names and UI display", "appCustomizationSubtitle": "Personalize how names and UI display",
"@appCustomizationSubtitle": {"description": "Subtitle shown under App Customization tile and page header."}, "@appCustomizationSubtitle": {"description": "Subtitle shown under App Customization tile and page header."},
"quickActionsDescription": "Pick up to two shortcuts to pin near the composer",
"@quickActionsDescription": {"description": "Helper text explaining quick action pill selection in customization."},
"display": "Display", "display": "Display",
"@display": {"description": "Section header for visual and layout related settings."}, "@display": {"description": "Section header for visual and layout related settings."},
"realtime": "Realtime", "realtime": "Realtime",

View File

@@ -265,6 +265,7 @@
, ,
"appCustomization": "Personnalisation de l'app", "appCustomization": "Personnalisation de l'app",
"appCustomizationSubtitle": "Personnalisez l'affichage des noms et de l'UI", "appCustomizationSubtitle": "Personnalisez l'affichage des noms et de l'UI",
"quickActionsDescription": "Choisissez jusqu'à deux raccourcis à épingler près du champ de saisie",
"display": "Affichage", "display": "Affichage",
"realtime": "Temps réel", "realtime": "Temps réel",
"hideProviderInModelNames": "Masquer le fournisseur dans les noms de modèles", "hideProviderInModelNames": "Masquer le fournisseur dans les noms de modèles",

View File

@@ -265,6 +265,7 @@
, ,
"appCustomization": "Personalizzazione app", "appCustomization": "Personalizzazione app",
"appCustomizationSubtitle": "Personalizza la visualizzazione dei nomi e dell'UI", "appCustomizationSubtitle": "Personalizza la visualizzazione dei nomi e dell'UI",
"quickActionsDescription": "Scegli fino a due scorciatoie da fissare vicino al campo di input",
"display": "Schermo", "display": "Schermo",
"realtime": "Tempo reale", "realtime": "Tempo reale",
"hideProviderInModelNames": "Nascondi provider nei nomi dei modelli", "hideProviderInModelNames": "Nascondi provider nei nomi dei modelli",

View File

@@ -1506,6 +1506,12 @@ abstract class AppLocalizations {
/// **'Personalize how names and UI display'** /// **'Personalize how names and UI display'**
String get appCustomizationSubtitle; String get appCustomizationSubtitle;
/// Helper text explaining quick action pill selection in customization.
///
/// In en, this message translates to:
/// **'Pick up to two shortcuts to pin near the composer'**
String get quickActionsDescription;
/// Section header for visual and layout related settings. /// Section header for visual and layout related settings.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View File

@@ -784,6 +784,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get appCustomizationSubtitle => String get appCustomizationSubtitle =>
'Personalisieren, wie Namen und UI angezeigt werden'; 'Personalisieren, wie Namen und UI angezeigt werden';
@override
String get quickActionsDescription =>
'Wähle bis zu zwei Schnellzugriffe, die neben dem Eingabefeld angepinnt werden';
@override @override
String get display => 'Anzeige'; String get display => 'Anzeige';

View File

@@ -777,6 +777,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get appCustomizationSubtitle => 'Personalize how names and UI display'; String get appCustomizationSubtitle => 'Personalize how names and UI display';
@override
String get quickActionsDescription =>
'Pick up to two shortcuts to pin near the composer';
@override @override
String get display => 'Display'; String get display => 'Display';

View File

@@ -792,6 +792,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get appCustomizationSubtitle => String get appCustomizationSubtitle =>
'Personnalisez l\'affichage des noms et de l\'UI'; 'Personnalisez l\'affichage des noms et de l\'UI';
@override
String get quickActionsDescription =>
'Choisissez jusqu\'à deux raccourcis à épingler près du champ de saisie';
@override @override
String get display => 'Affichage'; String get display => 'Affichage';

View File

@@ -781,6 +781,10 @@ class AppLocalizationsIt extends AppLocalizations {
String get appCustomizationSubtitle => String get appCustomizationSubtitle =>
'Personalizza la visualizzazione dei nomi e dell\'UI'; 'Personalizza la visualizzazione dei nomi e dell\'UI';
@override
String get quickActionsDescription =>
'Scegli fino a due scorciatoie da fissare vicino al campo di input';
@override @override
String get display => 'Schermo'; String get display => 'Schermo';