mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 16:07:57 -06:00
feat: Add more user related state to hive
This commit is contained in:
@@ -29,7 +29,4 @@ class GlobalSettings with HiveObjectMixin {
|
||||
this.showOnboarding = true,
|
||||
this.currentLoggedInUser,
|
||||
});
|
||||
|
||||
static GlobalSettings get boxedValue =>
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
|
||||
part 'user_settings.g.dart';
|
||||
@@ -8,7 +9,11 @@ class UserSettings with HiveObjectMixin {
|
||||
@HiveField(0)
|
||||
bool isBiometricAuthenticationEnabled;
|
||||
|
||||
@HiveField(1)
|
||||
DocumentFilter currentDocumentFilter;
|
||||
|
||||
UserSettings({
|
||||
this.isBiometricAuthenticationEnabled = false,
|
||||
required this.currentDocumentFilter,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
@@ -14,91 +16,71 @@ import 'package:paperless_mobile/features/settings/view/dialogs/switch_account_d
|
||||
import 'package:paperless_mobile/features/settings/view/pages/switching_accounts_page.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/user_avatar.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class ManageAccountsPage extends StatelessWidget {
|
||||
const ManageAccountsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog.fullscreen(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: const CloseButton(),
|
||||
title: const Text("Accounts"), //TODO: INTL
|
||||
),
|
||||
body: GlobalSettingsBuilder(
|
||||
builder: (context, globalSettings) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
|
||||
builder: (context, box, _) {
|
||||
final userIds = box.keys.toList().cast<String>();
|
||||
final otherAccounts = userIds
|
||||
.whereNot((element) => element == globalSettings.currentLoggedInUser)
|
||||
.toList();
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Your account", //TODO: INTL
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
).padded(16),
|
||||
_buildAccountTile(
|
||||
context,
|
||||
globalSettings.currentLoggedInUser!,
|
||||
box.get(globalSettings.currentLoggedInUser!)!,
|
||||
globalSettings,
|
||||
),
|
||||
if (otherAccounts.isNotEmpty) const Divider(),
|
||||
],
|
||||
return GlobalSettingsBuilder(
|
||||
builder: (context, globalSettings) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable:
|
||||
Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
|
||||
builder: (context, box, _) {
|
||||
final userIds = box.keys.toList().cast<String>();
|
||||
final otherAccounts = userIds
|
||||
.whereNot(
|
||||
(element) => element == globalSettings.currentLoggedInUser)
|
||||
.toList();
|
||||
return SimpleDialog(
|
||||
insetPadding: EdgeInsets.all(24),
|
||||
contentPadding: EdgeInsets.all(8),
|
||||
title: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: CloseButton(),
|
||||
),
|
||||
Center(child: Text("Accounts")),
|
||||
],
|
||||
), //TODO: INTL
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
children: [
|
||||
_buildAccountTile(
|
||||
context,
|
||||
globalSettings.currentLoggedInUser!,
|
||||
box.get(globalSettings.currentLoggedInUser!)!,
|
||||
globalSettings),
|
||||
// if (otherAccounts.isNotEmpty) Text("Other accounts"),
|
||||
Column(
|
||||
children: [
|
||||
for (int index = 0; index < otherAccounts.length; index++)
|
||||
_buildAccountTile(
|
||||
context,
|
||||
otherAccounts[index],
|
||||
box.get(otherAccounts[index])!,
|
||||
globalSettings,
|
||||
),
|
||||
),
|
||||
if (otherAccounts.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Text(
|
||||
"Other accounts", //TODO: INTL
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
).padded(16),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => _buildAccountTile(
|
||||
context,
|
||||
otherAccounts[index],
|
||||
box.get(otherAccounts[index])!,
|
||||
globalSettings,
|
||||
),
|
||||
childCount: otherAccounts.length,
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: const Text("Add account"),
|
||||
leading: const Icon(Icons.person_add),
|
||||
onTap: () {
|
||||
_onAddAccount(context);
|
||||
},
|
||||
),
|
||||
// FilledButton.tonalIcon(
|
||||
// icon: Icon(Icons.person_add),
|
||||
// label: Text("Add account"),
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
title: const Text("Add account"),
|
||||
leading: const Icon(Icons.person_add),
|
||||
onTap: () {
|
||||
_onAddAccount(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,69 +90,96 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
UserAccount account,
|
||||
GlobalSettings settings,
|
||||
) {
|
||||
final isLoggedIn = userId == settings.currentLoggedInUser;
|
||||
final theme = Theme.of(context);
|
||||
return ListTile(
|
||||
title: Text(account.username),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (account.fullName != null) Text(account.fullName!),
|
||||
Text(account.serverUrl),
|
||||
],
|
||||
),
|
||||
isThreeLine: true,
|
||||
leading: UserAvatar(
|
||||
account: account,
|
||||
userId: userId,
|
||||
),
|
||||
trailing: PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
if (settings.currentLoggedInUser != userId)
|
||||
const PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text("Switch"), //TODO: INTL
|
||||
leading: Icon(Icons.switch_account_outlined),
|
||||
),
|
||||
value: 0,
|
||||
final child = SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: ListTile(
|
||||
title: Text(account.username),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (account.fullName != null) Text(account.fullName!),
|
||||
Text(
|
||||
account.serverUrl.replaceFirst(RegExp(r'https://?'), ''),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text("Remove"), // TODO: INTL
|
||||
leading: Icon(
|
||||
Icons.remove_circle_outline,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
value: 1,
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (value) async {
|
||||
if (value == 0) {
|
||||
// Switch
|
||||
final navigator = Navigator.of(context);
|
||||
if (settings.currentLoggedInUser == userId) return;
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SwitchingAccountsPage(),
|
||||
),
|
||||
);
|
||||
await context.read<AuthenticationCubit>().switchAccount(userId);
|
||||
navigator.popUntil((route) => route.isFirst);
|
||||
} else if (value == 1) {
|
||||
// Remove
|
||||
final shouldPop = userId == settings.currentLoggedInUser;
|
||||
await context.read<AuthenticationCubit>().removeAccount(userId);
|
||||
if (shouldPop) {
|
||||
Navigator.pop(context);
|
||||
],
|
||||
),
|
||||
isThreeLine: true,
|
||||
leading: UserAvatar(
|
||||
account: account,
|
||||
userId: userId,
|
||||
),
|
||||
trailing: PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
if (!isLoggedIn)
|
||||
const PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text("Switch"), //TODO: INTL
|
||||
leading: Icon(Icons.switch_account_rounded),
|
||||
),
|
||||
value: 0,
|
||||
),
|
||||
if (!isLoggedIn)
|
||||
const PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text("Remove"), // TODO: INTL
|
||||
leading: Icon(
|
||||
Icons.person_remove,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
value: 1,
|
||||
)
|
||||
else
|
||||
const PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text("Logout"), // TODO: INTL
|
||||
leading: Icon(
|
||||
Icons.person_remove,
|
||||
color: Colors.red,
|
||||
),
|
||||
),
|
||||
value: 1,
|
||||
),
|
||||
];
|
||||
},
|
||||
onSelected: (value) async {
|
||||
if (value == 0) {
|
||||
// Switch
|
||||
final navigator = Navigator.of(context);
|
||||
if (settings.currentLoggedInUser == userId) return;
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const SwitchingAccountsPage(),
|
||||
),
|
||||
);
|
||||
await context.read<AuthenticationCubit>().switchAccount(userId);
|
||||
navigator.popUntil((route) => route.isFirst);
|
||||
} else if (value == 1) {
|
||||
// Remove
|
||||
final shouldPop = userId == settings.currentLoggedInUser;
|
||||
await context.read<AuthenticationCubit>().removeAccount(userId);
|
||||
if (shouldPop) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
if (isLoggedIn) {
|
||||
return Card(
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
Future<void> _onAddAccount(BuildContext context) {
|
||||
@@ -179,7 +188,8 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LoginPage(
|
||||
titleString: "Add account", //TODO: INTL
|
||||
onSubmit: (context, username, password, serverUrl, clientCertificate) async {
|
||||
onSubmit: (context, username, password, serverUrl,
|
||||
clientCertificate) async {
|
||||
final userId = await context.read<AuthenticationCubit>().addAccount(
|
||||
credentials: LoginFormCredentials(
|
||||
username: username,
|
||||
@@ -192,8 +202,8 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
);
|
||||
final shoudSwitch = await showDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
SwitchAccountDialog(username: username, serverUrl: serverUrl),
|
||||
builder: (context) => SwitchAccountDialog(
|
||||
username: username, serverUrl: serverUrl),
|
||||
) ??
|
||||
false;
|
||||
if (shoudSwitch) {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/src/widgets/placeholder.dart';
|
||||
|
||||
class SwitchingAccountsPage extends StatelessWidget {
|
||||
const SwitchingAccountsPage({super.key});
|
||||
@@ -13,7 +11,14 @@ class SwitchingAccountsPage extends StatelessWidget {
|
||||
},
|
||||
child: Material(
|
||||
child: Center(
|
||||
child: Text("Switching accounts. Please wait..."),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(),
|
||||
Text("Switching accounts. Please wait..."),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -14,13 +14,13 @@ class BiometricAuthenticationSetting extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return UserSettingsBuilder(
|
||||
builder: (context, settings) {
|
||||
if (settings == null) {
|
||||
return UserAccountBuilder(
|
||||
builder: (context, account) {
|
||||
if (account == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return SwitchListTile(
|
||||
value: settings.isBiometricAuthenticationEnabled,
|
||||
value: account.settings.isBiometricAuthenticationEnabled,
|
||||
title: Text(S.of(context)!.biometricAuthentication),
|
||||
subtitle: Text(S.of(context)!.authenticateOnAppStart),
|
||||
onChanged: (val) async {
|
||||
@@ -33,8 +33,8 @@ class BiometricAuthenticationSetting extends StatelessWidget {
|
||||
.read<LocalAuthenticationService>()
|
||||
.authenticateLocalUser(localizedReason);
|
||||
if (isAuthenticated) {
|
||||
settings.isBiometricAuthenticationEnabled = val;
|
||||
settings.save();
|
||||
account.settings.isBiometricAuthenticationEnabled = val;
|
||||
account.save();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/features/login/model/user_account.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/global_settings.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/user_settings.dart';
|
||||
|
||||
class UserSettingsBuilder extends StatelessWidget {
|
||||
class UserAccountBuilder extends StatelessWidget {
|
||||
final Widget Function(
|
||||
BuildContext context,
|
||||
UserSettings? settings,
|
||||
UserAccount? settings,
|
||||
) builder;
|
||||
|
||||
const UserSettingsBuilder({
|
||||
const UserAccountBuilder({
|
||||
super.key,
|
||||
required this.builder,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<Box<UserSettings>>(
|
||||
valueListenable: Hive.box<UserSettings>(HiveBoxes.userSettings).listenable(),
|
||||
builder: (context, value, _) {
|
||||
final currentUser =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser;
|
||||
return ValueListenableBuilder<Box<UserAccount>>(
|
||||
valueListenable:
|
||||
Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
|
||||
builder: (context, accountBox, _) {
|
||||
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.currentLoggedInUser;
|
||||
if (currentUser != null) {
|
||||
final settings = value.get(currentUser);
|
||||
return builder(context, settings);
|
||||
final account = accountBox.get(currentUser);
|
||||
return builder(context, account);
|
||||
} else {
|
||||
return builder(context, null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user