mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-08 02:07:57 -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
|
||||
**/dot_images/*
|
||||
|
||||
docker/
|
||||
@@ -82,10 +82,13 @@ flutter {
|
||||
|
||||
dependencies {
|
||||
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'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
// Required for flutter_local_notifications
|
||||
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"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
@@ -11,10 +12,14 @@
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while 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" />
|
||||
the
|
||||
Android process has started. This theme is visible to the user
|
||||
while
|
||||
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>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
@@ -34,7 +39,8 @@
|
||||
|
||||
</activity>
|
||||
<!-- 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" />
|
||||
</application>
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
@@ -44,4 +50,5 @@
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
</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,
|
||||
_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)),
|
||||
));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -36,8 +36,7 @@ class DocumentListItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
child: ListTile(
|
||||
return ListTile(
|
||||
dense: true,
|
||||
selected: isSelected,
|
||||
onTap: () => _onTap(),
|
||||
@@ -91,7 +90,6 @@ class DocumentListItem extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
contentPadding: const EdgeInsets.all(8.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,13 +24,35 @@ class TextQueryFormField extends StatelessWidget {
|
||||
name: name,
|
||||
initialValue: initialValue,
|
||||
builder: (field) {
|
||||
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(
|
||||
initialValue: initialValue?.queryText,
|
||||
textInputAction: TextInputAction.done,
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: const Icon(Icons.search_outlined),
|
||||
labelText: _buildLabelText(context, field.value!.queryType),
|
||||
suffixIcon: PopupMenuButton<QueryType>(
|
||||
suffixIcon: _buildQueryTypeMenu(context, field),
|
||||
),
|
||||
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,
|
||||
@@ -38,23 +63,20 @@ class TextQueryFormField extends StatelessWidget {
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text(S
|
||||
.of(context)
|
||||
.documentFilterQueryOptionsTitleAndContentLabel),
|
||||
title: Text(
|
||||
S.of(context).documentFilterQueryOptionsTitleAndContentLabel),
|
||||
),
|
||||
value: QueryType.titleAndContent,
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
S.of(context).documentFilterQueryOptionsTitleLabel),
|
||||
title: Text(S.of(context).documentFilterQueryOptionsTitleLabel),
|
||||
),
|
||||
value: QueryType.title,
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
S.of(context).documentFilterQueryOptionsExtendedLabel),
|
||||
title: Text(S.of(context).documentFilterQueryOptionsExtendedLabel),
|
||||
),
|
||||
value: QueryType.extended,
|
||||
),
|
||||
@@ -62,13 +84,6 @@ class TextQueryFormField extends StatelessWidget {
|
||||
onSelected: (selection) {
|
||||
field.didChange(field.value?.copyWith(queryType: selection));
|
||||
},
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
field.didChange(field.value?.copyWith(queryText: value));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,13 +55,16 @@ class BulkDeleteConfirmationDialog extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildBulletPoint(DocumentModel doc) {
|
||||
return Text(
|
||||
"\t$_bulletPoint ${doc.title}",
|
||||
return ListTile(
|
||||
dense: true,
|
||||
title: Text(
|
||||
doc.title,
|
||||
maxLines: 1,
|
||||
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: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,15 +166,77 @@ class _HomePageState extends State<HomePage> {
|
||||
},
|
||||
),
|
||||
],
|
||||
child: Scaffold(
|
||||
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: (index) {
|
||||
if (_currentIndex != index) {
|
||||
setState(() => _currentIndex = index);
|
||||
}
|
||||
},
|
||||
onNavigationChanged: _onNavigationChanged,
|
||||
),
|
||||
drawer: const InfoDrawer(),
|
||||
body: [
|
||||
@@ -199,10 +262,18 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
const LabelsPage(),
|
||||
][_currentIndex],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onNavigationChanged(index) {
|
||||
if (_currentIndex != index) {
|
||||
setState(() => _currentIndex = index);
|
||||
}
|
||||
}
|
||||
|
||||
void _initializeData(BuildContext context) {
|
||||
try {
|
||||
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() {
|
||||
emit(state.copyWith(isHintAcknowledged: true));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
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_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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -32,6 +32,10 @@ class LocalNotificationService {
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
|
||||
);
|
||||
await _plugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermission();
|
||||
}
|
||||
|
||||
//TODO: INTL
|
||||
|
||||
@@ -142,7 +142,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
final file = await _assembleFileBytes(
|
||||
context.read<DocumentScannerCubit>().state,
|
||||
);
|
||||
final taskId = await Navigator.of(context).push(
|
||||
final taskId = await Navigator.of(context).push<String?>(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => LabelRepositoriesProvider(
|
||||
child: BlocProvider(
|
||||
@@ -153,8 +153,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
LabelRepository<Correspondent,
|
||||
CorrespondentRepositoryState>>(),
|
||||
documentTypeRepository: context.read<
|
||||
LabelRepository<DocumentType,
|
||||
DocumentTypeRepositoryState>>(),
|
||||
LabelRepository<DocumentType, DocumentTypeRepositoryState>>(),
|
||||
tagRepository:
|
||||
context.read<LabelRepository<Tag, TagRepositoryState>>(),
|
||||
),
|
||||
@@ -165,8 +164,7 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
),
|
||||
),
|
||||
),
|
||||
) ??
|
||||
false;
|
||||
);
|
||||
if (taskId != null) {
|
||||
// For paperless version older than 1.11.3, task id will always be null!
|
||||
context.read<DocumentScannerCubit>().reset();
|
||||
|
||||
@@ -563,5 +563,6 @@
|
||||
"verifyIdentityPageTitle": "",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||
"inboxPageAssignAsnLabel": "Assign ASN"
|
||||
}
|
||||
@@ -563,5 +563,6 @@
|
||||
"verifyIdentityPageTitle": "Verifiziere deine Identität",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Identität verifizieren",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||
"inboxPageAssignAsnLabel": "Assign ASN"
|
||||
}
|
||||
@@ -563,5 +563,6 @@
|
||||
"verifyIdentityPageTitle": "Verify your identity",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Verify Identity",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||
"inboxPageAssignAsnLabel": "Assign ASN"
|
||||
}
|
||||
@@ -113,8 +113,9 @@ void main() async {
|
||||
authApi,
|
||||
sessionManager,
|
||||
);
|
||||
await authCubit
|
||||
.restoreSessionState(appSettingsCubit.state.isLocalAuthenticationEnabled);
|
||||
await authCubit.restoreSessionState(
|
||||
appSettingsCubit.state.isLocalAuthenticationEnabled,
|
||||
);
|
||||
|
||||
if (authCubit.state.isAuthenticated) {
|
||||
final auth = authCubit.state.authentication!;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'query_type.dart';
|
||||
@@ -5,7 +6,7 @@ import 'query_type.dart';
|
||||
part 'text_query.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class TextQuery {
|
||||
class TextQuery extends Equatable {
|
||||
final QueryType queryType;
|
||||
final String? queryText;
|
||||
|
||||
@@ -61,4 +62,7 @@ class TextQuery {
|
||||
|
||||
factory TextQuery.fromJson(Map<String, dynamic> json) =>
|
||||
_$TextQueryFromJson(json);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [queryType, queryText];
|
||||
}
|
||||
|
||||
@@ -219,12 +219,12 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
||||
final response = await client.get(
|
||||
'/api/search/autocomplete/',
|
||||
queryParameters: {
|
||||
'query': query,
|
||||
'term': query,
|
||||
'limit': limit,
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
return response.data as List<String>;
|
||||
return (response.data as List).cast<String>();
|
||||
}
|
||||
throw const PaperlessServerException(ErrorCode.autocompleteQueryError);
|
||||
} on DioError catch (err) {
|
||||
|
||||
@@ -1292,6 +1292,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -85,6 +85,7 @@ dependencies:
|
||||
device_info_plus: ^4.1.3
|
||||
flutter_local_notifications: ^13.0.0
|
||||
flutter_staggered_grid_view: ^0.6.2
|
||||
responsive_builder: ^0.4.3
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
|
||||
Reference in New Issue
Block a user