Changed saved views handling, changed repository structure with automatic persistence.

This commit is contained in:
Anton Stubenbord
2023-01-08 00:01:04 +01:00
parent 23bcb355b1
commit 3c6c4e63d7
74 changed files with 1374 additions and 863 deletions

View File

@@ -6,7 +6,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart';
@@ -56,9 +58,16 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
floatingActionButton: widget.allowEdit
? BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
builder: (context, state) {
return FloatingActionButton(
child: const Icon(Icons.edit),
onPressed: () => _onEdit(state.document),
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivityState) {
if (!connectivityState.isConnected) {
return Container();
}
return FloatingActionButton(
child: const Icon(Icons.edit),
onPressed: () => _onEdit(state.document),
);
},
);
},
)
@@ -67,27 +76,37 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
builder: (context, state) {
return BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
IconButton(
icon: const Icon(Icons.delete),
onPressed: widget.allowEdit
? () => _onDelete(state.document)
: null,
).paddedSymmetrically(horizontal: 4),
DocumentDownloadButton(
document: state.document,
),
IconButton(
icon: const Icon(Icons.open_in_new),
onPressed: () => _onOpen(state.document),
).paddedOnly(right: 4.0),
IconButton(
icon: const Icon(Icons.share),
onPressed: () => _onShare(state.document),
),
],
child: BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivityState) {
final isConnected = connectivityState.isConnected;
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
IconButton(
icon: const Icon(Icons.delete),
onPressed: widget.allowEdit && isConnected
? () => _onDelete(state.document)
: null,
).paddedSymmetrically(horizontal: 4),
DocumentDownloadButton(
document: state.document,
enabled: isConnected,
),
IconButton(
icon: const Icon(Icons.open_in_new),
onPressed: isConnected
? () => _onOpen(state.document)
: null,
).paddedOnly(right: 4.0),
IconButton(
icon: const Icon(Icons.share),
onPressed: isConnected
? () => _onShare(state.document)
: null,
),
],
);
},
),
);
},
@@ -208,55 +227,69 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
}
Widget _buildDocumentMetaDataView(DocumentModel document) {
return FutureBuilder<DocumentMetaData>(
future: context.read<PaperlessDocumentsApi>().getMetaData(document),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, state) {
if (!state.isConnected) {
return const Center(
child: OfflineWidget(),
);
}
final meta = snapshot.data!;
return ListView(
children: [
_DetailsItem.text(DateFormat().format(document.modified),
label: S.of(context).documentModifiedPropertyLabel,
context: context)
.paddedOnly(bottom: 16),
_DetailsItem.text(DateFormat().format(document.added),
label: S.of(context).documentAddedPropertyLabel,
context: context)
.paddedSymmetrically(vertical: 16),
_DetailsItem(
label: S.of(context).documentArchiveSerialNumberPropertyLongLabel,
content: document.archiveSerialNumber != null
? Text(document.archiveSerialNumber.toString())
: OutlinedButton(
child: Text(S
.of(context)
.documentDetailsPageAssignAsnButtonLabel),
onPressed:
widget.allowEdit ? () => _assignAsn(document) : null,
),
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.mediaFilename,
context: context,
label: S.of(context).documentMetaDataMediaFilenamePropertyLabel,
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.originalChecksum,
context: context,
label: S.of(context).documentMetaDataChecksumLabel,
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(formatBytes(meta.originalSize, 2),
label: S.of(context).documentMetaDataOriginalFileSizeLabel,
context: context)
.paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.originalMimeType,
label: S.of(context).documentMetaDataOriginalMimeTypeLabel,
context: context,
).paddedSymmetrically(vertical: 16),
],
return FutureBuilder<DocumentMetaData>(
future: context.read<PaperlessDocumentsApi>().getMetaData(document),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final meta = snapshot.data!;
return ListView(
children: [
_DetailsItem.text(DateFormat().format(document.modified),
label: S.of(context).documentModifiedPropertyLabel,
context: context)
.paddedOnly(bottom: 16),
_DetailsItem.text(DateFormat().format(document.added),
label: S.of(context).documentAddedPropertyLabel,
context: context)
.paddedSymmetrically(vertical: 16),
_DetailsItem(
label: S
.of(context)
.documentArchiveSerialNumberPropertyLongLabel,
content: document.archiveSerialNumber != null
? Text(document.archiveSerialNumber.toString())
: OutlinedButton(
child: Text(S
.of(context)
.documentDetailsPageAssignAsnButtonLabel),
onPressed: widget.allowEdit
? () => _assignAsn(document)
: null,
),
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.mediaFilename,
context: context,
label:
S.of(context).documentMetaDataMediaFilenamePropertyLabel,
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.originalChecksum,
context: context,
label: S.of(context).documentMetaDataChecksumLabel,
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(formatBytes(meta.originalSize, 2),
label:
S.of(context).documentMetaDataOriginalFileSizeLabel,
context: context)
.paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.originalMimeType,
label: S.of(context).documentMetaDataOriginalMimeTypeLabel,
context: context,
).paddedSymmetrically(vertical: 16),
],
);
},
);
},
);

View File

@@ -10,7 +10,12 @@ import 'package:provider/provider.dart';
class DocumentDownloadButton extends StatefulWidget {
final DocumentModel? document;
const DocumentDownloadButton({super.key, required this.document});
final bool enabled;
const DocumentDownloadButton({
super.key,
required this.document,
this.enabled = true,
});
@override
State<DocumentDownloadButton> createState() => _DocumentDownloadButtonState();
@@ -29,7 +34,7 @@ class _DocumentDownloadButtonState extends State<DocumentDownloadButton> {
width: 16,
)
: const Icon(Icons.download),
onPressed: Platform.isAndroid && widget.document != null
onPressed: Platform.isAndroid && widget.document != null && widget.enabled
? () => _onDownload(widget.document!)
: null,
).paddedOnly(right: 4);

View File

@@ -5,6 +5,9 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/core/store/local_vault.dart';
part 'document_upload_state.dart';
@@ -12,18 +15,22 @@ part 'document_upload_state.dart';
class DocumentUploadCubit extends Cubit<DocumentUploadState> {
final PaperlessDocumentsApi _documentApi;
final LabelRepository<Tag> _tagRepository;
final LabelRepository<Correspondent> _correspondentRepository;
final LabelRepository<DocumentType> _documentTypeRepository;
final LabelRepository<Tag, TagRepositoryState> _tagRepository;
final LabelRepository<Correspondent, CorrespondentRepositoryState>
_correspondentRepository;
final LabelRepository<DocumentType, DocumentTypeRepositoryState>
_documentTypeRepository;
final List<StreamSubscription> _subs = [];
DocumentUploadCubit({
required LocalVault localVault,
required PaperlessDocumentsApi documentApi,
required LabelRepository<Tag> tagRepository,
required LabelRepository<Correspondent> correspondentRepository,
required LabelRepository<DocumentType> documentTypeRepository,
required LabelRepository<Tag, TagRepositoryState> tagRepository,
required LabelRepository<Correspondent, CorrespondentRepositoryState>
correspondentRepository,
required LabelRepository<DocumentType, DocumentTypeRepositoryState>
documentTypeRepository,
}) : _documentApi = documentApi,
_tagRepository = tagRepository,
_correspondentRepository = correspondentRepository,
@@ -36,13 +43,15 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
),
) {
_subs.add(_tagRepository.values.listen(
(tags) => emit(state.copyWith(tags: tags)),
(tags) => emit(state.copyWith(tags: tags?.values)),
));
_subs.add(_correspondentRepository.values.listen(
(correspondents) => emit(state.copyWith(correspondents: correspondents)),
(correspondents) =>
emit(state.copyWith(correspondents: correspondents?.values)),
));
_subs.add(_documentTypeRepository.values.listen(
(documentTypes) => emit(state.copyWith(documentTypes: documentTypes)),
(documentTypes) =>
emit(state.copyWith(documentTypes: documentTypes?.values)),
));
}

