mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 06:07:54 -06:00
fix: Fix scrolling issue, update selection app bar
This commit is contained in:
@@ -23,8 +23,8 @@ class SliverSearchBar extends StatelessWidget {
|
||||
floating: floating,
|
||||
pinned: pinned,
|
||||
delegate: CustomizableSliverPersistentHeaderDelegate(
|
||||
minExtent: kToolbarHeight + 8,
|
||||
maxExtent: kToolbarHeight + 8,
|
||||
minExtent: kToolbarHeight,
|
||||
maxExtent: kToolbarHeight,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: SearchBar(
|
||||
|
||||
@@ -114,31 +114,38 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
},
|
||||
builder: (context, connectivityState) {
|
||||
return SafeArea(
|
||||
top: context.read<DocumentsCubit>().state.selection.isEmpty,
|
||||
child: Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
||||
return b.Badge(
|
||||
position: b.BadgePosition.topEnd(top: -12, end: -6),
|
||||
showBadge: appliedFiltersCount > 0,
|
||||
badgeContent: Text(
|
||||
'$appliedFiltersCount',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
final show = state.selection.isEmpty;
|
||||
return AnimatedScale(
|
||||
scale: show ? 1 : 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeIn,
|
||||
child: b.Badge(
|
||||
position: b.BadgePosition.topEnd(top: -12, end: -6),
|
||||
showBadge: appliedFiltersCount > 0,
|
||||
badgeContent: Text(
|
||||
'$appliedFiltersCount',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
animationType: b.BadgeAnimationType.fade,
|
||||
badgeColor: Colors.red,
|
||||
child: _currentTab == 0
|
||||
? FloatingActionButton(
|
||||
child: const Icon(Icons.filter_alt_outlined),
|
||||
onPressed: _openDocumentFilter,
|
||||
)
|
||||
: FloatingActionButton(
|
||||
child: const Icon(Icons.add),
|
||||
onPressed: () => _onCreateSavedView(state.filter),
|
||||
),
|
||||
),
|
||||
animationType: b.BadgeAnimationType.fade,
|
||||
badgeColor: Colors.red,
|
||||
child: _currentTab == 0
|
||||
? FloatingActionButton(
|
||||
child: const Icon(Icons.filter_alt_outlined),
|
||||
onPressed: _openDocumentFilter,
|
||||
)
|
||||
: FloatingActionButton(
|
||||
child: const Icon(Icons.add),
|
||||
onPressed: () => _onCreateSavedView(state.filter),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -296,7 +303,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
_currentTab != 0 ||
|
||||
currState.isLoading ||
|
||||
currState.isLastPageLoaded) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
final offset = notification.metrics.pixels;
|
||||
@@ -311,6 +318,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
stackTrace,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
@@ -361,46 +369,25 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
|
||||
Widget _buildViewActions() {
|
||||
return SliverToBoxAdapter(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const SortDocumentsButton(),
|
||||
BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
return ViewTypeSelectionWidget(
|
||||
child: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SortDocumentsButton(
|
||||
enabled: state.selection.isEmpty,
|
||||
),
|
||||
ViewTypeSelectionWidget(
|
||||
viewType: state.viewType,
|
||||
onChanged: context.read<DocumentsCubit>().setViewType,
|
||||
);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
).paddedSymmetrically(horizontal: 8, vertical: 4),
|
||||
);
|
||||
}
|
||||
|
||||
void _onDelete(DocumentsState documentsState) async {
|
||||
final shouldDelete = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
BulkDeleteConfirmationDialog(state: documentsState),
|
||||
) ??
|
||||
false;
|
||||
if (shouldDelete) {
|
||||
try {
|
||||
await context
|
||||
.read<DocumentsCubit>()
|
||||
.bulkDelete(documentsState.selection);
|
||||
showSnackBar(
|
||||
context,
|
||||
S.of(context)!.documentsSuccessfullyDeleted,
|
||||
);
|
||||
context.read<DocumentsCubit>().resetSelection();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onCreateSavedView(DocumentFilter filter) async {
|
||||
final newView = await Navigator.of(context).push<SavedView?>(
|
||||
MaterialPageRoute(
|
||||
|
||||
@@ -44,6 +44,7 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
? min(600.0, availableHeight)
|
||||
: min(500.0, availableHeight);
|
||||
return Card(
|
||||
color: isSelected ? Theme.of(context).colorScheme.inversePrimary : null,
|
||||
child: InkWell(
|
||||
enableFeedback: true,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
|
||||
@@ -30,106 +30,108 @@ class DocumentListItem extends DocumentItem {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DocumentTypeBlocProvider(
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
selected: isSelected,
|
||||
onTap: () => _onTap(),
|
||||
selectedTileColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
onLongPress: () => onSelected?.call(document),
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
AbsorbPointer(
|
||||
absorbing: isSelectionActive,
|
||||
child: CorrespondentWidget(
|
||||
isClickable: isLabelClickable,
|
||||
correspondentId: document.correspondent,
|
||||
onSelected: onCorrespondentSelected,
|
||||
child: Material(
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
selected: isSelected,
|
||||
onTap: () => _onTap(),
|
||||
selectedTileColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
onLongPress: () => onSelected?.call(document),
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
AbsorbPointer(
|
||||
absorbing: isSelectionActive,
|
||||
child: CorrespondentWidget(
|
||||
isClickable: isLabelClickable,
|
||||
correspondentId: document.correspondent,
|
||||
onSelected: onCorrespondentSelected,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
document.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: isSelectionActive,
|
||||
child: TagsWidget(
|
||||
isClickable: isLabelClickable,
|
||||
tagIds: document.tags,
|
||||
isMultiLine: false,
|
||||
onTagSelected: (id) => onTagSelected?.call(id),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
document.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
AbsorbPointer(
|
||||
absorbing: isSelectionActive,
|
||||
child: TagsWidget(
|
||||
isClickable: isLabelClickable,
|
||||
tagIds: document.tags,
|
||||
isMultiLine: false,
|
||||
onTagSelected: (id) => onTagSelected?.call(id),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: BlocBuilder<LabelCubit<DocumentType>,
|
||||
LabelState<DocumentType>>(
|
||||
builder: (context, docTypes) {
|
||||
return RichText(
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
text: TextSpan(
|
||||
text: DateFormat.yMMMd().format(document.created),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall
|
||||
?.apply(color: Colors.grey),
|
||||
children: document.documentType != null
|
||||
? [
|
||||
const TextSpan(text: '\u30FB'),
|
||||
TextSpan(
|
||||
text: docTypes
|
||||
.labels[document.documentType]?.name,
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
// Row(
|
||||
// children: [
|
||||
// Text(
|
||||
// DateFormat.yMMMd().format(document.created),
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .bodySmall
|
||||
// ?.apply(color: Colors.grey),
|
||||
// ),
|
||||
// if (document.documentType != null) ...[
|
||||
// Text("\u30FB"),
|
||||
// DocumentTypeWidget(
|
||||
// documentTypeId: document.documentType,
|
||||
// textStyle: Theme.of(context).textTheme.bodySmall?.apply(
|
||||
// color: Colors.grey,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ],
|
||||
// ),
|
||||
),
|
||||
isThreeLine: document.tags.isNotEmpty,
|
||||
leading: AspectRatio(
|
||||
aspectRatio: _a4AspectRatio,
|
||||
child: GestureDetector(
|
||||
child: DocumentPreview(
|
||||
document: document,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
enableHero: enableHeroAnimation,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child:
|
||||
BlocBuilder<LabelCubit<DocumentType>, LabelState<DocumentType>>(
|
||||
builder: (context, docTypes) {
|
||||
return RichText(
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
text: TextSpan(
|
||||
text: DateFormat.yMMMd().format(document.created),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall
|
||||
?.apply(color: Colors.grey),
|
||||
children: document.documentType != null
|
||||
? [
|
||||
const TextSpan(text: '\u30FB'),
|
||||
TextSpan(
|
||||
text:
|
||||
docTypes.labels[document.documentType]?.name,
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
// Row(
|
||||
// children: [
|
||||
// Text(
|
||||
// DateFormat.yMMMd().format(document.created),
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .bodySmall
|
||||
// ?.apply(color: Colors.grey),
|
||||
// ),
|
||||
// if (document.documentType != null) ...[
|
||||
// Text("\u30FB"),
|
||||
// DocumentTypeWidget(
|
||||
// documentTypeId: document.documentType,
|
||||
// textStyle: Theme.of(context).textTheme.bodySmall?.apply(
|
||||
// color: Colors.grey,
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ],
|
||||
// ),
|
||||
),
|
||||
isThreeLine: document.tags.isNotEmpty,
|
||||
leading: AspectRatio(
|
||||
aspectRatio: _a4AspectRatio,
|
||||
child: GestureDetector(
|
||||
child: DocumentPreview(
|
||||
document: document,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
enableHero: enableHeroAnimation,
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(8.0),
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(8.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,11 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverAppBar(
|
||||
stretch: false,
|
||||
pinned: true,
|
||||
floating: true,
|
||||
snap: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
title: Text(
|
||||
S.of(context)!.countSelected(state.selection.length),
|
||||
),
|
||||
|
||||
@@ -29,6 +29,10 @@ class ViewTypeSelectionWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
return PopupMenuButton<ViewType>(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: 4 * 56.0,
|
||||
maxWidth: 5 * 56.0,
|
||||
), // Ensures text is not split into two lines
|
||||
position: PopupMenuPosition.under,
|
||||
initialValue: viewType,
|
||||
icon: Icon(icon),
|
||||
@@ -70,7 +74,10 @@ class ViewTypeSelectionWidget extends StatelessWidget {
|
||||
child: ListTile(
|
||||
selected: selected,
|
||||
trailing: selected ? const Icon(Icons.done) : null,
|
||||
title: Text(label),
|
||||
title: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
),
|
||||
iconColor: Theme.of(context).colorScheme.onSurface,
|
||||
textColor: Theme.of(context).colorScheme.onSurface,
|
||||
leading: Icon(icon),
|
||||
|
||||
@@ -8,8 +8,10 @@ import 'package:paperless_mobile/features/documents/view/widgets/search/sort_fie
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
|
||||
class SortDocumentsButton extends StatelessWidget {
|
||||
final bool enabled;
|
||||
const SortDocumentsButton({
|
||||
super.key,
|
||||
this.enabled = true,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -24,47 +26,50 @@ class SortDocumentsButton extends StatelessWidget {
|
||||
? Icons.arrow_upward
|
||||
: Icons.arrow_downward),
|
||||
label: Text(translateSortField(context, state.filter.sortField)),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
elevation: 2,
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
builder: (_) => BlocProvider<DocumentsCubit>.value(
|
||||
value: context.read<DocumentsCubit>(),
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
context.read<LabelRepository<DocumentType>>(),
|
||||
onPressed: enabled
|
||||
? () {
|
||||
showModalBottomSheet(
|
||||
elevation: 2,
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<Correspondent>(
|
||||
context.read<LabelRepository<Correspondent>>(),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: SortFieldSelectionBottomSheet(
|
||||
initialSortField: state.filter.sortField,
|
||||
initialSortOrder: state.filter.sortOrder,
|
||||
onSubmit: (field, order) =>
|
||||
context.read<DocumentsCubit>().updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
sortField: field,
|
||||
sortOrder: order,
|
||||
),
|
||||
builder: (_) => BlocProvider<DocumentsCubit>.value(
|
||||
value: context.read<DocumentsCubit>(),
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
context.read<LabelRepository<DocumentType>>(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<Correspondent>(
|
||||
context.read<LabelRepository<Correspondent>>(),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: SortFieldSelectionBottomSheet(
|
||||
initialSortField: state.filter.sortField,
|
||||
initialSortOrder: state.filter.sortOrder,
|
||||
onSubmit: (field, order) => context
|
||||
.read<DocumentsCubit>()
|
||||
.updateCurrentFilter(
|
||||
(filter) => filter.copyWith(
|
||||
sortField: field,
|
||||
sortOrder: order,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
: null,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user