fix: Fixed saved views bug, formatted files, minor changes

This commit is contained in:
Anton Stubenbord
2023-06-10 16:29:12 +02:00
parent 3161343c35
commit 4c3f97136e
93 changed files with 1049 additions and 585 deletions

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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(

View File

@@ -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,
);
},
),
],
),
),
),
),
),

View File

@@ -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]!,

View File

@@ -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),

View File

@@ -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,

View File

@@ -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);

View File

@@ -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;

View File

@@ -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),

View File

@@ -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,