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: