mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 06:07:54 -06:00
feat: Add permission checks, fix search, fix document upload, fix linked documents always being loaded all at once instead of paged
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/widgets/paperless_logo.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
@@ -92,8 +93,11 @@ class AppDrawer extends StatelessWidget {
|
||||
),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => Provider.value(
|
||||
value: context.read<ApiVersion>(),
|
||||
builder: (_) => MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: context.read<PaperlessServerStatsApi>()),
|
||||
Provider.value(value: context.read<ApiVersion>()),
|
||||
],
|
||||
child: const SettingsPage(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -240,7 +240,9 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
const DocumentPermissionsWidget(),
|
||||
DocumentPermissionsWidget(
|
||||
document: state.document,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
|
||||
class DocumentPermissionsWidget extends StatelessWidget {
|
||||
const DocumentPermissionsWidget({super.key});
|
||||
class DocumentPermissionsWidget extends StatefulWidget {
|
||||
final DocumentModel document;
|
||||
const DocumentPermissionsWidget({super.key, required this.document});
|
||||
|
||||
@override
|
||||
State<DocumentPermissionsWidget> createState() => _DocumentPermissionsWidgetState();
|
||||
}
|
||||
|
||||
class _DocumentPermissionsWidgetState extends State<DocumentPermissionsWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SliverToBoxAdapter(
|
||||
|
||||
@@ -18,8 +18,6 @@ import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_scan/view/widgets/scanned_image_item.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
@@ -27,88 +27,89 @@ class DocumentSearchBar extends StatefulWidget {
|
||||
class _DocumentSearchBarState extends State<DocumentSearchBar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return OpenContainer(
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
transitionType: ContainerTransitionType.fadeThrough,
|
||||
closedElevation: 1,
|
||||
middleColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
openColor: Theme.of(context).colorScheme.background,
|
||||
closedColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
closedShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(56),
|
||||
),
|
||||
closedBuilder: (_, action) {
|
||||
return InkWell(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 720,
|
||||
minWidth: 360,
|
||||
maxHeight: 56,
|
||||
minHeight: 48,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.menu),
|
||||
onPressed: Scaffold.of(context).openDrawer,
|
||||
),
|
||||
Expanded(
|
||||
child: Hero(
|
||||
tag: "search_hero_tag",
|
||||
child: TextField(
|
||||
enabled: false,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: S.of(context)!.searchDocuments,
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 8),
|
||||
child: OpenContainer(
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
transitionType: ContainerTransitionType.fadeThrough,
|
||||
closedElevation: 1,
|
||||
middleColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
openColor: Theme.of(context).colorScheme.background,
|
||||
closedColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
closedShape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(56),
|
||||
),
|
||||
closedBuilder: (_, action) {
|
||||
return InkWell(
|
||||
onTap: action,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 720,
|
||||
minWidth: 360,
|
||||
maxHeight: 56,
|
||||
minHeight: 48,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
onPressed: Scaffold.of(context).openDrawer,
|
||||
),
|
||||
Flexible(
|
||||
child: Text(
|
||||
S.of(context)!.searchDocuments,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildUserAvatar(context),
|
||||
],
|
||||
_buildUserAvatar(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
openBuilder: (_, action) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: context.read<LabelRepository>()),
|
||||
Provider.value(value: context.read<PaperlessDocumentsApi>()),
|
||||
Provider.value(value: context.read<CacheManager>()),
|
||||
Provider.value(value: context.read<ApiVersion>()),
|
||||
Provider.value(value: context.read<UserRepository>()),
|
||||
],
|
||||
child: Provider(
|
||||
create: (_) => DocumentSearchCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||
.get(LocalUserAccount.current.id)!,
|
||||
);
|
||||
},
|
||||
openBuilder: (_, action) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: context.read<LabelRepository>()),
|
||||
Provider.value(value: context.read<PaperlessDocumentsApi>()),
|
||||
Provider.value(value: context.read<CacheManager>()),
|
||||
Provider.value(value: context.read<ApiVersion>()),
|
||||
if (context.read<ApiVersion>().hasMultiUserSupport)
|
||||
Provider.value(value: context.read<UserRepository>()),
|
||||
],
|
||||
child: Provider(
|
||||
create: (_) => DocumentSearchCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||
.get(LocalUserAccount.current.id)!,
|
||||
),
|
||||
builder: (_, __) => const DocumentSearchPage(),
|
||||
),
|
||||
builder: (_, __) => const DocumentSearchPage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconButton _buildUserAvatar(BuildContext context) {
|
||||
return IconButton(
|
||||
padding: const EdgeInsets.all(6),
|
||||
icon: GlobalSettingsBuilder(
|
||||
builder: (context, settings) {
|
||||
return ValueListenableBuilder(
|
||||
|
||||
@@ -21,6 +21,7 @@ class SliverSearchBar extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final currentUser =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser;
|
||||
|
||||
return SliverPersistentHeader(
|
||||
floating: floating,
|
||||
pinned: pinned,
|
||||
|
||||
@@ -114,8 +114,9 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> with DocumentPagingBl
|
||||
|
||||
void setViewType(ViewType viewType) {
|
||||
emit(state.copyWith(viewType: viewType));
|
||||
_userState.documentsPageViewType = viewType;
|
||||
_userState.save();
|
||||
_userState
|
||||
..documentsPageViewType = viewType
|
||||
..save();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'package:paperless_mobile/features/saved_view/view/saved_view_list.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:sliver_tools/sliver_tools.dart';
|
||||
|
||||
class DocumentFilterIntent {
|
||||
final DocumentFilter? filter;
|
||||
@@ -107,7 +108,7 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
||||
},
|
||||
builder: (context, connectivityState) {
|
||||
return SafeArea(
|
||||
top: context.watch<DocumentsCubit>().state.selection.isEmpty,
|
||||
top: true,
|
||||
child: Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
@@ -160,13 +161,18 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
||||
handle: searchBarHandle,
|
||||
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
if (state.selection.isNotEmpty) {
|
||||
// Show selection app bar when selection mode is active
|
||||
return DocumentSelectionSliverAppBar(
|
||||
state: state,
|
||||
);
|
||||
}
|
||||
return const SliverSearchBar(floating: true);
|
||||
return AnimatedSwitcher(
|
||||
layoutBuilder: SliverAnimatedSwitcher.defaultLayoutBuilder,
|
||||
transitionBuilder: SliverAnimatedSwitcher.defaultTransitionBuilder,
|
||||
child: state.selection.isEmpty
|
||||
? const SliverSearchBar(floating: true)
|
||||
: DocumentSelectionSliverAppBar(
|
||||
state: state,
|
||||
),
|
||||
duration: const Duration(
|
||||
milliseconds: 250,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -35,7 +35,7 @@ class ViewTypeSelectionWidget extends StatelessWidget {
|
||||
), // Ensures text is not split into two lines
|
||||
position: PopupMenuPosition.under,
|
||||
initialValue: viewType,
|
||||
icon: Icon(icon),
|
||||
icon: Icon(icon, color: Theme.of(context).colorScheme.primary),
|
||||
itemBuilder: (context) => [
|
||||
_buildViewTypeOption(
|
||||
context,
|
||||
|
||||
@@ -10,13 +10,12 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/file_description.dart';
|
||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/features/document_scan/view/scanner_page.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
||||
import 'package:paperless_mobile/features/home/view/route_description.dart';
|
||||
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||
@@ -139,25 +138,23 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LocalUserAccount.current.paperlessUser
|
||||
.hasPermission(PermissionAction.add, PermissionTarget.document)) {
|
||||
Fluttertoast.showToast(
|
||||
msg: "You do not have the permissions to upload documents.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
final fileDescription = FileDescription.fromPath(mediaFile.path);
|
||||
if (await File(mediaFile.path).exists()) {
|
||||
final bytes = File(mediaFile.path).readAsBytesSync();
|
||||
final result = await Navigator.push<DocumentUploadResult>(
|
||||
final result = await pushDocumentUploadPreparationPage(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: DocumentUploadCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
),
|
||||
child: DocumentUploadPreparationPage(
|
||||
fileBytes: bytes,
|
||||
filename: fileDescription.filename,
|
||||
title: fileDescription.filename,
|
||||
fileExtension: fileDescription.extension,
|
||||
),
|
||||
),
|
||||
),
|
||||
bytes: bytes,
|
||||
filename: fileDescription.filename,
|
||||
title: fileDescription.filename,
|
||||
fileExtension: fileDescription.extension,
|
||||
);
|
||||
if (result?.success ?? false) {
|
||||
await Fluttertoast.showToast(
|
||||
|
||||
@@ -5,12 +5,14 @@ class RouteDescription {
|
||||
final Icon icon;
|
||||
final Icon selectedIcon;
|
||||
final Widget Function(Widget icon)? badgeBuilder;
|
||||
final bool enabled;
|
||||
|
||||
RouteDescription({
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.selectedIcon,
|
||||
this.badgeBuilder,
|
||||
this.enabled = true,
|
||||
});
|
||||
|
||||
NavigationDestination toNavigationDestination() {
|
||||
|
||||
@@ -146,7 +146,6 @@ class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateM
|
||||
labels: context.watch<LabelCubit>().state.correspondents,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
correspondent: IdQueryParameter.fromId(label.id!),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
canEdit: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.change, PermissionTarget.correspondent),
|
||||
@@ -171,7 +170,6 @@ class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateM
|
||||
labels: context.watch<LabelCubit>().state.documentTypes,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
documentType: IdQueryParameter.fromId(label.id!),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
canEdit: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.change, PermissionTarget.documentType),
|
||||
@@ -196,7 +194,6 @@ class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateM
|
||||
labels: context.watch<LabelCubit>().state.tags,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
tags: TagsQuery.ids(include: [label.id!]),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
canEdit: LocalUserAccount.current.paperlessUser
|
||||
.hasPermission(PermissionAction.change, PermissionTarget.tag),
|
||||
@@ -231,7 +228,6 @@ class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateM
|
||||
onEdit: _openEditStoragePathPage,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
storagePath: IdQueryParameter.fromId(label.id!),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
canEdit: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||
PermissionAction.change, PermissionTarget.storagePath),
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
@@ -88,39 +85,38 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await withEncryptedBox<UserCredentials, void>(HiveBoxes.localUserCredentials,
|
||||
(credentialsBox) async {
|
||||
if (!credentialsBox.containsKey(localUserId)) {
|
||||
await credentialsBox.close();
|
||||
debugPrint("Invalid authentication for $localUserId");
|
||||
return;
|
||||
}
|
||||
final credentials = credentialsBox.get(localUserId);
|
||||
await _resetExternalState();
|
||||
|
||||
final credentialsBox = await _getUserCredentialsBox();
|
||||
if (!credentialsBox.containsKey(localUserId)) {
|
||||
await credentialsBox.close();
|
||||
debugPrint("Invalid authentication for $localUserId");
|
||||
return;
|
||||
}
|
||||
final credentials = credentialsBox.get(localUserId);
|
||||
await credentialsBox.close();
|
||||
_sessionManager.updateSettings(
|
||||
authToken: credentials!.token,
|
||||
clientCertificate: credentials.clientCertificate,
|
||||
baseUrl: account.serverUrl,
|
||||
);
|
||||
|
||||
await _resetExternalState();
|
||||
globalSettings.currentLoggedInUser = localUserId;
|
||||
await globalSettings.save();
|
||||
|
||||
_sessionManager.updateSettings(
|
||||
authToken: credentials!.token,
|
||||
clientCertificate: credentials.clientCertificate,
|
||||
baseUrl: account.serverUrl,
|
||||
);
|
||||
final apiVersion = await _getApiVersion(_sessionManager.client);
|
||||
|
||||
globalSettings.currentLoggedInUser = localUserId;
|
||||
await globalSettings.save();
|
||||
await _updateRemoteUser(
|
||||
_sessionManager,
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).get(localUserId)!,
|
||||
apiVersion,
|
||||
);
|
||||
|
||||
final apiVersion = await _getApiVersion(_sessionManager.client);
|
||||
|
||||
await _updateRemoteUser(
|
||||
_sessionManager,
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).get(localUserId)!,
|
||||
apiVersion,
|
||||
);
|
||||
|
||||
emit(AuthenticationState.authenticated(
|
||||
localUserId: localUserId,
|
||||
apiVersion: apiVersion,
|
||||
));
|
||||
emit(AuthenticationState.authenticated(
|
||||
localUserId: localUserId,
|
||||
apiVersion: apiVersion,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
Future<String> addAccount({
|
||||
@@ -146,14 +142,14 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
Future<void> removeAccount(String userId) async {
|
||||
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
final userAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||
final userCredentialsBox = await _getUserCredentialsBox();
|
||||
final userAppStateBox = Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
|
||||
final currentUser = globalSettings.currentLoggedInUser;
|
||||
|
||||
await userAccountBox.delete(userId);
|
||||
await userAppStateBox.delete(userId);
|
||||
await userCredentialsBox.delete(userId);
|
||||
await userCredentialsBox.close();
|
||||
await withEncryptedBox(HiveBoxes.localUserCredentials, (box) {
|
||||
box.delete(userId);
|
||||
});
|
||||
|
||||
if (currentUser == userId) {
|
||||
return logout();
|
||||
@@ -182,9 +178,10 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
}
|
||||
}
|
||||
|
||||
final userCredentialsBox = await _getUserCredentialsBox();
|
||||
final authentication = userCredentialsBox.get(globalSettings.currentLoggedInUser!);
|
||||
await userCredentialsBox.close();
|
||||
final authentication = await withEncryptedBox<UserCredentials, UserCredentials>(
|
||||
HiveBoxes.localUserCredentials, (box) {
|
||||
return box.get(globalSettings.currentLoggedInUser!);
|
||||
});
|
||||
|
||||
if (authentication == null) {
|
||||
throw Exception(
|
||||
@@ -218,28 +215,6 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
emit(const AuthenticationState.unauthenticated());
|
||||
}
|
||||
|
||||
Future<Uint8List> _getEncryptedBoxKey() async {
|
||||
const secureStorage = FlutterSecureStorage();
|
||||
if (!await secureStorage.containsKey(key: 'key')) {
|
||||
final key = Hive.generateSecureKey();
|
||||
|
||||
await secureStorage.write(
|
||||
key: 'key',
|
||||
value: base64UrlEncode(key),
|
||||
);
|
||||
}
|
||||
final key = (await secureStorage.read(key: 'key'))!;
|
||||
return base64Decode(key);
|
||||
}
|
||||
|
||||
Future<Box<UserCredentials>> _getUserCredentialsBox() async {
|
||||
final keyBytes = await _getEncryptedBoxKey();
|
||||
return Hive.openBox<UserCredentials>(
|
||||
HiveBoxes.localUserCredentials,
|
||||
encryptionCipher: HiveAesCipher(keyBytes),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _resetExternalState() async {
|
||||
_sessionManager.resetSettings();
|
||||
await HydratedBloc.storage.clear();
|
||||
@@ -305,15 +280,15 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
);
|
||||
|
||||
// Save credentials in encrypted box
|
||||
final userCredentialsBox = await _getUserCredentialsBox();
|
||||
await userCredentialsBox.put(
|
||||
localUserId,
|
||||
UserCredentials(
|
||||
token: token,
|
||||
clientCertificate: clientCert,
|
||||
),
|
||||
);
|
||||
userCredentialsBox.close();
|
||||
await withEncryptedBox(HiveBoxes.localUserCredentials, (box) async {
|
||||
await box.put(
|
||||
localUserId,
|
||||
UserCredentials(
|
||||
token: token,
|
||||
clientCertificate: clientCert,
|
||||
),
|
||||
);
|
||||
});
|
||||
return serverUser.id;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:paperless_mobile/features/settings/view/pages/application_settin
|
||||
import 'package:paperless_mobile/features/settings/view/pages/security_settings_page.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/user_settings_builder.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class SettingsPage extends StatelessWidget {
|
||||
const SettingsPage({super.key});
|
||||
@@ -25,10 +26,21 @@ class SettingsPage extends StatelessWidget {
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
subtitle: FutureBuilder<PaperlessServerInformationModel>(
|
||||
future: context.read<PaperlessServerStatsApi>().getServerInformation(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return Text(
|
||||
"Something went wrong while retrieving server data.", //TODO: INTL
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall
|
||||
?.copyWith(color: Theme.of(context).colorScheme.error),
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
}
|
||||
if (!snapshot.hasData) {
|
||||
return Text(
|
||||
"Loading server information...",
|
||||
"Loading server information...", //TODO: INTL
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
@@ -39,7 +51,9 @@ class SettingsPage extends StatelessWidget {
|
||||
' ' +
|
||||
serverData.version.toString() +
|
||||
' (API v${serverData.apiVersion})',
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user