mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 01:15:44 -06:00
feat: Update translations, add pdf view to document edit page
This commit is contained in:
@@ -38,6 +38,8 @@ class DioHttpErrorInterceptor extends Interceptor {
|
||||
const PaperlessApiException(ErrorCode.missingClientCertificate),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
handler.reject(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,15 @@ class DocumentChangedNotifier {
|
||||
Object subscriber, {
|
||||
DocumentChangedCallback? onUpdated,
|
||||
DocumentChangedCallback? onDeleted,
|
||||
Iterable<int>? ids,
|
||||
}) {
|
||||
_subscribers.putIfAbsent(
|
||||
subscriber,
|
||||
() => [
|
||||
_updated.listen((value) {
|
||||
_updated.where((doc) => ids?.contains(doc.id) ?? true).listen((value) {
|
||||
onUpdated?.call(value);
|
||||
}),
|
||||
_deleted.listen((value) {
|
||||
_deleted.where((doc) => ids?.contains(doc.id) ?? true).listen((value) {
|
||||
onDeleted?.call(value);
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -53,6 +53,7 @@ class FormBuilderLocalizedDatePicker extends StatefulWidget {
|
||||
final DateTime? initialValue;
|
||||
final DateTime firstDate;
|
||||
final DateTime lastDate;
|
||||
final FocusNode? focusNode;
|
||||
|
||||
/// If set to true, the field will not throw any validation errors when empty.
|
||||
final bool allowUnset;
|
||||
@@ -67,6 +68,7 @@ class FormBuilderLocalizedDatePicker extends StatefulWidget {
|
||||
required this.labelText,
|
||||
this.prefixIcon,
|
||||
this.allowUnset = false,
|
||||
this.focusNode,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -100,8 +102,11 @@ class _FormBuilderLocalizedDatePickerState
|
||||
final initialText = widget.initialValue != null
|
||||
? DateFormat(formatString).format(widget.initialValue!)
|
||||
: null;
|
||||
final defaultFocusNode = FocusNode(debugLabel: formatString);
|
||||
final focusNode =
|
||||
i == 0 ? (widget.focusNode ?? defaultFocusNode) : defaultFocusNode;
|
||||
final controls = _NeighbourAwareDateInputSegmentControls(
|
||||
node: FocusNode(debugLabel: formatString),
|
||||
node: focusNode,
|
||||
controller: TextEditingController(text: initialText),
|
||||
format: formatString,
|
||||
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(
|
||||
dense: true,
|
||||
leading: const Icon(Icons.bug_report_outlined),
|
||||
@@ -179,14 +171,6 @@ class AppDrawer extends StatelessWidget {
|
||||
.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(
|
||||
dense: true,
|
||||
leading: const Icon(Icons.settings_outlined),
|
||||
|
||||
@@ -63,6 +63,7 @@ class ChangelogDialog extends StatelessWidget {
|
||||
}
|
||||
|
||||
const _versionNumbers = {
|
||||
"54": "3.0.7",
|
||||
"53": "3.0.6",
|
||||
"52": "3.0.5",
|
||||
"51": "3.0.4",
|
||||
|
||||
@@ -31,14 +31,13 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
this._notificationService, {
|
||||
required this.id,
|
||||
}) : super(const DocumentDetailsInitial()) {
|
||||
_notifier.addListener(this, onUpdated: (document) {
|
||||
if (state is DocumentDetailsLoaded) {
|
||||
final currentState = state as DocumentDetailsLoaded;
|
||||
if (document.id == currentState.document.id) {
|
||||
replace(document);
|
||||
}
|
||||
}
|
||||
});
|
||||
_notifier.addListener(
|
||||
this,
|
||||
onUpdated: (document) {
|
||||
replace(document);
|
||||
},
|
||||
ids: [id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
|
||||
@@ -22,11 +22,13 @@ class DocumentEditCubit extends Cubit<DocumentEditState> {
|
||||
required DocumentModel document,
|
||||
}) : _initialDocument = document,
|
||||
super(DocumentEditState(document: document)) {
|
||||
_notifier.addListener(this, onUpdated: (doc) {
|
||||
if (doc.id == document.id) {
|
||||
_notifier.addListener(
|
||||
this,
|
||||
onUpdated: (doc) {
|
||||
emit(state.copyWith(document: doc));
|
||||
}
|
||||
});
|
||||
},
|
||||
ids: [document.id],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateDocument(DocumentModel document) async {
|
||||
|
||||
@@ -9,13 +9,13 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.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/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/form_builder_fields/form_builder_localized_date_picker.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/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/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
@@ -32,7 +32,8 @@ class DocumentEditPage extends StatefulWidget {
|
||||
State<DocumentEditPage> createState() => _DocumentEditPageState();
|
||||
}
|
||||
|
||||
class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
class _DocumentEditPageState extends State<DocumentEditPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
static const fkTitle = "title";
|
||||
static const fkCorrespondent = "correspondent";
|
||||
static const fkTags = "tags";
|
||||
@@ -43,6 +44,23 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
||||
@@ -75,197 +93,228 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
doc.created != createdAt ||
|
||||
(doc.content != content && isContentTouched);
|
||||
},
|
||||
child: DefaultTabController(
|
||||
length: 2,
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
heroTag: "fab_document_edit",
|
||||
onPressed: () => _onSubmit(state.document),
|
||||
icon: const Icon(Icons.save),
|
||||
label: Text(S.of(context)!.saveChanges),
|
||||
),
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context)!.editDocument),
|
||||
bottom: TabBar(
|
||||
tabs: [
|
||||
Tab(text: S.of(context)!.overview),
|
||||
Tab(text: S.of(context)!.content)
|
||||
],
|
||||
),
|
||||
),
|
||||
extendBody: true,
|
||||
body: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||
top: 8,
|
||||
left: 8,
|
||||
right: 8,
|
||||
),
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: TabBarView(
|
||||
children: [
|
||||
ListView(
|
||||
children: [
|
||||
_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(),
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context)!.editDocument),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: _isShowingPdf
|
||||
? S.of(context)!.hidePdf
|
||||
: S.of(context)!.showPdf,
|
||||
padding: EdgeInsets.all(12),
|
||||
icon: AnimatedCrossFade(
|
||||
duration: _animationController.duration!,
|
||||
reverseDuration: _animationController.reverseDuration,
|
||||
crossFadeState: _isShowingPdf
|
||||
? CrossFadeState.showFirst
|
||||
: CrossFadeState.showSecond,
|
||||
firstChild: Icon(Icons.visibility_off_outlined),
|
||||
secondChild: Icon(Icons.visibility_outlined),
|
||||
),
|
||||
onPressed: () {
|
||||
if (_isShowingPdf) {
|
||||
setState(() {
|
||||
_isShowingPdf = false;
|
||||
});
|
||||
_animationController.reverse();
|
||||
} else {
|
||||
setState(() {
|
||||
_isShowingPdf = true;
|
||||
});
|
||||
_animationController.forward();
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
floatingActionButton: !_isShowingPdf
|
||||
? FloatingActionButton.extended(
|
||||
heroTag: "fab_document_edit",
|
||||
onPressed: () => _onSubmit(state.document),
|
||||
icon: const Icon(Icons.save),
|
||||
label: Text(S.of(context)!.saveChanges),
|
||||
)
|
||||
: null,
|
||||
appBar: TabBar(
|
||||
tabs: [
|
||||
Tab(text: S.of(context)!.overview),
|
||||
Tab(text: S.of(context)!.content),
|
||||
],
|
||||
),
|
||||
extendBody: true,
|
||||
body: _buildEditForm(
|
||||
context,
|
||||
state,
|
||||
filteredSuggestions,
|
||||
currentUser,
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
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,
|
||||
int? correspondent,
|
||||
|
||||
@@ -5,9 +5,13 @@ import 'package:flutter_pdfview/flutter_pdfview.dart';
|
||||
class DocumentView extends StatefulWidget {
|
||||
final Future<Uint8List> documentBytes;
|
||||
final String? title;
|
||||
final bool showAppBar;
|
||||
final bool showControls;
|
||||
const DocumentView({
|
||||
Key? key,
|
||||
required this.documentBytes,
|
||||
this.showAppBar = true,
|
||||
this.showControls = true,
|
||||
this.title,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -27,43 +31,47 @@ class _DocumentViewState extends State<DocumentView> {
|
||||
final canGoToNextPage = isInitialized && _currentPage! + 1 < _totalPages!;
|
||||
final canGoToPreviousPage = isInitialized && _currentPage! > 0;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: widget.title != null ? Text(widget.title!) : null,
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
appBar: widget.showAppBar
|
||||
? AppBar(
|
||||
title: widget.title != null ? Text(widget.title!) : null,
|
||||
)
|
||||
: null,
|
||||
bottomNavigationBar: widget.showControls
|
||||
? BottomAppBar(
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton.filled(
|
||||
onPressed: canGoToPreviousPage
|
||||
? () {
|
||||
_controller?.setPage(_currentPage! - 1);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.arrow_left),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
IconButton.filled(
|
||||
onPressed: canGoToNextPage
|
||||
? () {
|
||||
_controller?.setPage(_currentPage! + 1);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.arrow_right),
|
||||
Flexible(
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton.filled(
|
||||
onPressed: canGoToPreviousPage
|
||||
? () {
|
||||
_controller?.setPage(_currentPage! - 1);
|
||||
}
|
||||
: null,
|
||||
icon: const Icon(Icons.arrow_left),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
IconButton.filled(
|
||||
onPressed: canGoToNextPage
|
||||
? () {
|
||||
_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)
|
||||
Text(
|
||||
"${_currentPage! + 1}/$_totalPages",
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
body: FutureBuilder(
|
||||
future: widget.documentBytes,
|
||||
builder: (context, snapshot) {
|
||||
@@ -93,12 +101,7 @@ class _DocumentViewState extends State<DocumentView> {
|
||||
onViewCreated: (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() {
|
||||
const threshold = 400;
|
||||
const threshold = kToolbarHeight * 2;
|
||||
final offset =
|
||||
_nestedScrollViewKey.currentState!.innerController.position.pixels;
|
||||
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: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/items/document_detailed_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,
|
||||
mainAxisSpacing: 4,
|
||||
crossAxisSpacing: 4,
|
||||
mainAxisExtent: 356,
|
||||
mainAxisExtent: 324,
|
||||
),
|
||||
itemCount: documents.length,
|
||||
itemBuilder: (context, index) {
|
||||
@@ -176,7 +177,7 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||
onStoragePathSelected: onStoragePathSelected,
|
||||
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/repository/label_repository.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/items/document_item.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(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: RichText(
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
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,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
child: DateAndDocumentTypeLabelWidget(
|
||||
document: document,
|
||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||
),
|
||||
),
|
||||
if (document.archiveSerialNumber != null)
|
||||
@@ -143,30 +134,7 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
?.apply(color: Theme.of(context).hintColor),
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 8, 8, 4),
|
||||
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),
|
||||
).paddedLTRB(8, 4, 8, 8),
|
||||
if (highlights != null)
|
||||
Html(
|
||||
data: '<p>${highlights!}</p>',
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.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/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||
@@ -29,111 +30,133 @@ class DocumentGridItem extends DocumentItem {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Card(
|
||||
elevation: 1.0,
|
||||
color: isSelected
|
||||
? Theme.of(context).colorScheme.inversePrimary
|
||||
: Theme.of(context).cardColor,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: _onTap,
|
||||
onLongPress: onSelected != null ? () => onSelected!(document) : null,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: DocumentPreview(
|
||||
documentId: document.id,
|
||||
borderRadius: 12.0,
|
||||
enableHero: enableHeroAnimation,
|
||||
),
|
||||
),
|
||||
Align(
|
||||
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),
|
||||
),
|
||||
],
|
||||
return Stack(
|
||||
children: [
|
||||
Card(
|
||||
elevation: 1.0,
|
||||
color: isSelected
|
||||
? Theme.of(context).colorScheme.inversePrimary
|
||||
: Theme.of(context).cardColor,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: _onTap,
|
||||
onLongPress:
|
||||
onSelected != null ? () => onSelected!(document) : null,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: DocumentPreview(
|
||||
documentId: document.id,
|
||||
borderRadius: 12.0,
|
||||
enableHero: enableHeroAnimation,
|
||||
),
|
||||
),
|
||||
),
|
||||
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: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_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/items/document_item.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||
@@ -31,6 +33,7 @@ class DocumentListItem extends DocumentItem {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labels = context.watch<LabelRepository>().state;
|
||||
|
||||
return ListTile(
|
||||
tileColor: backgroundColor,
|
||||
dense: true,
|
||||
@@ -75,35 +78,11 @@ class DocumentListItem extends DocumentItem {
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: IntrinsicWidth(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: RichText(
|
||||
maxLines: 1,
|
||||
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,
|
||||
),
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: DateAndDocumentTypeLabelWidget(
|
||||
document: document,
|
||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||
),
|
||||
),
|
||||
isThreeLine: document.tags.isNotEmpty,
|
||||
|
||||
@@ -21,15 +21,19 @@ class CorrespondentWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child: GestureDetector(
|
||||
onTap: () => onSelected?.call(correspondent?.id),
|
||||
child: Text(
|
||||
correspondent?.name ?? "-",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style:
|
||||
(textStyle ?? Theme.of(context).textTheme.bodyMedium)?.copyWith(
|
||||
color: textColor ?? Theme.of(context).colorScheme.primary,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
onTap: () => onSelected?.call(correspondent?.id),
|
||||
child: Text(
|
||||
correspondent?.name ?? "-",
|
||||
maxLines: 1,
|
||||
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) {
|
||||
return AbsorbPointer(
|
||||
absorbing: !isClickable,
|
||||
child: GestureDetector(
|
||||
onTap: () => onSelected?.call(documentType?.id),
|
||||
child: Text(
|
||||
documentType?.toString() ?? "-",
|
||||
style: (textStyle ?? Theme.of(context).textTheme.bodyMedium)
|
||||
?.copyWith(color: Theme.of(context).colorScheme.tertiary),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
onTap: () => onSelected?.call(documentType?.id),
|
||||
child: Text(
|
||||
documentType?.toString() ?? "-",
|
||||
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(
|
||||
title: Text(S.of(context)!.appLogs(formattedDate)),
|
||||
title: Text(S
|
||||
.of(context)!
|
||||
.appLogs(formattedDate)), //TODO: CHange to App-Logs in german
|
||||
actions: [
|
||||
if (state is AppLogsStateLoaded)
|
||||
IconButton(
|
||||
|
||||
@@ -84,40 +84,42 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
),
|
||||
),
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
ServerAddressFormField(
|
||||
initialValue: widget.initialServerUrl,
|
||||
onSubmit: (address) {
|
||||
_updateReachability(address);
|
||||
},
|
||||
).padded(),
|
||||
ClientCertificateFormField(
|
||||
initialBytes: widget.initialClientCertificate?.bytes,
|
||||
initialPassphrase: widget.initialClientCertificate?.passphrase,
|
||||
onChanged: (_) => _updateReachability(),
|
||||
).padded(),
|
||||
_buildStatusIndicator(),
|
||||
if (_reachabilityStatus == ReachabilityStatus.reachable) ...[
|
||||
UserCredentialsFormField(
|
||||
formKey: _formKey,
|
||||
initialUsername: widget.initialUsername,
|
||||
initialPassword: widget.initialPassword,
|
||||
onFieldsSubmitted: _onSubmit,
|
||||
),
|
||||
Text(
|
||||
S.of(context)!.loginRequiredPermissionsHint,
|
||||
style: Theme.of(context).textTheme.bodySmall?.apply(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
).padded(16),
|
||||
]
|
||||
],
|
||||
body: AutofillGroup(
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
ServerAddressFormField(
|
||||
initialValue: widget.initialServerUrl,
|
||||
onSubmit: (address) {
|
||||
_updateReachability(address);
|
||||
},
|
||||
).padded(),
|
||||
ClientCertificateFormField(
|
||||
initialBytes: widget.initialClientCertificate?.bytes,
|
||||
initialPassphrase: widget.initialClientCertificate?.passphrase,
|
||||
onChanged: (_) => _updateReachability(),
|
||||
).padded(),
|
||||
_buildStatusIndicator(),
|
||||
if (_reachabilityStatus == ReachabilityStatus.reachable) ...[
|
||||
UserCredentialsFormField(
|
||||
formKey: _formKey,
|
||||
initialUsername: widget.initialUsername,
|
||||
initialPassword: widget.initialPassword,
|
||||
onFieldsSubmitted: _onSubmit,
|
||||
),
|
||||
Text(
|
||||
S.of(context)!.loginRequiredPermissionsHint,
|
||||
style: Theme.of(context).textTheme.bodySmall?.apply(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
).padded(16),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -41,65 +41,62 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
username: widget.initialUsername,
|
||||
),
|
||||
name: UserCredentialsFormField.fkCredentials,
|
||||
builder: (field) => AutofillGroup(
|
||||
child: Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
key: const ValueKey('login-username'),
|
||||
focusNode: _usernameFocusNode,
|
||||
textCapitalization: TextCapitalization.none,
|
||||
textInputAction: TextInputAction.next,
|
||||
onFieldSubmitted: (value) {
|
||||
_passwordFocusNode.requestFocus();
|
||||
},
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
autocorrect: false,
|
||||
onChanged: (username) => field.didChange(
|
||||
field.value?.copyWith(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),
|
||||
),
|
||||
builder: (field) => Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
key: const ValueKey('login-username'),
|
||||
focusNode: _usernameFocusNode,
|
||||
textCapitalization: TextCapitalization.none,
|
||||
textInputAction: TextInputAction.next,
|
||||
onFieldSubmitted: (value) {
|
||||
_passwordFocusNode.requestFocus();
|
||||
},
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
autocorrect: false,
|
||||
onChanged: (username) => field.didChange(
|
||||
field.value?.copyWith(username: username) ??
|
||||
LoginFormCredentials(username: username),
|
||||
),
|
||||
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;
|
||||
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;
|
||||
},
|
||||
}
|
||||
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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
@@ -21,11 +22,12 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
|
||||
Future<void> loadMore() async {
|
||||
final hasConnection =
|
||||
await connectivityStatusService.isConnectedToInternet();
|
||||
if (state.isLastPageLoaded || !hasConnection) {
|
||||
if (state.isLastPageLoaded || !hasConnection || state.isLoading) {
|
||||
return;
|
||||
}
|
||||
emit(state.copyWithPaged(isLoading: true));
|
||||
final newFilter = state.filter.copyWith(page: state.filter.page + 1);
|
||||
debugPrint("Fetching page ${newFilter.page}");
|
||||
try {
|
||||
final result = await api.findAll(newFilter);
|
||||
emit(
|
||||
@@ -217,7 +219,6 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
notifier.removeListener(this);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.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/changelogs_tile.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/default_download_file_type_setting.dart';
|
||||
@@ -37,6 +39,9 @@ class SettingsPage extends StatelessWidget {
|
||||
const SkipDocumentPreprationOnShareSetting(),
|
||||
_buildSectionHeader(context, S.of(context)!.storage),
|
||||
const ClearCacheSetting(),
|
||||
_buildSectionHeader(context, S.of(context)!.misc),
|
||||
const AppLogsTile(),
|
||||
const ChangelogsTile(),
|
||||
],
|
||||
),
|
||||
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),
|
||||
'tr': LanguageOption('Türkçe', true),
|
||||
'pl': LanguageOption('Polska', true),
|
||||
'ca': LanguageOption('Catalan', true),
|
||||
'ca': LanguageOption('Català', true),
|
||||
'ru': LanguageOption('Русский', true),
|
||||
};
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
||||
final int documentId;
|
||||
@override
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
|
||||
@override
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
@@ -33,19 +34,9 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
||||
onDeleted: remove,
|
||||
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 {
|
||||
if (!state.hasLoaded) {
|
||||
await updateFilter(
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
part of 'similar_documents_cubit.dart';
|
||||
|
||||
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({
|
||||
required super.filter,
|
||||
super.hasLoaded,
|
||||
super.isLoading,
|
||||
super.value,
|
||||
this.correspondents = const {},
|
||||
this.documentTypes = const {},
|
||||
this.tags = const {},
|
||||
this.storagePaths = const {},
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -23,10 +14,6 @@ class SimilarDocumentsState extends DocumentPagingState {
|
||||
hasLoaded,
|
||||
isLoading,
|
||||
value,
|
||||
correspondents,
|
||||
documentTypes,
|
||||
tags,
|
||||
storagePaths,
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -49,20 +36,12 @@ class SimilarDocumentsState extends DocumentPagingState {
|
||||
bool? isLoading,
|
||||
List<PagedSearchResult<DocumentModel>>? value,
|
||||
DocumentFilter? filter,
|
||||
Map<int, Correspondent>? correspondents,
|
||||
Map<int, DocumentType>? documentTypes,
|
||||
Map<int, Tag>? tags,
|
||||
Map<int, StoragePath>? storagePaths,
|
||||
}) {
|
||||
return SimilarDocumentsState(
|
||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
value: value ?? this.value,
|
||||
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": {
|
||||
"description": "Warning message shown when the user tries to close a route without saving the changes."
|
||||
},
|
||||
"changelog": "Changelog",
|
||||
"noLogsFoundOn": "No logs found on {date}.",
|
||||
"logfileBottomReached": "You have reached the bottom of this logfile.",
|
||||
"appLogs": "App logs {date}",
|
||||
"saveLogsToFile": "Save logs to file",
|
||||
"copyToClipboard": "Copy to clipboard",
|
||||
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
||||
"loadingLogsFrom": "Loading logs from {date}...",
|
||||
"clearLogs": "Clear logs from {date}"
|
||||
"changelog": "Historial de canvis",
|
||||
"noLogsFoundOn": "Sense logs trovats per {date}.",
|
||||
"logfileBottomReached": "Final d'aquest arxiu de registres.",
|
||||
"appLogs": "Logs d'aplicació {date}",
|
||||
"saveLogsToFile": "Desar registres a arxiu",
|
||||
"copyToClipboard": "Copia al porta-retalls",
|
||||
"couldNotLoadLogfileFrom": "No es pot carregar log desde {date}.",
|
||||
"loadingLogsFrom": "Carregant registres des de {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",
|
||||
"couldNotLoadLogfileFrom": "Could not load logfile 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",
|
||||
"couldNotLoadLogfileFrom": "Logs vom {date} konnten nicht geladen werden.",
|
||||
"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",
|
||||
"couldNotLoadLogfileFrom": "Could not load logfile 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": {
|
||||
"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": {
|
||||
"description": "Message shown when the app loads user data from the server"
|
||||
@@ -993,21 +993,31 @@
|
||||
"@restoringSession": {
|
||||
"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": {
|
||||
"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": {
|
||||
"description": "Warning message shown when the user tries to close a route without saving the changes."
|
||||
},
|
||||
"changelog": "Changelog",
|
||||
"noLogsFoundOn": "No logs found on {date}.",
|
||||
"logfileBottomReached": "You have reached the bottom of this logfile.",
|
||||
"appLogs": "App logs {date}",
|
||||
"saveLogsToFile": "Save logs to file",
|
||||
"copyToClipboard": "Copy to clipboard",
|
||||
"couldNotLoadLogfileFrom": "Could not load logfile from {date}.",
|
||||
"loadingLogsFrom": "Loading logs from {date}...",
|
||||
"clearLogs": "Clear logs from {date}"
|
||||
"noLogsFoundOn": "No se encontraron registros en {date}.",
|
||||
"logfileBottomReached": "Has alcanzado el final del archivo de registro.",
|
||||
"appLogs": "Registros de la aplicación {date}",
|
||||
"saveLogsToFile": "Guardar registros en un archivo",
|
||||
"copyToClipboard": "Copiar al portapapeles",
|
||||
"couldNotLoadLogfileFrom": "No se pudo cargar el archivo de registro desde {date}.",
|
||||
"loadingLogsFrom": "Cargando registros desde {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",
|
||||
"couldNotLoadLogfileFrom": "Could not load logfile 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",
|
||||
"couldNotLoadLogfileFrom": "Could not load logfile 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",
|
||||
"couldNotLoadLogfileFrom": "Could not load logfile 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",
|
||||
"couldNotLoadLogfileFrom": "Could not load logfile 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: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/routes.dart';
|
||||
|
||||
@@ -15,10 +16,10 @@ class LoggingOutRoute extends GoRouteData {
|
||||
|
||||
@override
|
||||
Page<void> buildPage(BuildContext context, GoRouterState state) {
|
||||
return const NoTransitionPage(
|
||||
return NoTransitionPage(
|
||||
child: Scaffold(
|
||||
body: Center(
|
||||
child: Text("Logging out..."), //TODO: INTL
|
||||
child: Text(S.of(context)!.loggingOut),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user