mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-07 07:15:47 -06:00
feat: Update translations, add pdf view to document edit page
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
* Neu: App-Logs werden in Dateien geschrieben und können auch direkt in der App eingesehen werden
|
||||||
|
* Optimierung der Datums-Eingabe durch neues Eingabemaske
|
||||||
|
* Schneller Wechsel zwischen Dokumenten-PDF Ansicht und Bearbeitungsmaske
|
||||||
|
* Kleinere Visuelle Anpassungen und Bugfixes
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
* New: App-Logs are written to local files and can also be viewed in-app
|
||||||
|
* New and optimized date input fields
|
||||||
|
* Quickly switch between editing your document and a PDF-View
|
||||||
|
* Minor visual changes and bug fixes
|
||||||
@@ -38,6 +38,8 @@ class DioHttpErrorInterceptor extends Interceptor {
|
|||||||
const PaperlessApiException(ErrorCode.missingClientCertificate),
|
const PaperlessApiException(ErrorCode.missingClientCertificate),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
handler.reject(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,14 +27,15 @@ class DocumentChangedNotifier {
|
|||||||
Object subscriber, {
|
Object subscriber, {
|
||||||
DocumentChangedCallback? onUpdated,
|
DocumentChangedCallback? onUpdated,
|
||||||
DocumentChangedCallback? onDeleted,
|
DocumentChangedCallback? onDeleted,
|
||||||
|
Iterable<int>? ids,
|
||||||
}) {
|
}) {
|
||||||
_subscribers.putIfAbsent(
|
_subscribers.putIfAbsent(
|
||||||
subscriber,
|
subscriber,
|
||||||
() => [
|
() => [
|
||||||
_updated.listen((value) {
|
_updated.where((doc) => ids?.contains(doc.id) ?? true).listen((value) {
|
||||||
onUpdated?.call(value);
|
onUpdated?.call(value);
|
||||||
}),
|
}),
|
||||||
_deleted.listen((value) {
|
_deleted.where((doc) => ids?.contains(doc.id) ?? true).listen((value) {
|
||||||
onDeleted?.call(value);
|
onDeleted?.call(value);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class FormBuilderLocalizedDatePicker extends StatefulWidget {
|
|||||||
final DateTime? initialValue;
|
final DateTime? initialValue;
|
||||||
final DateTime firstDate;
|
final DateTime firstDate;
|
||||||
final DateTime lastDate;
|
final DateTime lastDate;
|
||||||
|
final FocusNode? focusNode;
|
||||||
|
|
||||||
/// If set to true, the field will not throw any validation errors when empty.
|
/// If set to true, the field will not throw any validation errors when empty.
|
||||||
final bool allowUnset;
|
final bool allowUnset;
|
||||||
@@ -67,6 +68,7 @@ class FormBuilderLocalizedDatePicker extends StatefulWidget {
|
|||||||
required this.labelText,
|
required this.labelText,
|
||||||
this.prefixIcon,
|
this.prefixIcon,
|
||||||
this.allowUnset = false,
|
this.allowUnset = false,
|
||||||
|
this.focusNode,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -100,8 +102,11 @@ class _FormBuilderLocalizedDatePickerState
|
|||||||
final initialText = widget.initialValue != null
|
final initialText = widget.initialValue != null
|
||||||
? DateFormat(formatString).format(widget.initialValue!)
|
? DateFormat(formatString).format(widget.initialValue!)
|
||||||
: null;
|
: null;
|
||||||
|
final defaultFocusNode = FocusNode(debugLabel: formatString);
|
||||||
|
final focusNode =
|
||||||
|
i == 0 ? (widget.focusNode ?? defaultFocusNode) : defaultFocusNode;
|
||||||
final controls = _NeighbourAwareDateInputSegmentControls(
|
final controls = _NeighbourAwareDateInputSegmentControls(
|
||||||
node: FocusNode(debugLabel: formatString),
|
node: focusNode,
|
||||||
controller: TextEditingController(text: initialText),
|
controller: TextEditingController(text: initialText),
|
||||||
format: formatString,
|
format: formatString,
|
||||||
position: i,
|
position: i,
|
||||||
|
|||||||
@@ -106,14 +106,6 @@ class AppDrawer extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
dense: true,
|
|
||||||
leading: const Icon(Icons.history),
|
|
||||||
title: Text(S.of(context)!.changelog),
|
|
||||||
onTap: () {
|
|
||||||
ChangelogRoute().push(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
leading: const Icon(Icons.bug_report_outlined),
|
leading: const Icon(Icons.bug_report_outlined),
|
||||||
@@ -179,14 +171,6 @@ class AppDrawer extends StatelessWidget {
|
|||||||
.fade(duration: 1.seconds, begin: 1, end: 0.3);
|
.fade(duration: 1.seconds, begin: 1, end: 0.3);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
dense: true,
|
|
||||||
leading: const Icon(Icons.subject),
|
|
||||||
title: Text(S.of(context)!.appLogs('')),
|
|
||||||
onTap: () {
|
|
||||||
AppLogsRoute().push(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
leading: const Icon(Icons.settings_outlined),
|
leading: const Icon(Icons.settings_outlined),
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class ChangelogDialog extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const _versionNumbers = {
|
const _versionNumbers = {
|
||||||
|
"54": "3.0.7",
|
||||||
"53": "3.0.6",
|
"53": "3.0.6",
|
||||||
"52": "3.0.5",
|
"52": "3.0.5",
|
||||||
"51": "3.0.4",
|
"51": "3.0.4",
|
||||||
|
|||||||
@@ -31,14 +31,13 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
this._notificationService, {
|
this._notificationService, {
|
||||||
required this.id,
|
required this.id,
|
||||||
}) : super(const DocumentDetailsInitial()) {
|
}) : super(const DocumentDetailsInitial()) {
|
||||||
_notifier.addListener(this, onUpdated: (document) {
|
_notifier.addListener(
|
||||||
if (state is DocumentDetailsLoaded) {
|
this,
|
||||||
final currentState = state as DocumentDetailsLoaded;
|
onUpdated: (document) {
|
||||||
if (document.id == currentState.document.id) {
|
replace(document);
|
||||||
replace(document);
|
},
|
||||||
}
|
ids: [id],
|
||||||
}
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ class DocumentEditCubit extends Cubit<DocumentEditState> {
|
|||||||
required DocumentModel document,
|
required DocumentModel document,
|
||||||
}) : _initialDocument = document,
|
}) : _initialDocument = document,
|
||||||
super(DocumentEditState(document: document)) {
|
super(DocumentEditState(document: document)) {
|
||||||
_notifier.addListener(this, onUpdated: (doc) {
|
_notifier.addListener(
|
||||||
if (doc.id == document.id) {
|
this,
|
||||||
|
onUpdated: (doc) {
|
||||||
emit(state.copyWith(document: doc));
|
emit(state.copyWith(document: doc));
|
||||||
}
|
},
|
||||||
});
|
ids: [document.id],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateDocument(DocumentModel document) async {
|
Future<void> updateDocument(DocumentModel document) async {
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import 'package:go_router/go_router.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/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
|
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart';
|
|
||||||
import 'package:paperless_mobile/core/widgets/dialog_utils/pop_with_unsaved_changes.dart';
|
import 'package:paperless_mobile/core/widgets/dialog_utils/pop_with_unsaved_changes.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_localized_date_picker.dart';
|
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_localized_date_picker.dart';
|
||||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
||||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
|
||||||
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
|
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
@@ -32,7 +32,8 @@ class DocumentEditPage extends StatefulWidget {
|
|||||||
State<DocumentEditPage> createState() => _DocumentEditPageState();
|
State<DocumentEditPage> createState() => _DocumentEditPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DocumentEditPageState extends State<DocumentEditPage> {
|
class _DocumentEditPageState extends State<DocumentEditPage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
static const fkTitle = "title";
|
static const fkTitle = "title";
|
||||||
static const fkCorrespondent = "correspondent";
|
static const fkCorrespondent = "correspondent";
|
||||||
static const fkTags = "tags";
|
static const fkTags = "tags";
|
||||||
@@ -43,6 +44,23 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
|
|
||||||
final _formKey = GlobalKey<FormBuilderState>();
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
|
|
||||||
|
bool _isShowingPdf = false;
|
||||||
|
|
||||||
|
late final AnimationController _animationController;
|
||||||
|
late final Animation<double> _animation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animationController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_animation =
|
||||||
|
CurvedAnimation(parent: _animationController, curve: Curves.easeInCubic)
|
||||||
|
.drive(Tween<double>(begin: 0, end: 1));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
final currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
||||||
@@ -75,197 +93,228 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
doc.created != createdAt ||
|
doc.created != createdAt ||
|
||||||
(doc.content != content && isContentTouched);
|
(doc.content != content && isContentTouched);
|
||||||
},
|
},
|
||||||
child: DefaultTabController(
|
child: FormBuilder(
|
||||||
length: 2,
|
key: _formKey,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
appBar: AppBar(
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
title: Text(S.of(context)!.editDocument),
|
||||||
heroTag: "fab_document_edit",
|
actions: [
|
||||||
onPressed: () => _onSubmit(state.document),
|
IconButton(
|
||||||
icon: const Icon(Icons.save),
|
tooltip: _isShowingPdf
|
||||||
label: Text(S.of(context)!.saveChanges),
|
? S.of(context)!.hidePdf
|
||||||
),
|
: S.of(context)!.showPdf,
|
||||||
appBar: AppBar(
|
padding: EdgeInsets.all(12),
|
||||||
title: Text(S.of(context)!.editDocument),
|
icon: AnimatedCrossFade(
|
||||||
bottom: TabBar(
|
duration: _animationController.duration!,
|
||||||
tabs: [
|
reverseDuration: _animationController.reverseDuration,
|
||||||
Tab(text: S.of(context)!.overview),
|
crossFadeState: _isShowingPdf
|
||||||
Tab(text: S.of(context)!.content)
|
? CrossFadeState.showFirst
|
||||||
],
|
: CrossFadeState.showSecond,
|
||||||
),
|
firstChild: Icon(Icons.visibility_off_outlined),
|
||||||
),
|
secondChild: Icon(Icons.visibility_outlined),
|
||||||
extendBody: true,
|
),
|
||||||
body: Padding(
|
onPressed: () {
|
||||||
padding: EdgeInsets.only(
|
if (_isShowingPdf) {
|
||||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
setState(() {
|
||||||
top: 8,
|
_isShowingPdf = false;
|
||||||
left: 8,
|
});
|
||||||
right: 8,
|
_animationController.reverse();
|
||||||
),
|
} else {
|
||||||
child: FormBuilder(
|
setState(() {
|
||||||
key: _formKey,
|
_isShowingPdf = true;
|
||||||
child: TabBarView(
|
});
|
||||||
children: [
|
_animationController.forward();
|
||||||
ListView(
|
}
|
||||||
children: [
|
},
|
||||||
_buildTitleFormField(state.document.title).padded(),
|
)
|
||||||
_buildCreatedAtFormField(
|
],
|
||||||
state.document.created,
|
),
|
||||||
filteredSuggestions,
|
body: Stack(
|
||||||
).padded(),
|
children: [
|
||||||
// Correspondent form field
|
DefaultTabController(
|
||||||
if (currentUser.canViewCorrespondents)
|
length: 2,
|
||||||
Column(
|
child: Scaffold(
|
||||||
children: [
|
resizeToAvoidBottomInset: true,
|
||||||
LabelFormField<Correspondent>(
|
floatingActionButton: !_isShowingPdf
|
||||||
showAnyAssignedOption: false,
|
? FloatingActionButton.extended(
|
||||||
showNotAssignedOption: false,
|
heroTag: "fab_document_edit",
|
||||||
onAddLabel: (currentInput) =>
|
onPressed: () => _onSubmit(state.document),
|
||||||
CreateLabelRoute(
|
icon: const Icon(Icons.save),
|
||||||
LabelType.correspondent,
|
label: Text(S.of(context)!.saveChanges),
|
||||||
name: currentInput,
|
)
|
||||||
).push<Correspondent>(context),
|
: null,
|
||||||
addLabelText:
|
appBar: TabBar(
|
||||||
S.of(context)!.addCorrespondent,
|
tabs: [
|
||||||
labelText: S.of(context)!.correspondent,
|
Tab(text: S.of(context)!.overview),
|
||||||
options: context
|
Tab(text: S.of(context)!.content),
|
||||||
.watch<LabelRepository>()
|
],
|
||||||
.state
|
),
|
||||||
.correspondents,
|
extendBody: true,
|
||||||
initialValue: state
|
body: _buildEditForm(
|
||||||
.document.correspondent !=
|
context,
|
||||||
null
|
state,
|
||||||
? SetIdQueryParameter(
|
filteredSuggestions,
|
||||||
id: state.document.correspondent!)
|
currentUser,
|
||||||
: const UnsetIdQueryParameter(),
|
),
|
||||||
name: fkCorrespondent,
|
|
||||||
prefixIcon:
|
|
||||||
const Icon(Icons.person_outlined),
|
|
||||||
allowSelectUnassigned: true,
|
|
||||||
canCreateNewLabel:
|
|
||||||
currentUser.canCreateCorrespondents,
|
|
||||||
suggestions:
|
|
||||||
filteredSuggestions?.correspondents ??
|
|
||||||
[],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padded(),
|
|
||||||
// DocumentType form field
|
|
||||||
if (currentUser.canViewDocumentTypes)
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
LabelFormField<DocumentType>(
|
|
||||||
showAnyAssignedOption: false,
|
|
||||||
showNotAssignedOption: false,
|
|
||||||
onAddLabel: (currentInput) =>
|
|
||||||
CreateLabelRoute(
|
|
||||||
LabelType.documentType,
|
|
||||||
name: currentInput,
|
|
||||||
).push<DocumentType>(context),
|
|
||||||
canCreateNewLabel:
|
|
||||||
currentUser.canCreateDocumentTypes,
|
|
||||||
addLabelText:
|
|
||||||
S.of(context)!.addDocumentType,
|
|
||||||
labelText: S.of(context)!.documentType,
|
|
||||||
initialValue: state.document.documentType !=
|
|
||||||
null
|
|
||||||
? SetIdQueryParameter(
|
|
||||||
id: state.document.documentType!)
|
|
||||||
: const UnsetIdQueryParameter(),
|
|
||||||
options: context
|
|
||||||
.watch<LabelRepository>()
|
|
||||||
.state
|
|
||||||
.documentTypes,
|
|
||||||
name: _DocumentEditPageState.fkDocumentType,
|
|
||||||
prefixIcon:
|
|
||||||
const Icon(Icons.description_outlined),
|
|
||||||
allowSelectUnassigned: true,
|
|
||||||
suggestions:
|
|
||||||
filteredSuggestions?.documentTypes ??
|
|
||||||
[],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padded(),
|
|
||||||
// StoragePath form field
|
|
||||||
if (currentUser.canViewStoragePaths)
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
LabelFormField<StoragePath>(
|
|
||||||
showAnyAssignedOption: false,
|
|
||||||
showNotAssignedOption: false,
|
|
||||||
onAddLabel: (currentInput) =>
|
|
||||||
CreateLabelRoute(
|
|
||||||
LabelType.storagePath,
|
|
||||||
name: currentInput,
|
|
||||||
).push<StoragePath>(context),
|
|
||||||
canCreateNewLabel:
|
|
||||||
currentUser.canCreateStoragePaths,
|
|
||||||
addLabelText: S.of(context)!.addStoragePath,
|
|
||||||
labelText: S.of(context)!.storagePath,
|
|
||||||
options: context
|
|
||||||
.watch<LabelRepository>()
|
|
||||||
.state
|
|
||||||
.storagePaths,
|
|
||||||
initialValue:
|
|
||||||
state.document.storagePath != null
|
|
||||||
? SetIdQueryParameter(
|
|
||||||
id: state.document.storagePath!)
|
|
||||||
: const UnsetIdQueryParameter(),
|
|
||||||
name: fkStoragePath,
|
|
||||||
prefixIcon:
|
|
||||||
const Icon(Icons.folder_outlined),
|
|
||||||
allowSelectUnassigned: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padded(),
|
|
||||||
// Tag form field
|
|
||||||
if (currentUser.canViewTags)
|
|
||||||
TagsFormField(
|
|
||||||
options:
|
|
||||||
context.watch<LabelRepository>().state.tags,
|
|
||||||
name: fkTags,
|
|
||||||
allowOnlySelection: true,
|
|
||||||
allowCreation: true,
|
|
||||||
allowExclude: false,
|
|
||||||
suggestions: filteredSuggestions?.tags ?? [],
|
|
||||||
initialValue: IdsTagsQuery(
|
|
||||||
include: state.document.tags.toList(),
|
|
||||||
),
|
|
||||||
).padded(),
|
|
||||||
if (filteredSuggestions?.tags
|
|
||||||
.toSet()
|
|
||||||
.difference(state.document.tags.toSet())
|
|
||||||
.isNotEmpty ??
|
|
||||||
false)
|
|
||||||
const SizedBox(height: 64),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
FormBuilderTextField(
|
|
||||||
name: fkContent,
|
|
||||||
maxLines: null,
|
|
||||||
keyboardType: TextInputType.multiline,
|
|
||||||
initialValue: state.document.content,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 84),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)),
|
AnimatedBuilder(
|
||||||
|
animation: _animation,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Transform.scale(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
scale: _animation.value,
|
||||||
|
child: DocumentView(
|
||||||
|
showAppBar: false,
|
||||||
|
showControls: false,
|
||||||
|
documentBytes: context
|
||||||
|
.read<PaperlessDocumentsApi>()
|
||||||
|
.downloadDocument(state.document.id),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Padding _buildEditForm(BuildContext context, DocumentEditState state,
|
||||||
|
FieldSuggestions? filteredSuggestions, UserModel currentUser) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: TabBarView(
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
children: [
|
||||||
|
ListView(
|
||||||
|
children: [
|
||||||
|
SizedBox(height: 16),
|
||||||
|
_buildTitleFormField(state.document.title).padded(),
|
||||||
|
_buildCreatedAtFormField(
|
||||||
|
state.document.created,
|
||||||
|
filteredSuggestions,
|
||||||
|
).padded(),
|
||||||
|
// Correspondent form field
|
||||||
|
if (currentUser.canViewCorrespondents)
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
LabelFormField<Correspondent>(
|
||||||
|
showAnyAssignedOption: false,
|
||||||
|
showNotAssignedOption: false,
|
||||||
|
onAddLabel: (currentInput) => CreateLabelRoute(
|
||||||
|
LabelType.correspondent,
|
||||||
|
name: currentInput,
|
||||||
|
).push<Correspondent>(context),
|
||||||
|
addLabelText: S.of(context)!.addCorrespondent,
|
||||||
|
labelText: S.of(context)!.correspondent,
|
||||||
|
options:
|
||||||
|
context.watch<LabelRepository>().state.correspondents,
|
||||||
|
initialValue: state.document.correspondent != null
|
||||||
|
? SetIdQueryParameter(
|
||||||
|
id: state.document.correspondent!)
|
||||||
|
: const UnsetIdQueryParameter(),
|
||||||
|
name: fkCorrespondent,
|
||||||
|
prefixIcon: const Icon(Icons.person_outlined),
|
||||||
|
allowSelectUnassigned: true,
|
||||||
|
canCreateNewLabel: currentUser.canCreateCorrespondents,
|
||||||
|
suggestions: filteredSuggestions?.correspondents ?? [],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padded(),
|
||||||
|
// DocumentType form field
|
||||||
|
if (currentUser.canViewDocumentTypes)
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
LabelFormField<DocumentType>(
|
||||||
|
showAnyAssignedOption: false,
|
||||||
|
showNotAssignedOption: false,
|
||||||
|
onAddLabel: (currentInput) => CreateLabelRoute(
|
||||||
|
LabelType.documentType,
|
||||||
|
name: currentInput,
|
||||||
|
).push<DocumentType>(context),
|
||||||
|
canCreateNewLabel: currentUser.canCreateDocumentTypes,
|
||||||
|
addLabelText: S.of(context)!.addDocumentType,
|
||||||
|
labelText: S.of(context)!.documentType,
|
||||||
|
initialValue: state.document.documentType != null
|
||||||
|
? SetIdQueryParameter(
|
||||||
|
id: state.document.documentType!)
|
||||||
|
: const UnsetIdQueryParameter(),
|
||||||
|
options:
|
||||||
|
context.watch<LabelRepository>().state.documentTypes,
|
||||||
|
name: _DocumentEditPageState.fkDocumentType,
|
||||||
|
prefixIcon: const Icon(Icons.description_outlined),
|
||||||
|
allowSelectUnassigned: true,
|
||||||
|
suggestions: filteredSuggestions?.documentTypes ?? [],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padded(),
|
||||||
|
// StoragePath form field
|
||||||
|
if (currentUser.canViewStoragePaths)
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
LabelFormField<StoragePath>(
|
||||||
|
showAnyAssignedOption: false,
|
||||||
|
showNotAssignedOption: false,
|
||||||
|
onAddLabel: (currentInput) => CreateLabelRoute(
|
||||||
|
LabelType.storagePath,
|
||||||
|
name: currentInput,
|
||||||
|
).push<StoragePath>(context),
|
||||||
|
canCreateNewLabel: currentUser.canCreateStoragePaths,
|
||||||
|
addLabelText: S.of(context)!.addStoragePath,
|
||||||
|
labelText: S.of(context)!.storagePath,
|
||||||
|
options:
|
||||||
|
context.watch<LabelRepository>().state.storagePaths,
|
||||||
|
initialValue: state.document.storagePath != null
|
||||||
|
? SetIdQueryParameter(id: state.document.storagePath!)
|
||||||
|
: const UnsetIdQueryParameter(),
|
||||||
|
name: fkStoragePath,
|
||||||
|
prefixIcon: const Icon(Icons.folder_outlined),
|
||||||
|
allowSelectUnassigned: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padded(),
|
||||||
|
// Tag form field
|
||||||
|
if (currentUser.canViewTags)
|
||||||
|
TagsFormField(
|
||||||
|
options: context.watch<LabelRepository>().state.tags,
|
||||||
|
name: fkTags,
|
||||||
|
allowOnlySelection: true,
|
||||||
|
allowCreation: true,
|
||||||
|
allowExclude: false,
|
||||||
|
suggestions: filteredSuggestions?.tags ?? [],
|
||||||
|
initialValue: IdsTagsQuery(
|
||||||
|
include: state.document.tags.toList(),
|
||||||
|
),
|
||||||
|
).padded(),
|
||||||
|
|
||||||
|
const SizedBox(height: 140),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
FormBuilderTextField(
|
||||||
|
name: fkContent,
|
||||||
|
maxLines: null,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
initialValue: state.document.content,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 84),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
String? title,
|
String? title,
|
||||||
int? correspondent,
|
int? correspondent,
|
||||||
|
|||||||
@@ -5,9 +5,13 @@ import 'package:flutter_pdfview/flutter_pdfview.dart';
|
|||||||
class DocumentView extends StatefulWidget {
|
class DocumentView extends StatefulWidget {
|
||||||
final Future<Uint8List> documentBytes;
|
final Future<Uint8List> documentBytes;
|
||||||
final String? title;
|
final String? title;
|
||||||
|
final bool showAppBar;
|
||||||
|
final bool showControls;
|
||||||
const DocumentView({
|
const DocumentView({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.documentBytes,
|
required this.documentBytes,
|
||||||
|
this.showAppBar = true,
|
||||||
|
this.showControls = true,
|
||||||
this.title,
|
this.title,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@@ -27,43 +31,47 @@ class _DocumentViewState extends State<DocumentView> {
|
|||||||
final canGoToNextPage = isInitialized && _currentPage! + 1 < _totalPages!;
|
final canGoToNextPage = isInitialized && _currentPage! + 1 < _totalPages!;
|
||||||
final canGoToPreviousPage = isInitialized && _currentPage! > 0;
|
final canGoToPreviousPage = isInitialized && _currentPage! > 0;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: widget.showAppBar
|
||||||
title: widget.title != null ? Text(widget.title!) : null,
|
? AppBar(
|
||||||
),
|
title: widget.title != null ? Text(widget.title!) : null,
|
||||||
bottomNavigationBar: BottomAppBar(
|
)
|
||||||
child: Row(
|
: null,
|
||||||
children: [
|
bottomNavigationBar: widget.showControls
|
||||||
Flexible(
|
? BottomAppBar(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
IconButton.filled(
|
Flexible(
|
||||||
onPressed: canGoToPreviousPage
|
child: Row(
|
||||||
? () {
|
children: [
|
||||||
_controller?.setPage(_currentPage! - 1);
|
IconButton.filled(
|
||||||
}
|
onPressed: canGoToPreviousPage
|
||||||
: null,
|
? () {
|
||||||
icon: const Icon(Icons.arrow_left),
|
_controller?.setPage(_currentPage! - 1);
|
||||||
),
|
}
|
||||||
const SizedBox(width: 16),
|
: null,
|
||||||
IconButton.filled(
|
icon: const Icon(Icons.arrow_left),
|
||||||
onPressed: canGoToNextPage
|
),
|
||||||
? () {
|
const SizedBox(width: 16),
|
||||||
_controller?.setPage(_currentPage! + 1);
|
IconButton.filled(
|
||||||
}
|
onPressed: canGoToNextPage
|
||||||
: null,
|
? () {
|
||||||
icon: const Icon(Icons.arrow_right),
|
_controller?.setPage(_currentPage! + 1);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
icon: const Icon(Icons.arrow_right),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
if (_currentPage != null && _totalPages != null)
|
||||||
|
Text(
|
||||||
|
"${_currentPage! + 1}/$_totalPages",
|
||||||
|
style: Theme.of(context).textTheme.labelLarge,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
if (_currentPage != null && _totalPages != null)
|
: null,
|
||||||
Text(
|
|
||||||
"${_currentPage! + 1}/$_totalPages",
|
|
||||||
style: Theme.of(context).textTheme.labelLarge,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: FutureBuilder(
|
body: FutureBuilder(
|
||||||
future: widget.documentBytes,
|
future: widget.documentBytes,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
@@ -93,12 +101,7 @@ class _DocumentViewState extends State<DocumentView> {
|
|||||||
onViewCreated: (controller) {
|
onViewCreated: (controller) {
|
||||||
_controller = controller;
|
_controller = controller;
|
||||||
},
|
},
|
||||||
onError: (error) {
|
|
||||||
print(error.toString());
|
|
||||||
},
|
|
||||||
onPageError: (page, error) {
|
|
||||||
print('$page: ${error.toString()}');
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _scrollExtentChangedListener() {
|
void _scrollExtentChangedListener() {
|
||||||
const threshold = 400;
|
const threshold = kToolbarHeight * 2;
|
||||||
final offset =
|
final offset =
|
||||||
_nestedScrollViewKey.currentState!.innerController.position.pixels;
|
_nestedScrollViewKey.currentState!.innerController.position.pixels;
|
||||||
if (offset < threshold && _showExtendedFab == false) {
|
if (offset < threshold && _showExtendedFab == false) {
|
||||||
@@ -429,6 +429,9 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: SizedBox(height: 96),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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/core/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_grid_loading_widget.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/document_grid_loading_widget.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_detailed_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_detailed_item.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_grid_item.dart';
|
||||||
@@ -159,7 +160,7 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
crossAxisCount: 2,
|
crossAxisCount: 2,
|
||||||
mainAxisSpacing: 4,
|
mainAxisSpacing: 4,
|
||||||
crossAxisSpacing: 4,
|
crossAxisSpacing: 4,
|
||||||
mainAxisExtent: 356,
|
mainAxisExtent: 324,
|
||||||
),
|
),
|
||||||
itemCount: documents.length,
|
itemCount: documents.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
@@ -176,7 +177,7 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||||
onStoragePathSelected: onStoragePathSelected,
|
onStoragePathSelected: onStoragePathSelected,
|
||||||
enableHeroAnimation: enableHeroAnimation,
|
enableHeroAnimation: enableHeroAnimation,
|
||||||
);
|
).paddedSymmetrically(horizontal: 4);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class DateAndDocumentTypeLabelWidget extends StatelessWidget {
|
||||||
|
const DateAndDocumentTypeLabelWidget({
|
||||||
|
super.key,
|
||||||
|
required this.document,
|
||||||
|
required this.onDocumentTypeSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DocumentModel document;
|
||||||
|
final void Function(int? documentTypeId)? onDocumentTypeSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final subtitleStyle =
|
||||||
|
Theme.of(context).textTheme.labelMedium?.apply(color: Colors.grey);
|
||||||
|
return RichText(
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
text: TextSpan(
|
||||||
|
text: DateFormat.yMMMMd(Localizations.localeOf(context).toString())
|
||||||
|
.format(document.created),
|
||||||
|
style: subtitleStyle,
|
||||||
|
children: document.documentType != null
|
||||||
|
? [
|
||||||
|
const TextSpan(text: '\u30FB'),
|
||||||
|
WidgetSpan(
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
onTap: onDocumentTypeSelected != null
|
||||||
|
? () => onDocumentTypeSelected!(document.documentType)
|
||||||
|
: null,
|
||||||
|
child: Text(
|
||||||
|
context
|
||||||
|
.watch<LabelRepository>()
|
||||||
|
.state
|
||||||
|
.documentTypes[document.documentType]!
|
||||||
|
.name,
|
||||||
|
style: subtitleStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
|||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/date_and_document_type_widget.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/documents/view/widgets/items/document_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||||
@@ -100,38 +101,28 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (paperlessUser.canViewCorrespondents)
|
||||||
|
CorrespondentWidget(
|
||||||
|
onSelected: onCorrespondentSelected,
|
||||||
|
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
correspondent: labels.correspondents[document.correspondent],
|
||||||
|
).paddedLTRB(8, 8, 8, 0),
|
||||||
|
Text(
|
||||||
|
document.title.isEmpty ? '(-)' : document.title,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
).paddedLTRB(8, 8, 8, 4),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: RichText(
|
child: DateAndDocumentTypeLabelWidget(
|
||||||
maxLines: 1,
|
document: document,
|
||||||
overflow: TextOverflow.ellipsis,
|
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||||
text: TextSpan(
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodySmall
|
|
||||||
?.apply(color: Theme.of(context).hintColor),
|
|
||||||
text: DateFormat.yMMMMd(
|
|
||||||
Localizations.localeOf(context).toString())
|
|
||||||
.format(document.created),
|
|
||||||
children: [
|
|
||||||
if (paperlessUser.canViewDocumentTypes &&
|
|
||||||
document.documentType != null) ...[
|
|
||||||
const TextSpan(text: '\u30FB'),
|
|
||||||
TextSpan(
|
|
||||||
text: labels
|
|
||||||
.documentTypes[document.documentType]?.name,
|
|
||||||
recognizer: onDocumentTypeSelected != null
|
|
||||||
? (TapGestureRecognizer()
|
|
||||||
..onTap = () => onDocumentTypeSelected!(
|
|
||||||
document.documentType))
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (document.archiveSerialNumber != null)
|
if (document.archiveSerialNumber != null)
|
||||||
@@ -143,30 +134,7 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
?.apply(color: Theme.of(context).hintColor),
|
?.apply(color: Theme.of(context).hintColor),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddedLTRB(8, 8, 8, 4),
|
).paddedLTRB(8, 4, 8, 8),
|
||||||
Text(
|
|
||||||
document.title.isEmpty ? '(-)' : document.title,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
).paddedLTRB(8, 0, 8, 4),
|
|
||||||
if (paperlessUser.canViewCorrespondents)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Icons.person_outline,
|
|
||||||
size: 16,
|
|
||||||
).paddedOnly(right: 4.0),
|
|
||||||
CorrespondentWidget(
|
|
||||||
onSelected: onCorrespondentSelected,
|
|
||||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
correspondent:
|
|
||||||
labels.correspondents[document.correspondent],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).paddedLTRB(8, 0, 8, 8),
|
|
||||||
if (highlights != null)
|
if (highlights != null)
|
||||||
Html(
|
Html(
|
||||||
data: '<p>${highlights!}</p>',
|
data: '<p>${highlights!}</p>',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.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/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
|
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.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/documents/view/widgets/items/document_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||||
@@ -29,111 +30,133 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
var currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
||||||
return Padding(
|
return Stack(
|
||||||
padding: const EdgeInsets.all(8.0),
|
children: [
|
||||||
child: Card(
|
Card(
|
||||||
elevation: 1.0,
|
elevation: 1.0,
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Theme.of(context).colorScheme.inversePrimary
|
? Theme.of(context).colorScheme.inversePrimary
|
||||||
: Theme.of(context).cardColor,
|
: Theme.of(context).cardColor,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
onTap: _onTap,
|
onTap: _onTap,
|
||||||
onLongPress: onSelected != null ? () => onSelected!(document) : null,
|
onLongPress:
|
||||||
child: Column(
|
onSelected != null ? () => onSelected!(document) : null,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
AspectRatio(
|
children: [
|
||||||
aspectRatio: 1,
|
ClipRRect(
|
||||||
child: Stack(
|
borderRadius: BorderRadius.circular(12),
|
||||||
children: [
|
child: AspectRatio(
|
||||||
Positioned.fill(
|
aspectRatio: 1,
|
||||||
child: DocumentPreview(
|
child: Stack(
|
||||||
documentId: document.id,
|
children: [
|
||||||
borderRadius: 12.0,
|
Positioned.fill(
|
||||||
enableHero: enableHeroAnimation,
|
child: DocumentPreview(
|
||||||
),
|
documentId: document.id,
|
||||||
),
|
borderRadius: 12.0,
|
||||||
Align(
|
enableHero: enableHeroAnimation,
|
||||||
alignment: Alignment.bottomLeft,
|
|
||||||
child: SizedBox(
|
|
||||||
height: 48,
|
|
||||||
child: NotificationListener<ScrollNotification>(
|
|
||||||
// Prevents ancestor notification listeners to be notified when this widget scrolls
|
|
||||||
onNotification: (notification) => true,
|
|
||||||
child: CustomScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
slivers: [
|
|
||||||
const SliverToBoxAdapter(
|
|
||||||
child: SizedBox(width: 8),
|
|
||||||
),
|
|
||||||
if (currentUser.canViewTags)
|
|
||||||
TagsWidget.sliver(
|
|
||||||
tags: document.tags
|
|
||||||
.map((e) => context
|
|
||||||
.watch<LabelRepository>()
|
|
||||||
.state
|
|
||||||
.tags[e]!)
|
|
||||||
.toList(),
|
|
||||||
onTagSelected: onTagSelected,
|
|
||||||
),
|
|
||||||
const SliverToBoxAdapter(
|
|
||||||
child: SizedBox(width: 8),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Align(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
child: SizedBox(
|
||||||
|
height: kMinInteractiveDimension,
|
||||||
|
child: NotificationListener<ScrollNotification>(
|
||||||
|
// Prevents ancestor notification listeners to be notified when this widget scrolls
|
||||||
|
onNotification: (notification) => true,
|
||||||
|
child: CustomScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
slivers: [
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: SizedBox(width: 8),
|
||||||
|
),
|
||||||
|
if (currentUser.canViewTags)
|
||||||
|
TagsWidget.sliver(
|
||||||
|
tags: document.tags
|
||||||
|
.map((e) => context
|
||||||
|
.watch<LabelRepository>()
|
||||||
|
.state
|
||||||
|
.tags[e]!)
|
||||||
|
.toList(),
|
||||||
|
onTagSelected: onTagSelected,
|
||||||
|
),
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: SizedBox(width: 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
if (currentUser.canViewCorrespondents)
|
|
||||||
CorrespondentWidget(
|
|
||||||
correspondent: context
|
|
||||||
.watch<LabelRepository>()
|
|
||||||
.state
|
|
||||||
.correspondents[document.correspondent],
|
|
||||||
onSelected: onCorrespondentSelected,
|
|
||||||
),
|
|
||||||
if (currentUser.canViewDocumentTypes)
|
|
||||||
DocumentTypeWidget(
|
|
||||||
documentType: context
|
|
||||||
.watch<LabelRepository>()
|
|
||||||
.state
|
|
||||||
.documentTypes[document.documentType],
|
|
||||||
onSelected: onDocumentTypeSelected,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
|
||||||
child: Text(
|
|
||||||
document.title.isEmpty ? '-' : document.title,
|
|
||||||
maxLines: 3,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Text(
|
|
||||||
DateFormat.yMMMMd(
|
|
||||||
Localizations.localeOf(context).toString())
|
|
||||||
.format(document.created),
|
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
],
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (currentUser.canViewCorrespondents)
|
||||||
|
CorrespondentWidget(
|
||||||
|
correspondent: context
|
||||||
|
.watch<LabelRepository>()
|
||||||
|
.state
|
||||||
|
.correspondents[document.correspondent],
|
||||||
|
onSelected: onCorrespondentSelected,
|
||||||
|
),
|
||||||
|
if (currentUser.canViewDocumentTypes)
|
||||||
|
DocumentTypeWidget(
|
||||||
|
documentType: context
|
||||||
|
.watch<LabelRepository>()
|
||||||
|
.state
|
||||||
|
.documentTypes[document.documentType],
|
||||||
|
onSelected: onDocumentTypeSelected,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Text(
|
||||||
|
document.title.isEmpty ? '-' : document.title,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateFormat.yMMMMd(
|
||||||
|
Localizations.localeOf(context).toString(),
|
||||||
|
).format(document.created),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
if (document.archiveSerialNumber != null)
|
||||||
|
Text(
|
||||||
|
'#' + document.archiveSerialNumber!.toString(),
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall
|
||||||
|
?.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:paperless_api/src/models/document_model.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
|
import 'package:paperless_mobile/core/repository/label_repository_state.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/date_and_document_type_widget.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/documents/view/widgets/items/document_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||||
@@ -31,6 +33,7 @@ class DocumentListItem extends DocumentItem {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final labels = context.watch<LabelRepository>().state;
|
final labels = context.watch<LabelRepository>().state;
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
tileColor: backgroundColor,
|
tileColor: backgroundColor,
|
||||||
dense: true,
|
dense: true,
|
||||||
@@ -75,35 +78,11 @@ class DocumentListItem extends DocumentItem {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: IntrinsicWidth(
|
subtitle: Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
child: DateAndDocumentTypeLabelWidget(
|
||||||
child: RichText(
|
document: document,
|
||||||
maxLines: 1,
|
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
text: TextSpan(
|
|
||||||
text:
|
|
||||||
DateFormat.yMMMMd(Localizations.localeOf(context).toString())
|
|
||||||
.format(document.created),
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelSmall
|
|
||||||
?.apply(color: Colors.grey),
|
|
||||||
children: document.documentType != null
|
|
||||||
? [
|
|
||||||
const TextSpan(text: '\u30FB'),
|
|
||||||
TextSpan(
|
|
||||||
text: labels.documentTypes[document.documentType]?.name,
|
|
||||||
recognizer: onDocumentTypeSelected != null
|
|
||||||
? (TapGestureRecognizer()
|
|
||||||
..onTap = () => onDocumentTypeSelected!(
|
|
||||||
document.documentType))
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
isThreeLine: document.tags.isNotEmpty,
|
isThreeLine: document.tags.isNotEmpty,
|
||||||
|
|||||||
@@ -21,15 +21,19 @@ class CorrespondentWidget extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AbsorbPointer(
|
return AbsorbPointer(
|
||||||
absorbing: !isClickable,
|
absorbing: !isClickable,
|
||||||
child: GestureDetector(
|
child: Material(
|
||||||
onTap: () => onSelected?.call(correspondent?.id),
|
color: Colors.transparent,
|
||||||
child: Text(
|
child: InkWell(
|
||||||
correspondent?.name ?? "-",
|
borderRadius: BorderRadius.circular(4),
|
||||||
maxLines: 1,
|
onTap: () => onSelected?.call(correspondent?.id),
|
||||||
overflow: TextOverflow.ellipsis,
|
child: Text(
|
||||||
style:
|
correspondent?.name ?? "-",
|
||||||
(textStyle ?? Theme.of(context).textTheme.bodyMedium)?.copyWith(
|
maxLines: 1,
|
||||||
color: textColor ?? Theme.of(context).colorScheme.primary,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style:
|
||||||
|
(textStyle ?? Theme.of(context).textTheme.bodyMedium)?.copyWith(
|
||||||
|
color: textColor ?? Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -18,14 +18,18 @@ class DocumentTypeWidget extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AbsorbPointer(
|
return AbsorbPointer(
|
||||||
absorbing: !isClickable,
|
absorbing: !isClickable,
|
||||||
child: GestureDetector(
|
child: Material(
|
||||||
onTap: () => onSelected?.call(documentType?.id),
|
color: Colors.transparent,
|
||||||
child: Text(
|
child: InkWell(
|
||||||
documentType?.toString() ?? "-",
|
borderRadius: BorderRadius.circular(4),
|
||||||
style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
|
onTap: () => onSelected?.call(documentType?.id),
|
||||||
?.copyWith(color: Theme.of(context).colorScheme.tertiary),
|
child: Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
documentType?.toString() ?? "-",
|
||||||
maxLines: 1,
|
style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
|
||||||
|
?.copyWith(color: Theme.of(context).colorScheme.tertiary),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -70,7 +70,9 @@ class _AppLogsPageState extends State<AppLogsPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(S.of(context)!.appLogs(formattedDate)),
|
title: Text(S
|
||||||
|
.of(context)!
|
||||||
|
.appLogs(formattedDate)), //TODO: CHange to App-Logs in german
|
||||||
actions: [
|
actions: [
|
||||||
if (state is AppLogsStateLoaded)
|
if (state is AppLogsStateLoaded)
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|||||||
@@ -84,40 +84,42 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
resizeToAvoidBottomInset: true,
|
resizeToAvoidBottomInset: true,
|
||||||
body: FormBuilder(
|
body: AutofillGroup(
|
||||||
key: _formKey,
|
child: FormBuilder(
|
||||||
child: ListView(
|
key: _formKey,
|
||||||
children: [
|
child: ListView(
|
||||||
ServerAddressFormField(
|
children: [
|
||||||
initialValue: widget.initialServerUrl,
|
ServerAddressFormField(
|
||||||
onSubmit: (address) {
|
initialValue: widget.initialServerUrl,
|
||||||
_updateReachability(address);
|
onSubmit: (address) {
|
||||||
},
|
_updateReachability(address);
|
||||||
).padded(),
|
},
|
||||||
ClientCertificateFormField(
|
).padded(),
|
||||||
initialBytes: widget.initialClientCertificate?.bytes,
|
ClientCertificateFormField(
|
||||||
initialPassphrase: widget.initialClientCertificate?.passphrase,
|
initialBytes: widget.initialClientCertificate?.bytes,
|
||||||
onChanged: (_) => _updateReachability(),
|
initialPassphrase: widget.initialClientCertificate?.passphrase,
|
||||||
).padded(),
|
onChanged: (_) => _updateReachability(),
|
||||||
_buildStatusIndicator(),
|
).padded(),
|
||||||
if (_reachabilityStatus == ReachabilityStatus.reachable) ...[
|
_buildStatusIndicator(),
|
||||||
UserCredentialsFormField(
|
if (_reachabilityStatus == ReachabilityStatus.reachable) ...[
|
||||||
formKey: _formKey,
|
UserCredentialsFormField(
|
||||||
initialUsername: widget.initialUsername,
|
formKey: _formKey,
|
||||||
initialPassword: widget.initialPassword,
|
initialUsername: widget.initialUsername,
|
||||||
onFieldsSubmitted: _onSubmit,
|
initialPassword: widget.initialPassword,
|
||||||
),
|
onFieldsSubmitted: _onSubmit,
|
||||||
Text(
|
),
|
||||||
S.of(context)!.loginRequiredPermissionsHint,
|
Text(
|
||||||
style: Theme.of(context).textTheme.bodySmall?.apply(
|
S.of(context)!.loginRequiredPermissionsHint,
|
||||||
color: Theme.of(context)
|
style: Theme.of(context).textTheme.bodySmall?.apply(
|
||||||
.colorScheme
|
color: Theme.of(context)
|
||||||
.onBackground
|
.colorScheme
|
||||||
.withOpacity(0.6),
|
.onBackground
|
||||||
),
|
.withOpacity(0.6),
|
||||||
).padded(16),
|
),
|
||||||
]
|
).padded(16),
|
||||||
],
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,65 +41,62 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
|||||||
username: widget.initialUsername,
|
username: widget.initialUsername,
|
||||||
),
|
),
|
||||||
name: UserCredentialsFormField.fkCredentials,
|
name: UserCredentialsFormField.fkCredentials,
|
||||||
builder: (field) => AutofillGroup(
|
builder: (field) => Column(
|
||||||
child: Column(
|
children: [
|
||||||
children: [
|
TextFormField(
|
||||||
TextFormField(
|
key: const ValueKey('login-username'),
|
||||||
key: const ValueKey('login-username'),
|
focusNode: _usernameFocusNode,
|
||||||
focusNode: _usernameFocusNode,
|
textCapitalization: TextCapitalization.none,
|
||||||
textCapitalization: TextCapitalization.none,
|
textInputAction: TextInputAction.next,
|
||||||
textInputAction: TextInputAction.next,
|
onFieldSubmitted: (value) {
|
||||||
onFieldSubmitted: (value) {
|
_passwordFocusNode.requestFocus();
|
||||||
_passwordFocusNode.requestFocus();
|
},
|
||||||
},
|
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
autocorrect: false,
|
||||||
autocorrect: false,
|
onChanged: (username) => field.didChange(
|
||||||
onChanged: (username) => field.didChange(
|
field.value?.copyWith(username: username) ??
|
||||||
field.value?.copyWith(username: username) ??
|
LoginFormCredentials(username: username),
|
||||||
LoginFormCredentials(username: username),
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
if (value?.trim().isEmpty ?? true) {
|
|
||||||
return S.of(context)!.usernameMustNotBeEmpty;
|
|
||||||
}
|
|
||||||
final serverAddress = widget.formKey.currentState!
|
|
||||||
.getRawValue<String>(
|
|
||||||
ServerAddressFormField.fkServerAddress);
|
|
||||||
if (serverAddress != null) {
|
|
||||||
final userExists = Hive.localUserAccountBox.values
|
|
||||||
.map((e) => e.id)
|
|
||||||
.contains('$value@$serverAddress');
|
|
||||||
if (userExists) {
|
|
||||||
return S.of(context)!.userAlreadyExists;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
autofillHints: const [AutofillHints.username],
|
|
||||||
decoration: InputDecoration(
|
|
||||||
label: Text(S.of(context)!.username),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
ObscuredInputTextFormField(
|
validator: (value) {
|
||||||
key: const ValueKey('login-password'),
|
if (value?.trim().isEmpty ?? true) {
|
||||||
focusNode: _passwordFocusNode,
|
return S.of(context)!.usernameMustNotBeEmpty;
|
||||||
label: S.of(context)!.password,
|
}
|
||||||
onChanged: (password) => field.didChange(
|
final serverAddress = widget.formKey.currentState!
|
||||||
field.value?.copyWith(password: password) ??
|
.getRawValue<String>(ServerAddressFormField.fkServerAddress);
|
||||||
LoginFormCredentials(password: password),
|
if (serverAddress != null) {
|
||||||
),
|
final userExists = Hive.localUserAccountBox.values
|
||||||
onFieldSubmitted: (_) {
|
.map((e) => e.id)
|
||||||
widget.onFieldsSubmitted();
|
.contains('$value@$serverAddress');
|
||||||
},
|
if (userExists) {
|
||||||
validator: (value) {
|
return S.of(context)!.userAlreadyExists;
|
||||||
if (value?.trim().isEmpty ?? true) {
|
|
||||||
return S.of(context)!.passwordMustNotBeEmpty;
|
|
||||||
}
|
}
|
||||||
return null;
|
}
|
||||||
},
|
return null;
|
||||||
|
},
|
||||||
|
autofillHints: const [AutofillHints.username],
|
||||||
|
decoration: InputDecoration(
|
||||||
|
label: Text(S.of(context)!.username),
|
||||||
),
|
),
|
||||||
].map((child) => child.padded()).toList(),
|
),
|
||||||
),
|
ObscuredInputTextFormField(
|
||||||
|
key: const ValueKey('login-password'),
|
||||||
|
focusNode: _passwordFocusNode,
|
||||||
|
label: S.of(context)!.password,
|
||||||
|
onChanged: (password) => field.didChange(
|
||||||
|
field.value?.copyWith(password: password) ??
|
||||||
|
LoginFormCredentials(password: password),
|
||||||
|
),
|
||||||
|
onFieldSubmitted: (_) {
|
||||||
|
widget.onFieldsSubmitted();
|
||||||
|
},
|
||||||
|
validator: (value) {
|
||||||
|
if (value?.trim().isEmpty ?? true) {
|
||||||
|
return S.of(context)!.passwordMustNotBeEmpty;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
].map((child) => child.padded()).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||||
@@ -21,11 +22,12 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
|
|||||||
Future<void> loadMore() async {
|
Future<void> loadMore() async {
|
||||||
final hasConnection =
|
final hasConnection =
|
||||||
await connectivityStatusService.isConnectedToInternet();
|
await connectivityStatusService.isConnectedToInternet();
|
||||||
if (state.isLastPageLoaded || !hasConnection) {
|
if (state.isLastPageLoaded || !hasConnection || state.isLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit(state.copyWithPaged(isLoading: true));
|
emit(state.copyWithPaged(isLoading: true));
|
||||||
final newFilter = state.filter.copyWith(page: state.filter.page + 1);
|
final newFilter = state.filter.copyWith(page: state.filter.page + 1);
|
||||||
|
debugPrint("Fetching page ${newFilter.page}");
|
||||||
try {
|
try {
|
||||||
final result = await api.findAll(newFilter);
|
final result = await api.findAll(newFilter);
|
||||||
emit(
|
emit(
|
||||||
@@ -217,7 +219,6 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
notifier.removeListener(this);
|
notifier.removeListener(this);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
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/settings/view/widgets/app_logs_tile.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/biometric_authentication_setting.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/biometric_authentication_setting.dart';
|
||||||
|
import 'package:paperless_mobile/features/settings/view/widgets/changelogs_tile.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/clear_storage_settings.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/clear_storage_settings.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/color_scheme_option_setting.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/color_scheme_option_setting.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/widgets/default_download_file_type_setting.dart';
|
import 'package:paperless_mobile/features/settings/view/widgets/default_download_file_type_setting.dart';
|
||||||
@@ -37,6 +39,9 @@ class SettingsPage extends StatelessWidget {
|
|||||||
const SkipDocumentPreprationOnShareSetting(),
|
const SkipDocumentPreprationOnShareSetting(),
|
||||||
_buildSectionHeader(context, S.of(context)!.storage),
|
_buildSectionHeader(context, S.of(context)!.storage),
|
||||||
const ClearCacheSetting(),
|
const ClearCacheSetting(),
|
||||||
|
_buildSectionHeader(context, S.of(context)!.misc),
|
||||||
|
const AppLogsTile(),
|
||||||
|
const ChangelogsTile(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomNavigationBar: UserAccountBuilder(
|
bottomNavigationBar: UserAccountBuilder(
|
||||||
|
|||||||
18
lib/features/settings/view/widgets/app_logs_tile.dart
Normal file
18
lib/features/settings/view/widgets/app_logs_tile.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:paperless_mobile/routes/typed/top_level/app_logs_route.dart';
|
||||||
|
|
||||||
|
class AppLogsTile extends StatelessWidget {
|
||||||
|
const AppLogsTile({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
leading: const Icon(Icons.subject),
|
||||||
|
title: Text(S.of(context)!.appLogs('')),
|
||||||
|
onTap: () {
|
||||||
|
AppLogsRoute().push(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
lib/features/settings/view/widgets/changelogs_tile.dart
Normal file
18
lib/features/settings/view/widgets/changelogs_tile.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:paperless_mobile/routes/typed/top_level/changelog_route.dart';
|
||||||
|
|
||||||
|
class ChangelogsTile extends StatelessWidget {
|
||||||
|
const ChangelogsTile({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
leading: const Icon(Icons.history),
|
||||||
|
title: Text(S.of(context)!.changelog),
|
||||||
|
onTap: () {
|
||||||
|
ChangelogRoute().push(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
|
|||||||
'cs': LanguageOption('Česky', true),
|
'cs': LanguageOption('Česky', true),
|
||||||
'tr': LanguageOption('Türkçe', true),
|
'tr': LanguageOption('Türkçe', true),
|
||||||
'pl': LanguageOption('Polska', true),
|
'pl': LanguageOption('Polska', true),
|
||||||
'ca': LanguageOption('Catalan', true),
|
'ca': LanguageOption('Català', true),
|
||||||
'ru': LanguageOption('Русский', true),
|
'ru': LanguageOption('Русский', true),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
|||||||
final int documentId;
|
final int documentId;
|
||||||
@override
|
@override
|
||||||
final ConnectivityStatusService connectivityStatusService;
|
final ConnectivityStatusService connectivityStatusService;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final PaperlessDocumentsApi api;
|
final PaperlessDocumentsApi api;
|
||||||
|
|
||||||
@@ -33,19 +34,9 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
|||||||
onDeleted: remove,
|
onDeleted: remove,
|
||||||
onUpdated: replace,
|
onUpdated: replace,
|
||||||
);
|
);
|
||||||
_labelRepository.addListener(
|
|
||||||
this,
|
|
||||||
onChanged: (labels) {
|
|
||||||
emit(state.copyWith(
|
|
||||||
correspondents: labels.correspondents,
|
|
||||||
documentTypes: labels.documentTypes,
|
|
||||||
tags: labels.tags,
|
|
||||||
storagePaths: labels.storagePaths,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
if (!state.hasLoaded) {
|
if (!state.hasLoaded) {
|
||||||
await updateFilter(
|
await updateFilter(
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
part of 'similar_documents_cubit.dart';
|
part of 'similar_documents_cubit.dart';
|
||||||
|
|
||||||
class SimilarDocumentsState extends DocumentPagingState {
|
class SimilarDocumentsState extends DocumentPagingState {
|
||||||
final Map<int, Correspondent> correspondents;
|
|
||||||
final Map<int, DocumentType> documentTypes;
|
|
||||||
final Map<int, Tag> tags;
|
|
||||||
final Map<int, StoragePath> storagePaths;
|
|
||||||
|
|
||||||
const SimilarDocumentsState({
|
const SimilarDocumentsState({
|
||||||
required super.filter,
|
required super.filter,
|
||||||
super.hasLoaded,
|
super.hasLoaded,
|
||||||
super.isLoading,
|
super.isLoading,
|
||||||
super.value,
|
super.value,
|
||||||
this.correspondents = const {},
|
|
||||||
this.documentTypes = const {},
|
|
||||||
this.tags = const {},
|
|
||||||
this.storagePaths = const {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -23,10 +14,6 @@ class SimilarDocumentsState extends DocumentPagingState {
|
|||||||
hasLoaded,
|
hasLoaded,
|
||||||
isLoading,
|
isLoading,
|
||||||
value,
|
value,
|
||||||
correspondents,
|
|
||||||
documentTypes,
|
|
||||||
tags,
|
|
||||||
storagePaths,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -49,20 +36,12 @@ class SimilarDocumentsState extends DocumentPagingState {
|
|||||||
bool? isLoading,
|
bool? isLoading,
|
||||||
List<PagedSearchResult<DocumentModel>>? value,
|
List<PagedSearchResult<DocumentModel>>? value,
|
||||||
DocumentFilter? filter,
|
DocumentFilter? filter,
|
||||||
Map<int, Correspondent>? correspondents,
|
|
||||||
Map<int, DocumentType>? documentTypes,
|
|
||||||
Map<int, Tag>? tags,
|
|
||||||
Map<int, StoragePath>? storagePaths,
|
|
||||||
}) {
|
}) {
|
||||||
return SimilarDocumentsState(
|
return SimilarDocumentsState(
|
||||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||||
isLoading: isLoading ?? this.isLoading,
|
isLoading: isLoading ?? this.isLoading,
|
||||||
value: value ?? this.value,
|
value: value ?? this.value,
|
||||||
filter: filter ?? this.filter,
|
filter: filter ?? this.filter,
|
||||||
correspondents: correspondents ?? this.correspondents,
|
|
||||||
documentTypes: documentTypes ?? this.documentTypes,
|
|
||||||
tags: tags ?? this.tags,
|
|
||||||
storagePaths: storagePaths ?? this.storagePaths,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1001,13 +1001,23 @@
|
|||||||
"@discardChangesWarning": {
|
"@discardChangesWarning": {
|
||||||
"description": "Warning message shown when the user tries to close a route without saving the changes."
|
"description": "Warning message shown when the user tries to close a route without saving the changes."
|
||||||
},
|
},
|
||||||
"changelog": "Changelog",
|
"changelog": "Historial de canvis",
|
||||||
"noLogsFoundOn": "No logs found on {date}.",
|
"noLogsFoundOn": "Sense logs trovats per {date}.",
|
||||||
"logfileBottomReached": "You have reached the bottom of this logfile.",
|
"logfileBottomReached": "Final d'aquest arxiu de registres.",
|
||||||
"appLogs": "App logs {date}",
|
"appLogs": "Logs d'aplicació {date}",
|
||||||
"saveLogsToFile": "Save logs to file",
|
"saveLogsToFile": "Desar registres a arxiu",
|
||||||
"copyToClipboard": "Copy to clipboard",
|
"copyToClipboard": "Copia al porta-retalls",
|
||||||
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
"couldNotLoadLogfileFrom": "No es pot carregar log desde {date}.",
|
||||||
"loadingLogsFrom": "Loading logs from {date}...",
|
"loadingLogsFrom": "Carregant registres des de {date}...",
|
||||||
"clearLogs": "Clear logs from {date}"
|
"clearLogs": "Netejar registres des de {date}",
|
||||||
|
"showPdf": "Show PDF",
|
||||||
|
"@showPdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
|
||||||
|
},
|
||||||
|
"hidePdf": "Hide PDF",
|
||||||
|
"@hidePdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||||
|
},
|
||||||
|
"misc": "Miscellaneous",
|
||||||
|
"loggingOut": "Logging out..."
|
||||||
}
|
}
|
||||||
@@ -1009,5 +1009,15 @@
|
|||||||
"copyToClipboard": "Copy to clipboard",
|
"copyToClipboard": "Copy to clipboard",
|
||||||
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
||||||
"loadingLogsFrom": "Loading logs from {date}...",
|
"loadingLogsFrom": "Loading logs from {date}...",
|
||||||
"clearLogs": "Clear logs from {date}"
|
"clearLogs": "Clear logs from {date}",
|
||||||
|
"showPdf": "Show PDF",
|
||||||
|
"@showPdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
|
||||||
|
},
|
||||||
|
"hidePdf": "Hide PDF",
|
||||||
|
"@hidePdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||||
|
},
|
||||||
|
"misc": "Miscellaneous",
|
||||||
|
"loggingOut": "Logging out..."
|
||||||
}
|
}
|
||||||
@@ -1009,5 +1009,15 @@
|
|||||||
"copyToClipboard": "In Zwischenablage kopieren",
|
"copyToClipboard": "In Zwischenablage kopieren",
|
||||||
"couldNotLoadLogfileFrom": "Logs vom {date} konnten nicht geladen werden.",
|
"couldNotLoadLogfileFrom": "Logs vom {date} konnten nicht geladen werden.",
|
||||||
"loadingLogsFrom": "Lade Logs vom {date}...",
|
"loadingLogsFrom": "Lade Logs vom {date}...",
|
||||||
"clearLogs": "Logs vom {date} leeren"
|
"clearLogs": "Logs vom {date} leeren",
|
||||||
|
"showPdf": "PDF anzeigen",
|
||||||
|
"@showPdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
|
||||||
|
},
|
||||||
|
"hidePdf": "PDF ausblenden",
|
||||||
|
"@hidePdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||||
|
},
|
||||||
|
"misc": "Sonstige",
|
||||||
|
"loggingOut": "Abmelden..."
|
||||||
}
|
}
|
||||||
@@ -1009,5 +1009,15 @@
|
|||||||
"copyToClipboard": "Copy to clipboard",
|
"copyToClipboard": "Copy to clipboard",
|
||||||
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
||||||
"loadingLogsFrom": "Loading logs from {date}...",
|
"loadingLogsFrom": "Loading logs from {date}...",
|
||||||
"clearLogs": "Clear logs from {date}"
|
"clearLogs": "Clear logs from {date}",
|
||||||
|
"showPdf": "Show PDF",
|
||||||
|
"@showPdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
|
||||||
|
},
|
||||||
|
"hidePdf": "Hide PDF",
|
||||||
|
"@hidePdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||||
|
},
|
||||||
|
"misc": "Miscellaneous",
|
||||||
|
"loggingOut": "Logging out..."
|
||||||
}
|
}
|
||||||
@@ -984,7 +984,7 @@
|
|||||||
"@authenticatingDots": {
|
"@authenticatingDots": {
|
||||||
"description": "Message shown when the app is authenticating the user"
|
"description": "Message shown when the app is authenticating the user"
|
||||||
},
|
},
|
||||||
"persistingUserInformation": "Manteniendo información del usuario...",
|
"persistingUserInformation": "Preservando información del usuario...",
|
||||||
"fetchingUserInformation": "Obteniendo información del usuario...",
|
"fetchingUserInformation": "Obteniendo información del usuario...",
|
||||||
"@fetchingUserInformation": {
|
"@fetchingUserInformation": {
|
||||||
"description": "Message shown when the app loads user data from the server"
|
"description": "Message shown when the app loads user data from the server"
|
||||||
@@ -993,21 +993,31 @@
|
|||||||
"@restoringSession": {
|
"@restoringSession": {
|
||||||
"description": "Message shown when the user opens the app and the previous user is tried to be authenticated and logged in"
|
"description": "Message shown when the user opens the app and the previous user is tried to be authenticated and logged in"
|
||||||
},
|
},
|
||||||
"documentsAssigned": "{count, plural, zero{No documents} one{1 document} other{{count} documents}}",
|
"documentsAssigned": "{count, plural, zero{Sin documentos} one{1 documento} other{{count} documentos}}",
|
||||||
"@documentsAssigned": {
|
"@documentsAssigned": {
|
||||||
"description": "Text shown with a correspondent, document type etc. to indicate the number of documents this filter will maximally yield."
|
"description": "Text shown with a correspondent, document type etc. to indicate the number of documents this filter will maximally yield."
|
||||||
},
|
},
|
||||||
"discardChangesWarning": "You have unsaved changes. By continuing, all changes will be lost. Do you want to discard these changes?",
|
"discardChangesWarning": "Tienes cambios sin guardar. Si continúa, se perderán todos los cambios. ¿Quiere descartar estos cambios?",
|
||||||
"@discardChangesWarning": {
|
"@discardChangesWarning": {
|
||||||
"description": "Warning message shown when the user tries to close a route without saving the changes."
|
"description": "Warning message shown when the user tries to close a route without saving the changes."
|
||||||
},
|
},
|
||||||
"changelog": "Changelog",
|
"changelog": "Changelog",
|
||||||
"noLogsFoundOn": "No logs found on {date}.",
|
"noLogsFoundOn": "No se encontraron registros en {date}.",
|
||||||
"logfileBottomReached": "You have reached the bottom of this logfile.",
|
"logfileBottomReached": "Has alcanzado el final del archivo de registro.",
|
||||||
"appLogs": "App logs {date}",
|
"appLogs": "Registros de la aplicación {date}",
|
||||||
"saveLogsToFile": "Save logs to file",
|
"saveLogsToFile": "Guardar registros en un archivo",
|
||||||
"copyToClipboard": "Copy to clipboard",
|
"copyToClipboard": "Copiar al portapapeles",
|
||||||
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
"couldNotLoadLogfileFrom": "No se pudo cargar el archivo de registro desde {date}.",
|
||||||
"loadingLogsFrom": "Loading logs from {date}...",
|
"loadingLogsFrom": "Cargando registros desde {date}...",
|
||||||
"clearLogs": "Clear logs from {date}"
|
"clearLogs": "Limpiar registros desde {date}",
|
||||||
|
"showPdf": "Show PDF",
|
||||||
|
"@showPdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
|
||||||
|
},
|
||||||
|
"hidePdf": "Hide PDF",
|
||||||
|
"@hidePdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||||
|
},
|
||||||
|
"misc": "Miscellaneous",
|
||||||
|
"loggingOut": "Logging out..."
|
||||||
}
|
}
|
||||||
@@ -1009,5 +1009,15 @@
|
|||||||
"copyToClipboard": "Copy to clipboard",
|
"copyToClipboard": "Copy to clipboard",
|
||||||
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
||||||
"loadingLogsFrom": "Loading logs from {date}...",
|
"loadingLogsFrom": "Loading logs from {date}...",
|
||||||
"clearLogs": "Clear logs from {date}"
|
"clearLogs": "Clear logs from {date}",
|
||||||
|
"showPdf": "Show PDF",
|
||||||
|
"@showPdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
|
||||||
|
},
|
||||||
|
"hidePdf": "Hide PDF",
|
||||||
|
"@hidePdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||||
|
},
|
||||||
|
"misc": "Sonstige",
|
||||||
|
"loggingOut": "Logging out..."
|
||||||
}
|
}
|
||||||
@@ -1009,5 +1009,15 @@
|
|||||||
"copyToClipboard": "Copy to clipboard",
|
"copyToClipboard": "Copy to clipboard",
|
||||||
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
||||||
"loadingLogsFrom": "Loading logs from {date}...",
|
"loadingLogsFrom": "Loading logs from {date}...",
|
||||||
"clearLogs": "Clear logs from {date}"
|
"clearLogs": "Clear logs from {date}",
|
||||||
|
"showPdf": "Show PDF",
|
||||||
|
"@showPdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
|
||||||
|
},
|
||||||
|
"hidePdf": "Hide PDF",
|
||||||
|
"@hidePdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||||
|
},
|
||||||
|
"misc": "Miscellaneous",
|
||||||
|
"loggingOut": "Logging out..."
|
||||||
}
|
}
|
||||||
@@ -1009,5 +1009,15 @@
|
|||||||
"copyToClipboard": "Copy to clipboard",
|
"copyToClipboard": "Copy to clipboard",
|
||||||
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
||||||
"loadingLogsFrom": "Loading logs from {date}...",
|
"loadingLogsFrom": "Loading logs from {date}...",
|
||||||
"clearLogs": "Clear logs from {date}"
|
"clearLogs": "Clear logs from {date}",
|
||||||
|
"showPdf": "Show PDF",
|
||||||
|
"@showPdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
|
||||||
|
},
|
||||||
|
"hidePdf": "Hide PDF",
|
||||||
|
"@hidePdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||||
|
},
|
||||||
|
"misc": "Miscellaneous",
|
||||||
|
"loggingOut": "Logging out..."
|
||||||
}
|
}
|
||||||
@@ -1009,5 +1009,15 @@
|
|||||||
"copyToClipboard": "Copy to clipboard",
|
"copyToClipboard": "Copy to clipboard",
|
||||||
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
||||||
"loadingLogsFrom": "Loading logs from {date}...",
|
"loadingLogsFrom": "Loading logs from {date}...",
|
||||||
"clearLogs": "Clear logs from {date}"
|
"clearLogs": "Clear logs from {date}",
|
||||||
|
"showPdf": "Show PDF",
|
||||||
|
"@showPdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" button on the document edit page"
|
||||||
|
},
|
||||||
|
"hidePdf": "Hide PDF",
|
||||||
|
"@hidePdf": {
|
||||||
|
"description": "Tooltip shown on the \"show pdf\" icon button on the document edit page"
|
||||||
|
},
|
||||||
|
"misc": "Miscellaneous",
|
||||||
|
"loggingOut": "Logging out..."
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
import 'package:paperless_mobile/routes/navigation_keys.dart';
|
||||||
import 'package:paperless_mobile/routes/routes.dart';
|
import 'package:paperless_mobile/routes/routes.dart';
|
||||||
|
|
||||||
@@ -15,10 +16,10 @@ class LoggingOutRoute extends GoRouteData {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||||
return const NoTransitionPage(
|
return NoTransitionPage(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text("Logging out..."), //TODO: INTL
|
child: Text(S.of(context)!.loggingOut),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 3.0.6+53
|
version: 3.1.0+54
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user