From 7cddcd29d62b74fa2b78a9e8be34fc2189d2f80c Mon Sep 17 00:00:00 2001 From: Armand <4831c0@proton.me> Date: Fri, 8 Aug 2025 22:37:12 +0200 Subject: [PATCH] settings system --- .../helpers/db/models/app_settings_model.dart | 1 + firka/lib/helpers/settings/setting.dart | 183 ++++++++++++++-- firka/lib/ui/phone/pages/extras/extras.dart | 16 ++ .../screens/settings/settings_screen.dart | 195 ++++++++++++++++++ 4 files changed, 379 insertions(+), 16 deletions(-) create mode 100644 firka/lib/ui/phone/screens/settings/settings_screen.dart diff --git a/firka/lib/helpers/db/models/app_settings_model.dart b/firka/lib/helpers/db/models/app_settings_model.dart index a7967f85..3802c1b6 100644 --- a/firka/lib/helpers/db/models/app_settings_model.dart +++ b/firka/lib/helpers/db/models/app_settings_model.dart @@ -7,6 +7,7 @@ class AppSettingsModel { Id? id; double? valueDouble; bool? valueBool; + int? valueIndex; String? valueString; AppSettingsModel(); diff --git a/firka/lib/helpers/settings/setting.dart b/firka/lib/helpers/settings/setting.dart index 0be8e8e7..a937fa34 100644 --- a/firka/lib/helpers/settings/setting.dart +++ b/firka/lib/helpers/settings/setting.dart @@ -5,21 +5,71 @@ import 'package:firka/ui/widget/firka_icon.dart'; import 'package:isar/isar.dart'; import 'package:majesticons_flutter/majesticons_flutter.dart'; +const bellRing = 1_001; +const rounding1 = 1_002; +const rounding2 = 1_003; +const rounding3 = 1_004; +const rounding4 = 1_005; +const classAvgOnGraph = 1_006; +const leftHandedMode = 1_007; +const language = 1_008; + class SettingsStore { LinkedHashMap items = LinkedHashMap.of({}); SettingsStore() { items["settings"] = SettingsGroup( - 0_001, + 0, LinkedHashMap.of({ - "application": SettingsSubGroup(1_001, FirkaIconType.Majesticons, - Majesticon.settingsCogSolid, "Alkalmazás", []), - "customization": SettingsSubGroup(2_001, FirkaIconType.Majesticons, - Majesticon.flower2Solid, "Személyre szabás", []), - "notifications": SettingsSubGroup(1_001, FirkaIconType.Majesticons, - Majesticon.bellSolid, "Értesítések", []), - "extras": SettingsSubGroup(1_001, FirkaIconType.Majesticons, - Majesticon.lightningBoltSolid, "Extrák", []), + "settings_header": SettingsHeader(0, "Beállítások"), + "settings_padding": SettingsPadding(0, 20), + "application": SettingsSubGroup( + 0, + FirkaIconType.Majesticons, + Majesticon.settingsCogSolid, + "Alkalmazás", + LinkedHashMap.of({ + // TODO: Make a back arrow widget + "settings_header": SettingsHeader(0, "Általános"), + "settings_padding": SettingsPadding(0, 23), + + "bell_delay": SettingsDouble( + bellRing, null, null, "Csengő eltolódása", 0), + "rounding_1": SettingsDouble(rounding1, null, null, + "Alapértelmezett kerekítés 1 -> 2", 0.5), + "rounding_2": SettingsDouble(rounding2, null, null, + "Alapértelmezett kerekítés 2 -> 3", 0.5), + "rounding_3": SettingsDouble(rounding3, null, null, + "Alapértelmezett kerekítés 3 -> 4", 0.5), + "rounding_4": SettingsDouble(rounding4, null, null, + "Alapértelmezett kerekítés 4 -> 5", 0.5), + "class_avg_on_graph": SettingsBoolean(classAvgOnGraph, null, + null, "Osztályátlag a grafikonon", true), + "navbar": SettingsSubGroup( + 0, + null, // TODO: icon + null, + "Navigációs sáv", + LinkedHashMap.of({}), + ), + "left_handed_mode": SettingsBoolean( + leftHandedMode, null, null, "Balkezes mód", false), + "language_header": SettingsHeaderSmall(0, "Nyelv"), + "language": SettingsItemsRadio(language, null, null, + ["Autómatikus", "Magyar", "Angol", "Német"], 0) + })), + "customization": SettingsSubGroup( + 0, + FirkaIconType.Majesticons, + Majesticon.flower2Solid, + "Személyre szabás", + LinkedHashMap.of({})), + "notifications": SettingsSubGroup(0, FirkaIconType.Majesticons, + Majesticon.bellSolid, "Értesítések", LinkedHashMap.of({})), + "extras": SettingsSubGroup(0, FirkaIconType.Majesticons, + Majesticon.lightningBoltSolid, "Extrák", LinkedHashMap.of({})), + "settings_other_padding": SettingsPadding(0, 20), + "settings_other_header": SettingsHeaderSmall(0, "Egyéb"), })); items; @@ -74,8 +124,10 @@ extension SettingExt on LinkedHashMap { class SettingsItem { Id key; + FirkaIconType? iconType; + Object? iconData; - SettingsItem(this.key); + SettingsItem(this.key, this.iconType, this.iconData); void save(IsarCollection model) {} @@ -85,6 +137,10 @@ class SettingsItem { class SettingsGroup implements SettingsItem { @override Id key; + @override + FirkaIconType? iconType; + @override + Object? iconData; LinkedHashMap children; SettingsGroup(this.key, this.children); @@ -107,32 +163,56 @@ class SettingsGroup implements SettingsItem { class SettingsSubGroup implements SettingsItem { @override Id key; + @override FirkaIconType? iconType; + @override Object? iconData; String title; - List children; + LinkedHashMap children; SettingsSubGroup( this.key, this.iconType, this.iconData, this.title, this.children); @override void load(IsarCollection model) { - for (var item in children) { + for (var item in children.values) { item.load(model); } } @override void save(IsarCollection model) { - for (var item in children) { + for (var item in children.values) { item.save(model); } } } +class SettingsPadding implements SettingsItem { + @override + Id key; + @override + FirkaIconType? iconType; + @override + Object? iconData; + double padding; + + SettingsPadding(this.key, this.padding); + + @override + void load(IsarCollection model) {} + + @override + void save(IsarCollection model) {} +} + class SettingsHeader implements SettingsItem { @override Id key; + @override + FirkaIconType? iconType; + @override + Object? iconData; String title; SettingsHeader(this.key, this.title); @@ -144,9 +224,31 @@ class SettingsHeader implements SettingsItem { void save(IsarCollection model) {} } +class SettingsHeaderSmall implements SettingsItem { + @override + Id key; + @override + FirkaIconType? iconType; + @override + Object? iconData; + String title; + + SettingsHeaderSmall(this.key, this.title); + + @override + void load(IsarCollection model) {} + + @override + void save(IsarCollection model) {} +} + class SettingsSubtitle implements SettingsItem { @override Id key; + @override + FirkaIconType? iconType; + @override + Object? iconData; String title; SettingsSubtitle(this.key, this.title); @@ -161,11 +263,16 @@ class SettingsSubtitle implements SettingsItem { class SettingsBoolean implements SettingsItem { @override Id key; + @override + FirkaIconType? iconType; + @override + Object? iconData; String title; bool value = false; bool defaultValue; - SettingsBoolean(this.key, this.title, this.defaultValue); + SettingsBoolean( + this.key, this.iconType, this.iconData, this.title, this.defaultValue); @override void load(IsarCollection model) { @@ -187,14 +294,53 @@ class SettingsBoolean implements SettingsItem { } } +class SettingsItemsRadio implements SettingsItem { + @override + Id key; + @override + FirkaIconType? iconType; + @override + Object? iconData; + List values; + int activeIndex = 0; + int defaultIndex; + + SettingsItemsRadio( + this.key, this.iconType, this.iconData, this.values, this.defaultIndex); + + @override + void load(IsarCollection model) { + var v = model.getSync(key); + if (v == null || v.valueIndex == null) { + activeIndex = v!.valueIndex!; + } else { + activeIndex = defaultIndex; + } + } + + @override + void save(IsarCollection model) { + var v = AppSettingsModel(); + v.id = key; + v.valueIndex = activeIndex; + + model.put(v); + } +} + class SettingsDouble implements SettingsItem { @override Id key; + @override + FirkaIconType? iconType; + @override + Object? iconData; String title; double value = 0; double defaultValue; - SettingsDouble(this.key, this.title, this.defaultValue); + SettingsDouble( + this.key, this.iconType, this.iconData, this.title, this.defaultValue); @override void load(IsarCollection model) { @@ -219,11 +365,16 @@ class SettingsDouble implements SettingsItem { class SettingsString implements SettingsItem { @override Id key; + @override + FirkaIconType? iconType; + @override + Object? iconData; String title; String value = ""; String defaultValue; - SettingsString(this.key, this.title, this.defaultValue); + SettingsString( + this.key, this.iconType, this.iconData, this.title, this.defaultValue); @override void load(IsarCollection model) { diff --git a/firka/lib/ui/phone/pages/extras/extras.dart b/firka/lib/ui/phone/pages/extras/extras.dart index 3a2c2af3..a0101577 100644 --- a/firka/lib/ui/phone/pages/extras/extras.dart +++ b/firka/lib/ui/phone/pages/extras/extras.dart @@ -1,6 +1,7 @@ import 'package:firka/helpers/ui/firka_card.dart'; import 'package:firka/main.dart'; import 'package:firka/ui/model/style.dart'; +import 'package:firka/ui/phone/screens/settings/settings_screen.dart'; import 'package:flutter/material.dart'; import '../../screens/debug/debug_screen.dart'; @@ -39,6 +40,7 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { children: [ GestureDetector( onTap: () => { + Navigator.pop(context), Navigator.push( context, MaterialPageRoute( @@ -48,6 +50,20 @@ void showExtrasBottomSheet(BuildContext context, AppInitialization data) { left: [Text('Debug screen')], right: [], ), + ), + GestureDetector( + onTap: () { + Navigator.pop(context); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + SettingsScreen(data, data.settings.items))); + }, + child: FirkaCard( + left: [Text('Settings')], + right: [], + ), ) ], ), diff --git a/firka/lib/ui/phone/screens/settings/settings_screen.dart b/firka/lib/ui/phone/screens/settings/settings_screen.dart new file mode 100644 index 00000000..cd95cd80 --- /dev/null +++ b/firka/lib/ui/phone/screens/settings/settings_screen.dart @@ -0,0 +1,195 @@ +import 'dart:collection'; + +import 'package:firka/helpers/db/models/app_settings_model.dart'; +import 'package:firka/helpers/ui/firka_card.dart'; +import 'package:firka/main.dart'; +import 'package:firka/ui/model/style.dart'; +import 'package:firka/ui/widget/firka_icon.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../../../../helpers/settings/setting.dart'; + +class SettingsScreen extends StatefulWidget { + final AppInitialization data; + final LinkedHashMap items; + + const SettingsScreen(this.data, this.items, {super.key}); + + @override + State createState() => _SettingsScreenState(data, items); +} + +class _SettingsScreenState extends State { + final AppInitialization data; + final LinkedHashMap items; + + _SettingsScreenState(this.data, this.items); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _updateSystemUI(); + }); + } + + void _updateSystemUI() { + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + statusBarBrightness: Brightness.light, + statusBarIconBrightness: Brightness.dark, + statusBarColor: Colors.transparent, + systemNavigationBarColor: appStyle.colors.background, + systemNavigationBarIconBrightness: Brightness.dark, + systemNavigationBarDividerColor: Colors.transparent, + )); + } + + List createWidgetTree(Iterable items) { + var widgets = List.empty(growable: true); + + for (var item in items) { + if (item is SettingsGroup) { + widgets.addAll(createWidgetTree(item.children.values)); + } + if (item is SettingsPadding) { + widgets.add(SizedBox( + width: item.padding, + height: item.padding, + )); + } + if (item is SettingsHeader) { + widgets.add(Text( + item.title, + style: appStyle.fonts.H_H1.apply(color: appStyle.colors.textPrimary), + )); + } + if (item is SettingsHeaderSmall) { + widgets.add(Text( + item.title, + style: + appStyle.fonts.H_14px.apply(color: appStyle.colors.textPrimary), + )); + } + if (item is SettingsSubGroup) { + List cardWidgets = []; + + if (item.iconType != null && item.iconData != null) { + cardWidgets.add(FirkaIconWidget( + item.iconType!, + item.iconData!, + color: appStyle.colors.accent, + )); + cardWidgets.add(SizedBox(width: 8)); + } + + cardWidgets.add(Text(item.title, + style: appStyle.fonts.B_14SB + .apply(color: appStyle.colors.textPrimary))); + + widgets.add(GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SettingsScreen(data, item.children))); + }, + child: FirkaCard(left: cardWidgets), + )); + } + + if (item is SettingsDouble) { + var v = item.value.toStringAsPrecision(2); + + widgets.add(FirkaCard(left: [ + Text(item.title, + style: appStyle.fonts.B_16SB + .apply(color: appStyle.colors.textPrimary)) + ], right: [ + Text(v == "0.0" ? "0" : v, + style: appStyle.fonts.B_14R + .apply(color: appStyle.colors.textPrimary)) + ])); + } + if (item is SettingsBoolean) { + widgets.add(FirkaCard( + left: [ + Text(item.title, + style: appStyle.fonts.B_16SB + .apply(color: appStyle.colors.textPrimary)) + ], + right: [ + Switch( + value: item.value, + activeColor: appStyle.colors.accent, + onChanged: (v) { + setState(() { + item.value = v; + }); + + data.isar.writeTxn(() async { + item.save(data.isar.appSettingsModels); + }); + }) + ], + )); + } + if (item is SettingsItemsRadio) { + for (var i = 0; i < item.values.length; i++) { + var k = item.values[i]; + + widgets.add(FirkaCard(left: [ + Text(k, + style: appStyle.fonts.B_16R + .apply(color: appStyle.colors.textPrimary)) + ], right: [ + Checkbox( + value: item.values[item.activeIndex] == k, + fillColor: WidgetStateProperty.resolveWith( + (Set states) { + return appStyle.colors.secondary; + }), + onChanged: (_) { + setState(() { + item.activeIndex = i; + }); + + data.isar.writeTxn(() async { + item.save(data.isar.appSettingsModels); + }); + }) + ])); + } + } + } + + return widgets; + } + + @override + Widget build(BuildContext context) { + _updateSystemUI(); // Update system UI on every build, to compensate for the android system being dumb + + var body = createWidgetTree(items.values); + + return Scaffold( + backgroundColor: appStyle.colors.background, + body: SafeArea( + child: SizedBox( + height: MediaQuery.of(context).size.height, + child: Stack( + children: [ + Padding( + padding: EdgeInsetsGeometry.all(20), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: body)), + ) + ], + ), + ), + ), + ); + } +}