feat: Add improved date input, fix bugs, restructurings

This commit is contained in:
Anton Stubenbord
2023-10-20 17:28:54 +02:00
parent 18e178b644
commit 652abb6945
32 changed files with 840 additions and 775 deletions

View File

@@ -2,20 +2,18 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:open_filex/open_filex.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/widgets/material/colored_tab_bar.dart';
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_content_widget.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_meta_data_widget.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_overview_widget.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_permissions_widget.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/document_share_button.dart';
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
@@ -29,13 +27,21 @@ import 'package:paperless_mobile/routes/typed/shells/authenticated_route.dart';
import 'package:paperless_mobile/theme.dart';
class DocumentDetailsPage extends StatefulWidget {
final int id;
final String? title;
final bool isLabelClickable;
final String? titleAndContentQueryString;
final String? thumbnailUrl;
final String? heroTag;
const DocumentDetailsPage({
Key? key,
this.isLabelClickable = true,
this.titleAndContentQueryString,
this.thumbnailUrl,
required this.id,
this.heroTag,
this.title,
}) : super(key: key);
@override
@@ -57,152 +63,157 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
final hasMultiUserSupport =
context.watch<LocalUserAccount>().hasMultiUserSupport;
final tabLength = 4 + (hasMultiUserSupport && false ? 1 : 0);
final title = context.watch<DocumentDetailsCubit>().state.document.title;
return AnnotatedRegion(
value: buildOverlayStyle(
Theme.of(context),
systemNavigationBarColor: Theme.of(context).bottomAppBarTheme.color,
),
child: WillPopScope(
onWillPop: () async {
Navigator.of(context)
.pop(context.read<DocumentDetailsCubit>().state.document);
return false;
},
child: DefaultTabController(
length: tabLength,
child: BlocListener<ConnectivityCubit, ConnectivityState>(
listenWhen: (previous, current) =>
!previous.isConnected && current.isConnected,
listener: (context, state) {
context.read<DocumentDetailsCubit>().loadMetaData();
},
child: BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
builder: (context, state) {
return DefaultTabController(
length: tabLength,
child: Scaffold(
extendBodyBehindAppBar: false,
floatingActionButtonLocation:
FloatingActionButtonLocation.endDocked,
floatingActionButton: _buildEditButton(),
floatingActionButton: switch (state) {
DocumentDetailsLoaded(document: var document) =>
_buildEditButton(document),
_ => null
},
bottomNavigationBar: _buildBottomAppBar(),
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context),
sliver: SliverAppBar(
title: Text(title),
leading: const BackButton(),
pinned: true,
forceElevated: innerBoxIsScrolled,
collapsedHeight: kToolbarHeight,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
background: BlocBuilder<DocumentDetailsCubit,
DocumentDetailsState>(
builder: (context, state) {
return Hero(
tag: "thumb_${state.document.id}",
child: GestureDetector(
onTap: () {
DocumentPreviewRoute($extra: state.document)
.push(context);
},
child: Stack(
alignment: Alignment.topCenter,
children: [
Positioned.fill(
child: DocumentPreview(
enableHero: false,
document: state.document,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
),
),
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
stops: [0.2, 0.4],
colors: [
Theme.of(context)
.colorScheme
.background
.withOpacity(0.6),
Theme.of(context)
.colorScheme
.background
.withOpacity(0.3),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
sliver:
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
builder: (context, state) {
final title = switch (state) {
DocumentDetailsLoaded(document: var document) =>
document.title,
_ => widget.title ?? '',
};
return SliverAppBar(
title: Text(title),
leading: const BackButton(),
pinned: true,
forceElevated: innerBoxIsScrolled,
collapsedHeight: kToolbarHeight,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
background: Builder(
builder: (context) {
return Hero(
tag: widget.heroTag ?? "thumb_${widget.id}",
child: GestureDetector(
onTap: () {
DocumentPreviewRoute(
id: widget.id,
title: title,
).push(context);
},
child: Stack(
alignment: Alignment.topCenter,
children: [
Positioned.fill(
child: DocumentPreview(
documentId: widget.id,
title: title,
enableHero: false,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
),
),
),
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
stops: [0.2, 0.4],
colors: [
Theme.of(context)
.colorScheme
.background
.withOpacity(0.6),
Theme.of(context)
.colorScheme
.background
.withOpacity(0.3),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
),
],
),
],
),
),
);
},
),
),
bottom: ColoredTabBar(
tabBar: TabBar(
isScrollable: true,
tabs: [
Tab(
child: Text(
S.of(context)!.overview,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
);
},
),
Tab(
child: Text(
S.of(context)!.content,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
Tab(
child: Text(
S.of(context)!.metaData,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
Tab(
child: Text(
S.of(context)!.similarDocuments,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
if (hasMultiUserSupport && false)
Tab(
child: Text(
"Permissions",
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
bottom: ColoredTabBar(
tabBar: TabBar(
isScrollable: true,
tabs: [
Tab(
child: Text(
S.of(context)!.overview,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
),
],
),
),
Tab(
child: Text(
S.of(context)!.content,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
Tab(
child: Text(
S.of(context)!.metaData,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
Tab(
child: Text(
S.of(context)!.similarDocuments,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
),
// if (hasMultiUserSupport && false)
// Tab(
// child: Text(
// "Permissions",
// style: TextStyle(
// color: Theme.of(context)
// .colorScheme
// .onPrimaryContainer,
// ),
// ),
// ),
],
),
),
);
},
),
),
],
@@ -214,7 +225,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
context.read(),
context.read(),
context.read(),
documentId: state.document.id,
documentId: widget.id,
),
child: Padding(
padding: const EdgeInsets.symmetric(
@@ -229,12 +240,19 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
handle: NestedScrollView
.sliverOverlapAbsorberHandleFor(context),
),
DocumentOverviewWidget(
document: state.document,
itemSpacing: _itemSpacing,
queryString:
widget.titleAndContentQueryString,
),
switch (state) {
DocumentDetailsLoaded(
document: var document
) =>
DocumentOverviewWidget(
document: document,
itemSpacing: _itemSpacing,
queryString:
widget.titleAndContentQueryString,
),
DocumentDetailsError() => _buildErrorState(),
_ => _buildLoadingState(),
},
],
),
CustomScrollView(
@@ -243,13 +261,18 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
handle: NestedScrollView
.sliverOverlapAbsorberHandleFor(context),
),
DocumentContentWidget(
isFullContentLoaded:
state.isFullContentLoaded,
document: state.document,
queryString:
widget.titleAndContentQueryString,
),
switch (state) {
DocumentDetailsLoaded(
document: var document
) =>
DocumentContentWidget(
document: document,
queryString:
widget.titleAndContentQueryString,
),
DocumentDetailsError() => _buildErrorState(),
_ => _buildLoadingState(),
}
],
),
CustomScrollView(
@@ -258,10 +281,19 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
handle: NestedScrollView
.sliverOverlapAbsorberHandleFor(context),
),
DocumentMetaDataWidget(
document: state.document,
itemSpacing: _itemSpacing,
),
switch (state) {
DocumentDetailsLoaded(
document: var document,
metaData: var metaData,
) =>
DocumentMetaDataWidget(
document: document,
itemSpacing: _itemSpacing,
metaData: metaData,
),
DocumentDetailsError() => _buildErrorState(),
_ => _buildLoadingState(),
},
],
),
CustomScrollView(
@@ -277,20 +309,20 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
),
],
),
if (hasMultiUserSupport && false)
CustomScrollView(
controller: _pagingScrollController,
slivers: [
SliverOverlapInjector(
handle: NestedScrollView
.sliverOverlapAbsorberHandleFor(
context),
),
DocumentPermissionsWidget(
document: state.document,
),
],
),
// if (hasMultiUserSupport && false)
// CustomScrollView(
// controller: _pagingScrollController,
// slivers: [
// SliverOverlapInjector(
// handle: NestedScrollView
// .sliverOverlapAbsorberHandleFor(
// context),
// ),
// DocumentPermissionsWidget(
// document: state.document,
// ),
// ],
// ),
],
),
),
@@ -299,13 +331,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
),
),
),
),
),
);
},
),
);
}
Widget _buildEditButton() {
Widget _buildEditButton(DocumentModel document) {
final currentUser = context.watch<LocalUserAccount>();
bool canEdit = context.watchInternetConnection &&
@@ -313,7 +345,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
if (!canEdit) {
return const SizedBox.shrink();
}
final document = context.read<DocumentDetailsCubit>().state.document;
return Tooltip(
message: S.of(context)!.editDocumentTooltip,
preferBelow: false,
@@ -326,60 +357,80 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
);
}
Widget _buildErrorState() {
return SliverToBoxAdapter(
child: Center(
child: Text("Could not load document."),
),
);
}
Widget _buildLoadingState() {
return SliverFillRemaining(
child: Center(
child: CircularProgressIndicator(),
),
);
}
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState> _buildBottomAppBar() {
return BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
builder: (context, state) {
final currentUser = context.watch<LocalUserAccount>();
return BottomAppBar(
child: BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivityState) {
final currentUser = context.watch<LocalUserAccount>();
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
ConnectivityAwareActionWrapper(
disabled: !currentUser.paperlessUser.canDeleteDocuments,
offlineBuilder: (context, child) {
return const IconButton(
icon: Icon(Icons.delete),
onPressed: null,
).paddedSymmetrically(horizontal: 4);
},
child: IconButton(
tooltip: S.of(context)!.deleteDocumentTooltip,
icon: const Icon(Icons.delete),
onPressed: () => _onDelete(state.document),
).paddedSymmetrically(horizontal: 4),
child: Builder(
builder: (context) {
return switch (state) {
DocumentDetailsLoaded(document: var document) => Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
ConnectivityAwareActionWrapper(
disabled: !currentUser.paperlessUser.canDeleteDocuments,
offlineBuilder: (context, child) {
return const IconButton(
icon: Icon(Icons.delete),
onPressed: null,
).paddedSymmetrically(horizontal: 4);
},
child: IconButton(
tooltip: S.of(context)!.deleteDocumentTooltip,
icon: const Icon(Icons.delete),
onPressed: () => _onDelete(document),
).paddedSymmetrically(horizontal: 4),
),
ConnectivityAwareActionWrapper(
offlineBuilder: (context, child) =>
const DocumentDownloadButton(
document: null,
enabled: false,
),
child: DocumentDownloadButton(
document: document,
),
),
ConnectivityAwareActionWrapper(
offlineBuilder: (context, child) => const IconButton(
icon: Icon(Icons.open_in_new),
onPressed: null,
),
child: IconButton(
tooltip: S.of(context)!.openInSystemViewer,
icon: const Icon(Icons.open_in_new),
onPressed: _onOpenFileInSystemViewer,
).paddedOnly(right: 4.0),
),
DocumentShareButton(document: document),
IconButton(
tooltip: S.of(context)!.print,
onPressed: () => context
.read<DocumentDetailsCubit>()
.printDocument(),
icon: const Icon(Icons.print),
),
],
),
ConnectivityAwareActionWrapper(
offlineBuilder: (context, child) =>
const DocumentDownloadButton(
document: null,
enabled: false,
),
child: DocumentDownloadButton(
document: state.document,
),
),
ConnectivityAwareActionWrapper(
offlineBuilder: (context, child) => const IconButton(
icon: Icon(Icons.open_in_new),
onPressed: null,
),
child: IconButton(
tooltip: S.of(context)!.openInSystemViewer,
icon: const Icon(Icons.open_in_new),
onPressed: _onOpenFileInSystemViewer,
).paddedOnly(right: 4.0),
),
DocumentShareButton(document: state.document),
IconButton(
tooltip: S.of(context)!.print,
onPressed: () =>
context.read<DocumentDetailsCubit>().printDocument(),
icon: const Icon(Icons.print),
),
],
);
_ => SizedBox.shrink(),
};
},
),
);
@@ -423,11 +474,4 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
}
}
}
Future<void> _onOpen(DocumentModel document) async {
DocumentPreviewRoute(
$extra: document,
title: document.title,
).push(context);
}
}