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 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: