mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 23:15:43 -06:00
More work on inbox, refactorings (bloc separation of concerns), fixed saved views wrong sort order
This commit is contained in:
BIN
assets/images/empty_inbox.png
Normal file
BIN
assets/images/empty_inbox.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
@@ -1,62 +0,0 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
import 'package:paperless_mobile/core/model/paperless_statistics.dart';
|
|
||||||
import 'package:paperless_mobile/core/model/paperless_statistics_state.dart';
|
|
||||||
import 'package:paperless_mobile/core/service/paperless_statistics_service.dart';
|
|
||||||
|
|
||||||
@singleton
|
|
||||||
class PaperlessStatisticsCubit extends Cubit<PaperlessStatisticsState> {
|
|
||||||
final PaperlessStatisticsService statisticsService;
|
|
||||||
|
|
||||||
PaperlessStatisticsCubit(this.statisticsService)
|
|
||||||
: super(PaperlessStatisticsState(isLoaded: false));
|
|
||||||
|
|
||||||
Future<void> updateStatistics() async {
|
|
||||||
final stats = await statisticsService.getStatistics();
|
|
||||||
emit(PaperlessStatisticsState(isLoaded: true, statistics: stats));
|
|
||||||
}
|
|
||||||
|
|
||||||
void decrementInboxCount() {
|
|
||||||
if (state.isLoaded) {
|
|
||||||
emit(
|
|
||||||
PaperlessStatisticsState(
|
|
||||||
isLoaded: true,
|
|
||||||
statistics: PaperlessStatistics(
|
|
||||||
documentsInInbox: max(0, state.statistics!.documentsInInbox - 1),
|
|
||||||
documentsTotal: state.statistics!.documentsTotal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void incrementInboxCount() {
|
|
||||||
if (state.isLoaded) {
|
|
||||||
emit(
|
|
||||||
PaperlessStatisticsState(
|
|
||||||
isLoaded: true,
|
|
||||||
statistics: PaperlessStatistics(
|
|
||||||
documentsInInbox: state.statistics!.documentsInInbox + 1,
|
|
||||||
documentsTotal: state.statistics!.documentsTotal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetInboxCount() {
|
|
||||||
if (state.isLoaded) {
|
|
||||||
emit(
|
|
||||||
PaperlessStatisticsState(
|
|
||||||
isLoaded: true,
|
|
||||||
statistics: PaperlessStatistics(
|
|
||||||
documentsInInbox: 0,
|
|
||||||
documentsTotal: state.statistics!.documentsTotal,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,16 +4,16 @@ enum AssetImages {
|
|||||||
headacheDocuments("images/documents_headache.png"),
|
headacheDocuments("images/documents_headache.png"),
|
||||||
organizeDocuments("images/organize_documents.png"),
|
organizeDocuments("images/organize_documents.png"),
|
||||||
secureDocuments("images/secure_documents.png"),
|
secureDocuments("images/secure_documents.png"),
|
||||||
success("images/success.png");
|
success("images/success.png"),
|
||||||
|
emptyInbox("images/empty_inbox.png");
|
||||||
|
|
||||||
final String relativePath;
|
final String relativePath;
|
||||||
const AssetImages(String relativePath)
|
const AssetImages(String relativePath)
|
||||||
: relativePath = "assets/$relativePath";
|
: relativePath = "assets/$relativePath";
|
||||||
|
|
||||||
Image get image => Image.asset(
|
AssetImage get image => AssetImage(relativePath);
|
||||||
relativePath,
|
|
||||||
key: ObjectKey("assetimage_$relativePath"),
|
|
||||||
);
|
|
||||||
|
|
||||||
void load(context) => precacheImage(image.image, context);
|
void load(context) => precacheImage(image, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
late Image emptyInboxImage;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class AuthenticationInterceptor implements InterceptorContract {
|
|||||||
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
|
Future<BaseRequest> interceptRequest({required BaseRequest request}) async {
|
||||||
final authState = authenticationCubit.state;
|
final authState = authenticationCubit.state;
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
log("Intercepted request to ${request.url.toString()}");
|
log("Intercepted ${request.method} request to ${request.url.toString()}");
|
||||||
}
|
}
|
||||||
if (authState.authentication == null) {
|
if (authState.authentication == null) {
|
||||||
throw const ErrorMessage(ErrorCode.notAuthenticated);
|
throw const ErrorMessage(ErrorCode.notAuthenticated);
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ class ApplicationIntroSlideshow extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ApplicationIntroSlideshowState extends State<ApplicationIntroSlideshow> {
|
class _ApplicationIntroSlideshowState extends State<ApplicationIntroSlideshow> {
|
||||||
Image organizeImage = AssetImages.organizeDocuments.image;
|
AssetImage secureImage = AssetImages.secureDocuments.image;
|
||||||
Image secureImage = AssetImages.secureDocuments.image;
|
AssetImage successImage = AssetImages.success.image;
|
||||||
Image successImage = AssetImages.success.image;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -50,7 +49,9 @@ class _ApplicationIntroSlideshowState extends State<ApplicationIntroSlideshow> {
|
|||||||
),
|
),
|
||||||
image: Padding(
|
image: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: organizeImage,
|
child: Image(
|
||||||
|
image: AssetImages.organizeDocuments.image,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
bodyWidget: Column(
|
bodyWidget: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@@ -70,7 +71,7 @@ class _ApplicationIntroSlideshowState extends State<ApplicationIntroSlideshow> {
|
|||||||
),
|
),
|
||||||
image: Padding(
|
image: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: secureImage,
|
child: Image(image: AssetImages.secureDocuments.image),
|
||||||
),
|
),
|
||||||
bodyWidget: Column(
|
bodyWidget: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@@ -90,7 +91,7 @@ class _ApplicationIntroSlideshowState extends State<ApplicationIntroSlideshow> {
|
|||||||
),
|
),
|
||||||
image: Padding(
|
image: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: successImage,
|
child: Image(image: AssetImages.success.image),
|
||||||
),
|
),
|
||||||
bodyWidget: Column(
|
bodyWidget: Column(
|
||||||
children: const [
|
children: const [
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:injectable/injectable.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||||
|
|
||||||
|
part 'document_details_state.dart';
|
||||||
|
|
||||||
|
class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||||
|
final DocumentRepository _documentRepository;
|
||||||
|
|
||||||
|
DocumentDetailsCubit(this._documentRepository, DocumentModel initialDocument)
|
||||||
|
: super(DocumentDetailsState(document: initialDocument));
|
||||||
|
|
||||||
|
Future<void> delete(DocumentModel document) async {
|
||||||
|
await _documentRepository.delete(document);
|
||||||
|
emit(const DocumentDetailsState());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> update(DocumentModel document) async {
|
||||||
|
final updatedDocument = await _documentRepository.update(document);
|
||||||
|
emit(DocumentDetailsState(document: updatedDocument));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> assignAsn(DocumentModel document) async {
|
||||||
|
if (document.archiveSerialNumber == null) {
|
||||||
|
final int asn = await _documentRepository.findNextAsn();
|
||||||
|
update(document.copyWith(archiveSerialNumber: asn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
part of 'document_details_cubit.dart';
|
||||||
|
|
||||||
|
class DocumentDetailsState with EquatableMixin {
|
||||||
|
final DocumentModel? document;
|
||||||
|
|
||||||
|
const DocumentDetailsState({
|
||||||
|
this.document,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [document];
|
||||||
|
}
|
||||||
@@ -5,15 +5,13 @@ import 'dart:math';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.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:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/document_meta_data.model.dart';
|
import 'package:paperless_mobile/features/documents/model/document_meta_data.model.dart';
|
||||||
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||||
@@ -21,26 +19,26 @@ import 'package:paperless_mobile/features/documents/view/pages/document_edit_pag
|
|||||||
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';
|
||||||
|
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart';
|
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_widget.dart';
|
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_widget.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/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
class DocumentDetailsPage extends StatefulWidget {
|
class DocumentDetailsPage extends StatefulWidget {
|
||||||
final int documentId;
|
|
||||||
final bool allowEdit;
|
final bool allowEdit;
|
||||||
final bool isLabelClickable;
|
final bool isLabelClickable;
|
||||||
|
final String? titleAndContentQueryString;
|
||||||
|
|
||||||
const DocumentDetailsPage({
|
const DocumentDetailsPage({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.documentId,
|
|
||||||
this.allowEdit = true,
|
|
||||||
this.isLabelClickable = true,
|
this.isLabelClickable = true,
|
||||||
|
this.titleAndContentQueryString,
|
||||||
|
this.allowEdit = true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -48,130 +46,194 @@ class DocumentDetailsPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||||
static final DateFormat _detailedDateFormat =
|
@override
|
||||||
DateFormat("MMM d, yyyy HH:mm:ss");
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
initializeDateFormatting();
|
||||||
|
}
|
||||||
|
|
||||||
bool _isDownloadPending = false;
|
bool _isDownloadPending = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
return WillPopScope(
|
||||||
// buildWhen required because rebuild would happen after delete causing error.
|
onWillPop: () {
|
||||||
buildWhen: (previous, current) {
|
print("Returning document...");
|
||||||
return current.documents
|
Navigator.of(context)
|
||||||
.where((element) => element.id == widget.documentId)
|
.pop(BlocProvider.of<DocumentDetailsCubit>(context).state.document);
|
||||||
.isNotEmpty;
|
return Future.value(false);
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
child: DefaultTabController(
|
||||||
final document =
|
length: 3,
|
||||||
state.documents.where((doc) => doc.id == widget.documentId).first;
|
child: Scaffold(
|
||||||
return DefaultTabController(
|
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||||
length: 3,
|
floatingActionButton: widget.allowEdit
|
||||||
child: Scaffold(
|
? FloatingActionButton(
|
||||||
floatingActionButtonLocation:
|
child: const Icon(Icons.edit),
|
||||||
FloatingActionButtonLocation.endDocked,
|
onPressed: _onEdit,
|
||||||
floatingActionButton: widget.allowEdit
|
)
|
||||||
? FloatingActionButton(
|
: null,
|
||||||
child: const Icon(Icons.edit),
|
bottomNavigationBar:
|
||||||
onPressed: () => _onEdit(document),
|
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||||
)
|
builder: (context, state) {
|
||||||
: null,
|
return BottomAppBar(
|
||||||
bottomNavigationBar: BottomAppBar(
|
child: Row(
|
||||||
child: Row(
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
children: [
|
||||||
children: [
|
IconButton(
|
||||||
IconButton(
|
icon: const Icon(Icons.delete),
|
||||||
icon: const Icon(Icons.delete),
|
onPressed: widget.allowEdit && state.document != null
|
||||||
onPressed:
|
? () => _onDelete(state.document!)
|
||||||
widget.allowEdit ? () => _onDelete(document) : null,
|
: null,
|
||||||
).padded(const EdgeInsets.symmetric(horizontal: 4)),
|
).padded(const EdgeInsets.symmetric(horizontal: 4)),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.download),
|
icon: const Icon(Icons.download),
|
||||||
onPressed:
|
onPressed: Platform.isAndroid && state.document != null
|
||||||
Platform.isAndroid ? () => _onDownload(document) : null,
|
? () => _onDownload(state.document!)
|
||||||
).padded(const EdgeInsets.only(right: 4)),
|
: null,
|
||||||
IconButton(
|
).padded(const EdgeInsets.only(right: 4)),
|
||||||
icon: const Icon(Icons.open_in_new),
|
IconButton(
|
||||||
onPressed: () => _onOpen(document),
|
icon: const Icon(Icons.open_in_new),
|
||||||
).padded(const EdgeInsets.only(right: 4)),
|
onPressed: state.document != null
|
||||||
IconButton(
|
? () => _onOpen(state.document!)
|
||||||
icon: const Icon(Icons.share),
|
: null,
|
||||||
onPressed: () => _onShare(document),
|
).padded(const EdgeInsets.only(right: 4)),
|
||||||
),
|
IconButton(
|
||||||
],
|
icon: const Icon(Icons.share),
|
||||||
),
|
onPressed: state.document != null
|
||||||
),
|
? () => _onShare(state.document!)
|
||||||
body: NestedScrollView(
|
: null,
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
|
||||||
SliverAppBar(
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.arrow_back,
|
|
||||||
color: Colors
|
|
||||||
.black, //TODO: check if there is a way to dynamically determine color...
|
|
||||||
),
|
),
|
||||||
onPressed: () => Navigator.pop(context),
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
body: NestedScrollView(
|
||||||
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
|
SliverAppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.arrow_back,
|
||||||
|
color: Colors
|
||||||
|
.black, //TODO: check if there is a way to dynamically determine color...
|
||||||
),
|
),
|
||||||
floating: true,
|
onPressed: () => Navigator.pop(
|
||||||
pinned: true,
|
context,
|
||||||
expandedHeight: 200.0,
|
BlocProvider.of<DocumentDetailsCubit>(context)
|
||||||
flexibleSpace: DocumentPreview(
|
.state
|
||||||
id: document.id,
|
.document),
|
||||||
fit: BoxFit.cover,
|
),
|
||||||
),
|
floating: true,
|
||||||
bottom: ColoredTabBar(
|
pinned: true,
|
||||||
backgroundColor:
|
expandedHeight: 200.0,
|
||||||
Theme.of(context).colorScheme.primaryContainer,
|
flexibleSpace:
|
||||||
tabBar: TabBar(
|
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||||
tabs: [
|
builder: (context, state) {
|
||||||
Tab(
|
if (state.document == null) {
|
||||||
child: Text(
|
return Container(height: 200);
|
||||||
S.of(context).documentDetailsPageTabOverviewLabel,
|
}
|
||||||
style: TextStyle(
|
return DocumentPreview(
|
||||||
color: Theme.of(context)
|
id: state.document!.id,
|
||||||
.colorScheme
|
fit: BoxFit.cover,
|
||||||
.onPrimaryContainer),
|
);
|
||||||
),
|
},
|
||||||
|
),
|
||||||
|
bottom: ColoredTabBar(
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
tabBar: TabBar(
|
||||||
|
tabs: [
|
||||||
|
Tab(
|
||||||
|
child: Text(
|
||||||
|
S.of(context).documentDetailsPageTabOverviewLabel,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer),
|
||||||
),
|
),
|
||||||
Tab(
|
),
|
||||||
child: Text(
|
Tab(
|
||||||
S.of(context).documentDetailsPageTabContentLabel,
|
child: Text(
|
||||||
style: TextStyle(
|
S.of(context).documentDetailsPageTabContentLabel,
|
||||||
color: Theme.of(context)
|
style: TextStyle(
|
||||||
.colorScheme
|
color: Theme.of(context)
|
||||||
.onPrimaryContainer),
|
.colorScheme
|
||||||
),
|
.onPrimaryContainer),
|
||||||
),
|
),
|
||||||
Tab(
|
),
|
||||||
child: Text(
|
Tab(
|
||||||
S.of(context).documentDetailsPageTabMetaDataLabel,
|
child: Text(
|
||||||
style: TextStyle(
|
S.of(context).documentDetailsPageTabMetaDataLabel,
|
||||||
color: Theme.of(context)
|
style: TextStyle(
|
||||||
.colorScheme
|
color: Theme.of(context)
|
||||||
.onPrimaryContainer),
|
.colorScheme
|
||||||
),
|
.onPrimaryContainer),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
body: TabBarView(
|
|
||||||
children: [
|
|
||||||
_buildDocumentOverview(
|
|
||||||
document, state.filter.titleAndContentMatchString),
|
|
||||||
_buildDocumentContentView(
|
|
||||||
document, state.filter.titleAndContentMatchString),
|
|
||||||
_buildDocumentMetaDataView(document),
|
|
||||||
].padded(),
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
body: BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state.document == null) {
|
||||||
|
return TabBarView(
|
||||||
|
children: [
|
||||||
|
Container(),
|
||||||
|
Container(),
|
||||||
|
Container(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return TabBarView(
|
||||||
|
children: [
|
||||||
|
_buildDocumentOverview(
|
||||||
|
state.document!,
|
||||||
|
widget.titleAndContentQueryString,
|
||||||
|
),
|
||||||
|
_buildDocumentContentView(
|
||||||
|
state.document!,
|
||||||
|
widget.titleAndContentQueryString,
|
||||||
|
),
|
||||||
|
_buildDocumentMetaDataView(
|
||||||
|
state.document!,
|
||||||
|
),
|
||||||
|
].padded(),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onEdit() async {
|
||||||
|
{
|
||||||
|
final cubit = BlocProvider.of<DocumentDetailsCubit>(context);
|
||||||
|
if (cubit.state.document == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.push<bool>(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => GlobalStateBlocProvider(
|
||||||
|
child: DocumentEditPage(
|
||||||
|
document: cubit.state.document!,
|
||||||
|
onEdit: (updatedDocument) {
|
||||||
|
return BlocProvider.of<DocumentDetailsCubit>(context)
|
||||||
|
.update(updatedDocument);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
maintainState: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildDocumentMetaDataView(DocumentModel document) {
|
Widget _buildDocumentMetaDataView(DocumentModel document) {
|
||||||
return FutureBuilder<DocumentMetaData>(
|
return FutureBuilder<DocumentMetaData>(
|
||||||
future: getIt<DocumentRepository>().getMetaData(document),
|
future: getIt<DocumentRepository>().getMetaData(document),
|
||||||
@@ -182,11 +244,11 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
final meta = snapshot.data!;
|
final meta = snapshot.data!;
|
||||||
return ListView(
|
return ListView(
|
||||||
children: [
|
children: [
|
||||||
_DetailsItem.text(_detailedDateFormat.format(document.modified),
|
_DetailsItem.text(DateFormat().format(document.modified),
|
||||||
label: S.of(context).documentModifiedPropertyLabel,
|
label: S.of(context).documentModifiedPropertyLabel,
|
||||||
context: context),
|
context: context),
|
||||||
_separator(),
|
_separator(),
|
||||||
_DetailsItem.text(_detailedDateFormat.format(document.added),
|
_DetailsItem.text(DateFormat().format(document.added),
|
||||||
label: S.of(context).documentAddedPropertyLabel,
|
label: S.of(context).documentAddedPropertyLabel,
|
||||||
context: context),
|
context: context),
|
||||||
_separator(),
|
_separator(),
|
||||||
@@ -233,7 +295,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
|
|
||||||
Future<void> _assignAsn(DocumentModel document) async {
|
Future<void> _assignAsn(DocumentModel document) async {
|
||||||
try {
|
try {
|
||||||
await BlocProvider.of<DocumentsCubit>(context).assignAsn(document);
|
await BlocProvider.of<DocumentDetailsCubit>(context).assignAsn(document);
|
||||||
} on ErrorMessage catch (error, stackTrace) {
|
} on ErrorMessage catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
@@ -265,8 +327,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
),
|
),
|
||||||
_separator(),
|
_separator(),
|
||||||
_DetailsItem.text(
|
_DetailsItem.text(
|
||||||
DateFormat.yMMMd(Localizations.localeOf(context).toLanguageTag())
|
DateFormat().format(document.created),
|
||||||
.format(document.created),
|
|
||||||
context: context,
|
context: context,
|
||||||
label: S.of(context).documentCreatedPropertyLabel,
|
label: S.of(context).documentCreatedPropertyLabel,
|
||||||
),
|
),
|
||||||
@@ -311,6 +372,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
child: TagsWidget(
|
child: TagsWidget(
|
||||||
isClickable: widget.isLabelClickable,
|
isClickable: widget.isLabelClickable,
|
||||||
tagIds: document.tags,
|
tagIds: document.tags,
|
||||||
|
isSelectedPredicate: (_) => false,
|
||||||
|
onTagSelected: (int tagId) {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -345,22 +408,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
return const SizedBox(height: 32.0);
|
return const SizedBox(height: 32.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onEdit(DocumentModel document) async {
|
|
||||||
final wasUpdated = await Navigator.push<bool>(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (_) => GlobalStateBlocProvider(
|
|
||||||
child: DocumentEditPage(document: document),
|
|
||||||
),
|
|
||||||
maintainState: true,
|
|
||||||
),
|
|
||||||
) ??
|
|
||||||
false;
|
|
||||||
if (wasUpdated) {
|
|
||||||
BlocProvider.of<PaperlessStatisticsCubit>(context).updateStatistics();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onDownload(DocumentModel document) async {
|
Future<void> _onDownload(DocumentModel document) async {
|
||||||
if (!Platform.isAndroid) {
|
if (!Platform.isAndroid) {
|
||||||
showSnackBar(
|
showSnackBar(
|
||||||
@@ -411,12 +458,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
false;
|
false;
|
||||||
if (delete) {
|
if (delete) {
|
||||||
try {
|
try {
|
||||||
await BlocProvider.of<DocumentsCubit>(context).remove(document);
|
await BlocProvider.of<DocumentDetailsCubit>(context).delete(document);
|
||||||
showSnackBar(context, S.of(context).documentDeleteSuccessMessage);
|
showSnackBar(context, S.of(context).documentDeleteSuccessMessage);
|
||||||
} on ErrorMessage catch (error, stackTrace) {
|
} on ErrorMessage catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
} finally {
|
} finally {
|
||||||
Navigator.pop(context);
|
// Document deleted => go back to primary route
|
||||||
|
Navigator.popUntil(context, (route) => route.isFirst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,11 +15,6 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
|||||||
|
|
||||||
DocumentsCubit(this.documentRepository) : super(DocumentsState.initial);
|
DocumentsCubit(this.documentRepository) : super(DocumentsState.initial);
|
||||||
|
|
||||||
Future<void> remove(DocumentModel document) async {
|
|
||||||
await documentRepository.delete(document);
|
|
||||||
await reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> bulkRemove(List<DocumentModel> documents) async {
|
Future<void> bulkRemove(List<DocumentModel> documents) async {
|
||||||
await documentRepository.bulkAction(
|
await documentRepository.bulkAction(
|
||||||
BulkDeleteAction(documents.map((doc) => doc.id)),
|
BulkDeleteAction(documents.map((doc) => doc.id)),
|
||||||
@@ -40,8 +35,13 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
|||||||
await reload();
|
await reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> update(DocumentModel document) async {
|
Future<void> update(
|
||||||
await documentRepository.update(document);
|
DocumentModel document, [
|
||||||
|
bool updateRemote = true,
|
||||||
|
]) async {
|
||||||
|
if (updateRemote) {
|
||||||
|
await documentRepository.update(document);
|
||||||
|
}
|
||||||
await reload();
|
await reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +83,6 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
|||||||
isLoaded: true, value: [...state.value, result], filter: newFilter));
|
isLoaded: true, value: [...state.value, result], filter: newFilter));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> assignAsn(DocumentModel document) async {
|
|
||||||
if (document.archiveSerialNumber == null) {
|
|
||||||
final int asn = await documentRepository.findNextAsn();
|
|
||||||
update(document.copyWith(archiveSerialNumber: asn));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Update filter state and automatically reload documents. Always resets page to 1.
|
/// Update filter state and automatically reload documents. Always resets page to 1.
|
||||||
/// Use [DocumentsCubit.loadMore] to load more data.
|
/// Use [DocumentsCubit.loadMore] to load more data.
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class BulkDeleteAction extends BulkAction {
|
|||||||
return {
|
return {
|
||||||
'documents': documentIds.toList(),
|
'documents': documentIds.toList(),
|
||||||
'method': 'delete',
|
'method': 'delete',
|
||||||
|
'parameters': {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,8 +34,9 @@ class BulkModifyTagsAction extends BulkAction {
|
|||||||
BulkModifyTagsAction.addTags(super.documents, this.addTags)
|
BulkModifyTagsAction.addTags(super.documents, this.addTags)
|
||||||
: removeTags = const [];
|
: removeTags = const [];
|
||||||
|
|
||||||
BulkModifyTagsAction.removeTags(super.documents, this.removeTags)
|
BulkModifyTagsAction.removeTags(super.documents, Iterable<int> tags)
|
||||||
: addTags = const [];
|
: addTags = const [],
|
||||||
|
removeTags = tags;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
JSON toJson() {
|
JSON toJson() {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
import 'package:paperless_mobile/core/type/types.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/query_parameters/id_query_parameter.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
|
|
||||||
|
|
||||||
class DocumentModel extends Equatable {
|
class DocumentModel extends Equatable {
|
||||||
static const idKey = 'id';
|
static const idKey = 'id';
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class SavedView with EquatableMixin {
|
|||||||
DocumentFilter toDocumentFilter() {
|
DocumentFilter toDocumentFilter() {
|
||||||
return filterRules.fold(
|
return filterRules.fold(
|
||||||
DocumentFilter(
|
DocumentFilter(
|
||||||
sortOrder: sortReverse ? SortOrder.ascending : SortOrder.descending,
|
sortOrder: sortReverse ? SortOrder.descending : SortOrder.ascending,
|
||||||
sortField: sortField,
|
sortField: sortField,
|
||||||
),
|
),
|
||||||
(filter, filterRule) => filterRule.applyToFilter(filter),
|
(filter, filterRule) => filterRule.applyToFilter(filter),
|
||||||
@@ -80,7 +80,7 @@ class SavedView with EquatableMixin {
|
|||||||
sortField: filter.sortField,
|
sortField: filter.sortField,
|
||||||
showInSidebar: showInSidebar,
|
showInSidebar: showInSidebar,
|
||||||
showOnDashboard: showOnDashboard,
|
showOnDashboard: showOnDashboard,
|
||||||
sortReverse: filter.sortOrder == SortOrder.ascending,
|
sortReverse: filter.sortOrder == SortOrder.descending,
|
||||||
);
|
);
|
||||||
|
|
||||||
JSON toJson() {
|
JSON toJson() {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -33,7 +34,13 @@ import 'package:paperless_mobile/util.dart';
|
|||||||
|
|
||||||
class DocumentEditPage extends StatefulWidget {
|
class DocumentEditPage extends StatefulWidget {
|
||||||
final DocumentModel document;
|
final DocumentModel document;
|
||||||
const DocumentEditPage({Key? key, required this.document}) : super(key: key);
|
final FutureOr<void> Function(DocumentModel updatedDocument) onEdit;
|
||||||
|
|
||||||
|
const DocumentEditPage({
|
||||||
|
Key? key,
|
||||||
|
required this.document,
|
||||||
|
required this.onEdit,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DocumentEditPage> createState() => _DocumentEditPageState();
|
State<DocumentEditPage> createState() => _DocumentEditPageState();
|
||||||
@@ -66,7 +73,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||||
final values = _formKey.currentState!.value;
|
final values = _formKey.currentState!.value;
|
||||||
final updatedDocument = widget.document.copyWith(
|
var updatedDocument = widget.document.copyWith(
|
||||||
title: values[fkTitle],
|
title: values[fkTitle],
|
||||||
created: values[fkCreatedDate],
|
created: values[fkCreatedDate],
|
||||||
overwriteDocumentType: true,
|
overwriteDocumentType: true,
|
||||||
@@ -81,15 +88,17 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isSubmitLoading = true;
|
_isSubmitLoading = true;
|
||||||
});
|
});
|
||||||
bool wasUpdated = false;
|
|
||||||
try {
|
try {
|
||||||
await getIt<DocumentsCubit>().update(updatedDocument);
|
await widget.onEdit(updatedDocument);
|
||||||
showSnackBar(context, S.of(context).documentUpdateErrorMessage);
|
showSnackBar(context, S.of(context).documentUpdateSuccessMessage);
|
||||||
wasUpdated = true;
|
|
||||||
} on ErrorMessage catch (error, stackTrace) {
|
} on ErrorMessage catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
} finally {
|
} finally {
|
||||||
Navigator.pop(context, wasUpdated);
|
setState(() {
|
||||||
|
_isSubmitLoading = false;
|
||||||
|
});
|
||||||
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/document_details_page.dart';
|
import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/grid/document_grid.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/grid/document_grid.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list.dart';
|
||||||
@@ -160,22 +163,25 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
switch (settings.preferredViewType) {
|
switch (settings.preferredViewType) {
|
||||||
case ViewType.list:
|
case ViewType.list:
|
||||||
child = DocumentListView(
|
child = DocumentListView(
|
||||||
onTap: _openDocumentDetails,
|
onTap: _openDetails,
|
||||||
state: state,
|
state: state,
|
||||||
onSelected: _onSelected,
|
onSelected: _onSelected,
|
||||||
pagingController: _pagingController,
|
pagingController: _pagingController,
|
||||||
hasInternetConnection:
|
hasInternetConnection:
|
||||||
connectivityState == ConnectivityState.connected,
|
connectivityState == ConnectivityState.connected,
|
||||||
|
onTagSelected: _addTagToFilter,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case ViewType.grid:
|
case ViewType.grid:
|
||||||
child = DocumentGridView(
|
child = DocumentGridView(
|
||||||
onTap: _openDocumentDetails,
|
onTap: _openDetails,
|
||||||
state: state,
|
state: state,
|
||||||
onSelected: _onSelected,
|
onSelected: _onSelected,
|
||||||
pagingController: _pagingController,
|
pagingController: _pagingController,
|
||||||
hasInternetConnection:
|
hasInternetConnection:
|
||||||
connectivityState == ConnectivityState.connected);
|
connectivityState == ConnectivityState.connected,
|
||||||
|
onTagSelected: (int tagId) => _addTagToFilter,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,26 +228,63 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openDocumentDetails(DocumentModel model) {
|
Future<void> _openDetails(DocumentModel document) async {
|
||||||
Navigator.push(
|
await Navigator.of(context).push<DocumentModel?>(
|
||||||
context,
|
_buildDetailsPageRoute(document),
|
||||||
MaterialPageRoute(
|
);
|
||||||
builder: (_) => MultiBlocProvider(
|
BlocProvider.of<DocumentsCubit>(context).reload();
|
||||||
providers: [
|
}
|
||||||
BlocProvider.value(value: BlocProvider.of<DocumentsCubit>(context)),
|
|
||||||
BlocProvider.value(
|
MaterialPageRoute<DocumentModel?> _buildDetailsPageRoute(
|
||||||
value: BlocProvider.of<CorrespondentCubit>(context)),
|
DocumentModel document) {
|
||||||
BlocProvider.value(
|
return MaterialPageRoute(
|
||||||
value: BlocProvider.of<DocumentTypeCubit>(context)),
|
builder: (_) => MultiBlocProvider(
|
||||||
BlocProvider.value(value: BlocProvider.of<TagCubit>(context)),
|
providers: [
|
||||||
BlocProvider.value(
|
BlocProvider.value(
|
||||||
value: BlocProvider.of<StoragePathCubit>(context)),
|
value: BlocProvider.of<DocumentsCubit>(context),
|
||||||
BlocProvider.value(
|
),
|
||||||
value: BlocProvider.of<PaperlessStatisticsCubit>(context)),
|
BlocProvider.value(
|
||||||
],
|
value: BlocProvider.of<CorrespondentCubit>(context),
|
||||||
child: DocumentDetailsPage(documentId: model.id),
|
),
|
||||||
),
|
BlocProvider.value(
|
||||||
|
value: BlocProvider.of<DocumentTypeCubit>(context),
|
||||||
|
),
|
||||||
|
BlocProvider.value(
|
||||||
|
value: BlocProvider.of<TagCubit>(context),
|
||||||
|
),
|
||||||
|
BlocProvider.value(
|
||||||
|
value: BlocProvider.of<StoragePathCubit>(context),
|
||||||
|
),
|
||||||
|
BlocProvider.value(
|
||||||
|
value: DocumentDetailsCubit(getIt<DocumentRepository>(), document),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: const DocumentDetailsPage(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _addTagToFilter(int tagId) {
|
||||||
|
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
||||||
|
try {
|
||||||
|
final tagsQuery = cubit.state.filter.tags is IdsTagsQuery
|
||||||
|
? cubit.state.filter.tags as IdsTagsQuery
|
||||||
|
: const IdsTagsQuery();
|
||||||
|
if (tagsQuery.includedIds.contains(tagId)) {
|
||||||
|
cubit.updateCurrentFilter(
|
||||||
|
(filter) => filter.copyWith(
|
||||||
|
tags: tagsQuery.withIdsRemoved([tagId]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
cubit.updateCurrentFilter(
|
||||||
|
(filter) => filter.copyWith(
|
||||||
|
tags: tagsQuery.withIdQueriesAdded([IncludeTagIdQuery(tagId)]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} on ErrorMessage catch (error, stackTrace) {
|
||||||
|
showErrorMessage(context, error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import 'package:paperless_mobile/core/widgets/empty_state.dart';
|
|||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ class DocumentsEmptyState extends StatelessWidget {
|
|||||||
title: S.of(context).documentsPageEmptyStateOopsText,
|
title: S.of(context).documentsPageEmptyStateOopsText,
|
||||||
subtitle: S.of(context).documentsPageEmptyStateNothingHereText,
|
subtitle: S.of(context).documentsPageEmptyStateNothingHereText,
|
||||||
bottomChild: state.filter != DocumentFilter.initial
|
bottomChild: state.filter != DocumentFilter.initial
|
||||||
? ElevatedButton(
|
? TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await BlocProvider.of<DocumentsCubit>(context).updateFilter();
|
await BlocProvider.of<DocumentsCubit>(context).updateFilter();
|
||||||
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
BlocProvider.of<SavedViewCubit>(context).resetSelection();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/grid/document_grid_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/grid/document_grid_item.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ class DocumentGridView extends StatelessWidget {
|
|||||||
final PagingController<int, DocumentModel> pagingController;
|
final PagingController<int, DocumentModel> pagingController;
|
||||||
final DocumentsState state;
|
final DocumentsState state;
|
||||||
final bool hasInternetConnection;
|
final bool hasInternetConnection;
|
||||||
|
final void Function(int tagId) onTagSelected;
|
||||||
|
|
||||||
const DocumentGridView({
|
const DocumentGridView({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -19,6 +21,7 @@ class DocumentGridView extends StatelessWidget {
|
|||||||
required this.state,
|
required this.state,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
required this.hasInternetConnection,
|
required this.hasInternetConnection,
|
||||||
|
required this.onTagSelected,
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -38,6 +41,14 @@ class DocumentGridView extends StatelessWidget {
|
|||||||
isSelected: state.selection.contains(item),
|
isSelected: state.selection.contains(item),
|
||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
isAtLeastOneSelected: state.selection.isNotEmpty,
|
isAtLeastOneSelected: state.selection.isNotEmpty,
|
||||||
|
isTagSelectedPredicate: (int tagId) {
|
||||||
|
return state.filter.tags is IdsTagsQuery
|
||||||
|
? (state.filter.tags as IdsTagsQuery)
|
||||||
|
.includedIds
|
||||||
|
.contains(tagId)
|
||||||
|
: false;
|
||||||
|
},
|
||||||
|
onTagSelected: onTagSelected,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
noItemsFoundIndicatorBuilder: (context) =>
|
noItemsFoundIndicatorBuilder: (context) =>
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class DocumentGridItem extends StatelessWidget {
|
|||||||
final void Function(DocumentModel) onTap;
|
final void Function(DocumentModel) onTap;
|
||||||
final void Function(DocumentModel) onSelected;
|
final void Function(DocumentModel) onSelected;
|
||||||
final bool isAtLeastOneSelected;
|
final bool isAtLeastOneSelected;
|
||||||
|
final bool Function(int tagId) isTagSelectedPredicate;
|
||||||
|
final void Function(int tagId) onTagSelected;
|
||||||
|
|
||||||
const DocumentGridItem({
|
const DocumentGridItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -20,6 +22,8 @@ class DocumentGridItem extends StatelessWidget {
|
|||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
required this.isAtLeastOneSelected,
|
required this.isAtLeastOneSelected,
|
||||||
|
required this.isTagSelectedPredicate,
|
||||||
|
required this.onTagSelected,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -65,6 +69,8 @@ class DocumentGridItem extends StatelessWidget {
|
|||||||
TagsWidget(
|
TagsWidget(
|
||||||
tagIds: document.tags,
|
tagIds: document.tags,
|
||||||
isMultiLine: false,
|
isMultiLine: false,
|
||||||
|
isSelectedPredicate: isTagSelectedPredicate,
|
||||||
|
onTagSelected: onTagSelected,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.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/bloc/documents_state.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart';
|
||||||
|
|
||||||
class DocumentListView extends StatelessWidget {
|
class DocumentListView extends StatelessWidget {
|
||||||
final void Function(DocumentModel) onTap;
|
final void Function(DocumentModel) onTap;
|
||||||
@@ -14,6 +16,8 @@ class DocumentListView extends StatelessWidget {
|
|||||||
final DocumentsState state;
|
final DocumentsState state;
|
||||||
final bool hasInternetConnection;
|
final bool hasInternetConnection;
|
||||||
final bool isLabelClickable;
|
final bool isLabelClickable;
|
||||||
|
final void Function(int tagId) onTagSelected;
|
||||||
|
|
||||||
const DocumentListView({
|
const DocumentListView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
@@ -22,6 +26,7 @@ class DocumentListView extends StatelessWidget {
|
|||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
required this.hasInternetConnection,
|
required this.hasInternetConnection,
|
||||||
this.isLabelClickable = true,
|
this.isLabelClickable = true,
|
||||||
|
required this.onTagSelected,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -38,6 +43,14 @@ class DocumentListView extends StatelessWidget {
|
|||||||
isSelected: state.selection.contains(document),
|
isSelected: state.selection.contains(document),
|
||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
isAtLeastOneSelected: state.selection.isNotEmpty,
|
isAtLeastOneSelected: state.selection.isNotEmpty,
|
||||||
|
isTagSelectedPredicate: (int tagId) {
|
||||||
|
return state.filter.tags is IdsTagsQuery
|
||||||
|
? (state.filter.tags as IdsTagsQuery)
|
||||||
|
.includedIds
|
||||||
|
.contains(tagId)
|
||||||
|
: false;
|
||||||
|
},
|
||||||
|
onTagSelected: onTagSelected,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
noItemsFoundIndicatorBuilder: (context) => hasInternetConnection
|
noItemsFoundIndicatorBuilder: (context) => hasInternetConnection
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||||
|
import 'package:paperless_mobile/features/labels/tags/model/tag.model.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';
|
||||||
|
|
||||||
class DocumentListItem extends StatelessWidget {
|
class DocumentListItem extends StatelessWidget {
|
||||||
@@ -12,6 +13,9 @@ class DocumentListItem extends StatelessWidget {
|
|||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final bool isAtLeastOneSelected;
|
final bool isAtLeastOneSelected;
|
||||||
final bool isLabelClickable;
|
final bool isLabelClickable;
|
||||||
|
final bool Function(int tagId) isTagSelectedPredicate;
|
||||||
|
|
||||||
|
final void Function(int tagId) onTagSelected;
|
||||||
|
|
||||||
const DocumentListItem({
|
const DocumentListItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -21,6 +25,8 @@ class DocumentListItem extends StatelessWidget {
|
|||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
required this.isAtLeastOneSelected,
|
required this.isAtLeastOneSelected,
|
||||||
this.isLabelClickable = true,
|
this.isLabelClickable = true,
|
||||||
|
required this.isTagSelectedPredicate,
|
||||||
|
required this.onTagSelected,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -62,6 +68,8 @@ class DocumentListItem extends StatelessWidget {
|
|||||||
isClickable: isLabelClickable,
|
isClickable: isLabelClickable,
|
||||||
tagIds: document.tags,
|
tagIds: document.tags,
|
||||||
isMultiLine: false,
|
isMultiLine: false,
|
||||||
|
isSelectedPredicate: isTagSelectedPredicate,
|
||||||
|
onTagSelected: onTagSelected,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:paperless_mobile/core/model/error_message.dart';
|
|||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart';
|
import 'package:paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart';
|
||||||
@@ -23,6 +22,7 @@ import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_
|
|||||||
import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart';
|
import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/model/saved_view.model.dart';
|
import 'package:paperless_mobile/features/documents/model/saved_view.model.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
|
||||||
|
|
||||||
class ConfirmDeleteSavedViewDialog extends StatelessWidget {
|
class ConfirmDeleteSavedViewDialog extends StatelessWidget {
|
||||||
const ConfirmDeleteSavedViewDialog({
|
const ConfirmDeleteSavedViewDialog({
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/bloc/saved_view_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/model/saved_view.model.dart';
|
import 'package:paperless_mobile/features/documents/model/saved_view.model.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/add_saved_view_page.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/add_saved_view_page.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_state.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/widget/bottom_navigation_bar.dart';
|
import 'package:paperless_mobile/features/home/view/widget/bottom_navigation_bar.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart';
|
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart';
|
||||||
@@ -20,6 +18,7 @@ import 'package:paperless_mobile/features/labels/document_type/bloc/document_typ
|
|||||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
|
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
|
import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
@@ -88,7 +87,6 @@ class _HomePageState extends State<HomePage> {
|
|||||||
return Future.wait([
|
return Future.wait([
|
||||||
BlocProvider.of<PaperlessServerInformationCubit>(context)
|
BlocProvider.of<PaperlessServerInformationCubit>(context)
|
||||||
.updateInformtion(),
|
.updateInformtion(),
|
||||||
getIt<PaperlessStatisticsCubit>().updateStatistics(),
|
|
||||||
getIt<DocumentTypeCubit>().initialize(),
|
getIt<DocumentTypeCubit>().initialize(),
|
||||||
getIt<CorrespondentCubit>().initialize(),
|
getIt<CorrespondentCubit>().initialize(),
|
||||||
getIt<TagCubit>().initialize(),
|
getIt<TagCubit>().initialize(),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:badges/badges.dart';
|
import 'package:badges/badges.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:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/core/model/paperless_statistics_state.dart';
|
import 'package:paperless_mobile/core/model/paperless_statistics_state.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||||
@@ -120,17 +119,10 @@ class InfoDrawer extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlocBuilder<PaperlessStatisticsCubit, PaperlessStatisticsState>(
|
ListTile(
|
||||||
builder: (context, state) {
|
title: Text(S.of(context).bottomNavInboxPageLabel),
|
||||||
return ListTile(
|
leading: const Icon(Icons.inbox),
|
||||||
title: Text(S.of(context).bottomNavInboxPageLabel),
|
onTap: () => _onOpenInbox(context),
|
||||||
leading: const Icon(Icons.inbox),
|
|
||||||
trailing: state.isLoaded
|
|
||||||
? Text(state.statistics!.documentsInInbox.toString())
|
|
||||||
: null,
|
|
||||||
onTap: () => _onOpenInbox(context),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Divider(),
|
Divider(),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -217,15 +209,9 @@ class InfoDrawer extends StatelessWidget {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => GlobalStateBlocProvider(
|
builder: (_) => GlobalStateBlocProvider(
|
||||||
additionalProviders: [
|
additionalProviders: [
|
||||||
BlocProvider<PaperlessStatisticsCubit>.value(
|
|
||||||
value: BlocProvider.of<PaperlessStatisticsCubit>(context),
|
|
||||||
),
|
|
||||||
BlocProvider<InboxCubit>.value(
|
BlocProvider<InboxCubit>.value(
|
||||||
value: getIt<InboxCubit>()..initialize(),
|
value: getIt<InboxCubit>()..initialize(),
|
||||||
),
|
),
|
||||||
BlocProvider<DocumentsCubit>.value(
|
|
||||||
value: getIt<DocumentsCubit>(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
child: const InboxPage(),
|
child: const InboxPage(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -48,11 +48,13 @@ class InboxCubit extends Cubit<InboxState> {
|
|||||||
sortField: SortField.added,
|
sortField: SortField.added,
|
||||||
))
|
))
|
||||||
.then((psr) => psr.results);
|
.then((psr) => psr.results);
|
||||||
emit(InboxState(
|
emit(
|
||||||
isLoaded: true,
|
InboxState(
|
||||||
inboxItems: inboxDocuments,
|
isLoaded: true,
|
||||||
inboxTags: state.inboxTags,
|
inboxItems: inboxDocuments,
|
||||||
));
|
inboxTags: state.inboxTags,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -61,12 +63,13 @@ class InboxCubit extends Cubit<InboxState> {
|
|||||||
///
|
///
|
||||||
Future<Iterable<int>> remove(DocumentModel document) async {
|
Future<Iterable<int>> remove(DocumentModel document) async {
|
||||||
if (!state.isLoaded) {
|
if (!state.isLoaded) {
|
||||||
throw "State has not yet loaded. Ensure the state is loaded when calling this method!";
|
throw "State has not loaded yet. Ensure the state is loaded when calling this method!";
|
||||||
}
|
}
|
||||||
final tagsToRemove =
|
final tagsToRemove =
|
||||||
document.tags.toSet().intersection(state.inboxTags.toSet());
|
document.tags.toSet().intersection(state.inboxTags.toSet());
|
||||||
|
|
||||||
final updatedTags = {...document.tags}..removeAll(tagsToRemove);
|
final updatedTags = {...document.tags}..removeAll(tagsToRemove);
|
||||||
|
|
||||||
await _documentRepository.update(
|
await _documentRepository.update(
|
||||||
document.copyWith(
|
document.copyWith(
|
||||||
tags: updatedTags,
|
tags: updatedTags,
|
||||||
@@ -84,31 +87,43 @@ class InboxCubit extends Cubit<InboxState> {
|
|||||||
return tagsToRemove;
|
return tagsToRemove;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Adds the previously removed tags to the document and performs an update.
|
||||||
|
///
|
||||||
Future<void> undoRemove(
|
Future<void> undoRemove(
|
||||||
DocumentModel document, Iterable<int> removedTags) async {
|
DocumentModel document,
|
||||||
|
Iterable<int> removedTags,
|
||||||
|
) async {
|
||||||
final updatedDoc = document.copyWith(
|
final updatedDoc = document.copyWith(
|
||||||
tags: {...document.tags, ...removedTags},
|
tags: {...document.tags, ...removedTags},
|
||||||
overwriteTags: true,
|
overwriteTags: true,
|
||||||
);
|
);
|
||||||
await _documentRepository.update(updatedDoc);
|
await _documentRepository.update(updatedDoc);
|
||||||
emit(InboxState(
|
emit(
|
||||||
isLoaded: true,
|
InboxState(
|
||||||
inboxItems: [...state.inboxItems, updatedDoc]
|
isLoaded: true,
|
||||||
..sort((d1, d2) => d1.added.compareTo(d2.added)),
|
inboxItems: [...state.inboxItems, updatedDoc]
|
||||||
inboxTags: state.inboxTags,
|
..sort((d1, d2) => d2.added.compareTo(d1.added)),
|
||||||
));
|
inboxTags: state.inboxTags,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Removes inbox tags from all documents in the inbox.
|
/// Removes inbox tags from all documents in the inbox.
|
||||||
///
|
///
|
||||||
Future<void> clearInbox() async {
|
Future<void> clearInbox() async {
|
||||||
await _documentRepository.bulkAction(BulkModifyTagsAction.removeTags(
|
await _documentRepository.bulkAction(
|
||||||
state.inboxItems.map((e) => e.id), state.inboxTags));
|
BulkModifyTagsAction.removeTags(
|
||||||
|
state.inboxItems.map((e) => e.id),
|
||||||
|
state.inboxTags,
|
||||||
|
),
|
||||||
|
);
|
||||||
emit(
|
emit(
|
||||||
InboxState(
|
InboxState(
|
||||||
isLoaded: true,
|
isLoaded: true,
|
||||||
inboxTags: state.inboxTags,
|
inboxTags: state.inboxTags,
|
||||||
|
inboxItems: [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
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:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
||||||
@@ -21,7 +19,8 @@ class InboxPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _InboxPageState extends State<InboxPage> {
|
class _InboxPageState extends State<InboxPage> {
|
||||||
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
|
final GlobalKey<RefreshIndicatorState> _emptyStateRefreshIndicatorKey =
|
||||||
|
GlobalKey();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -31,7 +30,7 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bloc = BlocProvider.of<InboxCubit>(context);
|
//TODO: Group by date (today, yseterday, etc.)
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(S.of(context).bottomNavInboxPageLabel),
|
title: Text(S.of(context).bottomNavInboxPageLabel),
|
||||||
@@ -39,6 +38,27 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed: () => Navigator.pop(context),
|
||||||
),
|
),
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: Size.fromHeight(14),
|
||||||
|
child: BlocBuilder<InboxCubit, InboxState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8.0),
|
||||||
|
child: ColoredBox(
|
||||||
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
child: Text(
|
||||||
|
'${state.inboxItems.length} unseen',
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
style: Theme.of(context).textTheme.caption,
|
||||||
|
).padded(const EdgeInsets.symmetric(horizontal: 4.0)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).padded(const EdgeInsets.symmetric(horizontal: 8.0)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@@ -47,8 +67,8 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
icon: const Icon(Icons.done_all),
|
icon: const Icon(Icons.done_all),
|
||||||
onPressed: state.isLoaded && state.inboxItems.isNotEmpty
|
onPressed: state.isLoaded && state.inboxItems.isNotEmpty
|
||||||
? () => _onMarkAllAsSeen(
|
? () => _onMarkAllAsSeen(
|
||||||
bloc.state.inboxItems,
|
state.inboxItems,
|
||||||
bloc.state.inboxTags,
|
state.inboxTags,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
@@ -61,10 +81,26 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.inboxItems.isEmpty) {
|
if (state.inboxItems.isEmpty) {
|
||||||
return Text(
|
return RefreshIndicator(
|
||||||
"You do not have new documents in your inbox.",
|
key: _emptyStateRefreshIndicatorKey,
|
||||||
textAlign: TextAlign.center,
|
onRefresh: () =>
|
||||||
).padded();
|
BlocProvider.of<InboxCubit>(context).reloadInbox(),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('You do not have unseen documents.'),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () =>
|
||||||
|
_emptyStateRefreshIndicatorKey.currentState?.show(),
|
||||||
|
child: Text('Refresh'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () => BlocProvider.of<InboxCubit>(context).reloadInbox(),
|
onRefresh: () => BlocProvider.of<InboxCubit>(context).reloadInbox(),
|
||||||
@@ -84,10 +120,9 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: AnimatedList(
|
child: ListView.builder(
|
||||||
key: _listKey,
|
itemCount: state.inboxItems.length,
|
||||||
initialItemCount: state.inboxItems.length,
|
itemBuilder: (context, index) {
|
||||||
itemBuilder: (context, index, animation) {
|
|
||||||
final doc = state.inboxItems.elementAt(index);
|
final doc = state.inboxItems.elementAt(index);
|
||||||
return _buildListItem(context, doc);
|
return _buildListItem(context, doc);
|
||||||
},
|
},
|
||||||
@@ -108,11 +143,11 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.done,
|
Icons.done_all,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
).padded(),
|
).padded(),
|
||||||
Text(
|
Text(
|
||||||
'Mark as read', //TODO: INTL
|
'Mark as seen', //TODO: INTL
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
@@ -120,57 +155,49 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
],
|
],
|
||||||
).padded(),
|
).padded(),
|
||||||
confirmDismiss: (_) => _onItemDismissed(doc),
|
confirmDismiss: (_) => _onItemDismissed(doc),
|
||||||
key: ObjectKey(doc.id),
|
key: UniqueKey(),
|
||||||
child: DocumentInboxItem(document: doc),
|
child: DocumentInboxItem(document: doc),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSlideAnimation(
|
|
||||||
BuildContext context,
|
|
||||||
animation,
|
|
||||||
Widget child,
|
|
||||||
) {
|
|
||||||
return SlideTransition(
|
|
||||||
position: Tween<Offset>(
|
|
||||||
begin: const Offset(-1, 0),
|
|
||||||
end: Offset.zero,
|
|
||||||
).animate(animation),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onMarkAllAsSeen(
|
Future<void> _onMarkAllAsSeen(
|
||||||
Iterable<DocumentModel> documents,
|
Iterable<DocumentModel> documents,
|
||||||
Iterable<int> inboxTags,
|
Iterable<int> inboxTags,
|
||||||
) async {
|
) async {
|
||||||
for (int i = documents.length - 1; i >= 0; i--) {
|
final isActionConfirmed = await showDialog(
|
||||||
final doc = documents.elementAt(i);
|
context: context,
|
||||||
_listKey.currentState?.removeItem(
|
builder: (context) => AlertDialog(
|
||||||
0,
|
title: Text('Confirm action'),
|
||||||
(context, animation) => _buildSlideAnimation(
|
content: Text(
|
||||||
context,
|
'Are you sure you want to mark all documents as seen? This will perform a bulk edit operation removing all inbox tags from the documents.\nThis action is not reversible! Are you sure you want to continue?',
|
||||||
animation,
|
),
|
||||||
_buildListItem(context, doc),
|
actions: [
|
||||||
),
|
TextButton(
|
||||||
);
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
await Future.delayed(const Duration(milliseconds: 75));
|
child: Text(S.of(context).genericActionCancelLabel),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
|
child: Text(S.of(context).genericActionOkLabel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
) ??
|
||||||
|
false;
|
||||||
|
if (isActionConfirmed) {
|
||||||
|
await BlocProvider.of<InboxCubit>(context).clearInbox();
|
||||||
}
|
}
|
||||||
await BlocProvider.of<DocumentsCubit>(context)
|
|
||||||
.bulkEditTags(documents, removeTags: inboxTags);
|
|
||||||
BlocProvider.of<PaperlessStatisticsCubit>(context).resetInboxCount();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _onItemDismissed(DocumentModel doc) async {
|
Future<bool> _onItemDismissed(DocumentModel doc) async {
|
||||||
try {
|
try {
|
||||||
final removedTags =
|
final removedTags =
|
||||||
await BlocProvider.of<InboxCubit>(context).remove(doc);
|
await BlocProvider.of<InboxCubit>(context).remove(doc);
|
||||||
BlocProvider.of<PaperlessStatisticsCubit>(context).decrementInboxCount();
|
|
||||||
showSnackBar(
|
showSnackBar(
|
||||||
context,
|
context,
|
||||||
'Document removed from inbox.', //TODO: INTL
|
'Document removed from inbox.', //TODO: INTL
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: 'UNDO', //TODO: INTL
|
label: 'UNDO', //TODO: INTL
|
||||||
textColor: Theme.of(context).colorScheme.primary,
|
|
||||||
onPressed: () => _onUndoMarkAsSeen(doc, removedTags),
|
onPressed: () => _onUndoMarkAsSeen(doc, removedTags),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -194,7 +221,6 @@ class _InboxPageState extends State<InboxPage> {
|
|||||||
try {
|
try {
|
||||||
await BlocProvider.of<InboxCubit>(context)
|
await BlocProvider.of<InboxCubit>(context)
|
||||||
.undoRemove(document, removedTags);
|
.undoRemove(document, removedTags);
|
||||||
BlocProvider.of<PaperlessStatisticsCubit>(context).incrementInboxCount();
|
|
||||||
} on ErrorMessage catch (error, stackTrace) {
|
} on ErrorMessage catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
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:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/document_details_page.dart';
|
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.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';
|
||||||
@@ -37,6 +40,8 @@ class DocumentInboxItem extends StatelessWidget {
|
|||||||
tagIds: document.tags,
|
tagIds: document.tags,
|
||||||
isMultiLine: false,
|
isMultiLine: false,
|
||||||
isClickable: false,
|
isClickable: false,
|
||||||
|
isSelectedPredicate: (_) => false,
|
||||||
|
onTagSelected: (_) {},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -45,11 +50,14 @@ class DocumentInboxItem extends StatelessWidget {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => GlobalStateBlocProvider(
|
builder: (_) => GlobalStateBlocProvider(
|
||||||
additionalProviders: [
|
additionalProviders: [
|
||||||
BlocProvider.value(
|
BlocProvider<DocumentDetailsCubit>(
|
||||||
value: BlocProvider.of<DocumentsCubit>(context)),
|
create: (context) => DocumentDetailsCubit(
|
||||||
|
getIt<DocumentRepository>(),
|
||||||
|
document,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: DocumentDetailsPage(
|
child: const DocumentDetailsPage(
|
||||||
documentId: document.id,
|
|
||||||
allowEdit: false,
|
allowEdit: false,
|
||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_cubit.dart';
|
||||||
|
|
||||||
class GlobalStateBlocProvider extends StatelessWidget {
|
class GlobalStateBlocProvider extends StatelessWidget {
|
||||||
final List<BlocProvider> additionalProviders;
|
final List<BlocProvider> additionalProviders;
|
||||||
|
|||||||
@@ -1,7 +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:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||||
@@ -24,8 +23,6 @@ class EditTagPage extends StatelessWidget {
|
|||||||
label: tag,
|
label: tag,
|
||||||
onSubmit: (tag) async {
|
onSubmit: (tag) async {
|
||||||
await BlocProvider.of<TagCubit>(context).replace(tag);
|
await BlocProvider.of<TagCubit>(context).replace(tag);
|
||||||
//If inbox property was added/removed from tag, the number of documents in inbox may increase/decrease.
|
|
||||||
BlocProvider.of<PaperlessStatisticsCubit>(context).updateStatistics();
|
|
||||||
},
|
},
|
||||||
onDelete: (tag) => _onDelete(tag, context),
|
onDelete: (tag) => _onDelete(tag, context),
|
||||||
fromJson: Tag.fromJson,
|
fromJson: Tag.fromJson,
|
||||||
|
|||||||
@@ -1,80 +1,43 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart';
|
import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart';
|
||||||
import 'package:paperless_mobile/util.dart';
|
|
||||||
|
|
||||||
class TagWidget extends StatelessWidget {
|
class TagWidget extends StatelessWidget {
|
||||||
final Tag tag;
|
final Tag tag;
|
||||||
final void Function()? afterTagTapped;
|
final VoidCallback? afterTagTapped;
|
||||||
|
final VoidCallback onSelected;
|
||||||
|
final bool isSelected;
|
||||||
final bool isClickable;
|
final bool isClickable;
|
||||||
|
|
||||||
const TagWidget({
|
const TagWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.tag,
|
required this.tag,
|
||||||
required this.afterTagTapped,
|
required this.afterTagTapped,
|
||||||
this.isClickable = true,
|
this.isClickable = true,
|
||||||
|
required this.onSelected,
|
||||||
|
required this.isSelected,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(right: 4.0),
|
padding: const EdgeInsets.only(right: 4.0),
|
||||||
child: BlocBuilder<DocumentsCubit, DocumentsState>(
|
child: AbsorbPointer(
|
||||||
builder: (context, state) {
|
absorbing: !isClickable,
|
||||||
final isIdsQuery = state.filter.tags is IdsTagsQuery;
|
child: FilterChip(
|
||||||
return FilterChip(
|
selected: isSelected,
|
||||||
selected: isIdsQuery
|
selectedColor: tag.color,
|
||||||
? (state.filter.tags as IdsTagsQuery)
|
onSelected: (_) => onSelected(),
|
||||||
.includedIds
|
visualDensity: const VisualDensity(vertical: -2),
|
||||||
.contains(tag.id)
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
: false,
|
label: Text(
|
||||||
selectedColor: tag.color,
|
tag.name,
|
||||||
onSelected: (_) => _addTagToFilter(context),
|
style: TextStyle(color: tag.textColor),
|
||||||
visualDensity: const VisualDensity(vertical: -2),
|
),
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
checkmarkColor: tag.textColor,
|
||||||
label: Text(
|
backgroundColor: tag.color,
|
||||||
tag.name,
|
side: BorderSide.none,
|
||||||
style: TextStyle(color: tag.textColor),
|
),
|
||||||
),
|
|
||||||
checkmarkColor: tag.textColor,
|
|
||||||
backgroundColor: tag.color,
|
|
||||||
side: BorderSide.none,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addTagToFilter(BuildContext context) {
|
|
||||||
if (!isClickable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final cubit = BlocProvider.of<DocumentsCubit>(context);
|
|
||||||
try {
|
|
||||||
final tagsQuery = cubit.state.filter.tags is IdsTagsQuery
|
|
||||||
? cubit.state.filter.tags as IdsTagsQuery
|
|
||||||
: const IdsTagsQuery();
|
|
||||||
if (tagsQuery.includedIds.contains(tag.id)) {
|
|
||||||
cubit.updateCurrentFilter(
|
|
||||||
(filter) => filter.copyWith(
|
|
||||||
tags: tagsQuery.withIdsRemoved([tag.id!]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
cubit.updateCurrentFilter(
|
|
||||||
(filter) => filter.copyWith(
|
|
||||||
tags: tagsQuery.withIdQueriesAdded([IncludeTagIdQuery(tag.id!)]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (afterTagTapped != null) {
|
|
||||||
afterTagTapped!();
|
|
||||||
}
|
|
||||||
} on ErrorMessage catch (error, stackTrace) {
|
|
||||||
showErrorMessage(context, error, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
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:paperless_mobile/features/labels/model/label_state.dart';
|
import 'package:paperless_mobile/features/labels/model/label_state.dart';
|
||||||
@@ -10,8 +8,10 @@ import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.da
|
|||||||
class TagsWidget extends StatefulWidget {
|
class TagsWidget extends StatefulWidget {
|
||||||
final Iterable<int> tagIds;
|
final Iterable<int> tagIds;
|
||||||
final bool isMultiLine;
|
final bool isMultiLine;
|
||||||
final void Function()? afterTagTapped;
|
final VoidCallback? afterTagTapped;
|
||||||
|
final void Function(int tagId) onTagSelected;
|
||||||
final bool isClickable;
|
final bool isClickable;
|
||||||
|
final bool Function(int id) isSelectedPredicate;
|
||||||
|
|
||||||
const TagsWidget({
|
const TagsWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -19,6 +19,8 @@ class TagsWidget extends StatefulWidget {
|
|||||||
this.afterTagTapped,
|
this.afterTagTapped,
|
||||||
this.isMultiLine = true,
|
this.isMultiLine = true,
|
||||||
this.isClickable = true,
|
this.isClickable = true,
|
||||||
|
required this.isSelectedPredicate,
|
||||||
|
required this.onTagSelected,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -37,6 +39,8 @@ class _TagsWidgetState extends State<TagsWidget> {
|
|||||||
tag: state.getLabel(id)!,
|
tag: state.getLabel(id)!,
|
||||||
afterTagTapped: widget.afterTagTapped,
|
afterTagTapped: widget.afterTagTapped,
|
||||||
isClickable: widget.isClickable,
|
isClickable: widget.isClickable,
|
||||||
|
isSelected: widget.isSelectedPredicate(id),
|
||||||
|
onSelected: () => widget.onTagSelected(id),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
@@ -218,7 +217,6 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
builder: (_) => GlobalStateBlocProvider(
|
builder: (_) => GlobalStateBlocProvider(
|
||||||
additionalProviders: [
|
additionalProviders: [
|
||||||
BlocProvider.value(value: BlocProvider.of<DocumentsCubit>(context)),
|
BlocProvider.value(value: BlocProvider.of<DocumentsCubit>(context)),
|
||||||
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
|
|
||||||
],
|
],
|
||||||
child: EditTagPage(tag: tag),
|
child: EditTagPage(tag: tag),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
||||||
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/pages/document_details_page.dart';
|
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||||
@@ -68,15 +71,15 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
|
|||||||
builder: (ctxt) => GlobalStateBlocProvider(
|
builder: (ctxt) => GlobalStateBlocProvider(
|
||||||
additionalProviders: [
|
additionalProviders: [
|
||||||
BlocProvider.value(
|
BlocProvider.value(
|
||||||
value: BlocProvider.of<DocumentsCubit>(
|
value: DocumentDetailsCubit(
|
||||||
context,
|
getIt<DocumentRepository>(),
|
||||||
|
document,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: DocumentDetailsPage(
|
child: const DocumentDetailsPage(
|
||||||
documentId: doc.id,
|
|
||||||
allowEdit: false,
|
|
||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
|
allowEdit: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -84,6 +87,8 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
|
|||||||
},
|
},
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
isAtLeastOneSelected: false,
|
isAtLeastOneSelected: false,
|
||||||
|
isTagSelectedPredicate: (_) => false,
|
||||||
|
onTagSelected: (int tag) {},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/saved_view_state.dart';
|
|
||||||
import 'package:paperless_mobile/features/documents/model/saved_view.model.dart';
|
import 'package:paperless_mobile/features/documents/model/saved_view.model.dart';
|
||||||
import 'package:paperless_mobile/features/documents/repository/saved_views_repository.dart';
|
import 'package:paperless_mobile/features/documents/repository/saved_views_repository.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:paperless_mobile/features/saved_view/bloc/saved_view_state.dart';
|
||||||
|
|
||||||
@singleton
|
@singleton
|
||||||
class SavedViewCubit extends Cubit<SavedViewState> {
|
class SavedViewCubit extends Cubit<SavedViewState> {
|
||||||
@@ -3,7 +3,6 @@ import 'dart:typed_data';
|
|||||||
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:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
import 'package:paperless_mobile/core/type/types.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:flutter/foundation.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:mime/mime.dart';
|
import 'package:mime/mime.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/core/global/constants.dart';
|
import 'package:paperless_mobile/core/global/constants.dart';
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
@@ -136,9 +135,6 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
],
|
],
|
||||||
child: DocumentUploadPage(
|
child: DocumentUploadPage(
|
||||||
fileBytes: bytes,
|
fileBytes: bytes,
|
||||||
onSuccessfullyConsumed: (_) =>
|
|
||||||
BlocProvider.of<PaperlessStatisticsCubit>(context)
|
|
||||||
.updateStatistics(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -259,9 +255,6 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
child: DocumentUploadPage(
|
child: DocumentUploadPage(
|
||||||
filename: filename,
|
filename: filename,
|
||||||
fileBytes: fileBytes,
|
fileBytes: fileBytes,
|
||||||
onSuccessfullyConsumed: (_) =>
|
|
||||||
BlocProvider.of<PaperlessStatisticsCubit>(context)
|
|
||||||
.updateStatistics(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -188,7 +188,7 @@
|
|||||||
"editLabelPageDeletionDialogText": "Dieser Kennzeichner wird von Dokumenten referenziert. Durch das Löschen dieses Kennzeichners werden alle Referenzen entfernt. Fortfahren?",
|
"editLabelPageDeletionDialogText": "Dieser Kennzeichner wird von Dokumenten referenziert. Durch das Löschen dieses Kennzeichners werden alle Referenzen entfernt. Fortfahren?",
|
||||||
"settingsPageStorageSettingsLabel": "Speicher",
|
"settingsPageStorageSettingsLabel": "Speicher",
|
||||||
"settingsPageStorageSettingsDescriptionText": "Dateien und Speicherplatz verwalten",
|
"settingsPageStorageSettingsDescriptionText": "Dateien und Speicherplatz verwalten",
|
||||||
"documentUpdateErrorMessage": "Dokument erfolgreich aktualisiert.",
|
"documentUpdateSuccessMessage": "Dokument erfolgreich aktualisiert.",
|
||||||
"errorMessageMissingClientCertificate": "Ein Client Zerfitikat wurde erwartet, aber nicht gesendet. Bitte konfiguriere ein gültiges Zertifikat.",
|
"errorMessageMissingClientCertificate": "Ein Client Zerfitikat wurde erwartet, aber nicht gesendet. Bitte konfiguriere ein gültiges Zertifikat.",
|
||||||
"serverInformationPaperlessVersionText": "Paperless Server-Version",
|
"serverInformationPaperlessVersionText": "Paperless Server-Version",
|
||||||
"errorReportLabel": "MELDEN",
|
"errorReportLabel": "MELDEN",
|
||||||
|
|||||||
@@ -189,7 +189,7 @@
|
|||||||
"editLabelPageDeletionDialogText": "This label contains references to other documents. By deleting this label, all references will be removed. Continue?",
|
"editLabelPageDeletionDialogText": "This label contains references to other documents. By deleting this label, all references will be removed. Continue?",
|
||||||
"settingsPageStorageSettingsLabel": "Storage",
|
"settingsPageStorageSettingsLabel": "Storage",
|
||||||
"settingsPageStorageSettingsDescriptionText": "Manage files and storage space",
|
"settingsPageStorageSettingsDescriptionText": "Manage files and storage space",
|
||||||
"documentUpdateErrorMessage": "Document successfully updated.",
|
"documentUpdateSuccessMessage": "Document successfully updated.",
|
||||||
"errorMessageMissingClientCertificate": "A client certificate was expected but not sent. Please provide a valid client certificate.",
|
"errorMessageMissingClientCertificate": "A client certificate was expected but not sent. Please provide a valid client certificate.",
|
||||||
"serverInformationPaperlessVersionText": "Paperless server version",
|
"serverInformationPaperlessVersionText": "Paperless server version",
|
||||||
"errorReportLabel": "REPORT",
|
"errorReportLabel": "REPORT",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -11,7 +12,6 @@ import 'package:intl/intl.dart';
|
|||||||
import 'package:intl/intl_standalone.dart';
|
import 'package:intl/intl_standalone.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/global/asset_images.dart';
|
import 'package:paperless_mobile/core/global/asset_images.dart';
|
||||||
@@ -51,6 +51,13 @@ void main() async {
|
|||||||
await getIt<ApplicationSettingsCubit>().initialize();
|
await getIt<ApplicationSettingsCubit>().initialize();
|
||||||
await getIt<AuthenticationCubit>().initialize();
|
await getIt<AuthenticationCubit>().initialize();
|
||||||
|
|
||||||
|
// Preload asset images
|
||||||
|
// WARNING: This seems to bloat up the app up to almost 200mb!
|
||||||
|
// await Future.forEach<AssetImage>(
|
||||||
|
// AssetImages.values.map((e) => e.image),
|
||||||
|
// (img) => loadImage(img),
|
||||||
|
// );
|
||||||
|
|
||||||
runApp(const PaperlessMobileEntrypoint());
|
runApp(const PaperlessMobileEntrypoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +77,6 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
|||||||
BlocProvider.value(value: getIt<ConnectivityCubit>()),
|
BlocProvider.value(value: getIt<ConnectivityCubit>()),
|
||||||
BlocProvider.value(value: getIt<AuthenticationCubit>()),
|
BlocProvider.value(value: getIt<AuthenticationCubit>()),
|
||||||
BlocProvider.value(value: getIt<PaperlessServerInformationCubit>()),
|
BlocProvider.value(value: getIt<PaperlessServerInformationCubit>()),
|
||||||
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
|
|
||||||
BlocProvider.value(value: getIt<ApplicationSettingsCubit>()),
|
BlocProvider.value(value: getIt<ApplicationSettingsCubit>()),
|
||||||
],
|
],
|
||||||
child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||||
@@ -229,7 +235,6 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
if (authentication.isAuthenticated) {
|
if (authentication.isAuthenticated) {
|
||||||
return GlobalStateBlocProvider(
|
return GlobalStateBlocProvider(
|
||||||
additionalProviders: [
|
additionalProviders: [
|
||||||
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
|
|
||||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||||
],
|
],
|
||||||
child: const HomePage(),
|
child: const HomePage(),
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
@@ -120,3 +124,35 @@ String formatLocalDate(BuildContext context, DateTime dateTime) {
|
|||||||
String extractFilenameFromPath(String path) {
|
String extractFilenameFromPath(String path) {
|
||||||
return path.split(RegExp('[./]')).reversed.skip(1).first;
|
return path.split(RegExp('[./]')).reversed.skip(1).first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Taken from https://github.com/flutter/flutter/issues/26127#issuecomment-782083060
|
||||||
|
Future<void> loadImage(ImageProvider provider) {
|
||||||
|
final config = ImageConfiguration(
|
||||||
|
bundle: rootBundle,
|
||||||
|
devicePixelRatio: window.devicePixelRatio,
|
||||||
|
platform: defaultTargetPlatform,
|
||||||
|
);
|
||||||
|
final Completer<void> completer = Completer();
|
||||||
|
final ImageStream stream = provider.resolve(config);
|
||||||
|
|
||||||
|
late final ImageStreamListener listener;
|
||||||
|
|
||||||
|
listener = ImageStreamListener((ImageInfo image, bool sync) {
|
||||||
|
debugPrint("Image ${image.debugLabel} finished loading");
|
||||||
|
completer.complete();
|
||||||
|
stream.removeListener(listener);
|
||||||
|
}, onError: (dynamic exception, StackTrace? stackTrace) {
|
||||||
|
completer.complete();
|
||||||
|
stream.removeListener(listener);
|
||||||
|
FlutterError.reportError(FlutterErrorDetails(
|
||||||
|
context: ErrorDescription('image failed to load'),
|
||||||
|
library: 'image resource service',
|
||||||
|
exception: exception,
|
||||||
|
stack: stackTrace,
|
||||||
|
silent: true,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.addListener(listener);
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
name: paperless_mobile
|
name: paperless_mobile
|
||||||
description:
|
description: Application to conveniently scan and share documents with a paperless-ng
|
||||||
Application to conveniently scan and share documents with a paperless-ng
|
|
||||||
server.
|
server.
|
||||||
|
|
||||||
# The following line prevents the package from being accidentally published to
|
# The following line prevents the package from being accidentally published to
|
||||||
@@ -139,6 +138,8 @@ flutter:
|
|||||||
# see https://flutter.dev/custom-fonts/#from-packages
|
# see https://flutter.dev/custom-fonts/#from-packages
|
||||||
flutter_intl:
|
flutter_intl:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
localizely:
|
||||||
|
project_id: 84b4144d-a628-4ba6-a8d0-4f9917444057
|
||||||
|
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
image: assets/logos/paperless_logo_green.png
|
image: assets/logos/paperless_logo_green.png
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ void main() {
|
|||||||
addedDateBefore: DateTime.parse("2022-09-26"),
|
addedDateBefore: DateTime.parse("2022-09-26"),
|
||||||
addedDateAfter: DateTime.parse("2000-01-01"),
|
addedDateAfter: DateTime.parse("2000-01-01"),
|
||||||
sortField: SortField.created,
|
sortField: SortField.created,
|
||||||
sortOrder: SortOrder.ascending,
|
sortOrder: SortOrder.descending,
|
||||||
queryText: "Never gonna give you up",
|
queryText: "Never gonna give you up",
|
||||||
queryType: QueryType.extended,
|
queryType: QueryType.extended,
|
||||||
),
|
),
|
||||||
@@ -106,7 +106,7 @@ void main() {
|
|||||||
"show_on_dashboard": false,
|
"show_on_dashboard": false,
|
||||||
"show_in_sidebar": false,
|
"show_in_sidebar": false,
|
||||||
"sort_field": SortField.created.name,
|
"sort_field": SortField.created.name,
|
||||||
"sort_reverse": false,
|
"sort_reverse": true,
|
||||||
"filter_rules": [],
|
"filter_rules": [],
|
||||||
}).toDocumentFilter(),
|
}).toDocumentFilter(),
|
||||||
equals(DocumentFilter.initial),
|
equals(DocumentFilter.initial),
|
||||||
@@ -121,7 +121,7 @@ void main() {
|
|||||||
"show_on_dashboard": false,
|
"show_on_dashboard": false,
|
||||||
"show_in_sidebar": false,
|
"show_in_sidebar": false,
|
||||||
"sort_field": SortField.created.name,
|
"sort_field": SortField.created.name,
|
||||||
"sort_reverse": false,
|
"sort_reverse": true,
|
||||||
"filter_rules": [
|
"filter_rules": [
|
||||||
{
|
{
|
||||||
'rule_type': FilterRule.correspondentRule,
|
'rule_type': FilterRule.correspondentRule,
|
||||||
@@ -185,7 +185,7 @@ void main() {
|
|||||||
showOnDashboard: false,
|
showOnDashboard: false,
|
||||||
showInSidebar: false,
|
showInSidebar: false,
|
||||||
sortField: SortField.added,
|
sortField: SortField.added,
|
||||||
sortReverse: true,
|
sortReverse: false,
|
||||||
filterRules: [
|
filterRules: [
|
||||||
FilterRule(FilterRule.correspondentRule, "1"),
|
FilterRule(FilterRule.correspondentRule, "1"),
|
||||||
FilterRule(FilterRule.documentTypeRule, "2"),
|
FilterRule(FilterRule.documentTypeRule, "2"),
|
||||||
@@ -232,7 +232,7 @@ void main() {
|
|||||||
showOnDashboard: false,
|
showOnDashboard: false,
|
||||||
showInSidebar: false,
|
showInSidebar: false,
|
||||||
sortField: SortField.created,
|
sortField: SortField.created,
|
||||||
sortReverse: false,
|
sortReverse: true,
|
||||||
filterRules: [],
|
filterRules: [],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -248,7 +248,7 @@ void main() {
|
|||||||
storagePath: StoragePathQuery.notAssigned(),
|
storagePath: StoragePathQuery.notAssigned(),
|
||||||
tags: OnlyNotAssignedTagsQuery(),
|
tags: OnlyNotAssignedTagsQuery(),
|
||||||
sortField: SortField.created,
|
sortField: SortField.created,
|
||||||
sortOrder: SortOrder.descending,
|
sortOrder: SortOrder.ascending,
|
||||||
),
|
),
|
||||||
name: "test_name",
|
name: "test_name",
|
||||||
showInSidebar: false,
|
showInSidebar: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user