Redesigned sorting

This commit is contained in:
Anton Stubenbord
2022-11-16 01:18:04 +01:00
parent 67ddf90a41
commit e9019bca9c
6 changed files with 368 additions and 321 deletions

View File

@@ -1,13 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/core/service/github_issue_service.dart';
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/model/document.model.dart';
@@ -19,14 +14,15 @@ import 'package:paperless_mobile/features/documents/view/widgets/search/document
import 'package:paperless_mobile/features/documents/view/widgets/selection/documents_page_app_bar.dart';
import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart';
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart';
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
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:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
class DocumentsPage extends StatefulWidget {
@@ -42,7 +38,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
firstPageKey: 1,
);
final PanelController _panelController = PanelController();
final PanelController _filterPanelController = PanelController();
@override
void initState() {
@@ -99,9 +95,9 @@ class _DocumentsPageState extends State<DocumentsPage> {
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_panelController.isPanelOpen) {
if (_filterPanelController.isPanelOpen) {
FocusScope.of(context).unfocus();
_panelController.close();
_filterPanelController.close();
return false;
}
final documentsCubit = BlocProvider.of<DocumentsCubit>(context);
@@ -129,7 +125,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
backdropEnabled: true,
parallaxEnabled: true,
parallaxOffset: .5,
controller: _panelController,
controller: _filterPanelController,
defaultPanelState: PanelState.CLOSED,
minHeight: 48,
maxHeight: (MediaQuery.of(context).size.height * 3) / 4,
@@ -140,7 +136,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
body: _buildBody(connectivityState),
color: Theme.of(context).scaffoldBackgroundColor,
panelBuilder: (scrollController) => DocumentFilterPanel(
panelController: _panelController,
panelController: _filterPanelController,
scrollController: scrollController,
),
),
@@ -194,33 +190,31 @@ class _DocumentsPageState extends State<DocumentsPage> {
return RefreshIndicator(
onRefresh: _onRefresh,
child: Container(
child: CustomScrollView(
slivers: [
DocumentsPageAppBar(
actions: [
const SortDocumentsButton(),
IconButton(
icon: Icon(
settings.preferredViewType == ViewType.grid
? Icons.list
: Icons.grid_view,
),
onPressed: () =>
BlocProvider.of<ApplicationSettingsCubit>(context)
.setViewType(
settings.preferredViewType.toggle()),
child: CustomScrollView(
slivers: [
DocumentsPageAppBar(
actions: [
const SortDocumentsButton(),
IconButton(
icon: Icon(
settings.preferredViewType == ViewType.grid
? Icons.list
: Icons.grid_view,
),
],
),
child,
SliverToBoxAdapter(
child: SizedBox(
height: MediaQuery.of(context).size.height / 4,
onPressed: () =>
BlocProvider.of<ApplicationSettingsCubit>(context)
.setViewType(
settings.preferredViewType.toggle()),
),
)
],
),
],
),
child,
SliverToBoxAdapter(
child: SizedBox(
height: MediaQuery.of(context).size.height / 4,
),
)
],
),
);
},

View File

@@ -52,16 +52,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
static const fkCreatedAt = DocumentModel.createdKey;
static const fkAddedAt = DocumentModel.addedKey;
static const _sortFields = [
SortField.created,
SortField.added,
SortField.modified,
SortField.title,
SortField.correspondentName,
SortField.documentType,
SortField.archiveSerialNumber
];
final _formKey = GlobalKey<FormBuilderState>();
late final DocumentsCubit _documentsCubit;
@@ -137,7 +127,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
const SizedBox(
height: 16.0,
),
_buildSortByChipsList(context, state),
Align(
alignment: Alignment.centerLeft,
child: Text(S.of(context).documentsFilterPageSearchLabel),
@@ -448,71 +437,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
);
}
Widget _buildSortByChipsList(BuildContext context, DocumentsState state) {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context).documentsPageOrderByLabel,
),
SizedBox(
height: kToolbarHeight,
child: ListView.separated(
itemCount: _sortFields.length,
scrollDirection: Axis.horizontal,
separatorBuilder: (context, index) => const SizedBox(
width: 8.0,
),
itemBuilder: (context, index) => _buildActionChip(
_sortFields[index], state.filter.sortField, context),
),
),
],
).padded();
}
Widget _buildActionChip(SortField sortField,
SortField? currentlySelectedOrder, BuildContext context) {
String text;
switch (sortField) {
case SortField.archiveSerialNumber:
text = S.of(context).documentArchiveSerialNumberPropertyShortLabel;
break;
case SortField.correspondentName:
text = S.of(context).documentCorrespondentPropertyLabel;
break;
case SortField.title:
text = S.of(context).documentTitlePropertyLabel;
break;
case SortField.documentType:
text = S.of(context).documentDocumentTypePropertyLabel;
break;
case SortField.created:
text = S.of(context).documentCreatedPropertyLabel;
break;
case SortField.added:
text = S.of(context).documentAddedPropertyLabel;
break;
case SortField.modified:
text = S.of(context).documentModifiedPropertyLabel;
break;
}
final docBloc = BlocProvider.of<DocumentsCubit>(context);
return ActionChip(
label: Text(text),
avatar: currentlySelectedOrder == sortField
? const Icon(
Icons.done,
color: Colors.green,
)
: null,
onPressed: () => docBloc.updateFilter(
filter: docBloc.state.filter.copyWith(sortField: sortField)),
);
}
void _onApplyFilter() async {
if (_formKey.currentState?.saveAndValidate() ?? false) {
final v = _formKey.currentState!.value;

View File

@@ -0,0 +1,136 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/sort_field.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/sort_order.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
class SortFieldSelectionBottomSheet extends StatefulWidget {
const SortFieldSelectionBottomSheet({super.key});
@override
State<SortFieldSelectionBottomSheet> createState() =>
_SortFieldSelectionBottomSheetState();
}
class _SortFieldSelectionBottomSheetState
extends State<SortFieldSelectionBottomSheet> {
static const _sortFields = [
SortField.created,
SortField.added,
SortField.modified,
SortField.title,
SortField.correspondentName,
SortField.documentType,
SortField.archiveSerialNumber
];
SortField? _selectedFieldLoading;
SortOrder? _selectedOrderLoading;
@override
Widget build(BuildContext context) {
return ClipRRect(
child: BlocBuilder<DocumentsCubit, DocumentsState>(
bloc: getIt<DocumentsCubit>(),
builder: (context, state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
S.of(context).documentsPageOrderByLabel,
style: Theme.of(context).textTheme.titleSmall,
textAlign: TextAlign.start,
).padded(EdgeInsets.symmetric(horizontal: 16, vertical: 16)),
Column(
children: _sortFields
.map(
(e) => _buildSortOption(
e,
state.filter.sortOrder,
state.filter.sortField == e,
_selectedFieldLoading == e,
),
)
.toList(),
),
],
);
},
),
);
}
Widget _buildSortOption(
SortField field,
SortOrder order,
bool isCurrentlySelected,
bool isNextSelected,
) {
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 32),
title: Text(
_localizedSortField(field),
style: Theme.of(context).textTheme.bodyText2,
),
trailing: isNextSelected
? (_buildOrderIcon(_selectedOrderLoading!))
: (_selectedOrderLoading == null && isCurrentlySelected
? _buildOrderIcon(order)
: null),
onTap: () async {
setState(() {
_selectedFieldLoading = field;
_selectedOrderLoading =
isCurrentlySelected ? order.toggle() : SortOrder.descending;
});
BlocProvider.of<DocumentsCubit>(context)
.updateCurrentFilter((filter) => filter.copyWith(
sortOrder: isCurrentlySelected
? order.toggle()
: SortOrder.descending,
sortField: field,
))
.whenComplete(() {
if (mounted) {
setState(() {
_selectedFieldLoading = null;
_selectedOrderLoading = null;
});
}
});
},
);
}
Widget _buildOrderIcon(SortOrder order) {
if (order == SortOrder.ascending) {
return Icon(Icons.arrow_upward);
}
return Icon(Icons.arrow_downward);
}
String _localizedSortField(SortField sortField) {
switch (sortField) {
case SortField.archiveSerialNumber:
return S.of(context).documentArchiveSerialNumberPropertyShortLabel;
case SortField.correspondentName:
return S.of(context).documentCorrespondentPropertyLabel;
case SortField.title:
return S.of(context).documentTitlePropertyLabel;
case SortField.documentType:
return S.of(context).documentDocumentTypePropertyLabel;
case SortField.created:
return S.of(context).documentCreatedPropertyLabel;
case SortField.added:
return S.of(context).documentAddedPropertyLabel;
case SortField.modified:
return S.of(context).documentModifiedPropertyLabel;
}
}
}

