main
dart 205 lines 8.63 KB
Raw
1 import 'package:flutter_gemma/flutter_gemma.dart';
2 import 'package:flutter_riverpod/flutter_riverpod.dart';
3 import 'package:package_info_plus/package_info_plus.dart';
4
5 import '../../features/chat/data/models/mcp_integration.dart';
6 import '../../features/chat/utils/image_upload_utils.dart';
7 import '../../features/settings/data/models/app_settings.dart';
8 import '../models/enums.dart';
9 import '../../features/tts/data/kitten_tts_model.dart';
10 import 'storage_providers.dart';
11 import '../theme/app_theme.dart';
12
13 final themeModeProvider = NotifierProvider<ThemeModeNotifier, AppThemeType>(() {
14 return ThemeModeNotifier();
15 });
16
17 class ThemeModeNotifier extends Notifier<AppThemeType> {
18 @override
19 AppThemeType build() {
20 final prefs = ref.watch(sharedPreferencesProvider);
21 final savedMode = prefs.getInt('themeMode');
22 if (savedMode != null &&
23 savedMode >= 0 &&
24 savedMode < AppThemeType.values.length) {
25 return AppThemeType.values[savedMode];
26 }
27 return AppThemeType.system;
28 }
29
30 void setThemeMode(AppThemeType mode) {
31 final prefs = ref.read(sharedPreferencesProvider);
32 state = mode;
33 prefs.setInt('themeMode', mode.index);
34 }
35 }
36
37 final settingsProvider = NotifierProvider<SettingsNotifier, AppSettings>(() {
38 return SettingsNotifier();
39 });
40
41 class SettingsNotifier extends Notifier<AppSettings> {
42 @override
43 AppSettings build() {
44 final prefs = ref.watch(sharedPreferencesProvider);
45 final savedJson = prefs.getString('appSettings');
46 if (savedJson != null) {
47 try {
48 return AppSettings.fromJson(savedJson);
49 } catch (_) {}
50 }
51 return AppSettings();
52 }
53
54 Future<void> updateSettings(AppSettings appSettings) async {
55 final prefs = ref.read(sharedPreferencesProvider);
56 state = appSettings;
57 await prefs.setString('appSettings', appSettings.toJson());
58 }
59
60 void setTemperature(double value) =>
61 _update(state.copyWith(temperature: value));
62 void setTopP(double value) => _update(state.copyWith(topP: value));
63 void setMaxTokens(int value) => _update(state.copyWith(maxTokens: value));
64 void setContextLength(int value) =>
65 _update(state.copyWith(contextLength: value));
66 void setFontSize(double value) => _update(state.copyWith(fontSize: value));
67 void setShowSystemMessages(bool value) =>
68 _update(state.copyWith(showSystemMessages: value));
69 void setHapticFeedback(bool value) =>
70 _update(state.copyWith(hapticFeedbackEnabled: value));
71 void setSendOnEnter(bool value) =>
72 _update(state.copyWith(sendOnEnter: value));
73 void setDefaultServer(String? id) =>
74 _update(state.copyWith(defaultServerId: id));
75 void setShowDataIndicator(bool value) =>
76 _update(state.copyWith(showDataIndicator: value));
77 void setAutoGenerateTitle(bool value) =>
78 _update(state.copyWith(autoGenerateTitle: value));
79 void setStreamingEnabled(bool value) =>
80 _update(state.copyWith(streamingEnabled: value));
81 void setDefaultPersona(String? id) =>
82 _update(state.copyWith(defaultPersonaId: id));
83 void setDefaultModel({String? serverId, String? modelId}) => _update(
84 state.copyWith(defaultModelServerId: serverId, defaultModelId: modelId),
85 );
86 void clearDefaultModel() =>
87 _update(state.copyWith(defaultModelServerId: null, defaultModelId: null));
88 void setHasCompletedOnboarding(bool value) =>
89 _update(state.copyWith(hasCompletedOnboarding: value));
90 void setMcpEnabled(bool value) => _update(state.copyWith(mcpEnabled: value));
91 void setNewChatMcpEnabled(bool value) =>
92 _update(state.copyWith(newChatMcpEnabled: value));
93 void setCodeThemeDark(SyntaxThemeName value) =>
94 _update(state.copyWith(codeThemeDark: value));
95 void setCodeThemeLight(SyntaxThemeName value) =>
96 _update(state.copyWith(codeThemeLight: value));
97 void setPreferredBackend(PreferredBackend value) =>
98 _update(state.copyWith(preferredBackend: value));
99 void setTtsEngine(EngineId value) =>
100 _update(state.copyWith(ttsEngine: value, ttsVoiceId: null));
101 void setTtsVoiceId(String? value) =>
102 _update(state.copyWith(ttsVoiceId: value));
103 void setTtsSpeed(double value) => _update(state.copyWith(ttsSpeed: value));
104 void setKittenTtsModelVariant(KittenTtsModelVariant value) =>
105 _update(state.copyWith(kittenTtsModelVariant: value));
106 void setAutoSpeakEnabled(bool value) =>
107 _update(state.copyWith(autoSpeakEnabled: value));
108 void setConciseVoiceResponsesEnabled(bool value) =>
109 _update(state.copyWith(conciseVoiceResponsesEnabled: value));
110 void setTtsProcessMarkdown(bool value) =>
111 _update(state.copyWith(ttsProcessMarkdown: value));
112 void setTtsSkipSeconds(int value) =>
113 _update(state.copyWith(ttsSkipSeconds: value));
114 void setSmartReplyEnabled(bool value) =>
115 _update(state.copyWith(smartReplyEnabled: value));
116 void setAiUserResponseEnabled(bool value) =>
117 _update(state.copyWith(aiUserResponseEnabled: value));
118 void setLocaleCode(String? value) =>
119 _update(state.copyWith(localeCode: value));
120 void setHuggingFaceToken(String? value) =>
121 _update(state.copyWith(huggingFaceToken: value));
122 void setUnloadModelsBeforeLoad(bool value) =>
123 _update(state.copyWith(unloadModelsBeforeLoad: value));
124 void setTempChatKeyboardIncognito(bool value) =>
125 _update(state.copyWith(tempChatKeyboardIncognito: value));
126 void setResumeLastChat(bool value) =>
127 _update(state.copyWith(resumeLastChat: value));
128 void setImageCompressionEnabled(bool value) =>
129 _update(state.copyWith(imageCompressionEnabled: value));
130 void setImageCompressionLevel(ImageCompressionLevel value) =>
131 _update(state.copyWith(imageCompressionLevel: value));
132 void setSmartRepliesUsePersona(bool value) =>
133 _update(state.copyWith(smartRepliesUsePersona: value));
134 void setKeepPersonaOnNewChat(bool value) =>
135 _update(state.copyWith(keepPersonaOnNewChat: value));
136 void setRoleSwapButtonEnabled(bool value) =>
137 _update(state.copyWith(roleSwapButtonEnabled: value));
138 void setShowSystemMessagesInChat(bool value) =>
139 _update(state.copyWith(showSystemMessagesInChat: value));
140 void setCalendarToolsEnabled(bool value) =>
141 _update(state.copyWith(calendarToolsEnabled: value));
142 void setLocationToolsEnabled(bool value) =>
143 _update(state.copyWith(locationToolsEnabled: value));
144 void setAutoCollapseThinking(bool value) =>
145 _update(state.copyWith(autoCollapseThinking: value));
146 void setSendTemperature(bool value) =>
147 _update(state.copyWith(sendTemperature: value));
148 void setSendTopP(bool value) => _update(state.copyWith(sendTopP: value));
149 void setDefaultSystemPrompt(String value) =>
150 _update(state.copyWith(defaultSystemPrompt: value.trim()));
151
152 void addSavedMcpIntegration(McpIntegration integration) {
153 final current = List<McpIntegration>.from(state.savedMcpIntegrations);
154 final exists = current.any(
155 (i) =>
156 (integration.pluginId != null &&
157 i.pluginId == integration.pluginId) ||
158 (integration.serverUrl != null &&
159 i.serverUrl == integration.serverUrl),
160 );
161 if (!exists) {
162 current.add(integration);
163 _update(state.copyWith(savedMcpIntegrations: current));
164 }
165 }
166
167 void removeSavedMcpIntegration(int index) {
168 if (index >= 0 && index < state.savedMcpIntegrations.length) {
169 final current = List<McpIntegration>.from(state.savedMcpIntegrations)
170 ..removeAt(index);
171 _update(state.copyWith(savedMcpIntegrations: current));
172 }
173 }
174
175 void toggleSavedMcpIntegration(int index, bool enabled) {
176 if (index >= 0 && index < state.savedMcpIntegrations.length) {
177 final current = List<McpIntegration>.from(state.savedMcpIntegrations);
178 current[index] = current[index].copyWith(enabled: enabled);
179 _update(state.copyWith(savedMcpIntegrations: current));
180 }
181 }
182
183 Future<void> _update(AppSettings updated) async {
184 state = updated;
185 final prefs = ref.read(sharedPreferencesProvider);
186 // Fire-and-forget persistence: in-memory state is updated synchronously
187 // above so the UI reflects the new value immediately. The async write to
188 // SharedPreferences is not awaited by setter callers (which are sync
189 // UI handlers), so a hard app kill between the state mutation and the
190 // write completing can lose the most recent setting change. Settings
191 // are flushed on every state change and on app pause via
192 // [flushPendingSettings] in the root widget.
193 await prefs.setString('appSettings', updated.toJson());
194 }
195
196 Future<void> resetToDefaults() async {
197 state = AppSettings();
198 final prefs = ref.read(sharedPreferencesProvider);
199 await prefs.setString('appSettings', state.toJson());
200 }
201 }
202
203 final packageInfoProvider = FutureProvider<PackageInfo>((ref) async {
204 return PackageInfo.fromPlatform();
205 });