mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-09 18:07:50 -06:00
fix: Enable logging in production
This commit is contained in:
@@ -4,9 +4,8 @@ import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/logging/view/app_logs_page.dart';
|
||||
import 'package:paperless_mobile/core/widgets/paperless_logo.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/logging/view/app_logs_page.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/sharing/cubit/receive_share_cubit.dart';
|
||||
@@ -38,10 +37,10 @@ class AppDrawer extends StatelessWidget {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const PaperlessLogo.green(
|
||||
width: 32,
|
||||
height: 32,
|
||||
),
|
||||
const $AssetsLogosGen()
|
||||
.paperlessLogoGreenSvg
|
||||
.svg(width: 32, height: 32),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
"Paperless Mobile",
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/theme.dart';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_fields/fullscreen_selection_form.dart';
|
||||
import 'package:paperless_mobile/extensions/dart_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/dart_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_bulk_action/view/widgets/confirm_bulk_modify_label_dialog.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_fields/fullscreen_selection_form.dart';
|
||||
import 'package:paperless_mobile/extensions/dart_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/dart_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bulk_action_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_bulk_action/view/widgets/confirm_bulk_modify_tags_dialog.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
@@ -30,10 +30,12 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
this._notifier,
|
||||
this._notificationService, {
|
||||
required DocumentModel initialDocument,
|
||||
}) : super(DocumentDetailsState(
|
||||
document: initialDocument,
|
||||
)) {
|
||||
_notifier.addListener(this, onUpdated: replace);
|
||||
}) : super(DocumentDetailsState(document: initialDocument)) {
|
||||
_notifier.addListener(this, onUpdated: (document) {
|
||||
if (document.id == state.document.id) {
|
||||
replace(document);
|
||||
}
|
||||
});
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) => emit(
|
||||
@@ -127,7 +129,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
if (!await File(targetPath).exists()) {
|
||||
await File(targetPath).create();
|
||||
} else {
|
||||
await _notificationService.notifyFileDownload(
|
||||
await _notificationService.notifyDocumentDownload(
|
||||
document: state.document,
|
||||
filename: p.basename(targetPath),
|
||||
filePath: targetPath,
|
||||
@@ -151,7 +153,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
targetPath,
|
||||
original: downloadOriginal,
|
||||
onProgressChanged: (progress) {
|
||||
_notificationService.notifyFileDownload(
|
||||
_notificationService.notifyDocumentDownload(
|
||||
document: state.document,
|
||||
filename: p.basename(targetPath),
|
||||
filePath: targetPath,
|
||||
@@ -162,7 +164,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
);
|
||||
},
|
||||
);
|
||||
await _notificationService.notifyFileDownload(
|
||||
await _notificationService.notifyDocumentDownload(
|
||||
document: state.document,
|
||||
filename: p.basename(targetPath),
|
||||
filePath: targetPath,
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/widgets/material/colored_tab_bar.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_content_widget.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart';
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
||||
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/dialogs/select_file_type_dialog.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.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/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/archive_serial_number_field.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:paperless_api/paperless_api.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/widgets/highlighted_text.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
|
||||
|
||||
@@ -4,9 +4,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/dialogs/select_file_type_dialog.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/file_download_type.dart';
|
||||
|
||||
@@ -22,7 +22,11 @@ class DocumentEditCubit extends Cubit<DocumentEditState> {
|
||||
required DocumentModel document,
|
||||
}) : _initialDocument = document,
|
||||
super(DocumentEditState(document: document)) {
|
||||
_notifier.addListener(this, onUpdated: replace);
|
||||
_notifier.addListener(this, onUpdated: (doc) {
|
||||
if (doc.id == document.id) {
|
||||
emit(state.copyWith(document: doc));
|
||||
}
|
||||
});
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) {
|
||||
@@ -69,10 +73,6 @@ class DocumentEditCubit extends Cubit<DocumentEditState> {
|
||||
emit(state.copyWith(suggestions: suggestions));
|
||||
}
|
||||
|
||||
void replace(DocumentModel document) {
|
||||
emit(state.copyWith(document: document));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_notifier.removeListener(this);
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
@@ -12,8 +11,9 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.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/extensions/flutter_extensions.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/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
@@ -21,7 +21,6 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/labels_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/shells/authenticated_route.dart';
|
||||
import 'package:paperless_mobile/theme.dart';
|
||||
|
||||
class DocumentEditPage extends StatefulWidget {
|
||||
const DocumentEditPage({
|
||||
@@ -401,6 +400,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
name: fkTitle,
|
||||
decoration: InputDecoration(
|
||||
label: Text(S.of(context)!.title),
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
_formKey.currentState?.fields[fkTitle]?.didChange(null);
|
||||
},
|
||||
),
|
||||
),
|
||||
initialValue: initialTitle,
|
||||
);
|
||||
@@ -408,6 +413,15 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
|
||||
Widget _buildCreatedAtFormField(
|
||||
DateTime? initialCreatedAtDate, FieldSuggestions? filteredSuggestions) {
|
||||
// return FormBuilderLocalizedDatePicker(
|
||||
// name: fkCreatedDate,
|
||||
// initialValue: initialCreatedAtDate,
|
||||
// labelText: S.of(context)!.createdAt,
|
||||
// firstDate: DateTime(1970, 1, 1),
|
||||
// lastDate: DateTime.now(),
|
||||
// locale: Localizations.localeOf(context),
|
||||
// prefixIcon: Icon(Icons.calendar_today),
|
||||
// );
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:flutter/foundation.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/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'dart:math' as math;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/remove_history_entry_dialog.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/document_search_bar.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/manage_accounts_page.dart';
|
||||
|
||||
@@ -9,13 +9,13 @@ import 'package:hive/hive.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
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/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/widgets/future_or_builder.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/core/extensions/document_iterable_extensions.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
@@ -44,18 +45,15 @@ class DocumentsCubit extends Cubit<DocumentsState>
|
||||
replace(document);
|
||||
emit(
|
||||
state.copyWith(
|
||||
selection: state.selection
|
||||
.map((e) => e.id == document.id ? document : e)
|
||||
.toList(),
|
||||
),
|
||||
selection:
|
||||
state.selection.withDocumentreplaced(document).toList()),
|
||||
);
|
||||
},
|
||||
onDeleted: (document) {
|
||||
remove(document);
|
||||
emit(
|
||||
state.copyWith(
|
||||
selection:
|
||||
state.selection.where((e) => e.id != document.id).toList(),
|
||||
selection: state.selection.withDocumentRemoved(document).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
||||
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
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/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.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';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/tags_placeholder.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/text_placeholder.dart';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
|
||||
class TagsPlaceholder extends StatelessWidget {
|
||||
static const _lengths = <double>[90, 70, 130];
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/saved_views_route.dart';
|
||||
import 'package:paperless_mobile/routes/typed/shells/authenticated_route.dart';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/saved_views/saved_view_chip.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/translation/sort_field_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class SortFieldSelectionBottomSheet extends StatefulWidget {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/selection/bulk_delete_confirmation_dialog.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'dart:async';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository_state.dart';
|
||||
|
||||
@@ -9,8 +9,8 @@ import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart';
|
||||
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart';
|
||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||
import 'package:paperless_mobile/extensions/dart_extensions.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/dart_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
||||
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/placeholder/tags_placeholder.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/routes/typed/branches/labels_route.dart';
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/fullscreen_tags_form.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
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/delegate/customizable_sliver_persistent_header_delegate.dart';
|
||||
import 'package:paperless_mobile/core/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/core/widgets/material/colored_tab_bar.dart';
|
||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class FullscreenLabelForm<T extends Label> extends StatefulWidget {
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/fullscreen_label_form.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/translation/matching_algorithm_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
|
||||
class LabelTabView<T extends Label> extends StatelessWidget {
|
||||
final Map<int, T> labels;
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
|
||||
import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart';
|
||||
import 'package:paperless_mobile/features/landing/view/widgets/expansion_card.dart';
|
||||
|
||||
119
lib/features/logging/cubit/app_logs_cubit.dart
Normal file
119
lib/features/logging/cubit/app_logs_cubit.dart
Normal file
@@ -0,0 +1,119 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_mobile/features/logging/models/parsed_log_message.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
part 'app_logs_state.dart';
|
||||
|
||||
final _fileNameFormat = DateFormat("yyyy-MM-dd");
|
||||
|
||||
class AppLogsCubit extends Cubit<AppLogsState> {
|
||||
StreamSubscription? _fileChangesSubscription;
|
||||
final LocalNotificationService _localNotificationService;
|
||||
AppLogsCubit(
|
||||
DateTime date,
|
||||
this._localNotificationService,
|
||||
) : super(AppLogsStateInitial(date: date));
|
||||
|
||||
Future<void> loadLogs(DateTime date) async {
|
||||
if (date == state.date) {
|
||||
return;
|
||||
}
|
||||
_fileChangesSubscription?.cancel();
|
||||
emit(AppLogsStateLoading(date: date));
|
||||
final logDir = FileService.instance.logDirectory;
|
||||
final availableLogs = (await logDir
|
||||
.list()
|
||||
.whereType<File>()
|
||||
.where((event) => event.path.endsWith('.log'))
|
||||
.map((e) =>
|
||||
_fileNameFormat.parse(p.basenameWithoutExtension(e.path)))
|
||||
.toList())
|
||||
.sorted();
|
||||
final logFile = _getLogfile(date);
|
||||
if (!await logFile.exists()) {
|
||||
emit(AppLogsStateLoaded(
|
||||
date: date,
|
||||
logs: [],
|
||||
availableLogs: availableLogs,
|
||||
));
|
||||
}
|
||||
try {
|
||||
_updateLogsFromFile(logFile, date, availableLogs);
|
||||
_fileChangesSubscription = logFile.watch().listen((event) async {
|
||||
if (!isClosed) {
|
||||
_updateLogsFromFile(logFile, date, availableLogs);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
emit(AppLogsStateError(
|
||||
error: e,
|
||||
date: date,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _updateLogsFromFile(
|
||||
File file, DateTime date, List<DateTime> availableLogs) async {
|
||||
final logs = await file.readAsLines();
|
||||
final parsedLogs = ParsedLogMessage.parse(logs).reversed.toList();
|
||||
emit(AppLogsStateLoaded(
|
||||
date: date,
|
||||
logs: parsedLogs,
|
||||
availableLogs: availableLogs,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> clearLogs(DateTime date) async {
|
||||
final logFile = _getLogfile(date);
|
||||
await logFile.writeAsString('');
|
||||
await loadLogs(date);
|
||||
}
|
||||
|
||||
Future<void> copyToClipboard(DateTime date) async {
|
||||
final file = _getLogfile(date);
|
||||
if (!await file.exists()) {
|
||||
return;
|
||||
}
|
||||
final content = await file.readAsString();
|
||||
Clipboard.setData(ClipboardData(text: content));
|
||||
}
|
||||
|
||||
Future<void> saveLogs(DateTime date, String locale) async {
|
||||
var formattedDate = _fileNameFormat.format(date);
|
||||
final filename = 'paperless_mobile_logs_$formattedDate.log';
|
||||
// final parentDir = await FilePicker.platform.getDirectoryPath(
|
||||
// dialogTitle: "Save log from ${DateFormat.yMd(locale).format(date)}",
|
||||
// initialDirectory: Platform.isAndroid
|
||||
// ? FileService.instance.downloadsDirectory.path
|
||||
// : null,
|
||||
// );
|
||||
// if (parentDir == null) {
|
||||
// return;
|
||||
// }
|
||||
final logFile = _getLogfile(date);
|
||||
final parentDir = FileService.instance.downloadsDirectory;
|
||||
final downloadedFile = await logFile.copy(p.join(parentDir.path, filename));
|
||||
_localNotificationService.notifyFileDownload(filePath: downloadedFile.path);
|
||||
}
|
||||
|
||||
File _getLogfile(DateTime date) {
|
||||
return File(p.join(FileService.instance.logDirectory.path,
|
||||
'${_fileNameFormat.format(date)}.log'));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _fileChangesSubscription?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
33
lib/features/logging/cubit/app_logs_state.dart
Normal file
33
lib/features/logging/cubit/app_logs_state.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
part of 'app_logs_cubit.dart';
|
||||
|
||||
sealed class AppLogsState {
|
||||
final DateTime date;
|
||||
const AppLogsState({required this.date});
|
||||
}
|
||||
|
||||
class AppLogsStateInitial extends AppLogsState {
|
||||
const AppLogsStateInitial({required super.date});
|
||||
}
|
||||
|
||||
class AppLogsStateLoading extends AppLogsState {
|
||||
const AppLogsStateLoading({required super.date});
|
||||
}
|
||||
|
||||
class AppLogsStateLoaded extends AppLogsState {
|
||||
const AppLogsStateLoaded({
|
||||
required super.date,
|
||||
required this.logs,
|
||||
required this.availableLogs,
|
||||
});
|
||||
final List<DateTime> availableLogs;
|
||||
final List<ParsedLogMessage> logs;
|
||||
}
|
||||
|
||||
class AppLogsStateError extends AppLogsState {
|
||||
const AppLogsStateError({
|
||||
required this.error,
|
||||
required super.date,
|
||||
});
|
||||
|
||||
final Object error;
|
||||
}
|
||||
45
lib/features/logging/data/formatted_printer.dart
Normal file
45
lib/features/logging/data/formatted_printer.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:paperless_mobile/features/logging/models/formatted_log_message.dart';
|
||||
|
||||
class FormattedPrinter extends LogPrinter {
|
||||
static final _timestampFormat = DateFormat("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
static const _mulitlineObjectEncoder = JsonEncoder.withIndent(null);
|
||||
|
||||
@override
|
||||
List<String> log(LogEvent event) {
|
||||
final unformattedMessage = event.message;
|
||||
final formattedMessage = switch (unformattedMessage) {
|
||||
FormattedLogMessage m => m.format(),
|
||||
Iterable i => _mulitlineObjectEncoder
|
||||
.convert(i)
|
||||
.padLeft(FormattedLogMessage.maxLength),
|
||||
Map m => _mulitlineObjectEncoder
|
||||
.convert(m)
|
||||
.padLeft(FormattedLogMessage.maxLength),
|
||||
_ => unformattedMessage.toString().padLeft(FormattedLogMessage.maxLength),
|
||||
};
|
||||
final formattedLevel = event.level.name
|
||||
.toUpperCase()
|
||||
.padRight(Level.values.map((e) => e.name.length).max);
|
||||
final formattedTimestamp = _timestampFormat.format(event.time);
|
||||
|
||||
return [
|
||||
'$formattedTimestamp\t$formattedLevel --- $formattedMessage',
|
||||
if (event.error != null) ...[
|
||||
"---BEGIN ERROR---",
|
||||
event.error.toString(),
|
||||
"---END ERROR---",
|
||||
],
|
||||
if (event.stackTrace != null) ...[
|
||||
"---BEGIN STACKTRACE---",
|
||||
event.stackTrace.toString(),
|
||||
"---END STACKTRACE---"
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
116
lib/features/logging/data/logger.dart
Normal file
116
lib/features/logging/data/logger.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:paperless_mobile/features/logging/models/formatted_log_message.dart';
|
||||
|
||||
late Logger logger;
|
||||
|
||||
extension FormattedLoggerExtension on Logger {
|
||||
void ft(
|
||||
dynamic message, {
|
||||
String className = '',
|
||||
String methodName = '',
|
||||
DateTime? time,
|
||||
Object? error,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
final formattedMessage = FormattedLogMessage(
|
||||
message,
|
||||
className: className,
|
||||
methodName: methodName,
|
||||
);
|
||||
log(
|
||||
Level.trace,
|
||||
formattedMessage,
|
||||
time: time,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
void fw(
|
||||
dynamic message, {
|
||||
String className = '',
|
||||
String methodName = '',
|
||||
DateTime? time,
|
||||
Object? error,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
final formattedMessage = FormattedLogMessage(
|
||||
message,
|
||||
className: className,
|
||||
methodName: methodName,
|
||||
);
|
||||
log(
|
||||
Level.warning,
|
||||
formattedMessage,
|
||||
time: time,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
void fd(
|
||||
dynamic message, {
|
||||
String className = '',
|
||||
String methodName = '',
|
||||
DateTime? time,
|
||||
Object? error,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
final formattedMessage = FormattedLogMessage(
|
||||
message,
|
||||
className: className,
|
||||
methodName: methodName,
|
||||
);
|
||||
log(
|
||||
Level.debug,
|
||||
formattedMessage,
|
||||
time: time,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
void fi(
|
||||
dynamic message, {
|
||||
String className = '',
|
||||
String methodName = '',
|
||||
DateTime? time,
|
||||
Object? error,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
final formattedMessage = FormattedLogMessage(
|
||||
message,
|
||||
className: className,
|
||||
methodName: methodName,
|
||||
);
|
||||
log(
|
||||
Level.info,
|
||||
formattedMessage,
|
||||
time: time,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}
|
||||
|
||||
void fe(
|
||||
dynamic message, {
|
||||
String className = '',
|
||||
String methodName = '',
|
||||
DateTime? time,
|
||||
Object? error,
|
||||
StackTrace? stackTrace,
|
||||
}) {
|
||||
final formattedMessage = FormattedLogMessage(
|
||||
message,
|
||||
className: className,
|
||||
methodName: methodName,
|
||||
);
|
||||
log(
|
||||
Level.error,
|
||||
formattedMessage,
|
||||
time: time,
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}
|
||||
}
|
||||
52
lib/features/logging/data/mirrored_file_output.dart
Normal file
52
lib/features/logging/data/mirrored_file_output.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
|
||||
typedef f = FileOutput;
|
||||
|
||||
class MirroredFileOutput extends LogOutput {
|
||||
var lock = Lock();
|
||||
MirroredFileOutput();
|
||||
|
||||
late final File file;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
final today = DateFormat("yyyy-MM-dd").format(DateTime.now());
|
||||
final logDir = FileService.instance.logDirectory;
|
||||
file = File(p.join(logDir.path, '$today.log'));
|
||||
debugPrint("Logging files to ${file.path}.");
|
||||
try {
|
||||
final oldLogs = await FileService.instance.getAllFiles(logDir);
|
||||
if (oldLogs.length > 10) {
|
||||
oldLogs
|
||||
.sortedBy((file) => file.lastModifiedSync())
|
||||
.reversed
|
||||
.skip(10)
|
||||
.forEach((log) => log.delete());
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("Failed to delete old logs...");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void output(OutputEvent event) async {
|
||||
await lock.synchronized(() async {
|
||||
for (var line in event.lines) {
|
||||
debugPrint(line);
|
||||
await file.writeAsString(
|
||||
"$line${Platform.lineTerminator}",
|
||||
mode: FileMode.append,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
19
lib/features/logging/models/formatted_log_message.dart
Normal file
19
lib/features/logging/models/formatted_log_message.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
/// Class passed to the printer to be formatted and printed.
|
||||
class FormattedLogMessage {
|
||||
static const maxLength = 55;
|
||||
final String message;
|
||||
final String methodName;
|
||||
final String className;
|
||||
|
||||
FormattedLogMessage(
|
||||
this.message, {
|
||||
required this.methodName,
|
||||
required this.className,
|
||||
});
|
||||
|
||||
String format() {
|
||||
final formattedClassName = className.padLeft(25);
|
||||
final formattedMethodName = methodName.padRight(25);
|
||||
return '[$formattedClassName] - $formattedMethodName: $message';
|
||||
}
|
||||
}
|
||||
149
lib/features/logging/models/parsed_log_message.dart
Normal file
149
lib/features/logging/models/parsed_log_message.dart
Normal file
@@ -0,0 +1,149 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:logger/logger.dart';
|
||||
|
||||
final _newLine = Platform.lineTerminator;
|
||||
|
||||
sealed class ParsedLogMessage {
|
||||
static List<ParsedLogMessage> parse(List<String> logs) {
|
||||
List<ParsedLogMessage> messages = [];
|
||||
int offset = 0;
|
||||
while (offset < logs.length) {
|
||||
final currentLine = logs[offset];
|
||||
if (ParsedFormattedLogMessage.canConsumeFirstLine(currentLine)) {
|
||||
final (consumedLines, result) =
|
||||
ParsedFormattedLogMessage.consume(logs.sublist(offset));
|
||||
messages.add(result);
|
||||
offset += consumedLines;
|
||||
} else {
|
||||
messages.add(UnformattedLogMessage(currentLine));
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
class ParsedErrorLogMessage {
|
||||
static final RegExp _errorBeginPattern = RegExp(r"---BEGIN ERROR---\s*");
|
||||
static final RegExp _errorEndPattern = RegExp(r"---END ERROR---\s*");
|
||||
static final RegExp _stackTraceBeginPattern =
|
||||
RegExp(r"---BEGIN STACKTRACE---\s*");
|
||||
static final RegExp _stackTraceEndPattern =
|
||||
RegExp(r"---END STACKTRACE---\s*");
|
||||
final String error;
|
||||
final String? stackTrace;
|
||||
ParsedErrorLogMessage({
|
||||
required this.error,
|
||||
this.stackTrace,
|
||||
});
|
||||
static bool canConsumeFirstLine(String line) =>
|
||||
_errorBeginPattern.hasMatch(line);
|
||||
|
||||
static (int consumedLines, ParsedErrorLogMessage? result) consume(
|
||||
List<String> log) {
|
||||
assert(log.isNotEmpty && canConsumeFirstLine(log.first));
|
||||
String errorText = "";
|
||||
int currentLine =
|
||||
1; // Skip first because we know that the first line is ---BEGIN ERROR---
|
||||
|
||||
while (!_errorEndPattern.hasMatch(log[currentLine])) {
|
||||
errorText += log[currentLine] + _newLine;
|
||||
currentLine++;
|
||||
}
|
||||
currentLine++;
|
||||
final hasStackTrace = _stackTraceBeginPattern.hasMatch(log[currentLine]);
|
||||
String? stackTrace;
|
||||
if (hasStackTrace) {
|
||||
currentLine++;
|
||||
String stackTraceText = '';
|
||||
|
||||
while (!_stackTraceEndPattern.hasMatch(log[currentLine])) {
|
||||
stackTraceText += log[currentLine] + _newLine;
|
||||
currentLine++;
|
||||
}
|
||||
stackTrace = stackTraceText;
|
||||
}
|
||||
return (
|
||||
currentLine + 1,
|
||||
ParsedErrorLogMessage(error: errorText, stackTrace: stackTrace)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UnformattedLogMessage extends ParsedLogMessage {
|
||||
final String message;
|
||||
|
||||
UnformattedLogMessage(this.message);
|
||||
}
|
||||
|
||||
class ParsedFormattedLogMessage extends ParsedLogMessage {
|
||||
static final RegExp pattern = RegExp(
|
||||
r'(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s*(?<level>[A-Z]*)'
|
||||
r'\s*---\s*(?:\[\s*(?<className>.*)\]\s*-\s*(?<methodName>.*)\s*)?:\s*(?<message>.+)',
|
||||
);
|
||||
|
||||
final Level level;
|
||||
final String message;
|
||||
final String? className;
|
||||
final String? methodName;
|
||||
final DateTime timestamp;
|
||||
|
||||
final ParsedErrorLogMessage? error;
|
||||
|
||||
ParsedFormattedLogMessage({
|
||||
required this.level,
|
||||
required this.message,
|
||||
this.className,
|
||||
this.methodName,
|
||||
required this.timestamp,
|
||||
this.error,
|
||||
});
|
||||
|
||||
static bool canConsumeFirstLine(String line) => pattern.hasMatch(line);
|
||||
|
||||
static (int consumedLines, ParsedFormattedLogMessage result) consume(
|
||||
List<String> log) {
|
||||
assert(log.isNotEmpty && canConsumeFirstLine(log.first));
|
||||
|
||||
final match = pattern.firstMatch(log.first)!;
|
||||
final result = ParsedFormattedLogMessage(
|
||||
level: Level.values.byName(match.namedGroup('level')!.toLowerCase()),
|
||||
message: match.namedGroup('message')!,
|
||||
className: match.namedGroup('className'),
|
||||
methodName: match.namedGroup('methodName'),
|
||||
timestamp: DateTime.parse(match.namedGroup('timestamp')!),
|
||||
);
|
||||
final updatedLog = log.sublist(1);
|
||||
if (updatedLog.isEmpty) {
|
||||
return (1, result);
|
||||
}
|
||||
if (ParsedErrorLogMessage.canConsumeFirstLine(updatedLog.first)) {
|
||||
final (consumedLines, parsedError) =
|
||||
ParsedErrorLogMessage.consume(updatedLog);
|
||||
return (
|
||||
consumedLines + 1,
|
||||
result.copyWith(error: parsedError),
|
||||
);
|
||||
}
|
||||
return (1, result);
|
||||
}
|
||||
|
||||
ParsedFormattedLogMessage copyWith({
|
||||
Level? level,
|
||||
String? message,
|
||||
String? className,
|
||||
String? methodName,
|
||||
DateTime? timestamp,
|
||||
ParsedErrorLogMessage? error,
|
||||
}) {
|
||||
return ParsedFormattedLogMessage(
|
||||
level: level ?? this.level,
|
||||
message: message ?? this.message,
|
||||
className: className ?? this.className,
|
||||
methodName: methodName ?? this.methodName,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
error: error ?? this.error,
|
||||
);
|
||||
}
|
||||
}
|
||||
22
lib/features/logging/utils/redaction_utils.dart
Normal file
22
lib/features/logging/utils/redaction_utils.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
(String username, String obscuredUrl) splitRedactUserId(String userId) {
|
||||
final parts = userId.split('@');
|
||||
if (parts.length != 2) {
|
||||
return ('unknown', 'unknown');
|
||||
}
|
||||
|
||||
final username = parts.first;
|
||||
final serverUrl = parts.last;
|
||||
final uri = Uri.parse(serverUrl);
|
||||
final hostLen = uri.host.length;
|
||||
final obscuredUrl = uri.scheme +
|
||||
"://" +
|
||||
uri.host.substring(0, 2) +
|
||||
List.filled(hostLen - 4, '*').join() +
|
||||
uri.host.substring(uri.host.length - 2, uri.host.length);
|
||||
return (username, obscuredUrl);
|
||||
}
|
||||
|
||||
String redactUserId(String userId) {
|
||||
final (username, obscuredUrl) = splitRedactUserId(userId);
|
||||
return '$username@$obscuredUrl';
|
||||
}
|
||||
282
lib/features/logging/view/app_logs_page.dart
Normal file
282
lib/features/logging/view/app_logs_page.dart
Normal file
@@ -0,0 +1,282 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:logger/logger.dart';
|
||||
import 'package:paperless_mobile/features/logging/cubit/app_logs_cubit.dart';
|
||||
import 'package:paperless_mobile/features/logging/models/parsed_log_message.dart';
|
||||
import 'package:paperless_mobile/core/extensions/dart_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class AppLogsPage extends StatefulWidget {
|
||||
const AppLogsPage({super.key});
|
||||
|
||||
@override
|
||||
State<AppLogsPage> createState() => _AppLogsPageState();
|
||||
}
|
||||
|
||||
class _AppLogsPageState extends State<AppLogsPage> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
bool autoScroll = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final locale = Localizations.localeOf(context).toString();
|
||||
final theme = Theme.of(context);
|
||||
return BlocBuilder<AppLogsCubit, AppLogsState>(
|
||||
builder: (context, state) {
|
||||
final formattedDate = DateFormat.yMMMd(locale).format(state.date);
|
||||
return Scaffold(
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: switch (state) {
|
||||
AppLogsStateInitial() => [],
|
||||
AppLogsStateLoading() => [],
|
||||
AppLogsStateLoaded() => [
|
||||
IconButton(
|
||||
tooltip: S.of(context)!.copyToClipboard,
|
||||
onPressed: () {
|
||||
context
|
||||
.read<AppLogsCubit>()
|
||||
.copyToClipboard(state.date);
|
||||
},
|
||||
icon: const Icon(Icons.copy),
|
||||
).padded(),
|
||||
IconButton(
|
||||
tooltip: S.of(context)!.saveLogsToFile,
|
||||
onPressed: () {
|
||||
context
|
||||
.read<AppLogsCubit>()
|
||||
.saveLogs(state.date, locale);
|
||||
},
|
||||
icon: const Icon(Icons.download),
|
||||
).padded(),
|
||||
IconButton(
|
||||
tooltip: S.of(context)!.clearLogs(formattedDate),
|
||||
onPressed: () {
|
||||
context.read<AppLogsCubit>().clearLogs(state.date);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.delete_sweep,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
).padded(),
|
||||
],
|
||||
_ => [],
|
||||
},
|
||||
),
|
||||
),
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context)!.appLogs(formattedDate)),
|
||||
actions: [
|
||||
if (state is AppLogsStateLoaded)
|
||||
IconButton(
|
||||
tooltip: MaterialLocalizations.of(context).datePickerHelpText,
|
||||
onPressed: () async {
|
||||
final selectedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: state.date,
|
||||
firstDate: state.availableLogs.first,
|
||||
lastDate: state.availableLogs.last,
|
||||
selectableDayPredicate: (day) => state.availableLogs
|
||||
.any((date) => day.isOnSameDayAs(date)),
|
||||
initialEntryMode: DatePickerEntryMode.calendarOnly,
|
||||
);
|
||||
if (selectedDate != null) {
|
||||
context.read<AppLogsCubit>().loadLogs(selectedDate);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.calendar_today),
|
||||
).padded(),
|
||||
],
|
||||
),
|
||||
body: switch (state) {
|
||||
AppLogsStateLoaded(
|
||||
logs: var logs,
|
||||
) =>
|
||||
Builder(
|
||||
builder: (context) {
|
||||
if (state.logs.isEmpty) {
|
||||
return Center(
|
||||
child: Text(S.of(context)!.noLogsFoundOn(formattedDate)),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
reverse: true,
|
||||
controller: _scrollController,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return Center(
|
||||
child: Text(S.of(context)!.logfileBottomReached,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.disabledColor,
|
||||
)),
|
||||
).padded(24);
|
||||
}
|
||||
final messages = state.logs;
|
||||
final logMessage = messages[index - 1];
|
||||
final altColor = CupertinoDynamicColor.withBrightness(
|
||||
color: Colors.grey.shade200,
|
||||
darkColor: Colors.grey.shade800,
|
||||
).resolveFrom(context);
|
||||
return ParsedLogMessageTile(
|
||||
message: logMessage,
|
||||
backgroundColor: (index % 2 == 0)
|
||||
? theme.colorScheme.background
|
||||
: altColor,
|
||||
);
|
||||
},
|
||||
itemCount: logs.length + 1,
|
||||
);
|
||||
},
|
||||
),
|
||||
AppLogsStateError() => Center(
|
||||
child:
|
||||
Text(S.of(context)!.couldNotLoadLogfileFrom(formattedDate)),
|
||||
),
|
||||
_ => _buildLoadingLogs(state.date)
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingLogs(DateTime date) {
|
||||
final formattedDate =
|
||||
DateFormat.yMd(Localizations.localeOf(context).toString()).format(date);
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
Text(S.of(context)!.loadingLogsFrom(formattedDate)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ParsedLogMessageTile extends StatelessWidget {
|
||||
final ParsedLogMessage message;
|
||||
final Color backgroundColor;
|
||||
|
||||
const ParsedLogMessageTile({
|
||||
super.key,
|
||||
required this.message,
|
||||
required this.backgroundColor,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return switch (message) {
|
||||
ParsedFormattedLogMessage m => FormattedLogMessageWidget(
|
||||
message: m,
|
||||
backgroundColor: backgroundColor,
|
||||
),
|
||||
UnformattedLogMessage(message: var m) => Text(m),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class FormattedLogMessageWidget extends StatelessWidget {
|
||||
final ParsedFormattedLogMessage message;
|
||||
final Color backgroundColor;
|
||||
const FormattedLogMessageWidget(
|
||||
{super.key, required this.message, required this.backgroundColor});
|
||||
static final _timeFormat = DateFormat("HH:mm:ss.SSS");
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final c = Theme.of(context).colorScheme;
|
||||
|
||||
final icon = switch (message.level) {
|
||||
Level.trace => Icons.troubleshoot,
|
||||
Level.debug => Icons.bug_report,
|
||||
Level.info => Icons.info_outline,
|
||||
Level.warning => Icons.warning,
|
||||
Level.error => Icons.error,
|
||||
Level.fatal => Icons.error_outline,
|
||||
_ => null,
|
||||
};
|
||||
final color = switch (message.level) {
|
||||
Level.trace => c.onBackground.withOpacity(0.75),
|
||||
Level.warning => Colors.yellow.shade600,
|
||||
Level.error => Colors.red,
|
||||
Level.fatal => Colors.red.shade900,
|
||||
Level.info => Colors.blue,
|
||||
_ => c.onBackground,
|
||||
};
|
||||
|
||||
final logStyle = Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12,
|
||||
);
|
||||
final formattedMethodName =
|
||||
message.methodName != null ? '${message.methodName!.trim()}()' : '';
|
||||
final source = switch (message.className) {
|
||||
'' || null => formattedMethodName,
|
||||
String className => '$className.$formattedMethodName',
|
||||
};
|
||||
return Material(
|
||||
color: backgroundColor,
|
||||
child: ExpansionTile(
|
||||
leading: Text(
|
||||
_timeFormat.format(message.timestamp),
|
||||
style: logStyle?.copyWith(color: color),
|
||||
),
|
||||
title: Text(
|
||||
message.message,
|
||||
style: logStyle?.copyWith(color: color),
|
||||
),
|
||||
trailing: Icon(
|
||||
icon,
|
||||
color: color,
|
||||
),
|
||||
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||
childrenPadding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||
expandedAlignment: Alignment.topLeft,
|
||||
children: source.isNotEmpty
|
||||
? [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.arrow_right),
|
||||
Flexible(
|
||||
child: Text(
|
||||
'In $source',
|
||||
style: logStyle?.copyWith(fontSize: 14),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
..._buildErrorWidgets(context),
|
||||
]
|
||||
: _buildErrorWidgets(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildErrorWidgets(BuildContext context) {
|
||||
if (message.error != null) {
|
||||
return [
|
||||
Divider(),
|
||||
Text(
|
||||
message.error!.error,
|
||||
style: TextStyle(color: Colors.red),
|
||||
).padded(),
|
||||
if (message.error?.stackTrace != null) ...[
|
||||
Text(
|
||||
message.error!.stackTrace!,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 10,
|
||||
),
|
||||
).paddedOnly(left: 8),
|
||||
],
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
|
||||
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_app_state.dart';
|
||||
@@ -14,8 +13,8 @@ import 'package:paperless_mobile/core/database/tables/local_user_settings.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/user_credentials.dart';
|
||||
import 'package:paperless_mobile/core/factory/paperless_api_factory.dart';
|
||||
import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart';
|
||||
import 'package:paperless_mobile/core/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/core/logging/utils/redaction_utils.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/features/logging/utils/redaction_utils.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/security/session_manager.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
|
||||
part 'authentication_information.g.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
|
||||
part 'client_certificate.g.dart';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/users/view/widgets/user_account_list_tile.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/routes/typed/shells/authenticated_route.dart';
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'dart:typed_data';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
|
||||
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/obscured_input_text_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/theme.dart';
|
||||
|
||||
class LoginTransitionPage extends StatelessWidget {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_actions.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_tap/notification_tap_response_payload.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_tap/open_downloaded_document_payload.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_tap/open_directory_notification_response_payload.dart';
|
||||
|
||||
class NotificationTapResponsePayloadConverter
|
||||
implements
|
||||
@@ -11,8 +11,8 @@ class NotificationTapResponsePayloadConverter
|
||||
NotificationTapResponsePayload fromJson(Map<String, dynamic> json) {
|
||||
final type = NotificationResponseOpenAction.values.byName(json['type']);
|
||||
switch (type) {
|
||||
case NotificationResponseOpenAction.openDownloadedDocumentPath:
|
||||
return OpenDownloadedDocumentPayload.fromJson(
|
||||
case NotificationResponseOpenAction.openDirectory:
|
||||
return OpenDirectoryNotificationResponsePayload.fromJson(
|
||||
json,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@ enum NotificationResponseButtonAction {
|
||||
|
||||
@JsonEnum()
|
||||
enum NotificationResponseOpenAction {
|
||||
openDownloadedDocumentPath;
|
||||
openDirectory;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
enum NotificationChannel {
|
||||
task("task_channel", "Paperless tasks"),
|
||||
documentDownload("document_download_channel", "Document downloads");
|
||||
documentDownload("document_download_channel", "Document downloads"),
|
||||
fileDownload("file_download_channel", "File downloads");
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_actions.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_tap/notification_tap_response_payload.dart';
|
||||
|
||||
part 'open_directory_notification_response_payload.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class OpenDirectoryNotificationResponsePayload
|
||||
extends NotificationTapResponsePayload {
|
||||
final String filePath;
|
||||
OpenDirectoryNotificationResponsePayload({
|
||||
required this.filePath,
|
||||
super.type = NotificationResponseOpenAction.openDirectory,
|
||||
});
|
||||
|
||||
factory OpenDirectoryNotificationResponsePayload.fromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
_$OpenDirectoryNotificationResponsePayloadFromJson(json);
|
||||
@override
|
||||
Map<String, dynamic> toJson() =>
|
||||
_$OpenDirectoryNotificationResponsePayloadToJson(this);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_actions.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_tap/notification_tap_response_payload.dart';
|
||||
|
||||
part 'open_downloaded_document_payload.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class OpenDownloadedDocumentPayload extends NotificationTapResponsePayload {
|
||||
final String filePath;
|
||||
OpenDownloadedDocumentPayload({
|
||||
required this.filePath,
|
||||
super.type = NotificationResponseOpenAction.openDownloadedDocumentPath,
|
||||
});
|
||||
|
||||
factory OpenDownloadedDocumentPayload.fromJson(Map<String, dynamic> json) =>
|
||||
_$OpenDownloadedDocumentPayloadFromJson(json);
|
||||
@override
|
||||
Map<String, dynamic> toJson() => _$OpenDownloadedDocumentPayloadToJson(this);
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/notifications/converters/notification_tap_response_payload.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_action/create_document_success_payload.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_tap/open_downloaded_document_payload.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_actions.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_channels.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_action/create_document_success_payload.dart';
|
||||
import 'package:paperless_mobile/features/notifications/models/notification_payloads/notification_tap/open_directory_notification_response_payload.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class LocalNotificationService {
|
||||
@@ -48,6 +48,31 @@ class LocalNotificationService {
|
||||
}
|
||||
|
||||
Future<void> notifyFileDownload({
|
||||
required String filePath,
|
||||
}) async {
|
||||
await _plugin.show(
|
||||
filePath.hashCode,
|
||||
filePath,
|
||||
"File download complete.",
|
||||
NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
NotificationChannel.fileDownload.id + "_${filePath.hashCode}",
|
||||
NotificationChannel.fileDownload.name,
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
showProgress: false,
|
||||
when: DateTime.now().millisecondsSinceEpoch,
|
||||
category: AndroidNotificationCategory.status,
|
||||
icon: 'file_download_done',
|
||||
),
|
||||
),
|
||||
payload: jsonEncode(
|
||||
OpenDirectoryNotificationResponsePayload(filePath: filePath)
|
||||
.toJson()),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> notifyDocumentDownload({
|
||||
required DocumentModel document,
|
||||
required String filename,
|
||||
required String filePath,
|
||||
@@ -89,7 +114,7 @@ class LocalNotificationService {
|
||||
),
|
||||
),
|
||||
payload: jsonEncode(
|
||||
OpenDownloadedDocumentPayload(
|
||||
OpenDirectoryNotificationResponsePayload(
|
||||
filePath: filePath,
|
||||
).toJson(),
|
||||
),
|
||||
@@ -139,7 +164,7 @@ class LocalNotificationService {
|
||||
),
|
||||
),
|
||||
payload: jsonEncode(
|
||||
OpenDownloadedDocumentPayload(filePath: filePath).toJson(),
|
||||
OpenDirectoryNotificationResponsePayload(filePath: filePath).toJson(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -281,9 +306,10 @@ class LocalNotificationService {
|
||||
NotificationResponse response,
|
||||
) {
|
||||
switch (type) {
|
||||
case NotificationResponseOpenAction.openDownloadedDocumentPath:
|
||||
final payload = OpenDownloadedDocumentPayload.fromJson(
|
||||
jsonDecode(response.payload!));
|
||||
case NotificationResponseOpenAction.openDirectory:
|
||||
final payload = OpenDirectoryNotificationResponsePayload.fromJson(
|
||||
jsonDecode(response.payload!),
|
||||
);
|
||||
OpenFilex.open(payload.filePath);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/extensions/document_iterable_extensions.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
|
||||
part 'saved_view_preview_state.dart';
|
||||
@@ -8,11 +11,55 @@ class SavedViewPreviewCubit extends Cubit<SavedViewPreviewState> {
|
||||
final PaperlessDocumentsApi _api;
|
||||
final SavedView view;
|
||||
final ConnectivityStatusService _connectivityStatusService;
|
||||
final DocumentChangedNotifier _changedNotifier;
|
||||
SavedViewPreviewCubit(
|
||||
this._api,
|
||||
this._connectivityStatusService, {
|
||||
this._connectivityStatusService,
|
||||
this._changedNotifier, {
|
||||
required this.view,
|
||||
}) : super(const InitialSavedViewPreviewState());
|
||||
}) : super(const InitialSavedViewPreviewState()) {
|
||||
_changedNotifier.addListener(
|
||||
this,
|
||||
onDeleted: (document) {
|
||||
final s = state;
|
||||
if (s is! LoadedSavedViewPreviewState) {
|
||||
return;
|
||||
}
|
||||
if (!s.documents.containsDocument(document)) {
|
||||
return;
|
||||
}
|
||||
emit(
|
||||
LoadedSavedViewPreviewState(
|
||||
documents: s.documents.withDocumentRemoved(document).toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
onUpdated: (document) {
|
||||
final s = state;
|
||||
if (s is! LoadedSavedViewPreviewState) {
|
||||
return;
|
||||
}
|
||||
if (!s.documents.containsDocument(document)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final shouldRemainInFilter = view.toDocumentFilter().matches(document);
|
||||
if (!shouldRemainInFilter) {
|
||||
emit(
|
||||
LoadedSavedViewPreviewState(
|
||||
documents: s.documents.withDocumentRemoved(document).toList(),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(
|
||||
LoadedSavedViewPreviewState(
|
||||
documents: s.documents.withDocumentreplaced(document).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
final isConnected =
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart';
|
||||
import 'package:paperless_mobile/features/landing/view/widgets/expansion_card.dart';
|
||||
@@ -24,6 +24,7 @@ class SavedViewPreview extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Provider(
|
||||
create: (context) => SavedViewPreviewCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
view: savedView,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
|
||||
part 'color_scheme_option.g.dart';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
|
||||
part 'file_download_type.g.dart';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
|
||||
part 'view_type.g.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/dialogs/switch_account_dialog.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
|
||||
class GlobalSettingsBuilder extends StatelessWidget {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/config/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
|
||||
Reference in New Issue
Block a user