mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-08 12:07:54 -06:00
Added dependencies which fix crash on android 12L/13, improved list layout in inbox
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -60,3 +60,5 @@ untranslated_messages.txt
|
|||||||
|
|
||||||
#lakos generated files
|
#lakos generated files
|
||||||
**/dot_images/*
|
**/dot_images/*
|
||||||
|
|
||||||
|
docker/
|
||||||
@@ -82,10 +82,13 @@ flutter {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation 'androidx.window:window:1.0.0'
|
||||||
|
implementation 'androidx.window:window-java:1.0.0'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
// Required for flutter_local_notifications
|
// Required for flutter_local_notifications
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.paperless_mobile">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.paperless_mobile">
|
||||||
<application android:label="Paperless Mobile"
|
<application android:label="Paperless Mobile"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/launcher_icon">
|
android:icon="@mipmap/launcher_icon">
|
||||||
@@ -11,10 +12,14 @@
|
|||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
Android process has started. This theme is visible to the user
|
||||||
to determine the Window background behind the Flutter UI. -->
|
while
|
||||||
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/NormalTheme" />
|
the Flutter UI initializes. After that, this theme continues
|
||||||
|
to
|
||||||
|
determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme" />
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
@@ -34,14 +39,16 @@
|
|||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to
|
||||||
|
generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data android:name="flutterEmbedding" android:value="2" />
|
<meta-data android:name="flutterEmbedding" android:value="2" />
|
||||||
</application>
|
</application>
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="32" />
|
android:maxSdkVersion="32" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="29" />
|
android:maxSdkVersion="29" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
</manifest>
|
</manifest>
|
||||||
7
lib/extensions/string_extensions.dart
Normal file
7
lib/extensions/string_extensions.dart
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
extension SizeLimitedString on String {
|
||||||
|
String withLengthLimitedTo(int length, [String overflow = "..."]) {
|
||||||
|
return this.length > length
|
||||||
|
? '${substring(0, length - overflow.length)}$overflow'
|
||||||
|
: this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,13 +35,7 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
|||||||
_tagRepository = tagRepository,
|
_tagRepository = tagRepository,
|
||||||
_correspondentRepository = correspondentRepository,
|
_correspondentRepository = correspondentRepository,
|
||||||
_documentTypeRepository = documentTypeRepository,
|
_documentTypeRepository = documentTypeRepository,
|
||||||
super(
|
super(const DocumentUploadState()) {
|
||||||
const DocumentUploadState(
|
|
||||||
tags: {},
|
|
||||||
correspondents: {},
|
|
||||||
documentTypes: {},
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
_subs.add(_tagRepository.values.listen(
|
_subs.add(_tagRepository.values.listen(
|
||||||
(tags) => emit(state.copyWith(tags: tags?.values)),
|
(tags) => emit(state.copyWith(tags: tags?.values)),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ class DocumentUploadState extends Equatable {
|
|||||||
final Map<int, DocumentType> documentTypes;
|
final Map<int, DocumentType> documentTypes;
|
||||||
|
|
||||||
const DocumentUploadState({
|
const DocumentUploadState({
|
||||||
required this.tags,
|
this.tags = const {},
|
||||||
required this.correspondents,
|
this.correspondents = const {},
|
||||||
required this.documentTypes,
|
this.documentTypes = const {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -236,8 +236,10 @@ class _DocumentUploadPreparationPageState
|
|||||||
|
|
||||||
final taskId = await cubit.upload(
|
final taskId = await cubit.upload(
|
||||||
widget.fileBytes,
|
widget.fileBytes,
|
||||||
filename:
|
filename: _padWithExtension(
|
||||||
_padWithPdfExtension(_formKey.currentState?.value[fkFileName]),
|
_formKey.currentState?.value[fkFileName],
|
||||||
|
widget.fileExtension,
|
||||||
|
),
|
||||||
title: title,
|
title: title,
|
||||||
documentType: docType.id,
|
documentType: docType.id,
|
||||||
correspondent: correspondent.id,
|
correspondent: correspondent.id,
|
||||||
@@ -261,11 +263,12 @@ class _DocumentUploadPreparationPageState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _padWithPdfExtension(String source) {
|
String _padWithExtension(String source, [String? extension]) {
|
||||||
return source.endsWith(".pdf") ? source : '$source.pdf';
|
final ext = extension ?? '.pdf';
|
||||||
|
return source.endsWith(ext) ? source : '$source$ext';
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatFilename(String source) {
|
String _formatFilename(String source) {
|
||||||
return source.replaceAll(RegExp(r"[\W_]"), "_");
|
return source.replaceAll(RegExp(r"[\W_]"), "_").toLowerCase();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
void unselectView() {
|
||||||
emit(state.copyWith(selectedSavedViewId: null));
|
emit(state.copyWith(selectedSavedViewId: null));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Suggestions: ",
|
S.of(context).documentEditPageSuggestionsLabel,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
double _offset = 0;
|
double _offset = 0;
|
||||||
double _last = 0;
|
double _last = 0;
|
||||||
|
|
||||||
static const double _savedViewWidgetHeight = 78 + 16;
|
static const double _savedViewWidgetHeight = 80 + 16;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|||||||
@@ -36,62 +36,60 @@ class DocumentListItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return ListTile(
|
||||||
child: ListTile(
|
dense: true,
|
||||||
dense: true,
|
selected: isSelected,
|
||||||
selected: isSelected,
|
onTap: () => _onTap(),
|
||||||
onTap: () => _onTap(),
|
selectedTileColor: Theme.of(context).colorScheme.inversePrimary,
|
||||||
selectedTileColor: Theme.of(context).colorScheme.inversePrimary,
|
onLongPress: () => onSelected?.call(document),
|
||||||
onLongPress: () => onSelected?.call(document),
|
title: Column(
|
||||||
title: Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Row(
|
children: [
|
||||||
children: [
|
AbsorbPointer(
|
||||||
AbsorbPointer(
|
absorbing: isAtLeastOneSelected,
|
||||||
absorbing: isAtLeastOneSelected,
|
child: CorrespondentWidget(
|
||||||
child: CorrespondentWidget(
|
isClickable: isLabelClickable,
|
||||||
isClickable: isLabelClickable,
|
correspondentId: document.correspondent,
|
||||||
correspondentId: document.correspondent,
|
onSelected: onCorrespondentSelected,
|
||||||
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),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
isThreeLine: document.tags.isNotEmpty,
|
document.title,
|
||||||
leading: AspectRatio(
|
overflow: TextOverflow.ellipsis,
|
||||||
aspectRatio: _a4AspectRatio,
|
maxLines: document.tags.isEmpty ? 2 : 1,
|
||||||
child: GestureDetector(
|
|
||||||
child: DocumentPreview(
|
|
||||||
id: document.id,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
alignment: Alignment.topCenter,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:paperless_api/paperless_api.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:paperless_mobile/generated/l10n.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class TextQueryFormField extends StatelessWidget {
|
class TextQueryFormField extends StatelessWidget {
|
||||||
final String name;
|
final String name;
|
||||||
@@ -21,57 +24,69 @@ class TextQueryFormField extends StatelessWidget {
|
|||||||
name: name,
|
name: name,
|
||||||
initialValue: initialValue,
|
initialValue: initialValue,
|
||||||
builder: (field) {
|
builder: (field) {
|
||||||
return TextFormField(
|
return Autocomplete(
|
||||||
initialValue: initialValue?.queryText,
|
optionsBuilder: (value) =>
|
||||||
textInputAction: TextInputAction.done,
|
context.read<DocumentsCubit>().autocomplete(value.text),
|
||||||
decoration: InputDecoration(
|
initialValue: initialValue?.queryText != null
|
||||||
prefixIcon: const Icon(Icons.search_outlined),
|
? TextEditingValue(text: initialValue!.queryText!)
|
||||||
labelText: _buildLabelText(context, field.value!.queryType),
|
: null,
|
||||||
suffixIcon: PopupMenuButton<QueryType>(
|
fieldViewBuilder:
|
||||||
icon: onlyExtendedQueryAllowed
|
(context, textEditingController, focusNode, onFieldSubmitted) {
|
||||||
? Icon(
|
return TextFormField(
|
||||||
Icons.more_vert,
|
controller: textEditingController,
|
||||||
color: Theme.of(context).disabledColor,
|
focusNode: focusNode,
|
||||||
)
|
decoration: InputDecoration(
|
||||||
: null,
|
prefixIcon: const Icon(Icons.search_outlined),
|
||||||
enabled: !onlyExtendedQueryAllowed,
|
labelText: _buildLabelText(context, field.value!.queryType),
|
||||||
itemBuilder: (context) => [
|
suffixIcon: _buildQueryTypeMenu(context, field),
|
||||||
PopupMenuItem(
|
),
|
||||||
child: ListTile(
|
onChanged: (value) {
|
||||||
title: Text(S
|
field.didChange(field.value?.copyWith(queryText: value));
|
||||||
.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));
|
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
),
|
|
||||||
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) {
|
String _buildLabelText(BuildContext context, QueryType queryType) {
|
||||||
switch (queryType) {
|
switch (queryType) {
|
||||||
case QueryType.title:
|
case QueryType.title:
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:paperless_api/paperless_api.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/features/documents/bloc/documents_state.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n.dart';
|
import 'package:paperless_mobile/generated/l10n.dart';
|
||||||
|
|
||||||
class BulkDeleteConfirmationDialog extends StatelessWidget {
|
class BulkDeleteConfirmationDialog extends StatelessWidget {
|
||||||
static const _bulletPoint = "\u2022";
|
|
||||||
final DocumentsState state;
|
final DocumentsState state;
|
||||||
const BulkDeleteConfirmationDialog({Key? key, required this.state})
|
const BulkDeleteConfirmationDialog({
|
||||||
: super(key: key);
|
Key? key,
|
||||||
|
required this.state,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -29,13 +29,7 @@ class BulkDeleteConfirmationDialog extends StatelessWidget {
|
|||||||
.documentsPageSelectionBulkDeleteDialogWarningTextMany,
|
.documentsPageSelectionBulkDeleteDialogWarningTextMany,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ConstrainedBox(
|
...state.selection.map(_buildBulletPoint).toList(),
|
||||||
constraints: const BoxConstraints(maxHeight: 150),
|
|
||||||
child: ListView(
|
|
||||||
shrinkWrap: true,
|
|
||||||
children: state.selection.map(_buildBulletPoint).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
S.of(context).documentsPageSelectionBulkDeleteDialogContinueText),
|
S.of(context).documentsPageSelectionBulkDeleteDialogContinueText),
|
||||||
@@ -61,12 +55,15 @@ class BulkDeleteConfirmationDialog extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBulletPoint(DocumentModel doc) {
|
Widget _buildBulletPoint(DocumentModel doc) {
|
||||||
return Text(
|
return ListTile(
|
||||||
"\t$_bulletPoint ${doc.title}",
|
dense: true,
|
||||||
maxLines: 1,
|
title: Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
doc.title,
|
||||||
style: const TextStyle(
|
maxLines: 1,
|
||||||
fontWeight: FontWeight.w700,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import 'package:paperless_mobile/generated/l10n.dart';
|
|||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
const HomePage({Key? key}) : super(key: key);
|
const HomePage({Key? key}) : super(key: key);
|
||||||
@@ -165,44 +166,114 @@ class _HomePageState extends State<HomePage> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: Scaffold(
|
child: ResponsiveBuilder(
|
||||||
key: rootScaffoldKey,
|
builder: (context, sizingInformation) {
|
||||||
bottomNavigationBar: BottomNavBar(
|
if (!sizingInformation.isMobile) {
|
||||||
selectedIndex: _currentIndex,
|
return Scaffold(
|
||||||
onNavigationChanged: (index) {
|
key: rootScaffoldKey,
|
||||||
if (_currentIndex != index) {
|
drawer: const InfoDrawer(),
|
||||||
setState(() => _currentIndex = index);
|
body: Row(children: [
|
||||||
}
|
NavigationRail(
|
||||||
},
|
labelType: NavigationRailLabelType.all,
|
||||||
),
|
destinations: [
|
||||||
drawer: const InfoDrawer(),
|
NavigationRailDestination(
|
||||||
body: [
|
icon: const Icon(Icons.description_outlined),
|
||||||
MultiBlocProvider(
|
selectedIcon: Icon(
|
||||||
providers: [
|
Icons.description,
|
||||||
BlocProvider(
|
color: Theme.of(context).colorScheme.primary,
|
||||||
create: (context) => DocumentsCubit(
|
),
|
||||||
context.read<PaperlessDocumentsApi>(),
|
label: Text(S.of(context).bottomNavDocumentsPageLabel),
|
||||||
context.read<SavedViewRepository>(),
|
),
|
||||||
|
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(
|
BlocProvider.value(
|
||||||
create: (context) => SavedViewCubit(
|
value: _scannerCubit,
|
||||||
context.read<SavedViewRepository>(),
|
child: const ScannerPage(),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
const LabelsPage(),
|
||||||
child: const DocumentsPage(),
|
][_currentIndex],
|
||||||
),
|
);
|
||||||
BlocProvider.value(
|
},
|
||||||
value: _scannerCubit,
|
|
||||||
child: const ScannerPage(),
|
|
||||||
),
|
|
||||||
const LabelsPage(),
|
|
||||||
][_currentIndex],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onNavigationChanged(index) {
|
||||||
|
if (_currentIndex != index) {
|
||||||
|
setState(() => _currentIndex = index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _initializeData(BuildContext context) {
|
void _initializeData(BuildContext context) {
|
||||||
try {
|
try {
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>().findAll();
|
context.read<LabelRepository<Tag, TagRepositoryState>>().findAll();
|
||||||
|
|||||||
@@ -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() {
|
void acknowledgeHint() {
|
||||||
emit(state.copyWith(isHintAcknowledged: true));
|
emit(state.copyWith(isHintAcknowledged: true));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,20 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:paperless_api/paperless_api.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/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/bloc/document_details_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.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/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.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/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 {
|
class InboxItem extends StatelessWidget {
|
||||||
static const _a4AspectRatio = 1 / 1.4142;
|
static const _a4AspectRatio = 1 / 1.4142;
|
||||||
@@ -23,7 +32,31 @@ class InboxItem extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
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,
|
isThreeLine: true,
|
||||||
leading: AspectRatio(
|
leading: AspectRatio(
|
||||||
aspectRatio: _a4AspectRatio,
|
aspectRatio: _a4AspectRatio,
|
||||||
@@ -37,16 +70,54 @@ class InboxItem extends StatelessWidget {
|
|||||||
subtitle: Column(
|
subtitle: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
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(
|
TagsWidget(
|
||||||
tagIds: document.tags,
|
tagIds: document.tags,
|
||||||
isMultiLine: false,
|
isMultiLine: false,
|
||||||
isClickable: false,
|
isClickable: false,
|
||||||
isSelectedPredicate: (_) => 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 {
|
onTap: () async {
|
||||||
final returnedDocument = await Navigator.push<DocumentModel?>(
|
final returnedDocument = await Navigator.push<DocumentModel?>(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ class TagWidget extends StatelessWidget {
|
|||||||
final VoidCallback onSelected;
|
final VoidCallback onSelected;
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final bool isClickable;
|
final bool isClickable;
|
||||||
|
final bool showShortName;
|
||||||
|
final bool dense;
|
||||||
|
|
||||||
const TagWidget({
|
const TagWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -15,6 +17,8 @@ class TagWidget extends StatelessWidget {
|
|||||||
this.isClickable = true,
|
this.isClickable = true,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
|
this.showShortName = false,
|
||||||
|
this.dense = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -24,13 +28,18 @@ class TagWidget extends StatelessWidget {
|
|||||||
child: AbsorbPointer(
|
child: AbsorbPointer(
|
||||||
absorbing: !isClickable,
|
absorbing: !isClickable,
|
||||||
child: FilterChip(
|
child: FilterChip(
|
||||||
|
labelPadding:
|
||||||
|
dense ? const EdgeInsets.symmetric(horizontal: 2) : null,
|
||||||
|
padding: dense ? const EdgeInsets.all(4) : null,
|
||||||
selected: isSelected,
|
selected: isSelected,
|
||||||
selectedColor: tag.color,
|
selectedColor: tag.color,
|
||||||
onSelected: (_) => onSelected(),
|
onSelected: (_) => onSelected(),
|
||||||
visualDensity: const VisualDensity(vertical: -2),
|
visualDensity: const VisualDensity(vertical: -2),
|
||||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||||
label: Text(
|
label: Text(
|
||||||
tag.name,
|
showShortName && tag.name.length > 6
|
||||||
|
? '${tag.name.substring(0, 6)}...'
|
||||||
|
: tag.name,
|
||||||
style: TextStyle(color: tag.textColor),
|
style: TextStyle(color: tag.textColor),
|
||||||
),
|
),
|
||||||
checkmarkColor: tag.textColor,
|
checkmarkColor: tag.textColor,
|
||||||
|
|||||||
@@ -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/bloc/providers/tag_bloc_provider.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.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 Iterable<int> tagIds;
|
||||||
final bool isMultiLine;
|
final bool isMultiLine;
|
||||||
final VoidCallback? afterTagTapped;
|
final VoidCallback? afterTagTapped;
|
||||||
final void Function(int tagId)? onTagSelected;
|
final void Function(int tagId)? onTagSelected;
|
||||||
final bool isClickable;
|
final bool isClickable;
|
||||||
final bool Function(int id) isSelectedPredicate;
|
final bool Function(int id) isSelectedPredicate;
|
||||||
|
final bool showShortNames;
|
||||||
|
final bool dense;
|
||||||
|
|
||||||
const TagsWidget({
|
const TagsWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -21,32 +23,31 @@ class TagsWidget extends StatefulWidget {
|
|||||||
this.isMultiLine = true,
|
this.isMultiLine = true,
|
||||||
this.isClickable = true,
|
this.isClickable = true,
|
||||||
required this.isSelectedPredicate,
|
required this.isSelectedPredicate,
|
||||||
required this.onTagSelected,
|
this.onTagSelected,
|
||||||
|
this.showShortNames = false,
|
||||||
|
this.dense = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
|
||||||
State<TagsWidget> createState() => _TagsWidgetState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TagsWidgetState extends State<TagsWidget> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TagBlocProvider(
|
return TagBlocProvider(
|
||||||
child: BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
|
child: BlocBuilder<LabelCubit<Tag>, LabelState<Tag>>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final children = widget.tagIds
|
final children = tagIds
|
||||||
.where((id) => state.labels.containsKey(id))
|
.where((id) => state.labels.containsKey(id))
|
||||||
.map(
|
.map(
|
||||||
(id) => TagWidget(
|
(id) => TagWidget(
|
||||||
tag: state.getLabel(id)!,
|
tag: state.getLabel(id)!,
|
||||||
afterTagTapped: widget.afterTagTapped,
|
afterTagTapped: afterTagTapped,
|
||||||
isClickable: widget.isClickable,
|
isClickable: isClickable,
|
||||||
isSelected: widget.isSelectedPredicate(id),
|
isSelected: isSelectedPredicate(id),
|
||||||
onSelected: () => widget.onTagSelected?.call(id),
|
onSelected: () => onTagSelected?.call(id),
|
||||||
|
showShortName: showShortNames,
|
||||||
|
dense: dense,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
if (widget.isMultiLine) {
|
if (isMultiLine) {
|
||||||
return Wrap(
|
return Wrap(
|
||||||
runAlignment: WrapAlignment.start,
|
runAlignment: WrapAlignment.start,
|
||||||
children: children,
|
children: children,
|
||||||
|
|||||||
40
lib/features/labels/view/widgets/label_text.dart
Normal file
40
lib/features/labels/view/widgets/label_text.dart
Normal 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,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:form_builder_validators/form_builder_validators.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 {
|
class ServerAddressFormField extends StatefulWidget {
|
||||||
static const String fkServerAddress = "serverAddress";
|
static const String fkServerAddress = "serverAddress";
|
||||||
|
|
||||||
final void Function(String address) onDone;
|
final void Function(String? address) onDone;
|
||||||
const ServerAddressFormField({
|
const ServerAddressFormField({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onDone,
|
required this.onDone,
|
||||||
@@ -31,7 +33,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final TextEditingController _textEditingController = TextEditingController();
|
final _textEditingController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -56,7 +58,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
|||||||
labelText: S.of(context).loginPageServerUrlFieldLabel,
|
labelText: S.of(context).loginPageServerUrlFieldLabel,
|
||||||
suffixIcon: _canClear
|
suffixIcon: _canClear
|
||||||
? IconButton(
|
? IconButton(
|
||||||
icon: Icon(Icons.clear),
|
icon: const Icon(Icons.clear),
|
||||||
color: Theme.of(context).iconTheme.color,
|
color: Theme.of(context).iconTheme.color,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_textEditingController.clear();
|
_textEditingController.clear();
|
||||||
@@ -64,18 +66,14 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
|||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
onSubmitted: (value) {
|
onSubmitted: (_) => _formatInput(),
|
||||||
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);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _replaceTrailingSlashes(String src) {
|
void _formatInput() {
|
||||||
return src.replaceAll(RegExp(r'^\/+|\/+$'), '');
|
String address = _textEditingController.text.trim();
|
||||||
|
address = address.replaceAll(RegExp(r'^\/+|\/+$'), '');
|
||||||
|
_textEditingController.text = address;
|
||||||
|
widget.onDone(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,8 +58,12 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
|
|||||||
),
|
),
|
||||||
bottomNavigationBar: BottomAppBar(
|
bottomNavigationBar: BottomAppBar(
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
TextButton(
|
||||||
|
child: Text("Test connection"),
|
||||||
|
onPressed: _updateReachability,
|
||||||
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
child: Text(S.of(context).loginPageContinueLabel),
|
child: Text(S.of(context).loginPageContinueLabel),
|
||||||
onPressed: _reachabilityStatus == ReachabilityStatus.reachable
|
onPressed: _reachabilityStatus == ReachabilityStatus.reachable
|
||||||
@@ -76,6 +80,7 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_isCheckingConnection = true;
|
_isCheckingConnection = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
final status = await context
|
final status = await context
|
||||||
.read<ConnectivityStatusService>()
|
.read<ConnectivityStatusService>()
|
||||||
.isPaperlessServerReachable(
|
.isPaperlessServerReachable(
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ class LocalNotificationService {
|
|||||||
initializationSettings,
|
initializationSettings,
|
||||||
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
|
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
|
||||||
);
|
);
|
||||||
|
await _plugin
|
||||||
|
.resolvePlatformSpecificImplementation<
|
||||||
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
|
?.requestPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: INTL
|
//TODO: INTL
|
||||||
|
|||||||
@@ -142,31 +142,29 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
final file = await _assembleFileBytes(
|
final file = await _assembleFileBytes(
|
||||||
context.read<DocumentScannerCubit>().state,
|
context.read<DocumentScannerCubit>().state,
|
||||||
);
|
);
|
||||||
final taskId = await Navigator.of(context).push(
|
final taskId = await Navigator.of(context).push<String?>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => LabelRepositoriesProvider(
|
builder: (_) => LabelRepositoriesProvider(
|
||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) => DocumentUploadCubit(
|
create: (context) => DocumentUploadCubit(
|
||||||
localVault: context.read<LocalVault>(),
|
localVault: context.read<LocalVault>(),
|
||||||
documentApi: context.read<PaperlessDocumentsApi>(),
|
documentApi: context.read<PaperlessDocumentsApi>(),
|
||||||
correspondentRepository: context.read<
|
correspondentRepository: context.read<
|
||||||
LabelRepository<Correspondent,
|
LabelRepository<Correspondent,
|
||||||
CorrespondentRepositoryState>>(),
|
CorrespondentRepositoryState>>(),
|
||||||
documentTypeRepository: context.read<
|
documentTypeRepository: context.read<
|
||||||
LabelRepository<DocumentType,
|
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
|
||||||
DocumentTypeRepositoryState>>(),
|
tagRepository:
|
||||||
tagRepository:
|
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
||||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
),
|
||||||
),
|
child: DocumentUploadPreparationPage(
|
||||||
child: DocumentUploadPreparationPage(
|
fileBytes: file.bytes,
|
||||||
fileBytes: file.bytes,
|
fileExtension: file.extension,
|
||||||
fileExtension: file.extension,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
) ??
|
),
|
||||||
false;
|
),
|
||||||
|
);
|
||||||
if (taskId != null) {
|
if (taskId != null) {
|
||||||
// For paperless version older than 1.11.3, task id will always be null!
|
// For paperless version older than 1.11.3, task id will always be null!
|
||||||
context.read<DocumentScannerCubit>().reset();
|
context.read<DocumentScannerCubit>().reset();
|
||||||
|
|||||||
@@ -563,5 +563,6 @@
|
|||||||
"verifyIdentityPageTitle": "",
|
"verifyIdentityPageTitle": "",
|
||||||
"@verifyIdentityPageTitle": {},
|
"@verifyIdentityPageTitle": {},
|
||||||
"verifyIdentityPageVerifyIdentityButtonLabel": "",
|
"verifyIdentityPageVerifyIdentityButtonLabel": "",
|
||||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||||
|
"inboxPageAssignAsnLabel": "Assign ASN"
|
||||||
}
|
}
|
||||||
@@ -563,5 +563,6 @@
|
|||||||
"verifyIdentityPageTitle": "Verifiziere deine Identität",
|
"verifyIdentityPageTitle": "Verifiziere deine Identität",
|
||||||
"@verifyIdentityPageTitle": {},
|
"@verifyIdentityPageTitle": {},
|
||||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Identität verifizieren",
|
"verifyIdentityPageVerifyIdentityButtonLabel": "Identität verifizieren",
|
||||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||||
|
"inboxPageAssignAsnLabel": "Assign ASN"
|
||||||
}
|
}
|
||||||
@@ -563,5 +563,6 @@
|
|||||||
"verifyIdentityPageTitle": "Verify your identity",
|
"verifyIdentityPageTitle": "Verify your identity",
|
||||||
"@verifyIdentityPageTitle": {},
|
"@verifyIdentityPageTitle": {},
|
||||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Verify Identity",
|
"verifyIdentityPageVerifyIdentityButtonLabel": "Verify Identity",
|
||||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||||
|
"inboxPageAssignAsnLabel": "Assign ASN"
|
||||||
}
|
}
|
||||||
@@ -113,8 +113,9 @@ void main() async {
|
|||||||
authApi,
|
authApi,
|
||||||
sessionManager,
|
sessionManager,
|
||||||
);
|
);
|
||||||
await authCubit
|
await authCubit.restoreSessionState(
|
||||||
.restoreSessionState(appSettingsCubit.state.isLocalAuthenticationEnabled);
|
appSettingsCubit.state.isLocalAuthenticationEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
if (authCubit.state.isAuthenticated) {
|
if (authCubit.state.isAuthenticated) {
|
||||||
final auth = authCubit.state.authentication!;
|
final auth = authCubit.state.authentication!;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
import 'query_type.dart';
|
import 'query_type.dart';
|
||||||
@@ -5,7 +6,7 @@ import 'query_type.dart';
|
|||||||
part 'text_query.g.dart';
|
part 'text_query.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class TextQuery {
|
class TextQuery extends Equatable {
|
||||||
final QueryType queryType;
|
final QueryType queryType;
|
||||||
final String? queryText;
|
final String? queryText;
|
||||||
|
|
||||||
@@ -61,4 +62,7 @@ class TextQuery {
|
|||||||
|
|
||||||
factory TextQuery.fromJson(Map<String, dynamic> json) =>
|
factory TextQuery.fromJson(Map<String, dynamic> json) =>
|
||||||
_$TextQueryFromJson(json);
|
_$TextQueryFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [queryType, queryText];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,12 +219,12 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
'/api/search/autocomplete/',
|
'/api/search/autocomplete/',
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'query': query,
|
'term': query,
|
||||||
'limit': limit,
|
'limit': limit,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
return response.data as List<String>;
|
return (response.data as List).cast<String>();
|
||||||
}
|
}
|
||||||
throw const PaperlessServerException(ErrorCode.autocompleteQueryError);
|
throw const PaperlessServerException(ErrorCode.autocompleteQueryError);
|
||||||
} on DioError catch (err) {
|
} on DioError catch (err) {
|
||||||
|
|||||||
@@ -1292,6 +1292,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.5"
|
version: "1.4.5"
|
||||||
|
responsive_builder:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: responsive_builder
|
||||||
|
sha256: f01bc341c73b6db7bd6319e22d2c160f28f924399ae46e6699ecc8160ba2765c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.3"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ dependencies:
|
|||||||
device_info_plus: ^4.1.3
|
device_info_plus: ^4.1.3
|
||||||
flutter_local_notifications: ^13.0.0
|
flutter_local_notifications: ^13.0.0
|
||||||
flutter_staggered_grid_view: ^0.6.2
|
flutter_staggered_grid_view: ^0.6.2
|
||||||
|
responsive_builder: ^0.4.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
integration_test:
|
integration_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user