mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 08:08:14 -06:00
fix: Fixed saved views bug, formatted files, minor changes
This commit is contained in:
@@ -14,7 +14,8 @@ import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
part 'documents_cubit.g.dart';
|
||||
part 'documents_state.dart';
|
||||
|
||||
class DocumentsCubit extends HydratedCubit<DocumentsState> with DocumentPagingBlocMixin {
|
||||
class DocumentsCubit extends HydratedCubit<DocumentsState>
|
||||
with DocumentPagingBlocMixin {
|
||||
@override
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
@@ -40,7 +41,9 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> with DocumentPagingBl
|
||||
replace(document);
|
||||
emit(
|
||||
state.copyWith(
|
||||
selection: state.selection.map((e) => e.id == document.id ? document : e).toList(),
|
||||
selection: state.selection
|
||||
.map((e) => e.id == document.id ? document : e)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -48,7 +51,8 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> with DocumentPagingBl
|
||||
remove(document);
|
||||
emit(
|
||||
state.copyWith(
|
||||
selection: state.selection.where((e) => e.id != document.id).toList(),
|
||||
selection:
|
||||
state.selection.where((e) => e.id != document.id).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -82,7 +86,9 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> with DocumentPagingBl
|
||||
if (state.selectedIds.contains(model.id)) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
selection: state.selection.where((element) => element.id != model.id).toList(),
|
||||
selection: state.selection
|
||||
.where((element) => element.id != model.id)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -86,7 +86,8 @@ class DocumentsState extends DocumentPagingState {
|
||||
);
|
||||
}
|
||||
|
||||
factory DocumentsState.fromJson(Map<String, dynamic> json) => _$DocumentsStateFromJson(json);
|
||||
factory DocumentsState.fromJson(Map<String, dynamic> json) =>
|
||||
_$DocumentsStateFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$DocumentsStateToJson(this);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ class _DocumentViewState extends State<DocumentView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isInitialized = _controller != null && _currentPage != null && _totalPages != null;
|
||||
final isInitialized =
|
||||
_controller != null && _currentPage != null && _totalPages != null;
|
||||
final canGoToNextPage = isInitialized && _currentPage! + 1 < _totalPages!;
|
||||
final canGoToPreviousPage = isInitialized && _currentPage! > 0;
|
||||
return Scaffold(
|
||||
|
||||
@@ -161,101 +161,96 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: searchBarHandle,
|
||||
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
if (state.selection.isEmpty) {
|
||||
return const SliverSearchBar(floating: true);
|
||||
} else {
|
||||
return DocumentSelectionSliverAppBar(
|
||||
state: state,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
SliverOverlapAbsorber(
|
||||
handle: tabBarHandle,
|
||||
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
if (state.selection.isNotEmpty) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
return SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate:
|
||||
CustomizableSliverPersistentHeaderDelegate(
|
||||
minExtent: kTextTabBarHeight,
|
||||
maxExtent: kTextTabBarHeight,
|
||||
child: ColoredTabBar(
|
||||
tabBar: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(text: S.of(context)!.documents),
|
||||
Tab(text: S.of(context)!.views),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
body: NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
final metrics = notification.metrics;
|
||||
if (metrics.maxScrollExtent == 0) {
|
||||
return true;
|
||||
child: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
handle: searchBarHandle,
|
||||
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
if (state.selection.isEmpty) {
|
||||
return const SliverSearchBar(floating: true);
|
||||
} else {
|
||||
return DocumentSelectionSliverAppBar(
|
||||
state: state,
|
||||
);
|
||||
}
|
||||
final desiredTab =
|
||||
(metrics.pixels / metrics.maxScrollExtent)
|
||||
.round();
|
||||
if (metrics.axis == Axis.horizontal &&
|
||||
_currentTab != desiredTab) {
|
||||
setState(() => _currentTab = desiredTab);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
physics: context
|
||||
.watch<DocumentsCubit>()
|
||||
.state
|
||||
.selection
|
||||
.isNotEmpty
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return _buildDocumentsTab(
|
||||
connectivityState,
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
SliverOverlapAbsorber(
|
||||
handle: tabBarHandle,
|
||||
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
if (state.selection.isNotEmpty) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
return SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate:
|
||||
CustomizableSliverPersistentHeaderDelegate(
|
||||
minExtent: kTextTabBarHeight,
|
||||
maxExtent: kTextTabBarHeight,
|
||||
child: ColoredTabBar(
|
||||
tabBar: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(text: S.of(context)!.documents),
|
||||
Tab(text: S.of(context)!.views),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return _buildSavedViewsTab(
|
||||
connectivityState,
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
body: NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
final metrics = notification.metrics;
|
||||
if (metrics.maxScrollExtent == 0) {
|
||||
return true;
|
||||
}
|
||||
final desiredTab =
|
||||
(metrics.pixels / metrics.maxScrollExtent).round();
|
||||
if (metrics.axis == Axis.horizontal &&
|
||||
_currentTab != desiredTab) {
|
||||
setState(() => _currentTab = desiredTab);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
physics: context
|
||||
.watch<DocumentsCubit>()
|
||||
.state
|
||||
.selection
|
||||
.isNotEmpty
|
||||
? const NeverScrollableScrollPhysics()
|
||||
: null,
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return _buildDocumentsTab(
|
||||
connectivityState,
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return _buildSavedViewsTab(
|
||||
connectivityState,
|
||||
context,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -43,7 +43,9 @@ class DocumentPreview extends StatelessWidget {
|
||||
fit: fit,
|
||||
alignment: alignment,
|
||||
cacheKey: "thumb_${document.id}",
|
||||
imageUrl: context.read<PaperlessDocumentsApi>().getThumbnailUrl(document.id),
|
||||
imageUrl: context
|
||||
.read<PaperlessDocumentsApi>()
|
||||
.getThumbnailUrl(document.id),
|
||||
errorWidget: (ctxt, msg, __) => Text(msg),
|
||||
placeholder: (context, value) => Shimmer.fromColors(
|
||||
baseColor: Colors.grey[300]!,
|
||||
|
||||
@@ -42,8 +42,9 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
padding.bottom -
|
||||
kBottomNavigationBarHeight -
|
||||
kToolbarHeight;
|
||||
final maxHeight =
|
||||
highlights != null ? min(600.0, availableHeight) : min(500.0, availableHeight);
|
||||
final maxHeight = highlights != null
|
||||
? min(600.0, availableHeight)
|
||||
: min(500.0, availableHeight);
|
||||
return Card(
|
||||
color: isSelected ? Theme.of(context).colorScheme.inversePrimary : null,
|
||||
child: InkWell(
|
||||
@@ -114,8 +115,10 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
correspondent:
|
||||
context.watch<LabelRepository>().state.correspondents[document.correspondent],
|
||||
correspondent: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.correspondents[document.correspondent],
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
@@ -130,8 +133,10 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
documentType:
|
||||
context.watch<LabelRepository>().state.documentTypes[document.documentType],
|
||||
documentType: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.documentTypes[document.documentType],
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
|
||||
@@ -30,8 +30,9 @@ class DocumentGridItem extends DocumentItem {
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Card(
|
||||
elevation: 1.0,
|
||||
color:
|
||||
isSelected ? Theme.of(context).colorScheme.inversePrimary : Theme.of(context).cardColor,
|
||||
color: isSelected
|
||||
? Theme.of(context).colorScheme.inversePrimary
|
||||
: Theme.of(context).cardColor,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: _onTap,
|
||||
@@ -74,7 +75,8 @@ class DocumentGridItem extends DocumentItem {
|
||||
const Spacer(),
|
||||
TagsWidget(
|
||||
tags: document.tags
|
||||
.map((e) => context.watch<LabelRepository>().state.tags[e]!)
|
||||
.map((e) =>
|
||||
context.watch<LabelRepository>().state.tags[e]!)
|
||||
.toList(),
|
||||
isMultiLine: false,
|
||||
onTagSelected: onTagSelected,
|
||||
|
||||
@@ -22,14 +22,17 @@ class DocumentFilterForm extends StatefulWidget {
|
||||
formKey.currentState?.save();
|
||||
final v = formKey.currentState!.value;
|
||||
return DocumentFilter(
|
||||
correspondent: v[DocumentFilterForm.fkCorrespondent] as IdQueryParameter? ??
|
||||
DocumentFilter.initial.correspondent,
|
||||
correspondent:
|
||||
v[DocumentFilterForm.fkCorrespondent] as IdQueryParameter? ??
|
||||
DocumentFilter.initial.correspondent,
|
||||
documentType: v[DocumentFilterForm.fkDocumentType] as IdQueryParameter? ??
|
||||
DocumentFilter.initial.documentType,
|
||||
storagePath: v[DocumentFilterForm.fkStoragePath] as IdQueryParameter? ??
|
||||
DocumentFilter.initial.storagePath,
|
||||
tags: v[DocumentModel.tagsKey] as TagsQuery? ?? DocumentFilter.initial.tags,
|
||||
query: v[DocumentFilterForm.fkQuery] as TextQuery? ?? DocumentFilter.initial.query,
|
||||
tags:
|
||||
v[DocumentModel.tagsKey] as TagsQuery? ?? DocumentFilter.initial.tags,
|
||||
query: v[DocumentFilterForm.fkQuery] as TextQuery? ??
|
||||
DocumentFilter.initial.query,
|
||||
created: (v[DocumentFilterForm.fkCreatedAt] as DateRangeQuery),
|
||||
added: (v[DocumentFilterForm.fkAddedAt] as DateRangeQuery),
|
||||
asnQuery: initialFilter.asnQuery,
|
||||
@@ -134,12 +137,15 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
}
|
||||
|
||||
void _checkQueryConstraints() {
|
||||
final filter = DocumentFilterForm.assembleFilter(widget.formKey, widget.initialFilter);
|
||||
final filter =
|
||||
DocumentFilterForm.assembleFilter(widget.formKey, widget.initialFilter);
|
||||
if (filter.forceExtendedQuery) {
|
||||
setState(() => _allowOnlyExtendedQuery = true);
|
||||
final queryField = widget.formKey.currentState?.fields[DocumentFilterForm.fkQuery];
|
||||
final queryField =
|
||||
widget.formKey.currentState?.fields[DocumentFilterForm.fkQuery];
|
||||
queryField?.didChange(
|
||||
(queryField.value as TextQuery?)?.copyWith(queryType: QueryType.extended),
|
||||
(queryField.value as TextQuery?)
|
||||
?.copyWith(queryType: QueryType.extended),
|
||||
);
|
||||
} else {
|
||||
setState(() => _allowOnlyExtendedQuery = false);
|
||||
|
||||
@@ -27,10 +27,12 @@ class SortFieldSelectionBottomSheet extends StatefulWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
State<SortFieldSelectionBottomSheet> createState() => _SortFieldSelectionBottomSheetState();
|
||||
State<SortFieldSelectionBottomSheet> createState() =>
|
||||
_SortFieldSelectionBottomSheetState();
|
||||
}
|
||||
|
||||
class _SortFieldSelectionBottomSheetState extends State<SortFieldSelectionBottomSheet> {
|
||||
class _SortFieldSelectionBottomSheetState
|
||||
extends State<SortFieldSelectionBottomSheet> {
|
||||
late SortField? _currentSortField;
|
||||
late SortOrder _currentSortOrder;
|
||||
|
||||
|
||||
@@ -33,12 +33,15 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
|
||||
onPressed: () async {
|
||||
final shouldDelete = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => BulkDeleteConfirmationDialog(state: state),
|
||||
builder: (context) =>
|
||||
BulkDeleteConfirmationDialog(state: state),
|
||||
) ??
|
||||
false;
|
||||
if (shouldDelete) {
|
||||
try {
|
||||
await context.read<DocumentsCubit>().bulkDelete(state.selection);
|
||||
await context
|
||||
.read<DocumentsCubit>()
|
||||
.bulkDelete(state.selection);
|
||||
showSnackBar(
|
||||
context,
|
||||
S.of(context)!.documentsSuccessfullyDeleted,
|
||||
@@ -62,21 +65,24 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
|
||||
label: Text(S.of(context)!.correspondent),
|
||||
avatar: const Icon(Icons.edit),
|
||||
onPressed: () {
|
||||
pushBulkEditCorrespondentRoute(context, selection: state.selection);
|
||||
pushBulkEditCorrespondentRoute(context,
|
||||
selection: state.selection);
|
||||
},
|
||||
).paddedOnly(left: 8, right: 4),
|
||||
ActionChip(
|
||||
label: Text(S.of(context)!.documentType),
|
||||
avatar: const Icon(Icons.edit),
|
||||
onPressed: () async {
|
||||
pushBulkEditDocumentTypeRoute(context, selection: state.selection);
|
||||
pushBulkEditDocumentTypeRoute(context,
|
||||
selection: state.selection);
|
||||
},
|
||||
).paddedOnly(left: 8, right: 4),
|
||||
ActionChip(
|
||||
label: Text(S.of(context)!.storagePath),
|
||||
avatar: const Icon(Icons.edit),
|
||||
onPressed: () async {
|
||||
pushBulkEditStoragePathRoute(context, selection: state.selection);
|
||||
pushBulkEditStoragePathRoute(context,
|
||||
selection: state.selection);
|
||||
},
|
||||
).paddedOnly(left: 8, right: 4),
|
||||
_buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4),
|
||||
|
||||
@@ -50,7 +50,9 @@ class SortDocumentsButton extends StatelessWidget {
|
||||
initialSortField: state.filter.sortField,
|
||||
initialSortOrder: state.filter.sortOrder,
|
||||
onSubmit: (field, order) {
|
||||
return context.read<DocumentsCubit>().updateCurrentFilter(
|
||||
return context
|
||||
.read<DocumentsCubit>()
|
||||
.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
sortField: field,
|
||||
sortOrder: order,
|
||||
|
||||
Reference in New Issue
Block a user