Added dependencies which fix crash on android 12L/13, improved list layout in inbox

This commit is contained in:
Anton Stubenbord
2023-01-14 19:33:00 +01:00
parent 0eb8e4954c
commit 21462c0463
31 changed files with 492 additions and 234 deletions

View File

@@ -35,13 +35,7 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
_tagRepository = tagRepository,
_correspondentRepository = correspondentRepository,
_documentTypeRepository = documentTypeRepository,
super(
const DocumentUploadState(
tags: {},
correspondents: {},
documentTypes: {},
),
) {
super(const DocumentUploadState()) {
_subs.add(_tagRepository.values.listen(
(tags) => emit(state.copyWith(tags: tags?.values)),
));

View File

@@ -7,9 +7,9 @@ class DocumentUploadState extends Equatable {
final Map<int, DocumentType> documentTypes;
const DocumentUploadState({
required this.tags,
required this.correspondents,
required this.documentTypes,
this.tags = const {},
this.correspondents = const {},
this.documentTypes = const {},
});
@override

View File

@@ -236,8 +236,10 @@ class _DocumentUploadPreparationPageState
final taskId = await cubit.upload(
widget.fileBytes,
filename:
_padWithPdfExtension(_formKey.currentState?.value[fkFileName]),
filename: _padWithExtension(
_formKey.currentState?.value[fkFileName],
widget.fileExtension,
),
title: title,
documentType: docType.id,
correspondent: correspondent.id,
@@ -261,11 +263,12 @@ class _DocumentUploadPreparationPageState
}
}
String _padWithPdfExtension(String source) {
return source.endsWith(".pdf") ? source : '$source.pdf';
String _padWithExtension(String source, [String? extension]) {
final ext = extension ?? '.pdf';
return source.endsWith(ext) ? source : '$source$ext';
}
String _formatFilename(String source) {
return source.replaceAll(RegExp(r"[\W_]"), "_");
return source.replaceAll(RegExp(r"[\W_]"), "_").toLowerCase();
}
}

View File

@@ -219,6 +219,11 @@ class DocumentsCubit extends Cubit<DocumentsState> with HydratedMixin {
}
}
Future<Iterable<String>> autocomplete(String query) async {
final res = await _api.autocomplete(query);
return res;
}
void unselectView() {
emit(state.copyWith(selectedSavedViewId: null));
}

View File

@@ -309,7 +309,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Suggestions: ",
S.of(context).documentEditPageSuggestionsLabel,
style: Theme.of(context).textTheme.bodySmall,
),
SizedBox(

View File

@@ -50,7 +50,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
double _offset = 0;
double _last = 0;
static const double _savedViewWidgetHeight = 78 + 16;
static const double _savedViewWidgetHeight = 80 + 16;
@override
void initState() {

View File

@@ -36,62 +36,60 @@ class DocumentListItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
child: ListTile(
dense: true,
selected: isSelected,
onTap: () => _onTap(),
selectedTileColor: Theme.of(context).colorScheme.inversePrimary,
onLongPress: () => onSelected?.call(document),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
children: [
AbsorbPointer(
absorbing: isAtLeastOneSelected,
child: CorrespondentWidget(
isClickable: isLabelClickable,
correspondentId: document.correspondent,
onSelected: onCorrespondentSelected,
),
return ListTile(
dense: true,
selected: isSelected,
onTap: () => _onTap(),
selectedTileColor: Theme.of(context).colorScheme.inversePrimary,
onLongPress: () => onSelected?.call(document),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Row(
children: [
AbsorbPointer(
absorbing: isAtLeastOneSelected,
child: CorrespondentWidget(
isClickable: isLabelClickable,
correspondentId: document.correspondent,
onSelected: onCorrespondentSelected,
),
],
),
Text(
document.title,
overflow: TextOverflow.ellipsis,
maxLines: document.tags.isEmpty ? 2 : 1,
),
],
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: AbsorbPointer(
absorbing: isAtLeastOneSelected,
child: TagsWidget(
isClickable: isLabelClickable,
tagIds: document.tags,
isMultiLine: false,
isSelectedPredicate: isTagSelectedPredicate,
onTagSelected: (id) => onTagSelected?.call(id),
),
),
],
),
),
isThreeLine: document.tags.isNotEmpty,
leading: AspectRatio(
aspectRatio: _a4AspectRatio,
child: GestureDetector(
child: DocumentPreview(
id: document.id,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
),
Text(
document.title,
overflow: TextOverflow.ellipsis,
maxLines: document.tags.isEmpty ? 2 : 1,
),
),
contentPadding: const EdgeInsets.all(8.0),
],
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: AbsorbPointer(
absorbing: isAtLeastOneSelected,
child: TagsWidget(
isClickable: isLabelClickable,
tagIds: document.tags,
isMultiLine: false,
isSelectedPredicate: isTagSelectedPredicate,
onTagSelected: (id) => onTagSelected?.call(id),
),
),
),
isThreeLine: document.tags.isNotEmpty,
leading: AspectRatio(
aspectRatio: _a4AspectRatio,
child: GestureDetector(
child: DocumentPreview(
id: document.id,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
),
),
),
contentPadding: const EdgeInsets.all(8.0),
);
}

