mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2026-01-31 08:25:00 -06:00
feat: Migrate to go_router
This commit is contained in:
@@ -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;
|
bool showOnboarding;
|
||||||
|
|
||||||
@HiveField(4)
|
@HiveField(4)
|
||||||
String? currentLoggedInUser;
|
String? loggedInUserId;
|
||||||
|
|
||||||
@HiveField(5)
|
@HiveField(5)
|
||||||
FileDownloadType defaultDownloadType;
|
FileDownloadType defaultDownloadType;
|
||||||
@@ -37,7 +37,7 @@ class GlobalSettings with HiveObjectMixin {
|
|||||||
this.preferredThemeMode = ThemeMode.system,
|
this.preferredThemeMode = ThemeMode.system,
|
||||||
this.preferredColorSchemeOption = ColorSchemeOption.classic,
|
this.preferredColorSchemeOption = ColorSchemeOption.classic,
|
||||||
this.showOnboarding = true,
|
this.showOnboarding = true,
|
||||||
this.currentLoggedInUser,
|
this.loggedInUserId,
|
||||||
this.defaultDownloadType = FileDownloadType.alwaysAsk,
|
this.defaultDownloadType = FileDownloadType.alwaysAsk,
|
||||||
this.defaultShareType = FileDownloadType.alwaysAsk,
|
this.defaultShareType = FileDownloadType.alwaysAsk,
|
||||||
this.enforceSinglePagePdfUpload = false,
|
this.enforceSinglePagePdfUpload = false,
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ class LocalUserAccount extends HiveObject {
|
|||||||
@HiveField(7)
|
@HiveField(7)
|
||||||
UserModel paperlessUser;
|
UserModel paperlessUser;
|
||||||
|
|
||||||
|
@HiveField(8, defaultValue: 2)
|
||||||
|
int apiVersion;
|
||||||
|
|
||||||
LocalUserAccount({
|
LocalUserAccount({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.serverUrl,
|
required this.serverUrl,
|
||||||
required this.settings,
|
required this.settings,
|
||||||
required this.paperlessUser,
|
required this.paperlessUser,
|
||||||
|
required this.apiVersion,
|
||||||
});
|
});
|
||||||
|
|
||||||
static LocalUserAccount get current =>
|
bool get hasMultiUserSupport => apiVersion >= 3;
|
||||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).get(
|
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
|
||||||
.getValue()!
|
|
||||||
.currentLoggedInUser)!;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class LocalUserAppState extends HiveObject {
|
|||||||
final currentLocalUserId =
|
final currentLocalUserId =
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
.getValue()!
|
.getValue()!
|
||||||
.currentLoggedInUser!;
|
.loggedInUserId!;
|
||||||
return Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
return Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||||
.get(currentLocalUserId)!;
|
.get(currentLocalUserId)!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import 'package:connectivity_plus/connectivity_plus.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.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/config/hive/hive_config.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/global_settings.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/database/tables/local_user_app_state.dart';
|
||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.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/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/cubit/saved_view_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view_details/view/saved_view_details_page.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/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
// These are convenience methods for nativating to views without having to pass providers around explicitly.
|
// These are convenience methods for nativating to views without having to pass providers around explicitly.
|
||||||
@@ -38,20 +39,11 @@ import 'package:provider/provider.dart';
|
|||||||
Future<void> pushDocumentSearchPage(BuildContext context) {
|
Future<void> pushDocumentSearchPage(BuildContext context) {
|
||||||
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
.getValue()!
|
.getValue()!
|
||||||
.currentLoggedInUser;
|
.loggedInUserId;
|
||||||
final userRepo = context.read<UserRepository>();
|
final userRepo = context.read<UserRepository>();
|
||||||
return Navigator.of(context).push(
|
return Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => MultiProvider(
|
builder: (_) => BlocProvider(
|
||||||
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(
|
create: (context) => DocumentSearchCubit(
|
||||||
context.read(),
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
@@ -59,38 +51,6 @@ Future<void> pushDocumentSearchPage(BuildContext context) {
|
|||||||
.get(currentUser)!,
|
.get(currentUser)!,
|
||||||
),
|
),
|
||||||
child: const DocumentSearchPage(),
|
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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -106,7 +66,7 @@ Future<void> pushSavedViewDetailsRoute(
|
|||||||
builder: (_) => MultiProvider(
|
builder: (_) => MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider.value(value: apiVersion),
|
Provider.value(value: apiVersion),
|
||||||
if (apiVersion.hasMultiUserSupport)
|
if (context.watch<LocalUserAccount>().hasMultiUserSupport)
|
||||||
Provider.value(value: context.read<UserRepository>()),
|
Provider.value(value: context.read<UserRepository>()),
|
||||||
Provider.value(value: context.read<LabelRepository>()),
|
Provider.value(value: context.read<LabelRepository>()),
|
||||||
Provider.value(value: context.read<DocumentChangedNotifier>()),
|
Provider.value(value: context.read<DocumentChangedNotifier>()),
|
||||||
@@ -147,8 +107,10 @@ Future<SavedView?> pushAddSavedViewRoute(BuildContext context,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pushLinkedDocumentsView(BuildContext context,
|
Future<void> pushLinkedDocumentsView(
|
||||||
{required DocumentFilter filter}) {
|
BuildContext context, {
|
||||||
|
required DocumentFilter filter,
|
||||||
|
}) {
|
||||||
return Navigator.push(
|
return Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@@ -161,7 +123,7 @@ Future<void> pushLinkedDocumentsView(BuildContext context,
|
|||||||
Provider.value(value: context.read<LocalNotificationService>()),
|
Provider.value(value: context.read<LocalNotificationService>()),
|
||||||
Provider.value(value: context.read<CacheManager>()),
|
Provider.value(value: context.read<CacheManager>()),
|
||||||
Provider.value(value: context.read<ConnectivityCubit>()),
|
Provider.value(value: context.read<ConnectivityCubit>()),
|
||||||
if (context.read<ApiVersion>().hasMultiUserSupport)
|
if (context.watch<LocalUserAccount>().hasMultiUserSupport)
|
||||||
Provider.value(value: context.read<UserRepository>()),
|
Provider.value(value: context.read<UserRepository>()),
|
||||||
],
|
],
|
||||||
builder: (context, _) => BlocProvider(
|
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/home/view/model/api_version.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.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:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
@@ -91,18 +92,7 @@ class AppDrawer extends StatelessWidget {
|
|||||||
title: Text(
|
title: Text(
|
||||||
S.of(context)!.settings,
|
S.of(context)!.settings,
|
||||||
),
|
),
|
||||||
onTap: () => Navigator.of(context).push(
|
onTap: () => SettingsRoute().push(context),
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => MultiProvider(
|
|
||||||
providers: [
|
|
||||||
Provider.value(
|
|
||||||
value: context.read<PaperlessServerStatsApi>()),
|
|
||||||
Provider.value(value: context.read<ApiVersion>()),
|
|
||||||
],
|
|
||||||
child: const SettingsPage(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
loadSuggestions();
|
|
||||||
loadMetaData();
|
loadMetaData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,13 +53,6 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
_notifier.notifyDeleted(document);
|
_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 {
|
Future<void> loadMetaData() async {
|
||||||
final metaData = await _api.getMetaData(state.document);
|
final metaData = await _api.getMetaData(state.document);
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ class DocumentDetailsState with _$DocumentDetailsState {
|
|||||||
DocumentMetaData? metaData,
|
DocumentMetaData? metaData,
|
||||||
@Default(false) bool isFullContentLoaded,
|
@Default(false) bool isFullContentLoaded,
|
||||||
String? fullContent,
|
String? fullContent,
|
||||||
FieldSuggestions? suggestions,
|
|
||||||
@Default({}) Map<int, Correspondent> correspondents,
|
@Default({}) Map<int, Correspondent> correspondents,
|
||||||
@Default({}) Map<int, DocumentType> documentTypes,
|
@Default({}) Map<int, DocumentType> documentTypes,
|
||||||
@Default({}) Map<int, Tag> tags,
|
@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_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_permissions_widget.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_share_button.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/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/delete_document_confirmation_dialog.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.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/features/similar_documents/view/similar_documents_view.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
|
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||||
|
|
||||||
class DocumentDetailsPage extends StatefulWidget {
|
class DocumentDetailsPage extends StatefulWidget {
|
||||||
final bool isLabelClickable;
|
final bool isLabelClickable;
|
||||||
@@ -46,9 +45,9 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final apiVersion = context.watch<ApiVersion>();
|
final hasMultiUserSupport =
|
||||||
|
context.watch<LocalUserAccount>().hasMultiUserSupport;
|
||||||
final tabLength = 4 + (apiVersion.hasMultiUserSupport ? 1 : 0);
|
final tabLength = 4 + (hasMultiUserSupport ? 1 : 0);
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
@@ -171,7 +170,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (apiVersion.hasMultiUserSupport)
|
if (hasMultiUserSupport)
|
||||||
Tab(
|
Tab(
|
||||||
child: Text(
|
child: Text(
|
||||||
"Permissions",
|
"Permissions",
|
||||||
@@ -259,7 +258,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (apiVersion.hasMultiUserSupport)
|
if (hasMultiUserSupport)
|
||||||
CustomScrollView(
|
CustomScrollView(
|
||||||
controller: _pagingScrollController,
|
controller: _pagingScrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
@@ -286,8 +285,10 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEditButton() {
|
Widget _buildEditButton() {
|
||||||
|
final currentUser = context.watch<LocalUserAccount>();
|
||||||
|
|
||||||
bool canEdit = context.watchInternetConnection &&
|
bool canEdit = context.watchInternetConnection &&
|
||||||
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
currentUser.paperlessUser.canEditDocuments;
|
||||||
if (!canEdit) {
|
if (!canEdit) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
@@ -302,7 +303,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
verticalOffset: 40,
|
verticalOffset: 40,
|
||||||
child: FloatingActionButton(
|
child: FloatingActionButton(
|
||||||
child: const Icon(Icons.edit),
|
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>(
|
child: BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
builder: (context, connectivityState) {
|
builder: (context, connectivityState) {
|
||||||
final isConnected = connectivityState.isConnected;
|
final isConnected = connectivityState.isConnected;
|
||||||
|
final currentUser = context.watch<LocalUserAccount>();
|
||||||
final canDelete = isConnected &&
|
final canDelete =
|
||||||
LocalUserAccount.current.paperlessUser.canDeleteDocuments;
|
isConnected && currentUser.paperlessUser.canDeleteDocuments;
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
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 {
|
void _onOpenFileInSystemViewer() async {
|
||||||
final status =
|
final status =
|
||||||
await context.read<DocumentDetailsCubit>().openDocumentInSystemViewer();
|
await context.read<DocumentDetailsCubit>().openDocumentInSystemViewer();
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final userCanEditDocument =
|
final userCanEditDocument =
|
||||||
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
context.watch<LocalUserAccount>().paperlessUser.canEditDocuments;
|
||||||
return BlocListener<DocumentDetailsCubit, DocumentDetailsState>(
|
return BlocListener<DocumentDetailsCubit, DocumentDetailsState>(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.document.archiveSerialNumber !=
|
previous.document.archiveSerialNumber !=
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.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,
|
label: S.of(context)!.createdAt,
|
||||||
).paddedOnly(bottom: itemSpacing),
|
).paddedOnly(bottom: itemSpacing),
|
||||||
if (document.documentType != null &&
|
if (document.documentType != null &&
|
||||||
LocalUserAccount.current.paperlessUser.canViewDocumentTypes)
|
context
|
||||||
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canViewDocumentTypes)
|
||||||
DetailsItem(
|
DetailsItem(
|
||||||
label: S.of(context)!.documentType,
|
label: S.of(context)!.documentType,
|
||||||
content: LabelText<DocumentType>(
|
content: LabelText<DocumentType>(
|
||||||
@@ -56,7 +60,10 @@ class DocumentOverviewWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
).paddedOnly(bottom: itemSpacing),
|
).paddedOnly(bottom: itemSpacing),
|
||||||
if (document.correspondent != null &&
|
if (document.correspondent != null &&
|
||||||
LocalUserAccount.current.paperlessUser.canViewCorrespondents)
|
context
|
||||||
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canViewCorrespondents)
|
||||||
DetailsItem(
|
DetailsItem(
|
||||||
label: S.of(context)!.correspondent,
|
label: S.of(context)!.correspondent,
|
||||||
content: LabelText<Correspondent>(
|
content: LabelText<Correspondent>(
|
||||||
@@ -65,7 +72,10 @@ class DocumentOverviewWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
).paddedOnly(bottom: itemSpacing),
|
).paddedOnly(bottom: itemSpacing),
|
||||||
if (document.storagePath != null &&
|
if (document.storagePath != null &&
|
||||||
LocalUserAccount.current.paperlessUser.canViewStoragePaths)
|
context
|
||||||
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canViewStoragePaths)
|
||||||
DetailsItem(
|
DetailsItem(
|
||||||
label: S.of(context)!.storagePath,
|
label: S.of(context)!.storagePath,
|
||||||
content: LabelText<StoragePath>(
|
content: LabelText<StoragePath>(
|
||||||
@@ -73,7 +83,7 @@ class DocumentOverviewWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
).paddedOnly(bottom: itemSpacing),
|
).paddedOnly(bottom: itemSpacing),
|
||||||
if (document.tags.isNotEmpty &&
|
if (document.tags.isNotEmpty &&
|
||||||
LocalUserAccount.current.paperlessUser.canViewTags)
|
context.watch<LocalUserAccount>().paperlessUser.canViewTags)
|
||||||
DetailsItem(
|
DetailsItem(
|
||||||
label: S.of(context)!.tags,
|
label: S.of(context)!.tags,
|
||||||
content: Padding(
|
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) {
|
void replace(DocumentModel document) {
|
||||||
emit(state.copyWith(document: document));
|
emit(state.copyWith(document: document));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ part of 'document_edit_cubit.dart';
|
|||||||
class DocumentEditState with _$DocumentEditState {
|
class DocumentEditState with _$DocumentEditState {
|
||||||
const factory DocumentEditState({
|
const factory DocumentEditState({
|
||||||
required DocumentModel document,
|
required DocumentModel document,
|
||||||
|
FieldSuggestions? suggestions,
|
||||||
@Default({}) Map<int, Correspondent> correspondents,
|
@Default({}) Map<int, Correspondent> correspondents,
|
||||||
@Default({}) Map<int, DocumentType> documentTypes,
|
@Default({}) Map<int, DocumentType> documentTypes,
|
||||||
@Default({}) Map<int, StoragePath> storagePaths,
|
@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';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
|
|
||||||
class DocumentEditPage extends StatefulWidget {
|
class DocumentEditPage extends StatefulWidget {
|
||||||
final FieldSuggestions? suggestions;
|
|
||||||
const DocumentEditPage({
|
const DocumentEditPage({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.suggestions,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -44,19 +42,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
|
final GlobalKey<FormBuilderState> _formKey = GlobalKey();
|
||||||
bool _isSubmitLoading = false;
|
bool _isSubmitLoading = false;
|
||||||
|
|
||||||
late final FieldSuggestions? _filteredSuggestions;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_filteredSuggestions = widget.suggestions
|
|
||||||
?.documentDifference(context.read<DocumentEditCubit>().state.document);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<DocumentEditCubit, DocumentEditState>(
|
return BlocBuilder<DocumentEditCubit, DocumentEditState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final filteredSuggestions = state.suggestions?.documentDifference(
|
||||||
|
context.read<DocumentEditCubit>().state.document);
|
||||||
return DefaultTabController(
|
return DefaultTabController(
|
||||||
length: 2,
|
length: 2,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
@@ -94,8 +85,10 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
ListView(
|
ListView(
|
||||||
children: [
|
children: [
|
||||||
_buildTitleFormField(state.document.title).padded(),
|
_buildTitleFormField(state.document.title).padded(),
|
||||||
_buildCreatedAtFormField(state.document.created)
|
_buildCreatedAtFormField(
|
||||||
.padded(),
|
state.document.created,
|
||||||
|
filteredSuggestions,
|
||||||
|
).padded(),
|
||||||
// Correspondent form field
|
// Correspondent form field
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -123,15 +116,17 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
name: fkCorrespondent,
|
name: fkCorrespondent,
|
||||||
prefixIcon: const Icon(Icons.person_outlined),
|
prefixIcon: const Icon(Icons.person_outlined),
|
||||||
allowSelectUnassigned: true,
|
allowSelectUnassigned: true,
|
||||||
canCreateNewLabel: LocalUserAccount.current
|
canCreateNewLabel: context
|
||||||
.paperlessUser.canCreateCorrespondents,
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canCreateCorrespondents,
|
||||||
),
|
),
|
||||||
if (_filteredSuggestions
|
if (filteredSuggestions
|
||||||
?.hasSuggestedCorrespondents ??
|
?.hasSuggestedCorrespondents ??
|
||||||
false)
|
false)
|
||||||
_buildSuggestionsSkeleton<int>(
|
_buildSuggestionsSkeleton<int>(
|
||||||
suggestions:
|
suggestions:
|
||||||
_filteredSuggestions!.correspondents,
|
filteredSuggestions!.correspondents,
|
||||||
itemBuilder: (context, itemData) =>
|
itemBuilder: (context, itemData) =>
|
||||||
ActionChip(
|
ActionChip(
|
||||||
label: Text(
|
label: Text(
|
||||||
@@ -160,8 +155,10 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
initialName: currentInput,
|
initialName: currentInput,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
canCreateNewLabel: LocalUserAccount.current
|
canCreateNewLabel: context
|
||||||
.paperlessUser.canCreateDocumentTypes,
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canCreateDocumentTypes,
|
||||||
addLabelText: S.of(context)!.addDocumentType,
|
addLabelText: S.of(context)!.addDocumentType,
|
||||||
labelText: S.of(context)!.documentType,
|
labelText: S.of(context)!.documentType,
|
||||||
initialValue:
|
initialValue:
|
||||||
@@ -175,12 +172,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
const Icon(Icons.description_outlined),
|
const Icon(Icons.description_outlined),
|
||||||
allowSelectUnassigned: true,
|
allowSelectUnassigned: true,
|
||||||
),
|
),
|
||||||
if (_filteredSuggestions
|
if (filteredSuggestions
|
||||||
?.hasSuggestedDocumentTypes ??
|
?.hasSuggestedDocumentTypes ??
|
||||||
false)
|
false)
|
||||||
_buildSuggestionsSkeleton<int>(
|
_buildSuggestionsSkeleton<int>(
|
||||||
suggestions:
|
suggestions:
|
||||||
_filteredSuggestions!.documentTypes,
|
filteredSuggestions!.documentTypes,
|
||||||
itemBuilder: (context, itemData) =>
|
itemBuilder: (context, itemData) =>
|
||||||
ActionChip(
|
ActionChip(
|
||||||
label: Text(
|
label: Text(
|
||||||
@@ -204,10 +201,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
RepositoryProvider.value(
|
RepositoryProvider.value(
|
||||||
value: context.read<LabelRepository>(),
|
value: context.read<LabelRepository>(),
|
||||||
child: AddStoragePathPage(
|
child: AddStoragePathPage(
|
||||||
initalName: initialValue),
|
initialName: initialValue),
|
||||||
),
|
),
|
||||||
canCreateNewLabel: LocalUserAccount.current
|
canCreateNewLabel: context
|
||||||
.paperlessUser.canCreateStoragePaths,
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canCreateStoragePaths,
|
||||||
addLabelText: S.of(context)!.addStoragePath,
|
addLabelText: S.of(context)!.addStoragePath,
|
||||||
labelText: S.of(context)!.storagePath,
|
labelText: S.of(context)!.storagePath,
|
||||||
options: state.storagePaths,
|
options: state.storagePaths,
|
||||||
@@ -232,14 +231,14 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
include: state.document.tags.toList(),
|
include: state.document.tags.toList(),
|
||||||
),
|
),
|
||||||
).padded(),
|
).padded(),
|
||||||
if (_filteredSuggestions?.tags
|
if (filteredSuggestions?.tags
|
||||||
.toSet()
|
.toSet()
|
||||||
.difference(state.document.tags.toSet())
|
.difference(state.document.tags.toSet())
|
||||||
.isNotEmpty ??
|
.isNotEmpty ??
|
||||||
false)
|
false)
|
||||||
_buildSuggestionsSkeleton<int>(
|
_buildSuggestionsSkeleton<int>(
|
||||||
suggestions:
|
suggestions:
|
||||||
(_filteredSuggestions?.tags.toSet() ?? {}),
|
(filteredSuggestions?.tags.toSet() ?? {}),
|
||||||
itemBuilder: (context, itemData) {
|
itemBuilder: (context, itemData) {
|
||||||
final tag = state.tags[itemData]!;
|
final tag = state.tags[itemData]!;
|
||||||
return ActionChip(
|
return ActionChip(
|
||||||
@@ -343,7 +342,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCreatedAtFormField(DateTime? initialCreatedAtDate) {
|
Widget _buildCreatedAtFormField(
|
||||||
|
DateTime? initialCreatedAtDate, FieldSuggestions? filteredSuggestions) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -358,9 +358,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
format: DateFormat.yMMMMd(),
|
format: DateFormat.yMMMMd(),
|
||||||
initialEntryMode: DatePickerEntryMode.calendar,
|
initialEntryMode: DatePickerEntryMode.calendar,
|
||||||
),
|
),
|
||||||
if (_filteredSuggestions?.hasSuggestedDates ?? false)
|
if (filteredSuggestions?.hasSuggestedDates ?? false)
|
||||||
_buildSuggestionsSkeleton<DateTime>(
|
_buildSuggestionsSkeleton<DateTime>(
|
||||||
suggestions: _filteredSuggestions!.dates,
|
suggestions: filteredSuggestions!.dates,
|
||||||
itemBuilder: (context, itemData) => ActionChip(
|
itemBuilder: (context, itemData) => ActionChip(
|
||||||
label: Text(DateFormat.yMMMd().format(itemData)),
|
label: Text(DateFormat.yMMMd().format(itemData)),
|
||||||
onPressed: () => _formKey.currentState?.fields[fkCreatedDate]
|
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<PaperlessDocumentsApi>()),
|
||||||
Provider.value(value: context.read<CacheManager>()),
|
Provider.value(value: context.read<CacheManager>()),
|
||||||
Provider.value(value: context.read<ApiVersion>()),
|
Provider.value(value: context.read<ApiVersion>()),
|
||||||
if (context.read<ApiVersion>().hasMultiUserSupport)
|
if (context.watch<LocalUserAccount>().hasMultiUserSupport)
|
||||||
Provider.value(value: context.read<UserRepository>()),
|
Provider.value(value: context.read<UserRepository>()),
|
||||||
],
|
],
|
||||||
child: Provider(
|
child: Provider(
|
||||||
@@ -99,7 +99,7 @@ class _DocumentSearchBarState extends State<DocumentSearchBar> {
|
|||||||
context.read(),
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||||
.get(LocalUserAccount.current.id)!,
|
.get(context.watch<LocalUserAccount>().id)!,
|
||||||
),
|
),
|
||||||
builder: (_, __) => const DocumentSearchPage(),
|
builder: (_, __) => const DocumentSearchPage(),
|
||||||
),
|
),
|
||||||
@@ -112,19 +112,7 @@ class _DocumentSearchBarState extends State<DocumentSearchBar> {
|
|||||||
IconButton _buildUserAvatar(BuildContext context) {
|
IconButton _buildUserAvatar(BuildContext context) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
padding: const EdgeInsets.all(6),
|
padding: const EdgeInsets.all(6),
|
||||||
icon: GlobalSettingsBuilder(
|
icon: UserAvatar(account: context.watch<LocalUserAccount>()),
|
||||||
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);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final apiVersion = context.read<ApiVersion>();
|
final apiVersion = context.read<ApiVersion>();
|
||||||
showDialog(
|
showDialog(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:math' as math;
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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/core/navigation/push_routes.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.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/adaptive_documents_view.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.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/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||||
|
|
||||||
class DocumentSearchPage extends StatefulWidget {
|
class DocumentSearchPage extends StatefulWidget {
|
||||||
const DocumentSearchPage({super.key});
|
const DocumentSearchPage({super.key});
|
||||||
@@ -218,11 +220,8 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
|||||||
hasLoaded: state.hasLoaded,
|
hasLoaded: state.hasLoaded,
|
||||||
enableHeroAnimation: false,
|
enableHeroAnimation: false,
|
||||||
onTap: (document) {
|
onTap: (document) {
|
||||||
pushDocumentDetailsRoute(
|
DocumentDetailsRoute($extra: document, isLabelClickable: false)
|
||||||
context,
|
.push(context);
|
||||||
document: document,
|
|
||||||
isLabelClickable: false,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class SliverSearchBar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (LocalUserAccount.current.paperlessUser.canViewDocuments) {
|
if (context.watch<LocalUserAccount>().paperlessUser.canViewDocuments) {
|
||||||
return SliverAppBar(
|
return SliverAppBar(
|
||||||
toolbarHeight: kToolbarHeight,
|
toolbarHeight: kToolbarHeight,
|
||||||
flexibleSpace: Container(
|
flexibleSpace: Container(
|
||||||
@@ -49,7 +49,7 @@ class SliverSearchBar extends StatelessWidget {
|
|||||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||||
.listenable(),
|
.listenable(),
|
||||||
builder: (context, box, _) {
|
builder: (context, box, _) {
|
||||||
final account = box.get(settings.currentLoggedInUser!)!;
|
final account = box.get(settings.loggedInUserId!)!;
|
||||||
return UserAvatar(account: account);
|
return UserAvatar(account: account);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -198,8 +198,10 @@ class _DocumentUploadPreparationPageState
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Correspondent
|
// Correspondent
|
||||||
if (LocalUserAccount
|
if (context
|
||||||
.current.paperlessUser.canViewCorrespondents)
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canViewCorrespondents)
|
||||||
LabelFormField<Correspondent>(
|
LabelFormField<Correspondent>(
|
||||||
showAnyAssignedOption: false,
|
showAnyAssignedOption: false,
|
||||||
showNotAssignedOption: false,
|
showNotAssignedOption: false,
|
||||||
@@ -220,11 +222,16 @@ class _DocumentUploadPreparationPageState
|
|||||||
options: state.correspondents,
|
options: state.correspondents,
|
||||||
prefixIcon: const Icon(Icons.person_outline),
|
prefixIcon: const Icon(Icons.person_outline),
|
||||||
allowSelectUnassigned: true,
|
allowSelectUnassigned: true,
|
||||||
canCreateNewLabel: LocalUserAccount
|
canCreateNewLabel: context
|
||||||
.current.paperlessUser.canCreateCorrespondents,
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canCreateCorrespondents,
|
||||||
),
|
),
|
||||||
// Document type
|
// Document type
|
||||||
if (LocalUserAccount.current.paperlessUser.canViewDocumentTypes)
|
if (context
|
||||||
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canViewDocumentTypes)
|
||||||
LabelFormField<DocumentType>(
|
LabelFormField<DocumentType>(
|
||||||
showAnyAssignedOption: false,
|
showAnyAssignedOption: false,
|
||||||
showNotAssignedOption: false,
|
showNotAssignedOption: false,
|
||||||
@@ -245,10 +252,12 @@ class _DocumentUploadPreparationPageState
|
|||||||
options: state.documentTypes,
|
options: state.documentTypes,
|
||||||
prefixIcon: const Icon(Icons.description_outlined),
|
prefixIcon: const Icon(Icons.description_outlined),
|
||||||
allowSelectUnassigned: true,
|
allowSelectUnassigned: true,
|
||||||
canCreateNewLabel: LocalUserAccount
|
canCreateNewLabel: context
|
||||||
.current.paperlessUser.canCreateDocumentTypes,
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canCreateDocumentTypes,
|
||||||
),
|
),
|
||||||
if (LocalUserAccount.current.paperlessUser.canViewTags)
|
if (context.watch<LocalUserAccount>().paperlessUser.canViewTags)
|
||||||
TagsFormField(
|
TagsFormField(
|
||||||
name: DocumentModel.tagsKey,
|
name: DocumentModel.tagsKey,
|
||||||
allowCreation: true,
|
allowCreation: true,
|
||||||
@@ -296,7 +305,7 @@ class _DocumentUploadPreparationPageState
|
|||||||
),
|
),
|
||||||
userId: Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
userId: Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
.getValue()!
|
.getValue()!
|
||||||
.currentLoggedInUser!,
|
.loggedInUserId!,
|
||||||
title: title,
|
title: title,
|
||||||
documentType: docType,
|
documentType: docType,
|
||||||
correspondent: correspondent,
|
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/features/tasks/cubit/task_status_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
|
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||||
|
|
||||||
class DocumentFilterIntent {
|
class DocumentFilterIntent {
|
||||||
final DocumentFilter? filter;
|
final DocumentFilter? filter;
|
||||||
@@ -55,7 +56,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final showSavedViews =
|
final showSavedViews =
|
||||||
LocalUserAccount.current.paperlessUser.canViewSavedViews;
|
context.read<LocalUserAccount>().paperlessUser.canViewSavedViews;
|
||||||
_tabController = TabController(
|
_tabController = TabController(
|
||||||
length: showSavedViews ? 2 : 1,
|
length: showSavedViews ? 2 : 1,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
@@ -116,7 +117,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
return SafeArea(
|
return SafeArea(
|
||||||
top: true,
|
top: true,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
drawer: const AppDrawer(),
|
drawer: AppDrawer(),
|
||||||
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
||||||
@@ -232,7 +233,9 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(text: S.of(context)!.documents),
|
Tab(text: S.of(context)!.documents),
|
||||||
if (LocalUserAccount.current.paperlessUser
|
if (context
|
||||||
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
.canViewSavedViews)
|
.canViewSavedViews)
|
||||||
Tab(text: S.of(context)!.views),
|
Tab(text: S.of(context)!.views),
|
||||||
],
|
],
|
||||||
@@ -276,8 +279,10 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (LocalUserAccount
|
if (context
|
||||||
.current.paperlessUser.canViewSavedViews)
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canViewSavedViews)
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return _buildSavedViewsTab(
|
return _buildSavedViewsTab(
|
||||||
@@ -378,7 +383,9 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
final allowToggleFilter = state.selection.isEmpty;
|
final allowToggleFilter = state.selection.isEmpty;
|
||||||
return SliverAdaptiveDocumentsView(
|
return SliverAdaptiveDocumentsView(
|
||||||
viewType: state.viewType,
|
viewType: state.viewType,
|
||||||
onTap: _openDetails,
|
onTap: (document) {
|
||||||
|
DocumentDetailsRoute($extra: document).push(context);
|
||||||
|
},
|
||||||
onSelected:
|
onSelected:
|
||||||
context.read<DocumentsCubit>().toggleDocumentSelection,
|
context.read<DocumentsCubit>().toggleDocumentSelection,
|
||||||
hasInternetConnection: connectivityState.isConnected,
|
hasInternetConnection: connectivityState.isConnected,
|
||||||
@@ -488,13 +495,6 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openDetails(DocumentModel document) {
|
|
||||||
pushDocumentDetailsRoute(
|
|
||||||
context,
|
|
||||||
document: document,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _addTagToFilter(int tagId) {
|
void _addTagToFilter(int tagId) {
|
||||||
final cubit = context.read<DocumentsCubit>();
|
final cubit = context.read<DocumentsCubit>();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import 'dart:math';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_html/flutter_html.dart';
|
import 'package:flutter_html/flutter_html.dart';
|
||||||
|
import 'package:hive_flutter/adapters.dart';
|
||||||
import 'package:intl/intl.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/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
@@ -32,6 +37,12 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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 size = MediaQuery.of(context).size;
|
||||||
final insets = MediaQuery.of(context).viewInsets;
|
final insets = MediaQuery.of(context).viewInsets;
|
||||||
final padding = MediaQuery.of(context).viewPadding;
|
final padding = MediaQuery.of(context).viewPadding;
|
||||||
@@ -104,6 +115,7 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
).paddedLTRB(8, 0, 8, 4),
|
).paddedLTRB(8, 0, 8, 4),
|
||||||
|
if (paperlessUser.canViewCorrespondents)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
const Icon(
|
||||||
@@ -122,6 +134,7 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddedLTRB(8, 0, 8, 4),
|
).paddedLTRB(8, 0, 8, 4),
|
||||||
|
if (paperlessUser.canViewDocumentTypes)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(
|
const Icon(
|
||||||
@@ -140,6 +153,7 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddedLTRB(8, 0, 8, 4),
|
).paddedLTRB(8, 0, 8, 4),
|
||||||
|
if (paperlessUser.canViewTags)
|
||||||
TagsWidget(
|
TagsWidget(
|
||||||
tags: document.tags
|
tags: document.tags
|
||||||
.map((e) => context.watch<LabelRepository>().state.tags[e]!)
|
.map((e) => context.watch<LabelRepository>().state.tags[e]!)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.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,
|
initialValue: widget.initialFilter.documentType,
|
||||||
prefixIcon: const Icon(Icons.description_outlined),
|
prefixIcon: const Icon(Icons.description_outlined),
|
||||||
allowSelectUnassigned: false,
|
allowSelectUnassigned: false,
|
||||||
canCreateNewLabel:
|
canCreateNewLabel: context
|
||||||
LocalUserAccount.current.paperlessUser.canCreateDocumentTypes,
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canCreateDocumentTypes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,8 +176,10 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
|||||||
initialValue: widget.initialFilter.correspondent,
|
initialValue: widget.initialFilter.correspondent,
|
||||||
prefixIcon: const Icon(Icons.person_outline),
|
prefixIcon: const Icon(Icons.person_outline),
|
||||||
allowSelectUnassigned: false,
|
allowSelectUnassigned: false,
|
||||||
canCreateNewLabel:
|
canCreateNewLabel: context
|
||||||
LocalUserAccount.current.paperlessUser.canCreateCorrespondents,
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canCreateCorrespondents,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +192,7 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
|||||||
prefixIcon: const Icon(Icons.folder_outlined),
|
prefixIcon: const Icon(Icons.folder_outlined),
|
||||||
allowSelectUnassigned: false,
|
allowSelectUnassigned: false,
|
||||||
canCreateNewLabel:
|
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';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
class AddStoragePathPage extends StatelessWidget {
|
class AddStoragePathPage extends StatelessWidget {
|
||||||
final String? initalName;
|
final String? initialName;
|
||||||
const AddStoragePathPage({Key? key, this.initalName}) : super(key: key);
|
const AddStoragePathPage({Key? key, this.initialName}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -19,7 +19,7 @@ class AddStoragePathPage extends StatelessWidget {
|
|||||||
child: AddLabelPage<StoragePath>(
|
child: AddLabelPage<StoragePath>(
|
||||||
pageTitle: Text(S.of(context)!.addStoragePath),
|
pageTitle: Text(S.of(context)!.addStoragePath),
|
||||||
fromJsonT: StoragePath.fromJson,
|
fromJsonT: StoragePath.fromJson,
|
||||||
initialName: initalName,
|
initialName: initialName,
|
||||||
onSubmit: (context, label) =>
|
onSubmit: (context, label) =>
|
||||||
context.read<EditLabelCubit>().addStoragePath(label),
|
context.read<EditLabelCubit>().addStoragePath(label),
|
||||||
additionalFields: const [
|
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';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
class AddTagPage extends StatelessWidget {
|
class AddTagPage extends StatelessWidget {
|
||||||
final String? initialValue;
|
final String? initialName;
|
||||||
const AddTagPage({Key? key, this.initialValue}) : super(key: key);
|
const AddTagPage({Key? key, this.initialName}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -22,7 +22,7 @@ class AddTagPage extends StatelessWidget {
|
|||||||
child: AddLabelPage<Tag>(
|
child: AddLabelPage<Tag>(
|
||||||
pageTitle: Text(S.of(context)!.addTag),
|
pageTitle: Text(S.of(context)!.addTag),
|
||||||
fromJsonT: Tag.fromJson,
|
fromJsonT: Tag.fromJson,
|
||||||
initialName: initialValue,
|
initialName: initialName,
|
||||||
onSubmit: (context, label) =>
|
onSubmit: (context, label) =>
|
||||||
context.read<EditLabelCubit>().addTag(label),
|
context.read<EditLabelCubit>().addTag(label),
|
||||||
additionalFields: [
|
additionalFields: [
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ class EditCorrespondentPage extends StatelessWidget {
|
|||||||
context.read<EditLabelCubit>().replaceCorrespondent(label),
|
context.read<EditLabelCubit>().replaceCorrespondent(label),
|
||||||
onDelete: (context, label) =>
|
onDelete: (context, label) =>
|
||||||
context.read<EditLabelCubit>().removeCorrespondent(label),
|
context.read<EditLabelCubit>().removeCorrespondent(label),
|
||||||
canDelete:
|
canDelete: context
|
||||||
LocalUserAccount.current.paperlessUser.canDeleteCorrespondents,
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canDeleteCorrespondents,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ class EditDocumentTypePage extends StatelessWidget {
|
|||||||
context.read<EditLabelCubit>().replaceDocumentType(label),
|
context.read<EditLabelCubit>().replaceDocumentType(label),
|
||||||
onDelete: (context, label) =>
|
onDelete: (context, label) =>
|
||||||
context.read<EditLabelCubit>().removeDocumentType(label),
|
context.read<EditLabelCubit>().removeDocumentType(label),
|
||||||
canDelete:
|
canDelete: context
|
||||||
LocalUserAccount.current.paperlessUser.canDeleteDocumentTypes,
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canDeleteDocumentTypes,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ class EditStoragePathPage extends StatelessWidget {
|
|||||||
context.read<EditLabelCubit>().replaceStoragePath(label),
|
context.read<EditLabelCubit>().replaceStoragePath(label),
|
||||||
onDelete: (context, label) =>
|
onDelete: (context, label) =>
|
||||||
context.read<EditLabelCubit>().removeStoragePath(label),
|
context.read<EditLabelCubit>().removeStoragePath(label),
|
||||||
canDelete: LocalUserAccount.current.paperlessUser.canDeleteStoragePaths,
|
canDelete: context
|
||||||
|
.watch<LocalUserAccount>()
|
||||||
|
.paperlessUser
|
||||||
|
.canDeleteStoragePaths,
|
||||||
additionalFields: [
|
additionalFields: [
|
||||||
StoragePathAutofillFormBuilderField(
|
StoragePathAutofillFormBuilderField(
|
||||||
name: StoragePath.pathKey,
|
name: StoragePath.pathKey,
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ class EditTagPage extends StatelessWidget {
|
|||||||
context.read<EditLabelCubit>().replaceTag(label),
|
context.read<EditLabelCubit>().replaceTag(label),
|
||||||
onDelete: (context, label) =>
|
onDelete: (context, label) =>
|
||||||
context.read<EditLabelCubit>().removeTag(label),
|
context.read<EditLabelCubit>().removeTag(label),
|
||||||
canDelete: LocalUserAccount.current.paperlessUser.canDeleteTags,
|
canDelete:
|
||||||
|
context.watch<LocalUserAccount>().paperlessUser.canDeleteTags,
|
||||||
additionalFields: [
|
additionalFields: [
|
||||||
FormBuilderColorPickerField(
|
FormBuilderColorPickerField(
|
||||||
initialValue: tag.color,
|
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:flutter_form_builder/flutter_form_builder.dart';
|
||||||
|
|
||||||
import 'package:paperless_api/paperless_api.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/core/translation/matching_algorithm_localization_mapper.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/model/api_version.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) {
|
Widget build(BuildContext context) {
|
||||||
List<MatchingAlgorithm> selectableMatchingAlgorithmValues =
|
List<MatchingAlgorithm> selectableMatchingAlgorithmValues =
|
||||||
getSelectableMatchingAlgorithmValues(
|
getSelectableMatchingAlgorithmValues(
|
||||||
context.watch<ApiVersion>().hasMultiUserSupport,
|
context.watch<LocalUserAccount>().hasMultiUserSupport,
|
||||||
);
|
);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
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),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 {
|
class ApiVersion {
|
||||||
final int version;
|
final int version;
|
||||||
|
|
||||||
ApiVersion(this.version);
|
const ApiVersion(this.version);
|
||||||
|
|
||||||
|
|
||||||
bool get hasMultiUserSupport => version >= 3;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
Future<void> initialize() async {
|
||||||
await refreshItemsInInboxCount(false);
|
await refreshItemsInInboxCount(false);
|
||||||
await loadInbox();
|
await loadInbox();
|
||||||
|
super.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refreshItemsInInboxCount([bool shouldLoadInbox = true]) async {
|
Future<void> refreshItemsInInboxCount([bool shouldLoadInbox = true]) async {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class _InboxPageState extends State<InboxPage>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final canEditDocument =
|
final canEditDocument =
|
||||||
LocalUserAccount.current.paperlessUser.canEditDocuments;
|
context.watch<LocalUserAccount>().paperlessUser.canEditDocuments;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
drawer: const AppDrawer(),
|
drawer: const AppDrawer(),
|
||||||
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.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/tags/view/widgets/tags_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_text.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/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||||
|
|
||||||
class InboxItemPlaceholder extends StatelessWidget {
|
class InboxItemPlaceholder extends StatelessWidget {
|
||||||
const InboxItemPlaceholder({super.key});
|
const InboxItemPlaceholder({super.key});
|
||||||
@@ -150,11 +152,10 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
pushDocumentDetailsRoute(
|
DocumentDetailsRoute(
|
||||||
context,
|
$extra: widget.document,
|
||||||
document: widget.document,
|
|
||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
);
|
).push(context);
|
||||||
},
|
},
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 200,
|
height: 200,
|
||||||
@@ -238,8 +239,9 @@ class _InboxItemState extends State<InboxItem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildActions(BuildContext context) {
|
Widget _buildActions(BuildContext context) {
|
||||||
final canEdit = LocalUserAccount.current.paperlessUser.canEditDocuments;
|
final currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
||||||
final canDelete = LocalUserAccount.current.paperlessUser.canDeleteDocuments;
|
final canEdit = currentUser.canEditDocuments;
|
||||||
|
final canDelete = currentUser.canDeleteDocuments;
|
||||||
final chipShape = RoundedRectangleBorder(
|
final chipShape = RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(32),
|
borderRadius: BorderRadius.circular(32),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ class _FullscreenTagsFormState extends State<FullscreenTagsForm> {
|
|||||||
final createdTag = await Navigator.of(context).push<Tag?>(
|
final createdTag = await Navigator.of(context).push<Tag?>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => AddTagPage(
|
builder: (context) => AddTagPage(
|
||||||
initialValue: _textEditingController.text,
|
initialName: _textEditingController.text,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
@@ -73,7 +74,7 @@ class TagsFormField extends StatelessWidget {
|
|||||||
initialValue: field.value,
|
initialValue: field.value,
|
||||||
allowOnlySelection: allowOnlySelection,
|
allowOnlySelection: allowOnlySelection,
|
||||||
allowCreation: allowCreation &&
|
allowCreation: allowCreation &&
|
||||||
LocalUserAccount.current.paperlessUser.canCreateTags,
|
context.watch<LocalUserAccount>().paperlessUser.canCreateTags,
|
||||||
allowExclude: allowExclude,
|
allowExclude: allowExclude,
|
||||||
),
|
),
|
||||||
onClosed: (data) {
|
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/global_settings.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.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/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/core/widgets/material/colored_tab_bar.dart';
|
||||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.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/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/cubit/label_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart';
|
import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.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 {
|
class LabelsPage extends StatefulWidget {
|
||||||
const LabelsPage({Key? key}) : super(key: key);
|
const LabelsPage({Key? key}) : super(key: key);
|
||||||
@@ -52,7 +42,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final user = LocalUserAccount.current.paperlessUser;
|
final user = context.read<LocalUserAccount>().paperlessUser;
|
||||||
_tabController = TabController(
|
_tabController = TabController(
|
||||||
length: _calculateTabCount(user), vsync: this)
|
length: _calculateTabCount(user), vsync: this)
|
||||||
..addListener(() => setState(() => _currentIndex = _tabController.index));
|
..addListener(() => setState(() => _currentIndex = _tabController.index));
|
||||||
@@ -67,7 +57,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
final currentUserId =
|
final currentUserId =
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
.getValue()!
|
.getValue()!
|
||||||
.currentLoggedInUser;
|
.loggedInUserId;
|
||||||
final user = box.get(currentUserId)!.paperlessUser;
|
final user = box.get(currentUserId)!.paperlessUser;
|
||||||
|
|
||||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||||
@@ -77,10 +67,14 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
drawer: const AppDrawer(),
|
drawer: const AppDrawer(),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: [
|
onPressed: [
|
||||||
if (user.canViewCorrespondents) _openAddCorrespondentPage,
|
if (user.canViewCorrespondents)
|
||||||
if (user.canViewDocumentTypes) _openAddDocumentTypePage,
|
() => CreateLabelRoute<Correspondent>().push(context),
|
||||||
if (user.canViewTags) _openAddTagPage,
|
if (user.canViewDocumentTypes)
|
||||||
if (user.canViewStoragePaths) _openAddStoragePathPage,
|
() => CreateLabelRoute<DocumentType>().push(context),
|
||||||
|
if (user.canViewTags)
|
||||||
|
() => CreateLabelRoute<Tag>().push(context),
|
||||||
|
if (user.canViewStoragePaths)
|
||||||
|
() => CreateLabelRoute<StoragePath>().push(context),
|
||||||
][_currentIndex],
|
][_currentIndex],
|
||||||
child: const Icon(Icons.add),
|
child: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
@@ -213,144 +207,13 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: [
|
children: [
|
||||||
if (user.canViewCorrespondents)
|
if (user.canViewCorrespondents)
|
||||||
Builder(
|
_buildCorrespondentsView(state, user),
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (user.canViewDocumentTypes)
|
if (user.canViewDocumentTypes)
|
||||||
Builder(
|
_buildDocumentTypesView(state, user),
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (user.canViewTags)
|
if (user.canViewTags)
|
||||||
Builder(
|
_buildTagsView(state, user),
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
if (user.canViewStoragePaths)
|
if (user.canViewStoragePaths)
|
||||||
Builder(
|
_buildStoragePathView(state, user),
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -365,73 +228,121 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openEditCorrespondentPage(Correspondent correspondent) {
|
Widget _buildCorrespondentsView(LabelState state, UserModel user) {
|
||||||
Navigator.push(
|
return Builder(
|
||||||
context,
|
builder: (context) {
|
||||||
_buildLabelPageRoute(EditCorrespondentPage(correspondent: correspondent)),
|
return CustomScrollView(
|
||||||
);
|
slivers: [
|
||||||
}
|
SliverOverlapInjector(handle: searchBarHandle),
|
||||||
|
SliverOverlapInjector(handle: tabBarHandle),
|
||||||
void _openEditDocumentTypePage(DocumentType docType) {
|
LabelTabView<Correspondent>(
|
||||||
Navigator.push(
|
labels: state.correspondents,
|
||||||
context,
|
filterBuilder: (label) => DocumentFilter(
|
||||||
_buildLabelPageRoute(EditDocumentTypePage(documentType: docType)),
|
correspondent: IdQueryParameter.fromId(label.id!),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _openEditTagPage(Tag tag) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
_buildLabelPageRoute(EditTagPage(tag: tag)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.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) {
|
Widget _buildReferencedDocumentsWidget(BuildContext context) {
|
||||||
final canOpen = (label.documentCount ?? 0) > 0 &&
|
final canOpen = (label.documentCount ?? 0) > 0 &&
|
||||||
LocalUserAccount.current.paperlessUser.canViewDocuments;
|
context.watch<LocalUserAccount>().paperlessUser.canViewDocuments;
|
||||||
return TextButton.icon(
|
return TextButton.icon(
|
||||||
label: const Icon(Icons.link),
|
label: const Icon(Icons.link),
|
||||||
icon: Text(formatMaxCount(label.documentCount)),
|
icon: Text(formatMaxCount(label.documentCount)),
|
||||||
|
|||||||
@@ -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/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.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/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/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||||
|
|
||||||
class LinkedDocumentsPage extends StatefulWidget {
|
class LinkedDocumentsPage extends StatefulWidget {
|
||||||
const LinkedDocumentsPage({super.key});
|
const LinkedDocumentsPage({super.key});
|
||||||
@@ -51,11 +53,10 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage>
|
|||||||
isLoading: state.isLoading,
|
isLoading: state.isLoading,
|
||||||
hasLoaded: state.hasLoaded,
|
hasLoaded: state.hasLoaded,
|
||||||
onTap: (document) {
|
onTap: (document) {
|
||||||
pushDocumentDetailsRoute(
|
DocumentDetailsRoute(
|
||||||
context,
|
$extra: document,
|
||||||
document: document,
|
|
||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
);
|
).push(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -55,12 +55,11 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
// Mark logged in user as currently active user.
|
// Mark logged in user as currently active user.
|
||||||
final globalSettings =
|
final globalSettings =
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
globalSettings.currentLoggedInUser = localUserId;
|
globalSettings.loggedInUserId = localUserId;
|
||||||
await globalSettings.save();
|
await globalSettings.save();
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
AuthenticationState.authenticated(
|
AuthenticationState.authenticated(
|
||||||
apiVersion: apiVersion,
|
|
||||||
localUserId: localUserId,
|
localUserId: localUserId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -75,7 +74,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
emit(const AuthenticationState.switchingAccounts());
|
emit(const AuthenticationState.switchingAccounts());
|
||||||
final globalSettings =
|
final globalSettings =
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
if (globalSettings.currentLoggedInUser == localUserId) {
|
if (globalSettings.loggedInUserId == localUserId) {
|
||||||
|
emit(AuthenticationState.authenticated(localUserId: localUserId));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final userAccountBox =
|
final userAccountBox =
|
||||||
@@ -112,7 +112,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
baseUrl: account.serverUrl,
|
baseUrl: account.serverUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
globalSettings.currentLoggedInUser = localUserId;
|
globalSettings.loggedInUserId = localUserId;
|
||||||
await globalSettings.save();
|
await globalSettings.save();
|
||||||
|
|
||||||
final apiVersion = await _getApiVersion(_sessionManager.client);
|
final apiVersion = await _getApiVersion(_sessionManager.client);
|
||||||
@@ -126,7 +126,6 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
|
|
||||||
emit(AuthenticationState.authenticated(
|
emit(AuthenticationState.authenticated(
|
||||||
localUserId: localUserId,
|
localUserId: localUserId,
|
||||||
apiVersion: apiVersion,
|
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -175,13 +174,14 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
);
|
);
|
||||||
final globalSettings =
|
final globalSettings =
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
final localUserId = globalSettings.currentLoggedInUser;
|
final localUserId = globalSettings.loggedInUserId;
|
||||||
if (localUserId == null) {
|
if (localUserId == null) {
|
||||||
_debugPrintMessage(
|
_debugPrintMessage(
|
||||||
"restoreSessionState",
|
"restoreSessionState",
|
||||||
"There is nothing to restore.",
|
"There is nothing to restore.",
|
||||||
);
|
);
|
||||||
// If there is nothing to restore, we can quit here.
|
// If there is nothing to restore, we can quit here.
|
||||||
|
emit(const AuthenticationState.unauthenticated());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final localUserAccountBox =
|
final localUserAccountBox =
|
||||||
@@ -223,7 +223,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
final authentication =
|
final authentication =
|
||||||
await withEncryptedBox<UserCredentials, UserCredentials>(
|
await withEncryptedBox<UserCredentials, UserCredentials>(
|
||||||
HiveBoxes.localUserCredentials, (box) {
|
HiveBoxes.localUserCredentials, (box) {
|
||||||
return box.get(globalSettings.currentLoggedInUser!);
|
return box.get(globalSettings.loggedInUserId!);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (authentication == null) {
|
if (authentication == null) {
|
||||||
@@ -261,7 +261,6 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
);
|
);
|
||||||
emit(
|
emit(
|
||||||
AuthenticationState.authenticated(
|
AuthenticationState.authenticated(
|
||||||
apiVersion: apiVersion,
|
|
||||||
localUserId: localUserId,
|
localUserId: localUserId,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -279,7 +278,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
await _resetExternalState();
|
await _resetExternalState();
|
||||||
final globalSettings =
|
final globalSettings =
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
globalSettings.currentLoggedInUser = null;
|
globalSettings.loggedInUserId = null;
|
||||||
await globalSettings.save();
|
await globalSettings.save();
|
||||||
|
|
||||||
emit(const AuthenticationState.unauthenticated());
|
emit(const AuthenticationState.unauthenticated());
|
||||||
@@ -389,6 +388,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
settings: LocalUserSettings(),
|
settings: LocalUserSettings(),
|
||||||
serverUrl: serverUrl,
|
serverUrl: serverUrl,
|
||||||
paperlessUser: serverUser,
|
paperlessUser: serverUser,
|
||||||
|
apiVersion: apiVersion,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_debugPrintMessage(
|
_debugPrintMessage(
|
||||||
|
|||||||
@@ -2,12 +2,18 @@ part of 'authentication_cubit.dart';
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class AuthenticationState with _$AuthenticationState {
|
class AuthenticationState with _$AuthenticationState {
|
||||||
|
const AuthenticationState._();
|
||||||
|
|
||||||
const factory AuthenticationState.unauthenticated() = _Unauthenticated;
|
const factory AuthenticationState.unauthenticated() = _Unauthenticated;
|
||||||
const factory AuthenticationState.requriresLocalAuthentication() =
|
const factory AuthenticationState.requriresLocalAuthentication() =
|
||||||
_RequiresLocalAuthentication;
|
_RequiresLocalAuthentication;
|
||||||
const factory AuthenticationState.authenticated({
|
const factory AuthenticationState.authenticated({
|
||||||
required String localUserId,
|
required String localUserId,
|
||||||
required int apiVersion,
|
|
||||||
}) = _Authenticated;
|
}) = _Authenticated;
|
||||||
const factory AuthenticationState.switchingAccounts() = _SwitchingAccounts;
|
const factory AuthenticationState.switchingAccounts() = _SwitchingAccounts;
|
||||||
|
|
||||||
|
bool get isAuthenticated => maybeWhen(
|
||||||
|
authenticated: (_) => true,
|
||||||
|
orElse: () => false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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:hive_flutter/adapters.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/config/hive/hive_config.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/global_settings.dart';
|
||||||
import 'package:paperless_mobile/core/exception/server_message_exception.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/cubit/authentication_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/login/model/client_certificate.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/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/add_account_page.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/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.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';
|
class LoginPage extends StatelessWidget {
|
||||||
import 'widgets/never_scrollable_scroll_behavior.dart';
|
const LoginPage({super.key});
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
@override
|
||||||
final FutureOr<void> Function(
|
Widget build(BuildContext context) {
|
||||||
|
return AddAccountPage(
|
||||||
|
titleString: S.of(context)!.connectToPaperless,
|
||||||
|
submitText: S.of(context)!.signIn,
|
||||||
|
onSubmit: _onLogin,
|
||||||
|
showLocalAccounts: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onLogin(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String username,
|
String username,
|
||||||
String password,
|
String password,
|
||||||
String serverUrl,
|
String serverUrl,
|
||||||
ClientCertificate? clientCertificate,
|
ClientCertificate? clientCertificate,
|
||||||
) onSubmit;
|
) async {
|
||||||
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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 {
|
try {
|
||||||
await widget.onSubmit(
|
await context.read<AuthenticationCubit>().login(
|
||||||
context,
|
credentials: LoginFormCredentials(
|
||||||
credentials.username!,
|
username: username,
|
||||||
credentials.password!,
|
password: password,
|
||||||
form[ServerAddressFormField.fkServerAddress],
|
),
|
||||||
clientCert,
|
serverUrl: serverUrl,
|
||||||
|
clientCertificate: clientCertificate,
|
||||||
);
|
);
|
||||||
} on PaperlessApiException catch (error) {
|
// Show onboarding after first login!
|
||||||
showErrorMessage(context, error);
|
final globalSettings =
|
||||||
} on ServerMessageException catch (error) {
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
showLocalizedError(context, error.message);
|
if (globalSettings.showOnboarding) {
|
||||||
} catch (error) {
|
Navigator.push(
|
||||||
showGenericError(context, error);
|
context,
|
||||||
}
|
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/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.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/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/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/features/saved_view_details/cubit/saved_view_details_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||||
|
|
||||||
class SavedViewDetailsPage extends StatefulWidget {
|
class SavedViewDetailsPage extends StatefulWidget {
|
||||||
final Future<void> Function(SavedView savedView) onDelete;
|
final Future<void> Function(SavedView savedView) onDelete;
|
||||||
@@ -28,7 +30,7 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cubit = context.read<SavedViewDetailsCubit>();
|
final cubit = context.watch<SavedViewDetailsCubit>();
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(cubit.savedView.name),
|
title: Text(cubit.savedView.name),
|
||||||
@@ -76,11 +78,10 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
|
|||||||
isLoading: state.isLoading,
|
isLoading: state.isLoading,
|
||||||
hasLoaded: state.hasLoaded,
|
hasLoaded: state.hasLoaded,
|
||||||
onTap: (document) {
|
onTap: (document) {
|
||||||
pushDocumentDetailsRoute(
|
DocumentDetailsRoute(
|
||||||
context,
|
$extra: document,
|
||||||
document: document,
|
|
||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
);
|
).push(context);
|
||||||
},
|
},
|
||||||
viewType: state.viewType,
|
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/home/view/model/api_version.dart';
|
||||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.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/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/dialogs/switch_account_dialog.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.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';
|
import 'package:paperless_mobile/features/users/view/widgets/user_account_list_tile.dart';
|
||||||
@@ -22,7 +22,7 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
builder: (context, globalSettings) {
|
builder: (context, globalSettings) {
|
||||||
// This is one of the few places where the currentLoggedInUser can be null
|
// This is one of the few places where the currentLoggedInUser can be null
|
||||||
// (exactly after loggin out as the current user to be precise).
|
// (exactly after loggin out as the current user to be precise).
|
||||||
if (globalSettings.currentLoggedInUser == null) {
|
if (globalSettings.loggedInUserId == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return ValueListenableBuilder(
|
return ValueListenableBuilder(
|
||||||
@@ -32,8 +32,7 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
builder: (context, box, _) {
|
builder: (context, box, _) {
|
||||||
final userIds = box.keys.toList().cast<String>();
|
final userIds = box.keys.toList().cast<String>();
|
||||||
final otherAccounts = userIds
|
final otherAccounts = userIds
|
||||||
.whereNot(
|
.whereNot((element) => element == globalSettings.loggedInUserId)
|
||||||
(element) => element == globalSettings.currentLoggedInUser)
|
|
||||||
.toList();
|
.toList();
|
||||||
return SimpleDialog(
|
return SimpleDialog(
|
||||||
insetPadding: const EdgeInsets.all(24),
|
insetPadding: const EdgeInsets.all(24),
|
||||||
@@ -54,7 +53,7 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Card(
|
Card(
|
||||||
child: UserAccountListTile(
|
child: UserAccountListTile(
|
||||||
account: box.get(globalSettings.currentLoggedInUser!)!,
|
account: box.get(globalSettings.loggedInUserId!)!,
|
||||||
trailing: PopupMenuButton(
|
trailing: PopupMenuButton(
|
||||||
icon: const Icon(Icons.more_vert),
|
icon: const Icon(Icons.more_vert),
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
@@ -71,8 +70,7 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
onSelected: (value) async {
|
onSelected: (value) async {
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
final currentUser =
|
final currentUser = globalSettings.loggedInUserId!;
|
||||||
globalSettings.currentLoggedInUser!;
|
|
||||||
await context.read<AuthenticationCubit>().logout();
|
await context.read<AuthenticationCubit>().logout();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await context
|
await context
|
||||||
@@ -117,7 +115,7 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
// Switch
|
// Switch
|
||||||
_onSwitchAccount(
|
_onSwitchAccount(
|
||||||
context,
|
context,
|
||||||
globalSettings.currentLoggedInUser!,
|
globalSettings.loggedInUserId!,
|
||||||
otherAccounts[index],
|
otherAccounts[index],
|
||||||
);
|
);
|
||||||
} else if (value == 1) {
|
} else if (value == 1) {
|
||||||
@@ -135,10 +133,10 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
title: Text(S.of(context)!.addAccount),
|
title: Text(S.of(context)!.addAccount),
|
||||||
leading: const Icon(Icons.person_add),
|
leading: const Icon(Icons.person_add),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_onAddAccount(context, globalSettings.currentLoggedInUser!);
|
_onAddAccount(context, globalSettings.loggedInUserId!);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (context.watch<ApiVersion>().hasMultiUserSupport)
|
if (context.watch<LocalUserAccount>().hasMultiUserSupport)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.admin_panel_settings),
|
leading: const Icon(Icons.admin_panel_settings),
|
||||||
title: Text(S.of(context)!.managePermissions),
|
title: Text(S.of(context)!.managePermissions),
|
||||||
@@ -155,7 +153,7 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
final userId = await Navigator.push(
|
final userId = await Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => LoginPage(
|
builder: (context) => AddAccountPage(
|
||||||
titleString: S.of(context)!.addAccount,
|
titleString: S.of(context)!.addAccount,
|
||||||
onSubmit: (context, username, password, serverUrl,
|
onSubmit: (context, username, password, serverUrl,
|
||||||
clientCertificate) async {
|
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, _) {
|
builder: (context, accountBox, _) {
|
||||||
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
.getValue()!
|
.getValue()!
|
||||||
.currentLoggedInUser;
|
.loggedInUserId;
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
final account = accountBox.get(currentUser);
|
final account = accountBox.get(currentUser);
|
||||||
return builder(context, account);
|
return builder(context, account);
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.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/core/widgets/offline_widget.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.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/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/features/similar_documents/cubit/similar_documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||||
|
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||||
|
|
||||||
class SimilarDocumentsView extends StatefulWidget {
|
class SimilarDocumentsView extends StatefulWidget {
|
||||||
final ScrollController pagingScrollController;
|
final ScrollController pagingScrollController;
|
||||||
@@ -64,11 +64,10 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
|||||||
hasLoaded: state.hasLoaded,
|
hasLoaded: state.hasLoaded,
|
||||||
enableHeroAnimation: false,
|
enableHeroAnimation: false,
|
||||||
onTap: (document) {
|
onTap: (document) {
|
||||||
pushDocumentDetailsRoute(
|
DocumentDetailsRoute(
|
||||||
context,
|
$extra: document,
|
||||||
document: document,
|
|
||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
);
|
).push(context);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
+246
-139
@@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:flutter_native_splash/flutter_native_splash.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:hive_flutter/adapters.dart';
|
||||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||||
import 'package:intl/date_symbol_data_local.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/notifier/document_changed_notifier.dart';
|
||||||
import 'package:paperless_mobile/core/security/session_manager.dart';
|
import 'package:paperless_mobile/core/security/session_manager.dart';
|
||||||
import 'package:paperless_mobile/core/service/connectivity_status_service.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/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/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/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/features/settings/view/widgets/global_settings_builder.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.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:paperless_mobile/theme.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -138,7 +143,9 @@ void main() async {
|
|||||||
});
|
});
|
||||||
|
|
||||||
final apiFactory = PaperlessApiFactoryImpl(sessionManager);
|
final apiFactory = PaperlessApiFactoryImpl(sessionManager);
|
||||||
|
final authenticationCubit =
|
||||||
|
AuthenticationCubit(localAuthService, apiFactory, sessionManager);
|
||||||
|
await authenticationCubit.restoreSessionState();
|
||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
@@ -154,13 +161,10 @@ void main() async {
|
|||||||
child: MultiBlocProvider(
|
child: MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider<ConnectivityCubit>.value(value: connectivityCubit),
|
BlocProvider<ConnectivityCubit>.value(value: connectivityCubit),
|
||||||
BlocProvider(
|
BlocProvider.value(value: authenticationCubit),
|
||||||
create: (context) => AuthenticationCubit(
|
|
||||||
localAuthService, apiFactory, sessionManager),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
child: PaperlessMobileEntrypoint(
|
child: GoRouterShell(
|
||||||
paperlessProviderFactory: apiFactory,
|
apiFactory: apiFactory,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -182,70 +186,69 @@ void main() async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class PaperlessMobileEntrypoint extends StatefulWidget {
|
// class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||||
final PaperlessApiFactory paperlessProviderFactory;
|
// final PaperlessApiFactory paperlessProviderFactory;
|
||||||
const PaperlessMobileEntrypoint({
|
// const PaperlessMobileEntrypoint({
|
||||||
Key? key,
|
// Key? key,
|
||||||
required this.paperlessProviderFactory,
|
// required this.paperlessProviderFactory,
|
||||||
}) : super(key: key);
|
// }) : 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
|
@override
|
||||||
State<PaperlessMobileEntrypoint> createState() =>
|
State<GoRouterShell> createState() => _GoRouterShellState();
|
||||||
_PaperlessMobileEntrypointState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
class _GoRouterShellState extends State<GoRouterShell> {
|
||||||
@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> {
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
@@ -257,7 +260,6 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
// Activate the highest supported refresh rate on the device
|
// Activate the highest supported refresh rate on the device
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
_setOptimalDisplayMode();
|
_setOptimalDisplayMode();
|
||||||
@@ -281,75 +283,180 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<AuthenticationCubit, AuthenticationState>(
|
return GlobalSettingsBuilder(
|
||||||
builder: (context, authentication) {
|
builder: (context, settings) {
|
||||||
return authentication.when(
|
return DynamicColorBuilder(
|
||||||
unauthenticated: () => LoginPage(
|
builder: (lightDynamic, darkDynamic) {
|
||||||
titleString: S.of(context)!.connectToPaperless,
|
return BlocListener<AuthenticationCubit, AuthenticationState>(
|
||||||
submitText: S.of(context)!.signIn,
|
listener: (context, state) {
|
||||||
onSubmit: _onLogin,
|
state.when(
|
||||||
showLocalAccounts: true,
|
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,
|
||||||
),
|
),
|
||||||
requriresLocalAuthentication: () => const VerifyIdentityPage(),
|
darkTheme: buildTheme(
|
||||||
authenticated: (localUserId, apiVersion) => HomeRoute(
|
brightness: Brightness.dark,
|
||||||
key: ValueKey(localUserId),
|
dynamicScheme: darkDynamic,
|
||||||
paperlessApiVersion: apiVersion,
|
preferredColorScheme: settings.preferredColorSchemeOption,
|
||||||
paperlessProviderFactory: widget.paperlessProviderFactory,
|
|
||||||
localUserId: localUserId,
|
|
||||||
),
|
),
|
||||||
switchingAccounts: () => const SwitchingAccountsPage(),
|
themeMode: settings.preferredThemeMode,
|
||||||
|
supportedLocales: S.supportedLocales,
|
||||||
|
locale: Locale.fromSubtags(
|
||||||
|
languageCode: settings.preferredLocaleSubtag,
|
||||||
|
),
|
||||||
|
localizationsDelegates: S.localizationsDelegates,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onLogin(
|
// class AuthenticationWrapper extends StatefulWidget {
|
||||||
BuildContext context,
|
// final PaperlessApiFactory paperlessProviderFactory;
|
||||||
String username,
|
|
||||||
String password,
|
// const AuthenticationWrapper({
|
||||||
String serverUrl,
|
// Key? key,
|
||||||
ClientCertificate? clientCertificate,
|
// required this.paperlessProviderFactory,
|
||||||
) async {
|
// }) : super(key: key);
|
||||||
try {
|
|
||||||
await context.read<AuthenticationCubit>().login(
|
// @override
|
||||||
credentials: LoginFormCredentials(
|
// State<AuthenticationWrapper> createState() => _AuthenticationWrapperState();
|
||||||
username: username,
|
// }
|
||||||
password: password,
|
|
||||||
),
|
// class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||||
serverUrl: serverUrl,
|
// @override
|
||||||
clientCertificate: clientCertificate,
|
// void didChangeDependencies() {
|
||||||
);
|
// super.didChangeDependencies();
|
||||||
// Show onboarding after first login!
|
// context.read<AuthenticationCubit>().restoreSessionState().then((value) {
|
||||||
final globalSettings =
|
// FlutterNativeSplash.remove();
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
// });
|
||||||
if (globalSettings.showOnboarding) {
|
// }
|
||||||
Navigator.push(
|
|
||||||
context,
|
// @override
|
||||||
MaterialPageRoute(
|
// void initState() {
|
||||||
builder: (context) => const ApplicationIntroSlideshow(),
|
// super.initState();
|
||||||
fullscreenDialog: true,
|
|
||||||
),
|
// // Activate the highest supported refresh rate on the device
|
||||||
).then((value) {
|
// if (Platform.isAndroid) {
|
||||||
globalSettings.showOnboarding = false;
|
// _setOptimalDisplayMode();
|
||||||
globalSettings.save();
|
// }
|
||||||
});
|
// initializeDateFormatting();
|
||||||
}
|
// }
|
||||||
} on PaperlessApiException catch (error, stackTrace) {
|
|
||||||
showErrorMessage(context, error, stackTrace);
|
// Future<void> _setOptimalDisplayMode() async {
|
||||||
} on PaperlessFormValidationException catch (exception, stackTrace) {
|
// final List<DisplayMode> supported = await FlutterDisplayMode.supported;
|
||||||
if (exception.hasUnspecificErrorMessage()) {
|
// final DisplayMode active = await FlutterDisplayMode.active;
|
||||||
showLocalizedError(context, exception.unspecificErrorMessage()!);
|
|
||||||
} else {
|
// final List<DisplayMode> sameResolution = supported
|
||||||
showGenericError(
|
// .where((m) => m.width == active.width && m.height == active.height)
|
||||||
context,
|
// .toList()
|
||||||
exception.validationMessages.values.first,
|
// ..sort((a, b) => b.refreshRate.compareTo(a.refreshRate));
|
||||||
stackTrace,
|
|
||||||
); //TODO: Check if we can show error message directly on field here.
|
// final DisplayMode mostOptimalMode =
|
||||||
}
|
// sameResolution.isNotEmpty ? sameResolution.first : active;
|
||||||
} catch (unknownError, stackTrace) {
|
// debugPrint('Setting refresh rate to ${mostOptimalMode.refreshRate}');
|
||||||
showGenericError(context, unknownError.toString(), stackTrace);
|
|
||||||
}
|
// 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -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>();
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'paperless_server_exception.g.dart';
|
part 'paperless_server_message_exception.g.dart';
|
||||||
|
|
||||||
@JsonSerializable(createToJson: false)
|
@JsonSerializable(createToJson: false)
|
||||||
class PaperlessServerMessageException implements Exception {
|
class PaperlessServerMessageException implements Exception {
|
||||||
@@ -13,5 +13,5 @@ class PaperlessServerMessageException implements Exception {
|
|||||||
}
|
}
|
||||||
|
|
||||||
factory PaperlessServerMessageException.fromJson(Map<String, dynamic> json) =>
|
factory PaperlessServerMessageException.fromJson(Map<String, dynamic> json) =>
|
||||||
_$PaperlessServerExceptionFromJson(json);
|
_$PaperlessServerMessageExceptionFromJson(json);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/label_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/matching_algorithm.dart';
|
|
||||||
|
|
||||||
part 'correspondent_model.g.dart';
|
|
||||||
|
|
||||||
@LocalDateTimeJsonConverter()
|
|
||||||
@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
|
|
||||||
class Correspondent extends Label {
|
|
||||||
final DateTime? lastCorrespondence;
|
|
||||||
|
|
||||||
const Correspondent({
|
|
||||||
this.lastCorrespondence,
|
|
||||||
required super.name,
|
|
||||||
super.id,
|
|
||||||
super.slug,
|
|
||||||
super.match,
|
|
||||||
super.matchingAlgorithm,
|
|
||||||
super.isInsensitive,
|
|
||||||
super.documentCount,
|
|
||||||
super.owner,
|
|
||||||
super.userCanChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory Correspondent.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$CorrespondentFromJson(json);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() => _$CorrespondentToJson(this);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Correspondent copyWith({
|
|
||||||
int? id,
|
|
||||||
String? name,
|
|
||||||
String? slug,
|
|
||||||
String? match,
|
|
||||||
MatchingAlgorithm? matchingAlgorithm,
|
|
||||||
bool? isInsensitive,
|
|
||||||
int? documentCount,
|
|
||||||
DateTime? lastCorrespondence,
|
|
||||||
}) {
|
|
||||||
return Correspondent(
|
|
||||||
id: id ?? this.id,
|
|
||||||
name: name ?? this.name,
|
|
||||||
documentCount: documentCount ?? documentCount,
|
|
||||||
isInsensitive: isInsensitive ?? isInsensitive,
|
|
||||||
match: match ?? this.match,
|
|
||||||
matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm,
|
|
||||||
slug: slug ?? this.slug,
|
|
||||||
lastCorrespondence: lastCorrespondence ?? this.lastCorrespondence,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get queryEndpoint => 'correspondents';
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
slug,
|
|
||||||
isInsensitive,
|
|
||||||
documentCount,
|
|
||||||
lastCorrespondence,
|
|
||||||
matchingAlgorithm,
|
|
||||||
match,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/label_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/matching_algorithm.dart';
|
|
||||||
part 'document_type_model.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
|
|
||||||
class DocumentType extends Label {
|
|
||||||
const DocumentType({
|
|
||||||
super.id,
|
|
||||||
required super.name,
|
|
||||||
super.slug,
|
|
||||||
super.match,
|
|
||||||
super.matchingAlgorithm,
|
|
||||||
super.isInsensitive,
|
|
||||||
super.documentCount,
|
|
||||||
super.owner,
|
|
||||||
super.userCanChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory DocumentType.fromJson(Map<String, dynamic> json) => _$DocumentTypeFromJson(json);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get queryEndpoint => 'document_types';
|
|
||||||
|
|
||||||
@override
|
|
||||||
DocumentType copyWith({
|
|
||||||
int? id,
|
|
||||||
String? name,
|
|
||||||
String? match,
|
|
||||||
MatchingAlgorithm? matchingAlgorithm,
|
|
||||||
bool? isInsensitive,
|
|
||||||
int? documentCount,
|
|
||||||
String? slug,
|
|
||||||
}) {
|
|
||||||
return DocumentType(
|
|
||||||
id: id ?? this.id,
|
|
||||||
name: name ?? this.name,
|
|
||||||
match: match ?? this.match,
|
|
||||||
matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm,
|
|
||||||
isInsensitive: isInsensitive ?? this.isInsensitive,
|
|
||||||
documentCount: documentCount ?? this.documentCount,
|
|
||||||
slug: slug ?? this.slug,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() => _$DocumentTypeToJson(this);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
slug,
|
|
||||||
isInsensitive,
|
|
||||||
documentCount,
|
|
||||||
matchingAlgorithm,
|
|
||||||
match,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,14 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:paperless_api/src/converters/hex_color_json_converter.dart';
|
||||||
|
import 'package:paperless_api/src/converters/local_date_time_json_converter.dart';
|
||||||
import 'package:paperless_api/src/models/labels/matching_algorithm.dart';
|
import 'package:paperless_api/src/models/labels/matching_algorithm.dart';
|
||||||
|
|
||||||
abstract class Label extends Equatable implements Comparable {
|
part 'label_model.g.dart';
|
||||||
|
|
||||||
|
sealed class Label extends Equatable implements Comparable {
|
||||||
static const idKey = "id";
|
static const idKey = "id";
|
||||||
static const nameKey = "name";
|
static const nameKey = "name";
|
||||||
static const slugKey = "slug";
|
static const slugKey = "slug";
|
||||||
@@ -56,3 +63,278 @@ abstract class Label extends Equatable implements Comparable {
|
|||||||
|
|
||||||
Map<String, dynamic> toJson();
|
Map<String, dynamic> toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@LocalDateTimeJsonConverter()
|
||||||
|
@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
|
||||||
|
class Correspondent extends Label {
|
||||||
|
final DateTime? lastCorrespondence;
|
||||||
|
|
||||||
|
const Correspondent({
|
||||||
|
this.lastCorrespondence,
|
||||||
|
required super.name,
|
||||||
|
super.id,
|
||||||
|
super.slug,
|
||||||
|
super.match,
|
||||||
|
super.matchingAlgorithm,
|
||||||
|
super.isInsensitive,
|
||||||
|
super.documentCount,
|
||||||
|
super.owner,
|
||||||
|
super.userCanChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Correspondent.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CorrespondentFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() => _$CorrespondentToJson(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Correspondent copyWith({
|
||||||
|
int? id,
|
||||||
|
String? name,
|
||||||
|
String? slug,
|
||||||
|
String? match,
|
||||||
|
MatchingAlgorithm? matchingAlgorithm,
|
||||||
|
bool? isInsensitive,
|
||||||
|
int? documentCount,
|
||||||
|
DateTime? lastCorrespondence,
|
||||||
|
}) {
|
||||||
|
return Correspondent(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
documentCount: documentCount ?? documentCount,
|
||||||
|
isInsensitive: isInsensitive ?? isInsensitive,
|
||||||
|
match: match ?? this.match,
|
||||||
|
matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm,
|
||||||
|
slug: slug ?? this.slug,
|
||||||
|
lastCorrespondence: lastCorrespondence ?? this.lastCorrespondence,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get queryEndpoint => 'correspondents';
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
isInsensitive,
|
||||||
|
documentCount,
|
||||||
|
lastCorrespondence,
|
||||||
|
matchingAlgorithm,
|
||||||
|
match,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
|
||||||
|
class DocumentType extends Label {
|
||||||
|
const DocumentType({
|
||||||
|
super.id,
|
||||||
|
required super.name,
|
||||||
|
super.slug,
|
||||||
|
super.match,
|
||||||
|
super.matchingAlgorithm,
|
||||||
|
super.isInsensitive,
|
||||||
|
super.documentCount,
|
||||||
|
super.owner,
|
||||||
|
super.userCanChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory DocumentType.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$DocumentTypeFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get queryEndpoint => 'document_types';
|
||||||
|
|
||||||
|
@override
|
||||||
|
DocumentType copyWith({
|
||||||
|
int? id,
|
||||||
|
String? name,
|
||||||
|
String? match,
|
||||||
|
MatchingAlgorithm? matchingAlgorithm,
|
||||||
|
bool? isInsensitive,
|
||||||
|
int? documentCount,
|
||||||
|
String? slug,
|
||||||
|
}) {
|
||||||
|
return DocumentType(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
match: match ?? this.match,
|
||||||
|
matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm,
|
||||||
|
isInsensitive: isInsensitive ?? this.isInsensitive,
|
||||||
|
documentCount: documentCount ?? this.documentCount,
|
||||||
|
slug: slug ?? this.slug,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() => _$DocumentTypeToJson(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
isInsensitive,
|
||||||
|
documentCount,
|
||||||
|
matchingAlgorithm,
|
||||||
|
match,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
|
||||||
|
class StoragePath extends Label {
|
||||||
|
static const pathKey = 'path';
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
const StoragePath({
|
||||||
|
super.id,
|
||||||
|
required super.name,
|
||||||
|
required this.path,
|
||||||
|
super.slug,
|
||||||
|
super.match,
|
||||||
|
super.matchingAlgorithm,
|
||||||
|
super.isInsensitive,
|
||||||
|
super.documentCount,
|
||||||
|
super.owner,
|
||||||
|
super.userCanChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory StoragePath.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$StoragePathFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
StoragePath copyWith({
|
||||||
|
int? id,
|
||||||
|
String? name,
|
||||||
|
String? slug,
|
||||||
|
String? match,
|
||||||
|
MatchingAlgorithm? matchingAlgorithm,
|
||||||
|
bool? isInsensitive,
|
||||||
|
int? documentCount,
|
||||||
|
String? path,
|
||||||
|
}) {
|
||||||
|
return StoragePath(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
documentCount: documentCount ?? documentCount,
|
||||||
|
isInsensitive: isInsensitive ?? isInsensitive,
|
||||||
|
path: path ?? this.path,
|
||||||
|
match: match ?? this.match,
|
||||||
|
matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm,
|
||||||
|
slug: slug ?? this.slug,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get queryEndpoint => 'storage_paths';
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
isInsensitive,
|
||||||
|
documentCount,
|
||||||
|
path,
|
||||||
|
matchingAlgorithm,
|
||||||
|
match,
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() => _$StoragePathToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HexColorJsonConverter()
|
||||||
|
@JsonSerializable(
|
||||||
|
fieldRename: FieldRename.snake,
|
||||||
|
explicitToJson: true,
|
||||||
|
)
|
||||||
|
class Tag extends Label {
|
||||||
|
static const colorKey = 'color';
|
||||||
|
static const isInboxTagKey = 'is_inbox_tag';
|
||||||
|
static const textColorKey = 'text_color';
|
||||||
|
static const legacyColourKey = 'colour';
|
||||||
|
final Color? textColor;
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
final bool isInboxTag;
|
||||||
|
|
||||||
|
const Tag({
|
||||||
|
super.id,
|
||||||
|
required super.name,
|
||||||
|
super.documentCount,
|
||||||
|
super.isInsensitive,
|
||||||
|
super.match,
|
||||||
|
super.matchingAlgorithm = MatchingAlgorithm.defaultValue,
|
||||||
|
super.slug,
|
||||||
|
this.color,
|
||||||
|
this.textColor,
|
||||||
|
this.isInboxTag = false,
|
||||||
|
super.owner,
|
||||||
|
super.userCanChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Tag copyWith({
|
||||||
|
int? id,
|
||||||
|
String? name,
|
||||||
|
String? match,
|
||||||
|
MatchingAlgorithm? matchingAlgorithm,
|
||||||
|
bool? isInsensitive,
|
||||||
|
int? documentCount,
|
||||||
|
String? slug,
|
||||||
|
Color? color,
|
||||||
|
Color? textColor,
|
||||||
|
bool? isInboxTag,
|
||||||
|
}) {
|
||||||
|
return Tag(
|
||||||
|
id: id ?? this.id,
|
||||||
|
name: name ?? this.name,
|
||||||
|
match: match ?? this.match,
|
||||||
|
matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm,
|
||||||
|
isInsensitive: isInsensitive ?? this.isInsensitive,
|
||||||
|
documentCount: documentCount ?? this.documentCount,
|
||||||
|
slug: slug ?? this.slug,
|
||||||
|
color: color ?? this.color,
|
||||||
|
textColor: textColor ?? this.textColor,
|
||||||
|
isInboxTag: isInboxTag ?? this.isInboxTag,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get queryEndpoint => 'tags';
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
isInsensitive,
|
||||||
|
documentCount,
|
||||||
|
matchingAlgorithm,
|
||||||
|
color,
|
||||||
|
textColor,
|
||||||
|
isInboxTag,
|
||||||
|
match,
|
||||||
|
];
|
||||||
|
|
||||||
|
factory Tag.fromJson(Map<String, dynamic> json) => _$TagFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() => _$TagToJson(this);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/label_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/matching_algorithm.dart';
|
|
||||||
part 'storage_path_model.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable(includeIfNull: false, fieldRename: FieldRename.snake)
|
|
||||||
class StoragePath extends Label {
|
|
||||||
static const pathKey = 'path';
|
|
||||||
final String path;
|
|
||||||
|
|
||||||
const StoragePath({
|
|
||||||
super.id,
|
|
||||||
required super.name,
|
|
||||||
required this.path,
|
|
||||||
super.slug,
|
|
||||||
super.match,
|
|
||||||
super.matchingAlgorithm,
|
|
||||||
super.isInsensitive,
|
|
||||||
super.documentCount,
|
|
||||||
super.owner,
|
|
||||||
super.userCanChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
factory StoragePath.fromJson(Map<String, dynamic> json) => _$StoragePathFromJson(json);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
StoragePath copyWith({
|
|
||||||
int? id,
|
|
||||||
String? name,
|
|
||||||
String? slug,
|
|
||||||
String? match,
|
|
||||||
MatchingAlgorithm? matchingAlgorithm,
|
|
||||||
bool? isInsensitive,
|
|
||||||
int? documentCount,
|
|
||||||
String? path,
|
|
||||||
}) {
|
|
||||||
return StoragePath(
|
|
||||||
id: id ?? this.id,
|
|
||||||
name: name ?? this.name,
|
|
||||||
documentCount: documentCount ?? documentCount,
|
|
||||||
isInsensitive: isInsensitive ?? isInsensitive,
|
|
||||||
path: path ?? this.path,
|
|
||||||
match: match ?? this.match,
|
|
||||||
matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm,
|
|
||||||
slug: slug ?? this.slug,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get queryEndpoint => 'storage_paths';
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
slug,
|
|
||||||
isInsensitive,
|
|
||||||
documentCount,
|
|
||||||
path,
|
|
||||||
matchingAlgorithm,
|
|
||||||
match,
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() => _$StoragePathToJson(this);
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
import 'package:paperless_api/src/converters/hex_color_json_converter.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/label_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/matching_algorithm.dart';
|
|
||||||
|
|
||||||
part 'tag_model.g.dart';
|
|
||||||
|
|
||||||
@HexColorJsonConverter()
|
|
||||||
@JsonSerializable(
|
|
||||||
fieldRename: FieldRename.snake,
|
|
||||||
explicitToJson: true,
|
|
||||||
)
|
|
||||||
class Tag extends Label {
|
|
||||||
static const colorKey = 'color';
|
|
||||||
static const isInboxTagKey = 'is_inbox_tag';
|
|
||||||
static const textColorKey = 'text_color';
|
|
||||||
static const legacyColourKey = 'colour';
|
|
||||||
final Color? textColor;
|
|
||||||
final Color? color;
|
|
||||||
|
|
||||||
final bool isInboxTag;
|
|
||||||
|
|
||||||
const Tag({
|
|
||||||
super.id,
|
|
||||||
required super.name,
|
|
||||||
super.documentCount,
|
|
||||||
super.isInsensitive,
|
|
||||||
super.match,
|
|
||||||
super.matchingAlgorithm = MatchingAlgorithm.defaultValue,
|
|
||||||
super.slug,
|
|
||||||
this.color,
|
|
||||||
this.textColor,
|
|
||||||
this.isInboxTag = false,
|
|
||||||
super.owner,
|
|
||||||
super.userCanChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => name;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Tag copyWith({
|
|
||||||
int? id,
|
|
||||||
String? name,
|
|
||||||
String? match,
|
|
||||||
MatchingAlgorithm? matchingAlgorithm,
|
|
||||||
bool? isInsensitive,
|
|
||||||
int? documentCount,
|
|
||||||
String? slug,
|
|
||||||
Color? color,
|
|
||||||
Color? textColor,
|
|
||||||
bool? isInboxTag,
|
|
||||||
}) {
|
|
||||||
return Tag(
|
|
||||||
id: id ?? this.id,
|
|
||||||
name: name ?? this.name,
|
|
||||||
match: match ?? this.match,
|
|
||||||
matchingAlgorithm: matchingAlgorithm ?? this.matchingAlgorithm,
|
|
||||||
isInsensitive: isInsensitive ?? this.isInsensitive,
|
|
||||||
documentCount: documentCount ?? this.documentCount,
|
|
||||||
slug: slug ?? this.slug,
|
|
||||||
color: color ?? this.color,
|
|
||||||
textColor: textColor ?? this.textColor,
|
|
||||||
isInboxTag: isInboxTag ?? this.isInboxTag,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get queryEndpoint => 'tags';
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get props => [
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
slug,
|
|
||||||
isInsensitive,
|
|
||||||
documentCount,
|
|
||||||
matchingAlgorithm,
|
|
||||||
color,
|
|
||||||
textColor,
|
|
||||||
isInboxTag,
|
|
||||||
match,
|
|
||||||
];
|
|
||||||
|
|
||||||
factory Tag.fromJson(Map<String, dynamic> json) => _$TagFromJson(json);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() => _$TagToJson(this);
|
|
||||||
}
|
|
||||||
@@ -5,12 +5,8 @@ export 'document_model.dart';
|
|||||||
export 'field_suggestions.dart';
|
export 'field_suggestions.dart';
|
||||||
export 'filter_rule_model.dart';
|
export 'filter_rule_model.dart';
|
||||||
export 'group_model.dart';
|
export 'group_model.dart';
|
||||||
export 'labels/correspondent_model.dart';
|
|
||||||
export 'labels/document_type_model.dart';
|
|
||||||
export 'labels/label_model.dart';
|
export 'labels/label_model.dart';
|
||||||
export 'labels/matching_algorithm.dart';
|
export 'labels/matching_algorithm.dart';
|
||||||
export 'labels/storage_path_model.dart';
|
|
||||||
export 'labels/tag_model.dart';
|
|
||||||
export 'paged_search_result.dart';
|
export 'paged_search_result.dart';
|
||||||
export 'paperless_api_exception.dart';
|
export 'paperless_api_exception.dart';
|
||||||
export 'paperless_server_information_model.dart';
|
export 'paperless_server_information_model.dart';
|
||||||
|
|||||||
@@ -81,4 +81,10 @@ extension UserPermissionExtension on UserModel {
|
|||||||
hasPermission(PermissionAction.add, PermissionTarget.storagePath);
|
hasPermission(PermissionAction.add, PermissionTarget.storagePath);
|
||||||
bool get canCreateSavedViews =>
|
bool get canCreateSavedViews =>
|
||||||
hasPermission(PermissionAction.add, PermissionTarget.savedView);
|
hasPermission(PermissionAction.add, PermissionTarget.savedView);
|
||||||
|
|
||||||
|
bool get canViewAnyLabel =>
|
||||||
|
canViewCorrespondents ||
|
||||||
|
canViewDocumentTypes ||
|
||||||
|
canViewTags ||
|
||||||
|
canViewStoragePaths;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import 'package:paperless_api/src/models/labels/correspondent_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/document_type_model.dart';
|
import 'package:paperless_api/src/models/models.dart';
|
||||||
import 'package:paperless_api/src/models/labels/storage_path_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/tag_model.dart';
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Provides basic CRUD operations for labels, including:
|
/// Provides basic CRUD operations for labels, including:
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
import 'package:paperless_api/src/extensions/dio_exception_extension.dart';
|
||||||
import 'package:paperless_api/src/models/labels/correspondent_model.dart';
|
import 'package:paperless_api/src/models/models.dart';
|
||||||
import 'package:paperless_api/src/models/labels/document_type_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/storage_path_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/labels/tag_model.dart';
|
|
||||||
import 'package:paperless_api/src/models/paperless_api_exception.dart';
|
import 'package:paperless_api/src/models/paperless_api_exception.dart';
|
||||||
import 'package:paperless_api/src/modules/labels_api/paperless_labels_api.dart';
|
import 'package:paperless_api/src/modules/labels_api/paperless_labels_api.dart';
|
||||||
import 'package:paperless_api/src/request_utils.dart';
|
import 'package:paperless_api/src/request_utils.dart';
|
||||||
|
|||||||
+56
-40
@@ -317,10 +317,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: dart_code_metrics
|
name: dart_code_metrics
|
||||||
sha256: "1dc1fa763b73ed52147bd91b015d81903edc3f227b77b1672fcddba43390ed18"
|
sha256: "3dede3f7abc077a4181ec7445448a289a9ce08e2981e6a4d49a3fb5099d47e1f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.7.5"
|
version: "5.7.6"
|
||||||
dart_code_metrics_presets:
|
dart_code_metrics_presets:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -381,18 +381,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dio
|
name: dio
|
||||||
sha256: a9d76e72985d7087eb7c5e7903224ae52b337131518d127c554b9405936752b8
|
sha256: "3866d67f93523161b643187af65f5ac08bc991a5bcdaf41a2d587fe4ccb49993"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.2.1+1"
|
version: "5.3.0"
|
||||||
dots_indicator:
|
dots_indicator:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dots_indicator
|
name: dots_indicator
|
||||||
sha256: "58b6a365744aa62aa1b70c4ea29e5106fbe064f5edaf7e9652e9b856edbfd9bb"
|
sha256: f1599baa429936ba87f06ae5f2adc920a367b16d08f74db58c3d0f6e93bcdb5c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "2.1.2"
|
||||||
dynamic_color:
|
dynamic_color:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -727,18 +727,18 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: freezed
|
name: freezed
|
||||||
sha256: a9520490532087cf38bf3f7de478ab6ebeb5f68bb1eb2641546d92719b224445
|
sha256: "2df89855fe181baae3b6d714dc3c4317acf4fccd495a6f36e5e00f24144c6c3b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.5"
|
version: "2.4.1"
|
||||||
freezed_annotation:
|
freezed_annotation:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: freezed_annotation
|
name: freezed_annotation
|
||||||
sha256: aeac15850ef1b38ee368d4c53ba9a847e900bb2c53a4db3f6881cbb3cb684338
|
sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.4.1"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -760,6 +760,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
go_router:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: go_router
|
||||||
|
sha256: b3cadd2cd59a4103fd5f6bc572ca75111264698784e927aa471921c3477d5475
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.0"
|
||||||
|
go_router_builder:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: go_router_builder
|
||||||
|
sha256: df2034629637d0c7c380aba5daa2f91be4733f2d632e7dff0b082d5ff3155068
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.4"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -865,10 +881,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: introduction_screen
|
name: introduction_screen
|
||||||
sha256: f39be426026785b8fea4ed93e226e7fc28ef49a4c78c3f86c958bae26dabef00
|
sha256: ef5a5479a8e06a84b9a7eff16c698b9b82f70cd1b6203b264bc3686f9bfb77e2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.9"
|
version: "3.1.11"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1143,10 +1159,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3"
|
sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.3"
|
version: "2.2.4"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1183,34 +1199,34 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: permission_handler
|
name: permission_handler
|
||||||
sha256: "415af30ba76a84faccfe1eb251fe1e4fdc790f876924c65ad7d6ed7a1404bcd6"
|
sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.4.2"
|
version: "10.4.3"
|
||||||
permission_handler_android:
|
permission_handler_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_android
|
name: permission_handler_android
|
||||||
sha256: "3b61f3da3b1c83bc3fb6a2b431e8dab01d0e5b45f6a3d9c7609770ec88b2a89e"
|
sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.3.0"
|
version: "10.3.3"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_apple
|
name: permission_handler_apple
|
||||||
sha256: "7a187b671a39919462af2b5e813148365b71a615979165a119868d667fe90c03"
|
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.3"
|
version: "9.1.4"
|
||||||
permission_handler_platform_interface:
|
permission_handler_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_platform_interface
|
name: permission_handler_platform_interface
|
||||||
sha256: "463a07cb7cc6c758a7a1c7da36ce666bb80a0b4b5e92df0fa36872e0ed456993"
|
sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.11.1"
|
version: "3.11.3"
|
||||||
permission_handler_windows:
|
permission_handler_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1247,10 +1263,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
|
sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.5"
|
||||||
pointer_interceptor:
|
pointer_interceptor:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1468,10 +1484,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sliver_tools
|
name: sliver_tools
|
||||||
sha256: ccdc502098a8bfa07b3ec582c282620031481300035584e1bb3aca296a505e8c
|
sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.10"
|
version: "0.2.12"
|
||||||
source_gen:
|
source_gen:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1516,18 +1532,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite
|
name: sqflite
|
||||||
sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9
|
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.8+4"
|
version: "2.3.0"
|
||||||
sqflite_common:
|
sqflite_common:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "8f7603f3f8f126740bc55c4ca2d1027aab4b74a1267a3e31ce51fe40e3b65b8f"
|
sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.5+1"
|
version: "2.5.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1652,10 +1668,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03"
|
sha256: "78cb6dea3e93148615109e58e42c35d1ffbf5ef66c44add673d0ab75f12ff3af"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.36"
|
version: "6.0.37"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1676,10 +1692,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e"
|
sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.5"
|
version: "3.0.6"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1772,26 +1788,26 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_android
|
name: webview_flutter_android
|
||||||
sha256: "1c93e96f3069bacdc734fad6b7e1d3a480fd516a3ae5b8858becf7f07515a2f3"
|
sha256: d936a09fbfd08cb78f7329e0bbacf6158fbdfe24ffc908b22444c07d295eb193
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.8.2"
|
version: "3.9.2"
|
||||||
webview_flutter_platform_interface:
|
webview_flutter_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_platform_interface
|
name: webview_flutter_platform_interface
|
||||||
sha256: "656e2aeaef318900fffd21468b6ddc7958c7092a642f0e7220bac328b70d4a81"
|
sha256: "564ef378cafc1a0e29f1d76ce175ef517a0a6115875dff7b43fccbef2b0aeb30"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.4.0"
|
||||||
webview_flutter_wkwebview:
|
webview_flutter_wkwebview:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_wkwebview
|
name: webview_flutter_wkwebview
|
||||||
sha256: a8d7e8b4be2a79e83b70235369971ec97d14df4cdbb40d305a8eeae67d8e6432
|
sha256: "5fa098f28b606f699e8ca52d9e4e11edbbfef65189f5f77ae92703ba5408fd25"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.6.2"
|
version: "3.7.2"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ dependencies:
|
|||||||
webview_flutter: ^4.2.1
|
webview_flutter: ^4.2.1
|
||||||
printing: ^5.11.0
|
printing: ^5.11.0
|
||||||
flutter_pdfview: ^1.3.1
|
flutter_pdfview: ^1.3.1
|
||||||
|
go_router: ^10.0.0
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
intl: ^0.18.1
|
intl: ^0.18.1
|
||||||
@@ -113,6 +114,7 @@ dev_dependencies:
|
|||||||
hive_generator: ^2.0.0
|
hive_generator: ^2.0.0
|
||||||
mock_server:
|
mock_server:
|
||||||
path: packages/mock_server
|
path: packages/mock_server
|
||||||
|
go_router_builder: ^2.2.4
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|||||||
Reference in New Issue
Block a user