Grouped settings UI

This commit is contained in:
JHubi1 2024-06-08 17:39:28 +02:00
parent 3663fdfcc1
commit a34e7286ee
No known key found for this signature in database
GPG Key ID: 7BF82570CBBBD050
9 changed files with 1095 additions and 658 deletions

View File

@ -150,9 +150,14 @@
"description": "Title of the export settings section",
"context": "Visible in the settings view"
},
"settingsTitleContact": "Kontakt",
"@settingsTitleContact": {
"description": "Title of the contact settings section",
"settingsTitleAbout": "Über",
"@settingsTitleAbout": {
"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"
},
"settingsHost": "Host",
@ -212,41 +217,11 @@
"description": "Text displayed as description for disable markdown toggle",
"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": {
"description": "Text displayed when behavior settings are not updated for older chats",
"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": {
"description": "Text displayed as description for system brightness option",
@ -282,6 +257,46 @@
"description": "Text displayed for cancel button, should be capitalized",
"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": {
"description": "Text displayed as description for export chats button",
@ -297,7 +312,7 @@
"description": "Title of the import dialog",
"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": {
"description": "Description of the import dialog",
"context": "Visible in the settings view"
@ -317,6 +332,16 @@
"description": "Text displayed when chats are imported successfully",
"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": {
"description": "Text displayed as description for check for updates button",
@ -378,7 +403,7 @@
"description": "Text displayed for cancel button, should be capitalized",
"context": "Visible in the settings view"
},
"settingsCheckForUpdates": "Beim Einstellungs-Öffnen Updates suchen",
"settingsCheckForUpdates": "Nach Updates suchen",
"@settingsCheckForUpdates": {
"description": "Text displayed as description for check for updates toggle",
"context": "Visible in the settings view"

View File

@ -150,9 +150,14 @@
"description": "Title of the export settings section",
"context": "Visible in the settings view"
},
"settingsTitleContact": "Contact",
"@settingsTitleContact": {
"description": "Title of the contact settings section",
"settingsTitleAbout": "About",
"@settingsTitleAbout": {
"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"
},
"settingsHost": "Host",
@ -212,41 +217,11 @@
"description": "Text displayed as description for disable markdown toggle",
"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": {
"description": "Text displayed when behavior settings are not updated for older chats",
"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": {
"description": "Text displayed as description for system brightness option",
@ -282,6 +257,46 @@
"description": "Text displayed for cancel button, should be capitalized",
"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": {
"description": "Text displayed as description for export chats button",
@ -297,7 +312,7 @@
"description": "Title of the import dialog",
"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": {
"description": "Description of the import dialog",
"context": "Visible in the settings view"
@ -317,6 +332,16 @@
"description": "Text displayed when chats are imported successfully",
"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": {
"description": "Text displayed as description for check for updates button",
@ -378,7 +403,7 @@
"description": "Text displayed for cancel button, should be capitalized",
"context": "Visible in the settings view"
},
"settingsCheckForUpdates": "Check for update on settings open",
"settingsCheckForUpdates": "Check for updates",
"@settingsCheckForUpdates": {
"description": "Text displayed as description for check for updates toggle",
"context": "Visible in the settings view"

View File

@ -1359,7 +1359,7 @@ class _MainAppState extends State<MainApp> {
onMessageLongPress: (context, p1) async {
HapticFeedback.selectionClick();
if (!(prefs!.getBool("enableEditing") ?? false)) {
if (!(prefs!.getBool("enableEditing") ?? true)) {
return;
}

View File

@ -5,20 +5,82 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'main.dart';
import 'worker_update.dart';
import 'package:ollama_app/worker_setter.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: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';
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 {
const ScreenSettings({super.key});
@ -87,19 +149,11 @@ class _ScreenSettingsState extends State<ScreenSettings> {
HapticFeedback.selectionClick();
}
final systemInputController = TextEditingController(
text: prefs?.getString("system") ?? "You are a helpful assistant");
@override
void initState() {
super.initState();
WidgetsFlutterBinding.ensureInitialized();
checkHost();
updatesSupported(setState, true);
if (prefs!.getBool("checkUpdateOnSettingsOpen") ?? false) {
checkUpdate(setState);
}
}
@override
@ -108,69 +162,25 @@ class _ScreenSettingsState extends State<ScreenSettings> {
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
Widget build(BuildContext context) {
return PopScope(
canPop: !hostLoading,
onPopInvoked: (didPop) {
settingsOpen = false;
FocusManager.instance.primaryFocus?.unfocus();
},
child: WindowBorder(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: AppBar(
title: Row(children: [
Text(AppLocalizations.of(context)!.optionSettings),
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
]),
actions:
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)
canPop: !hostLoading,
onPopInvoked: (didPop) {
settingsOpen = false;
FocusManager.instance.primaryFocus?.unfocus();
},
child: WindowBorder(
color: Theme.of(context).colorScheme.surface,
child: Scaffold(
appBar: AppBar(
title: Row(children: [
Text(AppLocalizations.of(context)!.optionSettings),
Expanded(child: SizedBox(height: 200, child: MoveWindow()))
]),
actions: (Platform.isWindows ||
Platform.isLinux ||
Platform.isMacOS)
? [
SizedBox(
height: 200,
@ -205,524 +215,192 @@ class _ScreenSettingsState extends State<ScreenSettings> {
)))
]
: null,
),
body: Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: ListView(children: [
const SizedBox(height: 16),
const SizedBox(height: 8),
TextField(
controller: hostInputController,
keyboardType: TextInputType.url,
readOnly: useHost,
onSubmitted: (value) {
HapticFeedback.selectionClick();
checkHost();
},
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.settingsHost,
hintText: "http://localhost:11434",
prefixIcon: IconButton(
onPressed: () async {
),
body: Padding(
padding: const EdgeInsets.only(left: 16, right: 16),
child: Column(children: [
Expanded(
child: ListView(children: [
const SizedBox(height: 16),
TextField(
controller: hostInputController,
keyboardType: TextInputType.url,
readOnly: useHost,
onSubmitted: (value) {
HapticFeedback.selectionClick();
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);
checkHost();
},
icon: const Icon(Icons.add_rounded)),
suffixIcon: useHost
? const SizedBox.shrink()
: (hostLoading
? Transform.scale(
scale: 0.5,
child: const CircularProgressIndicator())
: IconButton(
onPressed: () {
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!
.settingsHost,
hintText: "http://localhost:11434",
prefixIcon: IconButton(
onPressed: () async {
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),
)),
border: const OutlineInputBorder(),
error: (hostInvalidHost || hostInvalidUrl)
? InkWell(
onTap: () {
HapticFeedback.selectionClick();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
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(
icon: const Icon(Icons.add_rounded)),
suffixIcon: useHost
? const SizedBox.shrink()
: (hostLoading
? Transform.scale(
scale: 0.5,
child:
const CircularProgressIndicator())
: IconButton(
onPressed: () {
HapticFeedback.selectionClick();
Navigator.of(context).pop();
checkHost();
},
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))
]));
});
});
}),
title(AppLocalizations.of(context)!.settingsTitleExport),
InkWell(
onTap: () async {
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") ?? [])));
if (path == null) return;
if (Platform.isWindows ||
Platform.isLinux ||
Platform.isMacOS) {
File(path).writeAsString(
jsonEncode(prefs!.getStringList("chats") ?? []));
}
},
child: Row(children: [
const Icon(Icons.upload_rounded),
const SizedBox(width: 16, height: 42),
Expanded(
child: Text(AppLocalizations.of(context)!
.settingsExportChats))
])),
InkWell(
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(
icon: const Icon(
Icons.save_rounded),
)),
border: const OutlineInputBorder(),
error: (hostInvalidHost || hostInvalidUrl)
? InkWell(
onTap: () {
HapticFeedback.selectionClick();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(
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)!
.settingsUpdateAvailable(
latestVersion!)
: AppLocalizations.of(
.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)!
.settingsUpdateLatest))
])),
(updateStatus == "notAvailable")
? const SizedBox.shrink()
: toggle(
AppLocalizations.of(context)!.settingsCheckForUpdates,
(prefs!.getBool("checkUpdateOnSettingsOpen") ??
false), (value) {
HapticFeedback.selectionClick();
prefs!.setBool("checkUpdateOnSettingsOpen", value);
setState(() {});
}),
InkWell(
onTap: () {
launchUrl(
mode: LaunchMode.inAppBrowserView,
Uri.parse(repoUrl));
},
child: Row(children: [
const Icon(SimpleIcons.github),
const SizedBox(width: 16, height: 42),
Expanded(
child: Text(
AppLocalizations.of(context)!.settingsGithub))
])),
InkWell(
onTap: () {
launchUrl(
mode: LaunchMode.inAppBrowserView,
Uri.parse("$repoUrl/issues"));
},
child: Row(children: [
const Icon(Icons.report_rounded),
const SizedBox(width: 16, height: 42),
Expanded(
child: Text(AppLocalizations.of(context)!
.settingsReportIssue))
])),
InkWell(
onTap: () {
launchUrl(
mode: LaunchMode.inAppBrowserView,
Uri.parse(repoUrl.substring(
0, repoUrl.lastIndexOf('/'))));
},
child: Row(children: [
const Icon(Icons.developer_board_rounded),
const SizedBox(width: 16, height: 42),
Expanded(
child: Text(AppLocalizations.of(context)!
.settingsMainDeveloper))
])),
const SizedBox(height: 16),
]))),
),
);
.settingsHostValid,
style: const TextStyle(
color: Colors.green,
fontFamily:
"monospace"))
],
)))),
const Padding(
padding: EdgeInsets.only(
left: 8, right: 8, top: 16, bottom: 4),
child: Divider()),
button(
AppLocalizations.of(context)!
.settingsTitleBehavior,
Icons.psychology_rounded, () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const ScreenSettingsBehavior()));
}),
button(
AppLocalizations.of(context)!
.settingsTitleInterface,
Icons.web_asset_rounded, () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const ScreenSettingsInterface()));
}),
button(
AppLocalizations.of(context)!.settingsTitleExport,
Icons.share_rounded, () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const ScreenSettingsExport()));
}),
button(
AppLocalizations.of(context)!.settingsTitleAbout,
Icons.help_rounded, () {
HapticFeedback.selectionClick();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const ScreenSettingsAbout()));
})
]),
),
const SizedBox(height: 16),
button(
AppLocalizations.of(context)!
.settingsSavedAutomatically,
Icons.info_rounded,
null,
color: Colors.grey)
])))));
}
}

170
lib/settings/about.dart Normal file
View File

@ -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)
]))),
);
}
}

123
lib/settings/behavior.dart Normal file
View File

@ -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)
]))),
);
}
}

179
lib/settings/export.dart Normal file
View File

@ -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)
]))),
);
}
}

235
lib/settings/interface.dart Normal file
View File

@ -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)
]))),
);
}
}

View File

@ -75,6 +75,8 @@ void main() async {
await copyFile('build\\app\\outputs\\flutter-apk\\app-release.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',
'build\\.output\\ollama-v$version-x64.exe');
print('- done');