@tduncan / Cortana / commits / bca4e2e

refactor: unify theme tokens across UI components and flutter version upgrade to 3.44.2

abdulmominsakib committed Jun 17, 2026 at 09:25 UTC bca4e2e7d5b07f852f5905bb84f256636892cb66
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