main
dart 98 lines 3.36 KB
Raw
1 import 'package:flutter/material.dart';
2 import 'package:hugeicons/hugeicons.dart';
3
4 import 'package:localmind/core/theme/colors.dart';
5 import 'package:localmind/features/models/data/models/model_info.dart';
6 import 'package:localmind/l10n/app_localizations.dart';
7
8 /// Compact model picker chip that lives inside the chat input bar.
9 ///
10 /// Shows the active model's display name (or a "Select model" placeholder
11 /// when none is set) and opens the model picker bottom sheet on tap.
12 /// Designed as a pure text pill — no leading icon — to keep the bottom
13 /// action row visually quiet.
14 class ModelChip extends StatelessWidget {
15 const ModelChip({
16 super.key,
17 required this.model,
18 required this.onTap,
19 required this.enabled,
20 this.isCompactFont = false,
21 });
22
23 final ModelInfo? model;
24 final VoidCallback onTap;
25 final bool enabled;
26 final bool isCompactFont;
27
28 @override
29 Widget build(BuildContext context) {
30 final l10n = AppLocalizations.of(context)!;
31 final theme = Theme.of(context);
32 final isDark = theme.brightness == Brightness.dark;
33
34 final hasModel = model != null;
35 final fg = hasModel
36 ? (isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText)
37 : (isDark ? AppColors.darkMutedText : AppColors.lightMutedText);
38 final bg = hasModel
39 ? theme.colorScheme.primary.withValues(alpha: 0.12)
40 : (isDark
41 ? AppColors.darkSurfaceInput
42 : AppColors.lightSurface.withValues(alpha: 0.0));
43 final borderColor = hasModel
44 ? theme.colorScheme.primary.withValues(alpha: 0.4)
45 : (isDark ? AppColors.darkBorder : AppColors.lightBorder);
46
47 final label = hasModel ? model!.displayName : l10n.select_model_prompt;
48
49 final fontSize = isCompactFont ? 11.0 : 13.0;
50 final iconSize = isCompactFont ? 10.0 : 12.0;
51 final padding = isCompactFont
52 ? const EdgeInsetsDirectional.fromSTEB(8, 3, 6, 3)
53 : const EdgeInsetsDirectional.fromSTEB(12, 6, 10, 6);
54
55 return Material(
56 color: Colors.transparent,
57 child: InkWell(
58 onTap: enabled ? onTap : null,
59 borderRadius: BorderRadius.circular(999),
60 child: Container(
61 // Constrain the pill itself instead of the entire action row. This
62 // lets longer model names be readable while Flexible in the parent
63 // row still protects the fixed-size action buttons on the right.
64 constraints: const BoxConstraints(minWidth: 0, maxWidth: 280),
65 padding: padding,
66 decoration: BoxDecoration(
67 color: bg,
68 border: Border.all(color: borderColor),
69 borderRadius: BorderRadius.circular(999),
70 ),
71 child: Row(
72 mainAxisSize: MainAxisSize.min,
73 children: [
74 Flexible(
75 child: Text(
76 label,
77 maxLines: 1,
78 overflow: TextOverflow.ellipsis,
79 style: TextStyle(
80 fontSize: fontSize,
81 fontWeight: FontWeight.w600,
82 color: fg,
83 ),
84 ),
85 ),
86 const SizedBox(width: 4),
87 HugeIcon(
88 icon: HugeIcons.strokeRoundedArrowDown01,
89 size: iconSize,
90 color: fg.withValues(alpha: 0.7),
91 ),
92 ],
93 ),
94 ),
95 ),
96 );
97 }
98 }