From 1af8d6f0680afd132c10c61b61be259c7c66e26f Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Wed, 29 Mar 2023 18:02:00 +0200 Subject: [PATCH] Migrated from localstore to shared preferences (only for user, server and theme) This was done, because localstore is somewhat inconsistent in terms of events on different platforms. Also storing user, server and theme using shared-preferences should fit into flutters ecosystem a little better --- lib/backend/resolve_url.dart | 30 ++++-- lib/backend/themes.dart | 23 +--- lib/backend/user.dart | 34 +++--- lib/main.dart | 100 +++++++----------- lib/screens/auth.dart | 60 +++++++---- lib/screens/settings/main.dart | 4 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 56 ++++++++++ pubspec.yaml | 1 + 9 files changed, 183 insertions(+), 127 deletions(-) diff --git a/lib/backend/resolve_url.dart b/lib/backend/resolve_url.dart index 700ffe4..9c0d4a3 100644 --- a/lib/backend/resolve_url.dart +++ b/lib/backend/resolve_url.dart @@ -1,7 +1,8 @@ -import 'package:localstore/localstore.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; +import 'package:shared_preferences/shared_preferences.dart'; + const String wellKnownPath = "/.well-known/outbag/server"; const int defaultPort = 7223; @@ -48,15 +49,32 @@ class OutbagServer { } Future toDisk() async { - final db = Localstore.instance; - await db.collection('meta').doc('server').set(toMap()); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + + await prefs.setString('server-host', host); + await prefs.setInt('server-port', port); + await prefs.setString('server-path', path); + await prefs.setString('server-tag', tag); } static Future fromDisk() async { - final db = Localstore.instance; - final data = await db.collection('meta').doc('server').get(); + final SharedPreferences prefs = await SharedPreferences.getInstance(); - return OutbagServer.fromMap(data!); + return OutbagServer( + path: prefs.getString('server-path')!, + port: prefs.getInt('server-port')!, + tag: prefs.getString('server-tag')!, + host: prefs.getString('server-host')! + ); + } + + static Future removeDisk() async { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + + await prefs.remove('server-host'); + await prefs.remove('server-port'); + await prefs.remove('server-path'); + await prefs.remove('server-tag'); } } diff --git a/lib/backend/themes.dart b/lib/backend/themes.dart index 69f3541..bd71a9c 100644 --- a/lib/backend/themes.dart +++ b/lib/backend/themes.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:localstore/localstore.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class AppTheme { ThemeMode mode; @@ -47,27 +47,14 @@ class AppTheme { return [AppTheme.auto, AppTheme.light, AppTheme.dark]; } - static listen(Function(Map) cb) { - final db = Localstore.instance; - final stream = db.collection('settings').stream; - stream.listen(cb); - } - Future toDisk() async { - final db = Localstore.instance; - await db.collection('settings').doc('ui').set({'theme': mode.index}); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setInt('theme', mode.index); } static Future fromDisk() async { - final db = Localstore.instance; - final doc = await db.collection('settings').doc('ui').get(); - try { - final index = doc?['theme']; - final mode = ThemeMode.values[index]; - return AppTheme(mode); - } catch (_) { - return AppTheme(ThemeMode.system); - } + final SharedPreferences prefs = await SharedPreferences.getInstance(); + return AppTheme(ThemeMode.values[(prefs.getInt('theme')) ?? 0]); } @override diff --git a/lib/backend/user.dart b/lib/backend/user.dart index 3b604a1..5ee3b91 100644 --- a/lib/backend/user.dart +++ b/lib/backend/user.dart @@ -1,4 +1,4 @@ -import 'package:localstore/localstore.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import './resolve_url.dart'; class User { @@ -10,36 +10,30 @@ class User { final OutbagServer server; Future toDisk() async { - final db = Localstore.instance; - await db - .collection('meta') - .doc('auth') - .set({'username': username, 'password': password}); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + + await prefs.setString('username', username); + await prefs.setString('password', password); await server.toDisk(); } static Future fromDisk() async { - final db = Localstore.instance; - final data = await db.collection('meta').doc('auth').get(); + final SharedPreferences prefs = await SharedPreferences.getInstance(); + final server = await OutbagServer.fromDisk(); return User( - username: data?['username'], - password: data?['password'], - server: server, - ); + username: prefs.getString('username')!, + password: prefs.getString('password')!, + server: server); } static Future removeDisk() async { - final db = Localstore.instance; - await db.collection('meta').doc('auth').delete(); - return; - } + final SharedPreferences prefs = await SharedPreferences.getInstance(); - static listen(Function(Map) cb) async { - final db = Localstore.instance; - final stream = db.collection('meta').stream; - stream.listen(cb); + await prefs.remove('username'); + await prefs.remove('password'); + await OutbagServer.removeDisk(); } String get humanReadable { diff --git a/lib/main.dart b/lib/main.dart index a58cfe7..2c1d718 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -44,57 +44,41 @@ class _OutbagAppState extends State { AppTheme theme = AppTheme.auto; + void loadTheme() async { + // load theme + try { + final theme = await AppTheme.fromDisk(); + setState(() { + this.theme = theme; + }); + } catch (_) {} + } + + void loadUser() async { + // load user + try { + final user = await User.fromDisk(); + setState(() { + this.user = user; + }); + fetchInfo(user); + } catch (_) { + // user unavailable + // invalid credentials + // log out + setState(() { + user = null; + }); + } + } + @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) async { - // wait for user to be authorized - User.listen((_) async { - try { - final user = await User.fromDisk(); - setState(() { - this.user = user; - }); - fetchInfo(user); - } catch (_) { - // no userdata found - setState(() { - user = null; - }); - } - }); - - AppTheme.listen((_) async { - final theme = await AppTheme.fromDisk(); - setState(() { - this.theme = theme; - }); - }); - - // load theme - try { - final theme = await AppTheme.fromDisk(); - setState(() { - this.theme = theme; - }); - } catch (_) {} - - // load user - try { - final user = await User.fromDisk(); - setState(() { - this.user = user; - }); - fetchInfo(user); - } catch (_) { - // user unavailable - // invalid credentials - // log out - setState(() { - user = null; - }); - } + loadTheme(); + loadUser(); }); } @@ -184,20 +168,17 @@ class _OutbagAppState extends State { GoRoute( name: 'signin', path: 'signin', - builder: (context, state) => - const AuthPage(mode: Mode.signin), + builder: (context, state) => AuthPage(mode: Mode.signin, refresh: loadUser), ), GoRoute( name: 'signup', path: 'signup', - builder: (context, state) => - const AuthPage(mode: Mode.signup), + builder: (context, state) => AuthPage(mode: Mode.signup, refresh: loadUser), ), GoRoute( name: 'signup-ota', path: 'signup-ota', - builder: (context, state) => - const AuthPage(mode: Mode.signupOTA), + builder: (context, state) => AuthPage(mode: Mode.signupOTA, refresh: loadUser), ), ]), @@ -215,8 +196,7 @@ class _OutbagAppState extends State { GoRoute( name: 'settings', path: 'settings', - builder: (context, state) => - const SettingsPage()), + builder: (context, state) => SettingsPage(refreshTheme: loadTheme)), GoRoute( path: 'join-room', name: 'add-room', @@ -239,26 +219,22 @@ class _OutbagAppState extends State { GoRoute( name: 'edit-room', path: 'edit', - builder: (context, state) => - EditRoomPage( - state.params['server'] ?? - '', + builder: (context, state) => EditRoomPage( + state.params['server'] ?? '', state.params['id'] ?? '')), GoRoute( name: 'room-members', path: 'members', builder: (context, state) => ManageRoomMembersPage( - state.params['server'] ?? - '', + state.params['server'] ?? '', state.params['id'] ?? '')), GoRoute( name: 'room-permissions', path: 'roles', builder: (context, state) => EditRoomPermissionSetPage( - state.params['server'] ?? - '', + state.params['server'] ?? '', state.params['id'] ?? '')), ]) ]), diff --git a/lib/screens/auth.dart b/lib/screens/auth.dart index 7bf231a..3a0fd5b 100644 --- a/lib/screens/auth.dart +++ b/lib/screens/auth.dart @@ -15,7 +15,8 @@ enum Mode { class AuthPage extends StatefulWidget { final Mode mode; - const AuthPage({super.key, this.mode = Mode.signin}); + Function refresh; + AuthPage({super.key, required this.mode, required this.refresh}); @override State createState() => _AuthPageState(); @@ -53,7 +54,8 @@ class _AuthPageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ const CircularProgressIndicator(), - Text(AppLocalizations.of(context)!.loading, style: textTheme.titleLarge), + Text(AppLocalizations.of(context)!.loading, + style: textTheme.titleLarge), ]))) : Scaffold( appBar: AppBar( @@ -73,9 +75,12 @@ class _AuthPageState extends State { keyboardType: TextInputType.url, decoration: InputDecoration( prefixIcon: const Icon(Icons.dns), - labelText: AppLocalizations.of(context)!.inputServerLabel, - hintText: AppLocalizations.of(context)!.inputServerHint, - helperText:AppLocalizations.of(context)!.inputServerHelp, + labelText: AppLocalizations.of(context)! + .inputServerLabel, + hintText: + AppLocalizations.of(context)!.inputServerHint, + helperText: + AppLocalizations.of(context)!.inputServerHelp, border: const OutlineInputBorder(), ), ), @@ -87,10 +92,13 @@ class _AuthPageState extends State { keyboardType: TextInputType.emailAddress, decoration: InputDecoration( prefixIcon: const Icon(Icons.person), - labelText: AppLocalizations.of(context)!.inputUsernameLabel, - hintText: AppLocalizations.of(context)!.inputUsernameHint, - helperText:AppLocalizations.of(context)!.inputUsernameHelp, - border: const OutlineInputBorder(), + labelText: AppLocalizations.of(context)! + .inputUsernameLabel, + hintText: AppLocalizations.of(context)! + .inputUsernameHint, + helperText: AppLocalizations.of(context)! + .inputUsernameHelp, + border: const OutlineInputBorder(), ), ), ), @@ -102,9 +110,12 @@ class _AuthPageState extends State { obscureText: true, decoration: InputDecoration( prefixIcon: const Icon(Icons.lock), - labelText: AppLocalizations.of(context)!.inputPasswordLabel, - hintText: AppLocalizations.of(context)!.inputPasswordHint, - helperText:AppLocalizations.of(context)!.inputPasswordHelp, + labelText: AppLocalizations.of(context)! + .inputPasswordLabel, + hintText: AppLocalizations.of(context)! + .inputPasswordHint, + helperText: AppLocalizations.of(context)! + .inputPasswordHelp, border: const OutlineInputBorder(), ), ), @@ -120,9 +131,12 @@ class _AuthPageState extends State { obscureText: true, decoration: InputDecoration( prefixIcon: const Icon(Icons.lock), - labelText: AppLocalizations.of(context)!.inputPasswordRepeatLabel, - hintText: AppLocalizations.of(context)!.inputPasswordRepeatHint, - helperText:AppLocalizations.of(context)!.inputPasswordRepeatHelp, + labelText: AppLocalizations.of(context)! + .inputPasswordRepeatLabel, + hintText: AppLocalizations.of(context)! + .inputPasswordRepeatHint, + helperText: AppLocalizations.of(context)! + .inputPasswordRepeatHelp, border: const OutlineInputBorder(), ), ), @@ -139,9 +153,12 @@ class _AuthPageState extends State { keyboardType: TextInputType.visiblePassword, decoration: InputDecoration( prefixIcon: const Icon(Icons.key), - labelText: AppLocalizations.of(context)!.inputOTALabel, - hintText: AppLocalizations.of(context)!.inputOTAHint, - helperText:AppLocalizations.of(context)!.inputOTAHelp, + labelText: AppLocalizations.of(context)! + .inputOTALabel, + hintText: AppLocalizations.of(context)! + .inputOTAHint, + helperText: AppLocalizations.of(context)! + .inputOTAHelp, border: const OutlineInputBorder(), ), ), @@ -166,7 +183,8 @@ class _AuthPageState extends State { }); showSimpleSnackbar(scaffMgr, - text: AppLocalizations.of(context)!.errorPasswordsDoNotMatch, + text: AppLocalizations.of(context)! + .errorPasswordsDoNotMatch, action: AppLocalizations.of(context)!.dismiss); _ctrPasswordRpt.clear(); @@ -201,7 +219,8 @@ class _AuthPageState extends State { }); showSimpleSnackbar(scaffMgr, - text: AppLocalizations.of(context)!.errorInvalidServer(_ctrServer.text), + text: AppLocalizations.of(context)! + .errorInvalidServer(_ctrServer.text), action: AppLocalizations.of(context)!.dismiss); return; @@ -248,6 +267,7 @@ class _AuthPageState extends State { password: password, server: server) .toDisk(); + widget.refresh(); }, after: () { setState(() { showSpinner = false; diff --git a/lib/screens/settings/main.dart b/lib/screens/settings/main.dart index ac2f390..cd01d72 100644 --- a/lib/screens/settings/main.dart +++ b/lib/screens/settings/main.dart @@ -9,7 +9,8 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class SettingsPage extends StatefulWidget { - const SettingsPage({super.key}); + Function refreshTheme; + SettingsPage({super.key, required this.refreshTheme}); @override State createState() => _SettingsPageState(); @@ -113,6 +114,7 @@ class _SettingsPageState extends State { onSelectionChanged: (item) async { try { await item.first.toDisk(); + widget.refreshTheme(); } catch (_) {} }, ), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index e777c67..b8e2b22 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,9 @@ import FlutterMacOS import Foundation import path_provider_foundation +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 742eaa1..c3b3356 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -381,6 +381,62 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.5" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "78528fd87d0d08ffd3e69551173c026e8eacc7b7079c82eb6a77413957b7e394" + url: "https://pub.dev" + source: hosted + version: "2.0.20" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: ad423a80fe7b4e48b50d6111b3ea1027af0e959e49d485712e134863d9c1c521 + url: "https://pub.dev" + source: hosted + version: "2.0.17" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "1e755f8583229f185cfca61b1d80fb2344c9d660e1c69ede5450d8f478fa5310" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "3a59ed10890a8409ad0faad7bb2957dab4b92b8fbe553257b05d30ed8af2c707" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "824bfd02713e37603b2bdade0842e47d56e7db32b1dcdd1cae533fb88e2913fc" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "0dc2633f215a3d4aa3184c9b2c5766f4711e4e5a6b256e62aafee41f89f1bfb8" + url: "https://pub.dev" + source: hosted + version: "2.0.6" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "71bcd669bb9cdb6b39f22c4a7728b6d49e934f6cba73157ffa5a54f1eed67436" + url: "https://pub.dev" + source: hosted + version: "2.1.5" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 07e8e6c..d6f3893 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: flutter_localizations: sdk: flutter intl: any + shared_preferences: ^2.0.20 dev_dependencies: flutter_test: