mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-14 12:12:21 -06:00
feat: Add saved views to landing page
This commit is contained in:
@@ -61,15 +61,15 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
length: showSavedViews ? 2 : 1,
|
length: showSavedViews ? 2 : 1,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
Future.wait([
|
// Future.wait([
|
||||||
context.read<DocumentsCubit>().reload(),
|
// context.read<DocumentsCubit>().reload(),
|
||||||
context.read<SavedViewCubit>().reload(),
|
// context.read<SavedViewCubit>().reload(),
|
||||||
]).onError<PaperlessApiException>(
|
// ]).onError<PaperlessApiException>(
|
||||||
(error, stackTrace) {
|
// (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
// showErrorMessage(context, error, stackTrace);
|
||||||
return [];
|
// return [];
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
_tabController.addListener(_tabChangesListener);
|
_tabController.addListener(_tabChangesListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
return SafeArea(
|
return SafeArea(
|
||||||
top: true,
|
top: true,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
drawer: AppDrawer(),
|
drawer: const AppDrawer(),
|
||||||
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||||
@@ -26,6 +28,7 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Card(
|
child: Card(
|
||||||
@@ -64,6 +67,7 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
const SliverToBoxAdapter(
|
const SliverToBoxAdapter(
|
||||||
child: SizedBox(width: 8),
|
child: SizedBox(width: 8),
|
||||||
),
|
),
|
||||||
|
if (currentUser.canViewTags)
|
||||||
TagsWidget.sliver(
|
TagsWidget.sliver(
|
||||||
tags: document.tags
|
tags: document.tags
|
||||||
.map((e) => context
|
.map((e) => context
|
||||||
@@ -90,6 +94,7 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
if (currentUser.canViewCorrespondents)
|
||||||
CorrespondentWidget(
|
CorrespondentWidget(
|
||||||
correspondent: context
|
correspondent: context
|
||||||
.watch<LabelRepository>()
|
.watch<LabelRepository>()
|
||||||
@@ -97,6 +102,7 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
.correspondents[document.correspondent],
|
.correspondents[document.correspondent],
|
||||||
onSelected: onCorrespondentSelected,
|
onSelected: onCorrespondentSelected,
|
||||||
),
|
),
|
||||||
|
if (currentUser.canViewDocumentTypes)
|
||||||
DocumentTypeWidget(
|
DocumentTypeWidget(
|
||||||
documentType: context
|
documentType: context
|
||||||
.watch<LabelRepository>()
|
.watch<LabelRepository>()
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import 'package:provider/provider.dart';
|
|||||||
class DocumentListItem extends DocumentItem {
|
class DocumentListItem extends DocumentItem {
|
||||||
static const _a4AspectRatio = 1 / 1.4142;
|
static const _a4AspectRatio = 1 / 1.4142;
|
||||||
|
|
||||||
|
final Color? backgroundColor;
|
||||||
const DocumentListItem({
|
const DocumentListItem({
|
||||||
super.key,
|
super.key,
|
||||||
|
this.backgroundColor,
|
||||||
required super.document,
|
required super.document,
|
||||||
required super.isSelected,
|
required super.isSelected,
|
||||||
required super.isSelectionActive,
|
required super.isSelectionActive,
|
||||||
@@ -31,6 +33,7 @@ class DocumentListItem extends DocumentItem {
|
|||||||
final labels = context.watch<LabelRepository>().state;
|
final labels = context.watch<LabelRepository>().state;
|
||||||
return Material(
|
return Material(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
tileColor: backgroundColor,
|
||||||
dense: true,
|
dense: true,
|
||||||
selected: isSelected,
|
selected: isSelected,
|
||||||
onTap: () => _onTap(),
|
onTap: () => _onTap(),
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ class HomeShellWidget extends StatelessWidget {
|
|||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider(
|
Provider(
|
||||||
|
lazy: false,
|
||||||
create: (context) => DocumentsCubit(
|
create: (context) => DocumentsCubit(
|
||||||
context.read(),
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
|||||||
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
||||||
import 'package:paperless_mobile/features/landing/view/widgets/expansion_card.dart';
|
import 'package:paperless_mobile/features/landing/view/widgets/expansion_card.dart';
|
||||||
import 'package:paperless_mobile/features/landing/view/widgets/mime_types_pie_chart.dart';
|
import 'package:paperless_mobile/features/landing/view/widgets/mime_types_pie_chart.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view_details/view/saved_view_details_preview.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/routes/routes.dart';
|
import 'package:paperless_mobile/routes/routes.dart';
|
||||||
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||||
@@ -52,6 +54,27 @@ class _LandingPageState extends State<LandingPage> {
|
|||||||
).padded(24),
|
).padded(24),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(child: _buildStatisticsCard(context)),
|
SliverToBoxAdapter(child: _buildStatisticsCard(context)),
|
||||||
|
BlocBuilder<SavedViewCubit, SavedViewState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return state.maybeWhen(
|
||||||
|
loaded: (savedViews) {
|
||||||
|
return SliverList.builder(
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return SavedViewDetailsPreview(
|
||||||
|
savedView: savedViews.values.elementAt(index),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: savedViews.length,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
orElse: () => const SliverToBoxAdapter(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
|
||||||
|
|
||||||
class ExpansionCard extends StatelessWidget {
|
class ExpansionCard extends StatelessWidget {
|
||||||
final Widget title;
|
final Widget title;
|
||||||
@@ -10,7 +9,7 @@ class ExpansionCard extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
margin: EdgeInsets.all(16),
|
margin: const EdgeInsets.all(16),
|
||||||
child: Theme(
|
child: Theme(
|
||||||
data: Theme.of(context).copyWith(
|
data: Theme.of(context).copyWith(
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
@@ -23,6 +22,7 @@ class ExpansionCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: ExpansionTile(
|
child: ExpansionTile(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
initiallyExpanded: true,
|
initiallyExpanded: true,
|
||||||
title: title,
|
title: title,
|
||||||
children: [content],
|
children: [content],
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState>
|
|||||||
this._labelRepository,
|
this._labelRepository,
|
||||||
this._userState, {
|
this._userState, {
|
||||||
required this.savedView,
|
required this.savedView,
|
||||||
|
int initialCount = 25,
|
||||||
}) : super(
|
}) : super(
|
||||||
SavedViewDetailsState(
|
SavedViewDetailsState(
|
||||||
correspondents: _labelRepository.state.correspondents,
|
correspondents: _labelRepository.state.correspondents,
|
||||||
@@ -56,7 +57,12 @@ class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
updateFilter(filter: savedView.toDocumentFilter());
|
updateFilter(
|
||||||
|
filter: savedView.toDocumentFilter().copyWith(
|
||||||
|
page: 1,
|
||||||
|
pageSize: initialCount,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setViewType(ViewType viewType) {
|
void setViewType(ViewType viewType) {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
|
||||||
|
part 'saved_view_preview_state.dart';
|
||||||
|
part 'saved_view_preview_cubit.freezed.dart';
|
||||||
|
|
||||||
|
class SavedViewPreviewCubit extends Cubit<SavedViewPreviewState> {
|
||||||
|
final PaperlessDocumentsApi _api;
|
||||||
|
final SavedView view;
|
||||||
|
SavedViewPreviewCubit(this._api, this.view)
|
||||||
|
: super(const SavedViewPreviewState.initial());
|
||||||
|
|
||||||
|
Future<void> initialize() async {
|
||||||
|
emit(const SavedViewPreviewState.loading());
|
||||||
|
try {
|
||||||
|
final documents = await _api.findAll(
|
||||||
|
view.toDocumentFilter().copyWith(
|
||||||
|
page: 1,
|
||||||
|
pageSize: 5,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
emit(SavedViewPreviewState.loaded(documents: documents.results));
|
||||||
|
} catch (e) {
|
||||||
|
emit(const SavedViewPreviewState.error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
part of 'saved_view_preview_cubit.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SavedViewPreviewState with _$SavedViewPreviewState {
|
||||||
|
const factory SavedViewPreviewState.initial() = _Initial;
|
||||||
|
const factory SavedViewPreviewState.loading() = _Loading;
|
||||||
|
const factory SavedViewPreviewState.loaded({
|
||||||
|
required List<DocumentModel> documents,
|
||||||
|
}) = _Loaded;
|
||||||
|
const factory SavedViewPreviewState.error() = _Error;
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart';
|
||||||
|
import 'package:paperless_mobile/features/landing/view/widgets/expansion_card.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_preview_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/routes/typed/branches/documents_route.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class SavedViewDetailsPreview extends StatelessWidget {
|
||||||
|
final SavedView savedView;
|
||||||
|
const SavedViewDetailsPreview({
|
||||||
|
super.key,
|
||||||
|
required this.savedView,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Provider(
|
||||||
|
create: (context) =>
|
||||||
|
SavedViewPreviewCubit(context.read(), savedView)..initialize(),
|
||||||
|
builder: (context, child) {
|
||||||
|
return ExpansionCard(
|
||||||
|
title: Text(savedView.name),
|
||||||
|
content: BlocBuilder<SavedViewPreviewCubit, SavedViewPreviewState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
state.maybeWhen(
|
||||||
|
loaded: (documents) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
for (final document in documents)
|
||||||
|
DocumentListItem(
|
||||||
|
document: document,
|
||||||
|
isLabelClickable: false,
|
||||||
|
isSelected: false,
|
||||||
|
isSelectionActive: false,
|
||||||
|
onTap: (document) {
|
||||||
|
DocumentDetailsRoute($extra: document)
|
||||||
|
.push(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
child: Text("Show more"),
|
||||||
|
onPressed: documents.length >= 5 ? () {} : null,
|
||||||
|
),
|
||||||
|
TextButton.icon(
|
||||||
|
icon: Icon(Icons.open_in_new),
|
||||||
|
label: Text("Show in documents"),
|
||||||
|
onPressed: () {
|
||||||
|
context.read<DocumentsCubit>().updateFilter(
|
||||||
|
filter: savedView.toDocumentFilter(),
|
||||||
|
);
|
||||||
|
DocumentsRoute().go(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
error: () =>
|
||||||
|
const Text("Error loading preview"), //TODO: INTL
|
||||||
|
orElse: () => const Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user