feat: model and user avatars
This commit is contained in:
@@ -14,6 +14,7 @@ import '../../../core/auth/auth_state_manager.dart';
|
||||
import '../providers/chat_providers.dart';
|
||||
import '../../../core/utils/debug_logger.dart';
|
||||
import '../../../core/utils/user_display_name.dart';
|
||||
import '../../../core/utils/model_icon_utils.dart';
|
||||
|
||||
import '../widgets/modern_chat_input.dart';
|
||||
import '../widgets/user_message_bubble.dart';
|
||||
@@ -41,6 +42,7 @@ import '../../../shared/widgets/middle_ellipsis_text.dart';
|
||||
import '../../../shared/widgets/modal_safe_area.dart';
|
||||
import '../../../core/services/settings_service.dart';
|
||||
import '../../../shared/utils/conversation_context_menu.dart';
|
||||
import '../../../shared/widgets/model_avatar.dart';
|
||||
// Removed unused PlatformUtils import
|
||||
import '../../../core/services/platform_service.dart' as ps;
|
||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
||||
@@ -699,6 +701,8 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
return _buildEmptyState(Theme.of(context));
|
||||
}
|
||||
|
||||
final apiService = ref.watch(apiServiceProvider);
|
||||
|
||||
return OptimizedList<ChatMessage>(
|
||||
key: const ValueKey('actual_messages'),
|
||||
scrollController: _scrollController,
|
||||
@@ -717,6 +721,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
|
||||
// Resolve a friendly model display name for message headers
|
||||
String? displayModelName;
|
||||
Model? matchedModel;
|
||||
final rawModel = message.model;
|
||||
if (rawModel != null && rawModel.isNotEmpty) {
|
||||
final omitProvider = ref
|
||||
@@ -730,6 +735,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
final match = models.firstWhere(
|
||||
(m) => m.id == rawModel || m.name == rawModel,
|
||||
);
|
||||
matchedModel = match;
|
||||
displayModelName = _formatModelDisplayName(
|
||||
match.name,
|
||||
omitProvider: omitProvider,
|
||||
@@ -750,6 +756,11 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
}
|
||||
}
|
||||
|
||||
final modelIconUrl = resolveModelIconUrlForModel(
|
||||
apiService,
|
||||
matchedModel,
|
||||
);
|
||||
|
||||
// Wrap message in selection container if in selection mode
|
||||
Widget messageWidget;
|
||||
|
||||
@@ -770,6 +781,7 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
message: message,
|
||||
isStreaming: isStreaming,
|
||||
modelName: displayModelName,
|
||||
modelIconUrl: modelIconUrl,
|
||||
onCopy: () => _copyMessage(message.content),
|
||||
onRegenerate: () => _regenerateMessage(message),
|
||||
);
|
||||
@@ -917,7 +929,6 @@ class _ChatPageState extends ConsumerState<ChatPage> {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
// Use select to watch only connectivity status to reduce rebuilds
|
||||
final isOnline = ref.watch(isOnlineProvider.select((status) => status));
|
||||
|
||||
// Use select to watch only the selected model to reduce rebuilds
|
||||
final selectedModel = ref.watch(
|
||||
selectedModelProvider.select((model) => model),
|
||||
@@ -1819,6 +1830,8 @@ class _ModelSelectorSheetState extends ConsumerState<_ModelSelectorSheet> {
|
||||
required bool isSelected,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
final api = ref.watch(apiServiceProvider);
|
||||
final iconUrl = resolveModelIconUrlForModel(api, model);
|
||||
return PressableScale(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
@@ -1852,21 +1865,7 @@ class _ModelSelectorSheetState extends ConsumerState<_ModelSelectorSheet> {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: context.conduitTheme.buttonPrimary.withValues(
|
||||
alpha: 0.15,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
),
|
||||
child: Icon(
|
||||
Platform.isIOS ? CupertinoIcons.cube : Icons.psychology,
|
||||
color: context.conduitTheme.buttonPrimary,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
ModelAvatar(size: 32, imageUrl: iconUrl, label: model.name),
|
||||
const SizedBox(width: Spacing.md),
|
||||
Expanded(
|
||||
child: Column(
|
||||
|
||||
@@ -14,11 +14,13 @@ import 'enhanced_image_attachment.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
import 'enhanced_attachment.dart';
|
||||
import 'package:conduit/shared/widgets/chat_action_button.dart';
|
||||
import '../../../shared/widgets/model_avatar.dart';
|
||||
|
||||
class AssistantMessageWidget extends ConsumerStatefulWidget {
|
||||
final dynamic message;
|
||||
final bool isStreaming;
|
||||
final String? modelName;
|
||||
final String? modelIconUrl;
|
||||
final VoidCallback? onCopy;
|
||||
final VoidCallback? onRegenerate;
|
||||
final VoidCallback? onLike;
|
||||
@@ -29,6 +31,7 @@ class AssistantMessageWidget extends ConsumerStatefulWidget {
|
||||
required this.message,
|
||||
this.isStreaming = false,
|
||||
this.modelName,
|
||||
this.modelIconUrl,
|
||||
this.onCopy,
|
||||
this.onRegenerate,
|
||||
this.onLike,
|
||||
@@ -87,8 +90,9 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
_updateTypingIndicatorGate();
|
||||
}
|
||||
|
||||
// Rebuild cached avatar if model name changes
|
||||
if (oldWidget.modelName != widget.modelName) {
|
||||
// Rebuild cached avatar if model name or icon changes
|
||||
if (oldWidget.modelName != widget.modelName ||
|
||||
oldWidget.modelIconUrl != widget.modelIconUrl) {
|
||||
_buildCachedAvatar();
|
||||
}
|
||||
}
|
||||
@@ -409,28 +413,36 @@ class _AssistantMessageWidgetState extends ConsumerState<AssistantMessageWidget>
|
||||
}
|
||||
|
||||
void _buildCachedAvatar() {
|
||||
_cachedAvatar = Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
final theme = context.conduitTheme;
|
||||
final iconUrl = widget.modelIconUrl?.trim();
|
||||
final hasIcon = iconUrl != null && iconUrl.isNotEmpty;
|
||||
|
||||
final Widget leading = hasIcon
|
||||
? ModelAvatar(size: 20, imageUrl: iconUrl, label: widget.modelName)
|
||||
: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: context.conduitTheme.buttonPrimary,
|
||||
color: theme.buttonPrimary,
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.small),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.auto_awesome,
|
||||
color: context.conduitTheme.buttonPrimaryText,
|
||||
color: theme.buttonPrimaryText,
|
||||
size: 12,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
_cachedAvatar = Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
leading,
|
||||
const SizedBox(width: Spacing.xs),
|
||||
Text(
|
||||
widget.modelName ?? 'Assistant',
|
||||
style: TextStyle(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
color: theme.textSecondary,
|
||||
fontSize: AppTypography.bodySmall,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.1,
|
||||
|
||||
@@ -16,7 +16,12 @@ import '../../../shared/widgets/themed_dialogs.dart';
|
||||
import '../../../core/auth/auth_state_manager.dart';
|
||||
import 'package:conduit/l10n/app_localizations.dart';
|
||||
import '../../../core/utils/user_display_name.dart';
|
||||
import '../../../core/utils/model_icon_utils.dart';
|
||||
import '../../../core/utils/user_avatar_utils.dart';
|
||||
import '../../../shared/utils/conversation_context_menu.dart';
|
||||
import '../../../shared/widgets/user_avatar.dart';
|
||||
import '../../../shared/widgets/model_avatar.dart';
|
||||
import '../../../core/models/model.dart';
|
||||
|
||||
class ChatsDrawer extends ConsumerStatefulWidget {
|
||||
const ChatsDrawer({super.key});
|
||||
@@ -955,11 +960,41 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
(ref.watch(chat.isLoadingConversationProvider) == true);
|
||||
final bool isPinned = conv.pinned == true;
|
||||
|
||||
Model? model;
|
||||
final modelId = (conv.model is String && (conv.model as String).isNotEmpty)
|
||||
? conv.model as String
|
||||
: null;
|
||||
if (modelId != null) {
|
||||
final modelsAsync = ref.watch(modelsProvider);
|
||||
model = modelsAsync.maybeWhen(
|
||||
data: (models) {
|
||||
for (final m in models) {
|
||||
if (m.id == modelId) return m;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
orElse: () => null,
|
||||
);
|
||||
}
|
||||
|
||||
final api = ref.watch(apiServiceProvider);
|
||||
final modelIconUrl = resolveModelIconUrlForModel(api, model);
|
||||
|
||||
Widget? leading;
|
||||
if (modelId != null) {
|
||||
leading = ModelAvatar(
|
||||
size: 28,
|
||||
imageUrl: modelIconUrl,
|
||||
label: model?.name ?? modelId,
|
||||
);
|
||||
}
|
||||
|
||||
final tile = _ConversationTile(
|
||||
title: title,
|
||||
pinned: isPinned,
|
||||
selected: isActive,
|
||||
isLoading: isLoadingSelected,
|
||||
leading: leading,
|
||||
onTap: _isLoadingConversation
|
||||
? null
|
||||
: () => _selectConversation(context, conv.id),
|
||||
@@ -1180,6 +1215,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
);
|
||||
final dynamic authUser = ref.watch(authUserProvider);
|
||||
final user = userFromProfile ?? authUser;
|
||||
final api = ref.watch(apiServiceProvider);
|
||||
|
||||
String initialFor(String name) {
|
||||
if (name.isEmpty) return 'U';
|
||||
@@ -1189,6 +1225,7 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
|
||||
final displayName = deriveUserDisplayName(user);
|
||||
final initial = initialFor(displayName);
|
||||
final avatarUrl = resolveUserAvatarUrlForUser(api, user);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(Spacing.sm, 0, Spacing.sm, Spacing.sm),
|
||||
child: Column(
|
||||
@@ -1216,7 +1253,6 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
width: IconSize.xl,
|
||||
height: IconSize.xl,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.buttonPrimary.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppBorderRadius.avatar,
|
||||
),
|
||||
@@ -1225,13 +1261,11 @@ class _ChatsDrawerState extends ConsumerState<ChatsDrawer> {
|
||||
width: BorderWidth.thin,
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
initial,
|
||||
style: AppTypography.bodyLargeStyle.copyWith(
|
||||
color: theme.buttonPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: UserAvatar(
|
||||
size: IconSize.xl,
|
||||
imageUrl: avatarUrl,
|
||||
fallbackText: initial,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: Spacing.xs),
|
||||
@@ -1332,6 +1366,7 @@ class _ConversationTileContent extends StatelessWidget {
|
||||
final bool selected;
|
||||
final bool isLoading;
|
||||
final VoidCallback? onMorePressed;
|
||||
final Widget? leading;
|
||||
|
||||
const _ConversationTileContent({
|
||||
required this.title,
|
||||
@@ -1339,6 +1374,7 @@ class _ConversationTileContent extends StatelessWidget {
|
||||
required this.selected,
|
||||
required this.isLoading,
|
||||
this.onMorePressed,
|
||||
this.leading,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -1407,6 +1443,14 @@ class _ConversationTileContent extends StatelessWidget {
|
||||
return Row(
|
||||
mainAxisSize: hasFiniteWidth ? MainAxisSize.max : MainAxisSize.min,
|
||||
children: [
|
||||
if (leading != null) ...[
|
||||
SizedBox(
|
||||
width: TouchTarget.listItem,
|
||||
height: TouchTarget.listItem,
|
||||
child: Center(child: leading!),
|
||||
),
|
||||
const SizedBox(width: Spacing.sm),
|
||||
],
|
||||
Flexible(
|
||||
fit: textFit,
|
||||
child: Text(
|
||||
@@ -1429,6 +1473,7 @@ class _ConversationTile extends StatelessWidget {
|
||||
final bool pinned;
|
||||
final bool selected;
|
||||
final bool isLoading;
|
||||
final Widget? leading;
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onLongPress;
|
||||
final VoidCallback? onMorePressed;
|
||||
@@ -1438,6 +1483,7 @@ class _ConversationTile extends StatelessWidget {
|
||||
required this.pinned,
|
||||
required this.selected,
|
||||
required this.isLoading,
|
||||
this.leading,
|
||||
required this.onTap,
|
||||
this.onLongPress,
|
||||
this.onMorePressed,
|
||||
@@ -1504,6 +1550,7 @@ class _ConversationTile extends StatelessWidget {
|
||||
selected: selected,
|
||||
isLoading: isLoading,
|
||||
onMorePressed: onMorePressed,
|
||||
leading: leading,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -18,11 +18,18 @@ import '../../../core/providers/app_providers.dart';
|
||||
import '../../auth/providers/unified_auth_providers.dart';
|
||||
import '../../../core/services/settings_service.dart';
|
||||
import '../../../core/models/model.dart';
|
||||
import '../../../core/services/api_service.dart';
|
||||
import '../../../core/models/user.dart' as models;
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import '../../chat/views/chat_page_helpers.dart';
|
||||
import 'app_customization_page.dart';
|
||||
import '../../../shared/widgets/modal_safe_area.dart';
|
||||
import '../../../core/utils/user_display_name.dart';
|
||||
import '../../../core/utils/user_avatar_utils.dart';
|
||||
import '../../../core/utils/model_icon_utils.dart';
|
||||
import '../../../shared/widgets/user_avatar.dart';
|
||||
import '../../../shared/widgets/model_avatar.dart';
|
||||
|
||||
/// Profile page (You tab) showing user info and main actions
|
||||
/// Enhanced with production-grade design tokens for better cohesion
|
||||
@@ -32,6 +39,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
final api = ref.watch(apiServiceProvider);
|
||||
|
||||
return ErrorBoundary(
|
||||
child: user.when(
|
||||
@@ -70,7 +78,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Profile Header - Enhanced with better spacing and animations
|
||||
_buildProfileHeader(userData)
|
||||
_buildProfileHeader(context, userData, api)
|
||||
.animate()
|
||||
.fadeIn(duration: AnimationDuration.pageTransition)
|
||||
.slideY(
|
||||
@@ -171,48 +179,85 @@ class ProfilePage extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProfileHeader(dynamic user) {
|
||||
return Builder(
|
||||
builder: (context) => ConduitCard(
|
||||
padding: const EdgeInsets.all(Spacing.cardPadding),
|
||||
child: Row(
|
||||
children: [
|
||||
// Enhanced avatar with better sizing and shadows
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.avatar),
|
||||
boxShadow: ConduitShadows.card,
|
||||
),
|
||||
child: ConduitAvatar(
|
||||
size: IconSize.avatar,
|
||||
text: user?.name?.substring(0, 1) ?? 'U',
|
||||
),
|
||||
Widget _buildProfileHeader(
|
||||
BuildContext context,
|
||||
dynamic user,
|
||||
ApiService? api,
|
||||
) {
|
||||
final displayName = deriveUserDisplayName(user);
|
||||
final characters = displayName.characters;
|
||||
final initial = characters.isNotEmpty
|
||||
? characters.first.toUpperCase()
|
||||
: 'U';
|
||||
final avatarUrl = resolveUserAvatarUrlForUser(api, user);
|
||||
|
||||
String? extractEmail(dynamic source) {
|
||||
if (source is models.User) {
|
||||
return source.email;
|
||||
}
|
||||
if (source is Map) {
|
||||
final value = source['email'];
|
||||
if (value is String && value.trim().isNotEmpty) {
|
||||
return value.trim();
|
||||
}
|
||||
final nested = source['user'];
|
||||
if (nested is Map) {
|
||||
final nestedValue = nested['email'];
|
||||
if (nestedValue is String && nestedValue.trim().isNotEmpty) {
|
||||
return nestedValue.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final email = extractEmail(user) ?? 'No email';
|
||||
|
||||
return ConduitCard(
|
||||
padding: const EdgeInsets.all(Spacing.cardPadding),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.avatar),
|
||||
boxShadow: ConduitShadows.card,
|
||||
),
|
||||
const SizedBox(width: Spacing.md),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
user?.name ?? 'User',
|
||||
style: context.conduitTheme.headingMedium?.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: Spacing.sm),
|
||||
Text(
|
||||
user?.email ?? 'No email',
|
||||
style: context.conduitTheme.bodyMedium?.copyWith(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
// Status badge removed per design update
|
||||
],
|
||||
),
|
||||
child: UserAvatar(
|
||||
size: IconSize.avatar,
|
||||
imageUrl: avatarUrl,
|
||||
fallbackText: initial,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: Spacing.md),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
displayName,
|
||||
style:
|
||||
context.conduitTheme.headingMedium?.copyWith(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
) ??
|
||||
TextStyle(
|
||||
color: context.conduitTheme.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: Spacing.sm),
|
||||
Text(
|
||||
email,
|
||||
style:
|
||||
context.conduitTheme.bodyMedium?.copyWith(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
) ??
|
||||
TextStyle(color: context.conduitTheme.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -333,6 +378,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
Widget _buildDefaultModelTile(BuildContext context, WidgetRef ref) {
|
||||
final settings = ref.watch(appSettingsProvider);
|
||||
final modelsAsync = ref.watch(modelsProvider);
|
||||
final api = ref.watch(apiServiceProvider);
|
||||
|
||||
return modelsAsync.when(
|
||||
data: (models) {
|
||||
@@ -346,12 +392,23 @@ class ProfilePage extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.listItemPadding,
|
||||
vertical: Spacing.sm,
|
||||
),
|
||||
leading: Container(
|
||||
final selectedModelExplicit = settings.defaultModel != null;
|
||||
final modelIconUrl = selectedModelExplicit
|
||||
? resolveModelIconUrlForModel(api, currentModel)
|
||||
: null;
|
||||
final modelLabel = selectedModelExplicit
|
||||
? currentModel.name
|
||||
: AppLocalizations.of(context)!.autoSelect;
|
||||
|
||||
Widget leading;
|
||||
if (selectedModelExplicit) {
|
||||
leading = ModelAvatar(
|
||||
size: 32,
|
||||
imageUrl: modelIconUrl,
|
||||
label: currentModel.name,
|
||||
);
|
||||
} else {
|
||||
leading = Container(
|
||||
padding: const EdgeInsets.all(Spacing.sm),
|
||||
decoration: BoxDecoration(
|
||||
color: context.conduitTheme.buttonPrimary.withValues(
|
||||
@@ -361,13 +418,21 @@ class ProfilePage extends ConsumerWidget {
|
||||
),
|
||||
child: Icon(
|
||||
UiUtils.platformIcon(
|
||||
ios: CupertinoIcons.cube_box,
|
||||
android: Icons.psychology,
|
||||
ios: CupertinoIcons.wand_stars,
|
||||
android: Icons.auto_awesome,
|
||||
),
|
||||
color: context.conduitTheme.buttonPrimary,
|
||||
size: IconSize.medium,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: Spacing.listItemPadding,
|
||||
vertical: Spacing.sm,
|
||||
),
|
||||
leading: leading,
|
||||
title: Text(
|
||||
AppLocalizations.of(context)!.defaultModel,
|
||||
style: context.conduitTheme.bodyLarge?.copyWith(
|
||||
@@ -376,9 +441,7 @@ class ProfilePage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
settings.defaultModel != null
|
||||
? currentModel.name
|
||||
: AppLocalizations.of(context)!.autoSelect,
|
||||
modelLabel,
|
||||
style: context.conduitTheme.bodySmall?.copyWith(
|
||||
color: context.conduitTheme.textSecondary,
|
||||
),
|
||||
@@ -943,6 +1006,28 @@ class _DefaultModelBottomSheetState
|
||||
required bool isAutoSelect,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
final api = ref.watch(apiServiceProvider);
|
||||
|
||||
final Widget leading;
|
||||
if (isAutoSelect) {
|
||||
leading = Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: context.conduitTheme.buttonPrimary.withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
),
|
||||
child: Icon(
|
||||
Platform.isIOS ? CupertinoIcons.wand_stars : Icons.auto_awesome,
|
||||
color: context.conduitTheme.buttonPrimary,
|
||||
size: 16,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final iconUrl = resolveModelIconUrlForModel(api, model);
|
||||
leading = ModelAvatar(size: 32, imageUrl: iconUrl, label: model.name);
|
||||
}
|
||||
|
||||
return PressableScale(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
@@ -976,27 +1061,7 @@ class _DefaultModelBottomSheetState
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: context.conduitTheme.buttonPrimary.withValues(
|
||||
alpha: 0.15,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(AppBorderRadius.md),
|
||||
),
|
||||
child: Icon(
|
||||
isAutoSelect
|
||||
? (Platform.isIOS
|
||||
? CupertinoIcons.wand_stars
|
||||
: Icons.auto_awesome)
|
||||
: (Platform.isIOS
|
||||
? CupertinoIcons.cube
|
||||
: Icons.psychology),
|
||||
color: context.conduitTheme.buttonPrimary,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
leading,
|
||||
const SizedBox(width: Spacing.md),
|
||||
Expanded(
|
||||
child: Column(
|
||||
|
||||
Reference in New Issue
Block a user