View File

@@ -1,11 +1,17 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/sort_field.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/sort_order.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:paperless_mobile/features/documents/view/widgets/search/sort_field_selection_bottom_sheet.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart';
class SortDocumentsButton extends StatefulWidget {
@@ -18,52 +24,32 @@ class SortDocumentsButton extends StatefulWidget {
}
class _SortDocumentsButtonState extends State<SortDocumentsButton> {
bool _isLoading = false;
@override
Widget build(BuildContext context) {
return BlocBuilder<DocumentsCubit, DocumentsState>(
builder: (context, state) {
Widget child;
if (_isLoading) {
child = const FittedBox(
fit: BoxFit.scaleDown,
child: RefreshProgressIndicator(
strokeWidth: 4.0,
backgroundColor: Colors.transparent,
),
);
} else {
final bool isAscending =
state.filter.sortOrder == SortOrder.ascending;
child = IconButton(
icon: FaIcon(
isAscending
? FontAwesomeIcons.arrowDownAZ
: FontAwesomeIcons.arrowUpZA,
),
onPressed: () async {
setState(() => _isLoading = true);
try {
await BlocProvider.of<DocumentsCubit>(context)
.updateCurrentFilter(
(filter) => filter.copyWith(
sortOrder: state.filter.sortOrder.toggle(),
),
);
} on ErrorMessage catch (error, stackTrace) {
showError(context, error, stackTrace);
} finally {
setState(() => _isLoading = false);
}
},
);
}
return SizedBox(
height: Theme.of(context).iconTheme.size,
width: Theme.of(context).iconTheme.size,
child: child,
);
},
return IconButton(
icon: Icon(Icons.sort),
onPressed: _onOpenSortBottomSheet,
);
}
void _onOpenSortBottomSheet() {
showModalBottomSheet(
elevation: 2,
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
builder: (context) => BlocProvider.value(
value: getIt<DocumentsCubit>(),
child: FractionallySizedBox(
heightFactor: .6,
child: const SortFieldSelectionBottomSheet(),
),
),
);
}
}