Implement settings
Some checks failed
Build Docker and Deploy / Build Docker Container (push) Successful in 51s
Build Docker and Deploy / Run Unit-Tests (push) Failing after 11m17s
Build Docker and Deploy / Deploy to Server (push) Has been skipped

This commit is contained in:
2025-04-19 01:49:28 +02:00
parent 5417796f3f
commit b91ddc172d
33 changed files with 912 additions and 127 deletions

View File

@@ -0,0 +1,212 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/src/shared_preferences_legacy.dart';
import 'package:simplecloudnotifier/state/globals.dart';
enum AppSettingsDateFormat {
ISO(displayStr: 'ISO (yyyy-MM-dd)', key: 'ISO'),
German(displayStr: 'German (dd.MM.yyyy)', key: 'German'),
US(displayStr: 'US (MM/dd/yyyy)', key: 'US');
const AppSettingsDateFormat({required this.displayStr, required this.key});
final String displayStr;
final String key;
@override
toString() => displayStr;
DateFormat dateFormat() {
switch (this) {
case AppSettingsDateFormat.ISO:
return DateFormat('yyyy-MM-dd HH:mm');
case AppSettingsDateFormat.German:
return DateFormat('dd.MM.yyyy HH:mm');
case AppSettingsDateFormat.US:
return DateFormat('MM/dd/yyyy HH:mm');
}
}
static AppSettingsDateFormat? parse(String? string) {
if (string == null) return null;
return values.firstWhere((e) => e.key == string, orElse: null);
}
}
class AppSettings extends ChangeNotifier {
bool groupNotifications = true;
int messagePageSize = 128;
bool devMode = false;
bool showDebugButton = false;
bool backgroundRefreshMessageListOnPop = false;
bool alwaysBackgroundRefreshMessageListOnLifecycleResume = true;
AppSettingsDateFormat dateFormat = AppSettingsDateFormat.ISO;
int messagePreviewLength = 3;
AppNotificationSettings notification0 = AppNotificationSettings();
AppNotificationSettings notification1 = AppNotificationSettings();
AppNotificationSettings notification2 = AppNotificationSettings();
static AppSettings? _singleton = AppSettings._internal();
factory AppSettings() {
return _singleton ?? (_singleton = AppSettings._internal());
}
AppSettings._internal() {
load();
}
void reset() {
groupNotifications = true;
messagePageSize = 128;
devMode = false;
showDebugButton = false;
backgroundRefreshMessageListOnPop = false;
alwaysBackgroundRefreshMessageListOnLifecycleResume = true;
dateFormat = AppSettingsDateFormat.ISO;
messagePreviewLength = 3;
notification0 = AppNotificationSettings();
notification1 = AppNotificationSettings();
notification2 = AppNotificationSettings();
notifyListeners();
}
void load() {
groupNotifications = Globals().sharedPrefs.getBool('settings.groupNotifications') ?? groupNotifications;
messagePageSize = Globals().sharedPrefs.getInt('settings.messagePageSize') ?? messagePageSize;
devMode = Globals().sharedPrefs.getBool('settings.devMode') ?? devMode;
showDebugButton = Globals().sharedPrefs.getBool('settings.showDebugButton') ?? showDebugButton;
backgroundRefreshMessageListOnPop = Globals().sharedPrefs.getBool('settings.backgroundRefreshMessageListOnPop') ?? backgroundRefreshMessageListOnPop;
alwaysBackgroundRefreshMessageListOnLifecycleResume = Globals().sharedPrefs.getBool('settings.alwaysBackgroundRefreshMessageListOnLifecycleResume') ?? alwaysBackgroundRefreshMessageListOnLifecycleResume;
dateFormat = AppSettingsDateFormat.parse(Globals().sharedPrefs.getString('settings.dateFormat')) ?? dateFormat;
messagePreviewLength = Globals().sharedPrefs.getInt('settings.messagePreviewLength') ?? messagePreviewLength;
notification0 = AppNotificationSettings.load(Globals().sharedPrefs, 'settings.notification0');
notification1 = AppNotificationSettings.load(Globals().sharedPrefs, 'settings.notification1');
notification2 = AppNotificationSettings.load(Globals().sharedPrefs, 'settings.notification2');
}
Future<void> save() async {
await Globals().sharedPrefs.setBool('settings.groupNotifications', groupNotifications);
await Globals().sharedPrefs.setInt('settings.messagePageSize', messagePageSize);
await Globals().sharedPrefs.setBool('settings.devMode', devMode);
await Globals().sharedPrefs.setBool('settings.showDebugButton', showDebugButton);
await Globals().sharedPrefs.setBool('settings.backgroundRefreshMessageListOnPop', backgroundRefreshMessageListOnPop);
await Globals().sharedPrefs.setBool('settings.alwaysBackgroundRefreshMessageListOnLifecycleResume', alwaysBackgroundRefreshMessageListOnLifecycleResume);
await Globals().sharedPrefs.setString('settings.dateFormat', dateFormat.key);
await Globals().sharedPrefs.setInt('settings.messagePreviewLength', messagePreviewLength);
await notification0.save(Globals().sharedPrefs, 'settings.notification0');
await notification1.save(Globals().sharedPrefs, 'settings.notification1');
await notification2.save(Globals().sharedPrefs, 'settings.notification2');
}
void update(void Function(AppSettings p) fn) {
fn(this);
save();
notifyListeners();
}
void updateNotification(int prio, AppNotificationSettings Function(AppNotificationSettings p) fn) {
if (prio == 0) {
notification0 = fn(notification0);
} else if (prio == 1) {
notification1 = fn(notification1);
} else if (prio == 2) {
notification2 = fn(notification2);
}
save();
notifyListeners();
}
AppNotificationSettings getNotificationSettings(int? prio) {
if (prio != null && prio == 0) {
return notification0;
} else if (prio != null && prio == 1) {
return notification1;
} else if (prio != null && prio == 2) {
return notification2;
} else {
return AppNotificationSettings();
}
}
}
class AppNotificationSettings {
// Immutable
AppNotificationSettings({
this.enableLights = false,
this.enableVibration = true,
this.playSound = true,
this.sound = null,
this.silent = false,
this.timeoutAfter = null,
});
final bool enableLights;
final bool enableVibration;
final bool playSound;
final String? sound;
final bool silent;
final int? timeoutAfter;
Future<void> save(SharedPreferences sharedPrefs, String prefix) async {
await Globals().sharedPrefs.setBool('${prefix}.enableLights', enableLights);
await Globals().sharedPrefs.setBool('${prefix}.enableVibration', enableVibration);
await Globals().sharedPrefs.setBool('${prefix}.playSound', playSound);
await Globals().sharedPrefs.setString('${prefix}.sound', _encode(sound));
await Globals().sharedPrefs.setBool('${prefix}.silent', silent);
await Globals().sharedPrefs.setString('${prefix}.timeoutAfter', _encode(timeoutAfter));
}
UriAndroidNotificationSound? soundURI() {
return (sound != null) ? UriAndroidNotificationSound(sound!) : null;
}
AppNotificationSettings withEnableLights(bool v) => AppNotificationSettings(enableLights: v, enableVibration: enableVibration, playSound: playSound, sound: sound, silent: silent, timeoutAfter: timeoutAfter);
AppNotificationSettings withEnableVibration(bool v) => AppNotificationSettings(enableLights: enableLights, enableVibration: v, playSound: playSound, sound: sound, silent: silent, timeoutAfter: timeoutAfter);
AppNotificationSettings withPlaySound(bool v) => AppNotificationSettings(enableLights: enableLights, enableVibration: enableVibration, playSound: v, sound: sound, silent: silent, timeoutAfter: timeoutAfter);
AppNotificationSettings withSound(String? v) => AppNotificationSettings(enableLights: enableLights, enableVibration: enableVibration, playSound: playSound, sound: v, silent: silent, timeoutAfter: timeoutAfter);
AppNotificationSettings withSilent(bool v) => AppNotificationSettings(enableLights: enableLights, enableVibration: enableVibration, playSound: playSound, sound: sound, silent: v, timeoutAfter: timeoutAfter);
AppNotificationSettings withTimeoutAfter(int? v) => AppNotificationSettings(enableLights: enableLights, enableVibration: enableVibration, playSound: playSound, sound: sound, silent: silent, timeoutAfter: v);
static AppNotificationSettings load(SharedPreferences prefs, String prefix) {
final def = AppNotificationSettings();
final enableLights = prefs.getBool('${prefix}.enableLights') ?? def.enableLights;
final enableVibration = prefs.getBool('${prefix}.enableVibration') ?? def.enableVibration;
final playSound = prefs.getBool('${prefix}.playSound') ?? def.playSound;
final sound = _decode(prefs.getString('${prefix}.sound'), def.sound);
final silent = prefs.getBool('${prefix}.silent') ?? def.silent;
final timeoutAfter = _decode(prefs.getString('${prefix}.timeoutAfter'), def.timeoutAfter);
return AppNotificationSettings(
enableLights: enableLights,
enableVibration: enableVibration,
playSound: playSound,
sound: sound,
silent: silent,
timeoutAfter: timeoutAfter,
);
}
}
String _encode<T>(T v) {
return JsonEncoder().convert(v);
}
T _decode<T>(String? v, T fallback) {
if (v == null) return fallback;
try {
return JsonDecoder().convert(v) as T;
} catch (_) {
return fallback;
}
}

