mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 23:15:43 -06:00
Added search bar on all pages
This commit is contained in:
@@ -1,217 +1,217 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:url_launcher/link.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
// import 'package:paperless_mobile/constants.dart';
|
||||
// import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
// import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
// import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
// import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||
// import 'package:paperless_mobile/generated/l10n.dart';
|
||||
// import 'package:url_launcher/link.dart';
|
||||
// import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
/// Declares selectable actions in menu.
|
||||
enum AppPopupMenuEntries {
|
||||
// Documents preview
|
||||
documentsSelectListView,
|
||||
documentsSelectGridView,
|
||||
// Generic actions
|
||||
openAboutThisAppDialog,
|
||||
reportBug,
|
||||
openSettings,
|
||||
// Adds a divider
|
||||
divider;
|
||||
}
|
||||
// /// Declares selectable actions in menu.
|
||||
// enum AppPopupMenuEntries {
|
||||
// // Documents preview
|
||||
// documentsSelectListView,
|
||||
// documentsSelectGridView,
|
||||
// // Generic actions
|
||||
// openAboutThisAppDialog,
|
||||
// reportBug,
|
||||
// openSettings,
|
||||
// // Adds a divider
|
||||
// divider;
|
||||
// }
|
||||
|
||||
class AppOptionsPopupMenu extends StatelessWidget {
|
||||
final List<AppPopupMenuEntries> displayedActions;
|
||||
const AppOptionsPopupMenu({
|
||||
super.key,
|
||||
required this.displayedActions,
|
||||
});
|
||||
// class AppOptionsPopupMenu extends StatelessWidget {
|
||||
// final List<AppPopupMenuEntries> displayedActions;
|
||||
// const AppOptionsPopupMenu({
|
||||
// super.key,
|
||||
// required this.displayedActions,
|
||||
// });
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<AppPopupMenuEntries>(
|
||||
position: PopupMenuPosition.under,
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onSelected: (action) {
|
||||
switch (action) {
|
||||
case AppPopupMenuEntries.documentsSelectListView:
|
||||
context.read<ApplicationSettingsCubit>().setViewType(ViewType.list);
|
||||
break;
|
||||
case AppPopupMenuEntries.documentsSelectGridView:
|
||||
context.read<ApplicationSettingsCubit>().setViewType(ViewType.grid);
|
||||
break;
|
||||
case AppPopupMenuEntries.openAboutThisAppDialog:
|
||||
_showAboutDialog(context);
|
||||
break;
|
||||
case AppPopupMenuEntries.openSettings:
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: context.read<ApplicationSettingsCubit>(),
|
||||
child: const SettingsPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
break;
|
||||
case AppPopupMenuEntries.reportBug:
|
||||
launchUrlString(
|
||||
'https://github.com/astubenbord/paperless-mobile/issues/new',
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
itemBuilder: _buildEntries,
|
||||
);
|
||||
}
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return PopupMenuButton<AppPopupMenuEntries>(
|
||||
// position: PopupMenuPosition.under,
|
||||
// icon: const Icon(Icons.more_vert),
|
||||
// onSelected: (action) {
|
||||
// switch (action) {
|
||||
// case AppPopupMenuEntries.documentsSelectListView:
|
||||
// context.read<ApplicationSettingsCubit>().setViewType(ViewType.list);
|
||||
// break;
|
||||
// case AppPopupMenuEntries.documentsSelectGridView:
|
||||
// context.read<ApplicationSettingsCubit>().setViewType(ViewType.grid);
|
||||
// break;
|
||||
// case AppPopupMenuEntries.openAboutThisAppDialog:
|
||||
// _showAboutDialog(context);
|
||||
// break;
|
||||
// case AppPopupMenuEntries.openSettings:
|
||||
// Navigator.of(context).push(
|
||||
// MaterialPageRoute(
|
||||
// builder: (context) => BlocProvider.value(
|
||||
// value: context.read<ApplicationSettingsCubit>(),
|
||||
// child: const SettingsPage(),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// break;
|
||||
// case AppPopupMenuEntries.reportBug:
|
||||
// launchUrlString(
|
||||
// 'https://github.com/astubenbord/paperless-mobile/issues/new',
|
||||
// );
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// },
|
||||
// itemBuilder: _buildEntries,
|
||||
// );
|
||||
// }
|
||||
|
||||
PopupMenuItem<AppPopupMenuEntries> _buildReportBugTile(BuildContext context) {
|
||||
return PopupMenuItem(
|
||||
value: AppPopupMenuEntries.reportBug,
|
||||
padding: EdgeInsets.zero,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.bug_report),
|
||||
title: Text(S.of(context).appDrawerReportBugLabel),
|
||||
),
|
||||
);
|
||||
}
|
||||
// PopupMenuItem<AppPopupMenuEntries> _buildReportBugTile(BuildContext context) {
|
||||
// return PopupMenuItem(
|
||||
// value: AppPopupMenuEntries.reportBug,
|
||||
// padding: EdgeInsets.zero,
|
||||
// child: ListTile(
|
||||
// leading: const Icon(Icons.bug_report),
|
||||
// title: Text(S.of(context).appDrawerReportBugLabel),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
PopupMenuItem<AppPopupMenuEntries> _buildSettingsTile(BuildContext context) {
|
||||
return PopupMenuItem(
|
||||
padding: EdgeInsets.zero,
|
||||
value: AppPopupMenuEntries.openSettings,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.settings_outlined),
|
||||
title: Text(S.of(context).appDrawerSettingsLabel),
|
||||
),
|
||||
);
|
||||
}
|
||||
// PopupMenuItem<AppPopupMenuEntries> _buildSettingsTile(BuildContext context) {
|
||||
// return PopupMenuItem(
|
||||
// padding: EdgeInsets.zero,
|
||||
// value: AppPopupMenuEntries.openSettings,
|
||||
// child: ListTile(
|
||||
// leading: const Icon(Icons.settings_outlined),
|
||||
// title: Text(S.of(context).appDrawerSettingsLabel),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
PopupMenuItem<AppPopupMenuEntries> _buildAboutTile(BuildContext context) {
|
||||
return PopupMenuItem(
|
||||
padding: EdgeInsets.zero,
|
||||
value: AppPopupMenuEntries.openAboutThisAppDialog,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.info_outline),
|
||||
title: Text(S.of(context).appDrawerAboutLabel),
|
||||
),
|
||||
);
|
||||
}
|
||||
// PopupMenuItem<AppPopupMenuEntries> _buildAboutTile(BuildContext context) {
|
||||
// return PopupMenuItem(
|
||||
// padding: EdgeInsets.zero,
|
||||
// value: AppPopupMenuEntries.openAboutThisAppDialog,
|
||||
// child: ListTile(
|
||||
// leading: const Icon(Icons.info_outline),
|
||||
// title: Text(S.of(context).appDrawerAboutLabel),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
PopupMenuItem<AppPopupMenuEntries> _buildListViewTile() {
|
||||
return PopupMenuItem(
|
||||
padding: EdgeInsets.zero,
|
||||
child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||
builder: (context, state) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.list),
|
||||
title: const Text("List"),
|
||||
trailing: state.preferredViewType == ViewType.list
|
||||
? const Icon(Icons.check)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
value: AppPopupMenuEntries.documentsSelectListView,
|
||||
);
|
||||
}
|
||||
// PopupMenuItem<AppPopupMenuEntries> _buildListViewTile() {
|
||||
// return PopupMenuItem(
|
||||
// padding: EdgeInsets.zero,
|
||||
// child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||
// builder: (context, state) {
|
||||
// return ListTile(
|
||||
// leading: const Icon(Icons.list),
|
||||
// title: const Text("List"),
|
||||
// trailing: state.preferredViewType == ViewType.list
|
||||
// ? const Icon(Icons.check)
|
||||
// : null,
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// value: AppPopupMenuEntries.documentsSelectListView,
|
||||
// );
|
||||
// }
|
||||
|
||||
PopupMenuItem<AppPopupMenuEntries> _buildGridViewTile() {
|
||||
return PopupMenuItem(
|
||||
value: AppPopupMenuEntries.documentsSelectGridView,
|
||||
padding: EdgeInsets.zero,
|
||||
child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||
builder: (context, state) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.grid_view_rounded),
|
||||
title: const Text("Grid"),
|
||||
trailing: state.preferredViewType == ViewType.grid
|
||||
? const Icon(Icons.check)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
// PopupMenuItem<AppPopupMenuEntries> _buildGridViewTile() {
|
||||
// return PopupMenuItem(
|
||||
// value: AppPopupMenuEntries.documentsSelectGridView,
|
||||
// padding: EdgeInsets.zero,
|
||||
// child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||
// builder: (context, state) {
|
||||
// return ListTile(
|
||||
// leading: const Icon(Icons.grid_view_rounded),
|
||||
// title: const Text("Grid"),
|
||||
// trailing: state.preferredViewType == ViewType.grid
|
||||
// ? const Icon(Icons.check)
|
||||
// : null,
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
void _showAboutDialog(BuildContext context) {
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationIcon: const ImageIcon(
|
||||
AssetImage('assets/logos/paperless_logo_green.png'),
|
||||
),
|
||||
applicationName: 'Paperless Mobile',
|
||||
applicationVersion: packageInfo.version + '+' + packageInfo.buildNumber,
|
||||
children: [
|
||||
Text(S.of(context).aboutDialogDevelopedByText('Anton Stubenbord')),
|
||||
Link(
|
||||
uri: Uri.parse('https://github.com/astubenbord/paperless-mobile'),
|
||||
builder: (context, followLink) => GestureDetector(
|
||||
onTap: followLink,
|
||||
child: Text(
|
||||
'https://github.com/astubenbord/paperless-mobile',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.tertiary),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Credits',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
_buildOnboardingImageCredits(),
|
||||
],
|
||||
);
|
||||
}
|
||||
// void _showAboutDialog(BuildContext context) {
|
||||
// showAboutDialog(
|
||||
// context: context,
|
||||
// applicationIcon: const ImageIcon(
|
||||
// AssetImage('assets/logos/paperless_logo_green.png'),
|
||||
// ),
|
||||
// applicationName: 'Paperless Mobile',
|
||||
// applicationVersion: packageInfo.version + '+' + packageInfo.buildNumber,
|
||||
// children: [
|
||||
// Text(S.of(context).aboutDialogDevelopedByText('Anton Stubenbord')),
|
||||
// Link(
|
||||
// uri: Uri.parse('https://github.com/astubenbord/paperless-mobile'),
|
||||
// builder: (context, followLink) => GestureDetector(
|
||||
// onTap: followLink,
|
||||
// child: Text(
|
||||
// 'https://github.com/astubenbord/paperless-mobile',
|
||||
// style: TextStyle(color: Theme.of(context).colorScheme.tertiary),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 16),
|
||||
// Text(
|
||||
// 'Credits',
|
||||
// style: Theme.of(context).textTheme.titleMedium,
|
||||
// ),
|
||||
// _buildOnboardingImageCredits(),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget _buildOnboardingImageCredits() {
|
||||
return Link(
|
||||
uri: Uri.parse(
|
||||
'https://www.freepik.com/free-vector/business-team-working-cogwheel-mechanism-together_8270974.htm#query=setting&position=4&from_view=author'),
|
||||
builder: (context, followLink) => Wrap(
|
||||
children: [
|
||||
const Text('Onboarding images by '),
|
||||
GestureDetector(
|
||||
onTap: followLink,
|
||||
child: Text(
|
||||
'pch.vector',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.tertiary),
|
||||
),
|
||||
),
|
||||
const Text(' on Freepik.')
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
// Widget _buildOnboardingImageCredits() {
|
||||
// return Link(
|
||||
// uri: Uri.parse(
|
||||
// 'https://www.freepik.com/free-vector/business-team-working-cogwheel-mechanism-together_8270974.htm#query=setting&position=4&from_view=author'),
|
||||
// builder: (context, followLink) => Wrap(
|
||||
// children: [
|
||||
// const Text('Onboarding images by '),
|
||||
// GestureDetector(
|
||||
// onTap: followLink,
|
||||
// child: Text(
|
||||
// 'pch.vector',
|
||||
// style: TextStyle(color: Theme.of(context).colorScheme.tertiary),
|
||||
// ),
|
||||
// ),
|
||||
// const Text(' on Freepik.')
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
List<PopupMenuEntry<AppPopupMenuEntries>> _buildEntries(
|
||||
BuildContext context) {
|
||||
List<PopupMenuEntry<AppPopupMenuEntries>> items = [];
|
||||
for (final entry in displayedActions) {
|
||||
switch (entry) {
|
||||
case AppPopupMenuEntries.documentsSelectListView:
|
||||
items.add(_buildListViewTile());
|
||||
break;
|
||||
case AppPopupMenuEntries.documentsSelectGridView:
|
||||
items.add(_buildGridViewTile());
|
||||
break;
|
||||
case AppPopupMenuEntries.openAboutThisAppDialog:
|
||||
items.add(_buildAboutTile(context));
|
||||
break;
|
||||
case AppPopupMenuEntries.reportBug:
|
||||
items.add(_buildReportBugTile(context));
|
||||
break;
|
||||
case AppPopupMenuEntries.openSettings:
|
||||
items.add(_buildSettingsTile(context));
|
||||
break;
|
||||
case AppPopupMenuEntries.divider:
|
||||
items.add(const PopupMenuDivider());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
// List<PopupMenuEntry<AppPopupMenuEntries>> _buildEntries(
|
||||
// BuildContext context) {
|
||||
// List<PopupMenuEntry<AppPopupMenuEntries>> items = [];
|
||||
// for (final entry in displayedActions) {
|
||||
// switch (entry) {
|
||||
// case AppPopupMenuEntries.documentsSelectListView:
|
||||
// items.add(_buildListViewTile());
|
||||
// break;
|
||||
// case AppPopupMenuEntries.documentsSelectGridView:
|
||||
// items.add(_buildGridViewTile());
|
||||
// break;
|
||||
// case AppPopupMenuEntries.openAboutThisAppDialog:
|
||||
// items.add(_buildAboutTile(context));
|
||||
// break;
|
||||
// case AppPopupMenuEntries.reportBug:
|
||||
// items.add(_buildReportBugTile(context));
|
||||
// break;
|
||||
// case AppPopupMenuEntries.openSettings:
|
||||
// items.add(_buildSettingsTile(context));
|
||||
// break;
|
||||
// case AppPopupMenuEntries.divider:
|
||||
// items.add(const PopupMenuDivider());
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// return items;
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -39,7 +39,7 @@ class SearchBar extends StatelessWidget {
|
||||
surfaceTintColor: colorScheme.surfaceTint,
|
||||
borderRadius: BorderRadius.circular(effectiveHeight / 2),
|
||||
child: InkWell(
|
||||
onTap: () {},
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(effectiveHeight / 2),
|
||||
highlightColor: Colors.transparent,
|
||||
splashFactory: InkRipple.splashFactory,
|
||||
@@ -51,7 +51,9 @@ class SearchBar extends StatelessWidget {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: TextField(
|
||||
onTap: onTap,
|
||||
readOnly: true,
|
||||
enabled: false,
|
||||
cursorColor: colorScheme.primary,
|
||||
style: textTheme.bodyLarge,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
@@ -64,7 +66,6 @@ class SearchBar extends StatelessWidget {
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
onTap: onTap,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,18 +2,25 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class PaperlessLogo extends StatelessWidget {
|
||||
static const _paperlessGreen = Color(0xFF18541F);
|
||||
final double? height;
|
||||
final double? width;
|
||||
final String _path;
|
||||
final Color _color;
|
||||
|
||||
const PaperlessLogo.white({super.key, this.height, this.width})
|
||||
: _path = "assets/logos/paperless_logo_white.svg";
|
||||
const PaperlessLogo.white({
|
||||
super.key,
|
||||
this.height,
|
||||
this.width,
|
||||
}) : _color = Colors.white;
|
||||
|
||||
const PaperlessLogo.green({super.key, this.height, this.width})
|
||||
: _path = "assets/logos/paperless_logo_green.svg";
|
||||
: _color = _paperlessGreen;
|
||||
|
||||
const PaperlessLogo.black({super.key, this.height, this.width})
|
||||
: _path = "assets/logos/paperless_logo_black.svg";
|
||||
: _color = Colors.black;
|
||||
|
||||
const PaperlessLogo.colored(Color color, {super.key, this.height, this.width})
|
||||
: _color = color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -24,7 +31,8 @@ class PaperlessLogo extends StatelessWidget {
|
||||
),
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: SvgPicture.asset(
|
||||
_path,
|
||||
"assets/logos/paperless_logo_white.svg",
|
||||
color: _color,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/widgets/paperless_logo.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:url_launcher/link.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class AppDrawer extends StatelessWidget {
|
||||
const AppDrawer({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
top: true,
|
||||
child: Drawer(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const PaperlessLogo.green(),
|
||||
Text(
|
||||
"Paperless Mobile",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
const Divider(),
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: Text(S.of(context).appDrawerAboutLabel),
|
||||
leading: const Icon(Icons.info_outline),
|
||||
onTap: () => _showAboutDialog(context),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
leading: const Icon(Icons.bug_report_outlined),
|
||||
title: Text(S.of(context).appDrawerReportBugLabel),
|
||||
onTap: () {
|
||||
launchUrlString(
|
||||
'https://github.com/astubenbord/paperless-mobile/issues/new');
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
leading: const Icon(Icons.settings_outlined),
|
||||
title: Text(
|
||||
S.of(context).appDrawerSettingsLabel,
|
||||
),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: context.read<ApplicationSettingsCubit>(),
|
||||
child: const SettingsPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAboutDialog(BuildContext context) {
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationIcon: const ImageIcon(
|
||||
AssetImage('assets/logos/paperless_logo_green.png'),
|
||||
),
|
||||
applicationName: 'Paperless Mobile',
|
||||
applicationVersion: packageInfo.version + '+' + packageInfo.buildNumber,
|
||||
children: [
|
||||
Text(S.of(context).aboutDialogDevelopedByText('Anton Stubenbord')),
|
||||
Link(
|
||||
uri: Uri.parse('https://github.com/astubenbord/paperless-mobile'),
|
||||
builder: (context, followLink) => GestureDetector(
|
||||
onTap: followLink,
|
||||
child: Text(
|
||||
'https://github.com/astubenbord/paperless-mobile',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.tertiary),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Credits',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
_buildOnboardingImageCredits(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOnboardingImageCredits() {
|
||||
return Link(
|
||||
uri: Uri.parse(
|
||||
'https://www.freepik.com/free-vector/business-team-working-cogwheel-mechanism-together_8270974.htm#query=setting&position=4&from_view=author'),
|
||||
builder: (context, followLink) => Wrap(
|
||||
children: [
|
||||
const Text('Onboarding images by '),
|
||||
GestureDetector(
|
||||
onTap: followLink,
|
||||
child: Text(
|
||||
'pch.vector',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.tertiary),
|
||||
),
|
||||
),
|
||||
const Text(' on Freepik.')
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
|
||||
part 'document_details_state.dart';
|
||||
|
||||
|
||||
@@ -482,7 +482,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
child: _DetailsItem(
|
||||
label: S.of(context).documentStoragePathPropertyLabel,
|
||||
content: StoragePathWidget(
|
||||
isClickable: widget.isLabelClickable,
|
||||
pathId: document.storagePath,
|
||||
),
|
||||
).paddedSymmetrically(vertical: 16),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_state.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart';
|
||||
import 'package:paperless_mobile/features/search/cubit/document_search_state.dart';
|
||||
|
||||
class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
|
||||
with PagedDocumentsMixin {
|
||||
@@ -27,11 +27,8 @@ class DocumentSearchState extends PagedDocumentsState {
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
hasLoaded,
|
||||
isLoading,
|
||||
filter,
|
||||
value,
|
||||
List<Object?> get props => [
|
||||
...super.props,
|
||||
searchHistory,
|
||||
suggestions,
|
||||
view,
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||
import 'package:paperless_mobile/features/search/cubit/document_search_cubit.dart';
|
||||
import 'package:paperless_mobile/features/search/cubit/document_search_state.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||
|
||||
Future<void> showDocumentSearchPage(BuildContext context) {
|
||||
return Navigator.of(context).push(
|
||||
@@ -151,12 +153,27 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
||||
isLabelClickable: false,
|
||||
isLoading: state.isLoading,
|
||||
hasLoaded: state.hasLoaded,
|
||||
onTap: (document) async {
|
||||
final updatedDocument = await Navigator.pushNamed(
|
||||
context,
|
||||
DocumentDetailsRoute.routeName,
|
||||
arguments: DocumentDetailsRouteArguments(
|
||||
document: document,
|
||||
isLabelClickable: false,
|
||||
),
|
||||
) as DocumentModel?;
|
||||
if (updatedDocument != document) {
|
||||
context.read<DocumentSearchCubit>().reload();
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _selectSuggestion(String suggestion) {
|
||||
_queryController.text = suggestion;
|
||||
context.read<DocumentSearchCubit>().search(suggestion);
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
|
||||
@override
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
final SavedViewRepository _savedViewRepository;
|
||||
|
||||
DocumentsCubit(this.api, this._savedViewRepository)
|
||||
: super(const DocumentsState());
|
||||
DocumentsCubit(this.api) : super(const DocumentsState());
|
||||
|
||||
Future<void> bulkRemove(List<DocumentModel> documents) async {
|
||||
log("[DocumentsCubit] bulkRemove");
|
||||
|
||||
@@ -1,33 +1,28 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:badges/badges.dart' as b;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/document_search_page.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/view/widgets/adaptive_documents_view.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/new_items_loading_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_panel.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/view_actions.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/labels_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/view/saved_view_list.dart';
|
||||
import 'package:paperless_mobile/features/search/view/document_search_page.dart';
|
||||
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||
|
||||
class DocumentFilterIntent {
|
||||
final DocumentFilter? filter;
|
||||
@@ -116,6 +111,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
},
|
||||
builder: (context, connectivityState) {
|
||||
return Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
final appliedFiltersCount = state.filter.appliedFiltersCount;
|
||||
@@ -165,6 +161,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
context,
|
||||
),
|
||||
sliver: SearchAppBar(
|
||||
hintText: "Search documents", //TODO: INTL
|
||||
onOpenSearch: showDocumentSearchPage,
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
@@ -177,9 +174,12 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
),
|
||||
),
|
||||
],
|
||||
body: NotificationListener<ScrollUpdateNotification>(
|
||||
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 &&
|
||||
@@ -414,29 +414,21 @@ class _DocumentsPageState extends State<DocumentsPage>
|
||||
}
|
||||
|
||||
Future<void> _openDetails(DocumentModel document) async {
|
||||
final updatedModel = await Navigator.of(context).push<DocumentModel?>(
|
||||
_buildDetailsPageRoute(document),
|
||||
);
|
||||
final updatedModel = await Navigator.pushNamed(
|
||||
context,
|
||||
DocumentDetailsRoute.routeName,
|
||||
arguments: DocumentDetailsRouteArguments(
|
||||
document: document,
|
||||
),
|
||||
) as DocumentModel?;
|
||||
// final updatedModel = await Navigator.of(context).push<DocumentModel?>(
|
||||
// _buildDetailsPageRoute(document),
|
||||
// );
|
||||
if (updatedModel != document) {
|
||||
context.read<DocumentsCubit>().reload();
|
||||
}
|
||||
}
|
||||
|
||||
MaterialPageRoute<DocumentModel?> _buildDetailsPageRoute(
|
||||
DocumentModel document) {
|
||||
return MaterialPageRoute(
|
||||
builder: (_) => BlocProvider(
|
||||
create: (context) => DocumentDetailsCubit(
|
||||
context.read<PaperlessDocumentsApi>(),
|
||||
document,
|
||||
),
|
||||
child: const LabelRepositoriesProvider(
|
||||
child: DocumentDetailsPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _addTagToFilter(int tagId) {
|
||||
try {
|
||||
final tagsQuery =
|
||||
|
||||
@@ -48,11 +48,18 @@ class HomePage extends StatefulWidget {
|
||||
class _HomePageState extends State<HomePage> {
|
||||
int _currentIndex = 0;
|
||||
final DocumentScannerCubit _scannerCubit = DocumentScannerCubit();
|
||||
late final InboxCubit _inboxCubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeData(context);
|
||||
_inboxCubit = InboxCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
);
|
||||
context.read<ConnectivityCubit>().reload();
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_listenForReceivedFiles();
|
||||
@@ -147,6 +154,12 @@ class _HomePageState extends State<HomePage> {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_inboxCubit.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final destinations = [
|
||||
@@ -182,28 +195,15 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
label: S.of(context).bottomNavInboxPageLabel,
|
||||
),
|
||||
// RouteDescription(
|
||||
// icon: const Icon(Icons.settings_outlined),
|
||||
// selectedIcon: Icon(
|
||||
// Icons.settings,
|
||||
// color: Theme.of(context).colorScheme.primary,
|
||||
// ),
|
||||
// label: S.of(context).appDrawerSettingsLabel,
|
||||
// ),
|
||||
];
|
||||
final routes = <Widget>[
|
||||
MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => DocumentsCubit(
|
||||
context.read<PaperlessDocumentsApi>(),
|
||||
context.read<SavedViewRepository>(),
|
||||
),
|
||||
create: (context) => DocumentsCubit(context.read()),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => SavedViewCubit(
|
||||
context.read<SavedViewRepository>(),
|
||||
),
|
||||
create: (context) => SavedViewCubit(context.read()),
|
||||
),
|
||||
],
|
||||
child: const DocumentsPage(),
|
||||
@@ -213,13 +213,8 @@ class _HomePageState extends State<HomePage> {
|
||||
child: const ScannerPage(),
|
||||
),
|
||||
const LabelsPage(),
|
||||
BlocProvider(
|
||||
create: (context) => InboxCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: _inboxCubit,
|
||||
child: const InboxPage(),
|
||||
),
|
||||
// const SettingsPage(),
|
||||
@@ -249,7 +244,6 @@ class _HomePageState extends State<HomePage> {
|
||||
builder: (context, sizingInformation) {
|
||||
if (!sizingInformation.isMobile) {
|
||||
return Scaffold(
|
||||
// drawer: const AppDrawer(),
|
||||
body: Row(
|
||||
children: [
|
||||
NavigationRail(
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_list_loading_widget.dart';
|
||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||
import 'package:paperless_mobile/extensions/dart_extensions.dart';
|
||||
@@ -11,6 +13,7 @@ import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_empty_widget.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_item.dart';
|
||||
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
|
||||
@@ -54,39 +57,8 @@ class _InboxPageState extends State<InboxPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const _progressBarHeight = 4.0;
|
||||
return Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize:
|
||||
const Size.fromHeight(kToolbarHeight + _progressBarHeight),
|
||||
child: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
return AppBar(
|
||||
title: Text(S.of(context).bottomNavInboxPageLabel),
|
||||
actions: [
|
||||
if (state.hasLoaded)
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
child: ColoredBox(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
child: Text(
|
||||
(state.value.isEmpty
|
||||
? '0 '
|
||||
: '${state.value.first.count} ') +
|
||||
S.of(context).inboxPageUnseenText,
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
).paddedSymmetrically(horizontal: 4.0),
|
||||
),
|
||||
),
|
||||
).paddedSymmetrically(horizontal: 8)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
if (!state.hasLoaded || state.documents.isEmpty) {
|
||||
@@ -104,91 +76,95 @@ class _InboxPageState extends State<InboxPage> {
|
||||
);
|
||||
},
|
||||
),
|
||||
body: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
if (!state.hasLoaded) {
|
||||
return const DocumentsListLoadingWidget();
|
||||
}
|
||||
body: NestedScrollView(
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SearchAppBar(
|
||||
hintText: "Search documents",
|
||||
onOpenSearch: showDocumentSearchPage,
|
||||
),
|
||||
],
|
||||
body: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
if (!state.hasLoaded) {
|
||||
return const CustomScrollView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
slivers: [DocumentsListLoadingWidget()],
|
||||
);
|
||||
}
|
||||
|
||||
if (state.documents.isEmpty) {
|
||||
return InboxEmptyWidget(
|
||||
emptyStateRefreshIndicatorKey: _emptyStateRefreshIndicatorKey,
|
||||
);
|
||||
}
|
||||
if (state.documents.isEmpty) {
|
||||
return InboxEmptyWidget(
|
||||
emptyStateRefreshIndicatorKey: _emptyStateRefreshIndicatorKey,
|
||||
);
|
||||
}
|
||||
|
||||
// Build a list of slivers alternating between SliverToBoxAdapter
|
||||
// (group header) and a SliverList (inbox items).
|
||||
final List<Widget> slivers = _groupByDate(state.documents)
|
||||
.entries
|
||||
.map(
|
||||
(entry) => [
|
||||
SliverToBoxAdapter(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(32.0),
|
||||
child: Text(
|
||||
entry.key,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
textAlign: TextAlign.center,
|
||||
).padded(),
|
||||
),
|
||||
).paddedOnly(top: 8.0),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
childCount: entry.value.length,
|
||||
(context, index) {
|
||||
if (index < entry.value.length - 1) {
|
||||
return Column(
|
||||
children: [
|
||||
_buildListItem(
|
||||
entry.value[index],
|
||||
),
|
||||
const Divider(
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
),
|
||||
],
|
||||
// Build a list of slivers alternating between SliverToBoxAdapter
|
||||
// (group header) and a SliverList (inbox items).
|
||||
final List<Widget> slivers = _groupByDate(state.documents)
|
||||
.entries
|
||||
.map(
|
||||
(entry) => [
|
||||
SliverToBoxAdapter(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(32.0),
|
||||
child: Text(
|
||||
entry.key,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
textAlign: TextAlign.center,
|
||||
).padded(),
|
||||
),
|
||||
).paddedOnly(top: 8.0),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
childCount: entry.value.length,
|
||||
(context, index) {
|
||||
if (index < entry.value.length - 1) {
|
||||
return Column(
|
||||
children: [
|
||||
_buildListItem(
|
||||
entry.value[index],
|
||||
),
|
||||
const Divider(
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return _buildListItem(
|
||||
entry.value[index],
|
||||
);
|
||||
}
|
||||
return _buildListItem(
|
||||
entry.value[index],
|
||||
);
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
.flattened
|
||||
.toList()
|
||||
..add(const SliverToBoxAdapter(child: SizedBox(height: 78)));
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => context.read<InboxCubit>().initializeInbox(),
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: HintCard(
|
||||
show: !state.isHintAcknowledged,
|
||||
hintText: S.of(context).inboxPageUsageHintText,
|
||||
onHintAcknowledged: () =>
|
||||
context.read<InboxCubit>().acknowledgeHint(),
|
||||
),
|
||||
),
|
||||
...slivers,
|
||||
],
|
||||
)
|
||||
.flattened
|
||||
.toList()
|
||||
..add(const SliverToBoxAdapter(child: SizedBox(height: 78)));
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => context.read<InboxCubit>().initializeInbox(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
controller: _scrollController,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: HintCard(
|
||||
show: !state.isHintAcknowledged,
|
||||
hintText: S.of(context).inboxPageUsageHintText,
|
||||
onHintAcknowledged: () =>
|
||||
context.read<InboxCubit>().acknowledgeHint(),
|
||||
),
|
||||
),
|
||||
...slivers,
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||
|
||||
class InboxItem extends StatefulWidget {
|
||||
static const _a4AspectRatio = 1 / 1.4142;
|
||||
@@ -40,24 +41,16 @@ class _InboxItemState extends State<InboxItem> {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () async {
|
||||
final returnedDocument = await Navigator.push<DocumentModel?>(
|
||||
final updatedDocument = await Navigator.pushNamed(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider(
|
||||
create: (context) => DocumentDetailsCubit(
|
||||
context.read<PaperlessDocumentsApi>(),
|
||||
widget.document,
|
||||
),
|
||||
child: const LabelRepositoriesProvider(
|
||||
child: DocumentDetailsPage(
|
||||
isLabelClickable: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
DocumentDetailsRoute.routeName,
|
||||
arguments: DocumentDetailsRouteArguments(
|
||||
document: widget.document,
|
||||
isLabelClickable: false,
|
||||
),
|
||||
);
|
||||
if (returnedDocument != null) {
|
||||
widget.onDocumentUpdated(returnedDocument);
|
||||
) as DocumentModel?;
|
||||
if (updatedDocument != null) {
|
||||
widget.onDocumentUpdated(updatedDocument);
|
||||
}
|
||||
},
|
||||
child: SizedBox(
|
||||
|
||||
@@ -266,6 +266,10 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
Widget _buildNotAssignedTag(FormFieldState<TagsQuery> field) {
|
||||
return ColoredChipWrapper(
|
||||
child: InputChip(
|
||||
labelPadding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
padding: const EdgeInsets.all(4),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: BorderSide.none,
|
||||
label: Text(
|
||||
S.of(context).labelNotAssignedText,
|
||||
),
|
||||
@@ -288,6 +292,10 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
}
|
||||
return ColoredChipWrapper(
|
||||
child: InputChip(
|
||||
labelPadding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
padding: const EdgeInsets.all(4),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: BorderSide.none,
|
||||
label: Text(
|
||||
tag.name,
|
||||
style: TextStyle(
|
||||
@@ -312,6 +320,10 @@ class _TagFormFieldState extends State<TagFormField> {
|
||||
Widget _buildAnyAssignedTag(FormFieldState<TagsQuery> field) {
|
||||
return ColoredChipWrapper(
|
||||
child: InputChip(
|
||||
labelPadding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
padding: const EdgeInsets.all(4),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
side: BorderSide.none,
|
||||
label: Text(S.of(context).labelAnyAssignedText),
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.12),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
@@ -8,6 +10,9 @@ import 'package:paperless_mobile/core/repository/state/impl/document_type_reposi
|
||||
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_page.dart';
|
||||
@@ -16,12 +21,13 @@ import 'package:paperless_mobile/features/edit_label/view/impl/edit_corresponden
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/edit_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/correspondent_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/document_type_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/storage_path_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/providers/tag_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_tab_view.dart';
|
||||
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class LabelsPage extends StatefulWidget {
|
||||
@@ -51,154 +57,264 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
child: BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
builder: (context, connectedState) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
[
|
||||
S.of(context).labelsPageCorrespondentsTitleText,
|
||||
S.of(context).labelsPageDocumentTypesTitleText,
|
||||
S.of(context).labelsPageTagsTitleText,
|
||||
S.of(context).labelsPageStoragePathTitleText
|
||||
][_currentIndex],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: [
|
||||
_openAddCorrespondentPage,
|
||||
_openAddDocumentTypePage,
|
||||
_openAddTagPage,
|
||||
_openAddStoragePathPage,
|
||||
][_currentIndex],
|
||||
icon: const Icon(Icons.add),
|
||||
)
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: [
|
||||
_openAddCorrespondentPage,
|
||||
_openAddDocumentTypePage,
|
||||
_openAddTagPage,
|
||||
_openAddStoragePathPage,
|
||||
][_currentIndex],
|
||||
child: Icon(Icons.add),
|
||||
),
|
||||
body: NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SliverOverlapAbsorber(
|
||||
// This widget takes the overlapping behavior of the SliverAppBar,
|
||||
// and redirects it to the SliverOverlapInjector below. If it is
|
||||
// missing, then it is possible for the nested "inner" scroll view
|
||||
// below to end up under the SliverAppBar even when the inner
|
||||
// scroll view thinks it has not been scrolled.
|
||||
// This is not necessary if the "headerSliverBuilder" only builds
|
||||
// widgets that do not overlap the next sliver.
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
|
||||
context,
|
||||
),
|
||||
sliver: SearchAppBar(
|
||||
hintText: "Search documents", //TODO: INTL
|
||||
onOpenSearch: showDocumentSearchPage,
|
||||
bottom: TabBar(
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.person_outline,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.description_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.label_outline,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.folder_open,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size.fromHeight(
|
||||
kToolbarHeight + (!connectedState.isConnected ? 16 : 0)),
|
||||
child: Column(
|
||||
children: [
|
||||
if (!connectedState.isConnected) const OfflineBanner(),
|
||||
ColoredBox(
|
||||
color: Theme.of(context).bottomAppBarColor,
|
||||
child: TabBar(
|
||||
indicatorColor: Theme.of(context).colorScheme.primary,
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.person_outline,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.description_outlined,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.label_outline,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.folder_open,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
)
|
||||
],
|
||||
body: NotificationListener<ScrollNotification>(
|
||||
onNotification: (notification) {
|
||||
final metrics = notification.metrics;
|
||||
if (metrics.maxScrollExtent == 0) {
|
||||
return true;
|
||||
}
|
||||
final desiredTab =
|
||||
((metrics.pixels / metrics.maxScrollExtent) *
|
||||
(_tabController.length - 1))
|
||||
.round();
|
||||
|
||||
if (metrics.axis == Axis.horizontal &&
|
||||
_currentIndex != desiredTab) {
|
||||
setState(() => _currentIndex = desiredTab);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<Correspondent>(
|
||||
context.read<
|
||||
LabelRepository<Correspondent,
|
||||
CorrespondentRepositoryState>>(),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<DocumentType>(
|
||||
context.read<
|
||||
LabelRepository<DocumentType,
|
||||
DocumentTypeRepositoryState>>(),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<Tag>(
|
||||
context
|
||||
.read<LabelRepository<Tag, TagRepositoryState>>(),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => LabelCubit<StoragePath>(
|
||||
context.read<
|
||||
LabelRepository<StoragePath,
|
||||
StoragePathRepositoryState>>(),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: RefreshIndicator(
|
||||
edgeOffset: kToolbarHeight,
|
||||
notificationPredicate: (notification) =>
|
||||
connectedState.isConnected,
|
||||
onRefresh: () => [
|
||||
context.read<LabelCubit<Correspondent>>(),
|
||||
context.read<LabelCubit<DocumentType>>(),
|
||||
context.read<LabelCubit<Tag>>(),
|
||||
context.read<LabelCubit<StoragePath>>(),
|
||||
][_currentIndex]
|
||||
.reload(),
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
LabelTabView<Correspondent>(
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
correspondent:
|
||||
IdQueryParameter.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onEdit: _openEditCorrespondentPage,
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)
|
||||
.labelsPageCorrespondentEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageCorrespondentEmptyStateDescriptionText,
|
||||
onAddNew: _openAddCorrespondentPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
DocumentTypeBlocProvider(
|
||||
child: LabelTabView<DocumentType>(
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
documentType:
|
||||
IdQueryParameter.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onEdit: _openEditDocumentTypePage,
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)
|
||||
.labelsPageDocumentTypeEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageDocumentTypeEmptyStateDescriptionText,
|
||||
onAddNew: _openAddDocumentTypePage,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
TagBlocProvider(
|
||||
child: LabelTabView<Tag>(
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
tags: IdsTagsQuery.fromIds([label.id!]),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onEdit: _openEditTagPage,
|
||||
leadingBuilder: (t) => CircleAvatar(
|
||||
backgroundColor: t.color,
|
||||
child: t.isInboxTag ?? false
|
||||
? Icon(
|
||||
Icons.inbox,
|
||||
color: t.textColor,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)
|
||||
.labelsPageTagsEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageTagsEmptyStateDescriptionText,
|
||||
onAddNew: _openAddTagPage,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
StoragePathBlocProvider(
|
||||
child: LabelTabView<StoragePath>(
|
||||
onEdit: _openEditStoragePathPage,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
storagePath:
|
||||
IdQueryParameter.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
contentBuilder: (path) =>
|
||||
Text(path.path ?? ""),
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)
|
||||
.labelsPageStoragePathEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageStoragePathEmptyStateDescriptionText,
|
||||
onAddNew: _openAddStoragePathPage,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
CorrespondentBlocProvider(
|
||||
child: LabelTabView<Correspondent>(
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
correspondent: IdQueryParameter.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onEdit: _openEditCorrespondentPage,
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)
|
||||
.labelsPageCorrespondentEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageCorrespondentEmptyStateDescriptionText,
|
||||
onAddNew: _openAddCorrespondentPage,
|
||||
),
|
||||
),
|
||||
DocumentTypeBlocProvider(
|
||||
child: LabelTabView<DocumentType>(
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
documentType: IdQueryParameter.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onEdit: _openEditDocumentTypePage,
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)
|
||||
.labelsPageDocumentTypeEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageDocumentTypeEmptyStateDescriptionText,
|
||||
onAddNew: _openAddDocumentTypePage,
|
||||
),
|
||||
),
|
||||
TagBlocProvider(
|
||||
child: LabelTabView<Tag>(
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
tags: IdsTagsQuery.fromIds([label.id!]),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onEdit: _openEditTagPage,
|
||||
leadingBuilder: (t) => CircleAvatar(
|
||||
backgroundColor: t.color,
|
||||
child: t.isInboxTag ?? false
|
||||
? Icon(
|
||||
Icons.inbox,
|
||||
color: t.textColor,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageTagsEmptyStateAddNewLabel,
|
||||
emptyStateDescription:
|
||||
S.of(context).labelsPageTagsEmptyStateDescriptionText,
|
||||
onAddNew: _openAddTagPage,
|
||||
),
|
||||
),
|
||||
StoragePathBlocProvider(
|
||||
child: LabelTabView<StoragePath>(
|
||||
onEdit: _openEditStoragePathPage,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
storagePath: IdQueryParameter.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
contentBuilder: (path) => Text(path.path ?? ""),
|
||||
emptyStateActionButtonLabel: S
|
||||
.of(context)
|
||||
.labelsPageStoragePathEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageStoragePathEmptyStateDescriptionText,
|
||||
onAddNew: _openAddStoragePathPage,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -46,45 +46,46 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
||||
}
|
||||
final labels = state.labels.values.toList()..sort();
|
||||
if (labels.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
emptyStateDescription,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: onAddNew,
|
||||
child: Text(emptyStateActionButtonLabel),
|
||||
),
|
||||
].padded(),
|
||||
return SliverFillRemaining(
|
||||
child: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
emptyStateDescription,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: onAddNew,
|
||||
child: Text(emptyStateActionButtonLabel),
|
||||
),
|
||||
].padded(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return RefreshIndicator(
|
||||
onRefresh: context.read<LabelCubit<T>>().reload,
|
||||
notificationPredicate: (notification) =>
|
||||
connectivityState.isConnected,
|
||||
child: ListView(
|
||||
children: labels
|
||||
.map((l) => LabelItem<T>(
|
||||
name: l.name,
|
||||
content: contentBuilder?.call(l) ??
|
||||
Text(
|
||||
translateMatchingAlgorithmName(
|
||||
context, l.matchingAlgorithm) +
|
||||
((l.match?.isNotEmpty ?? false)
|
||||
? ": ${l.match}"
|
||||
: ""),
|
||||
maxLines: 2,
|
||||
),
|
||||
onOpenEditPage: onEdit,
|
||||
filterBuilder: filterBuilder,
|
||||
leading: leadingBuilder?.call(l),
|
||||
label: l,
|
||||
))
|
||||
.toList(),
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final l = labels.elementAt(index);
|
||||
return LabelItem<T>(
|
||||
name: l.name,
|
||||
content: contentBuilder?.call(l) ??
|
||||
Text(
|
||||
translateMatchingAlgorithmName(
|
||||
context, l.matchingAlgorithm) +
|
||||
((l.match?.isNotEmpty ?? false)
|
||||
? ": ${l.match}"
|
||||
: ""),
|
||||
maxLines: 2,
|
||||
),
|
||||
onOpenEditPage: onEdit,
|
||||
filterBuilder: filterBuilder,
|
||||
leading: leadingBuilder?.call(l),
|
||||
label: l,
|
||||
);
|
||||
},
|
||||
childCount: labels.length,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:paperless_mobile/features/linked_documents/bloc/linked_documents
|
||||
import 'package:paperless_mobile/features/linked_documents/bloc/state/linked_documents_state.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||
|
||||
class LinkedDocumentsPage extends StatefulWidget {
|
||||
const LinkedDocumentsPage({super.key});
|
||||
@@ -59,6 +60,19 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
|
||||
isLabelClickable: false,
|
||||
isLoading: state.isLoading,
|
||||
hasLoaded: state.hasLoaded,
|
||||
onTap: (document) async {
|
||||
final updatedDocument = await Navigator.pushNamed(
|
||||
context,
|
||||
DocumentDetailsRoute.routeName,
|
||||
arguments: DocumentDetailsRouteArguments(
|
||||
document: document,
|
||||
isLabelClickable: false,
|
||||
),
|
||||
) as DocumentModel?;
|
||||
if (updatedDocument != document) {
|
||||
context.read<LinkedDocumentsCubit>().reload();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -29,7 +29,8 @@ class SavedViewList extends StatelessWidget {
|
||||
return ListTile(
|
||||
title: Text(view.name),
|
||||
subtitle: Text(
|
||||
"${view.filterRules.length} filter(s) set"), //TODO: INTL w/ placeholder
|
||||
"${view.filterRules.length} filter(s) set",
|
||||
), //TODO: INTL w/ placeholder
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:paperless_mobile/features/documents/view/widgets/view_actions.da
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||
|
||||
class SavedViewPage extends StatefulWidget {
|
||||
final Future<void> Function(SavedView savedView) onDelete;
|
||||
@@ -101,6 +102,12 @@ class _SavedViewPageState extends State<SavedViewPage> {
|
||||
onTap: _onOpenDocumentDetails,
|
||||
viewType: _viewType,
|
||||
),
|
||||
if (state.hasLoaded && state.isLoading)
|
||||
const SliverToBoxAdapter(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
@@ -111,20 +118,14 @@ class _SavedViewPageState extends State<SavedViewPage> {
|
||||
}
|
||||
|
||||
void _onOpenDocumentDetails(DocumentModel document) async {
|
||||
final updatedDocument = await Navigator.push<DocumentModel>(
|
||||
final updatedDocument = await Navigator.pushNamed(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => BlocProvider(
|
||||
create: (context) => DocumentDetailsCubit(
|
||||
context.read<PaperlessDocumentsApi>(),
|
||||
document,
|
||||
),
|
||||
child: const LabelRepositoriesProvider(
|
||||
child: DocumentDetailsPage(),
|
||||
),
|
||||
),
|
||||
DocumentDetailsRoute.routeName,
|
||||
arguments: DocumentDetailsRouteArguments(
|
||||
document: document,
|
||||
isLabelClickable: false,
|
||||
),
|
||||
);
|
||||
) as DocumentModel?;
|
||||
if (updatedDocument != document) {
|
||||
// Reload in case document was edited and might not fulfill filter criteria of saved view anymore
|
||||
context.read<SavedViewDetailsCubit>().reload();
|
||||
|
||||
@@ -17,12 +17,14 @@ import 'package:paperless_mobile/core/repository/state/impl/document_type_reposi
|
||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/features/scan/view/widgets/scanned_image_item.dart';
|
||||
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/file_helpers.dart';
|
||||
@@ -47,20 +49,100 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
|
||||
builder: (context, connectedState) {
|
||||
return Scaffold(
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => _openDocumentScanner(context),
|
||||
child: const Icon(Icons.add_a_photo_outlined),
|
||||
),
|
||||
appBar: _buildAppBar(context, connectedState.isConnected),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: _buildBody(connectedState.isConnected),
|
||||
//appBar: _buildAppBar(context, connectedState.isConnected),
|
||||
// body: Padding(
|
||||
// padding: const EdgeInsets.all(8.0),
|
||||
// child: _buildBody(connectedState.isConnected),
|
||||
// ),
|
||||
body: BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||
builder: (context, state) {
|
||||
return NestedScrollView(
|
||||
floatHeaderSlivers: false,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
SearchAppBar(
|
||||
hintText: "Search documents", //TODO: INTL
|
||||
onOpenSearch: showDocumentSearchPage,
|
||||
bottom: PreferredSize(
|
||||
child: _buildActions(connectedState.isConnected),
|
||||
preferredSize: const Size.fromHeight(kTextTabBarHeight),
|
||||
),
|
||||
),
|
||||
],
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
if (state.isEmpty)
|
||||
SliverFillRemaining(
|
||||
child: _buildEmptyState(connectedState.isConnected),
|
||||
)
|
||||
else
|
||||
_buildImageGrid(state)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActions(bool isConnected) {
|
||||
return SizedBox(
|
||||
height: kTextTabBarHeight,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||
builder: (context, state) {
|
||||
return TextButton.icon(
|
||||
label: Text("Preview"), //TODO: INTL
|
||||
onPressed: state.isNotEmpty
|
||||
? () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DocumentView(
|
||||
documentBytes: _assembleFileBytes(
|
||||
state,
|
||||
forcePdf: true,
|
||||
).then((file) => file.bytes),
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
icon: const Icon(Icons.visibility_outlined),
|
||||
);
|
||||
},
|
||||
),
|
||||
BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||
builder: (context, state) {
|
||||
return TextButton.icon(
|
||||
label: Text("Clear all"), //TODO: INTL
|
||||
onPressed: state.isEmpty ? null : () => _reset(context),
|
||||
icon: const Icon(Icons.delete_sweep_outlined),
|
||||
);
|
||||
},
|
||||
),
|
||||
BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||
builder: (context, state) {
|
||||
return TextButton.icon(
|
||||
label: Text("Upload"), //TODO: INTL
|
||||
onPressed: state.isEmpty || !isConnected
|
||||
? null
|
||||
: () => _onPrepareDocumentUpload(context),
|
||||
icon: const Icon(Icons.upload_outlined),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
AppBar _buildAppBar(BuildContext context, bool isConnected) {
|
||||
return AppBar(
|
||||
title: Text(S.of(context).documentScannerPageTitle),
|
||||
@@ -170,7 +252,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBody(bool isConnected) {
|
||||
Widget _buildEmptyState(bool isConnected) {
|
||||
return BlocBuilder<DocumentScannerCubit, List<File>>(
|
||||
builder: (context, scans) {
|
||||
if (scans.isNotEmpty) {
|
||||
@@ -207,7 +289,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
}
|
||||
|
||||
Widget _buildImageGrid(List<File> scans) {
|
||||
return GridView.builder(
|
||||
return SliverGrid.builder(
|
||||
itemCount: scans.length,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_mobile/core/widgets/material/search/m3_search_bar.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/dialogs/account_settings_dialog.dart';
|
||||
|
||||
typedef OpenSearchCallback = void Function(BuildContext context);
|
||||
|
||||
@@ -8,11 +9,13 @@ class SearchAppBar extends StatefulWidget with PreferredSizeWidget {
|
||||
final PreferredSizeWidget? bottom;
|
||||
final OpenSearchCallback onOpenSearch;
|
||||
final Color? backgroundColor;
|
||||
final String hintText;
|
||||
const SearchAppBar({
|
||||
super.key,
|
||||
required this.onOpenSearch,
|
||||
this.bottom,
|
||||
this.backgroundColor,
|
||||
required this.hintText,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -26,25 +29,29 @@ class _SearchAppBarState extends State<SearchAppBar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverAppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
floating: true,
|
||||
pinned: true,
|
||||
snap: true,
|
||||
backgroundColor: widget.backgroundColor,
|
||||
title: SearchBar(
|
||||
height: kToolbarHeight - 8,
|
||||
supportingText: "Search documents",
|
||||
supportingText: widget.hintText,
|
||||
onTap: () => widget.onOpenSearch(context),
|
||||
leadingIcon: IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openDrawer();
|
||||
},
|
||||
onPressed: Scaffold.of(context).openDrawer,
|
||||
),
|
||||
trailingIcon: IconButton(
|
||||
icon: const CircleAvatar(
|
||||
child: Text("A"),
|
||||
),
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AccountSettingsDialog(),
|
||||
);
|
||||
},
|
||||
),
|
||||
).paddedOnly(top: 4, bottom: 4),
|
||||
bottom: widget.bottom,
|
||||
|
||||
103
lib/features/settings/view/dialogs/account_settings_dialog.dart
Normal file
103
lib/features/settings/view/dialogs/account_settings_dialog.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||
import 'package:paperless_mobile/core/widgets/paperless_logo.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/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
|
||||
class AccountSettingsDialog extends StatelessWidget {
|
||||
const AccountSettingsDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
scrollable: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
icon: const PaperlessLogo.green(),
|
||||
title: const Text(" Your Accounts"),
|
||||
content: BlocBuilder<PaperlessServerInformationCubit,
|
||||
PaperlessServerInformationState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
ExpansionTile(
|
||||
leading: CircleAvatar(
|
||||
child: Text(state.information?.username
|
||||
?.toUpperCase()
|
||||
.substring(0, 1) ??
|
||||
''),
|
||||
),
|
||||
title: Text(state.information?.username ?? ''),
|
||||
subtitle: Text(state.information?.host ?? ''),
|
||||
children: const [
|
||||
HintCard(
|
||||
hintText: "WIP: Coming soon with multi user support!",
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(),
|
||||
ListTile(
|
||||
dense: true,
|
||||
leading: const Icon(Icons.person_add_rounded),
|
||||
title: const Text("Add another account"), //TODO: INTL
|
||||
onTap: () {},
|
||||
),
|
||||
Divider(),
|
||||
OutlinedButton(
|
||||
child: Text(
|
||||
S.of(context).appDrawerLogoutLabel,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
await _onLogout(context);
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(S.of(context).genericActionCloseLabel),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLogout(BuildContext context) async {
|
||||
try {
|
||||
await context.read<AuthenticationCubit>().logout();
|
||||
await context.read<ApplicationSettingsCubit>().clear();
|
||||
await context.read<LabelRepository<Tag, TagRepositoryState>>().clear();
|
||||
await context
|
||||
.read<LabelRepository<Correspondent, CorrespondentRepositoryState>>()
|
||||
.clear();
|
||||
await context
|
||||
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>()
|
||||
.clear();
|
||||
await context
|
||||
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>()
|
||||
.clear();
|
||||
await context.read<SavedViewRepository>().clear();
|
||||
await HydratedBloc.storage.clear();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,16 +26,6 @@ class SettingsPage extends StatelessWidget {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).appDrawerSettingsLabel),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.logout),
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
onPressed: () async {
|
||||
await _onLogout(context);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BlocBuilder<PaperlessServerInformationCubit,
|
||||
PaperlessServerInformationState>(
|
||||
@@ -48,14 +38,16 @@ class SettingsPage extends StatelessWidget {
|
||||
" " +
|
||||
(info.username ?? 'unknown') +
|
||||
"@${info.host}",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
subtitle: Text(
|
||||
S.of(context).serverInformationPaperlessVersionText +
|
||||
' ' +
|
||||
info.version.toString() +
|
||||
' (API v${info.apiVersion})',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -100,25 +92,4 @@ class SettingsPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onLogout(BuildContext context) async {
|
||||
try {
|
||||
await context.read<AuthenticationCubit>().logout();
|
||||
await context.read<ApplicationSettingsCubit>().clear();
|
||||
await context.read<LabelRepository<Tag, TagRepositoryState>>().clear();
|
||||
await context
|
||||
.read<LabelRepository<Correspondent, CorrespondentRepositoryState>>()
|
||||
.clear();
|
||||
await context
|
||||
.read<LabelRepository<DocumentType, DocumentTypeRepositoryState>>()
|
||||
.clear();
|
||||
await context
|
||||
.read<LabelRepository<StoragePath, StoragePathRepositoryState>>()
|
||||
.clear();
|
||||
await context.read<SavedViewRepository>().clear();
|
||||
await HydratedBloc.storage.clear();
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,5 +627,6 @@
|
||||
"verifyIdentityPageTitle": "Ověř svou identitu",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Ověřit identitu",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||
"genericActionCloseLabel": "Close"
|
||||
}
|
||||
@@ -627,5 +627,6 @@
|
||||
"verifyIdentityPageTitle": "Verifiziere deine Identität",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Identität verifizieren",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||
"genericActionCloseLabel": "Close"
|
||||
}
|
||||
@@ -627,5 +627,6 @@
|
||||
"verifyIdentityPageTitle": "Verify your identity",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Verify Identity",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||
"genericActionCloseLabel": "Close"
|
||||
}
|
||||
@@ -627,5 +627,6 @@
|
||||
"verifyIdentityPageTitle": "Kimliğinizi doğrulayın",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Kimliği Doğrula",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||
"genericActionCloseLabel": "Close"
|
||||
}
|
||||
@@ -49,6 +49,7 @@ import 'package:paperless_mobile/features/settings/bloc/application_settings_sta
|
||||
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||
import 'package:paperless_mobile/theme.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@@ -254,6 +255,10 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
FormBuilderLocalizations.delegate,
|
||||
],
|
||||
routes: {
|
||||
DocumentDetailsRoute.routeName: (context) =>
|
||||
const DocumentDetailsRoute(),
|
||||
},
|
||||
home: const AuthenticationWrapper(),
|
||||
);
|
||||
},
|
||||
|
||||
46
lib/routes/document_details_route.dart
Normal file
46
lib/routes/document_details_route.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||
|
||||
class DocumentDetailsRoute extends StatelessWidget {
|
||||
static const String routeName = "/documentDetails";
|
||||
const DocumentDetailsRoute({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final args = ModalRoute.of(context)!.settings.arguments
|
||||
as DocumentDetailsRouteArguments;
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) => DocumentDetailsCubit(
|
||||
context.read<PaperlessDocumentsApi>(),
|
||||
args.document,
|
||||
),
|
||||
child: LabelRepositoriesProvider(
|
||||
child: DocumentDetailsPage(
|
||||
allowEdit: args.allowEdit,
|
||||
isLabelClickable: args.isLabelClickable,
|
||||
titleAndContentQueryString: args.titleAndContentQueryString,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentDetailsRouteArguments {
|
||||
final DocumentModel document;
|
||||
final bool isLabelClickable;
|
||||
final bool allowEdit;
|
||||
final String? titleAndContentQueryString;
|
||||
|
||||
DocumentDetailsRouteArguments({
|
||||
required this.document,
|
||||
this.isLabelClickable = true,
|
||||
this.allowEdit = true,
|
||||
this.titleAndContentQueryString,
|
||||
});
|
||||
}
|
||||
@@ -84,9 +84,11 @@ class DocumentModel extends Equatable {
|
||||
id: id,
|
||||
title: title ?? this.title,
|
||||
content: content ?? this.content,
|
||||
documentType: documentType?.call() ?? this.documentType,
|
||||
correspondent: correspondent?.call() ?? this.correspondent,
|
||||
storagePath: storagePath?.call() ?? this.storagePath,
|
||||
documentType:
|
||||
documentType != null ? documentType.call() : this.documentType,
|
||||
correspondent:
|
||||
correspondent != null ? correspondent.call() : this.correspondent,
|
||||
storagePath: storagePath != null ? storagePath.call() : this.storagePath,
|
||||
tags: tags ?? this.tags,
|
||||
created: created ?? this.created,
|
||||
modified: modified ?? this.modified,
|
||||
|
||||
16
pubspec.lock
16
pubspec.lock
@@ -65,6 +65,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.0"
|
||||
auto_route:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: auto_route
|
||||
sha256: "12047baeca0e01df93165ef33275b32119d72699ab9a49dc64c20e78f586f96d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.4"
|
||||
auto_route_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: auto_route_generator
|
||||
sha256: de5bfbc02ae4eebb339dd90d325749ae7536e903f6513ef72b88954072d72b0e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.3"
|
||||
badges:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -88,6 +88,7 @@ dependencies:
|
||||
responsive_builder: ^0.4.3
|
||||
open_filex: ^4.3.2
|
||||
dynamic_color: ^1.5.4
|
||||
auto_route: ^5.0.4
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
@@ -102,6 +103,7 @@ dev_dependencies:
|
||||
flutter_lints: ^1.0.0
|
||||
json_serializable: ^6.5.4
|
||||
dart_code_metrics: ^5.4.0
|
||||
auto_route_generator: ^5.0.3
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
Reference in New Issue
Block a user