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
This commit is contained in:
parent
e492a3f8ce
commit
1af8d6f068
9 changed files with 183 additions and 127 deletions
|
@ -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<void> 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<OutbagServer> 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<void> 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');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String, dynamic>) cb) {
|
||||
final db = Localstore.instance;
|
||||
final stream = db.collection('settings').stream;
|
||||
stream.listen(cb);
|
||||
}
|
||||
|
||||
Future<void> 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<AppTheme> 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
|
||||
|
|
|
@ -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<void> 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<User> 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<void> removeDisk() async {
|
||||
final db = Localstore.instance;
|
||||
await db.collection('meta').doc('auth').delete();
|
||||
return;
|
||||
}
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
|
||||
static listen(Function(Map<String, dynamic>) 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 {
|
||||
|
|
100
lib/main.dart
100
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'] ?? '')),
|
||||
])
|
||||
]),
|
||||
|
|
|
@ -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<StatefulWidget> createState() => _AuthPageState();
|
||||
|
@ -53,7 +54,8 @@ class _AuthPageState extends State<AuthPage> {
|
|||
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<AuthPage> {
|
|||
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<AuthPage> {
|
|||
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<AuthPage> {
|
|||
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<AuthPage> {
|
|||
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<AuthPage> {
|
|||
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<AuthPage> {
|
|||
});
|
||||
|
||||
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<AuthPage> {
|
|||
});
|
||||
|
||||
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<AuthPage> {
|
|||
password: password,
|
||||
server: server)
|
||||
.toDisk();
|
||||
widget.refresh();
|
||||
}, after: () {
|
||||
setState(() {
|
||||
showSpinner = false;
|
||||
|
|
|
@ -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<StatefulWidget> createState() => _SettingsPageState();
|
||||
|
@ -113,6 +114,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
onSelectionChanged: (item) async {
|
||||
try {
|
||||
await item.first.toDisk();
|
||||
widget.refreshTheme();
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
|
|
56
pubspec.lock
56
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
|
||||
|
|
|
@ -39,6 +39,7 @@ dependencies:
|
|||
flutter_localizations:
|
||||
sdk: flutter
|
||||
intl: any
|
||||
shared_preferences: ^2.0.20
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in a new issue