Added translations, fixed inbox, minor updates to pages

This commit is contained in:
Anton Stubenbord
2023-02-03 00:27:14 +01:00
parent ba5a1fcbc7
commit 3f305ce1d6
24 changed files with 982 additions and 177 deletions

View File

@@ -51,7 +51,7 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
hintStyle: theme.textTheme.bodyLarge?.apply(
color: theme.colorScheme.onSurfaceVariant,
),
hintText: "Search documents", //TODO: INTL
hintText: S.of(context).documentSearchSearchDocuments,
border: InputBorder.none,
),
controller: _queryController,
@@ -143,8 +143,10 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
slivers: [
SliverToBoxAdapter(child: header),
if (state.hasLoaded && !state.isLoading && state.documents.isEmpty)
const SliverToBoxAdapter(
child: Center(child: Text("No documents found.")), //TODO: INTL
SliverToBoxAdapter(
child: Center(
child: Text(S.of(context).documentSearchNoMatchesFound),
),
)
else
SliverAdaptiveDocumentsView(

View File

@@ -47,9 +47,7 @@ class DocumentsCubit extends HydratedCubit<DocumentsState>
),
);
} else {
emit(
state.copyWith(selection: [...state.selection, model]),
);
emit(state.copyWith(selection: [...state.selection, model]));
}
}

View File

@@ -291,7 +291,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
label: Text(S.of(context).documentCreatedPropertyLabel),
),
initialValue: initialCreatedAtDate,
format: DateFormat("dd. MMMM yyyy"), //TODO: Localized date format
format: DateFormat.yMMMMd(),
initialEntryMode: DatePickerEntryMode.calendar,
),
if (_filteredSuggestions.hasSuggestedDates)

View File

@@ -4,6 +4,7 @@ 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/extensions/flutter_extensions.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';
@@ -12,6 +13,7 @@ import 'package:paperless_mobile/features/documents/view/widgets/adaptive_docume
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.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/sort_documents_button.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';
@@ -19,6 +21,7 @@ import 'package:paperless_mobile/features/saved_view/view/saved_view_list.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/settings/model/view_type.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';
@@ -147,7 +150,6 @@ class _DocumentsPageState extends State<DocumentsPage>
return false;
},
child: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverOverlapAbsorber(
// This widget takes the overlapping behavior of the SliverAppBar,
@@ -160,17 +162,41 @@ class _DocumentsPageState extends State<DocumentsPage>
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
context,
),
sliver: SearchAppBar(
hintText: "Search documents", //TODO: INTL
onOpenSearch: showDocumentSearchPage,
bottom: TabBar(
controller: _tabController,
isScrollable: true,
tabs: [
Tab(text: S.of(context).documentsPageTitle),
Tab(text: S.of(context).savedViewsLabel),
],
),
sliver: BlocBuilder<DocumentsCubit, DocumentsState>(
builder: (context, state) {
if (state.selection.isNotEmpty) {
return SliverAppBar(
floating: false,
pinned: true,
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => context
.read<DocumentsCubit>()
.resetSelection(),
),
title: Text(
"${state.selection.length} ${S.of(context).documentsSelectedText}",
),
actions: [
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _onDelete(state),
),
],
);
}
return SearchAppBar(
hintText: S.of(context).documentSearchSearchDocuments,
onOpenSearch: showDocumentSearchPage,
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(text: S.of(context).documentsPageTitle),
Tab(text: S.of(context).savedViewsLabel),
],
),
);
},
),
),
],
@@ -186,7 +212,7 @@ class _DocumentsPageState extends State<DocumentsPage>
_currentTab != desiredTab) {
setState(() => _currentTab = desiredTab);
}
return true;
return false;
},
child: NotificationListener<ScrollMetricsNotification>(
onNotification: (notification) {
@@ -213,7 +239,7 @@ class _DocumentsPageState extends State<DocumentsPage>
),
);
}
return true;
return false;
},
child: TabBarView(
controller: _tabController,
@@ -233,6 +259,7 @@ class _DocumentsPageState extends State<DocumentsPage>
.sliverOverlapAbsorberHandleFor(
context),
),
_buildViewActions(),
BlocBuilder<DocumentsCubit, DocumentsState>(
buildWhen: (previous, current) =>
!const ListEquality().equals(
@@ -324,8 +351,33 @@ class _DocumentsPageState extends State<DocumentsPage>
);
}
//TODO: Add app bar...
void _onDelete(BuildContext context, DocumentsState documentsState) async {
Widget _buildViewActions() {
return SliverToBoxAdapter(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const SortDocumentsButton(),
BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
builder: (context, state) {
return IconButton(
icon: Icon(
state.preferredViewType == ViewType.list
? Icons.grid_view_rounded
: Icons.list,
),
onPressed: () =>
context.read<ApplicationSettingsCubit>().setViewType(
state.preferredViewType.toggle(),
),
);
},
)
],
).paddedSymmetrically(horizontal: 8, vertical: 4),
);
}
void _onDelete(DocumentsState documentsState) async {
final shouldDelete = await showDialog<bool>(
context: context,
builder: (context) =>

View File

@@ -20,15 +20,14 @@ import 'package:paperless_mobile/features/document_upload/view/document_upload_p
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
import 'package:paperless_mobile/features/home/view/route_description.dart';
import 'package:paperless_mobile/features/home/view/widget/_app_drawer.dart';
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/pages/inbox_page.dart';
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
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';
@@ -189,13 +188,24 @@ class _HomePageState extends State<HomePage> {
label: S.of(context).bottomNavLabelsPageLabel,
),
RouteDescription(
icon: const Icon(Icons.inbox_outlined),
selectedIcon: Icon(
Icons.inbox,
color: Theme.of(context).colorScheme.primary,
),
label: S.of(context).bottomNavInboxPageLabel,
),
icon: const Icon(Icons.inbox_outlined),
selectedIcon: Icon(
Icons.inbox,
color: Theme.of(context).colorScheme.primary,
),
label: S.of(context).bottomNavInboxPageLabel,
badgeBuilder: (icon) => BlocBuilder<InboxCubit, InboxState>(
bloc: _inboxCubit,
builder: (context, state) {
if (state.itemsInInboxCount > 0) {
return Badge.count(
count: state.itemsInInboxCount,
child: icon,
);
}
return icon;
},
)),
];
final routes = <Widget>[
MultiBlocProvider(

View File

@@ -16,8 +16,8 @@ class RouteDescription {
NavigationDestination toNavigationDestination() {
return NavigationDestination(
label: label,
icon: icon,
selectedIcon: selectedIcon,
icon: badgeBuilder?.call(icon) ?? icon,
selectedIcon: badgeBuilder?.call(selectedIcon) ?? selectedIcon,
);
}

View File

@@ -115,6 +115,7 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
document.copyWith(tags: updatedTags),
);
await remove(document);
emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount - 1));
return tagsToRemove;
}
@@ -129,6 +130,7 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
tags: {...document.tags, ...removedTags},
);
await _documentsApi.update(updatedDoc);
emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount + 1));
return reload();
}
@@ -147,6 +149,7 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
emit(state.copyWith(
hasLoaded: true,
value: [],
itemsInInboxCount: 0,
));
} finally {
emit(state.copyWith(isLoading: false));
@@ -160,6 +163,7 @@ class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin {
} else {
// Remove document from inbox.
remove(document);
emit(state.copyWith(itemsInInboxCount: state.itemsInInboxCount - 1));
}
}