View File

@@ -8,6 +8,8 @@ import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/type/types.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
@@ -169,8 +171,9 @@ class _DocumentUploadPreparationPageState
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialName) =>
RepositoryProvider(
create: (context) =>
context.read<LabelRepository<DocumentType>>(),
create: (context) => context.read<
LabelRepository<DocumentType,
DocumentTypeRepositoryState>>(),
child: AddDocumentTypePage(initialName: initialName),
),
textFieldLabel:
@@ -184,8 +187,9 @@ class _DocumentUploadPreparationPageState
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialName) =>
RepositoryProvider(
create: (context) =>
context.read<LabelRepository<Correspondent>>(),
create: (context) => context.read<
LabelRepository<Correspondent,
CorrespondentRepositoryState>>(),
child: AddCorrespondentPage(initialName: initialName),
),
textFieldLabel:

View File

@@ -1,15 +1,22 @@
import 'dart:async';
import 'dart:developer';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
class DocumentsCubit extends HydratedCubit<DocumentsState> {
class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
final PaperlessDocumentsApi _api;
final SavedViewRepository _savedViewRepository;
DocumentsCubit(this._api) : super(const DocumentsState());
DocumentsCubit(this._api, this._savedViewRepository)
: super(const DocumentsState()) {
hydrate();
}
Future<void> bulkRemove(List<DocumentModel> documents) async {
log("[DocumentsCubit] bulkRemove");
await _api.bulkAction(
BulkDeleteAction(documents.map((doc) => doc.id)),
);
@@ -21,6 +28,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> {
Iterable<int> addTags = const [],
Iterable<int> removeTags = const [],
}) async {
log("[DocumentsCubit] bulkEditTags");
await _api.bulkAction(BulkModifyTagsAction(
documents.map((doc) => doc.id),
addTags: addTags,
@@ -33,6 +41,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> {
DocumentModel document, [
bool updateRemote = true,
]) async {
log("[DocumentsCubit] update");
if (updateRemote) {
await _api.update(document);
}
@@ -40,6 +49,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> {
}
Future<void> load() async {
log("[DocumentsCubit] load");
emit(state.copyWith(isLoading: true));
try {
final result = await _api.find(state.filter);
@@ -48,33 +58,23 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> {
hasLoaded: true,
value: [...state.value, result],
));
} catch (err) {
} finally {
emit(state.copyWith(isLoading: false));
rethrow;
}
}
Future<void> reload() async {
log("[DocumentsCubit] reload");
emit(state.copyWith(isLoading: true));
try {
if (state.currentPageNumber >= 5) {
return _bulkReloadDocuments();
}
var newPages = <PagedSearchResult<DocumentModel>>[];
for (final page in state.value) {
final result =
await _api.find(state.filter.copyWith(page: page.pageKey));
newPages.add(result);
}
emit(DocumentsState(
final result = await _api.find(state.filter.copyWith(page: 1));
emit(state.copyWith(
hasLoaded: true,
value: newPages,
filter: state.filter,
value: [result],
isLoading: false,
));
} catch (err) {
} finally {
emit(state.copyWith(isLoading: false));
rethrow;
}
}
@@ -93,13 +93,13 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> {
filter: state.filter,
isLoading: false,
));
} catch (err) {
} finally {
emit(state.copyWith(isLoading: false));
rethrow;
}
}
Future<void> loadMore() async {
log("[DocumentsCubit] loadMore");
if (state.isLastPageLoaded) {
return;
}
@@ -115,21 +115,22 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> {
isLoading: false,
),
);
} catch (err) {
} finally {
emit(state.copyWith(isLoading: false));
rethrow;
}
}
///
/// Update filter state and automatically reload documents. Always resets page to 1.
/// Use [DocumentsCubit.loadMore] to load more data.
/// Updates document filter and automatically reloads documents. Always resets page to 1.
/// Use [loadMore] to load more data.
Future<void> updateFilter({
final DocumentFilter filter = DocumentFilter.initial,
}) async {
log("[DocumentsCubit] updateFilter");
try {
emit(state.copyWith(isLoading: true));
final result = await _api.find(filter.copyWith(page: 1));
emit(
DocumentsState(
filter: filter,
@@ -138,13 +139,13 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> {
isLoading: false,
),
);
} catch (err) {
} finally {
emit(state.copyWith(isLoading: false));
rethrow;
}
}
Future<void> resetFilter() {
log("[DocumentsCubit] resetFilter");
final filter = DocumentFilter.initial.copyWith(
sortField: state.filter.sortField,
sortOrder: state.filter.sortOrder,
@@ -161,6 +162,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> {
updateFilter(filter: transformFn(state.filter));
void toggleDocumentSelection(DocumentModel model) {
log("[DocumentsCubit] toggleSelection");
if (state.selection.contains(model)) {
emit(
state.copyWith(
@@ -177,16 +179,44 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> {
}
void resetSelection() {
log("[DocumentsCubit] resetSelection");
emit(state.copyWith(selection: []));
}
void reset() {
log("[DocumentsCubit] reset");
emit(const DocumentsState());
}
Future<void> selectView(int id) async {
emit(state.copyWith(isLoading: true));
try {
final filter =
_savedViewRepository.current?.values[id]?.toDocumentFilter();
if (filter == null) {
return;
}
final results = await _api.find(filter.copyWith(page: 1));
emit(
DocumentsState(
filter: filter,
hasLoaded: true,
isLoading: false,
selectedSavedViewId: id,
value: [results],
),
);
} finally {
emit(state.copyWith(isLoading: false));
}
}
void unselectView() {
emit(state.copyWith(selectedSavedViewId: null));
}
@override
DocumentsState? fromJson(Map<String, dynamic> json) {
log(json['filter'].toString());
return DocumentsState.fromJson(json);
}

View File

@@ -4,12 +4,12 @@ import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
@JsonSerializable()
class DocumentsState extends Equatable {
final bool isLoading;
final bool hasLoaded;
final DocumentFilter filter;
final List<PagedSearchResult<DocumentModel>> value;
final int? selectedSavedViewId;
@JsonKey(ignore: true)
final List<DocumentModel> selection;
@@ -20,6 +20,7 @@ class DocumentsState extends Equatable {
this.value = const [],
this.filter = const DocumentFilter(),
this.selection = const [],
this.selectedSavedViewId,
});
int get currentPageNumber {
@@ -69,6 +70,7 @@ class DocumentsState extends Equatable {
List<PagedSearchResult<DocumentModel>>? value,
DocumentFilter? filter,
List<DocumentModel>? selection,
int? selectedSavedViewId,
}) {
return DocumentsState(
hasLoaded: hasLoaded ?? this.hasLoaded,
@@ -76,17 +78,26 @@ class DocumentsState extends Equatable {
value: value ?? this.value,
filter: filter ?? this.filter,
selection: selection ?? this.selection,
selectedSavedViewId: selectedSavedViewId ?? this.selectedSavedViewId,
);
}
@override
List<Object?> get props => [hasLoaded, filter, value, selection, isLoading];
List<Object?> get props => [
hasLoaded,
filter,
value,
selection,
isLoading,
selectedSavedViewId,
];
Map<String, dynamic> toJson() {
final json = {
'hasLoaded': hasLoaded,
'isLoading': isLoading,
'filter': filter.toJson(),
'selectedSavedViewId': selectedSavedViewId,
'value':
value.map((e) => e.toJson(DocumentModelJsonConverter())).toList(),
};
@@ -97,9 +108,10 @@ class DocumentsState extends Equatable {
return DocumentsState(
hasLoaded: json['hasLoaded'],
isLoading: json['isLoading'],
selectedSavedViewId: json['selectedSavedViewId'],
value: (json['value'] as List<dynamic>)
.map((e) =>
PagedSearchResult.fromJson(e, DocumentModelJsonConverter()))
PagedSearchResult.fromJsonT(e, DocumentModelJsonConverter()))
.toList(),
filter: DocumentFilter.fromJson(json['filter']),
);

View File

@@ -7,6 +7,9 @@ import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/edit_document/cubit/edit_document_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
@@ -80,7 +83,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
state.document.storagePath, state.storagePaths)
.padded(),
TagFormField(
initialValue: IdsTagsQuery.included(state.document.tags),
initialValue:
IdsTagsQuery.included(state.document.tags.toList()),
notAssignedSelectable: false,
anyAssignedSelectable: false,
excludeAllowed: false,
@@ -100,7 +104,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
notAssignedSelectable: false,
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider(
create: (context) => context.read<LabelRepository<StoragePath>>(),
create: (context) => context
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
child: AddStoragePathPage(initalValue: initialValue),
),
textFieldLabel: S.of(context).documentStoragePathPropertyLabel,
@@ -117,7 +122,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
notAssignedSelectable: false,
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (initialValue) => RepositoryProvider(
create: (context) => context.read<LabelRepository<Correspondent>>(),
create: (context) => context.read<
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
child: AddCorrespondentPage(initialName: initialValue),
),
textFieldLabel: S.of(context).documentCorrespondentPropertyLabel,
@@ -134,7 +140,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
notAssignedSelectable: false,
formBuilderState: _formKey.currentState,
labelCreationWidgetBuilder: (currentInput) => RepositoryProvider(
create: (context) => context.read<LabelRepository<DocumentType>>(),
create: (context) => context
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
child: AddDocumentTypePage(
initialName: currentInput,
),

View File

@@ -1,8 +1,11 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/repository/impl/correspondent_repository_impl.dart';
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.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';
@@ -23,6 +26,17 @@ import 'package:paperless_mobile/features/settings/bloc/application_settings_cub
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
import 'package:paperless_mobile/util.dart';
import 'package:collection/collection.dart';
class DocumentFilterIntent {
final DocumentFilter? filter;
final bool shouldReset;
DocumentFilterIntent({
this.filter,
this.shouldReset = false,
});
}
class DocumentsPage extends StatefulWidget {
const DocumentsPage({Key? key}) : super(key: key);
@@ -35,13 +49,13 @@ class _DocumentsPageState extends State<DocumentsPage> {
final _pagingController = PagingController<int, DocumentModel>(
firstPageKey: 1,
);
final _refreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
@override
void initState() {
super.initState();
try {
context.read<DocumentsCubit>().load();
context.read<DocumentsCubit>().reload();
context.read<SavedViewCubit>().reload();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
@@ -62,7 +76,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
current == ConnectivityState.connected,
listener: (context, state) {
try {
context.read<DocumentsCubit>().load();
context.read<DocumentsCubit>().reload();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
@@ -101,7 +115,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
void _openDocumentFilter() async {
final draggableSheetController = DraggableScrollableController();
final filter = await showModalBottomSheet<DocumentFilter>(
final filterIntent = await showModalBottomSheet<DocumentFilterIntent>(
useSafeArea: true,
context: context,
shape: const RoundedRectangleBorder(
@@ -130,9 +144,23 @@ class _DocumentsPageState extends State<DocumentsPage> {
),
),
);
if (filter != null) {
context.read<DocumentsCubit>().updateFilter(filter: filter);
context.read<SavedViewCubit>().resetSelection();
if (filterIntent != null) {
try {
if (filterIntent.shouldReset) {
await context.read<DocumentsCubit>().resetFilter();
context.read<DocumentsCubit>().unselectView();
} else {
if (filterIntent.filter !=
context.read<DocumentsCubit>().state.filter) {
context.read<DocumentsCubit>().unselectView();
}
await context
.read<DocumentsCubit>()
.updateFilter(filter: filterIntent.filter!);
}
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}
@@ -141,6 +169,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
return BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
builder: (context, settings) {
return BlocBuilder<DocumentsCubit, DocumentsState>(
buildWhen: (previous, current) => !const ListEquality()
.equals(previous.documents, current.documents),
builder: (context, state) {
// Some ugly tricks to make it work with bloc, update pageController
_pagingController.value = PagingState(
@@ -184,59 +214,34 @@ class _DocumentsPageState extends State<DocumentsPage> {
state: state,
onReset: () {
context.read<DocumentsCubit>().resetFilter();
context.read<SavedViewCubit>().resetSelection();
context.read<DocumentsCubit>().unselectView();
},
),
);
}
return RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: _onRefresh,
notificationPredicate: (_) => isConnected,
child: CustomScrollView(
slivers: [
BlocListener<SavedViewCubit, SavedViewState>(
listenWhen: (previous, current) =>
previous.selectedSavedViewId !=
current.selectedSavedViewId,
listener: (context, state) {
try {
if (state.selectedSavedViewId == null) {
context.read<DocumentsCubit>().resetFilter();
} else {
final newFilter = state
.value[state.selectedSavedViewId]
?.toDocumentFilter();
if (newFilter != null) {
context
.read<DocumentsCubit>()
.updateFilter(filter: newFilter);
}
}
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
},
child: DocumentsPageAppBar(
isOffline:
connectivityState != ConnectivityState.connected,
actions: [
const SortDocumentsButton(),
IconButton(
icon: Icon(
settings.preferredViewType == ViewType.grid
? Icons.list
: Icons.grid_view,
),
onPressed: () => context
.read<ApplicationSettingsCubit>()
.setViewType(
settings.preferredViewType.toggle(),
),
DocumentsPageAppBar(
isOffline: connectivityState != ConnectivityState.connected,
actions: [
const SortDocumentsButton(),
IconButton(
icon: Icon(
settings.preferredViewType == ViewType.grid
? Icons.list
: Icons.grid_view,
),
],
),
onPressed: () => context
.read<ApplicationSettingsCubit>()
.setViewType(
settings.preferredViewType.toggle(),
),
),
],
),
child,
],
@@ -249,10 +254,13 @@ class _DocumentsPageState extends State<DocumentsPage> {
}
Future<void> _openDetails(DocumentModel document) async {
await Navigator.of(context).push<DocumentModel?>(
final potentiallyUpdatedModel =
await Navigator.of(context).push<DocumentModel?>(
_buildDetailsPageRoute(document),
);
context.read<DocumentsCubit>().reload();
if (potentiallyUpdatedModel != document) {
context.read<DocumentsCubit>().reload();
}
}
MaterialPageRoute<DocumentModel?> _buildDetailsPageRoute(
@@ -371,9 +379,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
Future<void> _onRefresh() async {
try {
await context.read<DocumentsCubit>().updateCurrentFilter(
(filter) => filter.copyWith(page: 1),
);
// We do not await here on purpose so we can show a linear progress indicator below the app bar.
await context.read<DocumentsCubit>().reload();
await context.read<SavedViewCubit>().reload();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);

View File

@@ -6,6 +6,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
import 'package:paperless_mobile/features/documents/view/widgets/search/text_query_form_field.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
@@ -228,10 +229,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
FocusScope.of(context).unfocus();
Navigator.pop(
context,
DocumentFilter.initial.copyWith(
sortField: widget.initialFilter.sortField,
sortOrder: widget.initialFilter.sortOrder,
),
DocumentFilterIntent(shouldReset: true),
);
}
@@ -293,7 +291,7 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
if (_formKey.currentState?.validate() ?? false) {
DocumentFilter newFilter = _assembleFilter();
FocusScope.of(context).unfocus();
Navigator.pop(context, newFilter);
Navigator.pop(context, DocumentFilterIntent(filter: newFilter));
}
}

View File

@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.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/view/widgets/search/sort_field_selection_bottom_sheet.dart';
@@ -37,12 +39,16 @@ class SortDocumentsButton extends StatelessWidget {
providers: [
BlocProvider(
create: (context) => LabelCubit<DocumentType>(
context.read<LabelRepository<DocumentType>>(),
context.read<
LabelRepository<DocumentType,
DocumentTypeRepositoryState>>(),
),
),
BlocProvider(
create: (context) => LabelCubit<Correspondent>(
context.read<LabelRepository<Correspondent>>(),
context.read<
LabelRepository<Correspondent,
CorrespondentRepositoryState>>(),
),
),
],

View File

@@ -5,6 +5,10 @@ import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:collection/collection.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
part 'edit_document_state.dart';
@@ -12,19 +16,25 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
final DocumentModel _initialDocument;
final PaperlessDocumentsApi _docsApi;
final LabelRepository<Correspondent> _correspondentRepository;
final LabelRepository<DocumentType> _documentTypeRepository;
final LabelRepository<StoragePath> _storagePathRepository;
final LabelRepository<Tag> _tagRepository;
final LabelRepository<Correspondent, CorrespondentRepositoryState>
_correspondentRepository;
final LabelRepository<DocumentType, DocumentTypeRepositoryState>
_documentTypeRepository;
final LabelRepository<StoragePath, StoragePathRepositoryState>
_storagePathRepository;
final LabelRepository<Tag, TagRepositoryState> _tagRepository;
final List<StreamSubscription> _subscriptions = [];
EditDocumentCubit(
DocumentModel document, {
required PaperlessDocumentsApi documentsApi,
required LabelRepository<Correspondent> correspondentRepository,
required LabelRepository<DocumentType> documentTypeRepository,
required LabelRepository<StoragePath> storagePathRepository,
required LabelRepository<Tag> tagRepository,
required LabelRepository<Correspondent, CorrespondentRepositoryState>
correspondentRepository,
required LabelRepository<DocumentType, DocumentTypeRepositoryState>
documentTypeRepository,
required LabelRepository<StoragePath, StoragePathRepositoryState>
storagePathRepository,
required LabelRepository<Tag, TagRepositoryState> tagRepository,
}) : _initialDocument = document,
_docsApi = documentsApi,
_correspondentRepository = correspondentRepository,
@@ -34,27 +44,27 @@ class EditDocumentCubit extends Cubit<EditDocumentState> {
super(
EditDocumentState(
document: document,
correspondents: correspondentRepository.current ?? {},
documentTypes: documentTypeRepository.current ?? {},
storagePaths: storagePathRepository.current ?? {},
tags: tagRepository.current ?? {},
correspondents: correspondentRepository.current?.values ?? {},
documentTypes: documentTypeRepository.current?.values ?? {},
storagePaths: storagePathRepository.current?.values ?? {},
tags: tagRepository.current?.values ?? {},
),
) {
_subscriptions.add(
_correspondentRepository.values
.listen((v) => emit(state.copyWith(correspondents: v))),
.listen((v) => emit(state.copyWith(correspondents: v?.values))),
);
_subscriptions.add(
_documentTypeRepository.values
.listen((v) => emit(state.copyWith(documentTypes: v))),
.listen((v) => emit(state.copyWith(documentTypes: v?.values))),
);
_subscriptions.add(
_storagePathRepository.values
.listen((v) => emit(state.copyWith(storagePaths: v))),
.listen((v) => emit(state.copyWith(storagePaths: v?.values))),
);
_subscriptions.add(
_tagRepository.values.listen(
(v) => emit(state.copyWith(tags: v)),
(v) => emit(state.copyWith(tags: v?.values)),
),
);
}

View File

@@ -3,18 +3,19 @@ import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_state.dart';
class EditLabelCubit<T extends Label> extends Cubit<EditLabelState<T>> {
final LabelRepository<T> _repository;
final LabelRepository<T, RepositoryState<Map<int, T>>> _repository;
StreamSubscription<Map<int, T>?>? _subscription;
StreamSubscription? _subscription;
EditLabelCubit(LabelRepository<T> repository)
EditLabelCubit(LabelRepository<T, RepositoryState<Map<int, T>>> repository)
: _repository = repository,
super(const EditLabelInitial()) {
_subscription = repository.values.listen(
(update) => emit(EditLabelState(labels: update ?? {})),
(event) => emit(EditLabelState(labels: event?.values ?? {})),
);
}

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
import 'package:paperless_mobile/generated/l10n.dart';
@@ -24,7 +25,8 @@ class AddLabelPage<T extends Label> extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EditLabelCubit(
context.read<LabelRepository<T>>(),
context
.read<LabelRepository<Label, RepositoryState<Map<int, Label>>>>(),
),
child: AddLabelFormWidget(
pageTitle: pageTitle,

View File

@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
import 'package:paperless_mobile/generated/l10n.dart';
@@ -24,7 +25,8 @@ class EditLabelPage<T extends Label> extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EditLabelCubit(
context.read<LabelRepository<T>>(),
context
.read<LabelRepository<Label, RepositoryState<Map<int, Label>>>>(),
),
child: EditLabelForm(
label: label,
@@ -63,7 +65,7 @@ class EditLabelForm<T extends Label> extends StatelessWidget {
initialValue: label,
fromJsonT: fromJsonT,
submitButtonConfig: SubmitButtonConfig<T>(
icon: const Icon(Icons.update),
icon: const Icon(Icons.done),
label: Text(S.of(context).genericActionUpdateLabel),
onSubmit: context.read<EditLabelCubit<T>>().update,
),

View File

@@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
import 'package:paperless_mobile/generated/l10n.dart';
@@ -14,7 +15,8 @@ class AddCorrespondentPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EditLabelCubit<Correspondent>(
context.read<LabelRepository<Correspondent>>(),
context.read<
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
),
child: AddLabelPage<Correspondent>(
pageTitle: Text(S.of(context).addCorrespondentPageTitle),

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
import 'package:paperless_mobile/generated/l10n.dart';
@@ -17,7 +18,8 @@ class AddDocumentTypePage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EditLabelCubit<DocumentType>(
context.read<LabelRepository<DocumentType>>(),
context
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
),
child: AddLabelPage<DocumentType>(
pageTitle: Text(S.of(context).addDocumentTypePageTitle),

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
@@ -15,7 +16,8 @@ class AddStoragePathPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EditLabelCubit<StoragePath>(
context.read<LabelRepository<StoragePath>>(),
context
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
),
child: AddLabelPage<StoragePath>(
pageTitle: Text(S.of(context).addStoragePathPageTitle),

View File

@@ -5,6 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
@@ -18,7 +19,7 @@ class AddTagPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EditLabelCubit<Tag>(
context.read<LabelRepository<Tag>>(),
context.read<LabelRepository<Tag, TagRepositoryState>>(),
),
child: AddLabelPage<Tag>(
pageTitle: Text(S.of(context).addTagPageTitle),

View File

@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
class EditCorrespondentPage extends StatelessWidget {
final Correspondent correspondent;
@@ -14,7 +14,8 @@ class EditCorrespondentPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EditLabelCubit<Correspondent>(
context.read<LabelRepository<Correspondent>>(),
context.read<
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
),
child: EditLabelPage<Correspondent>(
label: correspondent,

View File

@@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
@@ -13,7 +14,8 @@ class EditDocumentTypePage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EditLabelCubit<DocumentType>(
context.read<LabelRepository<DocumentType>>(),
context
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
),
child: EditLabelPage<DocumentType>(
label: documentType,

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
@@ -14,7 +15,8 @@ class EditStoragePathPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EditLabelCubit<StoragePath>(
context.read<LabelRepository<StoragePath>>(),
context
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
),
child: EditLabelPage<StoragePath>(
label: storagePath,

View File

@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart';
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
@@ -17,7 +18,7 @@ class EditTagPage extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => EditLabelCubit<Tag>(
context.read<LabelRepository<Tag>>(),
context.read<LabelRepository<Tag, TagRepositoryState>>(),
),
child: EditLabelPage<Tag>(
label: tag,

View File

@@ -1,4 +1,3 @@
import 'dart:developer';
import 'dart:io';
import 'package:flutter/material.dart';
@@ -12,6 +11,10 @@ import 'package:paperless_mobile/core/global/constants.dart';
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
@@ -25,9 +28,8 @@ import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:path/path.dart' as p;
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@@ -51,17 +53,13 @@ class _HomePageState extends State<HomePage> {
void _listenForReceivedFiles() async {
if (ShareIntentQueue.instance.hasUnhandledFiles) {
Fluttertoast.showToast(msg: "Sync: Has unhandled files!");
await _handleReceivedFile(ShareIntentQueue.instance.pop()!);
Fluttertoast.showToast(msg: "Sync: File handled!");
}
ShareIntentQueue.instance.addListener(() async {
final queue = ShareIntentQueue.instance;
while (queue.hasUnhandledFiles) {
Fluttertoast.showToast(msg: "Async: Has unhandled files!");
final file = queue.pop()!;
await _handleReceivedFile(file);
Fluttertoast.showToast(msg: "Async: File handled!");
}
});
}
@@ -73,28 +71,29 @@ class _HomePageState extends State<HomePage> {
}
Future<void> _handleReceivedFile(SharedMediaFile file) async {
final isGranted = await askForPermission(Permission.storage);
// final isGranted = await askForPermission(Permission.storage);
if (!isGranted) {
return;
}
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("Received File."),
content: Column(
children: [
Text("Path: ${file.path}"),
Text("Type: ${file.type.name}"),
Text("Exists: ${File(file.path).existsSync()}"),
FutureBuilder<bool>(
future: Permission.storage.isGranted,
builder: (context, snapshot) =>
Text("Has storage permission: ${snapshot.data}"),
)
],
),
));
// if (!isGranted) {
// return;
// }
// showDialog(
// context: context,
// builder: (context) => AlertDialog(
// title: Text("Received File."),
// content: Column(
// children: [
// Text("Path: ${file.path}"),
// Text("Type: ${file.type.name}"),
// Text("Exists: ${File(file.path).existsSync()}"),
// FutureBuilder<bool>(
// future: Permission.storage.isGranted,
// builder: (context, snapshot) =>
// Text("Has storage permission: ${snapshot.data}"),
// )
// ],
// ),
// ),
// );
SharedMediaFile mediaFile;
if (Platform.isIOS) {
// Workaround for file not found on iOS: https://stackoverflow.com/a/72813212
@@ -119,7 +118,7 @@ class _HomePageState extends State<HomePage> {
return;
}
final filename = extractFilenameFromPath(mediaFile.path);
final extension = p.extension(mediaFile.path);
try {
if (File(mediaFile.path).existsSync()) {
final bytes = File(mediaFile.path).readAsBytesSync();
@@ -137,6 +136,8 @@ class _HomePageState extends State<HomePage> {
child: DocumentUploadPreparationPage(
fileBytes: bytes,
filename: filename,
title: filename,
fileExtension: extension,
),
),
),
@@ -148,20 +149,16 @@ class _HomePageState extends State<HomePage> {
);
SystemNavigator.pop();
}
} else {
Fluttertoast.showToast(
msg: S.of(context).receiveSharedFilePermissionDeniedMessage,
toastLength: Toast.LENGTH_LONG,
);
}
} catch (e, stackTrace) {
showDialog(
context: context,
builder: (context) => AlertDialog(
content: Column(
children: [
Text(
e.toString(),
),
Text(stackTrace.toString()),
],
),
),
Fluttertoast.showToast(
msg: S.of(context).receiveSharedFilePermissionDeniedMessage,
toastLength: Toast.LENGTH_LONG,
);
}
}
@@ -191,6 +188,7 @@ class _HomePageState extends State<HomePage> {
BlocProvider(
create: (context) => DocumentsCubit(
context.read<PaperlessDocumentsApi>(),
context.read<SavedViewRepository>(),
),
),
BlocProvider(
@@ -213,10 +211,16 @@ class _HomePageState extends State<HomePage> {
void _initializeData(BuildContext context) {
try {
context.read<LabelRepository<Tag>>().findAll();
context.read<LabelRepository<Correspondent>>().findAll();
context.read<LabelRepository<DocumentType>>().findAll();
context.read<LabelRepository<StoragePath>>().findAll();
context.read<LabelRepository<Tag, TagRepositoryState>>().findAll();
context
.read<LabelRepository<Correspondent, CorrespondentRepositoryState>>()
.findAll();
context
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>()
.findAll();
context
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>()
.findAll();
context.read<SavedViewRepository>().findAll();
context.read<PaperlessServerInformationCubit>().updateInformtion();
} on PaperlessServerException catch (error, stackTrace) {

View File

@@ -7,6 +7,10 @@ import 'package:paperless_mobile/core/bloc/paperless_server_information_state.da
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/core/store/local_vault.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
@@ -102,154 +106,161 @@ class _InfoDrawerState extends State<InfoDrawer> {
// }
// },
// );
return ClipRRect(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(16.0),
bottomRight: Radius.circular(16.0),
),
child: Drawer(
shape: const RoundedRectangleBorder(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(16.0),
bottomRight: Radius.circular(16.0),
),
return SafeArea(
top: true,
child: ClipRRect(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(16.0),
bottomRight: Radius.circular(16.0),
),
child: ListView(
children: [
DrawerHeader(
padding: const EdgeInsets.only(
top: 8,
left: 8,
bottom: 0,
right: 8,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Image.asset(
'assets/logos/paperless_logo_white.png',
height: 32,
width: 32,
color: Theme.of(context).colorScheme.onPrimaryContainer,
).paddedOnly(right: 8.0),
Text(
S.of(context).appTitleText,
style:
Theme.of(context).textTheme.headlineSmall?.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
],
),
Align(
alignment: Alignment.bottomRight,
child: BlocBuilder<PaperlessServerInformationCubit,
PaperlessServerInformationState>(
builder: (context, state) {
if (!state.isLoaded) {
return Container();
}
final info = state.information!;
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
ListTile(
contentPadding: EdgeInsets.zero,
dense: true,
title: Text(
S.of(context).appDrawerHeaderLoggedInAsText +
(info.username ?? '?'),
style: Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end,
maxLines: 1,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
state.information!.host ?? '',
style:
Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end,
maxLines: 1,
),
Text(
'${S.of(context).serverInformationPaperlessVersionText} ${info.version} (API v${info.apiVersion})',
style:
Theme.of(context).textTheme.bodySmall,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end,
maxLines: 1,
),
],
),
isThreeLine: true,
),
],
);
},
),
),
],
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
child: Drawer(
shape: const RoundedRectangleBorder(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(16.0),
bottomRight: Radius.circular(16.0),
),
...[
ListTile(
title: Text(S.of(context).bottomNavInboxPageLabel),
leading: const Icon(Icons.inbox),
onTap: () => _onOpenInbox(),
shape: listtTileShape,
),
ListTile(
leading: const Icon(Icons.settings),
shape: listtTileShape,
title: Text(
S.of(context).appDrawerSettingsLabel,
),
child: ListView(
children: [
DrawerHeader(
padding: const EdgeInsets.only(
top: 8,
left: 8,
bottom: 0,
right: 8,
),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: context.read<ApplicationSettingsCubit>(),
child: const SettingsPage(),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Image.asset(
'assets/logos/paperless_logo_white.png',
height: 32,
width: 32,
color:
Theme.of(context).colorScheme.onPrimaryContainer,
).paddedOnly(right: 8.0),
Text(
S.of(context).appTitleText,
style: Theme.of(context)
.textTheme
.headlineSmall
?.copyWith(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
],
),
Align(
alignment: Alignment.bottomRight,
child: BlocBuilder<PaperlessServerInformationCubit,
PaperlessServerInformationState>(
builder: (context, state) {
if (!state.isLoaded) {
return Container();
}
final info = state.information!;
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
ListTile(
contentPadding: EdgeInsets.zero,
dense: true,
title: Text(
S.of(context).appDrawerHeaderLoggedInAsText +
(info.username ?? '?'),
style: Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end,
maxLines: 1,
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
state.information!.host ?? '',
style: Theme.of(context)
.textTheme
.bodyMedium,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end,
maxLines: 1,
),
Text(
'${S.of(context).serverInformationPaperlessVersionText} ${info.version} (API v${info.apiVersion})',
style:
Theme.of(context).textTheme.bodySmall,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end,
maxLines: 1,
),
],
),
isThreeLine: true,
),
],
);
},
),
),
],
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
),
...[
ListTile(
title: Text(S.of(context).bottomNavInboxPageLabel),
leading: const Icon(Icons.inbox),
onTap: () => _onOpenInbox(),
shape: listtTileShape,
),
ListTile(
leading: const Icon(Icons.settings),
shape: listtTileShape,
title: Text(
S.of(context).appDrawerSettingsLabel,
),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: context.read<ApplicationSettingsCubit>(),
child: const SettingsPage(),
),
),
),
),
),
ListTile(
leading: const Icon(Icons.bug_report),
title: Text(S.of(context).appDrawerReportBugLabel),
onTap: () {
launchUrlString(
'https://github.com/astubenbord/paperless-mobile/issues/new');
},
shape: listtTileShape,
),
ListTile(
title: Text(S.of(context).appDrawerAboutLabel),
leading: Icon(Icons.info_outline_rounded),
onTap: _onShowAboutDialog,
shape: listtTileShape,
),
ListTile(
leading: const Icon(Icons.logout),
title: Text(S.of(context).appDrawerLogoutLabel),
shape: listtTileShape,
onTap: () {
_onLogout();
},
)
ListTile(
leading: const Icon(Icons.bug_report),
title: Text(S.of(context).appDrawerReportBugLabel),
onTap: () {
launchUrlString(
'https://github.com/astubenbord/paperless-mobile/issues/new');
},
shape: listtTileShape,
),
ListTile(
title: Text(S.of(context).appDrawerAboutLabel),
leading: Icon(Icons.info_outline_rounded),
onTap: _onShowAboutDialog,
shape: listtTileShape,
),
ListTile(
leading: const Icon(Icons.logout),
title: Text(S.of(context).appDrawerLogoutLabel),
shape: listtTileShape,
onTap: () {
_onLogout();
},
)
],
],
],
),
),
),
);
@@ -260,10 +271,16 @@ class _InfoDrawerState extends State<InfoDrawer> {
context.read<AuthenticationCubit>().logout();
context.read<LocalVault>().clear();
context.read<ApplicationSettingsCubit>().clear();
context.read<LabelRepository<Tag>>().clear();
context.read<LabelRepository<Correspondent>>().clear();
context.read<LabelRepository<DocumentType>>().clear();
context.read<LabelRepository<StoragePath>>().clear();
context.read<LabelRepository<Tag, TagRepositoryState>>().clear();
context
.read<LabelRepository<Correspondent, CorrespondentRepositoryState>>()
.clear();
context
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>()
.clear();
context
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>()
.clear();
context.read<SavedViewRepository>().clear();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
@@ -276,7 +293,7 @@ class _InfoDrawerState extends State<InfoDrawer> {
builder: (_) => LabelRepositoriesProvider(
child: BlocProvider(
create: (context) => InboxCubit(
context.read<LabelRepository<Tag>>(),
context.read<LabelRepository<Tag, TagRepositoryState>>(),
context.read<PaperlessDocumentsApi>(),
)..loadInbox(),
child: const InboxPage(),

View File

@@ -3,6 +3,10 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
@@ -66,10 +70,16 @@ class VerifyIdentityPage extends StatelessWidget {
void _logout(BuildContext context) {
context.read<AuthenticationCubit>().logout();
context.read<LabelRepository<Tag>>().clear();
context.read<LabelRepository<Correspondent>>().clear();
context.read<LabelRepository<DocumentType>>().clear();
context.read<LabelRepository<StoragePath>>().clear();
context.read<LabelRepository<Tag, TagRepositoryState>>().clear();
context
.read<LabelRepository<Correspondent, CorrespondentRepositoryState>>()
.clear();
context
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>()
.clear();
context
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>()
.clear();
context.read<SavedViewRepository>().clear();
HydratedBloc.storage.clear();
}

View File

@@ -2,10 +2,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
class InboxCubit extends Cubit<InboxState> {
final LabelRepository<Tag> _tagsRepository;
final LabelRepository<Tag, TagRepositoryState> _tagsRepository;
final PaperlessDocumentsApi _documentsApi;
InboxCubit(this._tagsRepository, this._documentsApi)

View File

@@ -3,25 +3,26 @@ import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
final LabelRepository<T> _repository;
final LabelRepository<T, RepositoryState> _repository;
late StreamSubscription _subscription;
LabelCubit(LabelRepository<T> repository)
LabelCubit(LabelRepository<T, RepositoryState> repository)
: _repository = repository,
super(LabelState(
isLoaded: repository.isInitialized,
labels: repository.current ?? {},
labels: repository.current?.values ?? {},
)) {
_subscription = _repository.values.listen(
(update) {
if (update == null) {
(event) {
if (event == null) {
emit(LabelState());
}
emit(LabelState(isLoaded: true, labels: update!));
emit(LabelState(isLoaded: true, labels: event!.values));
},
);
}

View File

@@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
class CorrespondentBlocProvider extends StatelessWidget {
@@ -12,7 +13,8 @@ class CorrespondentBlocProvider extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LabelCubit<Correspondent>(
context.read<LabelRepository<Correspondent>>(),
context.read<
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
),
child: child,
);

View File

@@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
class DocumentTypeBlocProvider extends StatelessWidget {
@@ -12,7 +13,8 @@ class DocumentTypeBlocProvider extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LabelCubit<DocumentType>(
context.read<LabelRepository<DocumentType>>(),
context
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
),
child: child,
);

View File

@@ -2,6 +2,10 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
class LabelsBlocProvider extends StatelessWidget {
@@ -14,22 +18,25 @@ class LabelsBlocProvider extends StatelessWidget {
providers: [
BlocProvider<LabelCubit<StoragePath>>(
create: (context) => LabelCubit<StoragePath>(
context.read<LabelRepository<StoragePath>>(),
context.read<
LabelRepository<StoragePath, StoragePathRepositoryState>>(),
),
),
BlocProvider<LabelCubit<Correspondent>>(
create: (context) => LabelCubit<Correspondent>(
context.read<LabelRepository<Correspondent>>(),
context.read<
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
),
),
BlocProvider<LabelCubit<DocumentType>>(
create: (context) => LabelCubit<DocumentType>(
context.read<LabelRepository<DocumentType>>(),
context.read<
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
),
),
BlocProvider<LabelCubit<Tag>>(
create: (context) => LabelCubit<Tag>(
context.read<LabelRepository<Tag>>(),
context.read<LabelRepository<Tag, TagRepositoryState>>(),
),
),
],

View File

@@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
class StoragePathBlocProvider extends StatelessWidget {
@@ -12,7 +13,8 @@ class StoragePathBlocProvider extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LabelCubit<StoragePath>(
context.read<LabelRepository<StoragePath>>(),
context
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
),
child: child,
);

View File

@@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
class TagBlocProvider extends StatelessWidget {
@@ -12,7 +13,7 @@ class TagBlocProvider extends StatelessWidget {
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LabelCubit<Tag>(
context.read<LabelRepository<Tag>>(),
context.read<LabelRepository<Tag, TagRepositoryState>>(),
),
child: child,
);

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/bloc/providers/correspondent_bloc_provider.dart';

View File

@@ -4,6 +4,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart';
import 'package:paperless_mobile/generated/l10n.dart';
@@ -217,7 +218,8 @@ class _TagFormFieldState extends State<TagFormField> {
final Tag? tag = await Navigator.of(context).push<Tag>(
MaterialPageRoute(
builder: (_) => RepositoryProvider(
create: (context) => context.read<LabelRepository<Tag>>(),
create: (context) =>
context.read<LabelRepository<Tag, TagRepositoryState>>(),
child: AddTagPage(initialValue: _textEditingController.text),
),
),

View File

@@ -3,6 +3,10 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
@@ -208,7 +212,8 @@ class _LabelsPageState extends State<LabelsPage>
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider(
create: (context) => context.read<LabelRepository<Correspondent>>(),
create: (context) => context.read<
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
child: EditCorrespondentPage(correspondent: correspondent),
),
),
@@ -220,7 +225,8 @@ class _LabelsPageState extends State<LabelsPage>
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider(
create: (context) => context.read<LabelRepository<DocumentType>>(),
create: (context) => context.read<
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
child: EditDocumentTypePage(documentType: docType),
),
),
@@ -232,7 +238,8 @@ class _LabelsPageState extends State<LabelsPage>
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider(
create: (context) => context.read<LabelRepository<Tag>>(),
create: (context) =>
context.read<LabelRepository<Tag, TagRepositoryState>>(),
child: EditTagPage(tag: tag),
),
),
@@ -244,7 +251,8 @@ class _LabelsPageState extends State<LabelsPage>
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider(
create: (context) => context.read<LabelRepository<StoragePath>>(),
create: (context) => context
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
child: EditStoragePathPage(
storagePath: path,
),
@@ -258,7 +266,8 @@ class _LabelsPageState extends State<LabelsPage>
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider(
create: (context) => context.read<LabelRepository<Correspondent>>(),
create: (context) => context.read<
LabelRepository<Correspondent, CorrespondentRepositoryState>>(),
child: const AddCorrespondentPage(),
),
),
@@ -270,7 +279,8 @@ class _LabelsPageState extends State<LabelsPage>
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider(
create: (context) => context.read<LabelRepository<DocumentType>>(),
create: (context) => context.read<
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
child: const AddDocumentTypePage(),
),
),
@@ -282,7 +292,8 @@ class _LabelsPageState extends State<LabelsPage>
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider(
create: (context) => context.read<LabelRepository<Tag>>(),
create: (context) =>
context.read<LabelRepository<Tag, TagRepositoryState>>(),
child: const AddTagPage(),
),
),
@@ -294,7 +305,8 @@ class _LabelsPageState extends State<LabelsPage>
context,
MaterialPageRoute(
builder: (_) => RepositoryProvider(
create: (context) => context.read<LabelRepository<StoragePath>>(),
create: (context) => context
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>(),
child: const AddStoragePathPage(),
),
),

View File

@@ -8,7 +8,10 @@ import 'package:paperless_mobile/generated/l10n.dart';
class UserCredentialsFormField extends StatefulWidget {
static const fkCredentials = 'credentials';
const UserCredentialsFormField({Key? key}) : super(key: key);
const UserCredentialsFormField({
Key? key,
}) : super(key: key);
@override
State<UserCredentialsFormField> createState() =>

View File

@@ -9,54 +9,36 @@ class SavedViewCubit extends Cubit<SavedViewState> {
final SavedViewRepository _repository;
StreamSubscription? _subscription;
SavedViewCubit(this._repository) : super(SavedViewState(value: {})) {
SavedViewCubit(this._repository) : super(const SavedViewState()) {
_subscription = _repository.values.listen(
(savedViews) {
if (savedViews == null) {
emit(state.copyWith(isLoaded: false));
if (savedViews?.hasLoaded ?? false) {
emit(state.copyWith(value: savedViews?.values, hasLoaded: true));
} else {
emit(state.copyWith(value: savedViews, isLoaded: true));
emit(state.copyWith(hasLoaded: false));
}
},
);
}
void selectView(SavedView? view) {
emit(state.copyWith(
selectedSavedViewId: view?.id,
overwriteSelectedSavedViewId: true,
));
}
Future<SavedView> add(SavedView view) async {
final savedView = await _repository.create(view);
emit(state.copyWith(value: {...state.value, savedView.id!: savedView}));
return savedView;
}
Future<int> remove(SavedView view) async {
final id = await _repository.delete(view);
if (state.selectedSavedViewId == id) {
resetSelection();
}
return id;
Future<int> remove(SavedView view) {
return _repository.delete(view);
}
Future<void> initialize() async {
final views = await _repository.findAll();
final values = {for (var element in views) element.id!: element};
emit(SavedViewState(value: values, isLoaded: true));
emit(SavedViewState(value: values, hasLoaded: true));
}
Future<void> reload() => initialize();
void resetSelection() {
emit(SavedViewState(
value: state.value,
isLoaded: true,
));
}
@override
Future<void> close() {
_subscription?.cancel();

View File

@@ -2,34 +2,29 @@ import 'package:equatable/equatable.dart';
import 'package:paperless_api/paperless_api.dart';
class SavedViewState with EquatableMixin {
final bool isLoaded;
final bool hasLoaded;
final Map<int, SavedView> value;
final int? selectedSavedViewId;
SavedViewState({
required this.value,
this.isLoaded = false,
this.selectedSavedViewId,
const SavedViewState({
this.value = const {},
this.hasLoaded = false,
});
@override
List<Object?> get props => [
hasLoaded,
value,
selectedSavedViewId,
];
SavedViewState copyWith({
Map<int, SavedView>? value,
int? selectedSavedViewId,
bool overwriteSelectedSavedViewId = false,
bool? isLoaded,
bool? hasLoaded,
}) {
return SavedViewState(
value: value ?? this.value,
isLoaded: isLoaded ?? this.isLoaded,
selectedSavedViewId: overwriteSelectedSavedViewId
? selectedSavedViewId
: this.selectedSavedViewId,
hasLoaded: hasLoaded ?? this.hasLoaded,
);
}
}

View File

@@ -3,6 +3,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
@@ -27,71 +28,86 @@ class SavedViewSelectionWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BlocBuilder<SavedViewCubit, SavedViewState>(
builder: (context, state) {
if (!state.isLoaded) {
return _buildLoadingWidget(context);
}
if (state.value.isEmpty) {
return Text(S.of(context).savedViewsEmptyStateText);
}
return SizedBox(
height: height,
child: ListView.separated(
itemCount: state.value.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final view = state.value.values.elementAt(index);
return GestureDetector(
onLongPress: () => _onDelete(context, view),
child: FilterChip(
label: Text(state.value.values.toList()[index].name),
selected: view.id == state.selectedSavedViewId,
onSelected: enabled
? (isSelected) =>
_onSelected(isSelected, context, view)
: null,
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivityState) {
final hasInternetConnection = connectivityState.isConnected;
return Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BlocBuilder<SavedViewCubit, SavedViewState>(
builder: (context, state) {
if (!state.hasLoaded) {
return _buildLoadingWidget(context);
}
if (state.value.isEmpty) {
return Text(S.of(context).savedViewsEmptyStateText);
}
return SizedBox(
height: height,
child: ListView.separated(
itemCount: state.value.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
final view = state.value.values.elementAt(index);
return GestureDetector(
onLongPress: hasInternetConnection
? () => _onDelete(context, view)
: null,
child: BlocBuilder<DocumentsCubit, DocumentsState>(
builder: (context, docState) {
return FilterChip(
label: Text(
state.value.values.toList()[index].name,
),
selected: view.id == docState.selectedSavedViewId,
onSelected: enabled && hasInternetConnection
? (isSelected) =>
_onSelected(isSelected, context, view)
: null,
);
},
),
);
},
separatorBuilder: (context, index) => const SizedBox(
width: 4.0,
),
);
},
separatorBuilder: (context, index) => const SizedBox(
width: 8.0,
),
),
);
},
),
BlocBuilder<SavedViewCubit, SavedViewState>(
builder: (context, state) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).savedViewsLabel,
style: Theme.of(context).textTheme.titleSmall,
),
BlocBuilder<DocumentsCubit, DocumentsState>(
buildWhen: (previous, current) =>
previous.filter != current.filter,
builder: (context, docState) {
return TextButton.icon(
icon: const Icon(Icons.add),
onPressed: (enabled && state.isLoaded)
? () => _onCreatePressed(context, docState.filter)
: null,
label: Text(S.of(context).savedViewCreateNewLabel),
);
},
),
],
);
},
),
],
),
);
},
),
BlocBuilder<SavedViewCubit, SavedViewState>(
builder: (context, state) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).savedViewsLabel,
style: Theme.of(context).textTheme.titleSmall,
),
BlocBuilder<DocumentsCubit, DocumentsState>(
buildWhen: (previous, current) =>
previous.filter != current.filter,
builder: (context, docState) {
return TextButton.icon(
icon: const Icon(Icons.add),
onPressed: (enabled &&
state.hasLoaded &&
hasInternetConnection)
? () => _onCreatePressed(context, docState.filter)
: null,
label: Text(S.of(context).savedViewCreateNewLabel),
);
},
),
],
);
},
),
],
);
},
);
}
@@ -114,7 +130,7 @@ class SavedViewSelectionWidget extends StatelessWidget {
itemBuilder: (context, index) => FilterChip(
label: SizedBox(width: r.nextInt((index * 20) + 50).toDouble()),
onSelected: null),
separatorBuilder: (context, index) => SizedBox(width: 8.0),
separatorBuilder: (context, index) => const SizedBox(width: 4.0),
),
),
);
@@ -138,11 +154,15 @@ class SavedViewSelectionWidget extends StatelessWidget {
}
void _onSelected(
bool isSelected, BuildContext context, SavedView view) async {
bool isSelected,
BuildContext context,
SavedView view,
) async {
if (isSelected) {
context.read<SavedViewCubit>().selectView(view);
context.read<DocumentsCubit>().selectView(view.id!);
} else {
context.read<SavedViewCubit>().selectView(null);
context.read<DocumentsCubit>().resetFilter();
context.read<DocumentsCubit>().unselectView();
}
}
@@ -156,6 +176,10 @@ class SavedViewSelectionWidget extends StatelessWidget {
if (delete) {
try {
context.read<SavedViewCubit>().remove(view);
if (context.read<DocumentsCubit>().state.selectedSavedViewId ==
view.id) {
await context.read<DocumentsCubit>().resetFilter();
}
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}

View File

@@ -7,12 +7,14 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mime/mime.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/global/constants.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
import 'package:paperless_mobile/core/service/file_service.dart';
import 'package:paperless_mobile/core/store/local_vault.dart';
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
@@ -24,9 +26,9 @@ import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart'
import 'package:paperless_mobile/features/scan/view/widgets/grid_image_item_widget.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart';
import 'package:path/path.dart' as p;
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:path/path.dart' as p;
import 'package:permission_handler/permission_handler.dart';
class ScannerPage extends StatefulWidget {
@@ -145,11 +147,14 @@ class _ScannerPageState extends State<ScannerPage>
create: (context) => DocumentUploadCubit(
localVault: context.read<LocalVault>(),
documentApi: context.read<PaperlessDocumentsApi>(),
correspondentRepository:
context.read<LabelRepository<Correspondent>>(),
documentTypeRepository:
context.read<LabelRepository<DocumentType>>(),
tagRepository: context.read<LabelRepository<Tag>>(),
correspondentRepository: context.read<
LabelRepository<Correspondent,
CorrespondentRepositoryState>>(),
documentTypeRepository: context.read<
LabelRepository<DocumentType,
DocumentTypeRepositoryState>>(),
tagRepository:
context.read<LabelRepository<Tag, TagRepositoryState>>(),
),
child: DocumentUploadPreparationPage(
fileBytes: file.bytes,
@@ -260,16 +265,20 @@ class _ScannerPageState extends State<ScannerPage>
create: (context) => DocumentUploadCubit(
localVault: context.read<LocalVault>(),
documentApi: context.read<PaperlessDocumentsApi>(),
correspondentRepository:
context.read<LabelRepository<Correspondent>>(),
documentTypeRepository:
context.read<LabelRepository<DocumentType>>(),
tagRepository: context.read<LabelRepository<Tag>>(),
correspondentRepository: context.read<
LabelRepository<Correspondent,
CorrespondentRepositoryState>>(),
documentTypeRepository: context.read<
LabelRepository<DocumentType,
DocumentTypeRepositoryState>>(),
tagRepository:
context.read<LabelRepository<Tag, TagRepositoryState>>(),
),
child: DocumentUploadPreparationPage(
fileBytes: file.readAsBytesSync(),
filename: filename,
fileExtension: extension,
title: filename,
),
),
),

View File

@@ -1,10 +1,7 @@
import 'dart:collection';
import 'dart:developer';
import 'package:flutter/widgets.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:rxdart/rxdart.dart';
class ShareIntentQueue extends ChangeNotifier {
final Queue<SharedMediaFile> _queue = Queue();