Made Settings persistent with autosave

This commit is contained in:
Elias Müller 2023-06-03 23:26:18 +02:00
parent e26a1e9598
commit 3f05f68ac1
9 changed files with 253 additions and 156 deletions

View File

@ -21,6 +21,7 @@ class App extends StatefulWidget {
class _AppState extends State<App> { class _AppState extends State<App> {
int currentPage = 0; int currentPage = 0;
late Timer refetchChats;
@override @override
void initState() { void initState() {
@ -29,7 +30,8 @@ class _AppState extends State<App> {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Provider.of<ChatListProps>(context, listen: false).run(); Provider.of<ChatListProps>(context, listen: false).run();
}); });
Timer.periodic(const Duration(minutes: 1), (timer) { refetchChats = Timer.periodic(const Duration(minutes: 1), (timer) {
if(!context.mounted) return;
Provider.of<ChatListProps>(context, listen: false).run(); Provider.of<ChatListProps>(context, listen: false).run();
}); });
@ -101,4 +103,10 @@ class _AppState extends State<App> {
); );
} }
@override
void dispose() {
refetchChats.cancel();
super.dispose();
}
} }

View File

@ -9,12 +9,12 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'app.dart'; import 'app.dart';
import 'model/accountModel.dart'; import 'model/accountModel.dart';
import 'model/appTheme.dart';
import 'model/chatList/chatListProps.dart'; import 'model/chatList/chatListProps.dart';
import 'model/chatList/chatProps.dart'; import 'model/chatList/chatProps.dart';
import 'model/files/filesProps.dart'; import 'model/files/filesProps.dart';
import 'model/message/messageProps.dart'; import 'model/message/messageProps.dart';
import 'model/timetable/timetableProps.dart'; import 'model/timetable/timetableProps.dart';
import 'storage/settings/settingsProvider.dart';
import 'theming/darkAppTheme.dart'; import 'theming/darkAppTheme.dart';
import 'theming/lightAppTheme.dart'; import 'theming/lightAppTheme.dart';
import 'view/login/login.dart'; import 'view/login/login.dart';
@ -33,8 +33,9 @@ Future<void> main() async {
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [
ChangeNotifierProvider(create: (context) => SettingsProvider()),
ChangeNotifierProvider(create: (context) => AccountModel()), ChangeNotifierProvider(create: (context) => AccountModel()),
ChangeNotifierProvider(create: (context) => AppTheme()),
ChangeNotifierProvider(create: (context) => TimetableProps()), ChangeNotifierProvider(create: (context) => TimetableProps()),
ChangeNotifierProvider(create: (context) => ChatListProps()), ChangeNotifierProvider(create: (context) => ChatListProps()),
ChangeNotifierProvider(create: (context) => ChatProps()), ChangeNotifierProvider(create: (context) => ChatProps()),
@ -78,8 +79,8 @@ class _MainState extends State<Main> {
return Directionality( return Directionality(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
child: Consumer<AppTheme>( child: Consumer<SettingsProvider>(
builder: (context, value, child) { builder: (context, settings, child) {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
localizationsDelegates: const [ localizationsDelegates: const [
@ -95,7 +96,7 @@ class _MainState extends State<Main> {
title: 'Marianum Fulda', title: 'Marianum Fulda',
themeMode: value.getMode, themeMode: settings.val().appTheme,
theme: LightAppTheme.theme, theme: LightAppTheme.theme,
darkTheme: DarkAppTheme.theme, darkTheme: DarkAppTheme.theme,

View File

@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
import 'package:json_annotation/json_annotation.dart';
part 'settings.g.dart';
@JsonSerializable(explicitToJson: true)
class Settings {
@JsonKey(
toJson: _themeToJson,
fromJson: _themeFromJson,
)
ThemeMode appTheme;
bool devToolsEnabled;
Settings(this.appTheme, this.devToolsEnabled);
static String _themeToJson(ThemeMode m) => m.name;
static ThemeMode _themeFromJson(String m) => ThemeMode.values.firstWhere((element) => element.name == m);
factory Settings.fromJson(Map<String, dynamic> json) => _$SettingsFromJson(json);
Map<String, dynamic> toJson() => _$SettingsToJson(this);
}

View File

@ -0,0 +1,17 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'settings.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Settings _$SettingsFromJson(Map<String, dynamic> json) => Settings(
Settings._themeFromJson(json['appTheme'] as String),
json['devToolsEnabled'] as bool,
);
Map<String, dynamic> _$SettingsToJson(Settings instance) => <String, dynamic>{
'appTheme': Settings._themeToJson(instance.appTheme),
'devToolsEnabled': instance.devToolsEnabled,
};

View File

@ -0,0 +1,49 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'settings.dart';
class SettingsProvider extends ChangeNotifier {
static const String _fieldName = "settings";
late SharedPreferences _storage;
late Settings _settings = _defaults();
Settings val({bool write = false}) {
if(write) {
notifyListeners();
Future.delayed(const Duration(milliseconds: 300)).then((_) => update());
}
return _settings;
}
SettingsProvider() {
init();
}
void init() async {
_storage = await SharedPreferences.getInstance();
if(_storage.containsKey(_fieldName)) {
log("Settings from disk: ${_storage.getString(_fieldName)}");
_settings = Settings.fromJson(jsonDecode(_storage.getString(_fieldName)!));
} else {
_settings = _defaults();
}
notifyListeners();
}
void update() async {
await _storage.setString(_fieldName, jsonEncode(_settings.toJson()));
}
Settings _defaults() {
return Settings(
ThemeMode.system,
false,
);
}
}

View File

@ -1,14 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AppTheme extends ChangeNotifier { class AppTheme {
ThemeMode _mode = ThemeMode.system;
ThemeMode get getMode => _mode;
void setTheme(ThemeMode newMode) {
_mode = newMode;
notifyListeners();
}
static ThemeModeDisplay getDisplayOptions(ThemeMode theme) { static ThemeModeDisplay getDisplayOptions(ThemeMode theme) {
switch(theme) { switch(theme) {
case ThemeMode.system: case ThemeMode.system:

View File

@ -7,7 +7,7 @@ import 'package:jiffy/jiffy.dart';
import '../../../api/marianumcloud/talk/chat/getChatResponse.dart'; import '../../../api/marianumcloud/talk/chat/getChatResponse.dart';
import '../../../api/marianumcloud/talk/room/getRoomResponse.dart'; import '../../../api/marianumcloud/talk/room/getRoomResponse.dart';
import '../../../model/appTheme.dart'; import '../../../theming/appTheme.dart';
import '../../settings/debug/jsonViewer.dart'; import '../../settings/debug/jsonViewer.dart';
import '../files/fileElement.dart'; import '../files/fileElement.dart';
import 'chatMessage.dart'; import 'chatMessage.dart';

View File

@ -6,7 +6,7 @@ import 'package:provider/provider.dart';
import '../../../api/marianumcloud/talk/chat/getChatResponse.dart'; import '../../../api/marianumcloud/talk/chat/getChatResponse.dart';
import '../../../api/marianumcloud/talk/room/getRoomResponse.dart'; import '../../../api/marianumcloud/talk/room/getRoomResponse.dart';
import '../../../model/appTheme.dart'; import '../../../theming/appTheme.dart';
import '../../../model/chatList/chatProps.dart'; import '../../../model/chatList/chatProps.dart';
import 'chatBubble.dart'; import 'chatBubble.dart';
import 'chatTextfield.dart'; import 'chatTextfield.dart';

View File

@ -6,9 +6,11 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../model/accountModel.dart'; import '../../model/accountModel.dart';
import '../../model/appTheme.dart'; import '../../theming/appTheme.dart';
import '../../storage/settings/settingsProvider.dart';
import '../../widget/confirmDialog.dart'; import '../../widget/confirmDialog.dart';
import 'debug/debugOverview.dart'; import 'debug/debugOverview.dart';
import 'debug/jsonViewer.dart';
class Settings extends StatefulWidget { class Settings extends StatefulWidget {
const Settings({Key? key}) : super(key: key); const Settings({Key? key}) : super(key: key);
@ -29,6 +31,7 @@ class _SettingsState extends State<Settings> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<SettingsProvider>(builder: (context, settings, child) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("Einstellungen"), title: const Text("Einstellungen"),
@ -61,17 +64,15 @@ class _SettingsState extends State<Settings> {
const Divider(), const Divider(),
Consumer<AppTheme>( ListTile(
builder: (context, value, child) {
return ListTile(
leading: const Icon(Icons.dark_mode_outlined), leading: const Icon(Icons.dark_mode_outlined),
title: const Text("Farbgebung"), title: const Text("Farbgebung"),
trailing: DropdownButton<ThemeMode>( trailing: DropdownButton<ThemeMode>(
value: value.getMode, value: settings.val().appTheme,
icon: const Icon(Icons.arrow_drop_down), icon: const Icon(Icons.arrow_drop_down),
items: ThemeMode.values.map((e) => DropdownMenuItem<ThemeMode>( items: ThemeMode.values.map((e) => DropdownMenuItem<ThemeMode>(
value: e, value: e,
enabled: e != value.getMode, enabled: e != settings.val().appTheme,
child: Row( child: Row(
children: [ children: [
Icon(AppTheme.getDisplayOptions(e).icon), Icon(AppTheme.getDisplayOptions(e).icon),
@ -81,11 +82,11 @@ class _SettingsState extends State<Settings> {
), ),
)).toList(), )).toList(),
onChanged: (e) { onChanged: (e) {
Provider.of<AppTheme>(context, listen: false).setTheme(e ?? ThemeMode.system); setState(() {
settings.val(write: true).appTheme = e!;
});
}, },
), ),
);
},
), ),
const Divider(), const Divider(),
@ -93,10 +94,8 @@ class _SettingsState extends State<Settings> {
ListTile( ListTile(
leading: const Icon(Icons.live_help_outlined), leading: const Icon(Icons.live_help_outlined),
title: const Text("Informationen und Lizenzen"), title: const Text("Informationen und Lizenzen"),
onTap: () async { onTap: () {
final appInfo = await PackageInfo.fromPlatform(); PackageInfo.fromPlatform().then((appInfo) {
if(!context.mounted) return; // TODO Fix context used in async
showAboutDialog( showAboutDialog(
context: context, context: context,
applicationIcon: const Icon(Icons.apps), applicationIcon: const Icon(Icons.apps),
@ -107,6 +106,7 @@ class _SettingsState extends State<Settings> {
"Development build\n" "Development build\n"
"Marianum Fulda 2023 Elias Müller", "Marianum Fulda 2023 Elias Müller",
); );
});
}, },
trailing: const Icon(Icons.arrow_right), trailing: const Icon(Icons.arrow_right),
), ),
@ -136,18 +136,20 @@ class _SettingsState extends State<Settings> {
title: const Text("Entwicklermodus"), title: const Text("Entwicklermodus"),
trailing: Checkbox( trailing: Checkbox(
visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity), visualDensity: const VisualDensity(horizontal: VisualDensity.minimumDensity),
value: developerMode, value: settings.val().devToolsEnabled,
onChanged: (state) { onChanged: (state) {
setState(() { setState(() {
developerMode = !developerMode; settings.val(write: true).devToolsEnabled = state ?? false;
}); });
}, },
), ),
), ),
Visibility( Visibility(
visible: developerMode, visible: settings.val().devToolsEnabled,
child: ListTile( child: Column(
children: [
ListTile(
leading: const Icon(Icons.data_object), leading: const Icon(Icons.data_object),
title: const Text("Storage view"), title: const Text("Storage view"),
onTap: () { onTap: () {
@ -157,12 +159,8 @@ class _SettingsState extends State<Settings> {
}, },
trailing: const Icon(Icons.arrow_right), trailing: const Icon(Icons.arrow_right),
), ),
), ListTile(
leading: const Icon(Icons.logo_dev_outlined),
Visibility(
visible: developerMode && false, // TODO Implement verbose logging
child: ListTile(
leading: const Icon(Icons.logo_dev),
title: const Text("Logging verbosity"), title: const Text("Logging verbosity"),
trailing: DropdownButton<String>( trailing: DropdownButton<String>(
value: "1", value: "1",
@ -172,9 +170,19 @@ class _SettingsState extends State<Settings> {
}, },
), ),
), ),
ListTile(
leading: const Icon(Icons.settings_applications_outlined),
title: const Text("Settings JSON dump"),
onTap: () {
JsonViewer.asDialog(context, settings.val().toJson());
},
),
],
),
), ),
], ],
), ),
); );
});
} }
} }