View File

@@ -76,78 +76,81 @@ class _InboxPageState extends State<InboxPage> {
);
},
),
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()],
);
}
body: RefreshIndicator(
edgeOffset: 78,
onRefresh: () => context.read<InboxCubit>().initializeInbox(),
child: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SearchAppBar(
hintText: S.of(context).documentSearchSearchDocuments,
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,
),
],
);
}
return _buildListItem(
entry.value[index],
);
},
// 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),
),
),
],
)
.flattened
.toList()
..add(const SliverToBoxAdapter(child: SizedBox(height: 78)));
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],
);
},
),
),
],
)
.flattened
.toList()
..add(const SliverToBoxAdapter(child: SizedBox(height: 78)));
// edgeOffset: kToolbarHeight,
return RefreshIndicator(
onRefresh: () => context.read<InboxCubit>().initializeInbox(),
child: CustomScrollView(
return CustomScrollView(
controller: _scrollController,
slivers: [
SliverToBoxAdapter(
@@ -160,9 +163,9 @@ class _InboxPageState extends State<InboxPage> {
),
...slivers,
],
),
);
},
);
},
),
),
),
);

View File

@@ -51,9 +51,7 @@ class TagsWidget extends StatelessWidget {
} else {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: children,
),
child: Row(children: children),
);
}
},

View File

