Grouped settings UI
This commit is contained in:
parent
3663fdfcc1
commit
a34e7286ee
|
@ -150,9 +150,14 @@
|
||||||
"description": "Title of the export settings section",
|
"description": "Title of the export settings section",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsTitleContact": "Kontakt",
|
"settingsTitleAbout": "Über",
|
||||||
"@settingsTitleContact": {
|
"@settingsTitleAbout": {
|
||||||
"description": "Title of the contact settings section",
|
"description": "Title of the about settings section",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsSavedAutomatically": "Einstellungen werden automatisch gespeichert",
|
||||||
|
"@settingsSavedAutomatically": {
|
||||||
|
"description": "Text displayed when settings are saved automatically",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsHost": "Host",
|
"settingsHost": "Host",
|
||||||
|
@ -212,41 +217,11 @@
|
||||||
"description": "Text displayed as description for disable markdown toggle",
|
"description": "Text displayed as description for disable markdown toggle",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsBehaviorNotUpdatedForOlderChats": "Verhaltenseinstellungen werden nicht für ältere Chats aktualisiert. Starte einen neuen, um die Änderungen anzuwenden.",
|
"settingsBehaviorNotUpdatedForOlderChats": "Verhaltenseinstellungen werden nicht für ältere Chats aktualisiert",
|
||||||
"@settingsBehaviorNotUpdatedForOlderChats": {
|
"@settingsBehaviorNotUpdatedForOlderChats": {
|
||||||
"description": "Text displayed when behavior settings are not updated for older chats",
|
"description": "Text displayed when behavior settings are not updated for older chats",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsGenerateTitles": "Titel generieren",
|
|
||||||
"@settingsGenerateTitles": {
|
|
||||||
"description": "Text displayed as description for generate titles toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsAskBeforeDelete": "Vor Löschung des Chats fragen",
|
|
||||||
"@settingsAskBeforeDelete": {
|
|
||||||
"description": "Text displayed as description for ask before deletion toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsResetOnModelChange": "Zurücksetzen bei Modelländerung",
|
|
||||||
"@settingsResetOnModelChange": {
|
|
||||||
"description": "Text displayed as description for reset on model change toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsEnableEditing": "Nachrichtenbearbeitung aktivieren",
|
|
||||||
"@settingsEnableEditing": {
|
|
||||||
"description": "Text displayed as description for enable editing toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsShowTips": "Tipps in der Seitenleiste anzeigen",
|
|
||||||
"@settingsShowTips": {
|
|
||||||
"description": "Text displayed as description for show tips toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsShowModelTags": "Model-Tags anzeigen",
|
|
||||||
"@settingsShowModelTags": {
|
|
||||||
"description": "Text displayed as description for show model tags toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsBrightnessSystem": "System",
|
"settingsBrightnessSystem": "System",
|
||||||
"@settingsBrightnessSystem": {
|
"@settingsBrightnessSystem": {
|
||||||
"description": "Text displayed as description for system brightness option",
|
"description": "Text displayed as description for system brightness option",
|
||||||
|
@ -282,6 +257,46 @@
|
||||||
"description": "Text displayed for cancel button, should be capitalized",
|
"description": "Text displayed for cancel button, should be capitalized",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
|
"settingsGenerateTitles": "Titel generieren",
|
||||||
|
"@settingsGenerateTitles": {
|
||||||
|
"description": "Text displayed as description for generate titles toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsAskBeforeDelete": "Vor Löschung des Chats fragen",
|
||||||
|
"@settingsAskBeforeDelete": {
|
||||||
|
"description": "Text displayed as description for ask before deletion toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsResetOnModelChange": "Zurücksetzen bei Modelländerung",
|
||||||
|
"@settingsResetOnModelChange": {
|
||||||
|
"description": "Text displayed as description for reset on model change toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsEnableEditing": "Nachrichtenbearbeitung aktivieren",
|
||||||
|
"@settingsEnableEditing": {
|
||||||
|
"description": "Text displayed as description for enable editing toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsShowTips": "Tipps in der Seitenleiste anzeigen",
|
||||||
|
"@settingsShowTips": {
|
||||||
|
"description": "Text displayed as description for show tips toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsShowModelTags": "Model-Tags anzeigen",
|
||||||
|
"@settingsShowModelTags": {
|
||||||
|
"description": "Text displayed as description for show model tags toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsRequestTypeStream": "Stream",
|
||||||
|
"@settingsRequestTypeStream": {
|
||||||
|
"description": "Text displayed as description for stream request type. Do not translate if not required!",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsRequestTypeRequest": "Request",
|
||||||
|
"@settingsRequestTypeRequest": {
|
||||||
|
"description": "Text displayed as description for request request type. Do not translate if not required!",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
"settingsExportChats": "Chats exportieren",
|
"settingsExportChats": "Chats exportieren",
|
||||||
"@settingsExportChats": {
|
"@settingsExportChats": {
|
||||||
"description": "Text displayed as description for export chats button",
|
"description": "Text displayed as description for export chats button",
|
||||||
|
@ -297,7 +312,7 @@
|
||||||
"description": "Title of the import dialog",
|
"description": "Title of the import dialog",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsImportChatsDescription": "Der folgende Schritt importiert die Chats aus der ausgewählten Datei. Dadurch werden die aktuellen Chats überschrieben.\nMöchtest du fortfahren?",
|
"settingsImportChatsDescription": "Der folgende Schritt importiert die Chats aus der ausgewählten Datei. Dadurch werden alle aktuell verfügbaren Chats überschrieben.\nMöchtest du fortfahren?",
|
||||||
"@settingsImportChatsDescription": {
|
"@settingsImportChatsDescription": {
|
||||||
"description": "Description of the import dialog",
|
"description": "Description of the import dialog",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
|
@ -317,6 +332,16 @@
|
||||||
"description": "Text displayed when chats are imported successfully",
|
"description": "Text displayed when chats are imported successfully",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
|
"settingsExportInfo": "Diese Optionen ermöglichen es dir, deinen Chat-Verlauf zu exportieren und zu importieren. Dies kann nützlich sein, wenn du deinen Chat-Verlauf auf ein anderes Gerät übertragen oder deinen Chat-Verlauf sichern möchtest",
|
||||||
|
"@settingsExportInfo": {
|
||||||
|
"description": "Information displayed for export and import options",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsExportWarning": "Mehrere Chatverläufe werden nicht zusammengeführt! Du verlierst deinen aktuellen Chatverlauf, wenn du einen neuen importierst",
|
||||||
|
"@settingsExportWarning": {
|
||||||
|
"description": "Warning displayed for export and import options",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
"settingsUpdateCheck": "Nach Updates suchen",
|
"settingsUpdateCheck": "Nach Updates suchen",
|
||||||
"@settingsUpdateCheck": {
|
"@settingsUpdateCheck": {
|
||||||
"description": "Text displayed as description for check for updates button",
|
"description": "Text displayed as description for check for updates button",
|
||||||
|
@ -378,7 +403,7 @@
|
||||||
"description": "Text displayed for cancel button, should be capitalized",
|
"description": "Text displayed for cancel button, should be capitalized",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsCheckForUpdates": "Beim Einstellungs-Öffnen Updates suchen",
|
"settingsCheckForUpdates": "Nach Updates suchen",
|
||||||
"@settingsCheckForUpdates": {
|
"@settingsCheckForUpdates": {
|
||||||
"description": "Text displayed as description for check for updates toggle",
|
"description": "Text displayed as description for check for updates toggle",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
|
|
|
@ -150,9 +150,14 @@
|
||||||
"description": "Title of the export settings section",
|
"description": "Title of the export settings section",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsTitleContact": "Contact",
|
"settingsTitleAbout": "About",
|
||||||
"@settingsTitleContact": {
|
"@settingsTitleAbout": {
|
||||||
"description": "Title of the contact settings section",
|
"description": "Title of the about settings section",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsSavedAutomatically": "Settings are saved automatically",
|
||||||
|
"@settingsSavedAutomatically": {
|
||||||
|
"description": "Text displayed when settings are saved automatically",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsHost": "Host",
|
"settingsHost": "Host",
|
||||||
|
@ -212,41 +217,11 @@
|
||||||
"description": "Text displayed as description for disable markdown toggle",
|
"description": "Text displayed as description for disable markdown toggle",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsBehaviorNotUpdatedForOlderChats": "Behavior settings are not updated for older chats. Start a new one to apply the changes.",
|
"settingsBehaviorNotUpdatedForOlderChats": "Behavior settings are not updated for older chats",
|
||||||
"@settingsBehaviorNotUpdatedForOlderChats": {
|
"@settingsBehaviorNotUpdatedForOlderChats": {
|
||||||
"description": "Text displayed when behavior settings are not updated for older chats",
|
"description": "Text displayed when behavior settings are not updated for older chats",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsGenerateTitles": "Generate titles",
|
|
||||||
"@settingsGenerateTitles": {
|
|
||||||
"description": "Text displayed as description for generate titles toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsAskBeforeDelete": "Ask before chat deletion",
|
|
||||||
"@settingsAskBeforeDelete": {
|
|
||||||
"description": "Text displayed as description for ask before deletion toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsResetOnModelChange": "Reset on model change",
|
|
||||||
"@settingsResetOnModelChange": {
|
|
||||||
"description": "Text displayed as description for reset on model change toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsEnableEditing": "Enable editing of messages",
|
|
||||||
"@settingsEnableEditing": {
|
|
||||||
"description": "Text displayed as description for enable editing toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsShowTips": "Show tips in sidebar",
|
|
||||||
"@settingsShowTips": {
|
|
||||||
"description": "Text displayed as description for show tips toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsShowModelTags": "Show model tags",
|
|
||||||
"@settingsShowModelTags": {
|
|
||||||
"description": "Text displayed as description for show model tags toggle",
|
|
||||||
"context": "Visible in the settings view"
|
|
||||||
},
|
|
||||||
"settingsBrightnessSystem": "System",
|
"settingsBrightnessSystem": "System",
|
||||||
"@settingsBrightnessSystem": {
|
"@settingsBrightnessSystem": {
|
||||||
"description": "Text displayed as description for system brightness option",
|
"description": "Text displayed as description for system brightness option",
|
||||||
|
@ -282,6 +257,46 @@
|
||||||
"description": "Text displayed for cancel button, should be capitalized",
|
"description": "Text displayed for cancel button, should be capitalized",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
|
"settingsGenerateTitles": "Generate titles",
|
||||||
|
"@settingsGenerateTitles": {
|
||||||
|
"description": "Text displayed as description for generate titles toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsAskBeforeDelete": "Ask before chat deletion",
|
||||||
|
"@settingsAskBeforeDelete": {
|
||||||
|
"description": "Text displayed as description for ask before deletion toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsResetOnModelChange": "Reset on model change",
|
||||||
|
"@settingsResetOnModelChange": {
|
||||||
|
"description": "Text displayed as description for reset on model change toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsEnableEditing": "Enable editing of messages",
|
||||||
|
"@settingsEnableEditing": {
|
||||||
|
"description": "Text displayed as description for enable editing toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsShowTips": "Show tips in sidebar",
|
||||||
|
"@settingsShowTips": {
|
||||||
|
"description": "Text displayed as description for show tips toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsShowModelTags": "Show model tags",
|
||||||
|
"@settingsShowModelTags": {
|
||||||
|
"description": "Text displayed as description for show model tags toggle",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsRequestTypeStream": "Stream",
|
||||||
|
"@settingsRequestTypeStream": {
|
||||||
|
"description": "Text displayed as description for stream request type. Do not translate if not required!",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsRequestTypeRequest": "Request",
|
||||||
|
"@settingsRequestTypeRequest": {
|
||||||
|
"description": "Text displayed as description for request request type. Do not translate if not required!",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
"settingsExportChats": "Export chats",
|
"settingsExportChats": "Export chats",
|
||||||
"@settingsExportChats": {
|
"@settingsExportChats": {
|
||||||
"description": "Text displayed as description for export chats button",
|
"description": "Text displayed as description for export chats button",
|
||||||
|
@ -297,7 +312,7 @@
|
||||||
"description": "Title of the import dialog",
|
"description": "Title of the import dialog",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsImportChatsDescription": "The following step will import the chats from the selected file. This will overwrite the current chats.\nDo you want to continue?",
|
"settingsImportChatsDescription": "The following step will import the chats from the selected file. This will overwrite all currently available chats.\nDo you want to continue?",
|
||||||
"@settingsImportChatsDescription": {
|
"@settingsImportChatsDescription": {
|
||||||
"description": "Description of the import dialog",
|
"description": "Description of the import dialog",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
|
@ -317,6 +332,16 @@
|
||||||
"description": "Text displayed when chats are imported successfully",
|
"description": "Text displayed when chats are imported successfully",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
|
"settingsExportInfo": "This options allows you to export and import your chat history. This can be useful if you want to transfer your chat history to another device or backup your chat history",
|
||||||
|
"@settingsExportInfo": {
|
||||||
|
"description": "Information displayed for export and import options",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
|
"settingsExportWarning": "Multiple chat histories won't be merged! You'll loose your current chat history if you import a new one",
|
||||||
|
"@settingsExportWarning": {
|
||||||
|
"description": "Warning displayed for export and import options",
|
||||||
|
"context": "Visible in the settings view"
|
||||||
|
},
|
||||||
"settingsUpdateCheck": "Check for updates",
|
"settingsUpdateCheck": "Check for updates",
|
||||||
"@settingsUpdateCheck": {
|
"@settingsUpdateCheck": {
|
||||||
"description": "Text displayed as description for check for updates button",
|
"description": "Text displayed as description for check for updates button",
|
||||||
|
@ -378,7 +403,7 @@
|
||||||
"description": "Text displayed for cancel button, should be capitalized",
|
"description": "Text displayed for cancel button, should be capitalized",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
},
|
},
|
||||||
"settingsCheckForUpdates": "Check for update on settings open",
|
"settingsCheckForUpdates": "Check for updates",
|
||||||
"@settingsCheckForUpdates": {
|
"@settingsCheckForUpdates": {
|
||||||
"description": "Text displayed as description for check for updates toggle",
|
"description": "Text displayed as description for check for updates toggle",
|
||||||
"context": "Visible in the settings view"
|
"context": "Visible in the settings view"
|
||||||
|
|
|
@ -1359,7 +1359,7 @@ class _MainAppState extends State<MainApp> {
|
||||||
onMessageLongPress: (context, p1) async {
|
onMessageLongPress: (context, p1) async {
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
|
|
||||||
if (!(prefs!.getBool("enableEditing") ?? false)) {
|
if (!(prefs!.getBool("enableEditing") ?? true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,20 +5,82 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'main.dart';
|
import 'main.dart';
|
||||||
import 'worker_update.dart';
|
|
||||||
import 'package:ollama_app/worker_setter.dart';
|
import 'package:ollama_app/worker_setter.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'settings/behavior.dart';
|
||||||
|
import 'settings/interface.dart';
|
||||||
|
import 'settings/export.dart';
|
||||||
|
import 'settings/about.dart';
|
||||||
|
|
||||||
import 'package:dartx/dartx.dart';
|
import 'package:dartx/dartx.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:simple_icons/simple_icons.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
import 'package:restart_app/restart_app.dart';
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:version/version.dart';
|
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
|
||||||
|
Widget toggle(BuildContext context, String text, bool value,
|
||||||
|
Function(bool value) onChanged) {
|
||||||
|
var space = ""; // Invisible character: U+2063
|
||||||
|
var spacePlus = " $space";
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4, bottom: 4),
|
||||||
|
child: Stack(children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16, top: 12),
|
||||||
|
child: Divider(
|
||||||
|
color: (Theme.of(context).brightness == Brightness.light)
|
||||||
|
? Colors.grey[300]
|
||||||
|
: Colors.grey[900])),
|
||||||
|
Row(mainAxisSize: MainAxisSize.max, children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(text + spacePlus,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
style: TextStyle(
|
||||||
|
backgroundColor:
|
||||||
|
(Theme.of(context).brightness == Brightness.light)
|
||||||
|
? (theme ?? ThemeData()).colorScheme.surface
|
||||||
|
: (themeDark ?? ThemeData.dark())
|
||||||
|
.colorScheme
|
||||||
|
.surface))),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.only(left: 16),
|
||||||
|
color: (Theme.of(context).brightness == Brightness.light)
|
||||||
|
? (theme ?? ThemeData()).colorScheme.surface
|
||||||
|
: (themeDark ?? ThemeData.dark()).colorScheme.surface,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40, child: Switch(value: value, onChanged: onChanged)))
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget title(String text, {double top = 16, double bottom = 16}) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.only(left: 8, right: 8, top: top, bottom: bottom),
|
||||||
|
child: Row(children: [
|
||||||
|
const Expanded(child: Divider()),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||||
|
child: Text(text)),
|
||||||
|
const Expanded(child: Divider())
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget button(String text, IconData icon, void Function()? onPressed,
|
||||||
|
{Color? color}) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onPressed,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Row(children: [
|
||||||
|
Icon(icon, color: color),
|
||||||
|
const SizedBox(width: 16, height: 42),
|
||||||
|
Expanded(child: Text(text, style: TextStyle(color: color)))
|
||||||
|
]),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
class ScreenSettings extends StatefulWidget {
|
class ScreenSettings extends StatefulWidget {
|
||||||
const ScreenSettings({super.key});
|
const ScreenSettings({super.key});
|
||||||
|
|
||||||
|
@ -87,19 +149,11 @@ class _ScreenSettingsState extends State<ScreenSettings> {
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
final systemInputController = TextEditingController(
|
|
||||||
text: prefs?.getString("system") ?? "You are a helpful assistant");
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
checkHost();
|
checkHost();
|
||||||
updatesSupported(setState, true);
|
|
||||||
if (prefs!.getBool("checkUpdateOnSettingsOpen") ?? false) {
|
|
||||||
checkUpdate(setState);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -108,69 +162,25 @@ class _ScreenSettingsState extends State<ScreenSettings> {
|
||||||
hostInputController.dispose();
|
hostInputController.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget toggle(String text, bool value, Function(bool value) onChanged) {
|
|
||||||
var space = ""; // Invisible character: U+2063
|
|
||||||
var spacePlus = " $space";
|
|
||||||
return Stack(children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16, top: 12),
|
|
||||||
child: Divider(
|
|
||||||
color: (Theme.of(context).brightness == Brightness.light)
|
|
||||||
? Colors.grey[300]
|
|
||||||
: Colors.grey[900])),
|
|
||||||
Row(mainAxisSize: MainAxisSize.max, children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(text + spacePlus,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
maxLines: 1,
|
|
||||||
style: TextStyle(
|
|
||||||
backgroundColor:
|
|
||||||
(Theme.of(context).brightness == Brightness.light)
|
|
||||||
? (theme ?? ThemeData()).colorScheme.surface
|
|
||||||
: (themeDark ?? ThemeData.dark())
|
|
||||||
.colorScheme
|
|
||||||
.surface))),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.only(left: 16),
|
|
||||||
color: (Theme.of(context).brightness == Brightness.light)
|
|
||||||
? (theme ?? ThemeData()).colorScheme.surface
|
|
||||||
: (themeDark ?? ThemeData.dark()).colorScheme.surface,
|
|
||||||
child: SizedBox(
|
|
||||||
height: 40, child: Switch(value: value, onChanged: onChanged)))
|
|
||||||
]),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget title(String text, {double top = 16, double bottom = 16}) {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(left: 8, right: 8, top: top, bottom: bottom),
|
|
||||||
child: Row(children: [
|
|
||||||
const Expanded(child: Divider()),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
|
||||||
child: Text(text)),
|
|
||||||
const Expanded(child: Divider())
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: !hostLoading,
|
canPop: !hostLoading,
|
||||||
onPopInvoked: (didPop) {
|
onPopInvoked: (didPop) {
|
||||||
settingsOpen = false;
|
settingsOpen = false;
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
},
|
},
|
||||||
child: WindowBorder(
|
child: WindowBorder(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Row(children: [
|
title: Row(children: [
|
||||||
Text(AppLocalizations.of(context)!.optionSettings),
|
Text(AppLocalizations.of(context)!.optionSettings),
|
||||||
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
|
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
|
||||||
]),
|
]),
|
||||||
actions:
|
actions: (Platform.isWindows ||
|
||||||
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)
|
Platform.isLinux ||
|
||||||
|
Platform.isMacOS)
|
||||||
? [
|
? [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 200,
|
height: 200,
|
||||||
|
@ -205,524 +215,192 @@ class _ScreenSettingsState extends State<ScreenSettings> {
|
||||||
)))
|
)))
|
||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||||
child: ListView(children: [
|
child: Column(children: [
|
||||||
const SizedBox(height: 16),
|
Expanded(
|
||||||
const SizedBox(height: 8),
|
child: ListView(children: [
|
||||||
TextField(
|
const SizedBox(height: 16),
|
||||||
controller: hostInputController,
|
TextField(
|
||||||
keyboardType: TextInputType.url,
|
controller: hostInputController,
|
||||||
readOnly: useHost,
|
keyboardType: TextInputType.url,
|
||||||
onSubmitted: (value) {
|
readOnly: useHost,
|
||||||
HapticFeedback.selectionClick();
|
onSubmitted: (value) {
|
||||||
checkHost();
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context)!.settingsHost,
|
|
||||||
hintText: "http://localhost:11434",
|
|
||||||
prefixIcon: IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
String tmp = await prompt(context,
|
checkHost();
|
||||||
placeholder:
|
|
||||||
"{\"Authorization\": \"Bearer ...\"}",
|
|
||||||
title: AppLocalizations.of(context)!
|
|
||||||
.settingsHostHeaderTitle,
|
|
||||||
value:
|
|
||||||
(prefs!.getString("hostHeaders") ?? ""),
|
|
||||||
valueIfCanceled: "{}",
|
|
||||||
validator: (content) async {
|
|
||||||
try {
|
|
||||||
var tmp = jsonDecode(content);
|
|
||||||
tmp as Map<String, dynamic>;
|
|
||||||
return true;
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
validatorError:
|
|
||||||
AppLocalizations.of(context)!
|
|
||||||
.settingsHostHeaderInvalid);
|
|
||||||
prefs!.setString("hostHeaders", tmp);
|
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.add_rounded)),
|
decoration: InputDecoration(
|
||||||
suffixIcon: useHost
|
labelText: AppLocalizations.of(context)!
|
||||||
? const SizedBox.shrink()
|
.settingsHost,
|
||||||
: (hostLoading
|
hintText: "http://localhost:11434",
|
||||||
? Transform.scale(
|
prefixIcon: IconButton(
|
||||||
scale: 0.5,
|
onPressed: () async {
|
||||||
child: const CircularProgressIndicator())
|
|
||||||
: IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
checkHost();
|
String tmp = await prompt(context,
|
||||||
|
placeholder:
|
||||||
|
"{\"Authorization\": \"Bearer ...\"}",
|
||||||
|
title: AppLocalizations.of(context)!
|
||||||
|
.settingsHostHeaderTitle,
|
||||||
|
value: (prefs!
|
||||||
|
.getString("hostHeaders") ??
|
||||||
|
""),
|
||||||
|
valueIfCanceled: "{}",
|
||||||
|
validator: (content) async {
|
||||||
|
try {
|
||||||
|
var tmp = jsonDecode(content);
|
||||||
|
tmp as Map<String, dynamic>;
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validatorError:
|
||||||
|
AppLocalizations.of(context)!
|
||||||
|
.settingsHostHeaderInvalid);
|
||||||
|
prefs!.setString("hostHeaders", tmp);
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.save_rounded),
|
icon: const Icon(Icons.add_rounded)),
|
||||||
)),
|
suffixIcon: useHost
|
||||||
border: const OutlineInputBorder(),
|
? const SizedBox.shrink()
|
||||||
error: (hostInvalidHost || hostInvalidUrl)
|
: (hostLoading
|
||||||
? InkWell(
|
? Transform.scale(
|
||||||
onTap: () {
|
scale: 0.5,
|
||||||
HapticFeedback.selectionClick();
|
child:
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
const CircularProgressIndicator())
|
||||||
SnackBar(
|
: IconButton(
|
||||||
content: Text(AppLocalizations.of(
|
|
||||||
context)!
|
|
||||||
.settingsHostInvalidDetailed(
|
|
||||||
hostInvalidHost
|
|
||||||
? "host"
|
|
||||||
: "url")),
|
|
||||||
showCloseIcon: true));
|
|
||||||
},
|
|
||||||
highlightColor: Colors.transparent,
|
|
||||||
splashFactory: NoSplash.splashFactory,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.error_rounded,
|
|
||||||
color: Colors.red),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!
|
|
||||||
.settingsHostInvalid(
|
|
||||||
hostInvalidHost
|
|
||||||
? "host"
|
|
||||||
: "url"),
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.red))
|
|
||||||
],
|
|
||||||
))
|
|
||||||
: null,
|
|
||||||
helper: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
},
|
|
||||||
highlightColor: Colors.transparent,
|
|
||||||
splashFactory: NoSplash.splashFactory,
|
|
||||||
child: hostLoading
|
|
||||||
? Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.search_rounded,
|
|
||||||
color: Colors.grey),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!
|
|
||||||
.settingsHostChecking,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.grey,
|
|
||||||
fontFamily: "monospace"))
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.check_rounded,
|
|
||||||
color: Colors.green),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context)!
|
|
||||||
.settingsHostValid,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.green,
|
|
||||||
fontFamily: "monospace"))
|
|
||||||
],
|
|
||||||
)))),
|
|
||||||
title(AppLocalizations.of(context)!.settingsTitleBehavior,
|
|
||||||
bottom: 24),
|
|
||||||
TextField(
|
|
||||||
controller: systemInputController,
|
|
||||||
keyboardType: TextInputType.multiline,
|
|
||||||
maxLines: 2,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: AppLocalizations.of(context)!
|
|
||||||
.settingsSystemMessage,
|
|
||||||
hintText: "You are a helpful assistant",
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
prefs?.setString(
|
|
||||||
"system",
|
|
||||||
(systemInputController.text.isNotEmpty)
|
|
||||||
? systemInputController.text
|
|
||||||
: "You are a helpful assistant");
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.save_rounded),
|
|
||||||
),
|
|
||||||
border: const OutlineInputBorder())),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
toggle(AppLocalizations.of(context)!.settingsDisableMarkdown,
|
|
||||||
(prefs!.getBool("noMarkdown") ?? false), (value) {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
prefs!.setBool("noMarkdown", value);
|
|
||||||
setState(() {});
|
|
||||||
}),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(children: [
|
|
||||||
const Icon(Icons.warning_rounded, color: Colors.grey),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!
|
|
||||||
.settingsBehaviorNotUpdatedForOlderChats,
|
|
||||||
style: const TextStyle(color: Colors.grey)))
|
|
||||||
]),
|
|
||||||
title(AppLocalizations.of(context)!.settingsTitleInterface),
|
|
||||||
SegmentedButton(
|
|
||||||
segments: const [
|
|
||||||
ButtonSegment(
|
|
||||||
value: "stream",
|
|
||||||
label: Text("Stream"),
|
|
||||||
icon: Icon(Icons.stream_rounded)),
|
|
||||||
ButtonSegment(
|
|
||||||
value: "request",
|
|
||||||
label: Text("Request"),
|
|
||||||
icon: Icon(Icons.send_rounded))
|
|
||||||
],
|
|
||||||
selected: {
|
|
||||||
prefs!.getString("requestType") ?? "stream"
|
|
||||||
},
|
|
||||||
onSelectionChanged: (p0) {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
setState(() {
|
|
||||||
prefs!.setString("requestType", p0.elementAt(0));
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
toggle(AppLocalizations.of(context)!.settingsGenerateTitles,
|
|
||||||
(prefs!.getBool("generateTitles") ?? true), (value) {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
prefs!.setBool("generateTitles", value);
|
|
||||||
setState(() {});
|
|
||||||
}),
|
|
||||||
toggle(AppLocalizations.of(context)!.settingsAskBeforeDelete,
|
|
||||||
(prefs!.getBool("askBeforeDeletion") ?? false), (value) {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
prefs!.setBool("askBeforeDeletion", value);
|
|
||||||
setState(() {});
|
|
||||||
}),
|
|
||||||
toggle(
|
|
||||||
AppLocalizations.of(context)!.settingsResetOnModelChange,
|
|
||||||
(prefs!.getBool("resetOnModelSelect") ?? true), (value) {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
prefs!.setBool("resetOnModelSelect", value);
|
|
||||||
setState(() {});
|
|
||||||
}),
|
|
||||||
toggle(AppLocalizations.of(context)!.settingsEnableEditing,
|
|
||||||
(prefs!.getBool("enableEditing") ?? false), (value) {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
prefs!.setBool("enableEditing", value);
|
|
||||||
setState(() {});
|
|
||||||
}),
|
|
||||||
toggle(AppLocalizations.of(context)!.settingsShowTips,
|
|
||||||
(prefs!.getBool("tips") ?? true), (value) {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
prefs!.setBool("tips", value);
|
|
||||||
setState(() {});
|
|
||||||
}),
|
|
||||||
toggle(AppLocalizations.of(context)!.settingsShowModelTags,
|
|
||||||
(prefs!.getBool("modelTags") ?? false), (value) {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
prefs!.setBool("modelTags", value);
|
|
||||||
setState(() {});
|
|
||||||
}),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
SegmentedButton(
|
|
||||||
segments: [
|
|
||||||
ButtonSegment(
|
|
||||||
value: "dark",
|
|
||||||
label: Text(AppLocalizations.of(context)!
|
|
||||||
.settingsBrightnessDark),
|
|
||||||
icon: const Icon(Icons.brightness_4_rounded)),
|
|
||||||
ButtonSegment(
|
|
||||||
value: "system",
|
|
||||||
label: Text(AppLocalizations.of(context)!
|
|
||||||
.settingsBrightnessSystem),
|
|
||||||
icon: const Icon(Icons.brightness_auto_rounded)),
|
|
||||||
ButtonSegment(
|
|
||||||
value: "light",
|
|
||||||
label: Text(AppLocalizations.of(context)!
|
|
||||||
.settingsBrightnessLight),
|
|
||||||
icon: const Icon(Icons.brightness_high_rounded))
|
|
||||||
],
|
|
||||||
selected: {
|
|
||||||
prefs!.getString("brightness") ?? "system"
|
|
||||||
},
|
|
||||||
onSelectionChanged: (p0) {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
var tmp = prefs!.getString("brightness") ?? "system";
|
|
||||||
prefs!.setString("brightness", p0.elementAt(0));
|
|
||||||
setState(() {});
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return StatefulBuilder(
|
|
||||||
builder: (context, setLocalState) {
|
|
||||||
return PopScope(
|
|
||||||
onPopInvoked: (didPop) {
|
|
||||||
prefs!.setString("brightness", tmp);
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
child: AlertDialog(
|
|
||||||
title: Text(AppLocalizations.of(
|
|
||||||
context)!
|
|
||||||
.settingsBrightnessRestartTitle),
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(AppLocalizations.of(context)!
|
|
||||||
.settingsBrightnessRestartDescription),
|
|
||||||
]),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
Navigator.of(context).pop();
|
checkHost();
|
||||||
},
|
},
|
||||||
child: Text(AppLocalizations.of(
|
icon: const Icon(
|
||||||
context)!
|
Icons.save_rounded),
|
||||||
.settingsBrightnessRestartCancel)),
|
)),
|
||||||
TextButton(
|
border: const OutlineInputBorder(),
|
||||||
onPressed: () async {
|
error: (hostInvalidHost || hostInvalidUrl)
|
||||||
HapticFeedback.selectionClick();
|
? InkWell(
|
||||||
await prefs!.setString(
|
onTap: () {
|
||||||
"brightness",
|
HapticFeedback.selectionClick();
|
||||||
p0.elementAt(0));
|
ScaffoldMessenger.of(context)
|
||||||
if (Platform.isWindows ||
|
.showSnackBar(SnackBar(
|
||||||
Platform.isLinux ||
|
content: Text(AppLocalizations
|
||||||
Platform.isMacOS) {
|
.of(context)!
|
||||||
exit(0);
|
.settingsHostInvalidDetailed(
|
||||||
} else {
|
hostInvalidHost
|
||||||
Restart.restartApp();
|
? "host"
|
||||||
}
|
: "url")),
|
||||||
},
|
showCloseIcon: true));
|
||||||
child: Text(AppLocalizations.of(
|
},
|
||||||
context)!
|
highlightColor: Colors.transparent,
|
||||||
.settingsBrightnessRestartRestart))
|
splashFactory: NoSplash.splashFactory,
|
||||||
]));
|
child: Row(
|
||||||
});
|
children: [
|
||||||
});
|
const Icon(Icons.error_rounded,
|
||||||
}),
|
color: Colors.red),
|
||||||
title(AppLocalizations.of(context)!.settingsTitleExport),
|
const SizedBox(width: 8),
|
||||||
InkWell(
|
Text(
|
||||||
onTap: () async {
|
AppLocalizations.of(context)!
|
||||||
var path = await FilePicker.platform.saveFile(
|
.settingsHostInvalid(
|
||||||
type: FileType.custom,
|
hostInvalidHost
|
||||||
allowedExtensions: ["json"],
|
? "host"
|
||||||
fileName:
|
: "url"),
|
||||||
"ollama-export-${DateFormat('yyyy-MM-dd-H-m-s').format(DateTime.now())}.json",
|
style: const TextStyle(
|
||||||
bytes: utf8.encode(jsonEncode(
|
color: Colors.red))
|
||||||
prefs!.getStringList("chats") ?? [])));
|
],
|
||||||
if (path == null) return;
|
))
|
||||||
if (Platform.isWindows ||
|
: null,
|
||||||
Platform.isLinux ||
|
helper: InkWell(
|
||||||
Platform.isMacOS) {
|
onTap: () {
|
||||||
File(path).writeAsString(
|
HapticFeedback.selectionClick();
|
||||||
jsonEncode(prefs!.getStringList("chats") ?? []));
|
},
|
||||||
}
|
highlightColor: Colors.transparent,
|
||||||
},
|
splashFactory: NoSplash.splashFactory,
|
||||||
child: Row(children: [
|
child: hostLoading
|
||||||
const Icon(Icons.upload_rounded),
|
? Row(
|
||||||
const SizedBox(width: 16, height: 42),
|
children: [
|
||||||
Expanded(
|
const Icon(Icons.search_rounded,
|
||||||
child: Text(AppLocalizations.of(context)!
|
color: Colors.grey),
|
||||||
.settingsExportChats))
|
const SizedBox(width: 8),
|
||||||
])),
|
Text(
|
||||||
InkWell(
|
AppLocalizations.of(
|
||||||
onTap: () {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text(AppLocalizations.of(context)!
|
|
||||||
.settingsImportChatsTitle),
|
|
||||||
content: Text(AppLocalizations.of(context)!
|
|
||||||
.settingsImportChatsDescription),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!
|
|
||||||
.settingsImportChatsCancel)),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
HapticFeedback.selectionClick();
|
|
||||||
FilePickerResult? result =
|
|
||||||
await FilePicker.platform
|
|
||||||
.pickFiles(
|
|
||||||
type: FileType.custom,
|
|
||||||
allowedExtensions: [
|
|
||||||
"json"
|
|
||||||
]);
|
|
||||||
if (result == null) {
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File file =
|
|
||||||
File(result.files.single.path!);
|
|
||||||
var content =
|
|
||||||
await file.readAsString();
|
|
||||||
List<dynamic> tmpHistory =
|
|
||||||
jsonDecode(content);
|
|
||||||
List<String> history = [];
|
|
||||||
|
|
||||||
for (var i = 0;
|
|
||||||
i < tmpHistory.length;
|
|
||||||
i++) {
|
|
||||||
history.add(tmpHistory[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
prefs!
|
|
||||||
.setStringList("chats", history);
|
|
||||||
|
|
||||||
messages = [];
|
|
||||||
chatUuid = null;
|
|
||||||
|
|
||||||
setState(() {});
|
|
||||||
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
ScaffoldMessenger.of(context)
|
|
||||||
.showSnackBar(SnackBar(
|
|
||||||
content: Text(AppLocalizations
|
|
||||||
// ignore: use_build_context_synchronously
|
|
||||||
.of(context)!
|
|
||||||
.settingsImportChatsSuccess),
|
|
||||||
showCloseIcon: true));
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
AppLocalizations.of(context)!
|
|
||||||
.settingsImportChatsImport))
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Row(children: [
|
|
||||||
const Icon(Icons.download_rounded),
|
|
||||||
const SizedBox(width: 16, height: 42),
|
|
||||||
Expanded(
|
|
||||||
child: Text(AppLocalizations.of(context)!
|
|
||||||
.settingsImportChats))
|
|
||||||
])),
|
|
||||||
title(AppLocalizations.of(context)!.settingsTitleContact),
|
|
||||||
(updateStatus == "notAvailable")
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
if (updateLoading) return;
|
|
||||||
if ((Version.parse(latestVersion ?? "1.0.0") >
|
|
||||||
Version.parse(currentVersion ?? "2.0.0")) &&
|
|
||||||
(updateStatus == "ok")) {
|
|
||||||
updateDialog(context, title);
|
|
||||||
} else {
|
|
||||||
checkUpdate(setState);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Row(children: [
|
|
||||||
updateLoading
|
|
||||||
? SizedBox(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
child: Transform.scale(
|
|
||||||
scale: 0.5,
|
|
||||||
child:
|
|
||||||
const CircularProgressIndicator()),
|
|
||||||
)
|
|
||||||
: Icon((updateStatus != "ok")
|
|
||||||
? Icons.warning_rounded
|
|
||||||
: (Version.parse(latestVersion ?? "1.0.0") >
|
|
||||||
Version.parse(
|
|
||||||
currentVersion ?? "2.0.0"))
|
|
||||||
? Icons.info_outline_rounded
|
|
||||||
: Icons.update_rounded),
|
|
||||||
const SizedBox(width: 16, height: 42),
|
|
||||||
Expanded(
|
|
||||||
child: Text(!updateChecked
|
|
||||||
? AppLocalizations.of(context)!
|
|
||||||
.settingsUpdateCheck
|
|
||||||
: updateLoading
|
|
||||||
? AppLocalizations.of(context)!
|
|
||||||
.settingsUpdateChecking
|
|
||||||
: (updateStatus == "rateLimit")
|
|
||||||
? AppLocalizations.of(context)!
|
|
||||||
.settingsUpdateRateLimit
|
|
||||||
: (updateStatus != "ok")
|
|
||||||
? AppLocalizations.of(context)!
|
|
||||||
.settingsUpdateIssue
|
|
||||||
: (Version.parse(
|
|
||||||
latestVersion ??
|
|
||||||
"1.0.0") >
|
|
||||||
Version.parse(
|
|
||||||
currentVersion ??
|
|
||||||
"2.0.0"))
|
|
||||||
? AppLocalizations.of(
|
|
||||||
context)!
|
context)!
|
||||||
.settingsUpdateAvailable(
|
.settingsHostChecking,
|
||||||
latestVersion!)
|
style: const TextStyle(
|
||||||
: AppLocalizations.of(
|
color: Colors.grey,
|
||||||
|
fontFamily:
|
||||||
|
"monospace"))
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.check_rounded,
|
||||||
|
color: Colors.green),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(
|
||||||
context)!
|
context)!
|
||||||
.settingsUpdateLatest))
|
.settingsHostValid,
|
||||||
])),
|
style: const TextStyle(
|
||||||
(updateStatus == "notAvailable")
|
color: Colors.green,
|
||||||
? const SizedBox.shrink()
|
fontFamily:
|
||||||
: toggle(
|
"monospace"))
|
||||||
AppLocalizations.of(context)!.settingsCheckForUpdates,
|
],
|
||||||
(prefs!.getBool("checkUpdateOnSettingsOpen") ??
|
)))),
|
||||||
false), (value) {
|
const Padding(
|
||||||
HapticFeedback.selectionClick();
|
padding: EdgeInsets.only(
|
||||||
prefs!.setBool("checkUpdateOnSettingsOpen", value);
|
left: 8, right: 8, top: 16, bottom: 4),
|
||||||
setState(() {});
|
child: Divider()),
|
||||||
}),
|
button(
|
||||||
InkWell(
|
AppLocalizations.of(context)!
|
||||||
onTap: () {
|
.settingsTitleBehavior,
|
||||||
launchUrl(
|
Icons.psychology_rounded, () {
|
||||||
mode: LaunchMode.inAppBrowserView,
|
HapticFeedback.selectionClick();
|
||||||
Uri.parse(repoUrl));
|
Navigator.push(
|
||||||
},
|
context,
|
||||||
child: Row(children: [
|
MaterialPageRoute(
|
||||||
const Icon(SimpleIcons.github),
|
builder: (context) =>
|
||||||
const SizedBox(width: 16, height: 42),
|
const ScreenSettingsBehavior()));
|
||||||
Expanded(
|
}),
|
||||||
child: Text(
|
button(
|
||||||
AppLocalizations.of(context)!.settingsGithub))
|
AppLocalizations.of(context)!
|
||||||
])),
|
.settingsTitleInterface,
|
||||||
InkWell(
|
Icons.web_asset_rounded, () {
|
||||||
onTap: () {
|
HapticFeedback.selectionClick();
|
||||||
launchUrl(
|
Navigator.push(
|
||||||
mode: LaunchMode.inAppBrowserView,
|
context,
|
||||||
Uri.parse("$repoUrl/issues"));
|
MaterialPageRoute(
|
||||||
},
|
builder: (context) =>
|
||||||
child: Row(children: [
|
const ScreenSettingsInterface()));
|
||||||
const Icon(Icons.report_rounded),
|
}),
|
||||||
const SizedBox(width: 16, height: 42),
|
button(
|
||||||
Expanded(
|
AppLocalizations.of(context)!.settingsTitleExport,
|
||||||
child: Text(AppLocalizations.of(context)!
|
Icons.share_rounded, () {
|
||||||
.settingsReportIssue))
|
HapticFeedback.selectionClick();
|
||||||
])),
|
Navigator.push(
|
||||||
InkWell(
|
context,
|
||||||
onTap: () {
|
MaterialPageRoute(
|
||||||
launchUrl(
|
builder: (context) =>
|
||||||
mode: LaunchMode.inAppBrowserView,
|
const ScreenSettingsExport()));
|
||||||
Uri.parse(repoUrl.substring(
|
}),
|
||||||
0, repoUrl.lastIndexOf('/'))));
|
button(
|
||||||
},
|
AppLocalizations.of(context)!.settingsTitleAbout,
|
||||||
child: Row(children: [
|
Icons.help_rounded, () {
|
||||||
const Icon(Icons.developer_board_rounded),
|
HapticFeedback.selectionClick();
|
||||||
const SizedBox(width: 16, height: 42),
|
Navigator.push(
|
||||||
Expanded(
|
context,
|
||||||
child: Text(AppLocalizations.of(context)!
|
MaterialPageRoute(
|
||||||
.settingsMainDeveloper))
|
builder: (context) =>
|
||||||
])),
|
const ScreenSettingsAbout()));
|
||||||
const SizedBox(height: 16),
|
})
|
||||||
]))),
|
]),
|
||||||
),
|
),
|
||||||
);
|
const SizedBox(height: 16),
|
||||||
|
button(
|
||||||
|
AppLocalizations.of(context)!
|
||||||
|
.settingsSavedAutomatically,
|
||||||
|
Icons.info_rounded,
|
||||||
|
null,
|
||||||
|
color: Colors.grey)
|
||||||
|
])))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
|
import '../screen_settings.dart';
|
||||||
|
import '../worker_update.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
import 'package:simple_icons/simple_icons.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:version/version.dart';
|
||||||
|
|
||||||
|
class ScreenSettingsAbout extends StatefulWidget {
|
||||||
|
const ScreenSettingsAbout({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ScreenSettingsAbout> createState() => _ScreenSettingsAboutState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScreenSettingsAboutState extends State<ScreenSettingsAbout> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
updatesSupported(setState, true);
|
||||||
|
if (prefs!.getBool("checkUpdateOnSettingsOpen") ?? false) {
|
||||||
|
checkUpdate(setState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WindowBorder(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Row(children: [
|
||||||
|
Text(AppLocalizations.of(context)!.settingsTitleAbout),
|
||||||
|
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
|
||||||
|
]),
|
||||||
|
actions:
|
||||||
|
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)
|
||||||
|
? [
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: WindowTitleBarBox(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: MinimizeWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
SizedBox(
|
||||||
|
height: 72,
|
||||||
|
child: MaximizeWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
SizedBox(
|
||||||
|
height: 72,
|
||||||
|
child: CloseWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
],
|
||||||
|
)))
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||||
|
child: Column(children: [
|
||||||
|
Expanded(
|
||||||
|
child: ListView(children: [
|
||||||
|
// const SizedBox(height: 16),
|
||||||
|
(updateStatus == "notAvailable")
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: button(
|
||||||
|
(!updateChecked
|
||||||
|
? AppLocalizations.of(context)!
|
||||||
|
.settingsUpdateCheck
|
||||||
|
: updateLoading
|
||||||
|
? AppLocalizations.of(context)!
|
||||||
|
.settingsUpdateChecking
|
||||||
|
: (updateStatus == "rateLimit")
|
||||||
|
? AppLocalizations.of(context)!
|
||||||
|
.settingsUpdateRateLimit
|
||||||
|
: (updateStatus != "ok")
|
||||||
|
? AppLocalizations.of(context)!
|
||||||
|
.settingsUpdateIssue
|
||||||
|
: (Version.parse(latestVersion ??
|
||||||
|
"1.0.0") >
|
||||||
|
Version.parse(
|
||||||
|
currentVersion ??
|
||||||
|
"2.0.0"))
|
||||||
|
? AppLocalizations.of(context)!
|
||||||
|
.settingsUpdateAvailable(
|
||||||
|
latestVersion!)
|
||||||
|
: AppLocalizations.of(context)!
|
||||||
|
.settingsUpdateLatest),
|
||||||
|
((updateStatus != "ok")
|
||||||
|
? Icons.warning_rounded
|
||||||
|
: (Version.parse(latestVersion ?? "1.0.0") >
|
||||||
|
Version.parse(
|
||||||
|
currentVersion ?? "2.0.0"))
|
||||||
|
? Icons.info_outline_rounded
|
||||||
|
: Icons.update_rounded), () {
|
||||||
|
if (updateLoading) return;
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
if ((Version.parse(latestVersion ?? "1.0.0") >
|
||||||
|
Version.parse(currentVersion ?? "2.0.0")) &&
|
||||||
|
(updateStatus == "ok")) {
|
||||||
|
updateDialog(context, title);
|
||||||
|
} else {
|
||||||
|
checkUpdate(setState);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
(updateStatus == "notAvailable")
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: toggle(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context)!
|
||||||
|
.settingsCheckForUpdates,
|
||||||
|
(prefs!.getBool("checkUpdateOnSettingsOpen") ??
|
||||||
|
false), (value) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
prefs!.setBool("checkUpdateOnSettingsOpen", value);
|
||||||
|
setState(() {});
|
||||||
|
}),
|
||||||
|
button(AppLocalizations.of(context)!.settingsGithub,
|
||||||
|
SimpleIcons.github, () {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
launchUrl(
|
||||||
|
mode: LaunchMode.inAppBrowserView,
|
||||||
|
Uri.parse(repoUrl));
|
||||||
|
}),
|
||||||
|
button(AppLocalizations.of(context)!.settingsReportIssue,
|
||||||
|
Icons.report_rounded, () {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
launchUrl(
|
||||||
|
mode: LaunchMode.inAppBrowserView,
|
||||||
|
Uri.parse("$repoUrl/issues"));
|
||||||
|
}),
|
||||||
|
button(AppLocalizations.of(context)!.settingsMainDeveloper,
|
||||||
|
Icons.developer_board_rounded, () {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
launchUrl(
|
||||||
|
mode: LaunchMode.inAppBrowserView,
|
||||||
|
Uri.parse(
|
||||||
|
repoUrl.substring(0, repoUrl.lastIndexOf('/'))));
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16)
|
||||||
|
]))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
|
import '../screen_settings.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
|
||||||
|
class ScreenSettingsBehavior extends StatefulWidget {
|
||||||
|
const ScreenSettingsBehavior({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ScreenSettingsBehavior> createState() => _ScreenSettingsBehaviorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScreenSettingsBehaviorState extends State<ScreenSettingsBehavior> {
|
||||||
|
final systemInputController = TextEditingController(
|
||||||
|
text: prefs?.getString("system") ?? "You are a helpful assistant");
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
systemInputController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WindowBorder(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Row(children: [
|
||||||
|
Text(AppLocalizations.of(context)!.settingsTitleBehavior),
|
||||||
|
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
|
||||||
|
]),
|
||||||
|
actions:
|
||||||
|
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)
|
||||||
|
? [
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: WindowTitleBarBox(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: MinimizeWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
SizedBox(
|
||||||
|
height: 72,
|
||||||
|
child: MaximizeWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
SizedBox(
|
||||||
|
height: 72,
|
||||||
|
child: CloseWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
],
|
||||||
|
)))
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||||
|
child: Column(children: [
|
||||||
|
Expanded(
|
||||||
|
child: ListView(children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextField(
|
||||||
|
controller: systemInputController,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
maxLines: 2,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: AppLocalizations.of(context)!
|
||||||
|
.settingsSystemMessage,
|
||||||
|
hintText: "You are a helpful assistant",
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
prefs?.setString(
|
||||||
|
"system",
|
||||||
|
(systemInputController.text.isNotEmpty)
|
||||||
|
? systemInputController.text
|
||||||
|
: "You are a helpful assistant");
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.save_rounded),
|
||||||
|
),
|
||||||
|
border: const OutlineInputBorder())),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
toggle(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context)!.settingsDisableMarkdown,
|
||||||
|
(prefs!.getBool("noMarkdown") ?? false), (value) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
prefs!.setBool("noMarkdown", value);
|
||||||
|
setState(() {});
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
button(
|
||||||
|
AppLocalizations.of(context)!
|
||||||
|
.settingsBehaviorNotUpdatedForOlderChats,
|
||||||
|
Icons.info_rounded,
|
||||||
|
null,
|
||||||
|
color: Colors.grey)
|
||||||
|
]))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
|
import '../screen_settings.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class ScreenSettingsExport extends StatefulWidget {
|
||||||
|
const ScreenSettingsExport({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ScreenSettingsExport> createState() => _ScreenSettingsExportState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScreenSettingsExportState extends State<ScreenSettingsExport> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WindowBorder(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Row(children: [
|
||||||
|
Text(AppLocalizations.of(context)!.settingsTitleExport),
|
||||||
|
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
|
||||||
|
]),
|
||||||
|
actions:
|
||||||
|
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)
|
||||||
|
? [
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: WindowTitleBarBox(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: MinimizeWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
SizedBox(
|
||||||
|
height: 72,
|
||||||
|
child: MaximizeWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
SizedBox(
|
||||||
|
height: 72,
|
||||||
|
child: CloseWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
],
|
||||||
|
)))
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||||
|
child: Column(children: [
|
||||||
|
Expanded(
|
||||||
|
child: ListView(children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
button(AppLocalizations.of(context)!.settingsExportChats,
|
||||||
|
Icons.upload_rounded, () async {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
var path = await FilePicker.platform.saveFile(
|
||||||
|
type: FileType.custom,
|
||||||
|
allowedExtensions: ["json"],
|
||||||
|
fileName:
|
||||||
|
"ollama-export-${DateFormat('yyyy-MM-dd-H-m-s').format(DateTime.now())}.json",
|
||||||
|
bytes: utf8.encode(
|
||||||
|
jsonEncode(prefs!.getStringList("chats") ?? [])));
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
if (path == null) return;
|
||||||
|
if (Platform.isWindows ||
|
||||||
|
Platform.isLinux ||
|
||||||
|
Platform.isMacOS) {
|
||||||
|
File(path).writeAsString(
|
||||||
|
jsonEncode(prefs!.getStringList("chats") ?? []));
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
button(AppLocalizations.of(context)!.settingsImportChats,
|
||||||
|
Icons.download_rounded, () {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(AppLocalizations.of(context)!
|
||||||
|
.settingsImportChatsTitle),
|
||||||
|
content: Text(AppLocalizations.of(context)!
|
||||||
|
.settingsImportChatsDescription),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text(AppLocalizations.of(context)!
|
||||||
|
.settingsImportChatsCancel)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
FilePickerResult? result =
|
||||||
|
await FilePicker.platform.pickFiles(
|
||||||
|
type: FileType.custom,
|
||||||
|
allowedExtensions: ["json"]);
|
||||||
|
if (result == null) {
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file =
|
||||||
|
File(result.files.single.path!);
|
||||||
|
var content = await file.readAsString();
|
||||||
|
List<dynamic> tmpHistory =
|
||||||
|
jsonDecode(content);
|
||||||
|
List<String> history = [];
|
||||||
|
|
||||||
|
for (var i = 0;
|
||||||
|
i < tmpHistory.length;
|
||||||
|
i++) {
|
||||||
|
history.add(tmpHistory[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
prefs!.setStringList("chats", history);
|
||||||
|
|
||||||
|
messages = [];
|
||||||
|
chatUuid = null;
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
.showSnackBar(SnackBar(
|
||||||
|
content: Text(AppLocalizations
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
.of(context)!
|
||||||
|
.settingsImportChatsSuccess),
|
||||||
|
showCloseIcon: true));
|
||||||
|
},
|
||||||
|
child: Text(AppLocalizations.of(context)!
|
||||||
|
.settingsImportChatsImport))
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
button(AppLocalizations.of(context)!.settingsExportInfo,
|
||||||
|
Icons.info_rounded, null,
|
||||||
|
color: Colors.grey),
|
||||||
|
button(AppLocalizations.of(context)!.settingsExportWarning,
|
||||||
|
Icons.warning_rounded, null,
|
||||||
|
color: Colors.orange)
|
||||||
|
]))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,235 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
|
import '../screen_settings.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
import 'package:restart_app/restart_app.dart';
|
||||||
|
|
||||||
|
class ScreenSettingsInterface extends StatefulWidget {
|
||||||
|
const ScreenSettingsInterface({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ScreenSettingsInterface> createState() =>
|
||||||
|
_ScreenSettingsInterfaceState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScreenSettingsInterfaceState extends State<ScreenSettingsInterface> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WindowBorder(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Row(children: [
|
||||||
|
Text(AppLocalizations.of(context)!.settingsTitleInterface),
|
||||||
|
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
|
||||||
|
]),
|
||||||
|
actions:
|
||||||
|
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)
|
||||||
|
? [
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: WindowTitleBarBox(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: MinimizeWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
SizedBox(
|
||||||
|
height: 72,
|
||||||
|
child: MaximizeWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
SizedBox(
|
||||||
|
height: 72,
|
||||||
|
child: CloseWindowButton(
|
||||||
|
animate: true,
|
||||||
|
colors: WindowButtonColors(
|
||||||
|
iconNormal: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary))),
|
||||||
|
],
|
||||||
|
)))
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||||
|
child: Column(children: [
|
||||||
|
Expanded(
|
||||||
|
child: ListView(children: [
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SegmentedButton(
|
||||||
|
segments: [
|
||||||
|
ButtonSegment(
|
||||||
|
value: "dark",
|
||||||
|
label: Text(AppLocalizations.of(context)!
|
||||||
|
.settingsBrightnessDark),
|
||||||
|
icon: const Icon(Icons.brightness_4_rounded)),
|
||||||
|
ButtonSegment(
|
||||||
|
value: "system",
|
||||||
|
label: Text(AppLocalizations.of(context)!
|
||||||
|
.settingsBrightnessSystem),
|
||||||
|
icon: const Icon(Icons.brightness_auto_rounded)),
|
||||||
|
ButtonSegment(
|
||||||
|
value: "light",
|
||||||
|
label: Text(AppLocalizations.of(context)!
|
||||||
|
.settingsBrightnessLight),
|
||||||
|
icon: const Icon(Icons.brightness_high_rounded))
|
||||||
|
],
|
||||||
|
selected: {
|
||||||
|
prefs!.getString("brightness") ?? "system"
|
||||||
|
},
|
||||||
|
onSelectionChanged: (p0) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
var tmp = prefs!.getString("brightness") ?? "system";
|
||||||
|
prefs!.setString("brightness", p0.elementAt(0));
|
||||||
|
setState(() {});
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (context, setLocalState) {
|
||||||
|
return PopScope(
|
||||||
|
onPopInvoked: (didPop) {
|
||||||
|
prefs!.setString("brightness", tmp);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
child: AlertDialog(
|
||||||
|
title: Text(AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.settingsBrightnessRestartTitle),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.settingsBrightnessRestartDescription),
|
||||||
|
]),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
HapticFeedback
|
||||||
|
.selectionClick();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text(AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.settingsBrightnessRestartCancel)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
HapticFeedback
|
||||||
|
.selectionClick();
|
||||||
|
await prefs!.setString(
|
||||||
|
"brightness",
|
||||||
|
p0.elementAt(0));
|
||||||
|
if (Platform.isWindows ||
|
||||||
|
Platform.isLinux ||
|
||||||
|
Platform.isMacOS) {
|
||||||
|
exit(0);
|
||||||
|
} else {
|
||||||
|
Restart.restartApp();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(AppLocalizations.of(
|
||||||
|
context)!
|
||||||
|
.settingsBrightnessRestartRestart))
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
toggle(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context)!.settingsGenerateTitles,
|
||||||
|
(prefs!.getBool("generateTitles") ?? true), (value) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
prefs!.setBool("generateTitles", value);
|
||||||
|
setState(() {});
|
||||||
|
}),
|
||||||
|
toggle(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context)!.settingsAskBeforeDelete,
|
||||||
|
(prefs!.getBool("askBeforeDeletion") ?? false),
|
||||||
|
(value) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
prefs!.setBool("askBeforeDeletion", value);
|
||||||
|
setState(() {});
|
||||||
|
}),
|
||||||
|
toggle(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context)!
|
||||||
|
.settingsResetOnModelChange,
|
||||||
|
(prefs!.getBool("resetOnModelSelect") ?? true),
|
||||||
|
(value) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
prefs!.setBool("resetOnModelSelect", value);
|
||||||
|
setState(() {});
|
||||||
|
}),
|
||||||
|
toggle(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context)!.settingsEnableEditing,
|
||||||
|
(prefs!.getBool("enableEditing") ?? true), (value) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
prefs!.setBool("enableEditing", value);
|
||||||
|
setState(() {});
|
||||||
|
}),
|
||||||
|
toggle(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context)!.settingsShowTips,
|
||||||
|
(prefs!.getBool("tips") ?? true), (value) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
prefs!.setBool("tips", value);
|
||||||
|
setState(() {});
|
||||||
|
}),
|
||||||
|
toggle(
|
||||||
|
context,
|
||||||
|
AppLocalizations.of(context)!.settingsShowModelTags,
|
||||||
|
(prefs!.getBool("modelTags") ?? false), (value) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
prefs!.setBool("modelTags", value);
|
||||||
|
setState(() {});
|
||||||
|
}),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SegmentedButton(
|
||||||
|
segments: [
|
||||||
|
ButtonSegment(
|
||||||
|
value: "stream",
|
||||||
|
label: Text(AppLocalizations.of(context)!
|
||||||
|
.settingsRequestTypeStream),
|
||||||
|
icon: const Icon(Icons.stream_rounded)),
|
||||||
|
ButtonSegment(
|
||||||
|
value: "request",
|
||||||
|
label: Text(AppLocalizations.of(context)!
|
||||||
|
.settingsRequestTypeRequest),
|
||||||
|
icon: const Icon(Icons.send_rounded))
|
||||||
|
],
|
||||||
|
selected: {
|
||||||
|
prefs!.getString("requestType") ?? "stream"
|
||||||
|
},
|
||||||
|
onSelectionChanged: (p0) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
setState(() {
|
||||||
|
prefs!.setString("requestType", p0.elementAt(0));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16)
|
||||||
|
]))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -75,6 +75,8 @@ void main() async {
|
||||||
|
|
||||||
await copyFile('build\\app\\outputs\\flutter-apk\\app-release.apk',
|
await copyFile('build\\app\\outputs\\flutter-apk\\app-release.apk',
|
||||||
'build\\.output\\ollama.apk');
|
'build\\.output\\ollama.apk');
|
||||||
|
await copyFile('build\\app\\outputs\\flutter-apk\\app-release.apk.sha1',
|
||||||
|
'build\\.output\\ollama.apk.sha1');
|
||||||
await copyFile('build\\windows\\x64\\runner\\ollama-v$version-x64.exe',
|
await copyFile('build\\windows\\x64\\runner\\ollama-v$version-x64.exe',
|
||||||
'build\\.output\\ollama-v$version-x64.exe');
|
'build\\.output\\ollama-v$version-x64.exe');
|
||||||
print('- done');
|
print('- done');
|
||||||
|
|
Loading…
Reference in New Issue