View File

@@ -1,7 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_type_ahead.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:provider/provider.dart';
class TextQueryFormField extends StatelessWidget {
final String name;
@@ -21,57 +24,69 @@ class TextQueryFormField extends StatelessWidget {
name: name,
initialValue: initialValue,
builder: (field) {
return TextFormField(
initialValue: initialValue?.queryText,
textInputAction: TextInputAction.done,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search_outlined),
labelText: _buildLabelText(context, field.value!.queryType),
suffixIcon: PopupMenuButton<QueryType>(
icon: onlyExtendedQueryAllowed
? Icon(
Icons.more_vert,
color: Theme.of(context).disabledColor,
)
: null,
enabled: !onlyExtendedQueryAllowed,
itemBuilder: (context) => [
PopupMenuItem(
child: ListTile(
title: Text(S
.of(context)
.documentFilterQueryOptionsTitleAndContentLabel),
),
value: QueryType.titleAndContent,
),
PopupMenuItem(
child: ListTile(
title: Text(
S.of(context).documentFilterQueryOptionsTitleLabel),
),
value: QueryType.title,
),
PopupMenuItem(
child: ListTile(
title: Text(
S.of(context).documentFilterQueryOptionsExtendedLabel),
),
value: QueryType.extended,
),
],
onSelected: (selection) {
field.didChange(field.value?.copyWith(queryType: selection));
return Autocomplete(
optionsBuilder: (value) =>
context.read<DocumentsCubit>().autocomplete(value.text),
initialValue: initialValue?.queryText != null
? TextEditingValue(text: initialValue!.queryText!)
: null,
fieldViewBuilder:
(context, textEditingController, focusNode, onFieldSubmitted) {
return TextFormField(
controller: textEditingController,
focusNode: focusNode,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search_outlined),
labelText: _buildLabelText(context, field.value!.queryType),
suffixIcon: _buildQueryTypeMenu(context, field),
),
onChanged: (value) {
field.didChange(field.value?.copyWith(queryText: value));
},
),
),
onChanged: (value) {
field.didChange(field.value?.copyWith(queryText: value));
);
},
);
},
);
}
PopupMenuButton<QueryType> _buildQueryTypeMenu(
BuildContext context, FormFieldState<TextQuery> field) {
return PopupMenuButton<QueryType>(
icon: onlyExtendedQueryAllowed
? Icon(
Icons.more_vert,
color: Theme.of(context).disabledColor,
)
: null,
enabled: !onlyExtendedQueryAllowed,
itemBuilder: (context) => [
PopupMenuItem(
child: ListTile(
title: Text(
S.of(context).documentFilterQueryOptionsTitleAndContentLabel),
),
value: QueryType.titleAndContent,
),
PopupMenuItem(
child: ListTile(
title: Text(S.of(context).documentFilterQueryOptionsTitleLabel),
),
value: QueryType.title,
),
PopupMenuItem(
child: ListTile(
title: Text(S.of(context).documentFilterQueryOptionsExtendedLabel),
),
value: QueryType.extended,
),
],
onSelected: (selection) {
field.didChange(field.value?.copyWith(queryType: selection));
},
);
}
String _buildLabelText(BuildContext context, QueryType queryType) {
switch (queryType) {
case QueryType.title:

View File

@@ -1,14 +1,14 @@
import 'package:flutter/material.dart';
import 'package:paperless_api/paperless_api.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/generated/l10n.dart';
class BulkDeleteConfirmationDialog extends StatelessWidget {
static const _bulletPoint = "\u2022";
final DocumentsState state;
const BulkDeleteConfirmationDialog({Key? key, required this.state})
: super(key: key);
const BulkDeleteConfirmationDialog({
Key? key,
required this.state,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -29,13 +29,7 @@ class BulkDeleteConfirmationDialog extends StatelessWidget {
.documentsPageSelectionBulkDeleteDialogWarningTextMany,
),
const SizedBox(height: 16),
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 150),
child: ListView(
shrinkWrap: true,
children: state.selection.map(_buildBulletPoint).toList(),
),
),
...state.selection.map(_buildBulletPoint).toList(),
const SizedBox(height: 16),
Text(
S.of(context).documentsPageSelectionBulkDeleteDialogContinueText),
@@ -61,12 +55,15 @@ class BulkDeleteConfirmationDialog extends StatelessWidget {
}
Widget _buildBulletPoint(DocumentModel doc) {
return Text(
"\t$_bulletPoint ${doc.title}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w700,
return ListTile(
dense: true,
title: Text(
doc.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.w700,
),
),
);
}

View File

@@ -32,6 +32,7 @@ import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:path/path.dart' as p;
import 'package:responsive_builder/responsive_builder.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@@ -165,44 +166,114 @@ class _HomePageState extends State<HomePage> {
},
),
],
child: Scaffold(
key: rootScaffoldKey,
bottomNavigationBar: BottomNavBar(
selectedIndex: _currentIndex,
onNavigationChanged: (index) {
if (_currentIndex != index) {
setState(() => _currentIndex = index);
}
},
),
drawer: const InfoDrawer(),
body: [
MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => DocumentsCubit(
context.read<PaperlessDocumentsApi>(),
context.read<SavedViewRepository>(),
child: ResponsiveBuilder(
builder: (context, sizingInformation) {
if (!sizingInformation.isMobile) {
return Scaffold(
key: rootScaffoldKey,
drawer: const InfoDrawer(),
body: Row(children: [
NavigationRail(
labelType: NavigationRailLabelType.all,
destinations: [
NavigationRailDestination(
icon: const Icon(Icons.description_outlined),
selectedIcon: Icon(
Icons.description,
color: Theme.of(context).colorScheme.primary,
),
label: Text(S.of(context).bottomNavDocumentsPageLabel),
),
NavigationRailDestination(
icon: const Icon(Icons.document_scanner_outlined),
selectedIcon: Icon(
Icons.document_scanner,
color: Theme.of(context).colorScheme.primary,
),
label: Text(S.of(context).bottomNavScannerPageLabel),
),
NavigationRailDestination(
icon: const Icon(Icons.sell_outlined),
selectedIcon: Icon(
Icons.sell,
color: Theme.of(context).colorScheme.primary,
),
label: Text(S.of(context).bottomNavLabelsPageLabel),
),
],
selectedIndex: _currentIndex,
onDestinationSelected: _onNavigationChanged,
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(
child: [
MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => DocumentsCubit(
context.read<PaperlessDocumentsApi>(),
context.read<SavedViewRepository>(),
),
),
BlocProvider(
create: (context) => SavedViewCubit(
context.read<SavedViewRepository>(),
),
),
],
child: const DocumentsPage(),
),
BlocProvider.value(
value: _scannerCubit,
child: const ScannerPage(),
),
const LabelsPage(),
][_currentIndex]),
]),
);
}
return Scaffold(
key: rootScaffoldKey,
bottomNavigationBar: BottomNavBar(
selectedIndex: _currentIndex,
onNavigationChanged: _onNavigationChanged,
),
drawer: const InfoDrawer(),
body: [
MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => DocumentsCubit(
context.read<PaperlessDocumentsApi>(),
context.read<SavedViewRepository>(),
),
),
BlocProvider(
create: (context) => SavedViewCubit(
context.read<SavedViewRepository>(),
),
),
],
child: const DocumentsPage(),
),
BlocProvider(
create: (context) => SavedViewCubit(
context.read<SavedViewRepository>(),
),
BlocProvider.value(
value: _scannerCubit,
child: const ScannerPage(),
),
],
child: const DocumentsPage(),
),
BlocProvider.value(
value: _scannerCubit,
child: const ScannerPage(),
),
const LabelsPage(),
][_currentIndex],
const LabelsPage(),
][_currentIndex],
);
},
),
);
}
void _onNavigationChanged(index) {
if (_currentIndex != index) {
setState(() => _currentIndex = index);
}
}
void _initializeData(BuildContext context) {
try {
context.read<LabelRepository<Tag, TagRepositoryState>>().findAll();

View File

@@ -122,6 +122,19 @@ class InboxCubit extends HydratedCubit<InboxState> {
}
}
Future<void> assignAsn(DocumentModel document) async {
if (document.archiveSerialNumber == null) {
final int asn = await _documentsApi.findNextAsn();
final updatedDocument = await _documentsApi
.update(document.copyWith(archiveSerialNumber: asn));
emit(
state.copyWith(
inboxItems: state.inboxItems
.map((e) => e.id == document.id ? updatedDocument : e)),
);
}
}
void acknowledgeHint() {
emit(state.copyWith(isHintAcknowledged: true));
}

View File

@@ -3,11 +3,20 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.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/extensions/flutter_extensions.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/documents/view/widgets/document_preview.dart';
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.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:badges/badges.dart' as b;
import 'package:paperless_mobile/extensions/string_extensions.dart';
class InboxItem extends StatelessWidget {
static const _a4AspectRatio = 1 / 1.4142;
@@ -23,7 +32,31 @@ class InboxItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(document.title),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
IntrinsicHeight(
child: Wrap(
direction: Axis.horizontal,
children: [
Row(
children: [
Text(
document.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyLarge,
)
],
),
Row(
children: [],
),
],
),
),
],
),
isThreeLine: true,
leading: AspectRatio(
aspectRatio: _a4AspectRatio,
@@ -37,16 +70,54 @@ class InboxItem extends StatelessWidget {
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(DateFormat().format(document.added)),
Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.person_outline,
size: Theme.of(context).textTheme.bodySmall?.fontSize,
),
Flexible(
child: LabelText<Correspondent, CorrespondentRepositoryState>(
id: document.correspondent,
style: Theme.of(context).textTheme.bodySmall,
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.description_outlined,
size: Theme.of(context).textTheme.bodySmall?.fontSize,
),
Flexible(
child: LabelText<DocumentType, DocumentTypeRepositoryState>(
id: document.documentType,
style: Theme.of(context).textTheme.bodySmall,
),
),
],
),
TagsWidget(
tagIds: document.tags,
isMultiLine: false,
isClickable: false,
isSelectedPredicate: (_) => false,
onTagSelected: (_) {},
showShortNames: true,
dense: true,
),
],
),
trailing: document.archiveSerialNumber != null
? Text(
document.archiveSerialNumber!.toString(),
style: Theme.of(context).textTheme.bodySmall,
)
: null,
onTap: () async {
final returnedDocument = await Navigator.push<DocumentModel?>(
context,

View File

@@ -7,6 +7,8 @@ class TagWidget extends StatelessWidget {
final VoidCallback onSelected;
final bool isSelected;
final bool isClickable;
final bool showShortName;
final bool dense;
const TagWidget({
super.key,
@@ -15,6 +17,8 @@ class TagWidget extends StatelessWidget {
this.isClickable = true,
required this.onSelected,
required this.isSelected,
this.showShortName = false,
this.dense = false,
});
@override
@@ -24,13 +28,18 @@ class TagWidget extends StatelessWidget {
child: AbsorbPointer(
absorbing: !isClickable,
child: FilterChip(
labelPadding:
dense ? const EdgeInsets.symmetric(horizontal: 2) : null,
padding: dense ? const EdgeInsets.all(4) : null,
selected: isSelected,
selectedColor: tag.color,
onSelected: (_) => onSelected(),
visualDensity: const VisualDensity(vertical: -2),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
label: Text(
tag.name,
showShortName && tag.name.length > 6
? '${tag.name.substring(0, 6)}...'
: tag.name,
style: TextStyle(color: tag.textColor),
),
checkmarkColor: tag.textColor,

View File

@@ -6,13 +6,15 @@ import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/bloc/providers/tag_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart';
class TagsWidget extends StatefulWidget {
class TagsWidget extends StatelessWidget {
final Iterable<int> tagIds;
final bool isMultiLine;
final VoidCallback? afterTagTapped;
final void Function(int tagId)? onTagSelected;
final bool isClickable;
final bool Function(int id) isSelectedPredicate;
final bool showShortNames;
final bool dense;
const TagsWidget({
Key? key,
@@ -21,32 +23,31 @@ class TagsWidget extends StatefulWidget {
this.isMultiLine = true,
this.isClickable = true,
required this.isSelectedPredicate,
required this.onTagSelected,
this.onTagSelected,
this.showShortNames = false,
this.dense = false,
}) : super(key: key);
@override
State<TagsWidget> createState() => _TagsWidgetState();
}
class _TagsWidgetState extends State<TagsWidget> {
@override
Widget build(BuildContext context) {
return TagBlocProvider(
child: BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
builder: (context, state) {
final children = widget.tagIds
final children = tagIds
.where((id) => state.labels.containsKey(id))
.map(
(id) => TagWidget(
tag: state.getLabel(id)!,
afterTagTapped: widget.afterTagTapped,
isClickable: widget.isClickable,
isSelected: widget.isSelectedPredicate(id),
onSelected: () => widget.onTagSelected?.call(id),
afterTagTapped: afterTagTapped,
isClickable: isClickable,
isSelected: isSelectedPredicate(id),
onSelected: () => onTagSelected?.call(id),
showShortName: showShortNames,
dense: dense,
),
)
.toList();
if (widget.isMultiLine) {
if (isMultiLine) {
return Wrap(
runAlignment: WrapAlignment.start,
children: children,

View File

@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/state/repository_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_state.dart';
import 'package:paperless_mobile/features/labels/bloc/providers/document_type_bloc_provider.dart';
class LabelText<T extends Label, State extends RepositoryState>
extends StatelessWidget {
final int? id;
final String placeholder;
final TextStyle? style;
const LabelText({
super.key,
this.style,
this.id,
this.placeholder = "",
});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LabelCubit<T>(
context.read<LabelRepository<T, State>>(),
),
child: BlocBuilder<LabelCubit<T>, LabelState<T>>(
builder: (context, state) {
return Text(
state.labels[id]?.toString() ?? placeholder,
style: style,
);
},
),
);
;
}
}

View File

@@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
@@ -6,7 +8,7 @@ import 'package:paperless_mobile/generated/l10n.dart';
class ServerAddressFormField extends StatefulWidget {
static const String fkServerAddress = "serverAddress";
final void Function(String address) onDone;
final void Function(String? address) onDone;
const ServerAddressFormField({
Key? key,
required this.onDone,
@@ -31,7 +33,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
});
}
final TextEditingController _textEditingController = TextEditingController();
final _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) {
@@ -56,7 +58,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
labelText: S.of(context).loginPageServerUrlFieldLabel,
suffixIcon: _canClear
? IconButton(
icon: Icon(Icons.clear),
icon: const Icon(Icons.clear),
color: Theme.of(context).iconTheme.color,
onPressed: () {
_textEditingController.clear();
@@ -64,18 +66,14 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
)
: null,
),
onSubmitted: (value) {
if (value == null) return;
// Remove trailing slash if it is a valid address.
String address = value.trim();
address = _replaceTrailingSlashes(address);
_textEditingController.text = address;
widget.onDone(address);
},
onSubmitted: (_) => _formatInput(),
);
}
String _replaceTrailingSlashes(String src) {
return src.replaceAll(RegExp(r'^\/+|\/+$'), '');
void _formatInput() {
String address = _textEditingController.text.trim();
address = address.replaceAll(RegExp(r'^\/+|\/+$'), '');
_textEditingController.text = address;
widget.onDone(address);
}
}

View File

@@ -58,8 +58,12 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
),
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: Text("Test connection"),
onPressed: _updateReachability,
),
FilledButton(
child: Text(S.of(context).loginPageContinueLabel),
onPressed: _reachabilityStatus == ReachabilityStatus.reachable
@@ -76,6 +80,7 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
setState(() {
_isCheckingConnection = true;
});
final status = await context
.read<ConnectivityStatusService>()
.isPaperlessServerReachable(

View File

@@ -32,6 +32,10 @@ class LocalNotificationService {
initializationSettings,
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
);
await _plugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.requestPermission();
}
//TODO: INTL

View File

@@ -142,31 +142,29 @@ class _ScannerPageState extends State<ScannerPage>
final file = await _assembleFileBytes(
context.read<DocumentScannerCubit>().state,
);
final taskId = await Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => LabelRepositoriesProvider(
child: BlocProvider(
create: (context) => DocumentUploadCubit(
localVault: context.read<LocalVault>(),
documentApi: context.read<PaperlessDocumentsApi>(),
correspondentRepository: context.read<
LabelRepository<Correspondent,
CorrespondentRepositoryState>>(),
documentTypeRepository: context.read<
LabelRepository<DocumentType,
DocumentTypeRepositoryState>>(),
tagRepository:
context.read<LabelRepository<Tag, TagRepositoryState>>(),
),
child: DocumentUploadPreparationPage(
fileBytes: file.bytes,
fileExtension: file.extension,
),
),
final taskId = await Navigator.of(context).push<String?>(
MaterialPageRoute(
builder: (_) => LabelRepositoriesProvider(
child: BlocProvider(
create: (context) => DocumentUploadCubit(
localVault: context.read<LocalVault>(),
documentApi: context.read<PaperlessDocumentsApi>(),
correspondentRepository: context.read<
LabelRepository<Correspondent,
CorrespondentRepositoryState>>(),
documentTypeRepository: context.read<
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
tagRepository:
context.read<LabelRepository<Tag, TagRepositoryState>>(),
),
child: DocumentUploadPreparationPage(
fileBytes: file.bytes,
fileExtension: file.extension,
),
),
) ??
false;
),
),
);
if (taskId != null) {
// For paperless version older than 1.11.3, task id will always be null!
context.read<DocumentScannerCubit>().reset();