View File

@@ -1,16 +1,87 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:simplecloudnotifier/state/globals.dart';
enum ThemeColor {
Pink(displayStr: 'Pink', key: 'PINK', value: Colors.pink),
Red(displayStr: 'Red', key: 'RED', value: Colors.red),
DeepOrange(displayStr: 'Deep-Orange', key: 'DEEPORANGE', value: Colors.deepOrange),
Orange(displayStr: 'Orange', key: 'ORANGE', value: Colors.orange),
Amber(displayStr: 'Amber', key: 'AMBER', value: Colors.amber),
Yellow(displayStr: 'Yellow', key: 'YELLOW', value: Colors.yellow),
Lime(displayStr: 'Lime', key: 'LIME', value: Colors.lime),
LightGreen(displayStr: 'Light-Green', key: 'LIGHTGREEN', value: Colors.lightGreen),
Green(displayStr: 'Green', key: 'GREEN', value: Colors.green),
Teal(displayStr: 'Teal', key: 'TEAL', value: Colors.teal),
Cyan(displayStr: 'Cyan', key: 'CYAN', value: Colors.cyan),
LightBlue(displayStr: 'Light-Blue', key: 'LIGHTBLUE', value: Colors.lightBlue),
Blue(displayStr: 'Blue', key: 'BLUE', value: Colors.blue),
Indigo(displayStr: 'Indigo', key: 'INDIGO', value: Colors.indigo),
Purple(displayStr: 'Purple', key: 'PURPLE', value: Colors.purple),
DeepPurple(displayStr: 'Deep-Purple', key: 'DEEPPURPLE', value: Colors.deepPurple),
BlueGrey(displayStr: 'Blue-Grey', key: 'BLUEGREY', value: Colors.blueGrey),
Brown(displayStr: 'Brown', key: 'BROWN', value: Colors.brown),
Grey(displayStr: 'Grey', key: 'GREY', value: Colors.grey);
const ThemeColor({required this.displayStr, required this.key, required this.value});
final String displayStr;
final String key;
final Color value;
@override
toString() => displayStr;
static ThemeColor? parse(String? string) {
if (string == null) return null;
return values.firstWhere((e) => e.key == string, orElse: null);
}
}
class AppTheme extends ChangeNotifier {
static AppTheme? _singleton = AppTheme._internal();
factory AppTheme() {
return _singleton ?? (_singleton = AppTheme._internal());
}
AppTheme._internal() {}
// --------------------------------------------------------------------------
bool _darkmode = false;
bool get darkMode => _darkmode;
ThemeColor _color = ThemeColor.Blue;
ThemeColor get color => _color;
void setDarkMode(bool v) {
_darkmode = v;
notifyListeners();
save();
}
void switchDarkMode() {
_darkmode = !_darkmode;
notifyListeners();
save();
}
void setColor(ThemeColor v) {
_color = v;
notifyListeners();
save();
}
// --------------------------------------------------------------------------
void load() {
_darkmode = Globals().sharedPrefs.getBool('theme.dark') ?? _darkmode;
_color = ThemeColor.parse(Globals().sharedPrefs.getString('theme.color')) ?? _color;
}
Future<void> save() async {
await Globals().sharedPrefs.setBool('theme.dark', _darkmode);
await Globals().sharedPrefs.setString('theme.color', _color.key);
}
}

View File

@@ -3,7 +3,7 @@ import 'package:simplecloudnotifier/api/api_client.dart';
import 'package:simplecloudnotifier/models/channel.dart';
import 'package:simplecloudnotifier/models/keytoken.dart';
import 'package:simplecloudnotifier/models/scn_message.dart';
import 'package:simplecloudnotifier/settings/app_settings.dart';
import 'package:simplecloudnotifier/state/app_settings.dart';
class SCNDataCache {
SCNDataCache._internal();