36 files changed
+1046
-924
.fvmrc
+1
-1
@@ -1,3 +1,3 @@
1
{
2
- "flutter": "3.41.1"
2
+ "flutter": "3.44.2"
3
}
\ No newline at end of file
.vscode/settings.json
+1
-1
@@ -1,3 +1,3 @@
1
{
2
- "dart.flutterSdkPath": ".fvm/versions/3.41.1"
2
+ "dart.flutterSdkPath": ".fvm/versions/3.44.2"
3
}
\ No newline at end of file
android/gradle.properties
+4
@@ -1,2 +1,6 @@
1
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
2
android.useAndroidX=true
3
+# This builtInKotlin flag was added automatically by Flutter migrator
4
+android.builtInKotlin=false
5
+# This newDsl flag was added automatically by Flutter migrator
6
+android.newDsl=false
lib/core/components/app_button.dart
+11
-7
@@ -1,5 +1,6 @@
1
import 'package:flutter/material.dart';
2
3
+import '../theme/colors.dart';
4
import 'app_sizes.dart';
5
6
class AppButton extends StatelessWidget {
@@ -25,7 +26,7 @@ class AppButton extends StatelessWidget {
26
@override
27
Widget build(BuildContext context) {
28
final isDark = Theme.of(context).brightness == Brightness.dark;
28
- final accent = AppColors.accentForDark(isDark);
29
+ final accent = isDark ? AppColors.darkAccent : AppColors.lightAccent;
30
final height = switch (size) {
31
AppButtonSize.small => 32.0,
32
AppButtonSize.medium => 44.0,
@@ -118,15 +119,17 @@ class AppCard extends StatelessWidget {
119
@override
120
Widget build(BuildContext context) {
121
final isDark = Theme.of(context).brightness == Brightness.dark;
121
- final radius = borderRadius ?? AppSizes.radiusMd;
122
+ final radius = borderRadius ?? AppSizes.radiusLg;
123
124
final container = Container(
124
- padding: padding ?? const EdgeInsets.all(AppSizes.md),
125
+ padding: padding ?? const EdgeInsets.all(AppSizes.lg),
126
margin: margin ?? const EdgeInsets.only(bottom: AppSizes.sm),
127
decoration: BoxDecoration(
127
- color: AppColors.surfaceForDark(isDark),
128
+ color: isDark ? AppColors.darkSurfaceCard : AppColors.lightSurface,
129
borderRadius: BorderRadius.circular(radius),
129
- border: Border.all(color: AppColors.borderForDark(isDark)),
130
+ border: Border.all(
131
+ color: isDark ? AppColors.darkBorder : AppColors.lightBorder,
132
+ ),
133
),
134
child: child,
135
);
@@ -147,13 +150,14 @@ class AppLoadingIndicator extends StatelessWidget {
150
151
@override
152
Widget build(BuildContext context) {
150
- final isDark = Theme.of(context).brightness == Brightness.dark;
153
return SizedBox(
154
width: size,
155
height: size,
156
child: CircularProgressIndicator(
157
strokeWidth: strokeWidth,
156
- color: AppColors.accentForDark(isDark),
158
+ color: Theme.of(context).brightness == Brightness.dark
159
+ ? AppColors.darkAccent
160
+ : AppColors.lightAccent,
161
),
162
);
163
}
lib/core/components/app_sizes.dart
+3
-31
@@ -1,5 +1,3 @@
1
-import 'package:flutter/material.dart';
2
-
1
class AppSizes {
2
AppSizes._();
3
@@ -17,7 +15,7 @@ class AppSizes {
15
static const double radiusMd = 8;
16
static const double radiusLg = 12;
17
static const double radiusXl = 16;
20
- static const double radiusRound = 999;
18
+ static const double radiusRound = 9999;
19
20
// Icon sizes
21
static const double iconSm = 16;
@@ -36,36 +34,10 @@ class AppSizes {
34
static const double textXxxl = 28;
35
36
// Layout
39
- static const double sidebarWidth = 300;
37
+ static const double sidebarWidth = 260;
38
static const double inputBarHeight = 64;
39
static const double appBarHeight = 56;
40
static const double cardMinHeight = 80;
41
static const double sheetMaxHeight = 0.7;
44
- static const double chatBubbleMaxWidth = 0.75;
45
-}
46
-
47
-class AppColors {
48
- AppColors._();
49
-
50
- static const Color darkAccent = Color(0xFF3B82F6);
51
- static const Color lightAccent = Color(0xFF2563EB);
52
- static const Color error = Color(0xFFEF4444);
53
- static const Color success = Color(0xFF22C55E);
54
- static const Color warning = Color(0xFFF59E0B);
55
-
56
- static Color darkSurface = const Color(0xFF1F1F1F);
57
- static Color darkBorder = const Color(0xFF2A2A2A);
58
- static Color darkMuted = const Color(0xFF666666);
59
-
60
- static Color lightSurface = const Color(0xFFF5F5F5);
61
- static Color lightBorder = const Color(0xFFE5E5E5);
62
- static Color lightMuted = const Color(0xFF999999);
63
-
64
- static Color accentForDark(bool isDark) => isDark ? darkAccent : lightAccent;
65
- static Color surfaceForDark(bool isDark) =>
66
- isDark ? darkSurface : lightSurface;
67
- static Color borderForDark(bool isDark) => isDark ? darkBorder : lightBorder;
68
- static Color mutedForDark(bool isDark) => isDark ? darkMuted : lightMuted;
69
- static Color primaryTextForDark(bool isDark) =>
70
- isDark ? Colors.white : Colors.black;
42
+ static const double chatBubbleMaxWidth = 768;
43
}
lib/core/theme/app_theme.dart
+10
-10
@@ -91,17 +91,17 @@ class AppTheme {
91
),
92
inputDecorationTheme: InputDecorationTheme(
93
filled: true,
94
- fillColor: AppColors.darkSurface,
94
+ fillColor: AppColors.darkSurfaceInput,
95
border: OutlineInputBorder(
96
- borderRadius: BorderRadius.circular(8),
96
+ borderRadius: BorderRadius.circular(12),
97
borderSide: const BorderSide(color: AppColors.darkBorder),
98
),
99
enabledBorder: OutlineInputBorder(
100
- borderRadius: BorderRadius.circular(8),
100
+ borderRadius: BorderRadius.circular(12),
101
borderSide: const BorderSide(color: AppColors.darkBorder),
102
),
103
focusedBorder: OutlineInputBorder(
104
- borderRadius: BorderRadius.circular(8),
104
+ borderRadius: BorderRadius.circular(12),
105
borderSide: const BorderSide(color: AppColors.darkAccent, width: 2),
106
),
107
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
@@ -109,7 +109,7 @@ class AppTheme {
109
elevatedButtonTheme: ElevatedButtonThemeData(
110
style: ElevatedButton.styleFrom(
111
backgroundColor: AppColors.darkAccent,
112
- foregroundColor: AppColors.darkBackground,
112
+ foregroundColor: Colors.white,
113
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
114
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
115
),
@@ -211,15 +211,15 @@ class AppTheme {
211
filled: true,
212
fillColor: AppColors.lightSurface,
213
border: OutlineInputBorder(
214
- borderRadius: BorderRadius.circular(8),
214
+ borderRadius: BorderRadius.circular(12),
215
borderSide: const BorderSide(color: AppColors.lightBorder),
216
),
217
enabledBorder: OutlineInputBorder(
218
- borderRadius: BorderRadius.circular(8),
218
+ borderRadius: BorderRadius.circular(12),
219
borderSide: const BorderSide(color: AppColors.lightBorder),
220
),
221
focusedBorder: OutlineInputBorder(
222
- borderRadius: BorderRadius.circular(8),
222
+ borderRadius: BorderRadius.circular(12),
223
borderSide: const BorderSide(color: AppColors.lightAccent, width: 2),
224
),
225
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
@@ -349,12 +349,12 @@ class AppTheme {
349
bottomSheetTheme: const BottomSheetThemeData(
350
backgroundColor: AppColors.claudeSurface,
351
shape: RoundedRectangleBorder(
352
- borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
352
+ borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
353
),
354
),
355
dialogTheme: DialogThemeData(
356
backgroundColor: AppColors.claudeSurface,
357
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
357
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
358
),
359
navigationBarTheme: NavigationBarThemeData(
360
backgroundColor: AppColors.claudeBackground,
lib/core/theme/colors.dart
+13
-11
@@ -1,13 +1,15 @@
1
import 'package:flutter/material.dart';
2
3
class AppColors {
4
- // Dark Theme (Obsidian)
5
- static const Color darkBackground = Color(0xFF0A0A0A);
6
- static const Color darkSurface = Color(0xFF171717);
7
- static const Color darkBorder = Color(0xFF262626);
8
- static const Color darkPrimaryText = Color(0xFFFAFAFA);
9
- static const Color darkMutedText = Color(0xFFA1A1AA);
10
- static const Color darkAccent = Color(0xFFFAFAFA); // White accent for monochromatic look
4
+ // Dark Theme — DESIGN.md tokens
5
+ static const Color darkBackground = Color(0xFF212121); // surface-main
6
+ static const Color darkSurface = Color(0xFF171717); // surface-sidebar
7
+ static const Color darkSurfaceInput = Color(0xFF2F2F2F); // surface-input
8
+ static const Color darkSurfaceCard = Color(0xFF2F2F2F); // surface-card
9
+ static const Color darkBorder = Color(0xFF424242); // border-subtle
10
+ static const Color darkPrimaryText = Color(0xFFECECEC); // text-primary
11
+ static const Color darkMutedText = Color(0xFFB4B4B4); // text-muted
12
+ static const Color darkAccent = Colors.white; // primary (OpenAI green)
13
14
// Light Theme
15
static const Color lightBackground = Color(0xFFFAFAFA);
@@ -15,7 +17,7 @@ class AppColors {
17
static const Color lightBorder = Color(0xFFE5E5E5);
18
static const Color lightPrimaryText = Color(0xFF0A0A0A);
19
static const Color lightMutedText = Color(0xFF71717A);
18
- static const Color lightAccent = Color(0xFF0A0A0A);
20
+ static const Color lightAccent = Colors.black; // primary (OpenAI green)
21
22
// Claude Theme
23
static const Color claudeBackground = Color(0xFFF5F4ED);
@@ -24,16 +26,16 @@ class AppColors {
26
static const Color claudePrimaryText = Color(0xFF141413);
27
static const Color claudeSecondaryText = Color(0xFF5E5D59);
28
static const Color claudeMutedText = Color(0xFF87867F);
27
- static const Color claudeAccent = Color(0xFFC96442);
29
+ static const Color claudeAccent =
30
+ Colors.deepOrangeAccent; // primary (OpenAI green)
31
29
- // Claude Dark Theme (Optional, but included in design)
32
+ // Claude Dark Theme
33
static const Color claudeDarkBackground = Color(0xFF141413);
34
static const Color claudeDarkSurface = Color(0xFF30302E);
35
static const Color claudeDarkBorder = Color(0xFF30302E);
36
static const Color claudeDarkPrimaryText = Color(0xFFFAF9F5);
37
static const Color claudeDarkSecondaryText = Color(0xFFB0AEA5);
38
36
-
39
static const Color success = Color(0xFF22C55E);
40
static const Color error = Color(0xFFEF4444);
41
static const Color warning = Color(0xFFF59E0B);
lib/core/theme/typography.dart
+21
-23
@@ -52,55 +52,55 @@ class AppTypography {
52
}
53
54
static TextTheme get claudeTextTheme {
55
- return GoogleFonts.loraTextTheme(
55
+ return GoogleFonts.interTextTheme(
56
const TextTheme(
57
displayLarge: TextStyle(
58
color: AppColors.claudePrimaryText,
59
- fontSize: 64,
60
- fontWeight: FontWeight.w500,
61
- height: 1.1,
59
+ fontSize: 24,
60
+ fontWeight: FontWeight.w600,
61
+ height: 1.2,
62
),
63
displayMedium: TextStyle(
64
color: AppColors.claudePrimaryText,
65
- fontSize: 52,
66
- fontWeight: FontWeight.w500,
65
+ fontSize: 24,
66
+ fontWeight: FontWeight.w600,
67
height: 1.2,
68
),
69
displaySmall: TextStyle(
70
color: AppColors.claudePrimaryText,
71
- fontSize: 36,
72
- fontWeight: FontWeight.w500,
73
- height: 1.3,
71
+ fontSize: 24,
72
+ fontWeight: FontWeight.w600,
73
+ height: 1.2,
74
),
75
headlineLarge: TextStyle(
76
color: AppColors.claudePrimaryText,
77
- fontSize: 32,
78
- fontWeight: FontWeight.w500,
79
- height: 1.1,
77
+ fontSize: 24,
78
+ fontWeight: FontWeight.w600,
79
+ height: 1.2,
80
),
81
headlineMedium: TextStyle(
82
color: AppColors.claudePrimaryText,
83
- fontSize: 25,
84
- fontWeight: FontWeight.w500,
83
+ fontSize: 20,
84
+ fontWeight: FontWeight.w600,
85
height: 1.2,
86
),
87
headlineSmall: TextStyle(
88
color: AppColors.claudePrimaryText,
89
- fontSize: 20,
90
- fontWeight: FontWeight.w500,
89
+ fontSize: 18,
90
+ fontWeight: FontWeight.w600,
91
height: 1.2,
92
),
93
titleLarge: TextStyle(
94
color: AppColors.claudePrimaryText,
95
fontSize: 17,
96
fontWeight: FontWeight.w500,
97
- height: 1.1,
97
+ height: 1.2,
98
),
99
),
100
).copyWith(
101
bodyLarge: GoogleFonts.inter(
102
color: AppColors.claudePrimaryText,
103
- fontSize: 17,
103
+ fontSize: 16,
104
fontWeight: FontWeight.w400,
105
height: 1.6,
106
),
@@ -111,8 +111,8 @@ class AppTypography {
111
height: 1.6,
112
),
113
bodySmall: GoogleFonts.inter(
114
- color: AppColors.claudeSecondaryText,
115
- fontSize: 15,
114
+ color: AppColors.claudeMutedText,
115
+ fontSize: 14,
116
fontWeight: FontWeight.w400,
117
height: 1.6,
118
),
@@ -123,18 +123,16 @@ class AppTypography {
123
height: 1.4,
124
),
125
labelMedium: GoogleFonts.inter(
126
- color: AppColors.claudeSecondaryText,
126
+ color: AppColors.claudeMutedText,
127
fontSize: 12,
128
fontWeight: FontWeight.w500,
129
height: 1.4,
130
- letterSpacing: 0.12,
130
),
131
labelSmall: GoogleFonts.inter(
132
color: AppColors.claudeMutedText,
133
fontSize: 10,
134
fontWeight: FontWeight.w500,
135
height: 1.6,
137
- letterSpacing: 0.5,
136
),
137
);
138
}
lib/features/chat/views/chat_screen.dart
+30
-29
@@ -12,6 +12,7 @@ import '../../personas/providers/personas_providers.dart';
12
import '../../servers/providers/server_providers.dart';
13
import 'package:shadcn_ui/shadcn_ui.dart';
14
15
+import 'package:localmind/core/theme/colors.dart';
16
import '../../../core/models/enums.dart';
17
import '../../../core/providers/app_providers.dart';
18
import '../../../core/routes/app_routes.dart';
@@ -544,8 +545,8 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
545
final p = personas[index];
546
final isSelected = p.id == currentPersonaId;
547
final accent = isDark
547
- ? const Color(0xFF3B82F6)
548
- : const Color(0xFF2563EB);
548
+ ? AppColors.darkAccent
549
+ : AppColors.lightAccent;
550
return ListTile(
551
leading: Text(
552
p.emoji,
@@ -566,8 +567,8 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
567
style: TextStyle(
568
fontSize: 12,
569
color: isDark
569
- ? const Color(0xFF888888)
570
- : const Color(0xFF999999),
570
+ ? AppColors.darkMutedText
571
+ : AppColors.lightMutedText,
572
),
573
maxLines: 1,
574
overflow: TextOverflow.ellipsis,
@@ -645,8 +646,8 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
646
final p = personas[index];
647
final isSelected = p.id == currentPersona?.id;
648
final accent = isDark
648
- ? const Color(0xFF3B82F6)
649
- : const Color(0xFF2563EB);
649
+ ? AppColors.darkAccent
650
+ : AppColors.lightAccent;
651
return ListTile(
652
leading: Text(
653
p.emoji,
@@ -667,8 +668,8 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
668
style: TextStyle(
669
fontSize: 12,
670
color: isDark
670
- ? const Color(0xFF888888)
671
- : const Color(0xFF999999),
671
+ ? AppColors.darkMutedText
672
+ : AppColors.lightMutedText,
673
),
674
maxLines: 1,
675
overflow: TextOverflow.ellipsis,
@@ -1001,12 +1002,12 @@ class _EmptyStateState extends State<_EmptyState>
1002
vertical: 12,
1003
),
1004
decoration: BoxDecoration(
1004
- color: isDark ? const Color(0xFF1A1A1A) : Colors.white,
1005
+ color: isDark ? AppColors.darkBackground : AppColors.lightSurface,
1006
borderRadius: BorderRadius.circular(12),
1007
border: Border.all(
1008
color: isDark
1008
- ? const Color(0xFF333333)
1009
- : const Color(0xFFE5E5E5),
1009
+ ? AppColors.darkBorder
1010
+ : AppColors.lightBorder,
1011
),
1012
),
1013
child: Row(
@@ -1050,12 +1051,12 @@ class _EmptyStateState extends State<_EmptyState>
1051
vertical: 12,
1052
),
1053
decoration: BoxDecoration(
1053
- color: isDark ? const Color(0xFF1A1A1A) : Colors.white,
1054
+ color: isDark ? AppColors.darkBackground : AppColors.lightSurface,
1055
borderRadius: BorderRadius.circular(12),
1056
border: Border.all(
1057
color: isDark
1057
- ? const Color(0xFF333333)
1058
- : const Color(0xFFE5E5E5),
1058
+ ? AppColors.darkBorder
1059
+ : AppColors.lightBorder,
1060
),
1061
),
1062
child: Row(
@@ -1159,12 +1160,12 @@ class _EmptyStateState extends State<_EmptyState>
1160
label: Text(prompt),
1161
onPressed: () => widget.onQuickPrompt(prompt),
1162
backgroundColor: isDark
1162
- ? const Color(0xFF2A2A2A)
1163
- : const Color(0xFFF5F5F5),
1163
+ ? AppColors.darkSurfaceCard
1164
+ : AppColors.lightSurface,
1165
side: BorderSide(
1166
color: isDark
1166
- ? const Color(0xFF3A3A3A)
1167
- : const Color(0xFFE5E5E5),
1167
+ ? AppColors.darkBorder
1168
+ : AppColors.lightBorder,
1169
),
1170
),
1171
);
@@ -1181,8 +1182,8 @@ class _EmptyStateState extends State<_EmptyState>
1182
fontSize: 14,
1183
fontWeight: FontWeight.w500,
1184
color: isDark
1184
- ? const Color(0xFFA0A0A0)
1185
- : const Color(0xFF666666),
1185
+ ? AppColors.darkMutedText
1186
+ : AppColors.lightMutedText,
1187
),
1188
),
1189
const SizedBox(width: 8),
@@ -1194,8 +1195,8 @@ class _EmptyStateState extends State<_EmptyState>
1195
fontSize: 14,
1196
fontWeight: FontWeight.w500,
1197
color: isDark
1197
- ? const Color(0xFF3B82F6)
1198
- : const Color(0xFF2563EB),
1198
+ ? AppColors.darkAccent
1199
+ : AppColors.lightAccent,
1200
),
1201
),
1202
),
@@ -1251,10 +1252,10 @@ class _RecentConversationItem extends ConsumerWidget {
1252
width: 300,
1253
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
1254
decoration: BoxDecoration(
1254
- color: isDark ? const Color(0xFF1A1A1A) : const Color(0xFFF5F5F5),
1255
+ color: isDark ? AppColors.darkBackground : AppColors.lightSurface,
1256
borderRadius: BorderRadius.circular(8),
1257
border: Border.all(
1257
- color: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFE5E5E5),
1258
+ color: isDark ? AppColors.darkSurfaceCard : AppColors.lightBorder,
1259
),
1260
),
1261
child: Row(
@@ -1262,7 +1263,7 @@ class _RecentConversationItem extends ConsumerWidget {
1263
Icon(
1264
Icons.chat_bubble_outline,
1265
size: 18,
1265
- color: isDark ? const Color(0xFF888888) : const Color(0xFF666666),
1266
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
1267
),
1268
const SizedBox(width: 12),
1269
Expanded(
@@ -1285,8 +1286,8 @@ class _RecentConversationItem extends ConsumerWidget {
1286
style: TextStyle(
1287
fontSize: 12,
1288
color: isDark
1288
- ? const Color(0xFF888888)
1289
- : const Color(0xFF666666),
1289
+ ? AppColors.darkMutedText
1290
+ : AppColors.lightMutedText,
1291
),
1292
maxLines: 1,
1293
overflow: TextOverflow.ellipsis,
@@ -1299,7 +1300,7 @@ class _RecentConversationItem extends ConsumerWidget {
1300
? Icons.chevron_left
1301
: Icons.chevron_right,
1302
size: 18,
1302
- color: isDark ? const Color(0xFF666666) : const Color(0xFF999999),
1303
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
1304
),
1305
],
1306
),
@@ -1622,7 +1623,7 @@ class _PersonaIndicator extends StatelessWidget {
1623
1624
return Container(
1625
width: double.infinity,
1625
- color: isDark ? const Color(0xFF121212) : const Color(0xFFF9F9F9),
1626
+ color: isDark ? AppColors.darkBackground : AppColors.lightSurface,
1627
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
1628
child: Row(
1629
children: [
lib/features/chat/views/components/audio_player_widget.dart
+3
-2
@@ -2,6 +2,7 @@ import 'dart:async';
2
3
import 'package:audioplayers/audioplayers.dart';
4
import 'package:flutter/material.dart';
5
+import 'package:localmind/core/theme/colors.dart';
6
7
class AudioPlayerWidget extends StatefulWidget {
8
final String source;
@@ -84,10 +85,10 @@ class _AudioPlayerWidgetState extends State<AudioPlayerWidget> {
85
constraints: const BoxConstraints(maxWidth: 280),
86
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
87
decoration: BoxDecoration(
87
- color: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFF0F0F0),
88
+ color: isDark ? AppColors.darkSurfaceCard : AppColors.lightSurface,
89
borderRadius: BorderRadius.circular(12),
90
border: Border.all(
90
- color: isDark ? const Color(0xFF3A3A3A) : const Color(0xFFE5E5E5),
91
+ color: isDark ? AppColors.darkBorder : AppColors.lightBorder,
92
),
93
),
94
child: Row(
lib/features/chat/views/components/chat_bubble.dart
+37
-34
@@ -4,6 +4,7 @@ import 'dart:io';
4
import 'package:flutter/material.dart';
5
import 'package:gpt_markdown/gpt_markdown.dart';
6
import 'package:localmind/l10n/app_localizations.dart';
7
+import 'package:localmind/core/theme/colors.dart';
8
import '../../../../core/models/enums.dart';
9
import '../../data/models/message.dart';
10
import 'message_action_bar.dart';
@@ -128,6 +129,8 @@ class _UserBubble extends StatelessWidget {
129
final theme = Theme.of(context);
130
final isDark = theme.brightness == Brightness.dark;
131
132
+ final double maxBubbleWidth = 768;
133
+ final double availableWidth = MediaQuery.of(context).size.width * 0.75;
134
return Align(
135
alignment: AlignmentDirectional.centerEnd,
136
child: Column(
@@ -135,7 +138,7 @@ class _UserBubble extends StatelessWidget {
138
children: [
139
Container(
140
constraints: BoxConstraints(
138
- maxWidth: MediaQuery.of(context).size.width * 0.75,
141
+ maxWidth: maxBubbleWidth < availableWidth ? maxBubbleWidth : availableWidth,
142
),
143
margin: const EdgeInsetsDirectional.only(
144
start: 48,
@@ -145,7 +148,7 @@ class _UserBubble extends StatelessWidget {
148
),
149
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
150
decoration: BoxDecoration(
148
- color: isDark ? const Color(0xFF3B82F6) : const Color(0xFF2563EB),
151
+ color: isDark ? AppColors.darkSurfaceCard : AppColors.lightSurface,
152
borderRadius: BorderRadiusDirectional.only(
153
topStart: Radius.circular(18),
154
topEnd: Radius.circular(18),
@@ -169,10 +172,10 @@ class _UserBubble extends StatelessWidget {
172
child: _ThemedGptMarkdown(
173
content: message.content,
174
isDark: isDark,
172
- style: const TextStyle(
173
- color: Colors.white,
174
- fontSize: 15,
175
- height: 1.4,
175
+ style: TextStyle(
176
+ color: isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText,
177
+ fontSize: 16,
178
+ height: 1.6,
179
),
180
),
181
),
@@ -189,8 +192,8 @@ class _UserBubble extends StatelessWidget {
192
style: TextStyle(
193
fontSize: 11,
194
color: isDark
192
- ? const Color(0xFF666666)
193
- : const Color(0xFF999999),
195
+ ? AppColors.darkMutedText
196
+ : AppColors.lightMutedText,
197
),
198
),
199
if (message.status == MessageStatus.error) ...[
@@ -293,8 +296,8 @@ class _AssistantBubble extends StatelessWidget {
296
style: TextStyle(
297
fontSize: 11,
298
color: isDark
296
- ? const Color(0xFF666666)
297
- : const Color(0xFF999999),
299
+ ? AppColors.darkMutedText
300
+ : AppColors.lightMutedText,
301
),
302
),
303
if (message.status == MessageStatus.error) ...[
@@ -466,10 +469,10 @@ class _ThemedGptMarkdown extends StatelessWidget {
469
highlightColor: isDark
470
? const Color(0xFF334155)
471
: const Color(0xFFDBEAFE),
469
- linkColor: isDark ? const Color(0xFF60A5FA) : const Color(0xFF2563EB),
472
+ linkColor: isDark ? AppColors.darkAccent : AppColors.lightAccent,
473
linkHoverColor: isDark
471
- ? const Color(0xFF93C5FD)
472
- : const Color(0xFF1D4ED8),
474
+ ? AppColors.darkAccent
475
+ : AppColors.lightAccent,
476
hrLineColor: isDark ? const Color(0xFF334155) : const Color(0xFFE2E8F0),
477
hrLineThickness: 1.0,
478
h1: style.copyWith(fontSize: 24, fontWeight: FontWeight.w700),
@@ -659,8 +662,8 @@ class _ToolBubble extends StatelessWidget {
662
Icons.build_outlined,
663
size: 14,
664
color: isDark
662
- ? const Color(0xFF9CA3AF)
663
- : const Color(0xFF6B7280),
665
+ ? AppColors.darkMutedText
666
+ : AppColors.lightMutedText,
667
),
668
const SizedBox(width: 4),
669
Text(
@@ -671,8 +674,8 @@ class _ToolBubble extends StatelessWidget {
674
fontSize: 12,
675
fontWeight: FontWeight.w500,
676
color: isDark
674
- ? const Color(0xFF9CA3AF)
675
- : const Color(0xFF6B7280),
677
+ ? AppColors.darkMutedText
678
+ : AppColors.lightMutedText,
679
),
680
),
681
],
@@ -832,13 +835,13 @@ class _ToolTimeline extends StatelessWidget {
835
margin: const EdgeInsets.only(top: 8, bottom: 4),
836
decoration: BoxDecoration(
837
color: isDark
835
- ? const Color(0xFF1E1E2E).withValues(alpha: 0.6)
836
- : const Color(0xFFF5F7FF).withValues(alpha: 0.8),
838
+ ? AppColors.darkSurfaceInput
839
+ : AppColors.lightSurface,
840
borderRadius: BorderRadius.circular(12),
841
border: Border.all(
842
color: isDark
840
- ? const Color(0xFF313244).withValues(alpha: 0.8)
841
- : const Color(0xFFE0E5F5),
843
+ ? AppColors.darkBorder
844
+ : AppColors.lightBorder,
845
),
846
boxShadow: [
847
BoxShadow(
@@ -858,8 +861,8 @@ class _ToolTimeline extends StatelessWidget {
861
border: Border(
862
bottom: BorderSide(
863
color: isDark
861
- ? const Color(0xFF313244).withValues(alpha: 0.5)
862
- : const Color(0xFFE0E5F5).withValues(alpha: 0.5),
864
+ ? AppColors.darkBorder
865
+ : AppColors.lightBorder,
866
),
867
),
868
),
@@ -869,8 +872,8 @@ class _ToolTimeline extends StatelessWidget {
872
Icons.terminal_rounded,
873
size: 16,
874
color: isDark
872
- ? const Color(0xFFBAC2DE)
873
- : const Color(0xFF585B70),
875
+ ? AppColors.darkMutedText
876
+ : AppColors.lightMutedText,
877
),
878
const SizedBox(width: 8),
879
Text(
@@ -880,15 +883,15 @@ class _ToolTimeline extends StatelessWidget {
883
fontWeight: FontWeight.bold,
884
letterSpacing: 0.5,
885
color: isDark
883
- ? const Color(0xFFBAC2DE)
884
- : const Color(0xFF585B70),
886
+ ? AppColors.darkMutedText
887
+ : AppColors.lightMutedText,
888
),
889
),
890
const Spacer(),
891
Container(
892
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
893
decoration: BoxDecoration(
891
- color: isDark ? const Color(0xFF313244) : const Color(0xFFE0E5F5),
894
+ color: isDark ? AppColors.darkSurfaceCard : AppColors.lightBorder,
895
borderRadius: BorderRadius.circular(6),
896
),
897
child: Text(
@@ -987,7 +990,7 @@ class _ToolRowWidgetState extends State<_ToolRowWidget> {
990
ToolEventStatus.completed => const Color(0xFF22C55E),
991
ToolEventStatus.failed ||
992
ToolEventStatus.rejected => const Color(0xFFEF4444),
990
- ToolEventStatus.running => const Color(0xFF3B82F6),
993
+ ToolEventStatus.running => AppColors.darkAccent,
994
ToolEventStatus.requested ||
995
ToolEventStatus.approved => const Color(0xFFF59E0B),
996
};
@@ -1086,7 +1089,7 @@ class _ToolRowWidgetState extends State<_ToolRowWidget> {
1089
'${call.durationMs}ms',
1090
style: TextStyle(
1091
fontSize: 10,
1089
- color: isDark ? const Color(0xFF6B7280) : const Color(0xFF9CA3AF),
1092
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
1093
),
1094
),
1095
],
@@ -1097,7 +1100,7 @@ class _ToolRowWidgetState extends State<_ToolRowWidget> {
1100
? Icons.keyboard_arrow_up_rounded
1101
: Icons.keyboard_arrow_down_rounded,
1102
size: 16,
1100
- color: isDark ? const Color(0xFF9CA3AF) : const Color(0xFF6B7280),
1103
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
1104
),
1105
],
1106
],
@@ -1311,10 +1314,10 @@ class _AttachmentItem extends StatelessWidget {
1314
width: 160,
1315
padding: const EdgeInsets.all(12),
1316
decoration: BoxDecoration(
1314
- color: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFF0F0F0),
1317
+ color: isDark ? AppColors.darkSurfaceCard : AppColors.lightSurface,
1318
borderRadius: BorderRadius.circular(12),
1319
border: Border.all(
1317
- color: isDark ? const Color(0xFF3A3A3A) : const Color(0xFFE5E5E5),
1320
+ color: isDark ? AppColors.darkBorder : AppColors.lightBorder,
1321
),
1322
),
1323
child: Row(
@@ -1323,7 +1326,7 @@ class _AttachmentItem extends StatelessWidget {
1326
Icon(
1327
Icons.insert_drive_file_outlined,
1328
size: 20,
1326
- color: isDark ? const Color(0xFF888888) : const Color(0xFF666666),
1329
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
1330
),
1331
const SizedBox(width: 8),
1332
Expanded(
lib/features/chat/views/components/chat_input_bar.dart
+9
-14
@@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
6
import 'package:haptic_feedback/haptic_feedback.dart';
7
import 'package:hugeicons/hugeicons.dart';
8
import 'package:localmind/l10n/app_localizations.dart';
9
+import 'package:localmind/core/theme/colors.dart';
10
11
import '../../../../core/models/enums.dart';
12
import '../../../servers/providers/server_providers.dart';
@@ -103,6 +104,7 @@ class _ChatInputBarState extends ConsumerState<ChatInputBar>
104
105
Widget _buildMicButton(bool isListening, ThemeData theme) {
106
final l10n = AppLocalizations.of(context)!;
107
+ final isDark = theme.brightness == Brightness.dark;
108
return AnimatedBuilder(
109
animation: _micAnimController,
110
builder: (context, child) {
@@ -134,7 +136,7 @@ class _ChatInputBarState extends ConsumerState<ChatInputBar>
136
? HugeIcons.strokeRoundedVoice
137
: HugeIcons.strokeRoundedMic01,
138
color: isListening
137
- ? Colors.white
139
+ ? (isDark ? Colors.white : Colors.black)
140
: theme.colorScheme.onSurface.withValues(alpha: 0.7),
141
size: 20,
142
),
@@ -306,18 +308,11 @@ class _ChatInputBarState extends ConsumerState<ChatInputBar>
308
margin: const EdgeInsets.only(left: 20, right: 20, top: 8, bottom: 16),
309
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
310
decoration: BoxDecoration(
309
- color: theme.colorScheme.surface,
310
- borderRadius: BorderRadius.circular(40),
311
- boxShadow: [
312
- BoxShadow(
313
- color: Colors.black.withValues(alpha: isDark ? 0.3 : 0.08),
314
- blurRadius: 20,
315
- offset: const Offset(0, 4),
316
- ),
317
- ],
311
+ color: isDark ? AppColors.darkSurfaceInput : AppColors.lightSurface,
312
+ borderRadius: BorderRadius.circular(12),
313
border: Border.all(
319
- color: theme.colorScheme.outline,
320
- width: 1.2,
314
+ color: isDark ? AppColors.darkBorder : AppColors.lightBorder,
315
+ width: 1,
316
),
317
),
318
child: Column(
@@ -350,8 +345,8 @@ class _ChatInputBarState extends ConsumerState<ChatInputBar>
345
borderRadius: BorderRadius.circular(12),
346
border: Border.all(
347
color: isDark
353
- ? const Color(0xFF333333)
354
- : const Color(0xFFE0E0E0),
348
+ ? AppColors.darkBorder
349
+ : AppColors.lightBorder,
350
),
351
),
352
child: ClipRRect(
lib/features/chat/views/components/chat_settings_sheet.dart
+8
-7
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
3
import 'package:shadcn_ui/shadcn_ui.dart';
4
import 'package:hugeicons/hugeicons.dart';
5
import 'package:localmind/l10n/app_localizations.dart';
6
+import 'package:localmind/core/theme/colors.dart';
7
import '../../../../core/providers/app_providers.dart';
8
import '../../../conversations/providers/conversation_providers.dart' as conv;
9
import '../../providers/chat_mcp_providers.dart';
@@ -59,7 +60,7 @@ class _ChatSettingsSheetState extends ConsumerState<ChatSettingsSheet> {
60
return Container(
61
padding: const EdgeInsets.fromLTRB(20, 12, 20, 20),
62
decoration: BoxDecoration(
62
- color: isDark ? const Color(0xFF121212) : Colors.white,
63
+ color: isDark ? AppColors.darkBackground : AppColors.lightSurface,
64
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
65
boxShadow: [
66
BoxShadow(
@@ -80,8 +81,8 @@ class _ChatSettingsSheetState extends ConsumerState<ChatSettingsSheet> {
81
height: 4,
82
decoration: BoxDecoration(
83
color: isDark
83
- ? const Color(0xFF2A2A2A)
84
- : const Color(0xFFE5E5E5),
84
+ ? AppColors.darkSurfaceCard
85
+ : AppColors.lightBorder,
86
borderRadius: BorderRadius.circular(2),
87
),
88
),
@@ -339,13 +340,13 @@ class _ChatSettingsSheetState extends ConsumerState<ChatSettingsSheet> {
340
margin: const EdgeInsets.only(bottom: 8),
341
decoration: BoxDecoration(
342
color: isDark
342
- ? const Color(0xFF1E1E1E)
343
- : const Color(0xFFF9FAFB),
343
+ ? AppColors.darkBackground
344
+ : AppColors.lightSurface,
345
borderRadius: BorderRadius.circular(12),
346
border: Border.all(
347
color: isDark
347
- ? const Color(0xFF2A2A2A)
348
- : const Color(0xFFE5E7EB),
348
+ ? AppColors.darkSurfaceCard
349
+ : AppColors.lightBorder,
350
),
351
),
352
child: ListTile(
lib/features/chat/views/components/message_action_bar.dart
+5
-4
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
3
import 'package:flutter_riverpod/flutter_riverpod.dart';
4
import 'package:shadcn_ui/shadcn_ui.dart';
5
import 'package:localmind/l10n/app_localizations.dart';
6
+import 'package:localmind/core/theme/colors.dart';
7
import '../../../tts/providers/tts_providers.dart' as tts;
8
9
class MessageActionBar extends ConsumerStatefulWidget {
@@ -259,11 +260,11 @@ class _ActionButtonState extends State<_ActionButton>
260
261
final baseColor = widget.isDestructive
262
? (isDark ? Colors.red[300] : Colors.red[600])
262
- : (isDark ? const Color(0xFF888888) : const Color(0xFF666666));
263
+ : (isDark ? AppColors.darkMutedText : AppColors.lightMutedText);
264
265
final hoverColor = widget.isDestructive
266
? (isDark ? Colors.red[200] : Colors.red[500])
266
- : (isDark ? const Color(0xFFAAAAAA) : const Color(0xFF444444));
267
+ : (isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText);
268
269
return MouseRegion(
270
onEnter: (_) => setState(() => _isHovered = true),
@@ -288,8 +289,8 @@ class _ActionButtonState extends State<_ActionButton>
289
decoration: BoxDecoration(
290
color: _isHovered
291
? (isDark
291
- ? const Color(0xFF2A2A2A)
292
- : const Color(0xFFE5E5E5))
292
+ ? AppColors.darkSurfaceCard
293
+ : AppColors.lightBorder)
294
: Colors.transparent,
295
borderRadius: BorderRadius.circular(4),
296
),
lib/features/chat/views/components/processing_indicator.dart
+3
-2
@@ -1,5 +1,6 @@
1
import 'package:flutter/material.dart';
2
import 'package:localmind/l10n/app_localizations.dart';
3
+import 'package:localmind/core/theme/colors.dart';
4
5
class ProcessingIndicator extends StatelessWidget {
6
const ProcessingIndicator({super.key});
@@ -18,7 +19,7 @@ class ProcessingIndicator extends StatelessWidget {
19
child: CircularProgressIndicator(
20
strokeWidth: 2,
21
valueColor: AlwaysStoppedAnimation<Color>(
21
- isDark ? Colors.grey[400]! : Colors.grey[600]!,
22
+ isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
23
),
24
),
25
),
@@ -26,7 +27,7 @@ class ProcessingIndicator extends StatelessWidget {
27
Text(
28
l10n.processing,
29
style: TextStyle(
29
- color: isDark ? Colors.grey[400] : Colors.grey[600],
30
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
31
fontSize: 14,
32
fontStyle: FontStyle.italic,
33
),
lib/features/chat/views/components/reasoning_widget.dart
+13
-12
@@ -1,6 +1,7 @@
1
import 'package:flutter/material.dart';
2
import 'package:gpt_markdown/gpt_markdown.dart';
3
import 'package:localmind/l10n/app_localizations.dart';
4
+import 'package:localmind/core/theme/colors.dart';
5
6
class ReasoningWidget extends StatefulWidget {
7
final String? reasoningContent;
@@ -81,13 +82,13 @@ class _ReasoningWidgetState extends State<ReasoningWidget> with SingleTickerProv
82
margin: const EdgeInsets.only(bottom: 12, top: 4),
83
decoration: BoxDecoration(
84
color: isDark
84
- ? const Color(0xFF1E1E2E).withValues(alpha: 0.6)
85
- : const Color(0xFFF5F7FF).withValues(alpha: 0.8),
85
+ ? AppColors.darkSurfaceInput
86
+ : AppColors.lightSurface,
87
borderRadius: BorderRadius.circular(12),
88
border: Border.all(
89
color: isDark
89
- ? const Color(0xFF313244).withValues(alpha: 0.8)
90
- : const Color(0xFFE0E5F5),
90
+ ? AppColors.darkBorder
91
+ : AppColors.lightBorder,
92
width: 1.0,
93
),
94
boxShadow: [
@@ -139,8 +140,8 @@ class _ReasoningWidgetState extends State<ReasoningWidget> with SingleTickerProv
140
Icons.psychology_outlined,
141
size: 18,
142
color: isDark
142
- ? const Color(0xFFBAC2DE)
143
- : const Color(0xFF585B70),
143
+ ? AppColors.darkMutedText
144
+ : AppColors.lightMutedText,
145
),
146
const SizedBox(width: 8),
147
Text(
@@ -173,8 +174,8 @@ class _ReasoningWidgetState extends State<ReasoningWidget> with SingleTickerProv
174
Icons.keyboard_arrow_down_rounded,
175
size: 18,
176
color: isDark
176
- ? const Color(0xFF9399B2)
177
- : const Color(0xFF7C7F93),
177
+ ? AppColors.darkMutedText
178
+ : AppColors.lightMutedText,
179
),
180
),
181
],
@@ -193,8 +194,8 @@ class _ReasoningWidgetState extends State<ReasoningWidget> with SingleTickerProv
194
gptThemeData: GptMarkdownThemeData(
195
brightness: isDark ? Brightness.dark : Brightness.light,
196
highlightColor: isDark
196
- ? const Color(0xFF313244)
197
- : const Color(0xFFEFF1F5),
197
+ ? AppColors.darkBorder
198
+ : AppColors.lightBorder,
199
linkColor: theme.colorScheme.primary,
200
),
201
child: GptMarkdown(
@@ -202,8 +203,8 @@ class _ReasoningWidgetState extends State<ReasoningWidget> with SingleTickerProv
203
style: TextStyle(
204
fontSize: 13,
205
color: isDark
205
- ? const Color(0xFFCDD6F4).withValues(alpha: 0.95)
206
- : const Color(0xFF4C4F69).withValues(alpha: 0.95),
206
+ ? AppColors.darkPrimaryText
207
+ : AppColors.lightPrimaryText,
208
height: 1.5,
209
),
210
),
lib/features/chat/views/components/typing_indicator.dart
+4
-3
@@ -1,4 +1,5 @@
1
import 'package:flutter/material.dart';
2
+import 'package:localmind/core/theme/colors.dart';
3
4
class TypingIndicator extends StatefulWidget {
5
const TypingIndicator({super.key});
@@ -45,7 +46,7 @@ class _TypingIndicatorState extends State<TypingIndicator>
46
final theme = Theme.of(context);
47
final isDark = theme.brightness == Brightness.dark;
48
48
- final dotColor = isDark ? const Color(0xFF888888) : const Color(0xFF666666);
49
+ final dotColor = isDark ? AppColors.darkMutedText : AppColors.lightMutedText;
50
51
return Padding(
52
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
@@ -111,8 +112,8 @@ class _StreamingIndicatorState extends State<StreamingIndicator>
112
final isDark = theme.brightness == Brightness.dark;
113
114
final cursorColor = isDark
114
- ? const Color(0xFF888888)
115
- : const Color(0xFF666666);
115
+ ? AppColors.darkMutedText
116
+ : AppColors.lightMutedText;
117
118
return AnimatedBuilder(
119
animation: _cursorController,
lib/features/conversations/views/chat_history_screen.dart
+11
-7
@@ -18,12 +18,17 @@ class ChatHistoryScreen extends ConsumerWidget {
18
final groupedConversations = ref.watch(groupedConversationsProvider);
19
final activeConversation = ref.watch(activeConversationProvider);
20
final searchQuery = ref.watch(conversationSearchProvider);
21
+ final topPadding = MediaQuery.of(context).padding.top;
22
22
- return SafeArea(
23
- child: Column(
24
- children: [
25
- Container(
26
- padding: const EdgeInsets.only(left: 16, right: 16, bottom: 12),
23
+ return Column(
24
+ children: [
25
+ Container(
26
+ padding: EdgeInsets.only(
27
+ left: 16,
28
+ right: 16,
29
+ top: topPadding + 8,
30
+ bottom: 16,
31
+ ),
32
decoration: BoxDecoration(
33
color: isDark ? const Color(0xFF0A0A0A) : const Color(0xFFFAFAFA),
34
border: Border(
@@ -77,8 +82,7 @@ class ChatHistoryScreen extends ConsumerWidget {
82
),
83
),
84
),
80
- ],
81
- ),
85
+ ],
86
);
87
}
88
}
lib/features/conversations/views/components/conversation_tile.dart
+17
-16
@@ -1,6 +1,7 @@
1
import 'package:flutter/material.dart';
2
import 'package:hugeicons/hugeicons.dart';
3
import 'package:localmind/l10n/app_localizations.dart';
4
+import 'package:localmind/core/theme/colors.dart';
5
import '../../data/models/conversation.dart';
6
7
class ConversationTile extends StatelessWidget {
@@ -62,8 +63,8 @@ class ConversationTile extends StatelessWidget {
63
child: Material(
64
color: isActive
65
? (isDark
65
- ? const Color(0xFF3B82F6).withValues(alpha: 0.2)
66
- : const Color(0xFF2563EB).withValues(alpha: 0.1))
66
+ ? AppColors.darkAccent.withValues(alpha: 0.2)
67
+ : AppColors.lightAccent.withValues(alpha: 0.1))
68
: Colors.transparent,
69
child: InkWell(
70
onTap: onTap,
@@ -77,13 +78,13 @@ class ConversationTile extends StatelessWidget {
78
? Icons.push_pin
79
: Icons.chat_bubble_outline,
80
size: 20,
80
- color: isActive
81
- ? (isDark
82
- ? const Color(0xFF3B82F6)
83
- : const Color(0xFF2563EB))
84
- : (isDark
85
- ? const Color(0xFF888888)
86
- : const Color(0xFF666666)),
81
+ color: isActive
82
+ ? (isDark
83
+ ? AppColors.darkAccent
84
+ : AppColors.lightAccent)
85
+ : (isDark
86
+ ? AppColors.darkMutedText
87
+ : AppColors.lightMutedText),
88
),
89
const SizedBox(width: 12),
90
Expanded(
@@ -97,7 +98,7 @@ class ConversationTile extends StatelessWidget {
98
fontWeight: isActive
99
? FontWeight.w600
100
: FontWeight.w500,
100
- color: isDark ? Colors.white : Colors.black,
101
+ color: isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText,
102
),
103
maxLines: 1,
104
overflow: TextOverflow.ellipsis,
@@ -109,8 +110,8 @@ class ConversationTile extends StatelessWidget {
110
style: TextStyle(
111
fontSize: 12,
112
color: isDark
112
- ? const Color(0xFF888888)
113
- : const Color(0xFF666666),
113
+ ? AppColors.darkMutedText
114
+ : AppColors.lightMutedText,
115
),
116
maxLines: 1,
117
overflow: TextOverflow.ellipsis,
@@ -125,8 +126,8 @@ class ConversationTile extends StatelessWidget {
126
style: TextStyle(
127
fontSize: 12,
128
color: isDark
128
- ? const Color(0xFF666666)
129
- : const Color(0xFF999999),
129
+ ? AppColors.darkMutedText
130
+ : AppColors.lightMutedText,
131
),
132
),
133
const SizedBox(width: 8),
@@ -136,8 +137,8 @@ class ConversationTile extends StatelessWidget {
137
icon: HugeIcons.strokeRoundedMoreVertical,
138
size: 18,
139
color: isDark
139
- ? const Color(0xFF888888)
140
- : const Color(0xFF666666),
140
+ ? AppColors.darkMutedText
141
+ : AppColors.lightMutedText,
142
),
143
padding: EdgeInsets.zero,
144
constraints: const BoxConstraints(),
lib/features/mcp/views/mcp_tools_screen.dart
+17
-15
@@ -1,3 +1,4 @@
1
+import "package:localmind/core/theme/colors.dart";
2
import 'package:flutter/material.dart';
3
import 'package:flutter_riverpod/flutter_riverpod.dart';
4
import 'package:hugeicons/hugeicons.dart';
@@ -50,12 +51,17 @@ class _McpToolsScreenState extends ConsumerState<McpToolsScreen> {
51
final isDark = theme.brightness == Brightness.dark;
52
final manager = ref.watch(mcpServerManagerProvider);
53
final hasExampleServer = manager.hasExampleServer();
54
+ final topPadding = MediaQuery.of(context).padding.top;
55
54
- return SafeArea(
55
- child: Column(
56
- children: [
57
- Container(
58
- padding: const EdgeInsets.only(left: 16, right: 16, bottom: 12),
56
+ return Column(
57
+ children: [
58
+ Container(
59
+ padding: EdgeInsets.only(
60
+ left: 16,
61
+ right: 16,
62
+ top: topPadding + 8,
63
+ bottom: 16,
64
+ ),
65
decoration: BoxDecoration(
66
color: isDark ? const Color(0xFF0A0A0A) : const Color(0xFFFAFAFA),
67
border: Border(
@@ -75,15 +81,12 @@ class _McpToolsScreenState extends ConsumerState<McpToolsScreen> {
81
),
82
),
83
const SizedBox(width: 8),
78
- const HugeIcon(
79
- icon: HugeIcons.strokeRoundedMcpServer,
80
- size: 22,
81
- ),
82
- const SizedBox(width: 10),
84
Text(
85
'MCP Tools',
85
- style: theme.textTheme.titleLarge?.copyWith(
86
+ style: TextStyle(
87
+ fontSize: 20,
88
fontWeight: FontWeight.bold,
89
+ color: isDark ? Colors.white : Colors.black,
90
),
91
),
92
],
@@ -148,8 +151,7 @@ class _McpToolsScreenState extends ConsumerState<McpToolsScreen> {
151
),
152
),
153
),
151
- ],
152
- ),
154
+ ],
155
);
156
}
157
}
@@ -171,7 +173,7 @@ class _ExampleServerPanel extends StatelessWidget {
173
color: isDark ? const Color(0xFF141414) : const Color(0xFFF8FAFC),
174
borderRadius: BorderRadius.circular(8),
175
border: Border.all(
174
- color: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFE2E8F0),
176
+ color: isDark ? AppColors.darkSurfaceCard : AppColors.lightSurface,
177
),
178
),
179
child: Column(
@@ -249,7 +251,7 @@ class _ToolRow extends StatelessWidget {
251
color: isDark ? const Color(0xFF111111) : Colors.white,
252
borderRadius: BorderRadius.circular(8),
253
border: Border.all(
252
- color: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFE5E7EB),
254
+ color: isDark ? AppColors.darkSurfaceCard : AppColors.lightSurface,
255
),
256
),
257
child: Row(
lib/features/models/screens/model_picker_sheet.dart
+46
-45
@@ -3,6 +3,7 @@ import 'dart:io';
3
import 'package:flutter/material.dart';
4
import 'package:flutter_riverpod/flutter_riverpod.dart';
5
import 'package:localmind/l10n/app_localizations.dart';
6
+import 'package:localmind/core/theme/colors.dart';
7
import '../../chat/providers/chat_providers.dart';
8
import '../data/models/model_info.dart';
9
import '../../servers/providers/server_providers.dart';
@@ -62,7 +63,7 @@ class ModelPickerSheet extends ConsumerWidget {
63
height: MediaQuery.of(context).size.height * 0.7,
64
padding: const EdgeInsets.all(16),
65
decoration: BoxDecoration(
65
- color: isDark ? const Color(0xFF1A1A1A) : Colors.white,
66
+ color: isDark ? AppColors.darkBackground : AppColors.lightSurface,
67
borderRadius: const BorderRadius.vertical(top: Radius.circular(20)),
68
),
69
child: Column(
@@ -114,16 +115,16 @@ class ModelPickerSheet extends ConsumerWidget {
115
style: TextStyle(
116
fontSize: 12,
117
color: isDark
117
- ? const Color(0xFF888888)
118
- : const Color(0xFF999999),
118
+ ? AppColors.darkMutedText
119
+ : AppColors.lightMutedText,
120
),
121
),
122
const SizedBox(height: 4),
123
LinearProgressIndicator(
124
value: modelLoading.progress,
125
backgroundColor: isDark
125
- ? const Color(0xFF333333)
126
- : const Color(0xFFE0E0E0),
126
+ ? AppColors.darkBorder
127
+ : AppColors.lightBorder,
128
),
129
],
130
),
@@ -150,8 +151,8 @@ class ModelPickerSheet extends ConsumerWidget {
151
style: TextStyle(
152
fontSize: 13,
153
color: isDark
153
- ? const Color(0xFF888888)
154
- : const Color(0xFF999999),
154
+ ? AppColors.darkMutedText
155
+ : AppColors.lightMutedText,
156
),
157
),
158
),
@@ -210,7 +211,7 @@ class _NoServerState extends StatelessWidget {
211
Icon(
212
Icons.computer_outlined,
213
size: 48,
213
- color: isDark ? const Color(0xFF444444) : const Color(0xFFCCCCCC),
214
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
215
),
216
const SizedBox(height: 16),
217
Text(
@@ -226,7 +227,7 @@ class _NoServerState extends StatelessWidget {
227
l10n.add_server_first,
228
style: TextStyle(
229
fontSize: 13,
229
- color: isDark ? const Color(0xFF888888) : const Color(0xFF999999),
230
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
231
),
232
),
233
],
@@ -286,8 +287,8 @@ class _ModelList extends ConsumerWidget {
287
style: TextStyle(
288
fontSize: 12,
289
color: isDark
289
- ? const Color(0xFF888888)
290
- : const Color(0xFF999999),
290
+ ? AppColors.darkMutedText
291
+ : AppColors.lightMutedText,
292
),
293
textAlign: TextAlign.center,
294
),
@@ -336,8 +337,8 @@ class _ModelList extends ConsumerWidget {
337
style: TextStyle(
338
fontSize: 14,
339
color: isDark
339
- ? const Color(0xFF888888)
340
- : const Color(0xFF999999),
340
+ ? AppColors.darkMutedText
341
+ : AppColors.lightMutedText,
342
),
343
),
344
);
@@ -456,7 +457,7 @@ class _ModelTile extends StatelessWidget {
457
@override
458
Widget build(BuildContext context) {
459
final l10n = AppLocalizations.of(context)!;
459
- final accent = isDark ? const Color(0xFF3B82F6) : const Color(0xFF2563EB);
460
+ final accent = isDark ? AppColors.darkAccent : AppColors.lightAccent;
461
462
return InkWell(
463
onTap: onTap,
@@ -538,8 +539,8 @@ class _ModelTile extends StatelessWidget {
539
Container(
540
width: 15,
541
height: 15,
541
- decoration: const BoxDecoration(
542
- color: Color(0xFF4CAF50),
542
+ decoration: BoxDecoration(
543
+ color: isDark ? AppColors.darkAccent : AppColors.lightAccent,
544
shape: BoxShape.circle,
545
),
546
),
@@ -566,8 +567,8 @@ class _ModelTile extends StatelessWidget {
567
Icons.chevron_right,
568
size: 20,
569
color: isDark
569
- ? const Color(0xFF555555)
570
- : const Color(0xFFCCCCCC),
570
+ ? AppColors.darkMutedText
571
+ : AppColors.lightMutedText,
572
),
573
],
574
),
@@ -618,13 +619,13 @@ class _ThinkingIndicatorState extends State<_ThinkingIndicator>
619
decoration: BoxDecoration(
620
color: widget.isDark
621
? Color.lerp(
621
- const Color(0xFF888888),
622
- const Color(0xFF4CAF50),
622
+ AppColors.darkMutedText,
623
+ AppColors.darkAccent,
624
_controller.value,
625
)
626
: Color.lerp(
626
- const Color(0xFF999999),
627
- const Color(0xFF4CAF50),
627
+ AppColors.lightMutedText,
628
+ AppColors.darkAccent,
629
_controller.value,
630
),
631
shape: BoxShape.circle,
@@ -638,8 +639,8 @@ class _ThinkingIndicatorState extends State<_ThinkingIndicator>
639
style: TextStyle(
640
fontSize: 12,
641
color: widget.isDark
641
- ? const Color(0xFF888888)
642
- : const Color(0xFF999999),
642
+ ? AppColors.darkMutedText
643
+ : AppColors.lightMutedText,
644
),
645
),
646
],
@@ -657,17 +658,17 @@ class _MetadataChip extends StatelessWidget {
658
return Container(
659
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
660
decoration: BoxDecoration(
660
- color: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFF0F0F0),
661
+ color: isDark ? AppColors.darkSurfaceCard : AppColors.lightSurface,
662
borderRadius: BorderRadius.circular(4),
663
border: Border.all(
663
- color: isDark ? const Color(0xFF3A3A3A) : const Color(0xFFE0E0E0),
664
+ color: isDark ? AppColors.darkBorder : AppColors.lightBorder,
665
),
666
),
667
child: Text(
668
label,
669
style: TextStyle(
670
fontSize: 11,
670
- color: isDark ? const Color(0xFFAAAAAA) : const Color(0xFF777777),
671
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
672
),
673
),
674
);
@@ -737,8 +738,8 @@ class _OnDeviceModelList extends ConsumerWidget {
738
style: TextStyle(
739
fontSize: 14,
740
color: isDark
740
- ? const Color(0xFF888888)
741
- : const Color(0xFF999999),
741
+ ? AppColors.darkMutedText
742
+ : AppColors.lightMutedText,
743
),
744
),
745
);
@@ -793,7 +794,7 @@ class _OnDeviceModelTile extends ConsumerWidget {
794
@override
795
Widget build(BuildContext context, WidgetRef ref) {
796
final l10n = AppLocalizations.of(context)!;
796
- final accent = isDark ? const Color(0xFF3B82F6) : const Color(0xFF2563EB);
797
+ final accent = isDark ? AppColors.darkAccent : AppColors.lightAccent;
798
final downloadProgress = ref.watch(
799
foregroundDownloadNotifierProvider,
800
)[model.id];
@@ -843,8 +844,8 @@ class _OnDeviceModelTile extends ConsumerWidget {
844
color: isDownloaded
845
? (isDark ? Colors.white : Colors.black)
846
: (isDark
846
- ? const Color(0xFF555555)
847
- : const Color(0xFF999999)),
847
+ ? AppColors.darkMutedText
848
+ : AppColors.lightMutedText),
849
),
850
maxLines: 1,
851
overflow: TextOverflow.ellipsis,
@@ -904,8 +905,8 @@ class _OnDeviceModelTile extends ConsumerWidget {
905
LinearProgressIndicator(
906
value: downloadProgress?.progress ?? 0.0,
907
backgroundColor: isDark
907
- ? const Color(0xFF333333)
908
- : const Color(0xFFE0E0E0),
908
+ ? AppColors.darkBorder
909
+ : AppColors.lightBorder,
910
),
911
const SizedBox(height: 2),
912
Text(
@@ -918,8 +919,8 @@ class _OnDeviceModelTile extends ConsumerWidget {
919
style: TextStyle(
920
fontSize: 10,
921
color: isDark
921
- ? const Color(0xFF888888)
922
- : const Color(0xFF999999),
922
+ ? AppColors.darkMutedText
923
+ : AppColors.lightMutedText,
924
),
925
),
926
],
@@ -956,7 +957,7 @@ class _OnDeviceModelTile extends ConsumerWidget {
957
width: 10,
958
height: 10,
959
decoration: const BoxDecoration(
959
- color: Color(0xFF4CAF50),
960
+ color: AppColors.darkAccent,
961
shape: BoxShape.circle,
962
),
963
),
@@ -982,8 +983,8 @@ class _OnDeviceModelTile extends ConsumerWidget {
983
Icons.delete_outline,
984
size: 18,
985
color: isDark
985
- ? const Color(0xFF666666)
986
- : const Color(0xFF999999),
986
+ ? AppColors.darkMutedText
987
+ : AppColors.lightMutedText,
988
),
989
tooltip: l10n.delete,
990
onPressed: () => _deleteModel(context, ref),
@@ -994,8 +995,8 @@ class _OnDeviceModelTile extends ConsumerWidget {
995
Icons.close,
996
size: 16,
997
color: isDark
997
- ? const Color(0xFF666666)
998
- : const Color(0xFF999999),
998
+ ? AppColors.darkMutedText
999
+ : AppColors.lightMutedText,
1000
),
1001
tooltip: l10n.cancel,
1002
onPressed: () => ref
@@ -1008,8 +1009,8 @@ class _OnDeviceModelTile extends ConsumerWidget {
1009
Icons.cloud_download_outlined,
1010
size: 18,
1011
color: isDark
1011
- ? const Color(0xFF555555)
1012
- : const Color(0xFF999999),
1012
+ ? AppColors.darkMutedText
1013
+ : AppColors.lightMutedText,
1014
),
1015
tooltip: l10n.download,
1016
onPressed: () {
@@ -1024,8 +1025,8 @@ class _OnDeviceModelTile extends ConsumerWidget {
1025
style: TextStyle(
1026
fontSize: 11,
1027
color: isDark
1027
- ? const Color(0xFF666666)
1028
- : const Color(0xFF999999),
1028
+ ? AppColors.darkMutedText
1029
+ : AppColors.lightMutedText,
1030
),
1031
),
1032
],
lib/features/on_device/views/model_manager_screen.dart
+110
-66
@@ -3,7 +3,6 @@ import 'dart:io';
3
import 'package:flutter/material.dart';
4
import 'package:flutter_riverpod/flutter_riverpod.dart';
5
import 'package:localmind/l10n/app_localizations.dart';
6
-import '../../sidebar/sidebar_widget.dart';
6
import 'package:shadcn_ui/shadcn_ui.dart';
7
8
import '../../../core/models/enums.dart';
@@ -45,77 +44,122 @@ class OnDeviceModelManagerScreen extends ConsumerWidget {
44
}
45
});
46
48
- return Scaffold(
49
- drawer: const SidebarWidget(),
50
- appBar: AppBar(title: Text(l10n.on_device_models_title)),
51
- body: ListView(
52
- padding: const EdgeInsets.all(16),
53
- children: [
54
- _buildMemoryInfo(context, ref, deviceMemoryAsync),
55
- if (engineState.status == OnDeviceEngineStatus.loaded)
56
- Container(
57
- padding: const EdgeInsets.all(12),
58
- margin: const EdgeInsets.only(bottom: 16),
59
- decoration: BoxDecoration(
60
- color: Colors.green.withValues(alpha: 0.1),
61
- borderRadius: BorderRadius.circular(8),
62
- border: Border.all(color: Colors.green),
63
- ),
64
- child: Row(
65
- children: [
66
- const Icon(Icons.check_circle, color: Colors.green, size: 20),
67
- const SizedBox(width: 8),
68
- Expanded(
69
- child: Text(
70
- l10n.model_loaded(
71
- engineState.loadedModelId ?? 'Unknown',
72
- engineState.backend?.name ?? 'CPU',
73
- ),
74
- style: const TextStyle(color: Colors.green),
75
- ),
76
- ),
77
- ],
78
- ),
79
- )
80
- else if (engineState.status == OnDeviceEngineStatus.loading)
81
- const Padding(
82
- padding: EdgeInsets.only(bottom: 16),
83
- child: LinearProgressIndicator(),
84
- )
85
- else if (engineState.status == OnDeviceEngineStatus.error)
86
- Container(
87
- padding: const EdgeInsets.all(12),
88
- margin: const EdgeInsets.only(bottom: 16),
89
- decoration: BoxDecoration(
90
- color: Colors.red.withValues(alpha: 0.1),
91
- borderRadius: BorderRadius.circular(8),
92
- border: Border.all(color: Colors.red),
93
- ),
94
- child: Text(
95
- engineState.error ?? l10n.unknown_error,
96
- style: const TextStyle(color: Colors.red),
47
+ final topPadding = MediaQuery.of(context).padding.top;
48
+
49
+ return Column(
50
+ children: [
51
+ Container(
52
+ padding: EdgeInsets.only(
53
+ left: 16,
54
+ right: 16,
55
+ top: topPadding + 8,
56
+ bottom: 16,
57
+ ),
58
+ decoration: BoxDecoration(
59
+ color: theme.brightness == Brightness.dark
60
+ ? const Color(0xFF0A0A0A)
61
+ : const Color(0xFFFAFAFA),
62
+ border: Border(
63
+ bottom: BorderSide(
64
+ color: theme.brightness == Brightness.dark
65
+ ? const Color(0xFF2A2A2A)
66
+ : const Color(0xFFE5E5E5),
67
+ ),
68
),
69
),
99
- Text(
100
- l10n.available_models,
101
- style: theme.textTheme.titleMedium?.copyWith(
102
- fontWeight: FontWeight.bold,
70
+ child: Row(
71
+ children: [
72
+ Builder(
73
+ builder: (context) => IconButton(
74
+ icon: const Icon(Icons.menu),
75
+ onPressed: () => Scaffold.of(context).openDrawer(),
76
+ ),
77
+ ),
78
+ const SizedBox(width: 8),
79
+ Text(
80
+ l10n.on_device_models_title,
81
+ style: TextStyle(
82
+ fontSize: 20,
83
+ fontWeight: FontWeight.bold,
84
+ color: theme.brightness == Brightness.dark
85
+ ? Colors.white
86
+ : Colors.black,
87
+ ),
88
+ ),
89
+ ],
90
),
91
),
105
- const SizedBox(height: 12),
106
- ...models.map(
107
- (model) => _ModelCard(
108
- model: model,
109
- theme: theme,
110
- downloadedAsync: downloadedAsync,
111
- downloadProgress: downloadProgress[model.id],
112
- engineState: engineState,
113
- deviceMemory: deviceMemoryAsync.value,
92
+ Expanded(
93
+ child: ListView(
94
+ padding: const EdgeInsets.all(16),
95
+ children: [
96
+ _buildMemoryInfo(context, ref, deviceMemoryAsync),
97
+ if (engineState.status == OnDeviceEngineStatus.loaded)
98
+ Container(
99
+ padding: const EdgeInsets.all(12),
100
+ margin: const EdgeInsets.only(bottom: 16),
101
+ decoration: BoxDecoration(
102
+ color: Colors.green.withValues(alpha: 0.1),
103
+ borderRadius: BorderRadius.circular(8),
104
+ border: Border.all(color: Colors.green),
105
+ ),
106
+ child: Row(
107
+ children: [
108
+ const Icon(Icons.check_circle, color: Colors.green, size: 20),
109
+ const SizedBox(width: 8),
110
+ Expanded(
111
+ child: Text(
112
+ l10n.model_loaded(
113
+ engineState.loadedModelId ?? 'Unknown',
114
+ engineState.backend?.name ?? 'CPU',
115
+ ),
116
+ style: const TextStyle(color: Colors.green),
117
+ ),
118
+ ),
119
+ ],
120
+ ),
121
+ )
122
+ else if (engineState.status == OnDeviceEngineStatus.loading)
123
+ const Padding(
124
+ padding: EdgeInsets.only(bottom: 16),
125
+ child: LinearProgressIndicator(),
126
+ )
127
+ else if (engineState.status == OnDeviceEngineStatus.error)
128
+ Container(
129
+ padding: const EdgeInsets.all(12),
130
+ margin: const EdgeInsets.only(bottom: 16),
131
+ decoration: BoxDecoration(
132
+ color: Colors.red.withValues(alpha: 0.1),
133
+ borderRadius: BorderRadius.circular(8),
134
+ border: Border.all(color: Colors.red),
135
+ ),
136
+ child: Text(
137
+ engineState.error ?? l10n.unknown_error,
138
+ style: const TextStyle(color: Colors.red),
139
+ ),
140
+ ),
141
+ Text(
142
+ l10n.available_models,
143
+ style: theme.textTheme.titleMedium?.copyWith(
144
+ fontWeight: FontWeight.bold,
145
+ ),
146
+ ),
147
+ const SizedBox(height: 12),
148
+ ...models.map(
149
+ (model) => _ModelCard(
150
+ model: model,
151
+ theme: theme,
152
+ downloadedAsync: downloadedAsync,
153
+ downloadProgress: downloadProgress[model.id],
154
+ engineState: engineState,
155
+ deviceMemory: deviceMemoryAsync.value,
156
+ ),
157
+ ),
158
+ const SizedBox(height: 24),
159
+ ],
160
),
161
),
116
- const SizedBox(height: 24),
117
- ],
118
- ),
162
+ ],
163
);
164
}
165
lib/features/onboarding/screens/onboarding_language_screen.dart
+1
-1
@@ -50,7 +50,7 @@ const _languages = [
50
flagAsset: 'assets/images/flag_it.png',
51
countryCode: 'IT',
52
shortText: 'It',
53
- gradient: [Color(0xFF3B82F6), Color(0xFF1D4ED8)],
53
+ gradient: [Color(0xFF71717A), Color(0xFF52525B)],
54
),
55
_LanguageOption(
56
code: 'es',
lib/features/personas/views/persona_list_screen.dart
+17
-12
@@ -1,3 +1,4 @@
1
+import "package:localmind/core/theme/colors.dart";
2
import 'package:flutter/material.dart';
3
import 'package:flutter_riverpod/flutter_riverpod.dart';
4
import 'package:go_router/go_router.dart';
@@ -18,14 +19,19 @@ class PersonaListScreen extends ConsumerWidget {
19
final selectedCategory = ref.watch(personaCategoryFilterProvider);
20
final theme = Theme.of(context);
21
final isDark = theme.brightness == Brightness.dark;
22
+ final topPadding = MediaQuery.of(context).padding.top;
23
22
- return SafeArea(
23
- child: Stack(
24
- children: [
25
- Column(
26
- children: [
27
- Container(
28
- padding: const EdgeInsets.only(left: 16, right: 16, bottom: 12),
24
+ return Stack(
25
+ children: [
26
+ Column(
27
+ children: [
28
+ Container(
29
+ padding: EdgeInsets.only(
30
+ left: 16,
31
+ right: 16,
32
+ top: topPadding + 8,
33
+ bottom: 16,
34
+ ),
35
decoration: BoxDecoration(
36
color: isDark
37
? const Color(0xFF0A0A0A)
@@ -186,8 +192,7 @@ class PersonaListScreen extends ConsumerWidget {
192
child: const Icon(Icons.add),
193
),
194
),
189
- ],
190
- ),
195
+ ],
196
);
197
}
198
@@ -291,7 +296,7 @@ class _SectionLabel extends StatelessWidget {
296
fontSize: 11,
297
fontWeight: FontWeight.w600,
298
letterSpacing: 0.8,
294
- color: isDark ? const Color(0xFF888888) : const Color(0xFF999999),
299
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
300
),
301
),
302
);
@@ -322,10 +327,10 @@ class _PersonaCard extends StatelessWidget {
327
margin: const EdgeInsets.only(bottom: 8),
328
padding: const EdgeInsets.all(12),
329
decoration: BoxDecoration(
325
- color: isDark ? const Color(0xFF1F1F1F) : const Color(0xFFF5F5F5),
330
+ color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
331
borderRadius: BorderRadius.circular(10),
332
border: Border.all(
328
- color: isDark ? const Color(0xFF2A2A2A) : const Color(0xFFE5E5E5),
333
+ color: isDark ? AppColors.darkSurfaceCard : AppColors.lightBorder,
334
),
335
),
336
child: Row(
lib/features/servers/views/server_list_screen.dart
+130
-130
@@ -20,151 +20,151 @@ class ServerListScreen extends ConsumerWidget {
20
final theme = Theme.of(context);
21
final isDark = theme.brightness == Brightness.dark;
22
final systemBottomInset = bottomSystemInset(context);
23
+ final topPadding = MediaQuery.of(context).padding.top;
24
24
- return SafeArea(
25
- child: Stack(
26
- children: [
27
- Column(
28
- children: [
29
- Container(
30
- padding: const EdgeInsets.only(left: 16, right: 16, bottom: 12),
31
- decoration: BoxDecoration(
32
- color: isDark
33
- ? const Color(0xFF0A0A0A)
34
- : const Color(0xFFFAFAFA),
35
- border: Border(
36
- bottom: BorderSide(
37
- color: isDark
38
- ? const Color(0xFF2A2A2A)
39
- : const Color(0xFFE5E5E5),
40
- ),
25
+ return Stack(
26
+ children: [
27
+ Column(
28
+ children: [
29
+ Container(
30
+ padding: EdgeInsets.only(
31
+ left: 16,
32
+ right: 16,
33
+ top: topPadding + 8,
34
+ bottom: 16,
35
+ ),
36
+ decoration: BoxDecoration(
37
+ color: isDark
38
+ ? const Color(0xFF0A0A0A)
39
+ : const Color(0xFFFAFAFA),
40
+ border: Border(
41
+ bottom: BorderSide(
42
+ color: isDark
43
+ ? const Color(0xFF2A2A2A)
44
+ : const Color(0xFFE5E5E5),
45
),
46
),
43
- child: Row(
44
- children: [
45
- Builder(
46
- builder: (context) => IconButton(
47
- icon: const Icon(Icons.menu),
48
- onPressed: () => Scaffold.of(context).openDrawer(),
49
- ),
47
+ ),
48
+ child: Row(
49
+ children: [
50
+ Builder(
51
+ builder: (context) => IconButton(
52
+ icon: const Icon(Icons.menu),
53
+ onPressed: () => Scaffold.of(context).openDrawer(),
54
),
51
- const SizedBox(width: 8),
52
- Text(
53
- l10n.servers_title,
54
- style: TextStyle(
55
- fontSize: 20,
56
- fontWeight: FontWeight.bold,
57
- color: isDark ? Colors.white : Colors.black,
58
- ),
55
+ ),
56
+ const SizedBox(width: 8),
57
+ Text(
58
+ l10n.servers_title,
59
+ style: TextStyle(
60
+ fontSize: 20,
61
+ fontWeight: FontWeight.bold,
62
+ color: isDark ? Colors.white : Colors.black,
63
),
60
- ],
61
- ),
64
+ ),
65
+ ],
66
),
67
+ ),
68
64
- Expanded(
65
- child: servers.when(
66
- data: (serverList) => serverList.isEmpty
67
- ? _buildEmptyState(context, l10n, theme)
68
- : RefreshIndicator(
69
- onRefresh: () async {
70
- for (final server in serverList) {
71
- await ref
72
- .read(serversProvider.notifier)
73
- .testConnection(
74
- server.id,
75
- ref.read(serverApiServiceProvider),
69
+ Expanded(
70
+ child: servers.when(
71
+ data: (serverList) => serverList.isEmpty
72
+ ? _buildEmptyState(context, l10n, theme)
73
+ : RefreshIndicator(
74
+ onRefresh: () async {
75
+ for (final server in serverList) {
76
+ await ref
77
+ .read(serversProvider.notifier)
78
+ .testConnection(
79
+ server.id,
80
+ ref.read(serverApiServiceProvider),
81
+ );
82
+ }
83
+ },
84
+ child: ListView.builder(
85
+ padding: EdgeInsets.fromLTRB(
86
+ 16,
87
+ 16,
88
+ 16,
89
+ 16 + systemBottomInset + 80,
90
+ ),
91
+ itemCount: serverList.length,
92
+ itemBuilder: (context, index) {
93
+ final server = serverList[index];
94
+ final isOnDevice =
95
+ server.type == ServerType.onDevice;
96
+ return Padding(
97
+ padding: const EdgeInsets.only(bottom: 12),
98
+ child: ServerCard(
99
+ server: server,
100
+ isActive: activeServer?.id == server.id,
101
+ onTap: () {
102
+ ref
103
+ .read(activeServerProvider.notifier)
104
+ .setActiveServer(server);
105
+ ScaffoldMessenger.of(context).showSnackBar(
106
+ SnackBar(
107
+ content: Text(
108
+ l10n.switched_to_server(server.name),
109
+ ),
110
+ duration: const Duration(seconds: 2),
111
+ ),
112
);
77
- }
78
- },
79
- child: ListView.builder(
80
- padding: EdgeInsets.fromLTRB(
81
- 16,
82
- 16,
83
- 16,
84
- 16 + systemBottomInset + 80,
85
- ),
86
- itemCount: serverList.length,
87
- itemBuilder: (context, index) {
88
- final server = serverList[index];
89
- final isOnDevice =
90
- server.type == ServerType.onDevice;
91
- return Padding(
92
- padding: const EdgeInsets.only(bottom: 12),
93
- child: ServerCard(
94
- server: server,
95
- isActive: activeServer?.id == server.id,
96
- onTap: () {
97
- ref
98
- .read(activeServerProvider.notifier)
99
- .setActiveServer(server);
100
- ScaffoldMessenger.of(context).showSnackBar(
101
- SnackBar(
102
- content: Text(
103
- l10n.switched_to_server(server.name),
104
- ),
105
- duration: const Duration(seconds: 2),
113
+ },
114
+ onEdit: isOnDevice
115
+ ? null
116
+ : () =>
117
+ _showEditDialog(context, ref, server),
118
+ onDelete: isOnDevice
119
+ ? null
120
+ : () => _showDeleteConfirmation(
121
+ context,
122
+ ref,
123
+ l10n,
124
+ server,
125
),
107
- );
108
- },
109
- onEdit: isOnDevice
110
- ? null
111
- : () => _showEditDialog(
112
- context,
113
- ref,
114
- server,
115
- ),
116
- onDelete: isOnDevice
117
- ? null
118
- : () => _showDeleteConfirmation(
119
- context,
120
- ref,
121
- l10n,
122
- server,
123
- ),
124
- onSetDefault: () {
125
- ref
126
- .read(serversProvider.notifier)
127
- .setDefault(server.id);
128
- },
129
- ),
130
- );
131
- },
132
- ),
133
- ),
134
- loading: () =>
135
- const Center(child: CircularProgressIndicator()),
136
- error: (err, stack) => Center(
137
- child: Column(
138
- mainAxisAlignment: MainAxisAlignment.center,
139
- children: [
140
- const Icon(
141
- Icons.error_outline,
142
- color: Colors.red,
143
- size: 48,
144
- ),
145
- const SizedBox(height: 16),
146
- Text(l10n.error_with_message(err.toString())),
147
- TextButton(
148
- onPressed: () => ref.invalidate(serversProvider),
149
- child: Text(l10n.retry),
126
+ onSetDefault: () {
127
+ ref
128
+ .read(serversProvider.notifier)
129
+ .setDefault(server.id);
130
+ },
131
+ ),
132
+ );
133
+ },
134
),
151
- ],
152
- ),
135
+ ),
136
+ loading: () => const Center(child: CircularProgressIndicator()),
137
+ error: (err, stack) => Center(
138
+ child: Column(
139
+ mainAxisAlignment: MainAxisAlignment.center,
140
+ children: [
141
+ const Icon(
142
+ Icons.error_outline,
143
+ color: Colors.red,
144
+ size: 48,
145
+ ),
146
+ const SizedBox(height: 16),
147
+ Text(l10n.error_with_message(err.toString())),
148
+ TextButton(
149
+ onPressed: () => ref.invalidate(serversProvider),
150
+ child: Text(l10n.retry),
151
+ ),
152
+ ],
153
),
154
),
155
),
156
- ],
157
- ),
158
- PositionedDirectional(
159
- bottom: 24,
160
- end: 24,
161
- child: FloatingActionButton(
162
- onPressed: () => context.push(AppRoutes.addServer),
163
- child: const Icon(Icons.add),
156
),
157
+ ],
158
+ ),
159
+ PositionedDirectional(
160
+ bottom: 24,
161
+ end: 24,
162
+ child: FloatingActionButton(
163
+ onPressed: () => context.push(AppRoutes.addServer),
164
+ child: const Icon(Icons.add),
165
),
166
- ],
167
- ),
166
+ ),
167
+ ],
168
);
169
}
170
lib/features/settings/views/settings_screen.dart
+354
-303
@@ -3,7 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
3
import 'package:go_router/go_router.dart';
4
import 'package:shadcn_ui/shadcn_ui.dart';
5
6
-import 'package:localmind/features/sidebar/sidebar_widget.dart';
6
import 'package:localmind/l10n/app_localizations.dart';
7
8
import '../../../core/models/enums.dart';
@@ -34,324 +33,376 @@ class SettingsViews extends ConsumerWidget {
33
.map((persona) => (persona.id, '${persona.emoji} ${persona.name}'))
34
.toList();
35
37
- return Scaffold(
38
- appBar: AppBar(title: Text(l10n.settings_title)),
39
- drawer: SidebarWidget(),
40
- body: LayoutBuilder(
41
- builder: (context, constraints) {
42
- final useTwoColumns = constraints.maxWidth >= 1080;
43
- final contentMaxWidth = useTwoColumns ? 1120.0 : 720.0;
44
- final horizontalPadding = constraints.maxWidth >= 720 ? 20.0 : 12.0;
45
-
46
- final appearanceCard = _SettingsSectionCard(
47
- title: l10n.settings_appearance,
48
- icon: Icons.palette_outlined,
49
- accent: const Color(0xFF8B5CF6),
50
- children: [
51
- _ThemeToggle(ref: ref),
52
- _LanguageSetting(
53
- current: settings.localeCode,
54
- onChanged: (value) =>
55
- ref.read(settingsProvider.notifier).setLocaleCode(value),
56
- ),
57
- _SliderSetting(
58
- label: l10n.font_size,
59
- value: settings.fontSize,
60
- min: 12.0,
61
- max: 24.0,
62
- divisions: 12,
63
- description: l10n.font_size_desc,
64
- onChanged: (value) =>
65
- ref.read(settingsProvider.notifier).setFontSize(value),
66
- valueFormat: (value) => value.toStringAsFixed(0),
67
- previewText: l10n.font_preview,
68
- ),
69
- _CodeThemeDropdown(
70
- label: l10n.code_theme_dark,
71
- current: settings.codeThemeDark,
72
- onChanged: (value) =>
73
- ref.read(settingsProvider.notifier).setCodeThemeDark(value),
74
- ),
75
- _CodeThemeDropdown(
76
- label: l10n.code_theme_light,
77
- current: settings.codeThemeLight,
78
- onChanged: (value) => ref
79
- .read(settingsProvider.notifier)
80
- .setCodeThemeLight(value),
81
- ),
82
- ],
83
- );
36
+ final isDark = Theme.of(context).brightness == Brightness.dark;
37
+ final topPadding = MediaQuery.of(context).padding.top;
38
85
- final ttsCard = _SettingsSectionCard(
86
- title: l10n.settings_tts,
87
- icon: Icons.graphic_eq_rounded,
88
- accent: const Color(0xFF0EA5E9),
89
- children: [
90
- _EngineDropdown(
91
- current: settings.ttsEngine,
92
- onChanged: (value) =>
93
- ref.read(settingsProvider.notifier).setTtsEngine(value),
94
- ),
95
- _SectionActionButton(
96
- icon: Icons.record_voice_over_outlined,
97
- label: l10n.manage_tts_models,
98
- onPressed: () => context.push(AppRoutes.ttsModels),
39
+ return Column(
40
+ children: [
41
+ Container(
42
+ padding: EdgeInsets.only(
43
+ left: 16,
44
+ right: 16,
45
+ top: topPadding + 8,
46
+ bottom: 16,
47
+ ),
48
+ decoration: BoxDecoration(
49
+ color: isDark ? const Color(0xFF0A0A0A) : const Color(0xFFFAFAFA),
50
+ border: Border(
51
+ bottom: BorderSide(
52
+ color: isDark
53
+ ? const Color(0xFF2A2A2A)
54
+ : const Color(0xFFE5E5E5),
55
),
100
- if (settings.ttsEngine != EngineId.system) ...[
101
- _VoiceSelector(
102
- engine: settings.ttsEngine,
103
- currentVoiceId: settings.ttsVoiceId,
104
- onChanged: (voice) => ref
105
- .read(settingsProvider.notifier)
106
- .setTtsVoiceId(voice?.id),
107
- ),
108
- _SliderSetting(
109
- label: l10n.tts_speed,
110
- value: settings.ttsSpeed,
111
- min: 0.5,
112
- max: 2.0,
113
- divisions: 15,
114
- description: l10n.tts_speed_desc,
115
- onChanged: (value) =>
116
- ref.read(settingsProvider.notifier).setTtsSpeed(value),
117
- valueFormat: (value) => '${value.toStringAsFixed(2)}x',
118
- ),
119
- ],
120
- ],
121
- );
122
-
123
- final behaviorCard = _SettingsSectionCard(
124
- title: l10n.settings_behavior,
125
- icon: Icons.tune_rounded,
126
- accent: const Color(0xFF22C55E),
56
+ ),
57
+ ),
58
+ child: Row(
59
children: [
128
- _ToggleSetting(
129
- label: l10n.streaming_responses,
130
- value: settings.streamingEnabled,
131
- onChanged: (value) => ref
132
- .read(settingsProvider.notifier)
133
- .setStreamingEnabled(value),
134
- ),
135
- _ToggleSetting(
136
- label: l10n.auto_generate_titles,
137
- value: settings.autoGenerateTitle,
138
- onChanged: (value) => ref
139
- .read(settingsProvider.notifier)
140
- .setAutoGenerateTitle(value),
141
- ),
142
- _ToggleSetting(
143
- label: l10n.send_on_enter,
144
- value: settings.sendOnEnter,
145
- onChanged: (value) =>
146
- ref.read(settingsProvider.notifier).setSendOnEnter(value),
147
- ),
148
- _ToggleSetting(
149
- label: l10n.show_system_messages,
150
- value: settings.showSystemMessages,
151
- onChanged: (value) => ref
152
- .read(settingsProvider.notifier)
153
- .setShowSystemMessages(value),
154
- ),
155
- _ToggleSetting(
156
- label: l10n.haptic_feedback,
157
- value: settings.hapticFeedbackEnabled,
158
- onChanged: (value) => ref
159
- .read(settingsProvider.notifier)
160
- .setHapticFeedback(value),
161
- ),
162
- _ToggleSetting(
163
- label: l10n.enable_mcp,
164
- value: settings.mcpEnabled,
165
- badges: [
166
- _FeatureBadge(label: l10n.experimental_label),
167
- ],
168
- onChanged: (value) =>
169
- ref.read(settingsProvider.notifier).setMcpEnabled(value),
170
- ),
171
- if (settings.mcpEnabled)
172
- _ToggleSetting(
173
- label: l10n.new_chat_mcp_default,
174
- value: settings.newChatMcpEnabled,
175
- onChanged: (value) => ref
176
- .read(settingsProvider.notifier)
177
- .setNewChatMcpEnabled(value),
60
+ Builder(
61
+ builder: (context) => IconButton(
62
+ icon: const Icon(Icons.menu),
63
+ onPressed: () => Scaffold.of(context).openDrawer(),
64
),
179
- ],
180
- );
181
-
182
- final onDeviceCard = _SettingsSectionCard(
183
- title: l10n.settings_on_device,
184
- icon: Icons.memory_rounded,
185
- accent: const Color(0xFFF97316),
186
- children: [
187
- _ToggleSetting(
188
- label: l10n.enable_smart_reply,
189
- value: settings.smartReplyEnabled,
190
- onChanged: (value) => ref
191
- .read(settingsProvider.notifier)
192
- .setSmartReplyEnabled(value),
65
),
194
- _SectionActionButton(
195
- icon: Icons.phone_android_rounded,
196
- label: l10n.manage_on_device_models,
197
- onPressed: () => context.push(AppRoutes.onDeviceModels),
66
+ const SizedBox(width: 8),
67
+ Text(
68
+ l10n.settings_title,
69
+ style: TextStyle(
70
+ fontSize: 20,
71
+ fontWeight: FontWeight.bold,
72
+ color: isDark ? Colors.white : Colors.black,
73
+ ),
74
),
199
- const _OnDeviceEngineStatusCard(),
75
],
201
- );
76
+ ),
77
+ ),
78
+ Expanded(
79
+ child: LayoutBuilder(
80
+ builder: (context, constraints) {
81
+ final useTwoColumns = constraints.maxWidth >= 1080;
82
+ final contentMaxWidth = useTwoColumns ? 1120.0 : 720.0;
83
+ final horizontalPadding = constraints.maxWidth >= 720
84
+ ? 20.0
85
+ : 12.0;
86
+
87
+ final appearanceCard = _SettingsSectionCard(
88
+ title: l10n.settings_appearance,
89
+ icon: Icons.palette_outlined,
90
+ accent: const Color(0xFF8B5CF6),
91
+ children: [
92
+ _ThemeToggle(ref: ref),
93
+ _LanguageSetting(
94
+ current: settings.localeCode,
95
+ onChanged: (value) => ref
96
+ .read(settingsProvider.notifier)
97
+ .setLocaleCode(value),
98
+ ),
99
+ _SliderSetting(
100
+ label: l10n.font_size,
101
+ value: settings.fontSize,
102
+ min: 12.0,
103
+ max: 24.0,
104
+ divisions: 12,
105
+ description: l10n.font_size_desc,
106
+ onChanged: (value) =>
107
+ ref.read(settingsProvider.notifier).setFontSize(value),
108
+ valueFormat: (value) => value.toStringAsFixed(0),
109
+ previewText: l10n.font_preview,
110
+ ),
111
+ _CodeThemeDropdown(
112
+ label: l10n.code_theme_dark,
113
+ current: settings.codeThemeDark,
114
+ onChanged: (value) => ref
115
+ .read(settingsProvider.notifier)
116
+ .setCodeThemeDark(value),
117
+ ),
118
+ _CodeThemeDropdown(
119
+ label: l10n.code_theme_light,
120
+ current: settings.codeThemeLight,
121
+ onChanged: (value) => ref
122
+ .read(settingsProvider.notifier)
123
+ .setCodeThemeLight(value),
124
+ ),
125
+ ],
126
+ );
127
203
- final defaultsCard = _SettingsSectionCard(
204
- title: l10n.settings_default_server,
205
- icon: Icons.hub_outlined,
206
- accent: const Color(0xFF06B6D4),
207
- children: [
208
- _DropdownSetting(
209
- label: l10n.settings_default_server,
210
- currentValue: settings.defaultServerId,
211
- items: servers,
212
- onChanged: (value) =>
213
- ref.read(settingsProvider.notifier).setDefaultServer(value),
214
- icon: Icons.computer_rounded,
215
- ),
216
- _DropdownSetting(
217
- label: l10n.settings_default_persona,
218
- currentValue: settings.defaultPersonaId,
219
- items: personas,
220
- onChanged: (value) => ref
221
- .read(settingsProvider.notifier)
222
- .setDefaultPersona(value),
223
- icon: Icons.smart_toy_outlined,
224
- ),
225
- ],
226
- );
128
+ final ttsCard = _SettingsSectionCard(
129
+ title: l10n.settings_tts,
130
+ icon: Icons.graphic_eq_rounded,
131
+ accent: const Color(0xFF0EA5E9),
132
+ children: [
133
+ _EngineDropdown(
134
+ current: settings.ttsEngine,
135
+ onChanged: (value) =>
136
+ ref.read(settingsProvider.notifier).setTtsEngine(value),
137
+ ),
138
+ _SectionActionButton(
139
+ icon: Icons.record_voice_over_outlined,
140
+ label: l10n.manage_tts_models,
141
+ onPressed: () => context.push(AppRoutes.ttsModels),
142
+ ),
143
+ if (settings.ttsEngine != EngineId.system) ...[
144
+ _VoiceSelector(
145
+ engine: settings.ttsEngine,
146
+ currentVoiceId: settings.ttsVoiceId,
147
+ onChanged: (voice) => ref
148
+ .read(settingsProvider.notifier)
149
+ .setTtsVoiceId(voice?.id),
150
+ ),
151
+ _SliderSetting(
152
+ label: l10n.tts_speed,
153
+ value: settings.ttsSpeed,
154
+ min: 0.5,
155
+ max: 2.0,
156
+ divisions: 15,
157
+ description: l10n.tts_speed_desc,
158
+ onChanged: (value) => ref
159
+ .read(settingsProvider.notifier)
160
+ .setTtsSpeed(value),
161
+ valueFormat: (value) => '${value.toStringAsFixed(2)}x',
162
+ ),
163
+ ],
164
+ ],
165
+ );
166
228
- final privacyCard = _SettingsSectionCard(
229
- title: l10n.settings_privacy,
230
- icon: Icons.lock_outline_rounded,
231
- accent: const Color(0xFF14B8A6),
232
- children: [
233
- _ToggleSetting(
234
- label: l10n.show_data_indicator,
235
- value: settings.showDataIndicator,
236
- onChanged: (value) => ref
237
- .read(settingsProvider.notifier)
238
- .setShowDataIndicator(value),
239
- ),
240
- _MutedCallout(message: l10n.privacy_info),
241
- ],
242
- );
167
+ final behaviorCard = _SettingsSectionCard(
168
+ title: l10n.settings_behavior,
169
+ icon: Icons.tune_rounded,
170
+ accent: const Color(0xFF22C55E),
171
+ children: [
172
+ _ToggleSetting(
173
+ label: l10n.streaming_responses,
174
+ value: settings.streamingEnabled,
175
+ onChanged: (value) => ref
176
+ .read(settingsProvider.notifier)
177
+ .setStreamingEnabled(value),
178
+ ),
179
+ _ToggleSetting(
180
+ label: l10n.auto_generate_titles,
181
+ value: settings.autoGenerateTitle,
182
+ onChanged: (value) => ref
183
+ .read(settingsProvider.notifier)
184
+ .setAutoGenerateTitle(value),
185
+ ),
186
+ _ToggleSetting(
187
+ label: l10n.send_on_enter,
188
+ value: settings.sendOnEnter,
189
+ onChanged: (value) => ref
190
+ .read(settingsProvider.notifier)
191
+ .setSendOnEnter(value),
192
+ ),
193
+ _ToggleSetting(
194
+ label: l10n.show_system_messages,
195
+ value: settings.showSystemMessages,
196
+ onChanged: (value) => ref
197
+ .read(settingsProvider.notifier)
198
+ .setShowSystemMessages(value),
199
+ ),
200
+ _ToggleSetting(
201
+ label: l10n.haptic_feedback,
202
+ value: settings.hapticFeedbackEnabled,
203
+ onChanged: (value) => ref
204
+ .read(settingsProvider.notifier)
205
+ .setHapticFeedback(value),
206
+ ),
207
+ _ToggleSetting(
208
+ label: l10n.enable_mcp,
209
+ value: settings.mcpEnabled,
210
+ badges: [_FeatureBadge(label: l10n.experimental_label)],
211
+ onChanged: (value) => ref
212
+ .read(settingsProvider.notifier)
213
+ .setMcpEnabled(value),
214
+ ),
215
+ if (settings.mcpEnabled)
216
+ _ToggleSetting(
217
+ label: l10n.new_chat_mcp_default,
218
+ value: settings.newChatMcpEnabled,
219
+ onChanged: (value) => ref
220
+ .read(settingsProvider.notifier)
221
+ .setNewChatMcpEnabled(value),
222
+ ),
223
+ ],
224
+ );
225
244
- final dataCard = _SettingsSectionCard(
245
- title: l10n.settings_data_management,
246
- icon: Icons.restore_page_outlined,
247
- accent: const Color(0xFFEF4444),
248
- children: [
249
- _DangerousAction(
250
- label: l10n.delete_all_conversations,
251
- icon: Icons.delete_outline_rounded,
252
- onConfirm: () =>
253
- ref.read(conversationsProvider.notifier).deleteAll(),
254
- ),
255
- _DangerousAction(
256
- label: l10n.reset_settings_defaults,
257
- icon: Icons.restore_rounded,
258
- onConfirm: () =>
259
- ref.read(settingsProvider.notifier).resetToDefaults(),
260
- ),
261
- ],
262
- );
226
+ final onDeviceCard = _SettingsSectionCard(
227
+ title: l10n.settings_on_device,
228
+ icon: Icons.memory_rounded,
229
+ accent: const Color(0xFFF97316),
230
+ children: [
231
+ _ToggleSetting(
232
+ label: l10n.enable_smart_reply,
233
+ value: settings.smartReplyEnabled,
234
+ onChanged: (value) => ref
235
+ .read(settingsProvider.notifier)
236
+ .setSmartReplyEnabled(value),
237
+ ),
238
+ _SectionActionButton(
239
+ icon: Icons.phone_android_rounded,
240
+ label: l10n.manage_on_device_models,
241
+ onPressed: () => context.push(AppRoutes.onDeviceModels),
242
+ ),
243
+ const _OnDeviceEngineStatusCard(),
244
+ ],
245
+ );
246
264
- final aboutCard = _SettingsSectionCard(
265
- title: l10n.settings_about,
266
- icon: Icons.info_outline_rounded,
267
- accent: const Color(0xFFF59E0B),
268
- children: [
269
- _AboutPanel(
270
- title: '${l10n.app_name} v${packageInfo.value?.version ?? l10n.app_version} (${packageInfo.value?.buildNumber ?? ''})',
271
- subtitle: l10n.onboarding_connect_desc.replaceAll('\n', ' '),
272
- providers: [
273
- l10n.server_type_lm_studio,
274
- l10n.server_type_ollama,
275
- l10n.server_type_openrouter,
247
+ final defaultsCard = _SettingsSectionCard(
248
+ title: l10n.settings_default_server,
249
+ icon: Icons.hub_outlined,
250
+ accent: const Color(0xFF06B6D4),
251
+ children: [
252
+ _DropdownSetting(
253
+ label: l10n.settings_default_server,
254
+ currentValue: settings.defaultServerId,
255
+ items: servers,
256
+ onChanged: (value) => ref
257
+ .read(settingsProvider.notifier)
258
+ .setDefaultServer(value),
259
+ icon: Icons.computer_rounded,
260
+ ),
261
+ _DropdownSetting(
262
+ label: l10n.settings_default_persona,
263
+ currentValue: settings.defaultPersonaId,
264
+ items: personas,
265
+ onChanged: (value) => ref
266
+ .read(settingsProvider.notifier)
267
+ .setDefaultPersona(value),
268
+ icon: Icons.smart_toy_outlined,
269
+ ),
270
],
277
- highlights: [
278
- l10n.settings_on_device,
279
- l10n.streaming_responses,
280
- l10n.nav_personas,
271
+ );
272
+
273
+ final privacyCard = _SettingsSectionCard(
274
+ title: l10n.settings_privacy,
275
+ icon: Icons.lock_outline_rounded,
276
+ accent: const Color(0xFF14B8A6),
277
+ children: [
278
+ _ToggleSetting(
279
+ label: l10n.show_data_indicator,
280
+ value: settings.showDataIndicator,
281
+ onChanged: (value) => ref
282
+ .read(settingsProvider.notifier)
283
+ .setShowDataIndicator(value),
284
+ ),
285
+ _MutedCallout(message: l10n.privacy_info),
286
],
282
- stack: const ['Flutter', 'Riverpod', 'shadcn_ui'],
283
- openSource: l10n.open_source_desc,
284
- ),
285
- ],
286
- );
287
-
288
- return ListView(
289
- padding: EdgeInsets.fromLTRB(
290
- horizontalPadding,
291
- 12,
292
- horizontalPadding,
293
- 24 + systemBottomInset,
294
- ),
295
- children: [
296
- Center(
297
- child: ConstrainedBox(
298
- constraints: BoxConstraints(maxWidth: contentMaxWidth),
299
- child: Column(
300
- crossAxisAlignment: CrossAxisAlignment.stretch,
301
- children: [
302
- _SettingsHero(
303
- settings: settings,
304
- themeType: currentTheme,
305
- ),
306
- const SizedBox(height: 16),
307
- if (useTwoColumns)
308
- Row(
309
- crossAxisAlignment: CrossAxisAlignment.start,
310
- children: [
311
- Expanded(
312
- child: _SettingsSectionColumn(
313
- children: [
314
- appearanceCard,
315
- behaviorCard,
316
- defaultsCard,
317
- privacyCard,
318
- ],
319
- ),
320
- ),
321
- const SizedBox(width: 20),
322
- Expanded(
323
- child: _SettingsSectionColumn(
324
- children: [
325
- ttsCard,
326
- onDeviceCard,
327
- dataCard,
328
- aboutCard,
329
- ],
330
- ),
331
- ),
332
- ],
333
- )
334
- else
335
- _SettingsSectionColumn(
336
- children: [
337
- appearanceCard,
338
- ttsCard,
339
- behaviorCard,
340
- onDeviceCard,
341
- defaultsCard,
342
- privacyCard,
343
- dataCard,
344
- aboutCard,
345
- ],
346
- ),
287
+ );
288
+
289
+ final dataCard = _SettingsSectionCard(
290
+ title: l10n.settings_data_management,
291
+ icon: Icons.restore_page_outlined,
292
+ accent: const Color(0xFFEF4444),
293
+ children: [
294
+ _DangerousAction(
295
+ label: l10n.delete_all_conversations,
296
+ icon: Icons.delete_outline_rounded,
297
+ onConfirm: () =>
298
+ ref.read(conversationsProvider.notifier).deleteAll(),
299
+ ),
300
+ _DangerousAction(
301
+ label: l10n.reset_settings_defaults,
302
+ icon: Icons.restore_rounded,
303
+ onConfirm: () =>
304
+ ref.read(settingsProvider.notifier).resetToDefaults(),
305
+ ),
306
+ ],
307
+ );
308
+
309
+ final aboutCard = _SettingsSectionCard(
310
+ title: l10n.settings_about,
311
+ icon: Icons.info_outline_rounded,
312
+ accent: const Color(0xFFF59E0B),
313
+ children: [
314
+ _AboutPanel(
315
+ title:
316
+ '${l10n.app_name} v${packageInfo.value?.version ?? l10n.app_version} (${packageInfo.value?.buildNumber ?? ''})',
317
+ subtitle: l10n.onboarding_connect_desc.replaceAll(
318
+ '\n',
319
+ ' ',
320
+ ),
321
+ providers: [
322
+ l10n.server_type_lm_studio,
323
+ l10n.server_type_ollama,
324
+ l10n.server_type_openrouter,
325
],
326
+ highlights: [
327
+ l10n.settings_on_device,
328
+ l10n.streaming_responses,
329
+ l10n.nav_personas,
330
+ ],
331
+ stack: const ['Flutter', 'Riverpod', 'shadcn_ui'],
332
+ openSource: l10n.open_source_desc,
333
),
334
+ ],
335
+ );
336
+
337
+ return ListView(
338
+ padding: EdgeInsets.fromLTRB(
339
+ horizontalPadding,
340
+ 12,
341
+ horizontalPadding,
342
+ 24 + systemBottomInset,
343
),
350
- ),
351
- ],
352
- );
353
- },
354
- ),
344
+ children: [
345
+ Center(
346
+ child: ConstrainedBox(
347
+ constraints: BoxConstraints(maxWidth: contentMaxWidth),
348
+ child: Column(
349
+ crossAxisAlignment: CrossAxisAlignment.stretch,
350
+ children: [
351
+ _SettingsHero(
352
+ settings: settings,
353
+ themeType: currentTheme,
354
+ ),
355
+ const SizedBox(height: 16),
356
+ if (useTwoColumns)
357
+ Row(
358
+ crossAxisAlignment: CrossAxisAlignment.start,
359
+ children: [
360
+ Expanded(
361
+ child: _SettingsSectionColumn(
362
+ children: [
363
+ appearanceCard,
364
+ behaviorCard,
365
+ defaultsCard,
366
+ privacyCard,
367
+ ],
368
+ ),
369
+ ),
370
+ const SizedBox(width: 20),
371
+ Expanded(
372
+ child: _SettingsSectionColumn(
373
+ children: [
374
+ ttsCard,
375
+ onDeviceCard,
376
+ dataCard,
377
+ aboutCard,
378
+ ],
379
+ ),
380
+ ),
381
+ ],
382
+ )
383
+ else
384
+ _SettingsSectionColumn(
385
+ children: [
386
+ appearanceCard,
387
+ ttsCard,
388
+ behaviorCard,
389
+ onDeviceCard,
390
+ defaultsCard,
391
+ privacyCard,
392
+ dataCard,
393
+ aboutCard,
394
+ ],
395
+ ),
396
+ ],
397
+ ),
398
+ ),
399
+ ),
400
+ ],
401
+ );
402
+ },
403
+ ),
404
+ ),
405
+ ],
406
);
407
}
408
}
lib/features/sidebar/components/active_server_indicator.dart
+3
-2
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
3
import 'package:hugeicons/hugeicons.dart';
4
import 'package:go_router/go_router.dart';
5
import 'package:shadcn_ui/shadcn_ui.dart';
6
+import 'package:localmind/core/theme/colors.dart';
7
8
import '../../../core/models/enums.dart';
9
import '../../../core/routes/app_routes.dart';
@@ -347,7 +348,7 @@ class ActiveServerIndicator extends ConsumerWidget {
348
HugeIcon(
349
icon: serverIcon ?? HugeIcons.strokeRoundedServerStack01,
350
size: 18,
350
- color: isDark ? Colors.white70 : Colors.black87,
351
+ color: isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText,
352
),
353
const SizedBox(width: 10),
354
Expanded(
@@ -359,7 +360,7 @@ class ActiveServerIndicator extends ConsumerWidget {
360
activeServer.name,
361
style: theme.textTheme.bodySmall?.copyWith(
362
fontWeight: FontWeight.w600,
362
- color: isDark ? Colors.white : Colors.black,
363
+ color: isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText,
364
),
365
overflow: TextOverflow.ellipsis,
366
maxLines: 1,
lib/features/sidebar/components/conversation_drawer_header.dart
+2
-1
@@ -1,5 +1,6 @@
1
import 'package:flutter/material.dart';
2
import 'package:flutter_riverpod/flutter_riverpod.dart';
3
+import 'package:localmind/core/theme/colors.dart';
4
5
import '../../../l10n/app_localizations.dart';
6
@@ -31,7 +32,7 @@ class ConversationDrawerHeader extends ConsumerWidget {
32
style: TextStyle(
33
fontSize: 18,
34
fontWeight: FontWeight.bold,
34
- color: isDark ? Colors.white : Colors.black,
35
+ color: isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText,
36
),
37
),
38
],
lib/features/sidebar/components/drawer_nav_item.dart
+15
-17
@@ -1,5 +1,6 @@
1
import 'package:flutter/material.dart';
2
import 'package:hugeicons/hugeicons.dart';
3
+import 'package:localmind/core/theme/colors.dart';
4
5
class DrawerNavItem extends StatelessWidget {
6
const DrawerNavItem({
@@ -21,21 +22,20 @@ class DrawerNavItem extends StatelessWidget {
22
Widget build(BuildContext context) {
23
final theme = Theme.of(context);
24
final isDark = theme.brightness == Brightness.dark;
25
+ final accent = isDark ? AppColors.darkAccent : AppColors.lightAccent;
26
27
return Padding(
26
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
28
+ padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
29
child: InkWell(
30
onTap: onTap,
29
- borderRadius: BorderRadius.circular(12),
31
+ borderRadius: BorderRadius.circular(8),
32
child: Container(
31
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
33
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
34
decoration: BoxDecoration(
35
color: isSelected
34
- ? (isDark
35
- ? theme.colorScheme.primary.withAlpha(30)
36
- : theme.colorScheme.primary.withAlpha(20))
36
+ ? accent.withAlpha(30)
37
: Colors.transparent,
38
- borderRadius: BorderRadius.circular(12),
38
+ borderRadius: BorderRadius.circular(8),
39
),
40
child: Row(
41
children: [
@@ -43,10 +43,10 @@ class DrawerNavItem extends StatelessWidget {
43
icon: iconData,
44
size: 20,
45
color: isSelected
46
- ? theme.colorScheme.primary
47
- : (isDark ? Colors.white70 : Colors.black54),
46
+ ? accent
47
+ : (isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText),
48
),
49
- const SizedBox(width: 16),
49
+ const SizedBox(width: 12),
50
Expanded(
51
child: Text(
52
label,
@@ -54,8 +54,8 @@ class DrawerNavItem extends StatelessWidget {
54
fontSize: 14,
55
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
56
color: isSelected
57
- ? (isDark ? Colors.white : theme.colorScheme.primary)
58
- : (isDark ? Colors.white70 : Colors.black87),
57
+ ? accent
58
+ : (isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText),
59
),
60
overflow: TextOverflow.ellipsis,
61
),
@@ -65,9 +65,7 @@ class DrawerNavItem extends StatelessWidget {
65
Container(
66
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
67
decoration: BoxDecoration(
68
- color: isDark
69
- ? theme.colorScheme.primary.withAlpha(45)
70
- : theme.colorScheme.primary.withAlpha(20),
68
+ color: accent.withAlpha(45),
69
borderRadius: BorderRadius.circular(999),
70
),
71
child: Text(
@@ -77,8 +75,8 @@ class DrawerNavItem extends StatelessWidget {
75
fontWeight: FontWeight.w700,
76
letterSpacing: 0.3,
77
color: isSelected
80
- ? theme.colorScheme.primary
81
- : (isDark ? Colors.white : theme.colorScheme.primary),
78
+ ? accent
79
+ : (isDark ? AppColors.darkPrimaryText : accent),
80
),
81
),
82
),
lib/features/sidebar/components/github_repo_card.dart
+13
-15
@@ -1,6 +1,7 @@
1
import 'package:flutter/material.dart';
2
import 'package:hugeicons/hugeicons.dart';
3
import 'package:url_launcher/url_launcher.dart';
4
+import 'package:localmind/core/theme/colors.dart';
5
6
import '../../../l10n/app_localizations.dart';
7
@@ -19,24 +20,21 @@ class GitHubRepoCard extends StatelessWidget {
20
@override
21
Widget build(BuildContext context) {
22
final l10n = AppLocalizations.of(context)!;
22
- final theme = Theme.of(context);
23
- final isDark = theme.brightness == Brightness.dark;
24
-
25
- final cardBg = isDark ? const Color(0xFF1F1F1F) : const Color(0xFFF9FAFB);
26
- final borderColor = isDark ? const Color(0xFF374151) : const Color(0xFFE5E7EB);
27
- final accentColor = isDark ? const Color(0xFF3B82F6) : const Color(0xFF2563EB);
23
+ final isDark = Theme.of(context).brightness == Brightness.dark;
24
25
return Padding(
30
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
26
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
27
child: Container(
28
decoration: BoxDecoration(
33
- color: cardBg,
34
- border: Border.all(color: borderColor),
35
- borderRadius: BorderRadius.circular(10),
29
+ color: isDark ? AppColors.darkSurfaceCard : AppColors.lightSurface,
30
+ border: Border.all(
31
+ color: isDark ? AppColors.darkBorder : AppColors.lightBorder,
32
+ ),
33
+ borderRadius: BorderRadius.circular(8),
34
),
35
child: InkWell(
36
onTap: _launchUrl,
39
- borderRadius: BorderRadius.circular(10),
37
+ borderRadius: BorderRadius.circular(8),
38
child: Padding(
39
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
40
child: Row(
@@ -44,7 +42,7 @@ class GitHubRepoCard extends StatelessWidget {
42
HugeIcon(
43
icon: HugeIcons.strokeRoundedGithub,
44
size: 20,
47
- color: isDark ? Colors.white70 : Colors.black87,
45
+ color: isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText,
46
),
47
const SizedBox(width: 10),
48
Expanded(
@@ -57,7 +55,7 @@ class GitHubRepoCard extends StatelessWidget {
55
style: TextStyle(
56
fontSize: 13,
57
fontWeight: FontWeight.w600,
60
- color: isDark ? Colors.white : Colors.black,
58
+ color: isDark ? AppColors.darkPrimaryText : AppColors.lightPrimaryText,
59
),
60
),
61
const SizedBox(height: 2),
@@ -66,7 +64,7 @@ class GitHubRepoCard extends StatelessWidget {
64
style: TextStyle(
65
fontSize: 11,
66
fontWeight: FontWeight.w500,
69
- color: accentColor,
67
+ color: isDark ? AppColors.darkAccent : AppColors.lightAccent,
68
),
69
),
70
],
@@ -75,7 +73,7 @@ class GitHubRepoCard extends StatelessWidget {
73
Icon(
74
Icons.open_in_new,
75
size: 14,
78
- color: isDark ? Colors.white54 : Colors.black54,
76
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
77
),
78
],
79
),
lib/features/sidebar/components/sidebar_search_button.dart
+22
-22
@@ -1,6 +1,7 @@
1
import 'package:flutter/material.dart';
2
import 'package:hugeicons/hugeicons.dart';
3
import 'package:go_router/go_router.dart';
4
+import 'package:localmind/core/theme/colors.dart';
5
import '../../../core/routes/app_routes.dart';
6
7
class SidebarSearchButton extends StatelessWidget {
@@ -8,11 +9,10 @@ class SidebarSearchButton extends StatelessWidget {
9
10
@override
11
Widget build(BuildContext context) {
11
- final theme = Theme.of(context);
12
- final isDark = theme.brightness == Brightness.dark;
12
+ final isDark = Theme.of(context).brightness == Brightness.dark;
13
14
return Padding(
15
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
15
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
16
child: InkWell(
17
onTap: () {
18
if (Scaffold.maybeOf(context)?.isDrawerOpen ?? false) {
@@ -20,42 +20,42 @@ class SidebarSearchButton extends StatelessWidget {
20
}
21
context.go(AppRoutes.chatHistory);
22
},
23
- borderRadius: BorderRadius.circular(12),
23
+ borderRadius: BorderRadius.circular(8),
24
child: Container(
25
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
25
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
26
decoration: BoxDecoration(
27
- color: isDark
28
- ? theme.colorScheme.surfaceContainerHighest.withAlpha(50)
29
- : theme.colorScheme.surfaceContainerHighest.withAlpha(100),
30
- borderRadius: BorderRadius.circular(12),
27
+ color: isDark
28
+ ? AppColors.darkSurfaceInput
29
+ : AppColors.lightSurface,
30
+ borderRadius: BorderRadius.circular(8),
31
border: Border.all(
32
- color: isDark
33
- ? theme.colorScheme.outlineVariant.withAlpha(100)
34
- : theme.colorScheme.outlineVariant,
32
+ color: isDark ? AppColors.darkBorder : AppColors.lightBorder,
33
),
34
),
35
child: Row(
36
children: [
37
HugeIcon(
38
icon: HugeIcons.strokeRoundedSearch01,
41
- size: 20,
42
- color: isDark ? Colors.white70 : Colors.black54,
39
+ size: 18,
40
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
41
),
44
- const SizedBox(width: 12),
45
- Text(
46
- 'Search conversations...',
47
- style: TextStyle(
48
- fontSize: 14,
49
- color: isDark ? Colors.white38 : Colors.black38,
42
+ const SizedBox(width: 10),
43
+ Expanded(
44
+ child: Text(
45
+ 'Search conversations...',
46
+ style: TextStyle(
47
+ fontSize: 13,
48
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
49
+ ),
50
+ overflow: TextOverflow.ellipsis,
51
),
52
),
52
- const Spacer(),
53
Text(
54
'⌘K',
55
style: TextStyle(
56
fontSize: 11,
57
fontWeight: FontWeight.w500,
58
- color: isDark ? Colors.white24 : Colors.black26,
58
+ color: isDark ? AppColors.darkMutedText : AppColors.lightMutedText,
59
letterSpacing: 0.5,
60
),
61
),
lib/features/sidebar/sidebar_drawer.dart
+5
-1
@@ -1,4 +1,5 @@
1
import 'package:flutter/material.dart';
2
+import '../../core/components/app_sizes.dart';
3
import 'sidebar_widget.dart';
4
5
class ConversationDrawer extends StatelessWidget {
@@ -6,6 +7,9 @@ class ConversationDrawer extends StatelessWidget {
7
8
@override
9
Widget build(BuildContext context) {
9
- return const Drawer(child: SidebarWidget());
10
+ return SizedBox(
11
+ width: AppSizes.sidebarWidth,
12
+ child: const Drawer(child: SidebarWidget()),
13
+ );
14
}
15
}
lib/features/sidebar/sidebar_widget.dart
+2
-1
@@ -5,6 +5,7 @@ import 'package:hugeicons/hugeicons.dart';
5
import 'package:shadcn_ui/shadcn_ui.dart';
6
7
import '../../core/routes/app_routes.dart';
8
+import '../../core/components/app_sizes.dart';
9
import '../../l10n/app_localizations.dart';
10
import '../chat/providers/chat_providers.dart';
11
import 'components/active_server_indicator.dart';
@@ -32,7 +33,7 @@ class SidebarWidget extends ConsumerWidget {
33
final isSettings = location.startsWith(AppRoutes.settings);
34
35
return Container(
35
- width: 300,
36
+ width: AppSizes.sidebarWidth,
37
decoration: BoxDecoration(
38
color: theme.scaffoldBackgroundColor,
39
border: Directionality.of(context) == TextDirection.rtl
lib/features/tts/views/tts_model_manager_screen.dart
+57
-15
@@ -8,7 +8,6 @@ import 'package:shadcn_ui/shadcn_ui.dart';
8
import '../../../core/models/enums.dart';
9
import '../../../core/providers/app_providers.dart';
10
import '../../settings/data/models/app_settings.dart';
11
-import '../../sidebar/sidebar_widget.dart';
11
import '../data/kitten_tts_model.dart';
12
import '../data/piper_tts_model.dart';
13
import '../providers/tts_model_providers.dart';
@@ -22,20 +21,63 @@ class TtsModelManagerScreen extends ConsumerWidget {
21
final l10n = AppLocalizations.of(context)!;
22
final settings = ref.watch(settingsProvider);
23
25
- return Scaffold(
26
- drawer: const SidebarWidget(),
27
- appBar: AppBar(title: Text(l10n.tts_models_title)),
28
- body: ListView(
29
- padding: const EdgeInsets.all(16),
30
- children: [
31
- _SystemEngineCard(isSelected: settings.ttsEngine == EngineId.system),
32
- const SizedBox(height: 16),
33
- _KittenEngineCard(isSelected: settings.ttsEngine == EngineId.kitten),
34
- const SizedBox(height: 16),
35
- _PiperEngineCard(isSelected: settings.ttsEngine == EngineId.piper),
36
- const SizedBox(height: 24),
37
- ],
38
- ),
24
+ final theme = Theme.of(context);
25
+ final isDark = theme.brightness == Brightness.dark;
26
+ final topPadding = MediaQuery.of(context).padding.top;
27
+
28
+ return Column(
29
+ children: [
30
+ Container(
31
+ padding: EdgeInsets.only(
32
+ left: 16,
33
+ right: 16,
34
+ top: topPadding + 8,
35
+ bottom: 16,
36
+ ),
37
+ decoration: BoxDecoration(
38
+ color: isDark ? const Color(0xFF0A0A0A) : const Color(0xFFFAFAFA),
39
+ border: Border(
40
+ bottom: BorderSide(
41
+ color: isDark
42
+ ? const Color(0xFF2A2A2A)
43
+ : const Color(0xFFE5E5E5),
44
+ ),
45
+ ),
46
+ ),
47
+ child: Row(
48
+ children: [
49
+ Builder(
50
+ builder: (context) => IconButton(
51
+ icon: const Icon(Icons.menu),
52
+ onPressed: () => Scaffold.of(context).openDrawer(),
53
+ ),
54
+ ),
55
+ const SizedBox(width: 8),
56
+ Text(
57
+ l10n.tts_models_title,
58
+ style: TextStyle(
59
+ fontSize: 20,
60
+ fontWeight: FontWeight.bold,
61
+ color: isDark ? Colors.white : Colors.black,
62
+ ),
63
+ ),
64
+ ],
65
+ ),
66
+ ),
67
+ Expanded(
68
+ child: ListView(
69
+ padding: const EdgeInsets.all(16),
70
+ children: [
71
+ _SystemEngineCard(isSelected: settings.ttsEngine == EngineId.system),
72
+ const SizedBox(height: 16),
73
+ _KittenEngineCard(isSelected: settings.ttsEngine == EngineId.kitten),
74
+ const SizedBox(height: 16),
75
+ _PiperEngineCard(isSelected: settings.ttsEngine == EngineId.piper),
76
+ const SizedBox(height: 24),
77
+ ],
78
+ ),
79
+ ),
80
+ ],
81
);
82
}
83
}
pubspec.lock
+46
-62
@@ -5,18 +5,18 @@ packages:
5
dependency: transitive
6
description:
7
name: _fe_analyzer_shared
8
- sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
8
+ sha256: "3b19a47f6ea7c2632760777c78174f47f6aec1e05f0cd611380d4593b8af1dbc"
9
url: "https://pub.dev"
10
source: hosted
11
- version: "93.0.0"
11
+ version: "96.0.0"
12
analyzer:
13
dependency: transitive
14
description:
15
name: analyzer
16
- sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
16
+ sha256: "0c516bc4ad36a1a75759e54d5047cb9d15cded4459df01aa35a0b5ec7db2c2a0"
17
url: "https://pub.dev"
18
source: hosted
19
- version: "10.0.1"
19
+ version: "10.2.0"
20
animations:
21
dependency: "direct main"
22
description:
@@ -321,14 +321,6 @@ packages:
321
url: "https://pub.dev"
322
source: hosted
323
version: "1.0.9"
324
- dart_sentencepiece_tokenizer:
325
- dependency: transitive
326
- description:
327
- name: dart_sentencepiece_tokenizer
328
- sha256: "85825632845cf6427ea0cd13dfba96b4341cf63525165155e5b9b97011239289"
329
- url: "https://pub.dev"
330
- source: hosted
331
- version: "1.3.2"
324
dart_style:
325
dependency: transitive
326
description:
@@ -474,10 +466,10 @@ packages:
466
dependency: "direct main"
467
description:
468
name: flutter_gemma
477
- sha256: "984960b54bbc0ff7e36cf568a02652b1d4bc016d6c75575b027fb6102fab48c3"
469
+ sha256: a45e48e04193080ebe37fe65a9adb33d8473feceb8e1f51659f7bee8f8c95e04
470
url: "https://pub.dev"
471
source: hosted
480
- version: "0.16.5"
472
+ version: "1.0.0"
473
flutter_lints:
474
dependency: "direct dev"
475
description:
@@ -825,10 +817,10 @@ packages:
817
dependency: transitive
818
description:
819
name: large_file_handler
828
- sha256: "1657db12b4591242b186c23eee437997be91e655ab0d803fd74829074460757f"
820
+ sha256: "234eda71f32b6a772349299d0ed185a5f3e10c59235576fd178f4d720dd956ec"
821
url: "https://pub.dev"
822
source: hosted
831
- version: "0.4.0"
823
+ version: "0.5.0"
824
leak_tracker:
825
dependency: transitive
826
description:
@@ -869,14 +861,6 @@ packages:
861
url: "https://pub.dev"
862
source: hosted
863
version: "1.3.0"
872
- local_hnsw:
873
- dependency: transitive
874
- description:
875
- name: local_hnsw
876
- sha256: "7fb0988e3f850121774d9dfe94068f843d87908d6b1e9ed5039710d8d9ae3f7a"
877
- url: "https://pub.dev"
878
- source: hosted
879
- version: "1.0.0"
864
logger:
865
dependency: "direct main"
866
description:
@@ -921,10 +905,10 @@ packages:
905
dependency: transitive
906
description:
907
name: meta
924
- sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
908
+ sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
909
url: "https://pub.dev"
910
source: hosted
927
- version: "1.17.0"
911
+ version: "1.18.0"
912
mime:
913
dependency: transitive
914
description:
@@ -945,10 +929,10 @@ packages:
929
dependency: transitive
930
description:
931
name: native_toolchain_c
948
- sha256: "8aaead321425bd3f03bd5894aa27c8ea6993eab95531da7e59f5d39c6e5708ec"
932
+ sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572"
933
url: "https://pub.dev"
934
source: hosted
951
- version: "0.18.0"
935
+ version: "0.17.6"
936
nested:
937
dependency: transitive
938
description:
@@ -989,6 +973,14 @@ packages:
973
url: "https://pub.dev"
974
source: hosted
975
version: "5.3.2"
976
+ objective_c:
977
+ dependency: transitive
978
+ description:
979
+ name: objective_c
980
+ sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52"
981
+ url: "https://pub.dev"
982
+ source: hosted
983
+ version: "9.3.0"
984
package_config:
985
dependency: transitive
986
description:
@@ -1049,10 +1041,10 @@ packages:
1041
dependency: transitive
1042
description:
1043
name: path_provider_foundation
1052
- sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4"
1044
+ sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699"
1045
url: "https://pub.dev"
1046
source: hosted
1055
- version: "2.5.1"
1047
+ version: "2.6.0"
1048
path_provider_linux:
1049
dependency: transitive
1050
description:
@@ -1201,10 +1193,10 @@ packages:
1193
dependency: transitive
1194
description:
1195
name: shared_preferences_android
1204
- sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
1196
+ sha256: "93ae5884a9df5d3bb696825bceb3a17590754548b5d740eba51500afc8d088f5"
1197
url: "https://pub.dev"
1198
source: hosted
1207
- version: "2.4.23"
1199
+ version: "2.4.26"
1200
shared_preferences_foundation:
1201
dependency: transitive
1202
description:
@@ -1430,50 +1422,42 @@ packages:
1422
dependency: transitive
1423
description:
1424
name: sqflite
1433
- sha256: "564cfed0746fe53140c23b70b308e045c3b31f17778f2f326ccb7d804ea0250a"
1425
+ sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082"
1426
url: "https://pub.dev"
1427
source: hosted
1436
- version: "2.4.2+1"
1428
+ version: "2.4.3"
1429
sqflite_android:
1430
dependency: transitive
1431
description:
1432
name: sqflite_android
1441
- sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40"
1433
+ sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b
1434
url: "https://pub.dev"
1435
source: hosted
1444
- version: "2.4.2+3"
1436
+ version: "2.4.3"
1437
sqflite_common:
1438
dependency: transitive
1439
description:
1440
name: sqflite_common
1449
- sha256: "1581ffbf7a0e333b380d6a30737d78516b826cb35beb7fb0bf8a3ea0c678b465"
1441
+ sha256: "5bf6a55c166e73bf651ba7ec3ed486e577620e3dc8f3a9c6a258a8031b624590"
1442
url: "https://pub.dev"
1443
source: hosted
1452
- version: "2.5.8"
1444
+ version: "2.5.11"
1445
sqflite_darwin:
1446
dependency: transitive
1447
description:
1448
name: sqflite_darwin
1457
- sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
1449
+ sha256: "164a5d73ab87a134566057219988bafde837029a64264e61f1f04376ef3cfcd2"
1450
url: "https://pub.dev"
1451
source: hosted
1460
- version: "2.4.2"
1452
+ version: "2.4.3"
1453
sqflite_platform_interface:
1454
dependency: transitive
1455
description:
1456
name: sqflite_platform_interface
1465
- sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
1457
+ sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50
1458
url: "https://pub.dev"
1459
source: hosted
1468
- version: "2.4.0"
1469
- sqlite3:
1470
- dependency: transitive
1471
- description:
1472
- name: sqlite3
1473
- sha256: "37356bcb56ce0d9404d602c41e4bdb7765e7e9732a3e47adb3d98c556a6abdad"
1474
- url: "https://pub.dev"
1475
- source: hosted
1476
- version: "3.3.3"
1460
+ version: "2.4.1"
1461
stack_trace:
1462
dependency: transitive
1463
description:
@@ -1518,10 +1502,10 @@ packages:
1502
dependency: transitive
1503
description:
1504
name: synchronized
1521
- sha256: "63896c27e81b28f8cb4e69ead0d3e8f03f1d1e5fc531a3e579cabed6a2c7c9e5"
1505
+ sha256: "93b153dcb6a26dcddee6ca087dd634b53e38c10b5aa163e8e49501a776456153"
1506
url: "https://pub.dev"
1507
source: hosted
1524
- version: "3.4.0+1"
1508
+ version: "3.4.1"
1509
syntax_highlight:
1510
dependency: "direct main"
1511
description:
@@ -1543,26 +1527,26 @@ packages:
1527
dependency: transitive
1528
description:
1529
name: test
1546
- sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
1530
+ sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20"
1531
url: "https://pub.dev"
1532
source: hosted
1549
- version: "1.30.0"
1533
+ version: "1.31.0"
1534
test_api:
1535
dependency: transitive
1536
description:
1537
name: test_api
1554
- sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
1538
+ sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
1539
url: "https://pub.dev"
1540
source: hosted
1557
- version: "0.7.10"
1541
+ version: "0.7.11"
1542
test_core:
1543
dependency: transitive
1544
description:
1545
name: test_core
1562
- sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
1546
+ sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34"
1547
url: "https://pub.dev"
1548
source: hosted
1565
- version: "0.6.16"
1549
+ version: "0.6.17"
1550
theme_extensions_builder_annotation:
1551
dependency: transitive
1552
description:
@@ -1623,10 +1607,10 @@ packages:
1607
dependency: transitive
1608
description:
1609
name: url_launcher_android
1626
- sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c"
1610
+ sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32
1611
url: "https://pub.dev"
1612
source: hosted
1629
- version: "6.3.30"
1613
+ version: "6.3.32"
1614
url_launcher_ios:
1615
dependency: transitive
1616
description:
@@ -1820,5 +1804,5 @@ packages:
1804
source: hosted
1805
version: "3.1.3"
1806
sdks:
1823
- dart: ">=3.11.0 <4.0.0"
1824
- flutter: ">=3.41.0"
1807
+ dart: ">=3.12.0 <4.0.0"
1808
+ flutter: ">=3.44.0"
pubspec.yaml
+2
-2
@@ -19,7 +19,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
19
version: 1.4.0+43
20
21
environment:
22
- sdk: ^3.11.0
22
+ sdk: ">=3.11.0 <4.0.0"
23
24
# Dependencies specify other packages that your package needs in order to work.
25
# To automatically upgrade your package dependencies to the latest versions
@@ -59,7 +59,7 @@ dependencies:
59
objectbox: ^5.3.1
60
objectbox_flutter_libs: ^5.3.1
61
path: ^1.9.1
62
- flutter_gemma: 0.16.5
62
+ flutter_gemma: 1.0.0
63
wakelock_plus: ^1.2.8
64
flutter_local_notifications: ^22.0.1
65
audioplayers: ^6.4.0