From 3d69abe2ac1b6eff5b6b5c54747c965f38784490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Hlatk=C3=BD?= <hlatky@freevision.sk> Date: Thu, 27 Jun 2024 09:37:16 +0200 Subject: [PATCH 1/7] Umoznit sharovat subor ak na Android neexistuje Downloads (#24) #18 | Allow sharing signed Document even when failed to save file --- CHANGELOG.md | 3 +- lib/bloc/present_signed_document_cubit.dart | 43 ++++++++++++------- lib/pages/test_page.dart | 1 + .../present_signed_document_screen.dart | 42 ++++++++++++++---- lib/ui/screens/sign_document_screen.dart | 16 +++++-- pubspec.lock | 4 +- 6 files changed, 79 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c10a1..1240dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ## NEXT - v1.0.2(33) -- Refactor Settings model +- Refactor Settings model +- #18 | Allow sharing signed Document even when failed to save file ## 2024-06-17 - v1.0.1(1) diff --git a/lib/bloc/present_signed_document_cubit.dart b/lib/bloc/present_signed_document_cubit.dart index 02dd557..550821c 100644 --- a/lib/bloc/present_signed_document_cubit.dart +++ b/lib/bloc/present_signed_document_cubit.dart @@ -1,5 +1,5 @@ import 'dart:convert' show base64Decode; -import 'dart:io' show File; +import 'dart:io' show File, Platform; import 'package:autogram_sign/autogram_sign.dart' show SignDocumentResponseBody; import 'package:flutter/foundation.dart'; @@ -18,6 +18,9 @@ import 'present_signed_document_state.dart'; export 'present_signed_document_state.dart'; /// Cubit for [PresentSignedDocumentScreen]. +/// +/// Allows saving document into public directory or getting [File] instance +/// which can be shared. @injectable class PresentSignedDocumentCubit extends Cubit<PresentSignedDocumentState> { static final _log = Logger("PresentSignedDocumentCubit"); @@ -33,6 +36,7 @@ class PresentSignedDocumentCubit extends Cubit<PresentSignedDocumentState> { }) : _appService = appService, super(const PresentSignedDocumentInitialState()); + /// Saves [signedDocument] into public directory. Future<void> saveDocument() async { _log.info("Saving signed document: ${signedDocument.filename}."); @@ -41,11 +45,10 @@ class PresentSignedDocumentCubit extends Cubit<PresentSignedDocumentState> { File? file; try { - file = await _getTargetFile(); - final bytes = await Future.microtask( - () => base64Decode(signedDocument.content), - ); - await file.writeAsBytes(bytes); + file = await _getTargetPath().then((path) => File(path)); + // TODO Catch and still allow sharing + // Need to change PresentSignedDocumentSuccessState impl. to allow File? + await _saveDocumentIntoFile(file!); _log.info("Signed Document was saved into $file"); @@ -73,24 +76,34 @@ class PresentSignedDocumentCubit extends Cubit<PresentSignedDocumentState> { final name = signedDocument.filename; final directory = await getTemporaryDirectory(); final path = p.join(directory.path, name); - final bytes = await Future.microtask( - () => base64Decode(signedDocument.content), - ); final file = File(path); - return file.writeAsBytes(bytes, flush: true); + await _saveDocumentIntoFile(file); + + return file; } - /// Returns target [File] where to save new file from [signedDocument]. + /// Returns file path, where [signedDocument] content should be saved. /// /// See also: /// - [getTargetFileName] - Future<File> _getTargetFile() async { + Future<String> _getTargetPath() async { final directory = await _appService.getDocumentsDirectory(); + + // Attempt to create Directory if not exists + if (!(await directory.exists()) && Platform.isAndroid) { + await directory.create(recursive: true); + } + final name = getTargetFileName(signedDocument.filename); - final path = p.join(directory.path, name); - return File(path); + return p.join(directory.path, name); + } + + /// Saves [signedDocument] content into given [file]. + Future<void> _saveDocumentIntoFile(File file) { + return Future.microtask(() => base64Decode(signedDocument.content)) + .then((bytes) => file.writeAsBytes(bytes, flush: true)); } /// Gets the target file name. @@ -98,7 +111,7 @@ class PresentSignedDocumentCubit extends Cubit<PresentSignedDocumentState> { @visibleForTesting static String getTargetFileName( String name, [ - // TODO This should get exact DateTime from previous cubit when it was really signed + // TODO This should get exact DateTime from previous cubit when it was actually signed // SignDocumentCubit signingTime ValueGetter<DateTime> clock = DateTime.now, ]) { diff --git a/lib/pages/test_page.dart b/lib/pages/test_page.dart index 7e5caf2..47a97a7 100644 --- a/lib/pages/test_page.dart +++ b/lib/pages/test_page.dart @@ -15,6 +15,7 @@ import 'package:share_plus/share_plus.dart'; import '../utils.dart' as utils; +// TODO Move to ui/screens, add some docs class TestPage extends HookWidget { const TestPage({super.key}); diff --git a/lib/ui/screens/present_signed_document_screen.dart b/lib/ui/screens/present_signed_document_screen.dart index 6142082..777b8d9 100644 --- a/lib/ui/screens/present_signed_document_screen.dart +++ b/lib/ui/screens/present_signed_document_screen.dart @@ -1,3 +1,4 @@ +import 'dart:developer' as developer; import 'dart:io' show File, OSError, PathAccessException; import 'package:autogram_sign/autogram_sign.dart' show SignDocumentResponseBody; @@ -20,6 +21,9 @@ import '../widgets/result_view.dart'; /// Screen for presenting signed document. /// +/// When [signingType] is [DocumentSigningType.local], then document is saved +/// into this device and also "Share" button is visible. +/// /// Uses [PresentSignedDocumentCubit]. class PresentSignedDocumentScreen extends StatelessWidget { final SignDocumentResponseBody signedDocument; @@ -144,6 +148,10 @@ class _Body extends StatelessWidget { } Widget _getChild(BuildContext context) { + final sharingEnabled = (signingType == DocumentSigningType.local); + final onShareFileRequested = + sharingEnabled ? this.onShareFileRequested : null; + return switch (state) { PresentSignedDocumentInitialState _ => _SuccessContent( file: null, @@ -153,7 +161,7 @@ class _Body extends StatelessWidget { PresentSignedDocumentLoadingState _ => const LoadingContent(), PresentSignedDocumentErrorState _ => _SuccessContent( file: null, - onShareFileRequested: null, + onShareFileRequested: onShareFileRequested, onCloseRequested: onCloseRequested, ), PresentSignedDocumentSuccessState state => _SuccessContent( @@ -257,7 +265,12 @@ Widget previewInitialPresentSignedDocumentScreen(BuildContext context) { return _Body( state: const PresentSignedDocumentInitialState(), signingType: signingType, - onCloseRequested: () {}, + onShareFileRequested: () { + developer.log('onShareFileRequested'); + }, + onCloseRequested: () { + developer.log('onCloseRequested'); + }, ); } @@ -276,6 +289,12 @@ Widget previewLoadingPresentSignedDocumentScreen(BuildContext context) { return _Body( state: const PresentSignedDocumentLoadingState(), signingType: signingType, + onShareFileRequested: () { + developer.log('onShareFileRequested'); + }, + onCloseRequested: () { + developer.log('onCloseRequested'); + }, ); } @@ -291,7 +310,7 @@ Widget previewErrorPresentSignedDocumentScreen(BuildContext context) { initialOption: DocumentSigningType.local, ); - // TODO Should preview whole Screen class also with BlocConsumer.listener + // TODO Should preview whole Screen class also with BlocConsumer.listener to display error in SnackBar const error = PathAccessException( "/storage/emulated/0/Download/container-signed-xades-baseline-b.sce", OSError("Permission denied", 13), @@ -301,8 +320,12 @@ Widget previewErrorPresentSignedDocumentScreen(BuildContext context) { return _Body( state: const PresentSignedDocumentErrorState(error), signingType: signingType, - onShareFileRequested: () {}, - onCloseRequested: () {}, + onShareFileRequested: () { + developer.log('onShareFileRequested'); + }, + onCloseRequested: () { + developer.log('onCloseRequested'); + }, ); } @@ -326,8 +349,11 @@ Widget previewSuccessPresentSignedDocumentScreen(BuildContext context) { return _Body( state: PresentSignedDocumentSuccessState(file), signingType: signingType, - onShareFileRequested: - signingType == DocumentSigningType.local ? () {} : null, - onCloseRequested: () {}, + onShareFileRequested: () { + developer.log('onShareFileRequested'); + }, + onCloseRequested: () { + developer.log('onCloseRequested'); + }, ); } diff --git a/lib/ui/screens/sign_document_screen.dart b/lib/ui/screens/sign_document_screen.dart index 1f4e8e7..9a08055 100644 --- a/lib/ui/screens/sign_document_screen.dart +++ b/lib/ui/screens/sign_document_screen.dart @@ -1,3 +1,5 @@ +import 'dart:developer' as developer; + import 'package:autogram_sign/autogram_sign.dart' show SignDocumentResponseBody, SignDocumentResponseBodyMimeType; import 'package:eidmsdk/types.dart' show Certificate; @@ -141,8 +143,11 @@ Widget previewLoadingSignDocumentScreen(BuildContext context) { type: SignDocumentScreen, ) Widget previewErrorSignDocumentScreen(BuildContext context) { - return const _Body( - state: SignDocumentErrorState("Error message!"), + return _Body( + state: const SignDocumentErrorState("Error message!"), + onRetryRequested: () { + developer.log('onRetryRequested'); + }, ); } @@ -152,8 +157,8 @@ Widget previewErrorSignDocumentScreen(BuildContext context) { type: SignDocumentScreen, ) Widget previewSuccessSignDocumentScreen(BuildContext context) { - return const _Body( - state: SignDocumentSuccessState( + return _Body( + state: const SignDocumentSuccessState( SignDocumentResponseBody( filename: "document.pdf", mimeType: SignDocumentResponseBodyMimeType.applicationPdfBase64, @@ -162,5 +167,8 @@ Widget previewSuccessSignDocumentScreen(BuildContext context) { signedBy: "", ), ), + onRetryRequested: () { + developer.log('onRetryRequested'); + }, ); } diff --git a/pubspec.lock b/pubspec.lock index 59de53e..1ef81f4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -767,10 +767,10 @@ packages: dependency: "direct main" description: name: pdf - sha256: "243f05342fc0bdf140eba5b069398985cdbdd3dbb1d776cf43d5ea29cc570ba6" + sha256: "81d5522bddc1ef5c28e8f0ee40b71708761753c163e0c93a40df56fd515ea0f0" url: "https://pub.dev" source: hosted - version: "3.10.8" + version: "3.11.0" pdf_widget_wrapper: dependency: transitive description: From c22c57e617dfc6074c69cf2cafe9e6bfd8a3c85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Hlatk=C3=BD?= <hlatky@freevision.sk> Date: Sun, 30 Jun 2024 17:36:29 +0200 Subject: [PATCH 2/7] About Screen upravy (#26) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix typo * Using markdown text with links * Using markdown in about texts * Prepare MarkdownText widget * Using MarkdownText on AboutScreen * Ad "utm_source" into links * Update TODO * Fix vertical content overflow * Document changes * change ssd url --------- Co-authored-by: Marek Ceľuch <celuchmarek@gmail.com> --- CHANGELOG.md | 2 + lib/l10n/app_localizations.dart | 4 +- lib/l10n/app_localizations_sk.dart | 4 +- lib/l10n/app_sk.arb | 4 +- lib/ui/screens/about_screen.dart | 31 ++++-- lib/ui/widgets/markdown_text.dart | 82 +++++++++++++++ lib/widgetbook_app.directories.g.dart | 142 ++++++++++++++------------ pubspec.lock | 16 +++ pubspec.yaml | 1 + 9 files changed, 207 insertions(+), 79 deletions(-) create mode 100644 lib/ui/widgets/markdown_text.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 1240dcc..14c8076 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Refactor Settings model - #18 | Allow sharing signed Document even when failed to save file +- About screen - add links into "about" text +- About screen - fix vertical content overflow on smaller screens ## 2024-06-17 - v1.0.1(1) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index cf01787..b6f8a2f 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -352,13 +352,13 @@ abstract class AppLocalizations { /// No description provided for @eidSDKLicenseText. /// /// In sk, this message translates to: - /// **'Na komunikáciu s čipom občianskeho preukazu je použitá knižnica eID mSDK od Ministerstva vnútra Slovenskej republiky. Knižnica eID mSDK a podmienky jej použitia sú zverejnené na stránke „https://github.com/eidmsdk“'** + /// **'Na komunikáciu s čipom občianskeho preukazu je použitá knižnica eID mSDK od Ministerstva vnútra Slovenskej republiky. Knižnica eID mSDK a podmienky jej použitia sú zverejnené na stránke „<https://github.com/eidmsdk>“.'** String get eidSDKLicenseText; /// No description provided for @aboutAuthorsText. /// /// In sk, this message translates to: - /// **'Autormi tohto projektu sú freevision s.r.o., Služby Slovensko.Digital, s.r.o. a ďalší dobrovoľníci. Prevádzku zabezpečuje Služby Slovensko.Digital, s.r.o. Zdrojové kódy sú dpstupné na GitHub-e organizácie Slovensko.Digital.'** + /// **'Autormi tohto projektu sú [freevision s.r.o.](https://freevision.sk/?utm_source=digital.slovensko.avm), [Služby Slovensko.Digital, s.r.o.](https://ekosystem.slovensko.digital/?utm_source=digital.slovensko.avm) a ďalší dobrovoľníci. Prevádzku zabezpečuje [Služby Slovensko.Digital, s.r.o.](https://ekosystem.slovensko.digital/?utm_source=digital.slovensko.avm)\n\nZdrojové kódy sú dostupné na [GitHub-e organizácie Slovensko.Digital](https://github.com/slovensko-digital/avm-app-flutter).'** String get aboutAuthorsText; /// No description provided for @thirdPartyLicensesLabel. diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 82acb06..43ff162 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -182,10 +182,10 @@ class AppLocalizationsSk extends AppLocalizations { String get aboutTitle => 'O aplikácii'; @override - String get eidSDKLicenseText => 'Na komunikáciu s čipom občianskeho preukazu je použitá knižnica eID mSDK od Ministerstva vnútra Slovenskej republiky. Knižnica eID mSDK a podmienky jej použitia sú zverejnené na stránke „https://github.com/eidmsdk“'; + String get eidSDKLicenseText => 'Na komunikáciu s čipom občianskeho preukazu je použitá knižnica eID mSDK od Ministerstva vnútra Slovenskej republiky. Knižnica eID mSDK a podmienky jej použitia sú zverejnené na stránke „<https://github.com/eidmsdk>“.'; @override - String get aboutAuthorsText => 'Autormi tohto projektu sú freevision s.r.o., Služby Slovensko.Digital, s.r.o. a ďalší dobrovoľníci. Prevádzku zabezpečuje Služby Slovensko.Digital, s.r.o. Zdrojové kódy sú dpstupné na GitHub-e organizácie Slovensko.Digital.'; + String get aboutAuthorsText => 'Autormi tohto projektu sú [freevision s.r.o.](https://freevision.sk/?utm_source=digital.slovensko.avm), [Služby Slovensko.Digital, s.r.o.](https://ekosystem.slovensko.digital/?utm_source=digital.slovensko.avm) a ďalší dobrovoľníci. Prevádzku zabezpečuje [Služby Slovensko.Digital, s.r.o.](https://ekosystem.slovensko.digital/?utm_source=digital.slovensko.avm)\n\nZdrojové kódy sú dostupné na [GitHub-e organizácie Slovensko.Digital](https://github.com/slovensko-digital/avm-app-flutter).'; @override String get thirdPartyLicensesLabel => 'Licencie knižníc tretích strán'; diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 29870f6..17b2485 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -48,8 +48,8 @@ "termsOfServiceUrl": "https://sluzby.slovensko.digital/autogram-v-mobile/vseobecne-obchodne-podmienky", "aboutTitle": "O aplikácii", - "eidSDKLicenseText": "Na komunikáciu s čipom občianskeho preukazu je použitá knižnica eID mSDK od Ministerstva vnútra Slovenskej republiky. Knižnica eID mSDK a podmienky jej použitia sú zverejnené na\u00A0stránke „https://github.com/eidmsdk“", - "aboutAuthorsText": "Autormi tohto projektu sú freevision\u00A0s.r.o., Služby\u00A0Slovensko.Digital,\u00A0s.r.o. a ďalší dobrovoľníci. Prevádzku zabezpečuje Služby\u00A0Slovensko.Digital,\u00A0s.r.o. Zdrojové kódy sú dpstupné na GitHub-e organizácie Slovensko.Digital.", + "eidSDKLicenseText": "Na komunikáciu s čipom občianskeho preukazu je použitá knižnica eID mSDK od Ministerstva vnútra Slovenskej republiky. Knižnica eID mSDK a podmienky jej použitia sú zverejnené na\u00A0stránke „<https://github.com/eidmsdk>“.", + "aboutAuthorsText": "Autormi tohto projektu sú [freevision\u00A0s.r.o.](https://freevision.sk/?utm_source=digital.slovensko.avm), [Služby\u00A0Slovensko.Digital,\u00A0s.r.o.](https://ekosystem.slovensko.digital/?utm_source=digital.slovensko.avm) a ďalší dobrovoľníci. Prevádzku zabezpečuje [Služby\u00A0Slovensko.Digital,\u00A0s.r.o.](https://ekosystem.slovensko.digital/?utm_source=digital.slovensko.avm)\n\nZdrojové kódy sú dostupné na [GitHub-e organizácie Slovensko.Digital](https://github.com/slovensko-digital/avm-app-flutter).", "thirdPartyLicensesLabel": "Licencie knižníc tretích strán", "introHeading": "Nový, lepší a krajší podpisovač v mobile", diff --git a/lib/ui/screens/about_screen.dart b/lib/ui/screens/about_screen.dart index 08e5e8b..a39a645 100644 --- a/lib/ui/screens/about_screen.dart +++ b/lib/ui/screens/about_screen.dart @@ -4,9 +4,13 @@ import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; import '../../strings_context.dart'; import '../app_theme.dart'; import '../widgets/app_version_text.dart'; +import '../widgets/markdown_text.dart'; import 'show_document_screen.dart'; -/// Displays About. +/// Displays About appliaction: +/// - headline, version +/// - authors, eID mSDK info +/// - link to [showLicensePage] /// /// See also: /// - [ShowDocumentScreen] @@ -44,9 +48,10 @@ class _Body extends StatelessWidget { const SizedBox(height: 16), const AppVersionText(), const SizedBox(height: 16), - Text(strings.aboutAuthorsText), + MarkdownText(strings.aboutAuthorsText), const SizedBox(height: 16), - Text(strings.eidSDKLicenseText), + MarkdownText(strings.eidSDKLicenseText), + const SizedBox(height: kButtonSpace), const Spacer(), TextButton( style: TextButton.styleFrom( @@ -58,9 +63,23 @@ class _Body extends StatelessWidget { ], ); - return Padding( - padding: kScreenMargin, - child: child, + return LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: constraints.maxWidth, + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: kScreenMargin, + child: child, + ), + ), + ), + ); + }, ); } diff --git a/lib/ui/widgets/markdown_text.dart b/lib/ui/widgets/markdown_text.dart new file mode 100644 index 0000000..9f957f7 --- /dev/null +++ b/lib/ui/widgets/markdown_text.dart @@ -0,0 +1,82 @@ +import 'dart:developer' as developer; + +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:url_launcher/url_launcher_string.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; + +/// Displays text formatted in Markdown. +/// +/// By default, links are open in external application. +class MarkdownText extends StatelessWidget { + final String data; + final MarkdownTapLinkCallback onLinkTap; + + const MarkdownText( + this.data, { + super.key, + this.onLinkTap = _defaultOnLinkTap, + }); + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).colorScheme; + final styleSheet = MarkdownStyleSheet( + a: TextStyle( + color: colors.primary, + fontWeight: FontWeight.bold, + //decoration: TextDecoration.underline, // UGLY :/ + decorationColor: colors.primary, + ), + ); + + return MarkdownBody( + data: data, + styleSheet: styleSheet, + onTapLink: onLinkTap, + ); + } + + static void _defaultOnLinkTap(String text, String? href, String title) { + if (href != null) { + launchUrlString(href, mode: LaunchMode.externalApplication); + } + } +} + +@widgetbook.UseCase( + path: '[Core]', + name: '', + type: MarkdownText, +) +Widget previewMarkdownText(BuildContext context) { + final data = context.knobs.string( + label: 'Data', + initialValue: """ +# Supported Markdown + +## Unordered List + Text formatting + +- This is normal text +- **This is bold text** +- _This is italic text_ +- ~~This is striked text~~ +- [Link text](https://slovensko.digital) + +## Ordered List + +1. Lorem ipsum dolor sit amet +2. Consectetur adipiscing elit +3. Integer molestie lorem at massa + """, + maxLines: 5, + ); + + return MarkdownText( + data, + onLinkTap: (String text, String? href, String title) { + developer.log("On link tap: $text -> $href"); + }, + ); +} diff --git a/lib/widgetbook_app.directories.g.dart b/lib/widgetbook_app.directories.g.dart index c752a52..48145ed 100644 --- a/lib/widgetbook_app.directories.g.dart +++ b/lib/widgetbook_app.directories.g.dart @@ -11,44 +11,45 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:autogram/ui/app_theme.dart' as _i3; import 'package:autogram/ui/assets.dart' as _i4; -import 'package:autogram/ui/fragment/show_web_page_fragment.dart' as _i21; -import 'package:autogram/ui/screens/about_screen.dart' as _i24; -import 'package:autogram/ui/screens/main_menu_screen.dart' as _i25; +import 'package:autogram/ui/fragment/show_web_page_fragment.dart' as _i22; +import 'package:autogram/ui/screens/about_screen.dart' as _i25; +import 'package:autogram/ui/screens/main_menu_screen.dart' as _i26; import 'package:autogram/ui/screens/main_screen.dart' as _i2; import 'package:autogram/ui/screens/onboarding_accept_document_screen.dart' - as _i26; -import 'package:autogram/ui/screens/onboarding_finished_screen.dart' as _i27; + as _i27; +import 'package:autogram/ui/screens/onboarding_finished_screen.dart' as _i28; import 'package:autogram/ui/screens/onboarding_select_signing_certificate_screen.dart' - as _i28; -import 'package:autogram/ui/screens/open_document_screen.dart' as _i29; -import 'package:autogram/ui/screens/paired_device_list_screen.dart' as _i30; + as _i29; +import 'package:autogram/ui/screens/open_document_screen.dart' as _i30; +import 'package:autogram/ui/screens/paired_device_list_screen.dart' as _i31; import 'package:autogram/ui/screens/present_signed_document_screen.dart' - as _i31; -import 'package:autogram/ui/screens/preview_document_screen.dart' as _i32; -import 'package:autogram/ui/screens/qr_code_scanner_screen.dart' as _i19; -import 'package:autogram/ui/screens/select_certificate_screen.dart' as _i33; -import 'package:autogram/ui/screens/settings_screen.dart' as _i34; -import 'package:autogram/ui/screens/show_document_screen.dart' as _i35; -import 'package:autogram/ui/screens/sign_document_screen.dart' as _i36; + as _i32; +import 'package:autogram/ui/screens/preview_document_screen.dart' as _i33; +import 'package:autogram/ui/screens/qr_code_scanner_screen.dart' as _i20; +import 'package:autogram/ui/screens/select_certificate_screen.dart' as _i34; +import 'package:autogram/ui/screens/settings_screen.dart' as _i35; +import 'package:autogram/ui/screens/show_document_screen.dart' as _i36; +import 'package:autogram/ui/screens/sign_document_screen.dart' as _i37; import 'package:autogram/ui/screens/start_remote_document_signing_screen.dart' - as _i37; + as _i38; import 'package:autogram/ui/widgets/app_version_text.dart' as _i9; import 'package:autogram/ui/widgets/autogram_logo.dart' as _i5; import 'package:autogram/ui/widgets/buttons.dart' as _i6; -import 'package:autogram/ui/widgets/certificate_picker.dart' as _i22; +import 'package:autogram/ui/widgets/certificate_picker.dart' as _i23; import 'package:autogram/ui/widgets/close_button.dart' as _i7; -import 'package:autogram/ui/widgets/dialogs.dart' as _i20; +import 'package:autogram/ui/widgets/dialogs.dart' as _i21; import 'package:autogram/ui/widgets/document_visualization.dart' as _i10; import 'package:autogram/ui/widgets/error_content.dart' as _i11; import 'package:autogram/ui/widgets/html_preview.dart' as _i12; import 'package:autogram/ui/widgets/loading_content.dart' as _i13; import 'package:autogram/ui/widgets/loading_indicator.dart' as _i8; -import 'package:autogram/ui/widgets/option_picker.dart' as _i14; -import 'package:autogram/ui/widgets/preference_tile.dart' as _i15; -import 'package:autogram/ui/widgets/result_view.dart' as _i16; -import 'package:autogram/ui/widgets/retry_view.dart' as _i17; -import 'package:autogram/ui/widgets/signature_type_picker.dart' as _i23; -import 'package:autogram/ui/widgets/step_indicator.dart' as _i18; +import 'package:autogram/ui/widgets/markdown_text.dart' as _i14; +import 'package:autogram/ui/widgets/option_picker.dart' as _i15; +import 'package:autogram/ui/widgets/preference_tile.dart' as _i16; +import 'package:autogram/ui/widgets/result_view.dart' as _i17; +import 'package:autogram/ui/widgets/retry_view.dart' as _i18; +import 'package:autogram/ui/widgets/signature_type_picker.dart' as _i24; +import 'package:autogram/ui/widgets/step_indicator.dart' as _i19; import 'package:widgetbook/widgetbook.dart' as _i1; final directories = <_i1.WidgetbookNode>[ @@ -183,18 +184,25 @@ final directories = <_i1.WidgetbookNode>[ builder: _i13.previewLoadingContent, ), ), + _i1.WidgetbookLeafComponent( + name: 'MarkdownText', + useCase: _i1.WidgetbookUseCase( + name: '', + builder: _i14.previewMarkdownText, + ), + ), _i1.WidgetbookLeafComponent( name: 'OptionPicker', useCase: _i1.WidgetbookUseCase( name: 'OptionPicker', - builder: _i14.previewOptionPicker, + builder: _i15.previewOptionPicker, ), ), _i1.WidgetbookLeafComponent( name: 'PreferenceTile', useCase: _i1.WidgetbookUseCase( name: 'PreferenceTile', - builder: _i15.previewPreferenceTile, + builder: _i16.previewPreferenceTile, ), ), _i1.WidgetbookComponent( @@ -202,19 +210,19 @@ final directories = <_i1.WidgetbookNode>[ useCases: [ _i1.WidgetbookUseCase( name: 'custom', - builder: _i16.previewCustomResultView, + builder: _i17.previewCustomResultView, ), _i1.WidgetbookUseCase( name: 'error', - builder: _i16.previewErrorResultView, + builder: _i17.previewErrorResultView, ), _i1.WidgetbookUseCase( name: 'info', - builder: _i16.previewInfoResultView, + builder: _i17.previewInfoResultView, ), _i1.WidgetbookUseCase( name: 'success', - builder: _i16.previewSuccessResultView, + builder: _i17.previewSuccessResultView, ), ], ), @@ -222,21 +230,21 @@ final directories = <_i1.WidgetbookNode>[ name: 'RetryView', useCase: _i1.WidgetbookUseCase( name: 'RetryView', - builder: _i17.previewRetryView, + builder: _i18.previewRetryView, ), ), _i1.WidgetbookLeafComponent( name: 'StepIndicator', useCase: _i1.WidgetbookUseCase( name: 'StepIndicator', - builder: _i18.previewStepIndicator, + builder: _i19.previewStepIndicator, ), ), _i1.WidgetbookLeafComponent( name: '_ViewFinder', useCase: _i1.WidgetbookUseCase( name: '', - builder: _i19.previewViewFinder, + builder: _i20.previewViewFinder, ), ), ], @@ -248,7 +256,7 @@ final directories = <_i1.WidgetbookNode>[ name: 'BottomSheet', useCase: _i1.WidgetbookUseCase( name: 'NotificationsPermissionRationale', - builder: _i20.previewNotificationsPermissionRationaleModal, + builder: _i21.previewNotificationsPermissionRationaleModal, ), ) ], @@ -260,7 +268,7 @@ final directories = <_i1.WidgetbookNode>[ name: 'ShowWebPageFragment', useCase: _i1.WidgetbookUseCase( name: 'ShowWebPageFragment', - builder: _i21.previewShowWebPageFragment, + builder: _i22.previewShowWebPageFragment, ), ) ], @@ -272,14 +280,14 @@ final directories = <_i1.WidgetbookNode>[ name: 'CertificatePicker', useCase: _i1.WidgetbookUseCase( name: 'CertificatePicker', - builder: _i22.previewCertificatePicker, + builder: _i23.previewCertificatePicker, ), ), _i1.WidgetbookLeafComponent( name: 'SignatureTypePicker', useCase: _i1.WidgetbookUseCase( name: '', - builder: _i23.previewSignatureTypePicker, + builder: _i24.previewSignatureTypePicker, ), ), ], @@ -291,14 +299,14 @@ final directories = <_i1.WidgetbookNode>[ name: 'AboutScreen', useCase: _i1.WidgetbookUseCase( name: 'AboutScreen', - builder: _i24.previewAboutScreen, + builder: _i25.previewAboutScreen, ), ), _i1.WidgetbookLeafComponent( name: 'MainMenuScreen', useCase: _i1.WidgetbookUseCase( name: '', - builder: _i25.previewMainMenuScreen, + builder: _i26.previewMainMenuScreen, ), ), _i1.WidgetbookLeafComponent( @@ -312,14 +320,14 @@ final directories = <_i1.WidgetbookNode>[ name: 'OnboardingAcceptDocumentScreen', useCase: _i1.WidgetbookUseCase( name: '', - builder: _i26.previewOnboardingAcceptDocumentScreen, + builder: _i27.previewOnboardingAcceptDocumentScreen, ), ), _i1.WidgetbookLeafComponent( name: 'OnboardingFinishedScreen', useCase: _i1.WidgetbookUseCase( name: '', - builder: _i27.previewOnboardingFinishedScreen, + builder: _i28.previewOnboardingFinishedScreen, ), ), _i1.WidgetbookComponent( @@ -327,20 +335,20 @@ final directories = <_i1.WidgetbookNode>[ useCases: [ _i1.WidgetbookUseCase( name: 'canceled', - builder: _i28.previewCanceledOnboardingSelectSigningCertificateBody, + builder: _i29.previewCanceledOnboardingSelectSigningCertificateBody, ), _i1.WidgetbookUseCase( name: 'initial', - builder: _i28.previewInitialOnboardingSelectSigningCertificateBody, + builder: _i29.previewInitialOnboardingSelectSigningCertificateBody, ), _i1.WidgetbookUseCase( name: 'no certificate', builder: - _i28.previewNoCertificateOnboardingSelectSigningCertificateBody, + _i29.previewNoCertificateOnboardingSelectSigningCertificateBody, ), _i1.WidgetbookUseCase( name: 'success', - builder: _i28.previewSuccessOnboardingSelectSigningCertificateBody, + builder: _i29.previewSuccessOnboardingSelectSigningCertificateBody, ), ], ), @@ -349,11 +357,11 @@ final directories = <_i1.WidgetbookNode>[ useCases: [ _i1.WidgetbookUseCase( name: 'error', - builder: _i29.previewErrorOpenDocumentScreen, + builder: _i30.previewErrorOpenDocumentScreen, ), _i1.WidgetbookUseCase( name: 'loading', - builder: _i29.previewLoadingOpenDocumentScreen, + builder: _i30.previewLoadingOpenDocumentScreen, ), ], ), @@ -361,7 +369,7 @@ final directories = <_i1.WidgetbookNode>[ name: 'PairedDeviceListScreen', useCase: _i1.WidgetbookUseCase( name: '', - builder: _i30.previewPairedDeviceListScreen, + builder: _i31.previewPairedDeviceListScreen, ), ), _i1.WidgetbookComponent( @@ -369,19 +377,19 @@ final directories = <_i1.WidgetbookNode>[ useCases: [ _i1.WidgetbookUseCase( name: 'error', - builder: _i31.previewErrorPresentSignedDocumentScreen, + builder: _i32.previewErrorPresentSignedDocumentScreen, ), _i1.WidgetbookUseCase( name: 'initial', - builder: _i31.previewInitialPresentSignedDocumentScreen, + builder: _i32.previewInitialPresentSignedDocumentScreen, ), _i1.WidgetbookUseCase( name: 'loading', - builder: _i31.previewLoadingPresentSignedDocumentScreen, + builder: _i32.previewLoadingPresentSignedDocumentScreen, ), _i1.WidgetbookUseCase( name: 'success', - builder: _i31.previewSuccessPresentSignedDocumentScreen, + builder: _i32.previewSuccessPresentSignedDocumentScreen, ), ], ), @@ -390,15 +398,15 @@ final directories = <_i1.WidgetbookNode>[ useCases: [ _i1.WidgetbookUseCase( name: 'error', - builder: _i32.previewErrorPreviewDocumentScreen, + builder: _i33.previewErrorPreviewDocumentScreen, ), _i1.WidgetbookUseCase( name: 'loading', - builder: _i32.previewLoadingPreviewDocumentScreen, + builder: _i33.previewLoadingPreviewDocumentScreen, ), _i1.WidgetbookUseCase( name: 'success', - builder: _i32.previewSuccessPreviewDocumentScreen, + builder: _i33.previewSuccessPreviewDocumentScreen, ), ], ), @@ -406,7 +414,7 @@ final directories = <_i1.WidgetbookNode>[ name: 'QRCodeScannerScreen', useCase: _i1.WidgetbookUseCase( name: '', - builder: _i19.previewQRCodeScannerScreen, + builder: _i20.previewQRCodeScannerScreen, ), ), _i1.WidgetbookComponent( @@ -414,23 +422,23 @@ final directories = <_i1.WidgetbookNode>[ useCases: [ _i1.WidgetbookUseCase( name: 'canceled', - builder: _i33.previewCanceledSelectCertificateScreen, + builder: _i34.previewCanceledSelectCertificateScreen, ), _i1.WidgetbookUseCase( name: 'error', - builder: _i33.previewErrorSelectCertificateScreen, + builder: _i34.previewErrorSelectCertificateScreen, ), _i1.WidgetbookUseCase( name: 'loading', - builder: _i33.previewLoadingSelectCertificateScreen, + builder: _i34.previewLoadingSelectCertificateScreen, ), _i1.WidgetbookUseCase( name: 'no certificate', - builder: _i33.previewNoCertificateSelectCertificateScreen, + builder: _i34.previewNoCertificateSelectCertificateScreen, ), _i1.WidgetbookUseCase( name: 'success', - builder: _i33.previewSuccessSelectCertificateScreen, + builder: _i34.previewSuccessSelectCertificateScreen, ), ], ), @@ -438,14 +446,14 @@ final directories = <_i1.WidgetbookNode>[ name: 'SettingsScreen', useCase: _i1.WidgetbookUseCase( name: 'SettingsScreen', - builder: _i34.previewSettingsScreen, + builder: _i35.previewSettingsScreen, ), ), _i1.WidgetbookLeafComponent( name: 'ShowDocumentScreen', useCase: _i1.WidgetbookUseCase( name: '', - builder: _i35.previewShowDocumentScreen, + builder: _i36.previewShowDocumentScreen, ), ), _i1.WidgetbookComponent( @@ -453,15 +461,15 @@ final directories = <_i1.WidgetbookNode>[ useCases: [ _i1.WidgetbookUseCase( name: 'error', - builder: _i36.previewErrorSignDocumentScreen, + builder: _i37.previewErrorSignDocumentScreen, ), _i1.WidgetbookUseCase( name: 'loading', - builder: _i36.previewLoadingSignDocumentScreen, + builder: _i37.previewLoadingSignDocumentScreen, ), _i1.WidgetbookUseCase( name: 'success', - builder: _i36.previewSuccessSignDocumentScreen, + builder: _i37.previewSuccessSignDocumentScreen, ), ], ), @@ -469,7 +477,7 @@ final directories = <_i1.WidgetbookNode>[ name: 'StartRemoteDocumentSigningScreen', useCase: _i1.WidgetbookUseCase( name: '', - builder: _i37.previewStartRemoteDocumentSigningScreen, + builder: _i38.previewStartRemoteDocumentSigningScreen, ), ), ], diff --git a/pubspec.lock b/pubspec.lock index 1ef81f4..76991d1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -417,6 +417,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + sha256: "5b24061317f850af858ef7151dadbb6eb77c1c449c954c7bb064e8a5e0e7d81f" + url: "https://pub.dev" + source: hosted + version: "0.6.20" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -603,6 +611,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 + url: "https://pub.dev" + source: hosted + version: "7.2.2" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fa2cca1..57adfa0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: url_launcher: mobile_scanner: html: + flutter_markdown: dev_dependencies: flutter_test: From 130b252adb87d0a1053fc0ae4580bbd88628d081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Ce=C4=BEuch?= <celuchmarek@gmail.com> Date: Sun, 30 Jun 2024 18:12:43 +0200 Subject: [PATCH 3/7] rm tokens from dependecny checkout and add .apk (#27) --- .github/workflows/android_package.yaml | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/android_package.yaml b/.github/workflows/android_package.yaml index ca0f212..6d9c927 100644 --- a/.github/workflows/android_package.yaml +++ b/.github/workflows/android_package.yaml @@ -31,14 +31,12 @@ jobs: uses: actions/checkout@v4 with: repository: slovensko-digital/eidmsdk-flutter - token: ${{ secrets.GH_PAT }} path: eidmsdk_flutter - name: Checkout "autogram_sign" uses: actions/checkout@v4 with: repository: slovensko-digital/avm-client-dart - token: ${{ secrets.GH_PAT }} path: autogram_sign - uses: actions/setup-java@v1 @@ -64,7 +62,7 @@ jobs: working-directory: ./app run: echo $ENCODED_STRING | base64 -d > release_keystore.jks - - name: Build + - name: Build Googla Play .aab env: AVM_KEYSTORE_FILE: ../../release_keystore.jks AVM_KEYSTORE_PASSWORD: ${{ secrets.GOOGLE_RELEASE_KEYSTORE_PASSWORD }} @@ -73,12 +71,27 @@ jobs: working-directory: ./app run: flutter build appbundle --release - - name: Upload Release Build to Artifacts + - name: Build Andorid .apk + env: + AVM_KEYSTORE_FILE: ../../release_keystore.jks + AVM_KEYSTORE_PASSWORD: ${{ secrets.GOOGLE_RELEASE_KEYSTORE_PASSWORD }} + AVM_KEY_ALIAS: ${{ secrets.GOOGLE_RELEASE_KEYSTORE_ALIAS }} + AVM_KEY_PASSWORD: ${{ secrets.GOOGLE_RELEASE_KEY_PASSWORD }} + working-directory: ./app + run: flutter build apk --release + + - name: Upload .aab Build to Artifacts uses: actions/upload-artifact@v3 with: name: release-artifacts path: ./app/build/app/outputs/bundle/release/app-release.aab + - name: Upload .apk Build to Artifacts + uses: actions/upload-artifact@v3 + with: + name: release-artifacts + path: ./app/build/app/outputs/apk/release/app-release.apk + - name: Create release if tag pushed uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 if: startsWith(github.ref, 'refs/tags/') @@ -89,3 +102,4 @@ jobs: prerelease: true files: | ./app/build/app/outputs/bundle/release/app-release.aab + ./app/build/app/outputs/apk/release/app-release.apk From 34fc6db9180c43505dd772102f2809d12de78a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Ce=C4=BEuch?= <celuchmarek@gmail.com> Date: Tue, 2 Jul 2024 13:31:24 +0200 Subject: [PATCH 4/7] mv eidmsdk secret token from gradle build (#28) * mv eidmsdk secret token from gradle build * use github token to download eidmsdk pkg * use personal pat for eidmsdk * add info about gh pat env to readme --- .github/workflows/android_package.yaml | 2 ++ README.md | 2 ++ android/build.gradle | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/android_package.yaml b/.github/workflows/android_package.yaml index 6d9c927..b4da0b1 100644 --- a/.github/workflows/android_package.yaml +++ b/.github/workflows/android_package.yaml @@ -68,6 +68,7 @@ jobs: AVM_KEYSTORE_PASSWORD: ${{ secrets.GOOGLE_RELEASE_KEYSTORE_PASSWORD }} AVM_KEY_ALIAS: ${{ secrets.GOOGLE_RELEASE_KEYSTORE_ALIAS }} AVM_KEY_PASSWORD: ${{ secrets.GOOGLE_RELEASE_KEY_PASSWORD }} + EIDMSDK_ACCESS_TOKEN: ${{ secrets.GH_PAT }} working-directory: ./app run: flutter build appbundle --release @@ -77,6 +78,7 @@ jobs: AVM_KEYSTORE_PASSWORD: ${{ secrets.GOOGLE_RELEASE_KEYSTORE_PASSWORD }} AVM_KEY_ALIAS: ${{ secrets.GOOGLE_RELEASE_KEYSTORE_ALIAS }} AVM_KEY_PASSWORD: ${{ secrets.GOOGLE_RELEASE_KEY_PASSWORD }} + EIDMSDK_ACCESS_TOKEN: ${{ secrets.GH_PAT }} working-directory: ./app run: flutter build apk --release diff --git a/README.md b/README.md index 1441f22..f981baa 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,8 @@ fvm dart run build_runner build --delete-conflicting-outputs Build **Android** APK: +eid-mSDK binaries are hosted on GitHub package registry. To access the package during build process environment variable `EIDMSDK_ACCESS_TOKEN` needs to be set to a GitHub Personal Access Token that has permission to read package registry. + ```shell fvm flutter build apk ``` diff --git a/android/build.gradle b/android/build.gradle index 9298f83..d22d8a6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -18,7 +18,7 @@ allprojects { url = "https://maven.pkg.github.com/eIDmSDK/eID-mSDK-Android/" credentials { username = "eIDmSDK" - password = "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" + password = System.getenv("EIDMSDK_ACCESS_TOKEN") } } } From b7838a28dc72b0ea55923f63afc7d55b4fcb114a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Hlatk=C3=BD?= <hlatky@freevision.sk> Date: Mon, 15 Jul 2024 10:16:43 +0200 Subject: [PATCH 5/7] Crashlytics enhanced (#30) --- CHANGELOG.md | 10 +++-- lib/app_navigator_observer.dart | 26 +++++++------ lib/bloc/create_document_cubit.dart | 2 +- lib/bloc/paired_device_list_cubit.dart | 2 +- lib/bloc/present_signed_document_cubit.dart | 11 +++--- lib/bloc/preview_document_cubit.dart | 2 +- lib/bloc/sign_document_cubit.dart | 2 +- lib/deep_links.dart | 2 +- lib/file_extensions.dart | 7 ++++ lib/main.dart | 39 ++++++++++++------- .../select_signing_certificate_fragment.dart | 1 + lib/ui/onboarding.dart | 7 +++- lib/ui/remote_document_signing.dart | 7 +++- lib/ui/screens/main_menu_screen.dart | 7 +++- lib/ui/screens/main_screen.dart | 8 +++- .../present_signed_document_screen.dart | 1 + lib/ui/screens/select_certificate_screen.dart | 1 + lib/ui/screens/settings_screen.dart | 3 ++ lib/utils.dart | 17 ++++++++ pubspec.lock | 16 ++++---- pubspec.yaml | 2 +- test/utils_test.dart | 36 +++++++++++++++++ 22 files changed, 155 insertions(+), 54 deletions(-) create mode 100644 test/utils_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 14c8076..cced286 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ # Changelog -## NEXT - v1.0.2(33) +## NEXT - v1.0.3(34) + +- #30 | Logging into Crashlytics + +## 2024-06-30 - v1.0.2(33) - Refactor Settings model - #18 | Allow sharing signed Document even when failed to save file -- About screen - add links into "about" text -- About screen - fix vertical content overflow on smaller screens +- #26 | About screen - add links into "about" text +- #26 | About screen - fix vertical content overflow on smaller screens ## 2024-06-17 - v1.0.1(1) diff --git a/lib/app_navigator_observer.dart b/lib/app_navigator_observer.dart index 5f66610..6c3257b 100644 --- a/lib/app_navigator_observer.dart +++ b/lib/app_navigator_observer.dart @@ -7,22 +7,22 @@ class AppNavigatorObserver extends NavigatorObserver { @override void didPush(Route route, Route? previousRoute) { - _log("Did push", {'route': route, 'previousRoute': previousRoute}); + _log("didPush", {'route': route, 'previousRoute': previousRoute}); } @override void didPop(Route route, Route? previousRoute) { - _log("Did pop", {'route': route, 'previousRoute': previousRoute}); + _log("didPop", {'route': route, 'previousRoute': previousRoute}); } @override void didRemove(Route route, Route? previousRoute) { - _log("Did remove", {'route': route, 'previousRoute': previousRoute}); + _log("didRemove", {'route': route, 'previousRoute': previousRoute}); } @override void didReplace({Route? newRoute, Route? oldRoute}) { - _log("Did replace", {'newRoute': newRoute, 'oldRoute': oldRoute}); + _log("didReplace", {'newRoute': newRoute, 'oldRoute': oldRoute}); } /// Logs navigation event. @@ -32,22 +32,24 @@ class AppNavigatorObserver extends NavigatorObserver { .map((param) => "$param: ${routes[param]?.debug}") .join(", "); - _logger.info("$event - $params"); + _logger.info("$event($params)"); } } extension _RouteExtensions<T> on Route<T> { /// Returns debug string for this [Route]. String? get debug { - final route = this; + final name = settings.name; - if (route is! MaterialPageRoute) { - return route.settings.name; - } + if (name != null && name.isNotEmpty) { + return name; + } else if (this is MaterialPageRoute) { + final text = (this as MaterialPageRoute?)?.builder.runtimeType.toString(); - final text = (route as MaterialPageRoute).builder.runtimeType.toString(); + // "(BuildContext) => NameScreen" + return text?.replaceFirst("(BuildContext) => ", ""); + } - // "(BuildContext) => NameScreen" - return text.replaceFirst("(BuildContext) => ", ""); + return null; } } diff --git a/lib/bloc/create_document_cubit.dart b/lib/bloc/create_document_cubit.dart index 32860d0..bdd48c9 100644 --- a/lib/bloc/create_document_cubit.dart +++ b/lib/bloc/create_document_cubit.dart @@ -22,7 +22,7 @@ export 'create_document_state.dart'; /// - [PreviewDocumentCubit] @injectable class CreateDocumentCubit extends Cubit<CreateDocumentState> { - static final _log = Logger("CreateDocumentCubit"); + static final _log = Logger((CreateDocumentCubit).toString()); final IAutogramService _service; final FutureOr<File> _file; diff --git a/lib/bloc/paired_device_list_cubit.dart b/lib/bloc/paired_device_list_cubit.dart index 99c9048..8970829 100644 --- a/lib/bloc/paired_device_list_cubit.dart +++ b/lib/bloc/paired_device_list_cubit.dart @@ -13,7 +13,7 @@ export 'paired_device_list_state.dart'; /// Cubit for the [PairedDeviceListScreen] with only [load] function. @injectable class PairedDeviceListCubit extends Cubit<PairedDeviceListState> { - static final _log = Logger("PairedDeviceListScreen"); + static final _log = Logger((PairedDeviceListScreen).toString()); // ignore: unused_field final IAutogramService _service; diff --git a/lib/bloc/present_signed_document_cubit.dart b/lib/bloc/present_signed_document_cubit.dart index 550821c..3308d6e 100644 --- a/lib/bloc/present_signed_document_cubit.dart +++ b/lib/bloc/present_signed_document_cubit.dart @@ -23,7 +23,7 @@ export 'present_signed_document_state.dart'; /// which can be shared. @injectable class PresentSignedDocumentCubit extends Cubit<PresentSignedDocumentState> { - static final _log = Logger("PresentSignedDocumentCubit"); + static final _log = Logger((PresentSignedDocumentCubit).toString()); static final _tsDateFormat = DateFormat('yyyyMMddHHmmss'); final AppService _appService; @@ -38,7 +38,8 @@ class PresentSignedDocumentCubit extends Cubit<PresentSignedDocumentState> { /// Saves [signedDocument] into public directory. Future<void> saveDocument() async { - _log.info("Saving signed document: ${signedDocument.filename}."); + _log.info( + "Saving signed document: ${File(signedDocument.filename).redactedInfo}."); emit(state.toLoading()); @@ -50,12 +51,12 @@ class PresentSignedDocumentCubit extends Cubit<PresentSignedDocumentState> { // Need to change PresentSignedDocumentSuccessState impl. to allow File? await _saveDocumentIntoFile(file!); - _log.info("Signed Document was saved into $file"); + _log.info("Signed Document was saved into ${file.redactedInfo}"); emit(state.toSuccess(file)); } catch (error, stackTrace) { - _log.severe( - "Error saving signed Document into $file.", error, stackTrace); + _log.severe("Error saving signed Document into ${file?.redactedInfo}.", + error, stackTrace); emit(state.toError(error)); } diff --git a/lib/bloc/preview_document_cubit.dart b/lib/bloc/preview_document_cubit.dart index 2650854..fe01598 100644 --- a/lib/bloc/preview_document_cubit.dart +++ b/lib/bloc/preview_document_cubit.dart @@ -15,7 +15,7 @@ export 'preview_document_state.dart'; /// - [CreateDocumentCubit] @injectable class PreviewDocumentCubit extends Cubit<PreviewDocumentState> { - static final _log = Logger("PreviewDocumentCubit"); + static final _log = Logger((PreviewDocumentCubit).toString()); final IAutogramService _service; final String documentId; diff --git a/lib/bloc/sign_document_cubit.dart b/lib/bloc/sign_document_cubit.dart index c000073..a3c2ccc 100644 --- a/lib/bloc/sign_document_cubit.dart +++ b/lib/bloc/sign_document_cubit.dart @@ -14,7 +14,7 @@ export 'sign_document_state.dart'; /// Cubit for the [SignDocumentScreen]. @injectable class SignDocumentCubit extends Cubit<SignDocumentState> { - static final _log = Logger("SignDocumentCubit"); + static final _log = Logger((SignDocumentCubit).toString()); static const _defaultLanguage = 'sk'; final IAutogramService _service; diff --git a/lib/deep_links.dart b/lib/deep_links.dart index 94c2180..8807c6d 100644 --- a/lib/deep_links.dart +++ b/lib/deep_links.dart @@ -6,7 +6,7 @@ /// - path: "/api/v1/" /// /// Throws [ArgumentError] in case of invalid or unknown schema or structure. -// TODO Move this code + test into autogram_sign module +// TODO Move this code + test into autogram_sign module; however, it needs to check uri.authority, so it should be function on IAutogramService DeepLinkAction parseDeepLinkAction(Uri uri) { // Validate schema, authority and path switch (uri.scheme) { diff --git a/lib/file_extensions.dart b/lib/file_extensions.dart index 5bafb0c..2d84f8d 100644 --- a/lib/file_extensions.dart +++ b/lib/file_extensions.dart @@ -1,5 +1,6 @@ import 'dart:io' show File; +import 'package:flutter/foundation.dart'; import 'package:path/path.dart' as path; /// Set of extensions on [File] type. @@ -13,4 +14,10 @@ extension FileExtensions on File { /// Calls [path.basenameWithoutExtension] on this [File]. String get basenameWithoutExtension => path.basenameWithoutExtension(this.path); + + /// Returns redacted file info usable for logging. + /// + /// In case of [kDebugMode], full [File.path] is returned; + /// "???.[FileExtensions.extension]" otherwise. + String get redactedInfo => (kDebugMode ? this.path : "???$extension"); } diff --git a/lib/main.dart b/lib/main.dart index f2c70e6..47ea1d5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,8 @@ import 'dart:developer' as developer; import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart' show WidgetsFlutterBinding, runApp; import 'package:logging/logging.dart' show Level, LogRecord, Logger; import 'package:provider/provider.dart' show MultiProvider, Provider; @@ -9,20 +11,21 @@ import 'app.dart'; import 'data/settings.dart'; import 'di.dart'; import 'firebase_options.dart'; +import 'utils.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - // Setup Logger - Logger.root - ..level = Level.ALL - ..onRecord.listen(_onRecord); - // Setup Firebase await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + // Setup Logger + Logger.root + ..level = Level.ALL + ..onRecord.listen(_onRecord); + // Setup DI configureDependencies(); @@ -40,15 +43,21 @@ void main() async { ); } +/// [Logger] log callback. void _onRecord(final LogRecord record) { - developer.log( - record.message, - name: record.loggerName, - time: record.time, - sequenceNumber: record.sequenceNumber, - level: record.level.value, - zone: record.zone, - error: record.error, - stackTrace: record.stackTrace, - ); + if (kDebugMode) { + developer.log( + record.message, + name: record.loggerName, + time: record.time, + sequenceNumber: record.sequenceNumber, + level: record.level.value, + zone: record.zone, + error: record.error, + stackTrace: record.stackTrace, + ); + } else { + // TODO Collect also some Settings (acceptedPrivacyPolicyVersion, acceptedTermsOfServiceVersion) on app init and on change + FirebaseCrashlytics.instance.log(formatCrashlyticsLog(record)); + } } diff --git a/lib/ui/fragment/select_signing_certificate_fragment.dart b/lib/ui/fragment/select_signing_certificate_fragment.dart index 5997739..779e497 100644 --- a/lib/ui/fragment/select_signing_certificate_fragment.dart +++ b/lib/ui/fragment/select_signing_certificate_fragment.dart @@ -65,6 +65,7 @@ class SelectSigningCertificateFragment extends StatelessWidget { Uri.parse(strings.selectSigningCertificateNoCertificateGuideUrl); final textStyle = Theme.of(context).textTheme.bodyMedium; + // TODO Use MarkdownText instead of RichText final body = RichText( text: TextSpan( children: [ diff --git a/lib/ui/onboarding.dart b/lib/ui/onboarding.dart index 0bba5a0..b745247 100644 --- a/lib/ui/onboarding.dart +++ b/lib/ui/onboarding.dart @@ -62,7 +62,12 @@ abstract class Onboarding { Widget screen, [ bool replace = false, ]) { - final route = MaterialPageRoute(builder: (_) => screen); + final route = MaterialPageRoute( + settings: RouteSettings( + name: screen.runtimeType.toString(), + ), + builder: (_) => screen, + ); final navigator = Navigator.of(context); return replace ? navigator.pushReplacement(route) : navigator.push(route); diff --git a/lib/ui/remote_document_signing.dart b/lib/ui/remote_document_signing.dart index 19dba95..4e39d41 100644 --- a/lib/ui/remote_document_signing.dart +++ b/lib/ui/remote_document_signing.dart @@ -44,8 +44,13 @@ abstract class RemoteDocumentSigning { Widget screen, [ bool replace = false, ]) { + final route = MaterialPageRoute( + settings: RouteSettings( + name: screen.runtimeType.toString(), + ), + builder: (_) => screen, + ); final navigator = Navigator.of(context); - final route = MaterialPageRoute(builder: (_) => screen); return replace ? navigator.pushReplacement(route) : navigator.push(route); } diff --git a/lib/ui/screens/main_menu_screen.dart b/lib/ui/screens/main_menu_screen.dart index 3e8e7b4..b49bb8b 100644 --- a/lib/ui/screens/main_menu_screen.dart +++ b/lib/ui/screens/main_menu_screen.dart @@ -127,7 +127,12 @@ class MainMenuScreen extends StatelessWidget { } static Future<void> _openScreen(BuildContext context, Widget screen) { - final route = MaterialPageRoute(builder: (_) => screen); + final route = MaterialPageRoute( + settings: RouteSettings( + name: screen.runtimeType.toString(), + ), + builder: (_) => screen, + ); return Navigator.of(context).pushReplacement(route); } diff --git a/lib/ui/screens/main_screen.dart b/lib/ui/screens/main_screen.dart index 6542592..1c85709 100644 --- a/lib/ui/screens/main_screen.dart +++ b/lib/ui/screens/main_screen.dart @@ -14,6 +14,7 @@ import '../../app_service.dart'; import '../../bloc/app_bloc.dart'; import '../../deep_links.dart'; import '../../di.dart'; +import '../../file_extensions.dart'; import '../../services/encryption_key_registry.dart'; import '../../strings_context.dart'; import '../app_theme.dart'; @@ -126,6 +127,9 @@ class _MainScreenState extends State<MainScreen> { return showGeneralDialog( context: context, + routeSettings: RouteSettings( + name: screen.runtimeType.toString(), + ), pageBuilder: (context, _, __) => screen, ); } @@ -205,9 +209,9 @@ class _MainScreenState extends State<MainScreen> { final selectedFile = result?.files.singleOrNull; if (selectedFile != null) { - final File file = File(selectedFile.path!); + final file = File(selectedFile.path!); - _logger.fine('File selected: $file'); + _logger.fine('File selected: ${file.redactedInfo}'); if (context.mounted) { _openNewFile(file); diff --git a/lib/ui/screens/present_signed_document_screen.dart b/lib/ui/screens/present_signed_document_screen.dart index 777b8d9..983f78d 100644 --- a/lib/ui/screens/present_signed_document_screen.dart +++ b/lib/ui/screens/present_signed_document_screen.dart @@ -198,6 +198,7 @@ class _SuccessContent extends StatelessWidget { decoration: TextDecoration.underline, fontWeight: FontWeight.bold, ); + // TODO Use MarkdownText instead of RichText body = RichText( text: TextSpan( text: strings.saveSignedDocumentSuccessMessage, diff --git a/lib/ui/screens/select_certificate_screen.dart b/lib/ui/screens/select_certificate_screen.dart index aefa393..74a78e3 100644 --- a/lib/ui/screens/select_certificate_screen.dart +++ b/lib/ui/screens/select_certificate_screen.dart @@ -232,6 +232,7 @@ class _SelectSignatureTypeContentState error: state.error, ), GetDocumentSignatureTypeSuccessState state => SignatureTypePicker( + // TODO Check why not passing state.signatureType value: _signatureType, canChange: (widget.signingType == DocumentSigningType.local), onValueChanged: (final SignatureType value) { diff --git a/lib/ui/screens/settings_screen.dart b/lib/ui/screens/settings_screen.dart index 2cb3a96..a1922c8 100644 --- a/lib/ui/screens/settings_screen.dart +++ b/lib/ui/screens/settings_screen.dart @@ -205,6 +205,9 @@ class _ValueListenableBoundTile<T> extends StatelessWidget { Future<void> _onEditItemRequested(BuildContext context, T value) async { final result = await showDialog<T>( context: context, + routeSettings: RouteSettings( + name: "Edit${T}Dialog", + ), builder: (context) { var selectedValue = setting.value; diff --git a/lib/utils.dart b/lib/utils.dart index 445c059..b6060ac 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -3,6 +3,7 @@ import 'dart:math' show Random; import 'package:basic_utils/basic_utils.dart' show X509CertificateData, X509Utils; +import 'package:logging/logging.dart' show LogRecord, Level; final Random _random = Random.secure(); @@ -26,3 +27,19 @@ X509CertificateData x509CertificateDataFromDer(String data) { return X509Utils.x509CertificateFromPem(pem); } + +/// Format given [log] as single message. +String formatCrashlyticsLog(final LogRecord log) { + final level = switch (log.level) { + Level.FINEST || Level.FINER || Level.FINE || Level.CONFIG => 'D', + Level.INFO => 'I', + Level.WARNING => 'W', + Level.SEVERE || Level.SHOUT => 'E', + _ => "?" + }; + final tag = log.loggerName; + final line1 = "${log.time}: $level/$tag: ${log.message}"; + final error = log.error; + + return (error != null ? "$line1\n$error" : line1); +} diff --git a/pubspec.lock b/pubspec.lock index 76991d1..b672e47 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -455,10 +455,10 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + sha256: f9f6597ac43cc262fa7d7f2e65259a6060c23a560525d1f2631be374540f2a9b url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" frontend_server_client: dependency: transitive description: @@ -775,10 +775,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" pdf: dependency: "direct main" description: @@ -1212,10 +1212,10 @@ packages: dependency: transitive description: name: uuid - sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.4.2" vector_graphics: dependency: transitive description: @@ -1356,10 +1356,10 @@ packages: dependency: "direct main" description: name: widgetbook - sha256: "872e7e9065ef6e85a1e93b3b41830f90af575c5a898b6c573acdc972fad0fb29" + sha256: de5b9887f9ad663bdcc1f957bc22aa0eb4dd7b406ac0142158bf35c0fad4a4f2 url: "https://pub.dev" source: hosted - version: "3.8.0" + version: "3.8.1" widgetbook_annotation: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 57adfa0..eb1a571 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: autogram description: "Autogram v mobile" publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.2+33 +version: 1.0.3+34 environment: sdk: '>=3.2.3 <4.0.0' diff --git a/test/utils_test.dart b/test/utils_test.dart new file mode 100644 index 0000000..21def36 --- /dev/null +++ b/test/utils_test.dart @@ -0,0 +1,36 @@ +import 'dart:io'; + +import 'package:autogram/utils.dart'; +import 'package:logging/logging.dart' show Level, LogRecord; +import 'package:test/test.dart'; + +/// Tests for the [formatCrashlyticsLog] function. +void main() { + group('formatCrashlyticsLog', () { + test('formatCrashlyticsLog formats simple log properly', () { + final infoLog = LogRecord(Level.INFO, 'Hello world!', 'Logger1'); + final configLog = + LogRecord(Level.CONFIG, 'This has been configured.', 'ConfigLogger'); + + expect( + formatCrashlyticsLog(infoLog), + '${infoLog.time}: I/Logger1: Hello world!', + ); + expect( + formatCrashlyticsLog(configLog), + '${configLog.time}: D/ConfigLogger: This has been configured.', + ); + }); + + test('formatCrashlyticsLog formats log with error properly', () { + const error = SocketException('No Internets!'); + final errorLog = + LogRecord(Level.SEVERE, 'Unable to download file.', 'FileDownloader', error); + + expect( + formatCrashlyticsLog(errorLog), + '${errorLog.time}: E/FileDownloader: Unable to download file.\n$error', + ); + }); + }); +} From acd9126a2e21f6c2d71aca43e7a77b272c02fcd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Hlatk=C3=BD?= <hlatky@freevision.sk> Date: Tue, 16 Jul 2024 13:40:33 +0200 Subject: [PATCH 6/7] Fix iOS Domain Association (#31) --- CHANGELOG.md | 3 +- ios/Podfile.lock | 67 ++++++++++++++++++++++++++++++++++ ios/Runner/Runner.entitlements | 4 ++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cced286..df55f90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # Changelog -## NEXT - v1.0.3(34) +## 2024-07-16 - v1.0.3(34) - #30 | Logging into Crashlytics +- #16 | Fix iOS URL domain association ## 2024-06-30 - v1.0.2(33) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index fd6988d..be6f800 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -84,6 +84,23 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) + - GoogleMLKit/BarcodeScanning (4.0.0): + - GoogleMLKit/MLKitCore + - MLKitBarcodeScanning (~> 3.0.0) + - GoogleMLKit/MLKitCore (4.0.0): + - MLKitCommon (~> 9.0.0) + - GoogleToolboxForMac/DebugUtils (2.3.2): + - GoogleToolboxForMac/Defines (= 2.3.2) + - GoogleToolboxForMac/Defines (2.3.2) + - GoogleToolboxForMac/Logger (2.3.2): + - GoogleToolboxForMac/Defines (= 2.3.2) + - "GoogleToolboxForMac/NSData+zlib (2.3.2)": + - GoogleToolboxForMac/Defines (= 2.3.2) + - "GoogleToolboxForMac/NSDictionary+URLArguments (2.3.2)": + - GoogleToolboxForMac/DebugUtils (= 2.3.2) + - GoogleToolboxForMac/Defines (= 2.3.2) + - "GoogleToolboxForMac/NSString+URLArguments (= 2.3.2)" + - "GoogleToolboxForMac/NSString+URLArguments (2.3.2)" - GoogleUtilities/Environment (7.13.0): - GoogleUtilities/Privacy - PromisesObjC (< 3.0, >= 1.2) @@ -96,8 +113,32 @@ PODS: - GoogleUtilities/UserDefaults (7.13.0): - GoogleUtilities/Logger - GoogleUtilities/Privacy + - GoogleUtilitiesComponents (1.1.0): + - GoogleUtilities/Logger + - GTMSessionFetcher/Core (2.3.0) - JWTDecode (3.1.0) - lottie-ios (4.4.0) + - MLImage (1.0.0-beta4) + - MLKitBarcodeScanning (3.0.0): + - MLKitCommon (~> 9.0) + - MLKitVision (~> 5.0) + - MLKitCommon (9.0.0): + - GoogleDataTransport (~> 9.0) + - GoogleToolboxForMac/Logger (~> 2.1) + - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" + - "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)" + - GoogleUtilities/UserDefaults (~> 7.0) + - GoogleUtilitiesComponents (~> 1.0) + - GTMSessionFetcher/Core (< 3.0, >= 1.1) + - MLKitVision (5.0.0): + - GoogleToolboxForMac/Logger (~> 2.1) + - "GoogleToolboxForMac/NSData+zlib (~> 2.1)" + - GTMSessionFetcher/Core (< 3.0, >= 1.1) + - MLImage (= 1.0.0-beta4) + - MLKitCommon (~> 9.0) + - mobile_scanner (3.5.6): + - Flutter + - GoogleMLKit/BarcodeScanning (~> 4.0.0) - nanopb (2.30910.0): - nanopb/decode (= 2.30910.0) - nanopb/encode (= 2.30910.0) @@ -125,6 +166,8 @@ PODS: - SwiftyGif (5.4.4) - url_launcher_ios (0.0.1): - Flutter + - wakelock_plus (0.0.1): + - Flutter - webview_flutter_wkwebview (0.0.1): - Flutter @@ -134,12 +177,14 @@ DEPENDENCIES: - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) - Flutter (from `Flutter`) + - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - printing (from `.symlinks/plugins/printing/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) SPEC REPOS: @@ -154,9 +199,17 @@ SPEC REPOS: - FirebaseInstallations - FirebaseSessions - GoogleDataTransport + - GoogleMLKit + - GoogleToolboxForMac - GoogleUtilities + - GoogleUtilitiesComponents + - GTMSessionFetcher - JWTDecode - lottie-ios + - MLImage + - MLKitBarcodeScanning + - MLKitCommon + - MLKitVision - nanopb - OpenSSL-Universal - PromisesObjC @@ -175,6 +228,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_crashlytics/ios" Flutter: :path: Flutter + mobile_scanner: + :path: ".symlinks/plugins/mobile_scanner/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: @@ -187,6 +242,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" + wakelock_plus: + :path: ".symlinks/plugins/wakelock_plus/ios" webview_flutter_wkwebview: :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" @@ -206,9 +263,18 @@ SPEC CHECKSUMS: FirebaseSessions: 2651b464e241c93fd44112f995d5ab663c970487 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a + GoogleMLKit: 2bd0dc6253c4d4f227aad460f69215a504b2980e + GoogleToolboxForMac: 8bef7c7c5cf7291c687cf5354f39f9db6399ad34 GoogleUtilities: d053d902a8edaa9904e1bd00c37535385b8ed152 + GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe + GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2 JWTDecode: 3eaab1e06b6f4dcbdd6716aff09ba4c2104ca8b7 lottie-ios: ef1be1f90d54255f08e09d767950e43714661178 + MLImage: 7bb7c4264164ade9bf64f679b40fb29c8f33ee9b + MLKitBarcodeScanning: 04e264482c5f3810cb89ebc134ef6b61e67db505 + MLKitCommon: c1b791c3e667091918d91bda4bba69a91011e390 + MLKitVision: 8baa5f46ee3352614169b85250574fde38c36f49 + mobile_scanner: 38dcd8a49d7d485f632b7de65e4900010187aef2 nanopb: 438bc412db1928dac798aa6fd75726007be04262 OpenSSL-Universal: 6e1ae0555546e604dbc632a2b9a24a9c46c41ef6 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 @@ -221,6 +287,7 @@ SPEC CHECKSUMS: shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 + wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 webview_flutter_wkwebview: 4f3e50f7273d31e5500066ed267e3ae4309c5ae4 PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5 diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index 2bb4dee..44bec6d 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -2,6 +2,10 @@ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> + <key>com.apple.developer.associated-domains</key> + <array> + <string>applinks:autogram.slovensko.digital</string> + </array> <key>com.apple.developer.nfc.readersession.formats</key> <array> <string>TAG</string> From 83dbbefa08071d3e679af0b35a4b659cf2c6c8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Hlatk=C3=BD?= <hlatky@freevision.sk> Date: Wed, 31 Jul 2024 09:33:34 +0200 Subject: [PATCH 7/7] Cleanup (#32) --- lib/app_service.dart | 2 +- lib/bloc/create_document_cubit.dart | 2 +- lib/bloc/present_signed_document_cubit.dart | 1 + lib/directory_extensions.dart | 4 +- lib/file_extensions.dart | 15 +---- lib/file_system_entity_extensions.dart | 23 +++++++ lib/files.dart | 2 +- lib/l10n/app_localizations.dart | 12 +--- lib/l10n/app_localizations_sk.dart | 9 ++- lib/l10n/app_sk.arb | 5 +- .../select_signing_certificate_fragment.dart | 38 ++---------- .../present_signed_document_screen.dart | 61 +++++++++---------- lib/ui/screens/preview_document_screen.dart | 2 +- pubspec.lock | 20 +++--- 14 files changed, 85 insertions(+), 111 deletions(-) create mode 100644 lib/file_system_entity_extensions.dart diff --git a/lib/app_service.dart b/lib/app_service.dart index f863b1e..3ccde09 100644 --- a/lib/app_service.dart +++ b/lib/app_service.dart @@ -6,7 +6,7 @@ import 'package:injectable/injectable.dart'; import 'package:logging/logging.dart' show Logger; import 'package:path_provider/path_provider.dart' as provider; -import 'file_extensions.dart'; +import 'file_system_entity_extensions.dart'; /// Provides platform (iOS and Android) specific app functions: /// diff --git a/lib/bloc/create_document_cubit.dart b/lib/bloc/create_document_cubit.dart index bdd48c9..52c8c37 100644 --- a/lib/bloc/create_document_cubit.dart +++ b/lib/bloc/create_document_cubit.dart @@ -8,7 +8,7 @@ import 'package:injectable/injectable.dart'; import 'package:logging/logging.dart'; import '../data/pdf_signing_option.dart'; -import '../file_extensions.dart'; +import '../file_system_entity_extensions.dart'; import '../files.dart'; import '../ui/screens/open_document_screen.dart'; import 'create_document_state.dart'; diff --git a/lib/bloc/present_signed_document_cubit.dart b/lib/bloc/present_signed_document_cubit.dart index 3308d6e..9de8cfb 100644 --- a/lib/bloc/present_signed_document_cubit.dart +++ b/lib/bloc/present_signed_document_cubit.dart @@ -12,6 +12,7 @@ import 'package:path_provider/path_provider.dart'; import '../app_service.dart'; import '../file_extensions.dart'; +import '../file_system_entity_extensions.dart'; import '../ui/screens/present_signed_document_screen.dart'; import 'present_signed_document_state.dart'; diff --git a/lib/directory_extensions.dart b/lib/directory_extensions.dart index 8e18dd8..a412832 100644 --- a/lib/directory_extensions.dart +++ b/lib/directory_extensions.dart @@ -1,12 +1,12 @@ import 'dart:io' show Directory, File; -import 'package:path/path.dart' as p; +import 'package:path/path.dart' as path; /// Set of extensions on [Directory] type. extension DirectoryExtensions on Directory { /// Returns flag indicating whether can write into this [Directory]. Future<bool> canWrite() async { - final tempFile = File(p.join(path, ".can_write")); + final tempFile = File(path.join(this.path, ".can_write")); try { await tempFile.writeAsBytes(const [], flush: true); diff --git a/lib/file_extensions.dart b/lib/file_extensions.dart index 2d84f8d..edff7df 100644 --- a/lib/file_extensions.dart +++ b/lib/file_extensions.dart @@ -1,23 +1,14 @@ import 'dart:io' show File; import 'package:flutter/foundation.dart'; -import 'package:path/path.dart' as path; + +import 'file_system_entity_extensions.dart'; /// Set of extensions on [File] type. extension FileExtensions on File { - /// Calls [path.extension] on this [File]. - String get extension => path.extension(this.path); - - /// Calls [path.basename] on this [File]. - String get basename => path.basename(this.path); - - /// Calls [path.basenameWithoutExtension] on this [File]. - String get basenameWithoutExtension => - path.basenameWithoutExtension(this.path); - /// Returns redacted file info usable for logging. /// /// In case of [kDebugMode], full [File.path] is returned; /// "???.[FileExtensions.extension]" otherwise. - String get redactedInfo => (kDebugMode ? this.path : "???$extension"); + String get redactedInfo => (kDebugMode ? path : "???$extension"); } diff --git a/lib/file_system_entity_extensions.dart b/lib/file_system_entity_extensions.dart new file mode 100644 index 0000000..268c31a --- /dev/null +++ b/lib/file_system_entity_extensions.dart @@ -0,0 +1,23 @@ +import 'dart:io' show FileSystemEntity; + +import 'package:path/path.dart' as path; + +import 'directory_extensions.dart'; +import 'file_extensions.dart'; + +/// Set of extensions on [FileSystemEntity] type. +/// +/// See also: +/// - [FileExtensions] +/// - [DirectoryExtensions] +extension FileSystemEntityExtensions on FileSystemEntity { + /// Calls [path.extension] on this [FileSystemEntity.path]. + String get extension => path.extension(this.path); + + /// Calls [path.basename] on this [FileSystemEntity.path]. + String get basename => path.basename(this.path); + + /// Calls [path.basenameWithoutExtension] on this [FileSystemEntity.path]. + String get basenameWithoutExtension => + path.basenameWithoutExtension(this.path); +} diff --git a/lib/files.dart b/lib/files.dart index dd8418b..a87cebe 100644 --- a/lib/files.dart +++ b/lib/files.dart @@ -1,6 +1,6 @@ import 'dart:io' show File; -import 'file_extensions.dart'; +import 'file_system_entity_extensions.dart'; abstract class Files { /// Mapping of extension => MIME type. diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index b6f8a2f..58b4080 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -490,15 +490,9 @@ abstract class AppLocalizations { /// No description provided for @selectSigningCertificateNoCertificateBody. /// /// In sk, this message translates to: - /// **'Nepodarilo sa nájsť certifikát pre kvalifikovaný elektronický podpis.\n\nCertifikát je potrebné vydať v aplikácii eID klient, prípadne použiť iný občiansky preukaz.\nNávod na vydanie certifikátu nájdete na '** + /// **'Nepodarilo sa nájsť certifikát pre **kvalifikovaný elektronický podpis**.\n\nCertifikát je potrebné vydať v aplikácii “eID Klient”, prípadne použiť iný občiansky preukaz.\nNávod na vydanie certifikátu nájdete na [navody.digital](https://navody.digital/zivotne-situacie/aktivacia-eid/krok/certifikaty).'** String get selectSigningCertificateNoCertificateBody; - /// No description provided for @selectSigningCertificateNoCertificateGuideUrl. - /// - /// In sk, this message translates to: - /// **'https://navody.digital/zivotne-situacie/aktivacia-eid/krok/certifikaty'** - String get selectSigningCertificateNoCertificateGuideUrl; - /// No description provided for @selectSigningCertificateErrorHeading. /// /// In sk, this message translates to: @@ -544,8 +538,8 @@ abstract class AppLocalizations { /// No description provided for @saveSignedDocumentSuccessMessage. /// /// In sk, this message translates to: - /// **'Dokument bol uložený do Downloads pod názvom '** - String get saveSignedDocumentSuccessMessage; + /// **'Dokument bol uložený do **{directory}** pod názvom [{name}](#).'** + String saveSignedDocumentSuccessMessage(Object directory, Object name); /// No description provided for @saveSignedDocumentErrorMessage. /// diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 43ff162..c869bf9 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -253,10 +253,7 @@ class AppLocalizationsSk extends AppLocalizations { String get selectSigningCertificateNoCertificateHeading => 'Certifikát nebol nájdený'; @override - String get selectSigningCertificateNoCertificateBody => 'Nepodarilo sa nájsť certifikát pre kvalifikovaný elektronický podpis.\n\nCertifikát je potrebné vydať v aplikácii eID klient, prípadne použiť iný občiansky preukaz.\nNávod na vydanie certifikátu nájdete na '; - - @override - String get selectSigningCertificateNoCertificateGuideUrl => 'https://navody.digital/zivotne-situacie/aktivacia-eid/krok/certifikaty'; + String get selectSigningCertificateNoCertificateBody => 'Nepodarilo sa nájsť certifikát pre **kvalifikovaný elektronický podpis**.\n\nCertifikát je potrebné vydať v aplikácii “eID Klient”, prípadne použiť iný občiansky preukaz.\nNávod na vydanie certifikátu nájdete na [navody.digital](https://navody.digital/zivotne-situacie/aktivacia-eid/krok/certifikaty).'; @override String get selectSigningCertificateErrorHeading => 'Chyba pri načítavaní certifikátov z občianskeho preukazu.'; @@ -284,7 +281,9 @@ class AppLocalizationsSk extends AppLocalizations { String get documentSigningSuccessTitle => 'Dokument bol úspešne podpísaný'; @override - String get saveSignedDocumentSuccessMessage => 'Dokument bol uložený do Downloads pod názvom '; + String saveSignedDocumentSuccessMessage(Object directory, Object name) { + return 'Dokument bol uložený do **$directory** pod názvom [$name](#).'; + } @override String saveSignedDocumentErrorMessage(Object error) { diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 17b2485..5ddffae 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -79,8 +79,7 @@ "selectSigningCertificateCanceledHeading": "Čítanie certifikátu bolo prerušené", "selectSigningCertificateCanceledBody": "Skúste prosím znovu načítať certifikát z vášho občianskeho preukazu.", "selectSigningCertificateNoCertificateHeading": "Certifikát nebol nájdený", - "selectSigningCertificateNoCertificateBody": "Nepodarilo sa nájsť certifikát pre kvalifikovaný elektronický podpis.\n\nCertifikát je potrebné vydať v aplikácii eID klient, prípadne použiť iný občiansky preukaz.\nNávod na vydanie certifikátu nájdete na ", - "selectSigningCertificateNoCertificateGuideUrl": "https://navody.digital/zivotne-situacie/aktivacia-eid/krok/certifikaty", + "selectSigningCertificateNoCertificateBody": "Nepodarilo sa nájsť certifikát pre **kvalifikovaný elektronický podpis**.\n\nCertifikát je potrebné vydať v aplikácii “eID Klient”, prípadne použiť iný občiansky preukaz.\nNávod na vydanie certifikátu nájdete na [navody.digital](https://navody.digital/zivotne-situacie/aktivacia-eid/krok/certifikaty).", "selectSigningCertificateErrorHeading": "Chyba pri načítavaní certifikátov z občianskeho preukazu.", @@ -92,7 +91,7 @@ "signDocumentErrorHeading": "Pri podpisovaní sa vyskytla chyba", "documentSigningSuccessTitle": "Dokument bol úspešne podpísaný", - "saveSignedDocumentSuccessMessage": "Dokument bol uložený do Downloads pod názvom ", + "saveSignedDocumentSuccessMessage": "Dokument bol uložený do **{directory}** pod názvom [{name}](#).", "saveSignedDocumentErrorMessage": "Pri ukladaní súboru sa vyskytla chyba:\n{error}", "shareSignedDocumentLabel": "Zdieľať podpísaný dokument", diff --git a/lib/ui/fragment/select_signing_certificate_fragment.dart b/lib/ui/fragment/select_signing_certificate_fragment.dart index 779e497..fb99bdb 100644 --- a/lib/ui/fragment/select_signing_certificate_fragment.dart +++ b/lib/ui/fragment/select_signing_certificate_fragment.dart @@ -1,12 +1,11 @@ import 'package:eidmsdk/types.dart' show Certificate; -import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart' show launchUrl; import '../../bloc/select_signing_certificate_cubit.dart'; import '../../strings_context.dart'; import '../widgets/error_content.dart'; import '../widgets/loading_content.dart'; +import '../widgets/markdown_text.dart'; import '../widgets/result_view.dart'; /// Fragment that is used in screens working with [SelectSigningCertificateState]. @@ -61,42 +60,13 @@ class SelectSigningCertificateFragment extends StatelessWidget { /// Partial content for "no certificates" state. static Widget noCertificatesContent(BuildContext context) { final strings = context.strings; - final address = - Uri.parse(strings.selectSigningCertificateNoCertificateGuideUrl); - - final textStyle = Theme.of(context).textTheme.bodyMedium; - // TODO Use MarkdownText instead of RichText - final body = RichText( - text: TextSpan( - children: [ - TextSpan( - text: strings.selectSigningCertificateNoCertificateBody, - style: textStyle, - ), - TextSpan( - text: address.authority, - style: textStyle?.copyWith( - color: Theme.of(context).colorScheme.primary, - decoration: TextDecoration.underline, - fontWeight: FontWeight.bold, - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - launchUrl(address); - }, - ), - TextSpan( - text: ".", - style: textStyle, - ), - ], - ), - ); return ResultView( icon: 'assets/images/lock.svg', titleText: strings.selectSigningCertificateNoCertificateHeading, - body: body, + body: MarkdownText( + strings.selectSigningCertificateNoCertificateBody, + ), ); } } diff --git a/lib/ui/screens/present_signed_document_screen.dart b/lib/ui/screens/present_signed_document_screen.dart index 983f78d..23bad56 100644 --- a/lib/ui/screens/present_signed_document_screen.dart +++ b/lib/ui/screens/present_signed_document_screen.dart @@ -2,7 +2,7 @@ import 'dart:developer' as developer; import 'dart:io' show File, OSError, PathAccessException; import 'package:autogram_sign/autogram_sign.dart' show SignDocumentResponseBody; -import 'package:flutter/gestures.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:share_plus/share_plus.dart'; @@ -12,11 +12,12 @@ import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; import '../../bloc/present_signed_document_cubit.dart'; import '../../data/document_signing_type.dart'; import '../../di.dart'; -import '../../file_extensions.dart'; +import '../../file_system_entity_extensions.dart'; import '../../strings_context.dart'; import '../../util/errors.dart'; import '../app_theme.dart'; import '../widgets/loading_content.dart'; +import '../widgets/markdown_text.dart'; import '../widgets/result_view.dart'; /// Screen for presenting signed document. @@ -153,11 +154,7 @@ class _Body extends StatelessWidget { sharingEnabled ? this.onShareFileRequested : null; return switch (state) { - PresentSignedDocumentInitialState _ => _SuccessContent( - file: null, - onShareFileRequested: null, - onCloseRequested: onCloseRequested, - ), + PresentSignedDocumentInitialState _ => const LoadingContent(), PresentSignedDocumentLoadingState _ => const LoadingContent(), PresentSignedDocumentErrorState _ => _SuccessContent( file: null, @@ -193,27 +190,15 @@ class _SuccessContent extends StatelessWidget { Widget body = const SizedBox(height: 58); if (file != null) { - final fileNameTextStyle = TextStyle( - color: Theme.of(context).colorScheme.primary, - decoration: TextDecoration.underline, - fontWeight: FontWeight.bold, - ); - // TODO Use MarkdownText instead of RichText - body = RichText( - text: TextSpan( - text: strings.saveSignedDocumentSuccessMessage, - style: Theme.of(context).textTheme.bodyLarge, - //style: TextStyle(color: Theme.of(context).colorScheme.onBackground), - children: [ - // Emphasize file name - TextSpan( - text: file.basename, - style: fileNameTextStyle, - recognizer: TapGestureRecognizer()..onTap = onShareFileRequested, - ) - ], - ), - textAlign: TextAlign.center, + final directory = _getParentDirectoryName(file); + final name = file.basename; + final text = strings.saveSignedDocumentSuccessMessage(directory, name); + + body = MarkdownText( + text, + onLinkTap: (_, __, ___) { + onShareFileRequested?.call(); + }, ); } @@ -249,6 +234,18 @@ class _SuccessContent extends StatelessWidget { ], ); } + + static String _getParentDirectoryName(File file) { + return kIsWeb + ? file.uri + .resolve('.') + .path + .split('/') + .where((e) => e.isNotEmpty) + .lastOrNull ?? + ' ' + : file.parent.basename; + } } @widgetbook.UseCase( @@ -341,11 +338,11 @@ Widget previewSuccessPresentSignedDocumentScreen(BuildContext context) { options: DocumentSigningType.values, initialOption: DocumentSigningType.local, ); - final fileName = context.knobs.string( - label: "File name", - initialValue: "document_signed.pdf", + final path = context.knobs.string( + label: "File path", + initialValue: "Downloads/document_signed.pdf", ); - final file = File(fileName); + final file = File(path); return _Body( state: PresentSignedDocumentSuccessState(file), diff --git a/lib/ui/screens/preview_document_screen.dart b/lib/ui/screens/preview_document_screen.dart index ed08e0c..df411fc 100644 --- a/lib/ui/screens/preview_document_screen.dart +++ b/lib/ui/screens/preview_document_screen.dart @@ -11,7 +11,7 @@ import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; import '../../bloc/preview_document_cubit.dart'; import '../../data/document_signing_type.dart'; -import '../../file_extensions.dart'; +import '../../file_system_entity_extensions.dart'; import '../../strings_context.dart'; import '../app_theme.dart'; import '../widgets/document_visualization.dart'; diff --git a/pubspec.lock b/pubspec.lock index b672e47..b66192c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -347,10 +347,10 @@ packages: dependency: transitive description: name: firebase_core_platform_interface - sha256: "1003a5a03a61fc9a22ef49f37cbcb9e46c86313a7b2e7029b9390cf8c6fc32cb" + sha256: "3c3a1e92d6f4916c32deea79c4a7587aa0e9dbbe5889c7a16afcf005a485ee02" url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.2.0" firebase_core_web: dependency: transitive description: @@ -455,10 +455,10 @@ packages: dependency: transitive description: name: freezed_annotation - sha256: f9f6597ac43cc262fa7d7f2e65259a6060c23a560525d1f2631be374540f2a9b + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.4" frontend_server_client: dependency: transitive description: @@ -879,10 +879,10 @@ packages: dependency: transitive description: name: qs_dart - sha256: "5f1827ccdfa061582c121e7a8fe4a83319fa455bcd1fd6e46ff5b17b57aed680" + sha256: bc7ec5dab9b4d92b5404146736f99953d38a68ffe65cafe0ebb6952e20261571 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" recase: dependency: transitive description: @@ -967,10 +967,10 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + sha256: "034650b71e73629ca08a0bd789fd1d83cc63c2d1e405946f7cef7bc37432f93a" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" shared_preferences_web: dependency: transitive description: @@ -1204,10 +1204,10 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" uuid: dependency: transitive description: