mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-07 16:07:53 -06:00
feat: Migrate to go_router
This commit is contained in:
20
lib/core/bloc/bloc_refresh_listenable.dart
Normal file
20
lib/core/bloc/bloc_refresh_listenable.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class GoRouterRefreshStream extends ChangeNotifier {
|
||||
GoRouterRefreshStream(Stream<dynamic> stream) {
|
||||
notifyListeners();
|
||||
_subscription = stream.asBroadcastStream().listen(
|
||||
(dynamic _) => notifyListeners(),
|
||||
);
|
||||
}
|
||||
|
||||
late final StreamSubscription<dynamic> _subscription;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_subscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class GlobalSettings with HiveObjectMixin {
|
||||
bool showOnboarding;
|
||||
|
||||
@HiveField(4)
|
||||
String? currentLoggedInUser;
|
||||
String? loggedInUserId;
|
||||
|
||||
@HiveField(5)
|
||||
FileDownloadType defaultDownloadType;
|
||||
@@ -37,7 +37,7 @@ class GlobalSettings with HiveObjectMixin {
|
||||
this.preferredThemeMode = ThemeMode.system,
|
||||
this.preferredColorSchemeOption = ColorSchemeOption.classic,
|
||||
this.showOnboarding = true,
|
||||
this.currentLoggedInUser,
|
||||
this.loggedInUserId,
|
||||
this.defaultDownloadType = FileDownloadType.alwaysAsk,
|
||||
this.defaultShareType = FileDownloadType.alwaysAsk,
|
||||
this.enforceSinglePagePdfUpload = false,
|
||||
|
||||
@@ -20,16 +20,16 @@ class LocalUserAccount extends HiveObject {
|
||||
@HiveField(7)
|
||||
UserModel paperlessUser;
|
||||
|
||||
@HiveField(8, defaultValue: 2)
|
||||
int apiVersion;
|
||||
|
||||
LocalUserAccount({
|
||||
required this.id,
|
||||
required this.serverUrl,
|
||||
required this.settings,
|
||||
required this.paperlessUser,
|
||||
required this.apiVersion,
|
||||
});
|
||||
|
||||
static LocalUserAccount get current =>
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).get(
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.currentLoggedInUser)!;
|
||||
bool get hasMultiUserSupport => apiVersion >= 3;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class LocalUserAppState extends HiveObject {
|
||||
final currentLocalUserId =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.currentLoggedInUser!;
|
||||
.loggedInUserId!;
|
||||
return Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||
.get(currentLocalUserId)!;
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.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';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
@@ -29,7 +31,6 @@ import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.da
|
||||
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view_details/view/saved_view_details_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
// These are convenience methods for nativating to views without having to pass providers around explicitly.
|
||||
@@ -38,59 +39,18 @@ import 'package:provider/provider.dart';
|
||||
Future<void> pushDocumentSearchPage(BuildContext context) {
|
||||
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.currentLoggedInUser;
|
||||
.loggedInUserId;
|
||||
final userRepo = context.read<UserRepository>();
|
||||
return Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: context.read<LabelRepository>()),
|
||||
Provider.value(value: context.read<PaperlessDocumentsApi>()),
|
||||
Provider.value(value: context.read<DocumentChangedNotifier>()),
|
||||
Provider.value(value: context.read<CacheManager>()),
|
||||
Provider.value(value: userRepo),
|
||||
],
|
||||
builder: (context, _) {
|
||||
return BlocProvider(
|
||||
create: (context) => DocumentSearchCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||
.get(currentUser)!,
|
||||
),
|
||||
child: const DocumentSearchPage(),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> pushDocumentDetailsRoute(
|
||||
BuildContext context, {
|
||||
required DocumentModel document,
|
||||
bool isLabelClickable = true,
|
||||
bool allowEdit = true,
|
||||
String? titleAndContentQueryString,
|
||||
}) {
|
||||
return Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: context.read<ApiVersion>()),
|
||||
Provider.value(value: context.read<LabelRepository>()),
|
||||
Provider.value(value: context.read<DocumentChangedNotifier>()),
|
||||
Provider.value(value: context.read<PaperlessDocumentsApi>()),
|
||||
Provider.value(value: context.read<LocalNotificationService>()),
|
||||
Provider.value(value: context.read<CacheManager>()),
|
||||
Provider.value(value: context.read<ConnectivityCubit>()),
|
||||
if (context.read<ApiVersion>().hasMultiUserSupport)
|
||||
Provider.value(value: context.read<UserRepository>()),
|
||||
],
|
||||
child: DocumentDetailsRoute(
|
||||
document: document,
|
||||
isLabelClickable: isLabelClickable,
|
||||
builder: (_) => BlocProvider(
|
||||
create: (context) => DocumentSearchCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||
.get(currentUser)!,
|
||||
),
|
||||
child: const DocumentSearchPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -106,7 +66,7 @@ Future<void> pushSavedViewDetailsRoute(
|
||||
builder: (_) => MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: apiVersion),
|
||||
if (apiVersion.hasMultiUserSupport)
|
||||
if (context.watch<LocalUserAccount>().hasMultiUserSupport)
|
||||
Provider.value(value: context.read<UserRepository>()),
|
||||
Provider.value(value: context.read<LabelRepository>()),
|
||||
Provider.value(value: context.read<DocumentChangedNotifier>()),
|
||||
@@ -147,8 +107,10 @@ Future<SavedView?> pushAddSavedViewRoute(BuildContext context,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> pushLinkedDocumentsView(BuildContext context,
|
||||
{required DocumentFilter filter}) {
|
||||
Future<void> pushLinkedDocumentsView(
|
||||
BuildContext context, {
|
||||
required DocumentFilter filter,
|
||||
}) {
|
||||
return Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
@@ -161,7 +123,7 @@ Future<void> pushLinkedDocumentsView(BuildContext context,
|
||||
Provider.value(value: context.read<LocalNotificationService>()),
|
||||
Provider.value(value: context.read<CacheManager>()),
|
||||
Provider.value(value: context.read<ConnectivityCubit>()),
|
||||
if (context.read<ApiVersion>().hasMultiUserSupport)
|
||||
if (context.watch<LocalUserAccount>().hasMultiUserSupport)
|
||||
Provider.value(value: context.read<UserRepository>()),
|
||||
],
|
||||
builder: (context, _) => BlocProvider(
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/routes/typed/top_level/settings_route.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
@@ -91,18 +92,7 @@ class AppDrawer extends StatelessWidget {
|
||||
title: Text(
|
||||
S.of(context)!.settings,
|
||||
),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MultiProvider(
|
||||
providers: [
|
||||
Provider.value(
|
||||
value: context.read<PaperlessServerStatsApi>()),
|
||||
Provider.value(value: context.read<ApiVersion>()),
|
||||
],
|
||||
child: const SettingsPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () => SettingsRoute().push(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -45,7 +45,6 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
),
|
||||
),
|
||||
);
|
||||
loadSuggestions();
|
||||
loadMetaData();
|
||||
}
|
||||
|
||||
@@ -54,13 +53,6 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
_notifier.notifyDeleted(document);
|
||||
}
|
||||
|
||||
Future<void> loadSuggestions() async {
|
||||
final suggestions = await _api.findSuggestions(state.document);
|
||||
if (!isClosed) {
|
||||
emit(state.copyWith(suggestions: suggestions));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadMetaData() async {
|
||||
final metaData = await _api.getMetaData(state.document);
|
||||
if (!isClosed) {
|
||||
|
||||
@@ -7,7 +7,6 @@ class DocumentDetailsState with _$DocumentDetailsState {
|
||||
DocumentMetaData? metaData,
|
||||
@Default(false) bool isFullContentLoaded,
|
||||
String? fullContent,
|
||||
FieldSuggestions? suggestions,
|
||||
@Default({}) Map<int, Correspondent> correspondents,
|
||||
@Default({}) Map<int, DocumentType> documentTypes,
|
||||
@Default({}) Map<int, Tag> tags,
|
||||
|
||||
@@ -14,8 +14,6 @@ import 'package:paperless_mobile/features/document_details/view/widgets/document
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_overview_widget.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_permissions_widget.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_share_button.dart';
|
||||
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_edit/view/document_edit_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
@@ -24,6 +22,7 @@ import 'package:paperless_mobile/features/similar_documents/cubit/similar_docume
|
||||
import 'package:paperless_mobile/features/similar_documents/view/similar_documents_view.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
|
||||
class DocumentDetailsPage extends StatefulWidget {
|
||||
final bool isLabelClickable;
|
||||
@@ -46,9 +45,9 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final apiVersion = context.watch<ApiVersion>();
|
||||
|
||||
final tabLength = 4 + (apiVersion.hasMultiUserSupport ? 1 : 0);
|
||||
final hasMultiUserSupport =
|
||||
context.watch<LocalUserAccount>().hasMultiUserSupport;
|
||||
final tabLength = 4 + (hasMultiUserSupport ? 1 : 0);
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
Navigator.of(context)
|
||||
@@ -171,7 +170,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (apiVersion.hasMultiUserSupport)
|
||||
if (hasMultiUserSupport)
|
||||
Tab(
|
||||
child: Text(
|
||||
"Permissions",
|
||||
@@ -259,7 +258,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (apiVersion.hasMultiUserSupport)
|
||||
if (hasMultiUserSupport)
|
||||
CustomScrollView(
|
||||
controller: _pagingScrollController,
|
||||
slivers: [
|
||||
@@ -286,8 +285,10 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
}
|
||||
|
||||
Widget _buildEditButton() {
|
||||
final currentUser = context.watch<LocalUserAccount>();
|
||||
|
||||
bool canEdit = context.watchInternetConnection &&
|
||||
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||
currentUser.paperlessUser.canEditDocuments;
|
||||
if (!canEdit) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
@@ -302,7 +303,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
verticalOffset: 40,
|
||||
child: FloatingActionButton(
|
||||
child: const Icon(Icons.edit),
|
||||
onPressed: () => _onEdit(state.document),
|
||||
onPressed: () => EditDocumentRoute(state.document).push(context),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -316,9 +317,9 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
child: BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
builder: (context, connectivityState) {
|
||||
final isConnected = connectivityState.isConnected;
|
||||
|
||||
final canDelete = isConnected &&
|
||||
LocalUserAccount.current.paperlessUser.canDeleteDocuments;
|
||||
final currentUser = context.watch<LocalUserAccount>();
|
||||
final canDelete =
|
||||
isConnected && currentUser.paperlessUser.canDeleteDocuments;
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
@@ -360,47 +361,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onEdit(DocumentModel document) async {
|
||||
{
|
||||
final cubit = context.read<DocumentDetailsCubit>();
|
||||
Navigator.push<bool>(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(
|
||||
value: DocumentEditCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
document: document,
|
||||
),
|
||||
),
|
||||
BlocProvider<DocumentDetailsCubit>.value(
|
||||
value: cubit,
|
||||
),
|
||||
],
|
||||
child: BlocListener<DocumentEditCubit, DocumentEditState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.document != current.document,
|
||||
listener: (context, state) {
|
||||
cubit.replace(state.document);
|
||||
},
|
||||
child: BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||
builder: (context, state) {
|
||||
return DocumentEditPage(
|
||||
suggestions: state.suggestions,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
maintainState: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onOpenFileInSystemViewer() async {
|
||||
final status =
|
||||
await context.read<DocumentDetailsCubit>().openDocumentInSystemViewer();
|
||||
|
||||
@@ -47,7 +47,7 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userCanEditDocument =
|
||||
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||
context.watch<LocalUserAccount>().paperlessUser.canEditDocuments;
|
||||
return BlocListener<DocumentDetailsCubit, DocumentDetailsState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.document.archiveSerialNumber !=
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
@@ -47,7 +48,10 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
label: S.of(context)!.createdAt,
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.documentType != null &&
|
||||
LocalUserAccount.current.paperlessUser.canViewDocumentTypes)
|
||||
context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewDocumentTypes)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.documentType,
|
||||
content: LabelText<DocumentType>(
|
||||
@@ -56,7 +60,10 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.correspondent != null &&
|
||||
LocalUserAccount.current.paperlessUser.canViewCorrespondents)
|
||||
context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewCorrespondents)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.correspondent,
|
||||
content: LabelText<Correspondent>(
|
||||
@@ -65,7 +72,10 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.storagePath != null &&
|
||||
LocalUserAccount.current.paperlessUser.canViewStoragePaths)
|
||||
context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewStoragePaths)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.storagePath,
|
||||
content: LabelText<StoragePath>(
|
||||
@@ -73,7 +83,7 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.tags.isNotEmpty &&
|
||||
LocalUserAccount.current.paperlessUser.canViewTags)
|
||||
context.watch<LocalUserAccount>().paperlessUser.canViewTags)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.tags,
|
||||
content: Padding(
|
||||
|
||||
@@ -57,6 +57,11 @@ class DocumentEditCubit extends Cubit<DocumentEditState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadFieldSuggestions() async {
|
||||
final suggestions = await _docsApi.findSuggestions(state.document);
|
||||
emit(state.copyWith(suggestions: suggestions));
|
||||
}
|
||||
|
||||
void replace(DocumentModel document) {
|
||||
emit(state.copyWith(document: document));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ part of 'document_edit_cubit.dart';
|
||||
class DocumentEditState with _$DocumentEditState {
|
||||
const factory DocumentEditState({
|
||||
required DocumentModel document,
|
||||
FieldSuggestions? suggestions,
|
||||
@Default({}) Map<int, Correspondent> correspondents,
|
||||
@Default({}) Map<int, DocumentType> documentTypes,
|
||||
@Default({}) Map<int, StoragePath> storagePaths,
|
||||
|
||||
@@ -22,10 +22,8 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
|
||||
class DocumentEditPage extends StatefulWidget {
|
||||
final FieldSuggestions? suggestions;
|
||||
const DocumentEditPage({
|
||||
Key? key,
|
||||
required this.suggestions,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -44,19 +42,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
|
||||
bool _isSubmitLoading = false;
|
||||
|
||||
late final FieldSuggestions? _filteredSuggestions;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_filteredSuggestions = widget.suggestions
|
||||
?.documentDifference(context.read<DocumentEditCubit>().state.document);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DocumentEditCubit, DocumentEditState>(
|
||||
builder: (context, state) {
|
||||
final filteredSuggestions = state.suggestions?.documentDifference(
|
||||
context.read<DocumentEditCubit>().state.document);
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
@@ -94,8 +85,10 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
ListView(
|
||||
children: [
|
||||
_buildTitleFormField(state.document.title).padded(),
|
||||
_buildCreatedAtFormField(state.document.created)
|
||||
.padded(),
|
||||
_buildCreatedAtFormField(
|
||||
state.document.created,
|
||||
filteredSuggestions,
|
||||
).padded(),
|
||||
// Correspondent form field
|
||||
Column(
|
||||
children: [
|
||||
@@ -123,15 +116,17 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
name: fkCorrespondent,
|
||||
prefixIcon: const Icon(Icons.person_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: LocalUserAccount.current
|
||||
.paperlessUser.canCreateCorrespondents,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateCorrespondents,
|
||||
),
|
||||
if (_filteredSuggestions
|
||||
if (filteredSuggestions
|
||||
?.hasSuggestedCorrespondents ??
|
||||
false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions:
|
||||
_filteredSuggestions!.correspondents,
|
||||
filteredSuggestions!.correspondents,
|
||||
itemBuilder: (context, itemData) =>
|
||||
ActionChip(
|
||||
label: Text(
|
||||
@@ -160,8 +155,10 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
initialName: currentInput,
|
||||
),
|
||||
),
|
||||
canCreateNewLabel: LocalUserAccount.current
|
||||
.paperlessUser.canCreateDocumentTypes,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateDocumentTypes,
|
||||
addLabelText: S.of(context)!.addDocumentType,
|
||||
labelText: S.of(context)!.documentType,
|
||||
initialValue:
|
||||
@@ -175,12 +172,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
),
|
||||
if (_filteredSuggestions
|
||||
if (filteredSuggestions
|
||||
?.hasSuggestedDocumentTypes ??
|
||||
false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions:
|
||||
_filteredSuggestions!.documentTypes,
|
||||
filteredSuggestions!.documentTypes,
|
||||
itemBuilder: (context, itemData) =>
|
||||
ActionChip(
|
||||
label: Text(
|
||||
@@ -204,10 +201,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
RepositoryProvider.value(
|
||||
value: context.read<LabelRepository>(),
|
||||
child: AddStoragePathPage(
|
||||
initalName: initialValue),
|
||||
initialName: initialValue),
|
||||
),
|
||||
canCreateNewLabel: LocalUserAccount.current
|
||||
.paperlessUser.canCreateStoragePaths,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateStoragePaths,
|
||||
addLabelText: S.of(context)!.addStoragePath,
|
||||
labelText: S.of(context)!.storagePath,
|
||||
options: state.storagePaths,
|
||||
@@ -232,14 +231,14 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
include: state.document.tags.toList(),
|
||||
),
|
||||
).padded(),
|
||||
if (_filteredSuggestions?.tags
|
||||
if (filteredSuggestions?.tags
|
||||
.toSet()
|
||||
.difference(state.document.tags.toSet())
|
||||
.isNotEmpty ??
|
||||
false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions:
|
||||
(_filteredSuggestions?.tags.toSet() ?? {}),
|
||||
(filteredSuggestions?.tags.toSet() ?? {}),
|
||||
itemBuilder: (context, itemData) {
|
||||
final tag = state.tags[itemData]!;
|
||||
return ActionChip(
|
||||
@@ -343,7 +342,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCreatedAtFormField(DateTime? initialCreatedAtDate) {
|
||||
Widget _buildCreatedAtFormField(
|
||||
DateTime? initialCreatedAtDate, FieldSuggestions? filteredSuggestions) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -358,9 +358,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
format: DateFormat.yMMMMd(),
|
||||
initialEntryMode: DatePickerEntryMode.calendar,
|
||||
),
|
||||
if (_filteredSuggestions?.hasSuggestedDates ?? false)
|
||||
if (filteredSuggestions?.hasSuggestedDates ?? false)
|
||||
_buildSuggestionsSkeleton<DateTime>(
|
||||
suggestions: _filteredSuggestions!.dates,
|
||||
suggestions: filteredSuggestions!.dates,
|
||||
itemBuilder: (context, itemData) => ActionChip(
|
||||
label: Text(DateFormat.yMMMd().format(itemData)),
|
||||
onPressed: () => _formKey.currentState?.fields[fkCreatedDate]
|
||||
|
||||
@@ -91,7 +91,7 @@ class _DocumentSearchBarState extends State<DocumentSearchBar> {
|
||||
Provider.value(value: context.read<PaperlessDocumentsApi>()),
|
||||
Provider.value(value: context.read<CacheManager>()),
|
||||
Provider.value(value: context.read<ApiVersion>()),
|
||||
if (context.read<ApiVersion>().hasMultiUserSupport)
|
||||
if (context.watch<LocalUserAccount>().hasMultiUserSupport)
|
||||
Provider.value(value: context.read<UserRepository>()),
|
||||
],
|
||||
child: Provider(
|
||||
@@ -99,7 +99,7 @@ class _DocumentSearchBarState extends State<DocumentSearchBar> {
|
||||
context.read(),
|
||||
context.read(),
|
||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||
.get(LocalUserAccount.current.id)!,
|
||||
.get(context.watch<LocalUserAccount>().id)!,
|
||||
),
|
||||
builder: (_, __) => const DocumentSearchPage(),
|
||||
),
|
||||
@@ -112,19 +112,7 @@ class _DocumentSearchBarState extends State<DocumentSearchBar> {
|
||||
IconButton _buildUserAvatar(BuildContext context) {
|
||||
return IconButton(
|
||||
padding: const EdgeInsets.all(6),
|
||||
icon: GlobalSettingsBuilder(
|
||||
builder: (context, settings) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable:
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||
.listenable(),
|
||||
builder: (context, box, _) {
|
||||
final account = box.get(settings.currentLoggedInUser!)!;
|
||||
return UserAvatar(account: account);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
icon: UserAvatar(account: context.watch<LocalUserAccount>()),
|
||||
onPressed: () {
|
||||
final apiVersion = context.read<ApiVersion>();
|
||||
showDialog(
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:math' as math;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
|
||||
@@ -11,6 +12,7 @@ import 'package:paperless_mobile/features/document_search/view/remove_history_en
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
|
||||
class DocumentSearchPage extends StatefulWidget {
|
||||
const DocumentSearchPage({super.key});
|
||||
@@ -218,11 +220,8 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
||||
hasLoaded: state.hasLoaded,
|
||||
enableHeroAnimation: false,
|
||||
onTap: (document) {
|
||||
pushDocumentDetailsRoute(
|
||||
context,
|
||||
document: document,
|
||||
isLabelClickable: false,
|
||||
);
|
||||
DocumentDetailsRoute($extra: document, isLabelClickable: false)
|
||||
.push(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
|
||||
@@ -25,7 +25,7 @@ class SliverSearchBar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (LocalUserAccount.current.paperlessUser.canViewDocuments) {
|
||||
if (context.watch<LocalUserAccount>().paperlessUser.canViewDocuments) {
|
||||
return SliverAppBar(
|
||||
toolbarHeight: kToolbarHeight,
|
||||
flexibleSpace: Container(
|
||||
@@ -49,7 +49,7 @@ class SliverSearchBar extends StatelessWidget {
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||
.listenable(),
|
||||
builder: (context, box, _) {
|
||||
final account = box.get(settings.currentLoggedInUser!)!;
|
||||
final account = box.get(settings.loggedInUserId!)!;
|
||||
return UserAvatar(account: account);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -198,8 +198,10 @@ class _DocumentUploadPreparationPageState
|
||||
),
|
||||
),
|
||||
// Correspondent
|
||||
if (LocalUserAccount
|
||||
.current.paperlessUser.canViewCorrespondents)
|
||||
if (context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewCorrespondents)
|
||||
LabelFormField<Correspondent>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
@@ -220,11 +222,16 @@ class _DocumentUploadPreparationPageState
|
||||
options: state.correspondents,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: LocalUserAccount
|
||||
.current.paperlessUser.canCreateCorrespondents,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateCorrespondents,
|
||||
),
|
||||
// Document type
|
||||
if (LocalUserAccount.current.paperlessUser.canViewDocumentTypes)
|
||||
if (context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewDocumentTypes)
|
||||
LabelFormField<DocumentType>(
|
||||
showAnyAssignedOption: false,
|
||||
showNotAssignedOption: false,
|
||||
@@ -245,10 +252,12 @@ class _DocumentUploadPreparationPageState
|
||||
options: state.documentTypes,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: LocalUserAccount
|
||||
.current.paperlessUser.canCreateDocumentTypes,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateDocumentTypes,
|
||||
),
|
||||
if (LocalUserAccount.current.paperlessUser.canViewTags)
|
||||
if (context.watch<LocalUserAccount>().paperlessUser.canViewTags)
|
||||
TagsFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
allowCreation: true,
|
||||
@@ -296,7 +305,7 @@ class _DocumentUploadPreparationPageState
|
||||
),
|
||||
userId: Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.currentLoggedInUser!,
|
||||
.loggedInUserId!,
|
||||
title: title,
|
||||
documentType: docType,
|
||||
correspondent: correspondent,
|
||||
|
||||
@@ -23,6 +23,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:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
|
||||
class DocumentFilterIntent {
|
||||
final DocumentFilter? filter;
|
||||
@@ -55,7 +56,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
void initState() {
|
||||
super.initState();
|
||||
final showSavedViews =
|
||||
LocalUserAccount.current.paperlessUser.canViewSavedViews;
|
||||
context.read<LocalUserAccount>().paperlessUser.canViewSavedViews;
|
||||
_tabController = TabController(
|
||||
length: showSavedViews ? 2 : 1,
|
||||
vsync: this,
|
||||
@@ -116,7 +117,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
return SafeArea(
|
||||
top: true,
|
||||
child: Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
drawer: AppDrawer(),
|
||||
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
||||
@@ -232,7 +233,9 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(text: S.of(context)!.documents),
|
||||
if (LocalUserAccount.current.paperlessUser
|
||||
if (context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewSavedViews)
|
||||
Tab(text: S.of(context)!.views),
|
||||
],
|
||||
@@ -276,8 +279,10 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
);
|
||||
},
|
||||
),
|
||||
if (LocalUserAccount
|
||||
.current.paperlessUser.canViewSavedViews)
|
||||
if (context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canViewSavedViews)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return _buildSavedViewsTab(
|
||||
@@ -378,7 +383,9 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
final allowToggleFilter = state.selection.isEmpty;
|
||||
return SliverAdaptiveDocumentsView(
|
||||
viewType: state.viewType,
|
||||
onTap: _openDetails,
|
||||
onTap: (document) {
|
||||
DocumentDetailsRoute($extra: document).push(context);
|
||||
},
|
||||
onSelected:
|
||||
context.read<DocumentsCubit>().toggleDocumentSelection,
|
||||
hasInternetConnection: connectivityState.isConnected,
|
||||
@@ -488,13 +495,6 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
}
|
||||
}
|
||||
|
||||
void _openDetails(DocumentModel document) {
|
||||
pushDocumentDetailsRoute(
|
||||
context,
|
||||
document: document,
|
||||
);
|
||||
}
|
||||
|
||||
void _addTagToFilter(int tagId) {
|
||||
final cubit = context.read<DocumentsCubit>();
|
||||
try {
|
||||
|
||||
@@ -2,7 +2,12 @@ import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.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/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
@@ -32,6 +37,12 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentUserId = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.loggedInUserId;
|
||||
final paperlessUser = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||
.get(currentUserId)!
|
||||
.paperlessUser;
|
||||
final size = MediaQuery.of(context).size;
|
||||
final insets = MediaQuery.of(context).viewInsets;
|
||||
final padding = MediaQuery.of(context).viewPadding;
|
||||
@@ -104,48 +115,51 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.person_outline,
|
||||
size: 16,
|
||||
).paddedOnly(right: 4.0),
|
||||
CorrespondentWidget(
|
||||
onSelected: onCorrespondentSelected,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
correspondent: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.correspondents[document.correspondent],
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.description_outlined,
|
||||
size: 16,
|
||||
).paddedOnly(right: 4.0),
|
||||
DocumentTypeWidget(
|
||||
onSelected: onDocumentTypeSelected,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
documentType: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.documentTypes[document.documentType],
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
TagsWidget(
|
||||
tags: document.tags
|
||||
.map((e) => context.watch<LabelRepository>().state.tags[e]!)
|
||||
.toList(),
|
||||
onTagSelected: onTagSelected,
|
||||
).padded(),
|
||||
if (paperlessUser.canViewCorrespondents)
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.person_outline,
|
||||
size: 16,
|
||||
).paddedOnly(right: 4.0),
|
||||
CorrespondentWidget(
|
||||
onSelected: onCorrespondentSelected,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
correspondent: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.correspondents[document.correspondent],
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
if (paperlessUser.canViewDocumentTypes)
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.description_outlined,
|
||||
size: 16,
|
||||
).paddedOnly(right: 4.0),
|
||||
DocumentTypeWidget(
|
||||
onSelected: onDocumentTypeSelected,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
documentType: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.documentTypes[document.documentType],
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
if (paperlessUser.canViewTags)
|
||||
TagsWidget(
|
||||
tags: document.tags
|
||||
.map((e) => context.watch<LabelRepository>().state.tags[e]!)
|
||||
.toList(),
|
||||
onTagSelected: onTagSelected,
|
||||
).padded(),
|
||||
if (highlights != null)
|
||||
Html(
|
||||
data: '<p>${highlights!}</p>',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
@@ -160,8 +161,10 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
initialValue: widget.initialFilter.documentType,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: false,
|
||||
canCreateNewLabel:
|
||||
LocalUserAccount.current.paperlessUser.canCreateDocumentTypes,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateDocumentTypes,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -173,8 +176,10 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
initialValue: widget.initialFilter.correspondent,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
allowSelectUnassigned: false,
|
||||
canCreateNewLabel:
|
||||
LocalUserAccount.current.paperlessUser.canCreateCorrespondents,
|
||||
canCreateNewLabel: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canCreateCorrespondents,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -187,7 +192,7 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
allowSelectUnassigned: false,
|
||||
canCreateNewLabel:
|
||||
LocalUserAccount.current.paperlessUser.canCreateStoragePaths,
|
||||
context.watch<LocalUserAccount>().paperlessUser.canCreateStoragePaths,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import 'package:paperless_mobile/features/labels/storage_path/view/widgets/stora
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class AddStoragePathPage extends StatelessWidget {
|
||||
final String? initalName;
|
||||
const AddStoragePathPage({Key? key, this.initalName}) : super(key: key);
|
||||
final String? initialName;
|
||||
const AddStoragePathPage({Key? key, this.initialName}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -19,7 +19,7 @@ class AddStoragePathPage extends StatelessWidget {
|
||||
child: AddLabelPage<StoragePath>(
|
||||
pageTitle: Text(S.of(context)!.addStoragePath),
|
||||
fromJsonT: StoragePath.fromJson,
|
||||
initialName: initalName,
|
||||
initialName: initialName,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().addStoragePath(label),
|
||||
additionalFields: const [
|
||||
|
||||
@@ -10,8 +10,8 @@ import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class AddTagPage extends StatelessWidget {
|
||||
final String? initialValue;
|
||||
const AddTagPage({Key? key, this.initialValue}) : super(key: key);
|
||||
final String? initialName;
|
||||
const AddTagPage({Key? key, this.initialName}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -22,7 +22,7 @@ class AddTagPage extends StatelessWidget {
|
||||
child: AddLabelPage<Tag>(
|
||||
pageTitle: Text(S.of(context)!.addTag),
|
||||
fromJsonT: Tag.fromJson,
|
||||
initialName: initialValue,
|
||||
initialName: initialName,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().addTag(label),
|
||||
additionalFields: [
|
||||
|
||||
@@ -24,8 +24,10 @@ class EditCorrespondentPage extends StatelessWidget {
|
||||
context.read<EditLabelCubit>().replaceCorrespondent(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeCorrespondent(label),
|
||||
canDelete:
|
||||
LocalUserAccount.current.paperlessUser.canDeleteCorrespondents,
|
||||
canDelete: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canDeleteCorrespondents,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -22,8 +22,10 @@ class EditDocumentTypePage extends StatelessWidget {
|
||||
context.read<EditLabelCubit>().replaceDocumentType(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeDocumentType(label),
|
||||
canDelete:
|
||||
LocalUserAccount.current.paperlessUser.canDeleteDocumentTypes,
|
||||
canDelete: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canDeleteDocumentTypes,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@ class EditStoragePathPage extends StatelessWidget {
|
||||
context.read<EditLabelCubit>().replaceStoragePath(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeStoragePath(label),
|
||||
canDelete: LocalUserAccount.current.paperlessUser.canDeleteStoragePaths,
|
||||
canDelete: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
.canDeleteStoragePaths,
|
||||
additionalFields: [
|
||||
StoragePathAutofillFormBuilderField(
|
||||
name: StoragePath.pathKey,
|
||||
|
||||
@@ -26,7 +26,8 @@ class EditTagPage extends StatelessWidget {
|
||||
context.read<EditLabelCubit>().replaceTag(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeTag(label),
|
||||
canDelete: LocalUserAccount.current.paperlessUser.canDeleteTags,
|
||||
canDelete:
|
||||
context.watch<LocalUserAccount>().paperlessUser.canDeleteTags,
|
||||
additionalFields: [
|
||||
FormBuilderColorPickerField(
|
||||
initialValue: tag.color,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||
@@ -68,7 +69,7 @@ class _LabelFormState<T extends Label> extends State<LabelForm<T>> {
|
||||
Widget build(BuildContext context) {
|
||||
List<MatchingAlgorithm> selectableMatchingAlgorithmValues =
|
||||
getSelectableMatchingAlgorithmValues(
|
||||
context.watch<ApiVersion>().hasMultiUserSupport,
|
||||
context.watch<LocalUserAccount>().hasMultiUserSupport,
|
||||
);
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
|
||||
@@ -1,330 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.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/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/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';
|
||||
import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:responsive_builder/responsive_builder.dart';
|
||||
|
||||
/// Wrapper around all functionality for a logged in user.
|
||||
/// Performs initialization logic.
|
||||
class HomePage extends StatefulWidget {
|
||||
final int paperlessApiVersion;
|
||||
const HomePage({Key? key, required this.paperlessApiVersion})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
_HomePageState createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||
int _currentIndex = 0;
|
||||
Timer? _inboxTimer;
|
||||
late final StreamSubscription _shareMediaSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.currentLoggedInUser!;
|
||||
// For sharing files coming from outside the app while the app is still opened
|
||||
_shareMediaSubscription = ReceiveSharingIntent.getMediaStream().listen(
|
||||
(files) =>
|
||||
ShareIntentQueue.instance.addAll(files, userId: currentUser));
|
||||
// For sharing files coming from outside the app while the app is closed
|
||||
ReceiveSharingIntent.getInitialMedia().then((files) =>
|
||||
ShareIntentQueue.instance.addAll(files, userId: currentUser));
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_listenForReceivedFiles();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
void _listenToInboxChanges() {
|
||||
if (LocalUserAccount.current.paperlessUser.canViewTags) {
|
||||
_inboxTimer = Timer.periodic(const Duration(seconds: 60), (timer) {
|
||||
if (!mounted) {
|
||||
timer.cancel();
|
||||
} else {
|
||||
context.read<InboxCubit>().refreshItemsInInboxCount();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
log('App is now in foreground');
|
||||
context.read<ConnectivityCubit>().reload();
|
||||
log("Reloaded device connectivity state");
|
||||
if (!(_inboxTimer?.isActive ?? true)) {
|
||||
_listenToInboxChanges();
|
||||
}
|
||||
break;
|
||||
case AppLifecycleState.inactive:
|
||||
case AppLifecycleState.paused:
|
||||
case AppLifecycleState.detached:
|
||||
default:
|
||||
log('App is now in background');
|
||||
_inboxTimer?.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_inboxTimer?.cancel();
|
||||
_shareMediaSubscription.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _listenForReceivedFiles() async {
|
||||
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.currentLoggedInUser!;
|
||||
if (ShareIntentQueue.instance.userHasUnhandlesFiles(currentUser)) {
|
||||
await _handleReceivedFile(ShareIntentQueue.instance.pop(currentUser)!);
|
||||
}
|
||||
ShareIntentQueue.instance.addListener(() async {
|
||||
final queue = ShareIntentQueue.instance;
|
||||
while (queue.userHasUnhandlesFiles(currentUser)) {
|
||||
final file = queue.pop(currentUser)!;
|
||||
await _handleReceivedFile(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool _isFileTypeSupported(SharedMediaFile file) {
|
||||
return supportedFileExtensions.contains(
|
||||
file.path.split('.').last.toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _handleReceivedFile(final SharedMediaFile file) async {
|
||||
SharedMediaFile mediaFile;
|
||||
if (Platform.isIOS) {
|
||||
// Workaround for file not found on iOS: https://stackoverflow.com/a/72813212
|
||||
mediaFile = SharedMediaFile(
|
||||
file.path.replaceAll('file://', ''),
|
||||
file.thumbnail,
|
||||
file.duration,
|
||||
file.type,
|
||||
);
|
||||
} else {
|
||||
mediaFile = file;
|
||||
}
|
||||
debugPrint("Consuming media file: ${mediaFile.path}");
|
||||
if (!_isFileTypeSupported(mediaFile)) {
|
||||
Fluttertoast.showToast(
|
||||
msg: translateError(context, ErrorCode.unsupportedFileFormat),
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
// As stated in the docs, SystemNavigator.pop() is ignored on IOS to comply with HCI guidelines.
|
||||
await SystemNavigator.pop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LocalUserAccount.current.paperlessUser.canCreateDocuments) {
|
||||
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 = await File(mediaFile.path).readAsBytes();
|
||||
final result = await pushDocumentUploadPreparationPage(
|
||||
context,
|
||||
bytes: bytes,
|
||||
filename: fileDescription.filename,
|
||||
title: fileDescription.filename,
|
||||
fileExtension: fileDescription.extension,
|
||||
);
|
||||
if (result?.success ?? false) {
|
||||
await Fluttertoast.showToast(
|
||||
msg: S.of(context)!.documentSuccessfullyUploadedProcessing,
|
||||
);
|
||||
SystemNavigator.pop();
|
||||
}
|
||||
} else {
|
||||
Fluttertoast.showToast(
|
||||
msg: S.of(context)!.couldNotAccessReceivedFile,
|
||||
toastLength: Toast.LENGTH_LONG,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final destinations = [
|
||||
RouteDescription(
|
||||
icon: const Icon(Icons.description_outlined),
|
||||
selectedIcon: Icon(
|
||||
Icons.description,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
label: S.of(context)!.documents,
|
||||
),
|
||||
if (LocalUserAccount.current.paperlessUser.canCreateDocuments)
|
||||
RouteDescription(
|
||||
icon: const Icon(Icons.document_scanner_outlined),
|
||||
selectedIcon: Icon(
|
||||
Icons.document_scanner,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
label: S.of(context)!.scanner,
|
||||
),
|
||||
RouteDescription(
|
||||
icon: const Icon(Icons.sell_outlined),
|
||||
selectedIcon: Icon(
|
||||
Icons.sell,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
label: S.of(context)!.labels,
|
||||
),
|
||||
if (LocalUserAccount.current.paperlessUser.canViewTags)
|
||||
RouteDescription(
|
||||
icon: const Icon(Icons.inbox_outlined),
|
||||
selectedIcon: Icon(
|
||||
Icons.inbox,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
label: S.of(context)!.inbox,
|
||||
badgeBuilder: (icon) => BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
return Badge.count(
|
||||
isLabelVisible: state.itemsInInboxCount > 0,
|
||||
count: state.itemsInInboxCount,
|
||||
child: icon,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
];
|
||||
final routes = <Widget>[
|
||||
const DocumentsPage(),
|
||||
if (LocalUserAccount.current.paperlessUser.canCreateDocuments)
|
||||
const ScannerPage(),
|
||||
const LabelsPage(),
|
||||
if (LocalUserAccount.current.paperlessUser.canViewTags) const InboxPage(),
|
||||
];
|
||||
return MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<ConnectivityCubit, ConnectivityState>(
|
||||
// If app was started offline, load data once it comes back online.
|
||||
listenWhen: (previous, current) =>
|
||||
previous != ConnectivityState.connected &&
|
||||
current == ConnectivityState.connected,
|
||||
listener: (context, state) async {
|
||||
try {
|
||||
debugPrint(
|
||||
"[HomePage] BlocListener#listener: "
|
||||
"Loading saved views and labels...",
|
||||
);
|
||||
await Future.wait([
|
||||
context.read<LabelRepository>().initialize(),
|
||||
context.read<SavedViewRepository>().initialize(),
|
||||
]);
|
||||
debugPrint("[HomePage] BlocListener#listener: "
|
||||
"Saved views and labels successfully loaded.");
|
||||
} catch (error, stackTrace) {
|
||||
debugPrint(
|
||||
'[HomePage] BlocListener.listener: '
|
||||
'An error occurred while loading saved views and labels.\n'
|
||||
'${error.toString()}',
|
||||
);
|
||||
debugPrintStack(stackTrace: stackTrace);
|
||||
}
|
||||
},
|
||||
),
|
||||
BlocListener<TaskStatusCubit, TaskStatusState>(
|
||||
listener: (context, state) {
|
||||
if (state.task != null) {
|
||||
// Handle local notifications on task change (only when app is running for now).
|
||||
context
|
||||
.read<LocalNotificationService>()
|
||||
.notifyTaskChanged(state.task!);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
child: ResponsiveBuilder(
|
||||
builder: (context, sizingInformation) {
|
||||
if (!sizingInformation.isMobile) {
|
||||
return Scaffold(
|
||||
body: Row(
|
||||
children: [
|
||||
NavigationRail(
|
||||
labelType: NavigationRailLabelType.all,
|
||||
destinations: destinations
|
||||
.map((e) => e.toNavigationRailDestination())
|
||||
.toList(),
|
||||
selectedIndex: _currentIndex,
|
||||
onDestinationSelected: _onNavigationChanged,
|
||||
),
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
Expanded(
|
||||
child: routes[_currentIndex],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return Scaffold(
|
||||
bottomNavigationBar: NavigationBar(
|
||||
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
|
||||
elevation: 4.0,
|
||||
selectedIndex: _currentIndex,
|
||||
onDestinationSelected: _onNavigationChanged,
|
||||
destinations:
|
||||
destinations.map((e) => e.toNavigationDestination()).toList(),
|
||||
),
|
||||
body: routes[_currentIndex],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onNavigationChanged(index) {
|
||||
if (_currentIndex != index) {
|
||||
setState(() => _currentIndex = index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.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/repository/user_repository.dart';
|
||||
import 'package:paperless_mobile/core/security/session_manager.dart';
|
||||
import 'package:paperless_mobile/core/service/dio_file_service.dart';
|
||||
import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/home/view/home_page.dart';
|
||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class HomeRoute extends StatelessWidget {
|
||||
/// The id of the currently authenticated user (e.g. demo@paperless.example.com)
|
||||
final String localUserId;
|
||||
|
||||
/// The Paperless API version of the currently connected instance
|
||||
final int paperlessApiVersion;
|
||||
|
||||
// A factory providing the API implementations given an API version
|
||||
final PaperlessApiFactory paperlessProviderFactory;
|
||||
|
||||
const HomeRoute({
|
||||
super.key,
|
||||
required this.paperlessApiVersion,
|
||||
required this.paperlessProviderFactory,
|
||||
required this.localUserId,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GlobalSettingsBuilder(
|
||||
builder: (context, settings) {
|
||||
final currentLocalUserId = settings.currentLoggedInUser;
|
||||
if (currentLocalUserId == null) {
|
||||
// This is the case when the current user logs out of the app.
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
final currentUser =
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||
.get(currentLocalUserId)!;
|
||||
final apiVersion = ApiVersion(paperlessApiVersion);
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: apiVersion),
|
||||
Provider<CacheManager>(
|
||||
create: (context) => CacheManager(
|
||||
Config(
|
||||
// Isolated cache per user.
|
||||
localUserId,
|
||||
fileService:
|
||||
DioFileService(context.read<SessionManager>().client),
|
||||
),
|
||||
),
|
||||
),
|
||||
ProxyProvider<SessionManager, PaperlessDocumentsApi>(
|
||||
update: (context, value, previous) =>
|
||||
paperlessProviderFactory.createDocumentsApi(
|
||||
value.client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
ProxyProvider<SessionManager, PaperlessLabelsApi>(
|
||||
update: (context, value, previous) =>
|
||||
paperlessProviderFactory.createLabelsApi(
|
||||
value.client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
ProxyProvider<SessionManager, PaperlessSavedViewsApi>(
|
||||
update: (context, value, previous) =>
|
||||
paperlessProviderFactory.createSavedViewsApi(
|
||||
value.client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
ProxyProvider<SessionManager, PaperlessServerStatsApi>(
|
||||
update: (context, value, previous) =>
|
||||
paperlessProviderFactory.createServerStatsApi(
|
||||
value.client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
ProxyProvider<SessionManager, PaperlessTasksApi>(
|
||||
update: (context, value, previous) =>
|
||||
paperlessProviderFactory.createTasksApi(
|
||||
value.client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
if (apiVersion.hasMultiUserSupport)
|
||||
ProxyProvider<SessionManager, PaperlessUserApiV3>(
|
||||
update: (context, value, previous) => PaperlessUserApiV3Impl(
|
||||
value.client,
|
||||
),
|
||||
),
|
||||
],
|
||||
builder: (context, child) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ProxyProvider<PaperlessLabelsApi, LabelRepository>(
|
||||
update: (context, value, previous) {
|
||||
final repo = LabelRepository(value);
|
||||
if (currentUser.paperlessUser.canViewCorrespondents) {
|
||||
repo.findAllCorrespondents();
|
||||
}
|
||||
if (currentUser.paperlessUser.canViewDocumentTypes) {
|
||||
repo.findAllDocumentTypes();
|
||||
}
|
||||
if (currentUser.paperlessUser.canViewTags) {
|
||||
repo.findAllTags();
|
||||
}
|
||||
if (currentUser.paperlessUser.canViewStoragePaths) {
|
||||
repo.findAllStoragePaths();
|
||||
}
|
||||
return repo;
|
||||
},
|
||||
),
|
||||
ProxyProvider<PaperlessSavedViewsApi, SavedViewRepository>(
|
||||
update: (context, value, previous) {
|
||||
final repo = SavedViewRepository(value);
|
||||
if (currentUser.paperlessUser.canViewSavedViews) {
|
||||
repo.initialize();
|
||||
}
|
||||
return repo;
|
||||
},
|
||||
),
|
||||
],
|
||||
builder: (context, child) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ProxyProvider3<
|
||||
PaperlessDocumentsApi,
|
||||
DocumentChangedNotifier,
|
||||
LabelRepository,
|
||||
DocumentsCubit>(
|
||||
update:
|
||||
(context, docApi, notifier, labelRepo, previous) =>
|
||||
DocumentsCubit(
|
||||
docApi,
|
||||
notifier,
|
||||
labelRepo,
|
||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||
.get(currentLocalUserId)!,
|
||||
)..initialize(),
|
||||
),
|
||||
Provider(
|
||||
create: (context) =>
|
||||
DocumentScannerCubit(context.read())),
|
||||
ProxyProvider4<
|
||||
PaperlessDocumentsApi,
|
||||
PaperlessServerStatsApi,
|
||||
LabelRepository,
|
||||
DocumentChangedNotifier,
|
||||
InboxCubit>(
|
||||
update: (context, docApi, statsApi, labelRepo, notifier,
|
||||
previous) =>
|
||||
InboxCubit(
|
||||
docApi,
|
||||
statsApi,
|
||||
labelRepo,
|
||||
notifier,
|
||||
)..initialize(),
|
||||
),
|
||||
ProxyProvider<SavedViewRepository, SavedViewCubit>(
|
||||
update: (context, savedViewRepo, previous) =>
|
||||
SavedViewCubit(savedViewRepo),
|
||||
),
|
||||
ProxyProvider<LabelRepository, LabelCubit>(
|
||||
update: (context, value, previous) => LabelCubit(value),
|
||||
),
|
||||
ProxyProvider<PaperlessTasksApi, TaskStatusCubit>(
|
||||
update: (context, value, previous) =>
|
||||
TaskStatusCubit(value),
|
||||
),
|
||||
if (paperlessApiVersion >= 3)
|
||||
ProxyProvider<PaperlessUserApiV3, UserRepository>(
|
||||
update: (context, value, previous) =>
|
||||
UserRepository(value)..initialize(),
|
||||
),
|
||||
],
|
||||
child: HomePage(paperlessApiVersion: paperlessApiVersion),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
208
lib/features/home/view/home_shell_widget.dart
Normal file
208
lib/features/home/view/home_shell_widget.dart
Normal file
@@ -0,0 +1,208 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/core/factory/paperless_api_factory.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/repository/user_repository.dart';
|
||||
import 'package:paperless_mobile/core/security/session_manager.dart';
|
||||
import 'package:paperless_mobile/core/service/dio_file_service.dart';
|
||||
import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class HomeShellWidget extends StatelessWidget {
|
||||
/// The id of the currently authenticated user (e.g. demo@paperless.example.com)
|
||||
final String localUserId;
|
||||
|
||||
/// The Paperless API version of the currently connected instance
|
||||
final int paperlessApiVersion;
|
||||
|
||||
// A factory providing the API implementations given an API version
|
||||
final PaperlessApiFactory paperlessProviderFactory;
|
||||
|
||||
final Widget child;
|
||||
|
||||
const HomeShellWidget({
|
||||
super.key,
|
||||
required this.paperlessApiVersion,
|
||||
required this.paperlessProviderFactory,
|
||||
required this.localUserId,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GlobalSettingsBuilder(
|
||||
builder: (context, settings) {
|
||||
final currentUserId = settings.loggedInUserId;
|
||||
if (currentUserId == null) {
|
||||
// This is the case when the current user logs out of the app.
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
final apiVersion = ApiVersion(paperlessApiVersion);
|
||||
return ValueListenableBuilder(
|
||||
valueListenable:
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||
.listenable(keys: [currentUserId]),
|
||||
builder: (context, box, _) {
|
||||
final currentLocalUser = box.get(currentUserId)!;
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: currentLocalUser),
|
||||
Provider.value(value: apiVersion),
|
||||
Provider(
|
||||
create: (context) => CacheManager(
|
||||
Config(
|
||||
// Isolated cache per user.
|
||||
localUserId,
|
||||
fileService:
|
||||
DioFileService(context.read<SessionManager>().client),
|
||||
),
|
||||
),
|
||||
),
|
||||
Provider(
|
||||
create: (context) =>
|
||||
paperlessProviderFactory.createDocumentsApi(
|
||||
context.read<SessionManager>().client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
Provider(
|
||||
create: (context) => paperlessProviderFactory.createLabelsApi(
|
||||
context.read<SessionManager>().client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
Provider(
|
||||
create: (context) =>
|
||||
paperlessProviderFactory.createSavedViewsApi(
|
||||
context.read<SessionManager>().client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
Provider(
|
||||
create: (context) =>
|
||||
paperlessProviderFactory.createServerStatsApi(
|
||||
context.read<SessionManager>().client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
Provider(
|
||||
create: (context) => paperlessProviderFactory.createTasksApi(
|
||||
context.read<SessionManager>().client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
if (currentLocalUser.hasMultiUserSupport)
|
||||
Provider(
|
||||
create: (context) => PaperlessUserApiV3Impl(
|
||||
context.read<SessionManager>().client,
|
||||
),
|
||||
),
|
||||
],
|
||||
builder: (context, _) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider(
|
||||
create: (context) {
|
||||
final repo = LabelRepository(context.read());
|
||||
if (currentLocalUser
|
||||
.paperlessUser.canViewCorrespondents) {
|
||||
repo.findAllCorrespondents();
|
||||
}
|
||||
if (currentLocalUser
|
||||
.paperlessUser.canViewDocumentTypes) {
|
||||
repo.findAllDocumentTypes();
|
||||
}
|
||||
if (currentLocalUser.paperlessUser.canViewTags) {
|
||||
repo.findAllTags();
|
||||
}
|
||||
if (currentLocalUser
|
||||
.paperlessUser.canViewStoragePaths) {
|
||||
repo.findAllStoragePaths();
|
||||
}
|
||||
return repo;
|
||||
},
|
||||
),
|
||||
Provider(
|
||||
create: (context) {
|
||||
final repo = SavedViewRepository(context.read());
|
||||
if (currentLocalUser.paperlessUser.canViewSavedViews) {
|
||||
repo.initialize();
|
||||
}
|
||||
return repo;
|
||||
},
|
||||
),
|
||||
],
|
||||
builder: (context, _) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider(
|
||||
create: (context) => DocumentsCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
Hive.box<LocalUserAppState>(
|
||||
HiveBoxes.localUserAppState)
|
||||
.get(currentUserId)!,
|
||||
)..initialize(),
|
||||
),
|
||||
Provider(
|
||||
create: (context) =>
|
||||
DocumentScannerCubit(context.read()),
|
||||
),
|
||||
if (currentLocalUser.paperlessUser.canViewDocuments &&
|
||||
currentLocalUser.paperlessUser.canViewTags)
|
||||
Provider(
|
||||
create: (context) => InboxCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
).initialize(),
|
||||
),
|
||||
Provider(
|
||||
create: (context) => SavedViewCubit(
|
||||
context.read(),
|
||||
),
|
||||
),
|
||||
Provider(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
),
|
||||
Provider(
|
||||
create: (context) => TaskStatusCubit(
|
||||
context.read(),
|
||||
),
|
||||
),
|
||||
if (currentLocalUser.hasMultiUserSupport)
|
||||
Provider(
|
||||
create: (context) => UserRepository(
|
||||
context.read(),
|
||||
)..initialize(),
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
class ApiVersion {
|
||||
final int version;
|
||||
|
||||
ApiVersion(this.version);
|
||||
const ApiVersion(this.version);
|
||||
|
||||
bool get hasMultiUserSupport => version >= 3;
|
||||
|
||||
}
|
||||
|
||||
168
lib/features/home/view/scaffold_with_navigation_bar.dart
Normal file
168
lib/features/home/view/scaffold_with_navigation_bar.dart
Normal file
@@ -0,0 +1,168 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
|
||||
const _landingPage = 0;
|
||||
const _documentsIndex = 1;
|
||||
const _scannerIndex = 2;
|
||||
const _labelsIndex = 3;
|
||||
const _inboxIndex = 4;
|
||||
|
||||
class ScaffoldWithNavigationBar extends StatefulWidget {
|
||||
final UserModel authenticatedUser;
|
||||
final StatefulNavigationShell navigationShell;
|
||||
const ScaffoldWithNavigationBar({
|
||||
super.key,
|
||||
required this.authenticatedUser,
|
||||
required this.navigationShell,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ScaffoldWithNavigationBar> createState() =>
|
||||
ScaffoldWithNavigationBarState();
|
||||
}
|
||||
|
||||
class ScaffoldWithNavigationBarState extends State<ScaffoldWithNavigationBar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final disabledColor = Theme.of(context).disabledColor;
|
||||
final primaryColor = Theme.of(context).colorScheme.primary;
|
||||
return Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
bottomNavigationBar: NavigationBar(
|
||||
selectedIndex: widget.navigationShell.currentIndex,
|
||||
onDestinationSelected: (index) {
|
||||
switch (index) {
|
||||
case _landingPage:
|
||||
widget.navigationShell.goBranch(index);
|
||||
break;
|
||||
case _documentsIndex:
|
||||
if (widget.authenticatedUser.canViewDocuments) {
|
||||
widget.navigationShell.goBranch(index);
|
||||
} else {
|
||||
showSnackBar(
|
||||
context, "You do not have permission to access this page.");
|
||||
}
|
||||
break;
|
||||
case _scannerIndex:
|
||||
if (widget.authenticatedUser.canCreateDocuments) {
|
||||
widget.navigationShell.goBranch(index);
|
||||
} else {
|
||||
showSnackBar(
|
||||
context, "You do not have permission to access this page.");
|
||||
}
|
||||
break;
|
||||
case _labelsIndex:
|
||||
if (widget.authenticatedUser.canViewAnyLabel) {
|
||||
widget.navigationShell.goBranch(index);
|
||||
} else {
|
||||
showSnackBar(
|
||||
context, "You do not have permission to access this page.");
|
||||
}
|
||||
break;
|
||||
case _inboxIndex:
|
||||
if (widget.authenticatedUser.canViewDocuments &&
|
||||
widget.authenticatedUser.canViewTags) {
|
||||
widget.navigationShell.goBranch(index);
|
||||
} else {
|
||||
showSnackBar(
|
||||
context, "You do not have permission to access this page.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
destinations: [
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.home_outlined),
|
||||
selectedIcon: Icon(
|
||||
Icons.home,
|
||||
color: primaryColor,
|
||||
),
|
||||
label: "Home", //TODO: INTL
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(
|
||||
Icons.description_outlined,
|
||||
color: !widget.authenticatedUser.canViewDocuments
|
||||
? disabledColor
|
||||
: null,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.description,
|
||||
color: primaryColor,
|
||||
),
|
||||
label: S.of(context)!.documents,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(
|
||||
Icons.document_scanner_outlined,
|
||||
color: !widget.authenticatedUser.canCreateDocuments
|
||||
? disabledColor
|
||||
: null,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.document_scanner,
|
||||
color: primaryColor,
|
||||
),
|
||||
label: S.of(context)!.scanner,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Icon(
|
||||
Icons.sell_outlined,
|
||||
color: !widget.authenticatedUser.canViewAnyLabel
|
||||
? disabledColor
|
||||
: null,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.sell,
|
||||
color: primaryColor,
|
||||
),
|
||||
label: S.of(context)!.labels,
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Builder(builder: (context) {
|
||||
if (!(widget.authenticatedUser.canViewDocuments &&
|
||||
widget.authenticatedUser.canViewTags)) {
|
||||
return Icon(
|
||||
Icons.close,
|
||||
color: disabledColor,
|
||||
);
|
||||
}
|
||||
return BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
return Badge.count(
|
||||
isLabelVisible: state.itemsInInboxCount > 0,
|
||||
count: state.itemsInInboxCount,
|
||||
child: const Icon(Icons.inbox_outlined),
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
selectedIcon: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
return Badge.count(
|
||||
isLabelVisible: state.itemsInInboxCount > 0,
|
||||
count: state.itemsInInboxCount,
|
||||
child: Icon(
|
||||
Icons.inbox,
|
||||
color: primaryColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
label: S.of(context)!.inbox,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: widget.navigationShell,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ class InboxCubit extends HydratedCubit<InboxState>
|
||||
Future<void> initialize() async {
|
||||
await refreshItemsInInboxCount(false);
|
||||
await loadInbox();
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
Future<void> refreshItemsInInboxCount([bool shouldLoadInbox = true]) async {
|
||||
|
||||
@@ -39,7 +39,7 @@ class _InboxPageState extends State<InboxPage>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final canEditDocument =
|
||||
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||
context.watch<LocalUserAccount>().paperlessUser.canEditDocuments;
|
||||
return Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||
@@ -15,6 +16,7 @@ import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
|
||||
class InboxItemPlaceholder extends StatelessWidget {
|
||||
const InboxItemPlaceholder({super.key});
|
||||
@@ -150,11 +152,10 @@ class _InboxItemState extends State<InboxItem> {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
pushDocumentDetailsRoute(
|
||||
context,
|
||||
document: widget.document,
|
||||
DocumentDetailsRoute(
|
||||
$extra: widget.document,
|
||||
isLabelClickable: false,
|
||||
);
|
||||
).push(context);
|
||||
},
|
||||
child: SizedBox(
|
||||
height: 200,
|
||||
@@ -238,8 +239,9 @@ class _InboxItemState extends State<InboxItem> {
|
||||
}
|
||||
|
||||
Widget _buildActions(BuildContext context) {
|
||||
final canEdit = LocalUserAccount.current.paperlessUser.canEditDocuments;
|
||||
final canDelete = LocalUserAccount.current.paperlessUser.canDeleteDocuments;
|
||||
final currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
||||
final canEdit = currentUser.canEditDocuments;
|
||||
final canDelete = currentUser.canDeleteDocuments;
|
||||
final chipShape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
);
|
||||
|
||||
@@ -191,7 +191,7 @@ class _FullscreenTagsFormState extends State<FullscreenTagsForm> {
|
||||
final createdTag = await Navigator.of(context).push<Tag?>(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AddTagPage(
|
||||
initialValue: _textEditingController.text,
|
||||
initialName: _textEditingController.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
@@ -73,7 +74,7 @@ class TagsFormField extends StatelessWidget {
|
||||
initialValue: field.value,
|
||||
allowOnlySelection: allowOnlySelection,
|
||||
allowCreation: allowCreation &&
|
||||
LocalUserAccount.current.paperlessUser.canCreateTags,
|
||||
context.watch<LocalUserAccount>().paperlessUser.canCreateTags,
|
||||
allowExclude: allowExclude,
|
||||
),
|
||||
onClosed: (data) {
|
||||
|
||||
@@ -7,23 +7,13 @@ import 'package:paperless_mobile/core/config/hive/hive_config.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/delegate/customizable_sliver_persistent_header_delegate.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/widgets/material/colored_tab_bar.dart';
|
||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/labels_route.dart';
|
||||
|
||||
class LabelsPage extends StatefulWidget {
|
||||
const LabelsPage({Key? key}) : super(key: key);
|
||||
@@ -52,7 +42,7 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final user = LocalUserAccount.current.paperlessUser;
|
||||
final user = context.read<LocalUserAccount>().paperlessUser;
|
||||
_tabController = TabController(
|
||||
length: _calculateTabCount(user), vsync: this)
|
||||
..addListener(() => setState(() => _currentIndex = _tabController.index));
|
||||
@@ -67,7 +57,7 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
final currentUserId =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.currentLoggedInUser;
|
||||
.loggedInUserId;
|
||||
final user = box.get(currentUserId)!.paperlessUser;
|
||||
|
||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
@@ -77,10 +67,14 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: [
|
||||
if (user.canViewCorrespondents) _openAddCorrespondentPage,
|
||||
if (user.canViewDocumentTypes) _openAddDocumentTypePage,
|
||||
if (user.canViewTags) _openAddTagPage,
|
||||
if (user.canViewStoragePaths) _openAddStoragePathPage,
|
||||
if (user.canViewCorrespondents)
|
||||
() => CreateLabelRoute<Correspondent>().push(context),
|
||||
if (user.canViewDocumentTypes)
|
||||
() => CreateLabelRoute<DocumentType>().push(context),
|
||||
if (user.canViewTags)
|
||||
() => CreateLabelRoute<Tag>().push(context),
|
||||
if (user.canViewStoragePaths)
|
||||
() => CreateLabelRoute<StoragePath>().push(context),
|
||||
][_currentIndex],
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
@@ -213,144 +207,13 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
controller: _tabController,
|
||||
children: [
|
||||
if (user.canViewCorrespondents)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(
|
||||
handle: tabBarHandle),
|
||||
LabelTabView<Correspondent>(
|
||||
labels: state.correspondents,
|
||||
filterBuilder: (label) =>
|
||||
DocumentFilter(
|
||||
correspondent:
|
||||
IdQueryParameter.fromId(
|
||||
label.id!),
|
||||
),
|
||||
canEdit: user.canEditCorrespondents,
|
||||
canAddNew:
|
||||
user.canCreateCorrespondents,
|
||||
onEdit: _openEditCorrespondentPage,
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)!
|
||||
.addNewCorrespondent,
|
||||
emptyStateDescription: S
|
||||
.of(context)!
|
||||
.noCorrespondentsSetUp,
|
||||
onAddNew: _openAddCorrespondentPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
_buildCorrespondentsView(state, user),
|
||||
if (user.canViewDocumentTypes)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(
|
||||
handle: tabBarHandle),
|
||||
LabelTabView<DocumentType>(
|
||||
labels: state.documentTypes,
|
||||
filterBuilder: (label) =>
|
||||
DocumentFilter(
|
||||
documentType:
|
||||
IdQueryParameter.fromId(
|
||||
label.id!),
|
||||
),
|
||||
canEdit: user.canEditDocumentTypes,
|
||||
canAddNew:
|
||||
user.canCreateDocumentTypes,
|
||||
onEdit: _openEditDocumentTypePage,
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)!
|
||||
.addNewDocumentType,
|
||||
emptyStateDescription: S
|
||||
.of(context)!
|
||||
.noDocumentTypesSetUp,
|
||||
onAddNew: _openAddDocumentTypePage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
_buildDocumentTypesView(state, user),
|
||||
if (user.canViewTags)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(
|
||||
handle: tabBarHandle),
|
||||
LabelTabView<Tag>(
|
||||
labels: state.tags,
|
||||
filterBuilder: (label) =>
|
||||
DocumentFilter(
|
||||
tags: TagsQuery.ids(
|
||||
include: [label.id!]),
|
||||
),
|
||||
canEdit: user.canEditTags,
|
||||
canAddNew: user.canCreateTags,
|
||||
onEdit: _openEditTagPage,
|
||||
leadingBuilder: (t) => CircleAvatar(
|
||||
backgroundColor: t.color,
|
||||
child: t.isInboxTag
|
||||
? Icon(
|
||||
Icons.inbox,
|
||||
color: t.textColor,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context)!.addNewTag,
|
||||
emptyStateDescription:
|
||||
S.of(context)!.noTagsSetUp,
|
||||
onAddNew: _openAddTagPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
_buildTagsView(state, user),
|
||||
if (user.canViewStoragePaths)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: searchBarHandle),
|
||||
SliverOverlapInjector(
|
||||
handle: tabBarHandle),
|
||||
LabelTabView<StoragePath>(
|
||||
labels: state.storagePaths,
|
||||
onEdit: _openEditStoragePathPage,
|
||||
filterBuilder: (label) =>
|
||||
DocumentFilter(
|
||||
storagePath:
|
||||
IdQueryParameter.fromId(
|
||||
label.id!),
|
||||
),
|
||||
canEdit: user.canEditStoragePaths,
|
||||
canAddNew:
|
||||
user.canCreateStoragePaths,
|
||||
contentBuilder: (path) =>
|
||||
Text(path.path),
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)!
|
||||
.addNewStoragePath,
|
||||
emptyStateDescription: S
|
||||
.of(context)!
|
||||
.noStoragePathsSetUp,
|
||||
onAddNew: _openAddStoragePathPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
_buildStoragePathView(state, user),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -365,73 +228,121 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
});
|
||||
}
|
||||
|
||||
void _openEditCorrespondentPage(Correspondent correspondent) {
|
||||
Navigator.push(
|
||||
context,
|
||||
_buildLabelPageRoute(EditCorrespondentPage(correspondent: correspondent)),
|
||||
Widget _buildCorrespondentsView(LabelState state, UserModel user) {
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(handle: searchBarHandle),
|
||||
SliverOverlapInjector(handle: tabBarHandle),
|
||||
LabelTabView<Correspondent>(
|
||||
labels: state.correspondents,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
correspondent: IdQueryParameter.fromId(label.id!),
|
||||
),
|
||||
canEdit: user.canEditCorrespondents,
|
||||
canAddNew: user.canCreateCorrespondents,
|
||||
onEdit: (correspondent) {
|
||||
EditLabelRoute(correspondent).push(context);
|
||||
},
|
||||
emptyStateActionButtonLabel: S.of(context)!.addNewCorrespondent,
|
||||
emptyStateDescription: S.of(context)!.noCorrespondentsSetUp,
|
||||
onAddNew: () => CreateLabelRoute<Correspondent>().push(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _openEditDocumentTypePage(DocumentType docType) {
|
||||
Navigator.push(
|
||||
context,
|
||||
_buildLabelPageRoute(EditDocumentTypePage(documentType: docType)),
|
||||
Widget _buildDocumentTypesView(LabelState state, UserModel user) {
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(handle: searchBarHandle),
|
||||
SliverOverlapInjector(handle: tabBarHandle),
|
||||
LabelTabView<DocumentType>(
|
||||
labels: state.documentTypes,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
documentType: IdQueryParameter.fromId(label.id!),
|
||||
),
|
||||
canEdit: user.canEditDocumentTypes,
|
||||
canAddNew: user.canCreateDocumentTypes,
|
||||
onEdit: (label) {
|
||||
EditLabelRoute(label).push(context);
|
||||
},
|
||||
emptyStateActionButtonLabel: S.of(context)!.addNewDocumentType,
|
||||
emptyStateDescription: S.of(context)!.noDocumentTypesSetUp,
|
||||
onAddNew: () => CreateLabelRoute<DocumentType>().push(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _openEditTagPage(Tag tag) {
|
||||
Navigator.push(
|
||||
context,
|
||||
_buildLabelPageRoute(EditTagPage(tag: tag)),
|
||||
Widget _buildTagsView(LabelState state, UserModel user) {
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(handle: searchBarHandle),
|
||||
SliverOverlapInjector(handle: tabBarHandle),
|
||||
LabelTabView<Tag>(
|
||||
labels: state.tags,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
tags: TagsQuery.ids(include: [label.id!]),
|
||||
),
|
||||
canEdit: user.canEditTags,
|
||||
canAddNew: user.canCreateTags,
|
||||
onEdit: (label) {
|
||||
EditLabelRoute(label).push(context);
|
||||
},
|
||||
leadingBuilder: (t) => CircleAvatar(
|
||||
backgroundColor: t.color,
|
||||
child: t.isInboxTag
|
||||
? Icon(
|
||||
Icons.inbox,
|
||||
color: t.textColor,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
emptyStateActionButtonLabel: S.of(context)!.addNewTag,
|
||||
emptyStateDescription: S.of(context)!.noTagsSetUp,
|
||||
onAddNew: () => CreateLabelRoute<Tag>().push(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _openEditStoragePathPage(StoragePath path) {
|
||||
Navigator.push(
|
||||
context,
|
||||
_buildLabelPageRoute(EditStoragePathPage(
|
||||
storagePath: path,
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
void _openAddCorrespondentPage() {
|
||||
Navigator.push(
|
||||
context,
|
||||
_buildLabelPageRoute(const AddCorrespondentPage()),
|
||||
);
|
||||
}
|
||||
|
||||
void _openAddDocumentTypePage() {
|
||||
Navigator.push(
|
||||
context,
|
||||
_buildLabelPageRoute(const AddDocumentTypePage()),
|
||||
);
|
||||
}
|
||||
|
||||
void _openAddTagPage() {
|
||||
Navigator.push(
|
||||
context,
|
||||
_buildLabelPageRoute(const AddTagPage()),
|
||||
);
|
||||
}
|
||||
|
||||
void _openAddStoragePathPage() {
|
||||
Navigator.push(
|
||||
context,
|
||||
_buildLabelPageRoute(const AddStoragePathPage()),
|
||||
);
|
||||
}
|
||||
|
||||
MaterialPageRoute<dynamic> _buildLabelPageRoute(Widget page) {
|
||||
return MaterialPageRoute(
|
||||
builder: (_) => MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: context.read<LabelRepository>()),
|
||||
Provider.value(value: context.read<ApiVersion>())
|
||||
],
|
||||
child: page,
|
||||
),
|
||||
Widget _buildStoragePathView(LabelState state, UserModel user) {
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(handle: searchBarHandle),
|
||||
SliverOverlapInjector(handle: tabBarHandle),
|
||||
LabelTabView<StoragePath>(
|
||||
labels: state.storagePaths,
|
||||
onEdit: (label) {
|
||||
EditLabelRoute(label).push(context);
|
||||
},
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
storagePath: IdQueryParameter.fromId(label.id!),
|
||||
),
|
||||
canEdit: user.canEditStoragePaths,
|
||||
canAddNew: user.canCreateStoragePaths,
|
||||
contentBuilder: (path) => Text(path.path),
|
||||
emptyStateActionButtonLabel: S.of(context)!.addNewStoragePath,
|
||||
emptyStateDescription: S.of(context)!.noStoragePathsSetUp,
|
||||
onAddNew: () => CreateLabelRoute<StoragePath>().push(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||
@@ -36,7 +37,7 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
||||
|
||||
Widget _buildReferencedDocumentsWidget(BuildContext context) {
|
||||
final canOpen = (label.documentCount ?? 0) > 0 &&
|
||||
LocalUserAccount.current.paperlessUser.canViewDocuments;
|
||||
context.watch<LocalUserAccount>().paperlessUser.canViewDocuments;
|
||||
return TextButton.icon(
|
||||
label: const Icon(Icons.link),
|
||||
icon: Text(formatMaxCount(label.documentCount)),
|
||||
|
||||
51
lib/features/landing/view/landing_page.dart
Normal file
51
lib/features/landing/view/landing_page.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
||||
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:sliver_tools/sliver_tools.dart';
|
||||
|
||||
class LandingPage extends StatefulWidget {
|
||||
const LandingPage({super.key});
|
||||
|
||||
@override
|
||||
State<LandingPage> createState() => _LandingPageState();
|
||||
}
|
||||
|
||||
class _LandingPageState extends State<LandingPage> {
|
||||
final _searchBarHandle = SliverOverlapAbsorberHandle();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: _searchBarHandle,
|
||||
sliver: SliverSearchBar(
|
||||
floating: true,
|
||||
titleText: S.of(context)!.documents,
|
||||
),
|
||||
),
|
||||
],
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Text(
|
||||
"Welcome!",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||
@@ -7,6 +8,7 @@ import 'package:paperless_mobile/features/documents/view/widgets/selection/view_
|
||||
import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
|
||||
class LinkedDocumentsPage extends StatefulWidget {
|
||||
const LinkedDocumentsPage({super.key});
|
||||
@@ -51,11 +53,10 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage>
|
||||
isLoading: state.isLoading,
|
||||
hasLoaded: state.hasLoaded,
|
||||
onTap: (document) {
|
||||
pushDocumentDetailsRoute(
|
||||
context,
|
||||
document: document,
|
||||
DocumentDetailsRoute(
|
||||
$extra: document,
|
||||
isLabelClickable: false,
|
||||
);
|
||||
).push(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -55,12 +55,11 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
// Mark logged in user as currently active user.
|
||||
final globalSettings =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
globalSettings.currentLoggedInUser = localUserId;
|
||||
globalSettings.loggedInUserId = localUserId;
|
||||
await globalSettings.save();
|
||||
|
||||
emit(
|
||||
AuthenticationState.authenticated(
|
||||
apiVersion: apiVersion,
|
||||
localUserId: localUserId,
|
||||
),
|
||||
);
|
||||
@@ -75,7 +74,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
emit(const AuthenticationState.switchingAccounts());
|
||||
final globalSettings =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
if (globalSettings.currentLoggedInUser == localUserId) {
|
||||
if (globalSettings.loggedInUserId == localUserId) {
|
||||
emit(AuthenticationState.authenticated(localUserId: localUserId));
|
||||
return;
|
||||
}
|
||||
final userAccountBox =
|
||||
@@ -112,7 +112,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
baseUrl: account.serverUrl,
|
||||
);
|
||||
|
||||
globalSettings.currentLoggedInUser = localUserId;
|
||||
globalSettings.loggedInUserId = localUserId;
|
||||
await globalSettings.save();
|
||||
|
||||
final apiVersion = await _getApiVersion(_sessionManager.client);
|
||||
@@ -126,7 +126,6 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
|
||||
emit(AuthenticationState.authenticated(
|
||||
localUserId: localUserId,
|
||||
apiVersion: apiVersion,
|
||||
));
|
||||
});
|
||||
}
|
||||
@@ -175,13 +174,14 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
);
|
||||
final globalSettings =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
final localUserId = globalSettings.currentLoggedInUser;
|
||||
final localUserId = globalSettings.loggedInUserId;
|
||||
if (localUserId == null) {
|
||||
_debugPrintMessage(
|
||||
"restoreSessionState",
|
||||
"There is nothing to restore.",
|
||||
);
|
||||
// If there is nothing to restore, we can quit here.
|
||||
emit(const AuthenticationState.unauthenticated());
|
||||
return;
|
||||
}
|
||||
final localUserAccountBox =
|
||||
@@ -223,7 +223,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
final authentication =
|
||||
await withEncryptedBox<UserCredentials, UserCredentials>(
|
||||
HiveBoxes.localUserCredentials, (box) {
|
||||
return box.get(globalSettings.currentLoggedInUser!);
|
||||
return box.get(globalSettings.loggedInUserId!);
|
||||
});
|
||||
|
||||
if (authentication == null) {
|
||||
@@ -261,7 +261,6 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
);
|
||||
emit(
|
||||
AuthenticationState.authenticated(
|
||||
apiVersion: apiVersion,
|
||||
localUserId: localUserId,
|
||||
),
|
||||
);
|
||||
@@ -279,7 +278,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
await _resetExternalState();
|
||||
final globalSettings =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
globalSettings.currentLoggedInUser = null;
|
||||
globalSettings.loggedInUserId = null;
|
||||
await globalSettings.save();
|
||||
|
||||
emit(const AuthenticationState.unauthenticated());
|
||||
@@ -389,6 +388,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
settings: LocalUserSettings(),
|
||||
serverUrl: serverUrl,
|
||||
paperlessUser: serverUser,
|
||||
apiVersion: apiVersion,
|
||||
),
|
||||
);
|
||||
_debugPrintMessage(
|
||||
|
||||
@@ -2,12 +2,18 @@ part of 'authentication_cubit.dart';
|
||||
|
||||
@freezed
|
||||
class AuthenticationState with _$AuthenticationState {
|
||||
const AuthenticationState._();
|
||||
|
||||
const factory AuthenticationState.unauthenticated() = _Unauthenticated;
|
||||
const factory AuthenticationState.requriresLocalAuthentication() =
|
||||
_RequiresLocalAuthentication;
|
||||
const factory AuthenticationState.authenticated({
|
||||
required String localUserId,
|
||||
required int apiVersion,
|
||||
}) = _Authenticated;
|
||||
const factory AuthenticationState.switchingAccounts() = _SwitchingAccounts;
|
||||
|
||||
bool get isAuthenticated => maybeWhen(
|
||||
authenticated: (_) => true,
|
||||
orElse: () => false,
|
||||
);
|
||||
}
|
||||
|
||||
161
lib/features/login/view/add_account_page.dart
Normal file
161
lib/features/login/view/add_account_page.dart
Normal file
@@ -0,0 +1,161 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/login_pages/server_connection_page.dart';
|
||||
import 'package:paperless_mobile/features/users/view/widgets/user_account_list_tile.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
|
||||
import 'widgets/login_pages/server_login_page.dart';
|
||||
import 'widgets/never_scrollable_scroll_behavior.dart';
|
||||
|
||||
class AddAccountPage extends StatefulWidget {
|
||||
final FutureOr<void> Function(
|
||||
BuildContext context,
|
||||
String username,
|
||||
String password,
|
||||
String serverUrl,
|
||||
ClientCertificate? clientCertificate,
|
||||
) onSubmit;
|
||||
|
||||
final String submitText;
|
||||
final String titleString;
|
||||
|
||||
final bool showLocalAccounts;
|
||||
|
||||
const AddAccountPage({
|
||||
Key? key,
|
||||
required this.onSubmit,
|
||||
required this.submitText,
|
||||
required this.titleString,
|
||||
this.showLocalAccounts = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AddAccountPage> createState() => _AddAccountPageState();
|
||||
}
|
||||
|
||||
class _AddAccountPageState extends State<AddAccountPage> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
final PageController _pageController = PageController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localAccounts =
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: FormBuilder(
|
||||
key: _formKey,
|
||||
child: PageView(
|
||||
controller: _pageController,
|
||||
scrollBehavior: NeverScrollableScrollBehavior(),
|
||||
children: [
|
||||
if (widget.showLocalAccounts && localAccounts.isNotEmpty)
|
||||
Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context)!.logInToExistingAccount),
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
FilledButton(
|
||||
child: Text(S.of(context)!.goToLogin),
|
||||
onPressed: () {
|
||||
_pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final account = localAccounts.values.elementAt(index);
|
||||
return Card(
|
||||
child: UserAccountListTile(
|
||||
account: account,
|
||||
onTap: () {
|
||||
context
|
||||
.read<AuthenticationCubit>()
|
||||
.switchAccount(account.id);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: localAccounts.length,
|
||||
),
|
||||
),
|
||||
ServerConnectionPage(
|
||||
titleText: widget.titleString,
|
||||
formBuilderKey: _formKey,
|
||||
onContinue: () {
|
||||
_pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
},
|
||||
),
|
||||
ServerLoginPage(
|
||||
formBuilderKey: _formKey,
|
||||
submitText: widget.submitText,
|
||||
onSubmit: _login,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _login() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final form = _formKey.currentState!.value;
|
||||
ClientCertificate? clientCert;
|
||||
final clientCertFormModel =
|
||||
form[ClientCertificateFormField.fkClientCertificate]
|
||||
as ClientCertificateFormModel?;
|
||||
if (clientCertFormModel != null) {
|
||||
clientCert = ClientCertificate(
|
||||
bytes: clientCertFormModel.bytes,
|
||||
passphrase: clientCertFormModel.passphrase,
|
||||
);
|
||||
}
|
||||
final credentials =
|
||||
form[UserCredentialsFormField.fkCredentials] as LoginFormCredentials;
|
||||
try {
|
||||
await widget.onSubmit(
|
||||
context,
|
||||
credentials.username!,
|
||||
credentials.password!,
|
||||
form[ServerAddressFormField.fkServerAddress],
|
||||
clientCert,
|
||||
);
|
||||
} on PaperlessApiException catch (error) {
|
||||
showErrorMessage(context, error);
|
||||
} on ServerMessageException catch (error) {
|
||||
showLocalizedError(context, error.message);
|
||||
} catch (error) {
|
||||
showGenericError(context, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,161 +1,78 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
|
||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/login_pages/server_connection_page.dart';
|
||||
import 'package:paperless_mobile/features/users/view/widgets/user_account_list_tile.dart';
|
||||
import 'package:paperless_mobile/features/login/view/add_account_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
|
||||
import 'widgets/login_pages/server_login_page.dart';
|
||||
import 'widgets/never_scrollable_scroll_behavior.dart';
|
||||
class LoginPage extends StatelessWidget {
|
||||
const LoginPage({super.key});
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
final FutureOr<void> Function(
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AddAccountPage(
|
||||
titleString: S.of(context)!.connectToPaperless,
|
||||
submitText: S.of(context)!.signIn,
|
||||
onSubmit: _onLogin,
|
||||
showLocalAccounts: true,
|
||||
);
|
||||
}
|
||||
|
||||
void _onLogin(
|
||||
BuildContext context,
|
||||
String username,
|
||||
String password,
|
||||
String serverUrl,
|
||||
ClientCertificate? clientCertificate,
|
||||
) onSubmit;
|
||||
|
||||
final String submitText;
|
||||
final String titleString;
|
||||
|
||||
final bool showLocalAccounts;
|
||||
|
||||
const LoginPage({
|
||||
Key? key,
|
||||
required this.onSubmit,
|
||||
required this.submitText,
|
||||
required this.titleString,
|
||||
this.showLocalAccounts = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
final PageController _pageController = PageController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localAccounts =
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: FormBuilder(
|
||||
key: _formKey,
|
||||
child: PageView(
|
||||
controller: _pageController,
|
||||
scrollBehavior: NeverScrollableScrollBehavior(),
|
||||
children: [
|
||||
if (widget.showLocalAccounts && localAccounts.isNotEmpty)
|
||||
Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context)!.logInToExistingAccount),
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
FilledButton(
|
||||
child: Text(S.of(context)!.goToLogin),
|
||||
onPressed: () {
|
||||
_pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final account = localAccounts.values.elementAt(index);
|
||||
return Card(
|
||||
child: UserAccountListTile(
|
||||
account: account,
|
||||
onTap: () {
|
||||
context
|
||||
.read<AuthenticationCubit>()
|
||||
.switchAccount(account.id);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: localAccounts.length,
|
||||
),
|
||||
),
|
||||
ServerConnectionPage(
|
||||
titleText: widget.titleString,
|
||||
formBuilderKey: _formKey,
|
||||
onContinue: () {
|
||||
_pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
},
|
||||
) async {
|
||||
try {
|
||||
await context.read<AuthenticationCubit>().login(
|
||||
credentials: LoginFormCredentials(
|
||||
username: username,
|
||||
password: password,
|
||||
),
|
||||
ServerLoginPage(
|
||||
formBuilderKey: _formKey,
|
||||
submitText: widget.submitText,
|
||||
onSubmit: _login,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _login() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final form = _formKey.currentState!.value;
|
||||
ClientCertificate? clientCert;
|
||||
final clientCertFormModel =
|
||||
form[ClientCertificateFormField.fkClientCertificate]
|
||||
as ClientCertificateFormModel?;
|
||||
if (clientCertFormModel != null) {
|
||||
clientCert = ClientCertificate(
|
||||
bytes: clientCertFormModel.bytes,
|
||||
passphrase: clientCertFormModel.passphrase,
|
||||
);
|
||||
}
|
||||
final credentials =
|
||||
form[UserCredentialsFormField.fkCredentials] as LoginFormCredentials;
|
||||
try {
|
||||
await widget.onSubmit(
|
||||
serverUrl: serverUrl,
|
||||
clientCertificate: clientCertificate,
|
||||
);
|
||||
// Show onboarding after first login!
|
||||
final globalSettings =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
if (globalSettings.showOnboarding) {
|
||||
Navigator.push(
|
||||
context,
|
||||
credentials.username!,
|
||||
credentials.password!,
|
||||
form[ServerAddressFormField.fkServerAddress],
|
||||
clientCert,
|
||||
);
|
||||
} on PaperlessApiException catch (error) {
|
||||
showErrorMessage(context, error);
|
||||
} on ServerMessageException catch (error) {
|
||||
showLocalizedError(context, error.message);
|
||||
} catch (error) {
|
||||
showGenericError(context, error);
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ApplicationIntroSlideshow(),
|
||||
fullscreenDialog: true,
|
||||
),
|
||||
).then((value) {
|
||||
globalSettings.showOnboarding = false;
|
||||
globalSettings.save();
|
||||
});
|
||||
}
|
||||
// DocumentsRoute().go(context);
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} on PaperlessFormValidationException catch (exception, stackTrace) {
|
||||
if (exception.hasUnspecificErrorMessage()) {
|
||||
showLocalizedError(context, exception.unspecificErrorMessage()!);
|
||||
} else {
|
||||
showGenericError(
|
||||
context,
|
||||
exception.validationMessages.values.first,
|
||||
stackTrace,
|
||||
); //TODO: Check if we can show error message directly on field here.
|
||||
}
|
||||
} catch (unknownError, stackTrace) {
|
||||
showGenericError(context, unknownError.toString(), stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||
@@ -9,6 +10,7 @@ import 'package:paperless_mobile/features/documents/view/widgets/selection/confi
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
|
||||
class SavedViewDetailsPage extends StatefulWidget {
|
||||
final Future<void> Function(SavedView savedView) onDelete;
|
||||
@@ -28,7 +30,7 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cubit = context.read<SavedViewDetailsCubit>();
|
||||
final cubit = context.watch<SavedViewDetailsCubit>();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(cubit.savedView.name),
|
||||
@@ -76,11 +78,10 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
|
||||
isLoading: state.isLoading,
|
||||
hasLoaded: state.hasLoaded,
|
||||
onTap: (document) {
|
||||
pushDocumentDetailsRoute(
|
||||
context,
|
||||
document: document,
|
||||
DocumentDetailsRoute(
|
||||
$extra: document,
|
||||
isLabelClickable: false,
|
||||
);
|
||||
).push(context);
|
||||
},
|
||||
viewType: state.viewType,
|
||||
),
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
||||
import 'package:paperless_mobile/features/login/view/add_account_page.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/dialogs/switch_account_dialog.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||
import 'package:paperless_mobile/features/users/view/widgets/user_account_list_tile.dart';
|
||||
@@ -22,7 +22,7 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
builder: (context, globalSettings) {
|
||||
// This is one of the few places where the currentLoggedInUser can be null
|
||||
// (exactly after loggin out as the current user to be precise).
|
||||
if (globalSettings.currentLoggedInUser == null) {
|
||||
if (globalSettings.loggedInUserId == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return ValueListenableBuilder(
|
||||
@@ -32,8 +32,7 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
builder: (context, box, _) {
|
||||
final userIds = box.keys.toList().cast<String>();
|
||||
final otherAccounts = userIds
|
||||
.whereNot(
|
||||
(element) => element == globalSettings.currentLoggedInUser)
|
||||
.whereNot((element) => element == globalSettings.loggedInUserId)
|
||||
.toList();
|
||||
return SimpleDialog(
|
||||
insetPadding: const EdgeInsets.all(24),
|
||||
@@ -54,7 +53,7 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
children: [
|
||||
Card(
|
||||
child: UserAccountListTile(
|
||||
account: box.get(globalSettings.currentLoggedInUser!)!,
|
||||
account: box.get(globalSettings.loggedInUserId!)!,
|
||||
trailing: PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (context) => [
|
||||
@@ -71,8 +70,7 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
],
|
||||
onSelected: (value) async {
|
||||
if (value == 0) {
|
||||
final currentUser =
|
||||
globalSettings.currentLoggedInUser!;
|
||||
final currentUser = globalSettings.loggedInUserId!;
|
||||
await context.read<AuthenticationCubit>().logout();
|
||||
Navigator.of(context).pop();
|
||||
await context
|
||||
@@ -117,7 +115,7 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
// Switch
|
||||
_onSwitchAccount(
|
||||
context,
|
||||
globalSettings.currentLoggedInUser!,
|
||||
globalSettings.loggedInUserId!,
|
||||
otherAccounts[index],
|
||||
);
|
||||
} else if (value == 1) {
|
||||
@@ -135,10 +133,10 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
title: Text(S.of(context)!.addAccount),
|
||||
leading: const Icon(Icons.person_add),
|
||||
onTap: () {
|
||||
_onAddAccount(context, globalSettings.currentLoggedInUser!);
|
||||
_onAddAccount(context, globalSettings.loggedInUserId!);
|
||||
},
|
||||
),
|
||||
if (context.watch<ApiVersion>().hasMultiUserSupport)
|
||||
if (context.watch<LocalUserAccount>().hasMultiUserSupport)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.admin_panel_settings),
|
||||
title: Text(S.of(context)!.managePermissions),
|
||||
@@ -155,7 +153,7 @@ class ManageAccountsPage extends StatelessWidget {
|
||||
final userId = await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LoginPage(
|
||||
builder: (context) => AddAccountPage(
|
||||
titleString: S.of(context)!.addAccount,
|
||||
onSubmit: (context, username, password, serverUrl,
|
||||
clientCertificate) async {
|
||||
|
||||
@@ -100,14 +100,4 @@ class SettingsPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _goto(Widget page, BuildContext context) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => page,
|
||||
maintainState: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class UserAccountBuilder extends StatelessWidget {
|
||||
builder: (context, accountBox, _) {
|
||||
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.currentLoggedInUser;
|
||||
.loggedInUserId;
|
||||
if (currentUser != null) {
|
||||
final account = accountBox.get(currentUser);
|
||||
return builder(context, account);
|
||||
|
||||
@@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
|
||||
class SimilarDocumentsView extends StatefulWidget {
|
||||
final ScrollController pagingScrollController;
|
||||
@@ -64,11 +64,10 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
||||
hasLoaded: state.hasLoaded,
|
||||
enableHeroAnimation: false,
|
||||
onTap: (document) {
|
||||
pushDocumentDetailsRoute(
|
||||
context,
|
||||
document: document,
|
||||
DocumentDetailsRoute(
|
||||
$extra: document,
|
||||
isLabelClickable: false,
|
||||
);
|
||||
).push(context);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
389
lib/main.dart
389
lib/main.dart
@@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
@@ -30,19 +31,23 @@ import 'package:paperless_mobile/core/interceptor/language_header.interceptor.da
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/security/session_manager.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
|
||||
import 'package:paperless_mobile/features/home/view/home_route.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/verify_identity_page.dart';
|
||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||
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/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/inbox_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/labels_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/landing_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/scanner_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/shells/provider_shell_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/shells/scaffold_shell_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/top_level/login_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/top_level/settings_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/top_level/switching_accounts_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/top_level/verify_identity_route.dart';
|
||||
import 'package:paperless_mobile/theme.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -138,7 +143,9 @@ void main() async {
|
||||
});
|
||||
|
||||
final apiFactory = PaperlessApiFactoryImpl(sessionManager);
|
||||
|
||||
final authenticationCubit =
|
||||
AuthenticationCubit(localAuthService, apiFactory, sessionManager);
|
||||
await authenticationCubit.restoreSessionState();
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
@@ -154,13 +161,10 @@ void main() async {
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<ConnectivityCubit>.value(value: connectivityCubit),
|
||||
BlocProvider(
|
||||
create: (context) => AuthenticationCubit(
|
||||
localAuthService, apiFactory, sessionManager),
|
||||
),
|
||||
BlocProvider.value(value: authenticationCubit),
|
||||
],
|
||||
child: PaperlessMobileEntrypoint(
|
||||
paperlessProviderFactory: apiFactory,
|
||||
child: GoRouterShell(
|
||||
apiFactory: apiFactory,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -182,70 +186,69 @@ void main() async {
|
||||
});
|
||||
}
|
||||
|
||||
class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||
final PaperlessApiFactory paperlessProviderFactory;
|
||||
const PaperlessMobileEntrypoint({
|
||||
Key? key,
|
||||
required this.paperlessProviderFactory,
|
||||
}) : super(key: key);
|
||||
// class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||
// final PaperlessApiFactory paperlessProviderFactory;
|
||||
// const PaperlessMobileEntrypoint({
|
||||
// Key? key,
|
||||
// required this.paperlessProviderFactory,
|
||||
// }) : super(key: key);
|
||||
|
||||
// @override
|
||||
// State<PaperlessMobileEntrypoint> createState() =>
|
||||
// _PaperlessMobileEntrypointState();
|
||||
// }
|
||||
|
||||
// class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return GlobalSettingsBuilder(
|
||||
// builder: (context, settings) {
|
||||
// return DynamicColorBuilder(
|
||||
// builder: (lightDynamic, darkDynamic) {
|
||||
// return MaterialApp(
|
||||
// debugShowCheckedModeBanner: true,
|
||||
// title: "Paperless Mobile",
|
||||
// theme: buildTheme(
|
||||
// brightness: Brightness.light,
|
||||
// dynamicScheme: lightDynamic,
|
||||
// preferredColorScheme: settings.preferredColorSchemeOption,
|
||||
// ),
|
||||
// darkTheme: buildTheme(
|
||||
// brightness: Brightness.dark,
|
||||
// dynamicScheme: darkDynamic,
|
||||
// preferredColorScheme: settings.preferredColorSchemeOption,
|
||||
// ),
|
||||
// themeMode: settings.preferredThemeMode,
|
||||
// supportedLocales: S.supportedLocales,
|
||||
// locale: Locale.fromSubtags(
|
||||
// languageCode: settings.preferredLocaleSubtag,
|
||||
// ),
|
||||
// localizationsDelegates: const [
|
||||
// ...S.localizationsDelegates,
|
||||
// ],
|
||||
// home: AuthenticationWrapper(
|
||||
// paperlessProviderFactory: widget.paperlessProviderFactory,
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
class GoRouterShell extends StatefulWidget {
|
||||
final PaperlessApiFactory apiFactory;
|
||||
const GoRouterShell({
|
||||
super.key,
|
||||
required this.apiFactory,
|
||||
});
|
||||
|
||||
@override
|
||||
State<PaperlessMobileEntrypoint> createState() =>
|
||||
_PaperlessMobileEntrypointState();
|
||||
State<GoRouterShell> createState() => _GoRouterShellState();
|
||||
}
|
||||
|
||||
class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GlobalSettingsBuilder(
|
||||
builder: (context, settings) {
|
||||
return DynamicColorBuilder(
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: true,
|
||||
title: "Paperless Mobile",
|
||||
theme: buildTheme(
|
||||
brightness: Brightness.light,
|
||||
dynamicScheme: lightDynamic,
|
||||
preferredColorScheme: settings.preferredColorSchemeOption,
|
||||
),
|
||||
darkTheme: buildTheme(
|
||||
brightness: Brightness.dark,
|
||||
dynamicScheme: darkDynamic,
|
||||
preferredColorScheme: settings.preferredColorSchemeOption,
|
||||
),
|
||||
themeMode: settings.preferredThemeMode,
|
||||
supportedLocales: S.supportedLocales,
|
||||
locale: Locale.fromSubtags(
|
||||
languageCode: settings.preferredLocaleSubtag,
|
||||
),
|
||||
localizationsDelegates: const [
|
||||
...S.localizationsDelegates,
|
||||
],
|
||||
home: AuthenticationWrapper(
|
||||
paperlessProviderFactory: widget.paperlessProviderFactory,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AuthenticationWrapper extends StatefulWidget {
|
||||
final PaperlessApiFactory paperlessProviderFactory;
|
||||
|
||||
const AuthenticationWrapper({
|
||||
Key? key,
|
||||
required this.paperlessProviderFactory,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AuthenticationWrapper> createState() => _AuthenticationWrapperState();
|
||||
}
|
||||
|
||||
class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
class _GoRouterShellState extends State<GoRouterShell> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
@@ -257,7 +260,6 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Activate the highest supported refresh rate on the device
|
||||
if (Platform.isAndroid) {
|
||||
_setOptimalDisplayMode();
|
||||
@@ -281,75 +283,180 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
|
||||
}
|
||||
|
||||
late final _router = GoRouter(
|
||||
debugLogDiagnostics: true,
|
||||
initialLocation: "/login",
|
||||
routes: [
|
||||
$loginRoute,
|
||||
$verifyIdentityRoute,
|
||||
$switchingAccountsRoute,
|
||||
$settingsRoute,
|
||||
ShellRoute(
|
||||
navigatorKey: rootNavigatorKey,
|
||||
builder: ProviderShellRoute(widget.apiFactory).build,
|
||||
routes: [
|
||||
// GoRoute(
|
||||
// parentNavigatorKey: rootNavigatorKey,
|
||||
// name: R.savedView,
|
||||
// path: "/saved_view/:id",
|
||||
// builder: (context, state) {
|
||||
// return Placeholder(
|
||||
// child: Text("Documents"),
|
||||
// );
|
||||
// },
|
||||
// routes: [
|
||||
// GoRoute(
|
||||
// path: "create",
|
||||
// name: R.createSavedView,
|
||||
// builder: (context, state) {
|
||||
// return Placeholder(
|
||||
// child: Text("Documents"),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
StatefulShellRoute.indexedStack(
|
||||
builder: const ScaffoldShellRoute().builder,
|
||||
branches: [
|
||||
StatefulShellBranch(
|
||||
navigatorKey: landingNavigatorKey,
|
||||
routes: [$landingRoute],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: documentsNavigatorKey,
|
||||
routes: [$documentsRoute],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: scannerNavigatorKey,
|
||||
routes: [$scannerRoute],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: labelsNavigatorKey,
|
||||
routes: [$labelsRoute],
|
||||
),
|
||||
StatefulShellBranch(
|
||||
navigatorKey: inboxNavigatorKey,
|
||||
routes: [$inboxRoute],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<AuthenticationCubit, AuthenticationState>(
|
||||
builder: (context, authentication) {
|
||||
return authentication.when(
|
||||
unauthenticated: () => LoginPage(
|
||||
titleString: S.of(context)!.connectToPaperless,
|
||||
submitText: S.of(context)!.signIn,
|
||||
onSubmit: _onLogin,
|
||||
showLocalAccounts: true,
|
||||
),
|
||||
requriresLocalAuthentication: () => const VerifyIdentityPage(),
|
||||
authenticated: (localUserId, apiVersion) => HomeRoute(
|
||||
key: ValueKey(localUserId),
|
||||
paperlessApiVersion: apiVersion,
|
||||
paperlessProviderFactory: widget.paperlessProviderFactory,
|
||||
localUserId: localUserId,
|
||||
),
|
||||
switchingAccounts: () => const SwitchingAccountsPage(),
|
||||
return GlobalSettingsBuilder(
|
||||
builder: (context, settings) {
|
||||
return DynamicColorBuilder(
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
return BlocListener<AuthenticationCubit, AuthenticationState>(
|
||||
listener: (context, state) {
|
||||
state.when(
|
||||
unauthenticated: () => const LoginRoute().go(context),
|
||||
requriresLocalAuthentication: () =>
|
||||
const VerifyIdentityRoute().go(context),
|
||||
authenticated: (localUserId) =>
|
||||
const LandingRoute().go(context),
|
||||
switchingAccounts: () =>
|
||||
const SwitchingAccountsRoute().go(context),
|
||||
);
|
||||
},
|
||||
child: MaterialApp.router(
|
||||
routerConfig: _router,
|
||||
debugShowCheckedModeBanner: true,
|
||||
title: "Paperless Mobile",
|
||||
theme: buildTheme(
|
||||
brightness: Brightness.light,
|
||||
dynamicScheme: lightDynamic,
|
||||
preferredColorScheme: settings.preferredColorSchemeOption,
|
||||
),
|
||||
darkTheme: buildTheme(
|
||||
brightness: Brightness.dark,
|
||||
dynamicScheme: darkDynamic,
|
||||
preferredColorScheme: settings.preferredColorSchemeOption,
|
||||
),
|
||||
themeMode: settings.preferredThemeMode,
|
||||
supportedLocales: S.supportedLocales,
|
||||
locale: Locale.fromSubtags(
|
||||
languageCode: settings.preferredLocaleSubtag,
|
||||
),
|
||||
localizationsDelegates: S.localizationsDelegates,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onLogin(
|
||||
BuildContext context,
|
||||
String username,
|
||||
String password,
|
||||
String serverUrl,
|
||||
ClientCertificate? clientCertificate,
|
||||
) async {
|
||||
try {
|
||||
await context.read<AuthenticationCubit>().login(
|
||||
credentials: LoginFormCredentials(
|
||||
username: username,
|
||||
password: password,
|
||||
),
|
||||
serverUrl: serverUrl,
|
||||
clientCertificate: clientCertificate,
|
||||
);
|
||||
// Show onboarding after first login!
|
||||
final globalSettings =
|
||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||
if (globalSettings.showOnboarding) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ApplicationIntroSlideshow(),
|
||||
fullscreenDialog: true,
|
||||
),
|
||||
).then((value) {
|
||||
globalSettings.showOnboarding = false;
|
||||
globalSettings.save();
|
||||
});
|
||||
}
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} on PaperlessFormValidationException catch (exception, stackTrace) {
|
||||
if (exception.hasUnspecificErrorMessage()) {
|
||||
showLocalizedError(context, exception.unspecificErrorMessage()!);
|
||||
} else {
|
||||
showGenericError(
|
||||
context,
|
||||
exception.validationMessages.values.first,
|
||||
stackTrace,
|
||||
); //TODO: Check if we can show error message directly on field here.
|
||||
}
|
||||
} catch (unknownError, stackTrace) {
|
||||
showGenericError(context, unknownError.toString(), stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// class AuthenticationWrapper extends StatefulWidget {
|
||||
// final PaperlessApiFactory paperlessProviderFactory;
|
||||
|
||||
// const AuthenticationWrapper({
|
||||
// Key? key,
|
||||
// required this.paperlessProviderFactory,
|
||||
// }) : super(key: key);
|
||||
|
||||
// @override
|
||||
// State<AuthenticationWrapper> createState() => _AuthenticationWrapperState();
|
||||
// }
|
||||
|
||||
// class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
// @override
|
||||
// void didChangeDependencies() {
|
||||
// super.didChangeDependencies();
|
||||
// context.read<AuthenticationCubit>().restoreSessionState().then((value) {
|
||||
// FlutterNativeSplash.remove();
|
||||
// });
|
||||
// }
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
|
||||
// // Activate the highest supported refresh rate on the device
|
||||
// if (Platform.isAndroid) {
|
||||
// _setOptimalDisplayMode();
|
||||
// }
|
||||
// initializeDateFormatting();
|
||||
// }
|
||||
|
||||
// Future<void> _setOptimalDisplayMode() async {
|
||||
// final List<DisplayMode> supported = await FlutterDisplayMode.supported;
|
||||
// final DisplayMode active = await FlutterDisplayMode.active;
|
||||
|
||||
// final List<DisplayMode> sameResolution = supported
|
||||
// .where((m) => m.width == active.width && m.height == active.height)
|
||||
// .toList()
|
||||
// ..sort((a, b) => b.refreshRate.compareTo(a.refreshRate));
|
||||
|
||||
// final DisplayMode mostOptimalMode =
|
||||
// sameResolution.isNotEmpty ? sameResolution.first : active;
|
||||
// debugPrint('Setting refresh rate to ${mostOptimalMode.refreshRate}');
|
||||
|
||||
// await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
|
||||
// }
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return BlocBuilder<AuthenticationCubit, AuthenticationState>(
|
||||
// builder: (context, authentication) {
|
||||
// return authentication.when(
|
||||
// unauthenticated: () => const LoginPage(),
|
||||
// requriresLocalAuthentication: () => const VerifyIdentityPage(),
|
||||
// authenticated: (localUserId, apiVersion) => HomeShellWidget(
|
||||
// key: ValueKey(localUserId),
|
||||
// paperlessApiVersion: apiVersion,
|
||||
// paperlessProviderFactory: widget.paperlessProviderFactory,
|
||||
// localUserId: localUserId,
|
||||
// ),
|
||||
// switchingAccounts: () => const SwitchingAccountsPage(),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||
|
||||
class DocumentDetailsRoute extends StatelessWidget {
|
||||
final DocumentModel document;
|
||||
final bool isLabelClickable;
|
||||
final String? titleAndContentQueryString;
|
||||
|
||||
const DocumentDetailsRoute({
|
||||
super.key,
|
||||
required this.document,
|
||||
this.isLabelClickable = true,
|
||||
this.titleAndContentQueryString,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => DocumentDetailsCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
initialDocument: document,
|
||||
),
|
||||
lazy: false,
|
||||
child: DocumentDetailsPage(
|
||||
isLabelClickable: isLabelClickable,
|
||||
titleAndContentQueryString: titleAndContentQueryString,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentDetailsRouteArguments {
|
||||
final DocumentModel document;
|
||||
final bool isLabelClickable;
|
||||
final bool allowEdit;
|
||||
final String? titleAndContentQueryString;
|
||||
|
||||
DocumentDetailsRouteArguments({
|
||||
required this.document,
|
||||
this.isLabelClickable = true,
|
||||
this.allowEdit = true,
|
||||
this.titleAndContentQueryString,
|
||||
});
|
||||
}
|
||||
8
lib/routes/navigation_keys.dart
Normal file
8
lib/routes/navigation_keys.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final landingNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final documentsNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final scannerNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final labelsNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final inboxNavigatorKey = GlobalKey<NavigatorState>();
|
||||
20
lib/routes/routes.dart
Normal file
20
lib/routes/routes.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
class R {
|
||||
const R._();
|
||||
static const landing = "landing";
|
||||
static const login = "login";
|
||||
static const documents = "documents";
|
||||
static const verifyIdentity = "verifyIdentity";
|
||||
static const switchingAccounts = "switchingAccounts";
|
||||
static const savedView = "savedView";
|
||||
static const createSavedView = "createSavedView";
|
||||
static const documentDetails = "documentDetails";
|
||||
static const editDocument = "editDocument";
|
||||
static const labels = "labels";
|
||||
static const createLabel = "createLabel";
|
||||
static const editLabel = "editLabel";
|
||||
static const scanner = "scanner";
|
||||
static const uploadDocument = "upload";
|
||||
static const inbox = "inbox";
|
||||
static const documentPreview = "documentPreview";
|
||||
static const settings = "settings";
|
||||
}
|
||||
113
lib/routes/typed/branches/documents_route.dart
Normal file
113
lib/routes/typed/branches/documents_route.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
import 'dart:ffi';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_edit/view/document_edit_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
||||
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
||||
import 'package:paperless_mobile/routes/routes.dart';
|
||||
|
||||
part 'documents_route.g.dart';
|
||||
|
||||
class DocumentsBranch extends StatefulShellBranchData {
|
||||
static final GlobalKey<NavigatorState> $navigatorKey = documentsNavigatorKey;
|
||||
const DocumentsBranch();
|
||||
}
|
||||
|
||||
@TypedGoRoute<DocumentsRoute>(
|
||||
path: "/documents",
|
||||
name: R.documents,
|
||||
routes: [
|
||||
TypedGoRoute<EditDocumentRoute>(
|
||||
path: "edit",
|
||||
name: R.editDocument,
|
||||
),
|
||||
TypedGoRoute<DocumentDetailsRoute>(
|
||||
path: "details",
|
||||
name: R.documentDetails,
|
||||
),
|
||||
TypedGoRoute<DocumentPreviewRoute>(
|
||||
path: "preview",
|
||||
name: R.documentPreview,
|
||||
)
|
||||
],
|
||||
)
|
||||
class DocumentsRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const DocumentsPage();
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentDetailsRoute extends GoRouteData {
|
||||
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
|
||||
|
||||
final bool isLabelClickable;
|
||||
final DocumentModel $extra;
|
||||
final String? queryString;
|
||||
|
||||
const DocumentDetailsRoute({
|
||||
required this.$extra,
|
||||
this.isLabelClickable = true,
|
||||
this.queryString,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return BlocProvider(
|
||||
create: (_) => DocumentDetailsCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
initialDocument: $extra,
|
||||
),
|
||||
lazy: false,
|
||||
child: DocumentDetailsPage(
|
||||
isLabelClickable: isLabelClickable,
|
||||
titleAndContentQueryString: queryString,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EditDocumentRoute extends GoRouteData {
|
||||
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
|
||||
|
||||
final DocumentModel $extra;
|
||||
|
||||
const EditDocumentRoute(this.$extra);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return BlocProvider(
|
||||
create: (context) => DocumentEditCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
document: $extra,
|
||||
)..loadFieldSuggestions(),
|
||||
child: const DocumentEditPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentPreviewRoute extends GoRouteData {
|
||||
final DocumentModel $extra;
|
||||
const DocumentPreviewRoute(this.$extra);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return DocumentView(
|
||||
documentBytes: context.read<PaperlessDocumentsApi>().download($extra),
|
||||
);
|
||||
}
|
||||
}
|
||||
17
lib/routes/typed/branches/inbox_route.dart
Normal file
17
lib/routes/typed/branches/inbox_route.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
||||
import 'package:paperless_mobile/routes/routes.dart';
|
||||
|
||||
part 'inbox_route.g.dart';
|
||||
|
||||
@TypedGoRoute<InboxRoute>(
|
||||
path: "/inbox",
|
||||
name: R.inbox,
|
||||
)
|
||||
class InboxRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const InboxPage();
|
||||
}
|
||||
}
|
||||
84
lib/routes/typed/branches/labels_route.dart
Normal file
84
lib/routes/typed/branches/labels_route.dart
Normal file
@@ -0,0 +1,84 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
|
||||
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
||||
import 'package:paperless_mobile/routes/routes.dart';
|
||||
|
||||
part 'labels_route.g.dart';
|
||||
|
||||
class LabelsBranch extends StatefulShellBranchData {
|
||||
static final GlobalKey<NavigatorState> $navigatorKey = labelsNavigatorKey;
|
||||
const LabelsBranch();
|
||||
}
|
||||
|
||||
@TypedGoRoute<LabelsRoute>(
|
||||
path: "/labels",
|
||||
name: R.labels,
|
||||
routes: [
|
||||
TypedGoRoute<EditLabelRoute>(
|
||||
path: "edit",
|
||||
name: R.editLabel,
|
||||
),
|
||||
TypedGoRoute<CreateLabelRoute>(
|
||||
path: "create",
|
||||
name: R.createLabel,
|
||||
),
|
||||
],
|
||||
)
|
||||
class LabelsRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const LabelsPage();
|
||||
}
|
||||
}
|
||||
|
||||
class EditLabelRoute extends GoRouteData {
|
||||
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
|
||||
|
||||
final Label $extra;
|
||||
|
||||
const EditLabelRoute(this.$extra);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return switch ($extra) {
|
||||
Correspondent c => EditCorrespondentPage(correspondent: c),
|
||||
DocumentType d => EditDocumentTypePage(documentType: d),
|
||||
Tag t => EditTagPage(tag: t),
|
||||
StoragePath s => EditStoragePathPage(storagePath: s),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class CreateLabelRoute<T extends Label> extends GoRouteData {
|
||||
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
|
||||
|
||||
final String? name;
|
||||
|
||||
CreateLabelRoute({
|
||||
this.name,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
if (T is Correspondent) {
|
||||
return AddCorrespondentPage(initialName: name);
|
||||
} else if (T is DocumentType) {
|
||||
return AddDocumentTypePage(initialName: name);
|
||||
} else if (T is Tag) {
|
||||
return AddTagPage(initialName: name);
|
||||
} else if (T is StoragePath) {
|
||||
return AddStoragePathPage(initialName: name);
|
||||
}
|
||||
throw ArgumentError();
|
||||
}
|
||||
}
|
||||
38
lib/routes/typed/branches/landing_route.dart
Normal file
38
lib/routes/typed/branches/landing_route.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_mobile/features/landing/view/landing_page.dart';
|
||||
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
||||
import 'package:paperless_mobile/routes/routes.dart';
|
||||
|
||||
part 'landing_route.g.dart';
|
||||
|
||||
class LandingBranch extends StatefulShellBranchData {
|
||||
static final GlobalKey<NavigatorState> $navigatorKey = landingNavigatorKey;
|
||||
|
||||
const LandingBranch();
|
||||
}
|
||||
|
||||
@TypedGoRoute<LandingRoute>(
|
||||
path: "/landing",
|
||||
name: R.landing,
|
||||
routes: [
|
||||
TypedGoRoute<SavedViewRoute>(
|
||||
path: "saved-view",
|
||||
name: R.savedView,
|
||||
),
|
||||
],
|
||||
)
|
||||
class LandingRoute extends GoRouteData {
|
||||
const LandingRoute();
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const LandingPage();
|
||||
}
|
||||
}
|
||||
|
||||
class SavedViewRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return Placeholder();
|
||||
}
|
||||
}
|
||||
82
lib/routes/typed/branches/scanner_route.dart
Normal file
82
lib/routes/typed/branches/scanner_route.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.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/routes/navigation_keys.dart';
|
||||
import 'package:paperless_mobile/routes/routes.dart';
|
||||
|
||||
part 'scanner_route.g.dart';
|
||||
|
||||
// @TypedStatefulShellBranch<ScannerBranch>(
|
||||
// routes: [
|
||||
// TypedGoRoute<ScannerRoute>(
|
||||
// path: "/scanner",
|
||||
// name: R.scanner,
|
||||
// routes: [
|
||||
// TypedGoRoute<DocumentUploadRoute>(
|
||||
// path: "upload",
|
||||
// name: R.uploadDocument,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// )
|
||||
class ScannerBranch extends StatefulShellBranchData {
|
||||
static final GlobalKey<NavigatorState> $navigatorKey = scannerNavigatorKey;
|
||||
|
||||
const ScannerBranch();
|
||||
}
|
||||
|
||||
@TypedGoRoute<ScannerRoute>(
|
||||
path: "/scanner",
|
||||
name: R.scanner,
|
||||
routes: [
|
||||
TypedGoRoute<DocumentUploadRoute>(
|
||||
path: "upload",
|
||||
name: R.uploadDocument,
|
||||
),
|
||||
],
|
||||
)
|
||||
class ScannerRoute extends GoRouteData {
|
||||
const ScannerRoute();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const ScannerPage();
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentUploadRoute extends GoRouteData {
|
||||
static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
|
||||
final Uint8List $extra;
|
||||
final String? title;
|
||||
final String? filename;
|
||||
final String? fileExtension;
|
||||
|
||||
const DocumentUploadRoute({
|
||||
required this.$extra,
|
||||
this.title,
|
||||
this.filename,
|
||||
this.fileExtension,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return BlocProvider(
|
||||
create: (_) => DocumentUploadCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
),
|
||||
child: DocumentUploadPreparationPage(
|
||||
title: title,
|
||||
fileExtension: fileExtension,
|
||||
filename: filename,
|
||||
fileBytes: $extra,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
72
lib/routes/typed/shells/provider_shell_route.dart
Normal file
72
lib/routes/typed/shells/provider_shell_route.dart
Normal file
@@ -0,0 +1,72 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.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/factory/paperless_api_factory.dart';
|
||||
import 'package:paperless_mobile/features/home/view/home_shell_widget.dart';
|
||||
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
||||
|
||||
//part 'provider_shell_route.g.dart';
|
||||
//TODO: Wait for https://github.com/flutter/flutter/issues/127371 to be merged
|
||||
// @TypedShellRoute<ProviderShellRoute>(
|
||||
// routes: [
|
||||
// TypedStatefulShellRoute(
|
||||
// branches: [
|
||||
// TypedStatefulShellBranch<LandingBranch>(
|
||||
// routes: [
|
||||
// TypedGoRoute<LandingRoute>(
|
||||
// path: "/landing",
|
||||
// // name: R.landing,
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// TypedStatefulShellBranch<DocumentsBranch>(
|
||||
// routes: [
|
||||
// TypedGoRoute<DocumentsRoute>(
|
||||
// path: "/documents",
|
||||
// routes: [
|
||||
// TypedGoRoute<DocumentDetailsRoute>(
|
||||
// path: "details",
|
||||
// // name: R.documentDetails,
|
||||
// ),
|
||||
// TypedGoRoute<DocumentEditRoute>(
|
||||
// path: "edit",
|
||||
// // name: R.editDocument,
|
||||
// ),
|
||||
// ],
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// )
|
||||
class ProviderShellRoute extends ShellRouteData {
|
||||
final PaperlessApiFactory apiFactory;
|
||||
static final GlobalKey<NavigatorState> $navigatorKey = rootNavigatorKey;
|
||||
|
||||
const ProviderShellRoute(this.apiFactory);
|
||||
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
Widget navigator,
|
||||
) {
|
||||
final currentUserId = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.loggedInUserId!;
|
||||
final authenticatedUser =
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).get(
|
||||
currentUserId,
|
||||
)!;
|
||||
return HomeShellWidget(
|
||||
localUserId: authenticatedUser.id,
|
||||
paperlessApiVersion: authenticatedUser.apiVersion,
|
||||
paperlessProviderFactory: apiFactory,
|
||||
child: navigator,
|
||||
);
|
||||
}
|
||||
}
|
||||
29
lib/routes/typed/shells/scaffold_shell_route.dart
Normal file
29
lib/routes/typed/shells/scaffold_shell_route.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.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/features/home/view/scaffold_with_navigation_bar.dart';
|
||||
|
||||
class ScaffoldShellRoute extends StatefulShellRouteData {
|
||||
const ScaffoldShellRoute();
|
||||
@override
|
||||
Widget builder(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
StatefulNavigationShell navigationShell,
|
||||
) {
|
||||
final currentUserId = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||
.getValue()!
|
||||
.loggedInUserId!;
|
||||
final authenticatedUser =
|
||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).get(
|
||||
currentUserId,
|
||||
)!;
|
||||
return ScaffoldWithNavigationBar(
|
||||
authenticatedUser: authenticatedUser.paperlessUser,
|
||||
navigationShell: navigationShell,
|
||||
);
|
||||
}
|
||||
}
|
||||
30
lib/routes/typed/top_level/login_route.dart
Normal file
30
lib/routes/typed/top_level/login_route.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
||||
import 'package:paperless_mobile/routes/routes.dart';
|
||||
|
||||
part 'login_route.g.dart';
|
||||
|
||||
@TypedGoRoute<LoginRoute>(
|
||||
path: "/login",
|
||||
name: R.login,
|
||||
)
|
||||
class LoginRoute extends GoRouteData {
|
||||
const LoginRoute();
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const LoginPage();
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<String?> redirect(BuildContext context, GoRouterState state) {
|
||||
if (context.read<AuthenticationCubit>().state.isAuthenticated) {
|
||||
return "/landing";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
17
lib/routes/typed/top_level/settings_route.dart
Normal file
17
lib/routes/typed/top_level/settings_route.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||
import 'package:paperless_mobile/routes/routes.dart';
|
||||
|
||||
part 'settings_route.g.dart';
|
||||
|
||||
@TypedGoRoute<SettingsRoute>(
|
||||
path: "/settings",
|
||||
name: R.settings,
|
||||
)
|
||||
class SettingsRoute extends GoRouteData {
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const SettingsPage();
|
||||
}
|
||||
}
|
||||
18
lib/routes/typed/top_level/switching_accounts_route.dart
Normal file
18
lib/routes/typed/top_level/switching_accounts_route.dart
Normal file
@@ -0,0 +1,18 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/pages/switching_accounts_page.dart';
|
||||
import 'package:paperless_mobile/routes/routes.dart';
|
||||
|
||||
part 'switching_accounts_route.g.dart';
|
||||
|
||||
@TypedGoRoute<SwitchingAccountsRoute>(
|
||||
path: '/switching-accounts',
|
||||
name: R.switchingAccounts,
|
||||
)
|
||||
class SwitchingAccountsRoute extends GoRouteData {
|
||||
const SwitchingAccountsRoute();
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const SwitchingAccountsPage();
|
||||
}
|
||||
}
|
||||
19
lib/routes/typed/top_level/verify_identity_route.dart
Normal file
19
lib/routes/typed/top_level/verify_identity_route.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/verify_identity_page.dart';
|
||||
import 'package:paperless_mobile/routes/routes.dart';
|
||||
|
||||
part 'verify_identity_route.g.dart';
|
||||
|
||||
@TypedGoRoute<VerifyIdentityRoute>(
|
||||
path: '/verify-identity',
|
||||
name: R.verifyIdentity,
|
||||
)
|
||||
class VerifyIdentityRoute extends GoRouteData {
|
||||
const VerifyIdentityRoute();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) {
|
||||
return const VerifyIdentityPage();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user