@@ -85,7 +85,6 @@ class _LabelFormFieldState<T extends Label> extends State<LabelFormField<T>> {
TextStyle(color: Theme.of(context).disabledColor, fontSize: 18.0),
),
),
getImmediateSuggestions: true,
loadingBuilder: (context) => Container(),
initialValue: widget.initialValue ?? const IdQueryParameter.unset(),
name: widget.name,
@@ -108,7 +107,6 @@ class _LabelFormFieldState<T extends Label> extends State<LabelFormField<T>> {
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
style: ListTileStyle.list,
),
suggestionsCallback: (pattern) {
final List<IdQueryParameter> suggestions = widget.labelOptions.entries

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/widgets/hint_card.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_details_cubit.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart';
@@ -17,10 +17,11 @@ class SavedViewList extends StatelessWidget {
return BlocBuilder<SavedViewCubit, SavedViewState>(
builder: (context, state) {
if (state.value.isEmpty) {
return Text(
S.of(context).savedViewsEmptyStateText,
textAlign: TextAlign.center,
).padded();
return SliverToBoxAdapter(
child: HintCard(
hintText: S.of(context).savedViewsEmptyStateText,
),
);
}
return SliverList(
delegate: SliverChildBuilderDelegate(
@@ -29,8 +30,10 @@ class SavedViewList extends StatelessWidget {
return ListTile(
title: Text(view.name),
subtitle: Text(
"${view.filterRules.length} filter(s) set",
), //TODO: INTL w/ placeholder
S
.of(context)
.savedViewsFiltersSetCount(view.filterRules.length),
),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
@@ -42,7 +45,6 @@ class SavedViewList extends StatelessWidget {
savedView: view,
),
),
BlocProvider.value(value: savedViewCubit),
],
child: SavedViewPage(
onDelete: savedViewCubit.remove,

View File

@@ -65,7 +65,7 @@ class _ScannerPageState extends State<ScannerPage>
floatHeaderSlivers: false,
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SearchAppBar(
hintText: "Search documents", //TODO: INTL
hintText: S.of(context).documentSearchSearchDocuments,
onOpenSearch: showDocumentSearchPage,
bottom: PreferredSize(
child: _buildActions(connectedState.isConnected),
@@ -101,7 +101,7 @@ class _ScannerPageState extends State<ScannerPage>
BlocBuilder<DocumentScannerCubit, List<File>>(
builder: (context, state) {
return TextButton.icon(
label: Text("Preview"), //TODO: INTL
label: Text(S.of(context).scannerPagePreviewLabel),
onPressed: state.isNotEmpty
? () => Navigator.of(context).push(
MaterialPageRoute(
@@ -121,7 +121,7 @@ class _ScannerPageState extends State<ScannerPage>
BlocBuilder<DocumentScannerCubit, List<File>>(
builder: (context, state) {
return TextButton.icon(
label: Text("Clear all"), //TODO: INTL
label: Text(S.of(context).scannerPageClearAllLabel),
onPressed: state.isEmpty ? null : () => _reset(context),
icon: const Icon(Icons.delete_sweep_outlined),
);
@@ -130,7 +130,7 @@ class _ScannerPageState extends State<ScannerPage>
BlocBuilder<DocumentScannerCubit, List<File>>(
builder: (context, state) {
return TextButton.icon(
label: Text("Upload"), //TODO: INTL
label: Text(S.of(context).scannerPageUploadLabel),
onPressed: state.isEmpty || !isConnected
? null
: () => _onPrepareDocumentUpload(context),

View File

@@ -11,7 +11,7 @@ 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/hint_card.dart';
import 'package:paperless_mobile/core/widgets/paperless_logo.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.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';
@@ -25,8 +25,12 @@ class AccountSettingsDialog extends StatelessWidget {
return AlertDialog(
scrollable: true,
contentPadding: EdgeInsets.zero,
icon: const PaperlessLogo.green(),
title: const Text(" Your Accounts"),
title: Row(
children: [
const CloseButton(),
Text(S.of(context).accountSettingsTitle),
],
),
content: BlocBuilder<PaperlessServerInformationCubit,
PaperlessServerInformationState>(
builder: (context, state) {
@@ -55,28 +59,27 @@ class AccountSettingsDialog extends StatelessWidget {
onTap: () {},
),
Divider(),
OutlinedButton(
FilledButton(
style: ButtonStyle(
backgroundColor: MaterialStatePropertyAll(
Theme.of(context).colorScheme.error,
),
),
child: Text(
S.of(context).appDrawerLogoutLabel,
style: TextStyle(
color: Theme.of(context).colorScheme.error,
color: Theme.of(context).colorScheme.onError,
),
),
onPressed: () async {
await _onLogout(context);
Navigator.of(context).maybePop();
},
),
).padded(16),
],
);
},
),
actions: [
TextButton(
child: Text(S.of(context).genericActionCloseLabel),
onPressed: () => Navigator.pop(context),
),
],
);
}

View File

@@ -47,11 +47,11 @@ class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
),
RadioOption(
value: 'cs',
label: _languageOptions['cs']! + " *",
label: _languageOptions['cs']! + "*",
),
RadioOption(
value: 'tr',
label: _languageOptions['tr']! + " *",
label: _languageOptions['tr']! + "*",
)
],
initialValue: context