diff --git a/.github/workflows/android_package.yaml b/.github/workflows/android_package.yaml new file mode 100644 index 0000000..ca0f212 --- /dev/null +++ b/.github/workflows/android_package.yaml @@ -0,0 +1,91 @@ +name: Build and sign application for Android + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+([0-9]+)" + + workflow_dispatch: + inputs: + name: + description: "Release-Build-Android" + default: "Generate release build for Android" + + +permissions: + contents: write + +jobs: + build: + environment: packaging + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + path: app + + - name: Checkout "eidmsdk_flutter" + 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 + with: + java-version: '17.x' + + - uses: subosito/flutter-action@v1 + with: + flutter-version: '3.16.5' + channel: 'stable' + + - name: Install dependencies + working-directory: ./app + run: flutter pub get + + - name: Test + working-directory: ./app + run: flutter test + + - name: Decode Keystore + env: + ENCODED_STRING: ${{ secrets.GOOGLE_KEYSTORE_BASE_64 }} + working-directory: ./app + run: echo $ENCODED_STRING | base64 -d > release_keystore.jks + + - name: Build + 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 appbundle --release + + - name: Upload Release Build to Artifacts + uses: actions/upload-artifact@v3 + with: + name: release-artifacts + path: ./app/build/app/outputs/bundle/release/app-release.aab + + - name: Create release if tag pushed + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + if: startsWith(github.ref, 'refs/tags/') + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + draft: true + prerelease: true + files: | + ./app/build/app/outputs/bundle/release/app-release.aab diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b00a1d..d6c10a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,49 @@ # Changelog +## NEXT - v1.0.2(33) + +- Refactor Settings model + +## 2024-06-17 - v1.0.1(1) + +- Update about text in iOS build + +## 2024-06-17 - v1.0.0(32) + +- Update about text + +## 2024-06-15 - v1.0.0(31) + +- Remove iPad from target devices as iPads have no NFC +- set minimum iOS version to 12 as apple required + +## 2024-06-14 - v1.0.0(30) + +- Update Android and iOS icons +- Update texts +- Set default signatureType without TimeStamp + +## 2024-06-13 - v1.0.0(29) + +- Fix Signature Type setting UX + +## 2024-06-13 - v1.0.0(28) + +- #4 | Displaying Onboarding screen for Remote Document Signing only 1st time +- #7 | When signing remote Document (from QR code or URL), then cannot change Signing Type + +## 2024-05-29 - v1.0.0(27) + +- Android: Setup release build signing key for Google Play + +## 2024-05-28 - v1.0.0(26) + +- Displaying Privacy Policy and Terms of Service documents in Onboarding and Main menu +- Storing "version" value from loaded documents above + ## 2024-05-07 - v1.0.0(25) -- Android: Fix auto verify Deep links - no need to manually enable domain association +- Android: Fix auto verify Deep links - no need to manually enable domain association ## 2024-05-07 - v1.0.0(24) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3660c79 --- /dev/null +++ b/LICENSE @@ -0,0 +1,287 @@ + EUROPEAN UNION PUBLIC LICENCE v. 1.2 + EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined +below) which is provided under the terms of this Licence. Any use of the Work, +other than as authorauthorised under this Licence is prohibited (to the extent such +use is covered by a right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: + + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- ‘The Licence’: this Licence. + +- ‘The Original Work’: the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable + Code as the case may be. + +- ‘Derivative Works’: the works or software that could be created by the + Licensee, based upon the Original Work or modifications thereof. This Licence + does not define the extent of modification or dependence on the Original Work + required in order to classify a work as a Derivative Work; this extent is + determined by copyright law applicable in the country mentioned in Article 15. + +- ‘The Work’: the Original Work or its Derivative Works. + +- ‘The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- ‘The Executable Code’: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- ‘The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. + +- ‘Contributor(s)’: any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. + +- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, +- reproduce the Work, +- modify the Work, and make Derivative Works based upon the Work, +- communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case may + be, the Work, +- distribute the Work or copies thereof, +- lend and rent the Work or copies thereof, +- sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Work, of the exhaustion of those rights or of other applicable limitations +thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of the +Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions on +the Work or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed under +a Compatible Licence, this Distribution or Communication can be done under the +terms of this Compatible Licence. For the sake of this clause, ‘Compatible +Licence’ refers to the licences listed in the appendix attached to this Licence. +Should the Licensee's obligations under the Compatible Licence conflict with +his/her obligations under this Licence, the obligations of the Compatible +Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the Work, +the Licensee will provide a machine-readable copy of the Source Code or indicate +a repository where this Source will be easily and freely available for as long +as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +‘bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an ‘as is’ basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such damage. +However, the Licensor will be liable under statutory product liability laws as +far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional agreement, +defining obligations or services consistent with this Licence. However, if +accepting obligations, You may act only on your own behalf and on your sole +responsibility, not on behalf of the original Licensor or any other Contributor, +and only if You agree to indemnify, defend, and hold each Contributor harmless +for any liability incurred by, or claims asserted against such Contributor by +the fact You have accepted any warranty or additional liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of electronic +communication by You (for example, by offering to download the Work from a +remote location) the distribution channel or media (for example, a website) must +at least provide to the public the information requested by the applicable law +regarding the Licensor, the Licence and the way it may be accessible, concluded, +stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions of +this Licence or updated versions of the Appendix, so far this is required and +reasonable, without reducing the scope of the rights granted by the Licence. New +versions of the Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +- any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty on + the Functioning of the European Union, + +- any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive jurisdiction + of the competent court where the Licensor resides or conducts its primary + business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +- this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, + +- this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: + +- GNU General Public License (GPL) v. 2, v. 3 +- GNU Affero General Public License (AGPL) v. 3 +- Open Software License (OSL) v. 2.1, v. 3.0 +- Eclipse Public License (EPL) v. 1.0 +- CeCILL v. 2.0, v. 2.1 +- Mozilla Public Licence (MPL) v. 2 +- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +- European Union Public Licence (EUPL) v. 1.1, v. 1.2 +- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong + Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the above +licences without producing a new version of the EUPL, as long as they provide +the rights granted in Article 2 of this Licence and protect the covered Source +Code from exclusive appropriation. + +All other changes or additions to this Appendix require the production of a new +EUPL version. diff --git a/README.md b/README.md index 8cad7fc..1441f22 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,27 @@ -# Autogram Mobile App (AVM) +# Autogram v mobile (AVM) -Flutter app for Android and iOS. +Flutter aplikácia pre Android a iOS. Podpisovač Autogram v mobile umožňuje podpisovanie elektronickým občianskym preukazom s NFC rozhraním. Detailnejšie info o arhitektúre projektu sa nachádzajú v repozitári [AVM server](https://github.com/slovensko-digital/avm-server). -## Entry points +[Autogram v mobile](https://sluzby.slovensko.digital/autogram-v-mobile/) vytvorili freevision s.r.o., Služby Slovensko.Digital s.r.o. s dobrovoľníkmi pod EUPL v1.2 licenciou. Prevádzkovateľom je Služby Slovensko.Digital s.r.o.. Prípadné issues riešime v [GitHub projekte](https://github.com/orgs/slovensko-digital/projects/5) alebo rovno v tomto repozitári. + +Celý projekt sa skladá z viacerých častí: +- **Server** + - [AVM server](https://github.com/slovensko-digital/avm-server) - Ruby on Rails API server poskytujúci funkcionalitu zdieľania a podpisovania dokumentov. + - [AVM service](https://github.com/slovensko-digital/avm-service) - Java microservice využívajúci Digital Signature Service knižnicu pre elektronické podpisovanie a generovanie vizualizácie dokumentov. +- **Mobilná aplikácia** + - [AVM app Flutter](https://github.com/slovensko-digital/avm-app-flutter) - Flutter aplikácia pre iOS a Android. + - [AVM client Dart](https://github.com/slovensko-digital/avm-client-dart) - Dart API klient pre komunikáciu s AVM serverom. + - [eID mSDK Flutter](https://github.com/slovensko-digital/eidmsdk-flutter) - Flutter wrapper "štátneho" [eID mSDK](https://github.com/eIDmSDK) pre komunikáciu s občianskym preukazom. +- [**Autogram extension**](https://github.com/slovensko-digital/autogram-extension) - Rozšírenie do prehliadača, ktoré umožňuje podpisovanie priamo na štátnych portáloch. + + +## Dart aplikácia +### Entry points - [main](lib/main.dart) - main app - [preview](lib/preview.dart) - [Widgetbook](https://www.widgetbook.io/blog/getting-started) app -## Key concepts +### Key concepts - Business logic should be separated in **Bloc** and placed in [lib/bloc/](lib/bloc) - `NameCubit` @@ -18,18 +32,18 @@ Flutter app for Android and iOS. - are placed in [`lib/ui`](lib/ui) - should have reasonable previews with `@widgetbook.UseCase` without relying on any `Bloc` or `Provider` types -## Implemented app flows +### Implemented app flows -### Onboarding +#### Onboarding -User onboarding - starts with one [`Onboarding`](lib/ui/onboarding.dart): +User onboarding - started with [`Onboarding`](lib/ui/onboarding.dart): 1. Accept Privacy Policy using [`OnboardingAcceptDocumentScreen`](lib/ui/screens/onboarding_accept_document_screen.dart) 2. Accept Terms of Service using same [`OnboardingAcceptDocumentScreen`](lib/ui/screens/onboarding_accept_document_screen.dart) 3. optionally [`OnboardingSelectSigningCertificateScreen`](lib/ui/screens/onboarding_select_signing_certificate_screen.dart) 4. Presenting finish - [`OnboardingFinishedScreen`](lib/ui/screens/onboarding_finished_screen.dart) -### Sign single document +#### Sign single document Signing of single (PDF, TXT, image, eForms XML, ...) document using [`Eidmsdk`](../eidmsdk_flutter/lib/eidmsdk.dart) and @@ -49,14 +63,15 @@ Signing of single (PDF, TXT, image, eForms XML, ...) document using 6. [`PresentSignedDocumentScreen`](lib/ui/screens/present_signed_document_screen.dart) - here, the (success / error) result is presented and signed document is saved into "Downloads". -### Remote document signing +#### Remote document signing -Similar to [Sign single document](#sign-single-document), but starts with: +Started with [`RemoteDocumentSigning`](lib/ui/remote_document_signing.dart). +It's similar to [Sign single document](#sign-single-document), but starts with: - [`StartRemoteDocumentSigningScreen`](lib/ui/screens/start_remote_document_signing_screen.dart) - [`QRCodeScannerScreen`](lib/ui/screens/qr_code_scanner_screen.dart) -## Scripts +### Scripts FVM init and Pub get: @@ -110,7 +125,7 @@ Build **WEB**: fvm flutter build web --target=lib/preview.dart ``` -## Identifiers and links +### Identifiers and links - Android Application ID, [Apple Bundle/App ID](https://developer.apple.com/account/resources/identifiers/bundleId/edit/832594XXZD): `digital.slovensko.avm` - Apple Team ID: `44U4JSRX4Z` (Služby Slovensko.Digital, s.r.o.) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2955684..be42fcb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -57,11 +57,18 @@ android { } } + signingConfigs { + release { + storeFile file(System.getenv("AVM_KEYSTORE_FILE")) + storePassword System.getenv("AVM_KEYSTORE_PASSWORD") + keyAlias System.getenv("AVM_KEY_ALIAS") + keyPassword System.getenv("AVM_KEY_PASSWORD") + } + } + buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig signingConfigs.release } } } diff --git a/android/app/src/main/ic_launcher-playstore.png b/android/app/src/main/ic_launcher-playstore.png index 9b80e81..62440ff 100644 Binary files a/android/app/src/main/ic_launcher-playstore.png and b/android/app/src/main/ic_launcher-playstore.png differ diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml index f74085f..1a85d7d 100644 --- a/android/app/src/main/res/drawable-v21/launch_background.xml +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -9,4 +9,4 @@ android:gravity="center" android:src="@mipmap/launch_image" /> --> - + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml index 5d1844a..92ea0d6 100644 --- a/android/app/src/main/res/drawable/ic_launcher_background.xml +++ b/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,21 +1,9 @@ - - - - - - - - - - + + + diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml index e0a605a..c7ef6c6 100644 --- a/android/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -1,17 +1,19 @@ - - - - - - + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml index 304732f..f0bf5d4 100644 --- a/android/app/src/main/res/drawable/launch_background.xml +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -9,4 +9,4 @@ android:gravity="center" android:src="@mipmap/launch_image" /> --> - + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index d3f33eb..bbd3e02 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index d3f33eb..bbd3e02 100644 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index 96576d2..217f23c 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png index ae082ca..3707fa9 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 4371585..4b06629 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png index 0f20191..1fde131 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 2645e6c..a8c0e95 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png index 5e08a66..e2fd64b 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index f44e8fd..486bab1 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png index e9083a3..15c98e1 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index d0aa0ba..378dec1 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png index 7ff15a5..5e97ce9 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 5276798..3551f10 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -481,11 +481,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; VALIDATE_PRODUCT = YES; }; name = Profile; @@ -615,11 +615,11 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; @@ -664,13 +664,13 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = 1; VALIDATE_PRODUCT = YES; }; name = Release; diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..b31b083 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1,116 @@ { "images" : [ { - "size" : "20x20", + "filename" : "Ikonka-20@2x.png", "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { - "size" : "20x20", + "filename" : "Ikonka-20@3x.png", "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" + "scale" : "3x", + "size" : "20x20" }, { - "size" : "29x29", + "filename" : "Ikonka-29@2x.png", "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" + "scale" : "2x", + "size" : "29x29" }, { - "size" : "29x29", + "filename" : "Ikonka-29@3x.png", "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "scale" : "3x", + "size" : "29x29" }, { - "size" : "29x29", + "filename" : "Ikonka-40@2x.png", "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" + "scale" : "2x", + "size" : "40x40" }, { - "size" : "40x40", + "filename" : "Ikonka-40@3x.png", "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" + "scale" : "3x", + "size" : "40x40" }, { - "size" : "40x40", + "filename" : "Ikonka-60@2x.png", "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" + "scale" : "2x", + "size" : "60x60" }, { - "size" : "60x60", + "filename" : "Ikonka-60@3x.png", "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" + "scale" : "3x", + "size" : "60x60" }, { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", + "filename" : "Ikonka-20.png", "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "20x20" }, { - "size" : "20x20", + "filename" : "Ikonka-20@2x.png", "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "20x20" }, { - "size" : "29x29", + "filename" : "Ikonka-29.png", "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "29x29" }, { - "size" : "29x29", + "filename" : "Ikonka-29@2x.png", "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "29x29" }, { - "size" : "40x40", + "filename" : "Ikonka-40.png", "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "40x40" }, { - "size" : "40x40", + "filename" : "Ikonka-40@2x.png", "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "40x40" }, { - "size" : "76x76", + "filename" : "Ikonka-76.png", "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "76x76" }, { - "size" : "76x76", + "filename" : "Ikonka-76@2x.png", "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "76x76" }, { - "size" : "83.5x83.5", + "filename" : "Ikonka-83.5@2x.png", "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" + "scale" : "2x", + "size" : "83.5x83.5" }, { - "size" : "1024x1024", + "filename" : "Ikonka-1024.png", "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" + "scale" : "1x", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } } diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index ea53eaf..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index d0416ef..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 6456dba..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index d65d47a..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index ee33af0..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index f620265..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index ab76cc4..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 6456dba..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index a05d77c..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index 3e1dbc3..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index 3e1dbc3..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index b25f74c..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index 3692e81..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index ff1666e..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index ca7c76d..0000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-1024.png new file mode 100644 index 0000000..e6adc40 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-1024.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-20.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-20.png new file mode 100644 index 0000000..026aa6b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-20.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-20@2x.png new file mode 100644 index 0000000..988cad8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-20@3x.png new file mode 100644 index 0000000..7bb6b1e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-29.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-29.png new file mode 100644 index 0000000..ca91dd0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-29.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-29@2x.png new file mode 100644 index 0000000..8894bf1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-29@3x.png new file mode 100644 index 0000000..27f72b4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-40.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-40.png new file mode 100644 index 0000000..988cad8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-40.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-40@2x.png new file mode 100644 index 0000000..64cc42e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-40@3x.png new file mode 100644 index 0000000..ad9bd99 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-60@2x.png new file mode 100644 index 0000000..ad9bd99 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-60@3x.png new file mode 100644 index 0000000..ab6b655 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-76.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-76.png new file mode 100644 index 0000000..54032f2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-76.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-76@2x.png new file mode 100644 index 0000000..6186eb9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-83.5@2x.png new file mode 100644 index 0000000..57eef8a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Ikonka-83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/Contents.json b/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/lib/bloc/get_document_signature_type_cubit.dart b/lib/bloc/get_document_signature_type_cubit.dart new file mode 100644 index 0000000..2c9045b --- /dev/null +++ b/lib/bloc/get_document_signature_type_cubit.dart @@ -0,0 +1,45 @@ +import 'package:flutter_bloc/flutter_bloc.dart' show Cubit; +import 'package:injectable/injectable.dart'; +import 'package:logging/logging.dart'; + +import '../data/signature_type.dart'; +import '../use_case/get_document_signature_type_use_case.dart'; +import 'get_document_signature_type_state.dart'; + +export 'get_document_signature_type_state.dart'; + +/// Cubit to get the Document [SignatureType]. +@injectable +class GetDocumentSignatureTypeCubit + extends Cubit { + static final _log = Logger((GetDocumentSignatureTypeCubit).toString()); + + final GetDocumentSignatureTypeUseCase _getDocumentSignatureType; + + GetDocumentSignatureTypeCubit({ + required GetDocumentSignatureTypeUseCase getDocumentSignatureType, + }) : _getDocumentSignatureType = getDocumentSignatureType, + super(const GetDocumentSignatureTypeInitialState()); + + /// Sets the [signatureType] directly. + void setSignatureType(SignatureType? signatureType) { + emit(GetDocumentSignatureTypeSuccessState(signatureType)); + } + + /// Gets the Document [SignatureType]. + Future getDocumentSignatureType(String documentId) async { + emit(const GetDocumentSignatureTypeLoadingState()); + + try { + final signatureType = await _getDocumentSignatureType(documentId); + + _log.info("Got Document SignatureType: ${signatureType?.name}."); + + emit(GetDocumentSignatureTypeSuccessState(signatureType)); + } catch (error, stackTrace) { + _log.severe("Error getting Document SignatureType.", error, stackTrace); + + emit(GetDocumentSignatureTypeErrorState(error)); + } + } +} diff --git a/lib/bloc/get_document_signature_type_state.dart b/lib/bloc/get_document_signature_type_state.dart new file mode 100644 index 0000000..202b3be --- /dev/null +++ b/lib/bloc/get_document_signature_type_state.dart @@ -0,0 +1,42 @@ +import 'package:flutter/foundation.dart' show immutable; + +import '../data/signature_type.dart'; +import 'get_document_signature_type_cubit.dart'; + +/// State for [GetDocumentSignatureTypeCubit]. +@immutable +sealed class GetDocumentSignatureTypeState { + const GetDocumentSignatureTypeState(); + + @override + String toString() => "$runtimeType()"; +} + +class GetDocumentSignatureTypeInitialState + extends GetDocumentSignatureTypeState { + const GetDocumentSignatureTypeInitialState(); +} + +class GetDocumentSignatureTypeLoadingState + extends GetDocumentSignatureTypeState { + const GetDocumentSignatureTypeLoadingState(); +} + +class GetDocumentSignatureTypeErrorState extends GetDocumentSignatureTypeState { + final Object error; + + const GetDocumentSignatureTypeErrorState(this.error); + + @override + String toString() => "$runtimeType(error: $error)"; +} + +class GetDocumentSignatureTypeSuccessState + extends GetDocumentSignatureTypeState { + final SignatureType? signatureType; + + const GetDocumentSignatureTypeSuccessState(this.signatureType); + + @override + String toString() => "$runtimeType(signatureType: $signatureType)"; +} diff --git a/lib/bloc/select_signing_certificate_cubit.dart b/lib/bloc/select_signing_certificate_cubit.dart index 5f6641f..58b8634 100644 --- a/lib/bloc/select_signing_certificate_cubit.dart +++ b/lib/bloc/select_signing_certificate_cubit.dart @@ -15,7 +15,7 @@ export 'select_signing_certificate_state.dart'; @injectable class SelectSigningCertificateCubit extends Cubit { - static final _log = Logger("SelectCertificateCubit"); + static final _log = Logger((SelectSigningCertificateCubit).toString()); static const _defaultLanguage = 'sk'; final Eidmsdk _eidmsdk; diff --git a/lib/bloc/select_signing_certificate_state.dart b/lib/bloc/select_signing_certificate_state.dart index 1f31ed5..16142aa 100644 --- a/lib/bloc/select_signing_certificate_state.dart +++ b/lib/bloc/select_signing_certificate_state.dart @@ -8,25 +8,20 @@ import 'select_signing_certificate_cubit.dart'; sealed class SelectSigningCertificateState { const SelectSigningCertificateState(); - SelectSigningCertificateLoadingState toLoading() { - return const SelectSigningCertificateLoadingState(); - } + SelectSigningCertificateLoadingState toLoading() => + const SelectSigningCertificateLoadingState(); - SelectSigningCertificateErrorState toError(Object error) { - return SelectSigningCertificateErrorState(error); - } + SelectSigningCertificateErrorState toError(Object error) => + SelectSigningCertificateErrorState(error); - SelectSigningCertificateSuccessState toSuccess(Certificate certificate) { - return SelectSigningCertificateSuccessState(certificate); - } + SelectSigningCertificateSuccessState toSuccess(Certificate certificate) => + SelectSigningCertificateSuccessState(certificate); - SelectSigningCertificateCanceledState toCanceled() { - return const SelectSigningCertificateCanceledState(); - } + SelectSigningCertificateCanceledState toCanceled() => + const SelectSigningCertificateCanceledState(); - SelectSigningCertificateNoCertificateState toNoCertificate() { - return const SelectSigningCertificateNoCertificateState(); - } + SelectSigningCertificateNoCertificateState toNoCertificate() => + const SelectSigningCertificateNoCertificateState(); @override String toString() { diff --git a/lib/data/settings.dart b/lib/data/settings.dart index 99845db..16340c7 100644 --- a/lib/data/settings.dart +++ b/lib/data/settings.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:eidmsdk/types.dart'; import 'package:flutter/foundation.dart'; import 'package:notified_preferences/notified_preferences.dart'; @@ -5,8 +7,8 @@ import 'package:notified_preferences/notified_preferences.dart'; import 'pdf_signing_option.dart'; import 'signature_type.dart'; -/// Interface for general app settings. -abstract interface class ISettings { +/// General app settings. +abstract interface class Settings { /// Accepted Privacy Policy document version value. ValueNotifier get acceptedPrivacyPolicyVersion; @@ -22,16 +24,27 @@ abstract interface class ISettings { /// The signing [SignatureType] value. ValueNotifier get signatureType; + /// Whether passed onboarding screen for "Remote Document Signing" feature. + ValueNotifier get remoteDocumentSigningOnboardingPassed; + /// Clear all setting. Future clear(); + + /// Creates and returns new [Settings]. + static Future create([ + FutureOr? preferences, + ]) async { + final settings = _SettingsImpl(); + await settings.initialize(preferences); + + return settings; + } } -/// General app settings. -/// -/// Uses **Shared Preferences** - need to call [Settings.initialize] before use. -// TODO Make only "Settings" type and private _SettingsImpl that will be returned by factory fun -// TODO Also register it using Injectable -class Settings with NotifiedPreferences implements ISettings { +/// [Settings] implementation that uses [SharedPreferences]. +/// Note, [clear] is from [NotifiedPreferences]. +// TODO Register Settings using Injectable as singleton - would need to pass instance into DI so no need to use "async" +class _SettingsImpl with NotifiedPreferences implements Settings { @override late final ValueNotifier acceptedPrivacyPolicyVersion = createSetting( @@ -57,7 +70,7 @@ class Settings with NotifiedPreferences implements ISettings { @override late final ValueNotifier signatureType = createEnumSetting( key: 'signing.signatureType', - initialValue: SignatureType.unset, + initialValue: SignatureType.withoutTimestamp, values: SignatureType.values, ); @@ -68,4 +81,11 @@ class Settings with NotifiedPreferences implements ISettings { initialValue: null, fromJson: (json) => Certificate.fromJson(json), ); + + @override + late final ValueNotifier remoteDocumentSigningOnboardingPassed = + createSetting( + key: "onboarding.remoteDocumentSigning.passed", + initialValue: false, + ); } diff --git a/lib/deep_links.dart b/lib/deep_links.dart index 4186a24..94c2180 100644 --- a/lib/deep_links.dart +++ b/lib/deep_links.dart @@ -31,12 +31,12 @@ DeepLinkAction parseDeepLinkAction(Uri uri) { "/api/v1/qr-code" => () { if (!uri.queryParameters.containsKey("guid")) { throw ArgumentError.value( - uri.toString(), "uri", "'guid' param is missing value."); + uri.toString(), "uri", '"guid" param is missing a value.'); } if (!uri.queryParameters.containsKey("key")) { throw ArgumentError.value( - uri.toString(), "uri", "'key' param is missing value."); + uri.toString(), "uri", '"key" param is missing a value.'); } return SignRemoteDocumentAction( diff --git a/lib/di.config.dart b/lib/di.config.dart index 71ecf93..2fc7be1 100644 --- a/lib/di.config.dart +++ b/lib/di.config.dart @@ -20,14 +20,16 @@ import 'package:injectable/injectable.dart' as _i2; import 'app_service.dart' as _i3; import 'bloc/create_document_cubit.dart' as _i15; +import 'bloc/get_document_signature_type_cubit.dart' as _i20; import 'bloc/paired_device_list_cubit.dart' as _i8; import 'bloc/present_signed_document_cubit.dart' as _i9; import 'bloc/preview_document_cubit.dart' as _i10; import 'bloc/select_signing_certificate_cubit.dart' as _i11; import 'bloc/sign_document_cubit.dart' as _i14; import 'data/pdf_signing_option.dart' as _i18; -import 'di.dart' as _i19; +import 'di.dart' as _i21; import 'services/encryption_key_registry.dart' as _i5; +import 'use_case/get_document_signature_type_use_case.dart' as _i19; import 'use_case/get_document_version_use_case.dart' as _i6; extension GetItInjectableX on _i1.GetIt { @@ -45,8 +47,8 @@ extension GetItInjectableX on _i1.GetIt { gh.singleton<_i3.AppService>(() => _i3.AppService()); gh.lazySingleton<_i4.Eidmsdk>(() => extrernalModule.eidmsdk); gh.singleton<_i5.EncryptionKeyRegistry>(() => _i5.EncryptionKeyRegistry()); - gh.lazySingleton<_i6.GetDocumentVersionUseCase>( - () => _i6.GetDocumentVersionUseCase()); + gh.lazySingleton<_i6.GetHtmlDocumentVersionUseCase>( + () => _i6.GetHtmlDocumentVersionUseCase()); gh.lazySingleton<_i7.IAutogramService>( () => extrernalModule.create(gh<_i5.EncryptionKeyRegistry>())); gh.factory<_i8.PairedDeviceListCubit>( @@ -97,11 +99,17 @@ extension GetItInjectableX on _i1.GetIt { file: file, pdfSigningOption: pdfSigningOption, )); + gh.lazySingleton<_i19.GetDocumentSignatureTypeUseCase>( + () => _i19.GetDocumentSignatureTypeUseCase(gh<_i7.IAutogramService>())); + gh.factory<_i20.GetDocumentSignatureTypeCubit>(() => + _i20.GetDocumentSignatureTypeCubit( + getDocumentSignatureType: + gh<_i19.GetDocumentSignatureTypeUseCase>())); return this; } } -class _$ExtrernalModule extends _i19.ExtrernalModule { +class _$ExtrernalModule extends _i21.ExtrernalModule { @override _i4.Eidmsdk get eidmsdk => _i4.Eidmsdk(); } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 170ff77..cf01787 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -271,6 +271,12 @@ abstract class AppLocalizations { /// **'Podpisovanie PDF'** String get signingPdfContainerTitle; + /// No description provided for @signatureTypeErrorHeading. + /// + /// In sk, this message translates to: + /// **'Chyba pri načítaní parametrov podpisu'** + String get signatureTypeErrorHeading; + /// No description provided for @signatureTypeTitle. /// /// In sk, this message translates to: @@ -346,9 +352,15 @@ 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\n„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.'** + String get aboutAuthorsText; + /// No description provided for @thirdPartyLicensesLabel. /// /// In sk, this message translates to: @@ -394,13 +406,13 @@ abstract class AppLocalizations { /// No description provided for @signRemoteDocumentBody1. /// /// In sk, this message translates to: - /// **'Mobilom môžete podpisovať aj dokumenty nachádzajúce sa vo vašom počítači, či v informačnom systéme pomocou rozšírenia Autogramu vo vašom internetovom prehladávači.'** + /// **'Mobilom môžete podpisovať aj dokumenty nachádzajúce sa vo vašom počítači, či v informačnom systéme pomocou rozšírenia “Autogram na štátnych weboch“ vo vašom internetovom prehladávači.'** String get signRemoteDocumentBody1; /// No description provided for @signRemoteDocumentBody2. /// /// In sk, this message translates to: - /// **'1. Pri podpisovaní v internetovom prehliadači vo vašom počítači vyberte možnosť “podísať v mobile”.\n2. Telefónom naskenujte QR kód z vášho počítača.'** + /// **'1. Pri podpisovaní v internetovom prehliadači vo vašom počítači vyberte možnosť “Podpísať mobilom”.\n2. Telefónom naskenujte QR kód z vášho počítača.'** String get signRemoteDocumentBody2; /// No description provided for @openDocumentTitle. @@ -421,11 +433,11 @@ abstract class AppLocalizations { /// **'Náhľad dokumentu'** String get previewDocumentTitle; - /// No description provided for @previewDocumentErrorTitle. + /// No description provided for @previewDocumentErrorHeading. /// /// In sk, this message translates to: /// **'Chyba pri načítaní vizualizácie dokumentu'** - String get previewDocumentErrorTitle; + String get previewDocumentErrorHeading; /// No description provided for @shareDocumentText. /// @@ -454,7 +466,7 @@ abstract class AppLocalizations { /// No description provided for @selectSigningCertificateBody. /// /// In sk, this message translates to: - /// **'Na podpisovanie mobilom potrebujete disponovať vhodným podpisovým certifikátom. Ak si prajete iba overiť podpisy v dokumentoch, tento krok je nepovinný a môžete ho preskočiť. Podpisový certifikát si môžete nastaviť neskôr počas podpisovania prvého dokumentu.\n\n\nAk si prajete nastaviť podpisový certifikát, pripravte si prosím občiansky preukaz a nasledujte inštrukcie na obrazovke.'** + /// **'Na podpisovanie mobilom potrebujete disponovať vhodným podpisovým certifikátom. Podpisový certifikát si môžete nastaviť aj neskôr počas podpisovania prvého dokumentu.\n\n\nAk si prajete nastaviť podpisový certifikát teraz, pripravte si, prosím, občiansky preukaz a nasledujte inštrukcie na obrazovke.'** String get selectSigningCertificateBody; /// No description provided for @selectSigningCertificateCanceledHeading. @@ -478,7 +490,7 @@ 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, 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 '** String get selectSigningCertificateNoCertificateBody; /// No description provided for @selectSigningCertificateNoCertificateGuideUrl. diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index 6d95bb8..82acb06 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -102,6 +102,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get signingPdfContainerTitle => 'Podpisovanie PDF'; + @override + String get signatureTypeErrorHeading => 'Chyba pri načítaní parametrov podpisu'; + @override String get signatureTypeTitle => 'Predvolený typ podpisu'; @@ -179,7 +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\n„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.'; @override String get thirdPartyLicensesLabel => 'Licencie knižníc tretích strán'; @@ -203,10 +209,10 @@ class AppLocalizationsSk extends AppLocalizations { String get qrCodeScannerUnsupportedContentErrorMessage => 'Naskenovali ste nepodporovaný alebo neznámy obsah QR kódu.'; @override - String get signRemoteDocumentBody1 => 'Mobilom môžete podpisovať aj dokumenty nachádzajúce sa vo vašom počítači, či v informačnom systéme pomocou rozšírenia Autogramu vo vašom internetovom prehladávači.'; + String get signRemoteDocumentBody1 => 'Mobilom môžete podpisovať aj dokumenty nachádzajúce sa vo vašom počítači, či v informačnom systéme pomocou rozšírenia “Autogram na štátnych weboch“ vo vašom internetovom prehladávači.'; @override - String get signRemoteDocumentBody2 => '1. Pri podpisovaní v internetovom prehliadači vo vašom počítači vyberte možnosť “podísať v mobile”.\n2. Telefónom naskenujte QR kód z vášho počítača.'; + String get signRemoteDocumentBody2 => '1. Pri podpisovaní v internetovom prehliadači vo vašom počítači vyberte možnosť “Podpísať mobilom”.\n2. Telefónom naskenujte QR kód z vášho počítača.'; @override String get openDocumentTitle => 'Otváranie dokumentu'; @@ -218,7 +224,7 @@ class AppLocalizationsSk extends AppLocalizations { String get previewDocumentTitle => 'Náhľad dokumentu'; @override - String get previewDocumentErrorTitle => 'Chyba pri načítaní vizualizácie dokumentu'; + String get previewDocumentErrorHeading => 'Chyba pri načítaní vizualizácie dokumentu'; @override String get shareDocumentText => '\n\nSúbor z aplikácie Autogram v mobile'; @@ -235,7 +241,7 @@ class AppLocalizationsSk extends AppLocalizations { String get selectSigningCertificateTitle => 'Nastavenie certifikátu'; @override - String get selectSigningCertificateBody => 'Na podpisovanie mobilom potrebujete disponovať vhodným podpisovým certifikátom. Ak si prajete iba overiť podpisy v dokumentoch, tento krok je nepovinný a môžete ho preskočiť. Podpisový certifikát si môžete nastaviť neskôr počas podpisovania prvého dokumentu.\n\n\nAk si prajete nastaviť podpisový certifikát, pripravte si prosím občiansky preukaz a nasledujte inštrukcie na obrazovke.'; + String get selectSigningCertificateBody => 'Na podpisovanie mobilom potrebujete disponovať vhodným podpisovým certifikátom. Podpisový certifikát si môžete nastaviť aj neskôr počas podpisovania prvého dokumentu.\n\n\nAk si prajete nastaviť podpisový certifikát teraz, pripravte si, prosím, občiansky preukaz a nasledujte inštrukcie na obrazovke.'; @override String get selectSigningCertificateCanceledHeading => 'Čítanie certifikátu bolo prerušené'; @@ -247,7 +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, prípadne použiť iný občiansky preukaz.\nNávod na vydanie certifikátu nájdete na '; + 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'; diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 75b11d6..29870f6 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -34,6 +34,7 @@ "signRemoteDocumentTitle": "Podpísať vzdialený dokument", "aboutLabel": "O aplikácii", "signingPdfContainerTitle": "Podpisovanie PDF", + "signatureTypeErrorHeading": "Chyba pri načítaní parametrov podpisu", "signatureTypeTitle": "Predvolený typ podpisu", "signatureTypeSummary": "{name, select, withoutTimestamp {Vlastnoručný podpis} withTimestamp {Osvedčený podpis} other {Spýtať sa pri podpisovaní}}", "signatureTypeValueTitle": "{name, select, withoutTimestamp {Vlastnoručný podpis} withTimestamp {Osvedčený podpis} other {-}}", @@ -47,7 +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\n„https://github.com/eidmsdk“.", + "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.", "thirdPartyLicensesLabel": "Licencie knižníc tretích strán", "introHeading": "Nový, lepší a krajší podpisovač v mobile", @@ -59,25 +61,25 @@ "qrCodeScannerInfoBody": "Prosím nasmerujte kameru\nna QR kód na obrazovke", "qrCodeScannerUnsupportedContentErrorMessage": "Naskenovali ste nepodporovaný alebo neznámy obsah QR kódu.", - "signRemoteDocumentBody1": "Mobilom môžete podpisovať aj dokumenty nachádzajúce sa vo vašom počítači, či v informačnom systéme pomocou rozšírenia Autogramu vo vašom internetovom prehladávači.", - "signRemoteDocumentBody2": "1. Pri podpisovaní v internetovom prehliadači vo vašom počítači vyberte možnosť “podísať v mobile”.\n2. Telefónom naskenujte QR kód z vášho počítača.", + "signRemoteDocumentBody1": "Mobilom môžete podpisovať aj dokumenty nachádzajúce sa vo vašom počítači, či v informačnom systéme pomocou rozšírenia “Autogram na štátnych weboch“ vo vašom internetovom prehladávači.", + "signRemoteDocumentBody2": "1. Pri podpisovaní v internetovom prehliadači vo vašom počítači vyberte možnosť “Podpísať mobilom”.\n2. Telefónom naskenujte QR kód z vášho počítača.", "openDocumentTitle": "Otváranie dokumentu", "openDocumentErrorTitle": "Chyba pri vytváraní dokumentu", "previewDocumentTitle": "Náhľad dokumentu", - "previewDocumentErrorTitle": "Chyba pri načítaní vizualizácie dokumentu", + "previewDocumentErrorHeading": "Chyba pri načítaní vizualizácie dokumentu", "shareDocumentText": "\n\nSúbor z aplikácie Autogram v mobile", "documentVisualizationCannotVisualizeTypeError": "Neviem vizualizovať {type} typ.", "selectCertificateTitle": "Výber typu podpisu", "selectSigningCertificateTitle": "Nastavenie certifikátu", - "selectSigningCertificateBody": "Na podpisovanie mobilom potrebujete disponovať vhodným podpisovým certifikátom. Ak si prajete iba overiť podpisy v dokumentoch, tento krok je nepovinný a môžete ho preskočiť. Podpisový certifikát si môžete nastaviť neskôr počas podpisovania prvého dokumentu.\n\n\nAk si prajete nastaviť podpisový certifikát, pripravte si prosím občiansky preukaz a nasledujte inštrukcie na obrazovke.", + "selectSigningCertificateBody": "Na podpisovanie mobilom potrebujete disponovať vhodným podpisovým certifikátom. Podpisový certifikát si môžete nastaviť aj neskôr počas podpisovania prvého dokumentu.\n\n\nAk si prajete nastaviť podpisový certifikát teraz, pripravte si, prosím, občiansky preukaz a nasledujte inštrukcie na obrazovke.", "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, prípadne použiť iný občiansky preukaz.\nNávod na vydanie certifikátu nájdete na ", + "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", "selectSigningCertificateErrorHeading": "Chyba pri načítavaní certifikátov z občianskeho preukazu.", diff --git a/lib/main.dart b/lib/main.dart index 0cbe831..f2c70e6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,15 +27,13 @@ void main() async { configureDependencies(); // Init Settings - final settings = Settings(); - await settings.initialize(); + final settings = await Settings.create(); // Run App runApp( MultiProvider( providers: [ - // ISettings - Provider.value(value: settings), + Provider.value(value: settings), ], child: const App(), ), diff --git a/lib/ui/onboarding.dart b/lib/ui/onboarding.dart index d05e8c9..0bba5a0 100644 --- a/lib/ui/onboarding.dart +++ b/lib/ui/onboarding.dart @@ -11,7 +11,7 @@ import 'screens/onboarding_select_signing_certificate_screen.dart'; /// Helper for Onboarding flow. /// -/// Reads [ISettings]. +/// Reads [Settings]. /// /// Onboarding flow: /// 1. Accept Privacy Policy - [OnboardingAcceptDocumentScreen] @@ -26,7 +26,7 @@ abstract class Onboarding { /// Refresh [onboardingRequired] value. static void refreshOnboardingRequired(BuildContext context) { - final settings = context.read(); + final settings = context.read(); bool flag; if (settings.acceptedPrivacyPolicyVersion.value == null) { @@ -42,7 +42,7 @@ abstract class Onboarding { /// Starts 1st Onboarding screen. static Future startOnboarding(BuildContext context) { - final settings = context.read(); + final settings = context.read(); final strings = context.strings; final screen = OnboardingAcceptDocumentScreen( title: strings.privacyPolicyTitle, @@ -69,7 +69,7 @@ abstract class Onboarding { } static void _handlePrivacyPolicyAccepted(BuildContext context) { - final settings = context.read(); + final settings = context.read(); final strings = context.strings; final screen = OnboardingAcceptDocumentScreen( title: strings.termsOfServiceTitle, @@ -85,7 +85,7 @@ abstract class Onboarding { } static void _handleTermsOfServiceAccepted(BuildContext context) { - final settings = context.read(); + final settings = context.read(); final hasSigningCertificate = settings.signingCertificate.value != null; final Widget screen; diff --git a/lib/ui/remote_document_signing.dart b/lib/ui/remote_document_signing.dart new file mode 100644 index 0000000..19dba95 --- /dev/null +++ b/lib/ui/remote_document_signing.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../app_service.dart'; +import '../data/settings.dart'; +import '../di.dart'; +import 'onboarding.dart'; +import 'screens/qr_code_scanner_screen.dart'; +import 'screens/start_remote_document_signing_screen.dart'; + +/// Helper for "Remote Document Signing" flow. +/// +/// Reads and sets [Settings.remoteDocumentSigningOnboardingPassed]. +/// +/// And then displays: +/// 1. When `false` - [StartRemoteDocumentSigningScreen] +/// 2. When `true` - [QRCodeScannerScreen] directly +/// +/// See also: +/// - [Onboarding] +abstract class RemoteDocumentSigning { + /// Starts 1st screen in this flow. + /// + /// When [forceReplace] is `true`, then [Navigator.pushReplacement] is used. + static Future startRemoteDocumentSigning( + BuildContext context, [ + bool forceReplace = false, + ]) async { + final settings = context.read(); + final onboardingPassed = + settings.remoteDocumentSigningOnboardingPassed.value; + Widget screen = onboardingPassed + ? const QRCodeScannerScreen() + : const StartRemoteDocumentSigningScreen(); + final result = await _navigateToScreen(context, screen, forceReplace); + + if (result is String) { + getIt.get().newIncomingUri(result); + } + } + + static Future _navigateToScreen( + BuildContext context, + Widget screen, [ + bool replace = false, + ]) { + final navigator = Navigator.of(context); + final route = MaterialPageRoute(builder: (_) => screen); + + return replace ? navigator.pushReplacement(route) : navigator.push(route); + } +} diff --git a/lib/ui/screens/about_screen.dart b/lib/ui/screens/about_screen.dart index 29a0f7d..08e5e8b 100644 --- a/lib/ui/screens/about_screen.dart +++ b/lib/ui/screens/about_screen.dart @@ -43,9 +43,10 @@ class _Body extends StatelessWidget { ), const SizedBox(height: 16), const AppVersionText(), - const SizedBox(height: 64), + const SizedBox(height: 16), + Text(strings.aboutAuthorsText), + const SizedBox(height: 16), Text(strings.eidSDKLicenseText), - const SizedBox(height: 64), const Spacer(), TextButton( style: TextButton.styleFrom( diff --git a/lib/ui/screens/main_menu_screen.dart b/lib/ui/screens/main_menu_screen.dart index 6f17d76..3e8e7b4 100644 --- a/lib/ui/screens/main_menu_screen.dart +++ b/lib/ui/screens/main_menu_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; import '../../strings_context.dart'; +import '../remote_document_signing.dart'; import '../widgets/app_version_text.dart'; import 'about_screen.dart'; import 'settings_screen.dart'; @@ -95,9 +96,8 @@ class MainMenuScreen extends StatelessWidget { } static Future _showSignRemoteDocument(BuildContext context) { - const screen = StartRemoteDocumentSigningScreen(); - - return _openScreen(context, screen); + // forceReplace=true so it's same as in _openScreen + return RemoteDocumentSigning.startRemoteDocumentSigning(context, true); } static Future _showPrivacyPolicy(BuildContext context) { diff --git a/lib/ui/screens/main_screen.dart b/lib/ui/screens/main_screen.dart index 26c99c4..6542592 100644 --- a/lib/ui/screens/main_screen.dart +++ b/lib/ui/screens/main_screen.dart @@ -18,6 +18,7 @@ import '../../services/encryption_key_registry.dart'; import '../../strings_context.dart'; import '../app_theme.dart'; import '../onboarding.dart'; +import '../remote_document_signing.dart'; import '../widgets/autogram_logo.dart'; import 'main_menu_screen.dart'; import 'open_document_screen.dart'; @@ -130,10 +131,7 @@ class _MainScreenState extends State { } Future _showQrCodeScanner() { - const screen = StartRemoteDocumentSigningScreen(); - final route = MaterialPageRoute(builder: (_) => screen); - - return Navigator.of(context).push(route); + return RemoteDocumentSigning.startRemoteDocumentSigning(context); } Future _openNewFile(FutureOr file) { diff --git a/lib/ui/screens/onboarding_accept_document_screen.dart b/lib/ui/screens/onboarding_accept_document_screen.dart index 455fa5e..4dbcd46 100644 --- a/lib/ui/screens/onboarding_accept_document_screen.dart +++ b/lib/ui/screens/onboarding_accept_document_screen.dart @@ -18,7 +18,7 @@ import 'show_document_screen.dart'; /// [Onboarding] screen to accept document - Privacy Policy or Terms of Service. /// -/// Uses [GetDocumentVersionUseCase]. +/// Uses [GetHtmlDocumentVersionUseCase]. /// /// See also: /// - [ShowDocumentScreen] @@ -103,7 +103,7 @@ class _OnboardingAcceptDocumentScreenState } void _onAccept() async { - final getDocumentVersion = getIt.get(); + final getDocumentVersion = getIt.get(); try { setState(() { @@ -143,10 +143,6 @@ class _OnboardingAcceptDocumentScreenState type: OnboardingAcceptDocumentScreen, ) Widget previewOnboardingAcceptDocumentScreen(BuildContext context) { - getIt.registerLazySingleton( - () => GetDocumentVersionUseCase(), - ); - final strings = context.strings; final title = context.knobs.string( label: "Title", diff --git a/lib/ui/screens/onboarding_select_signing_certificate_screen.dart b/lib/ui/screens/onboarding_select_signing_certificate_screen.dart index 6033cf1..8d6ce13 100644 --- a/lib/ui/screens/onboarding_select_signing_certificate_screen.dart +++ b/lib/ui/screens/onboarding_select_signing_certificate_screen.dart @@ -15,12 +15,12 @@ import '../widgets/loading_indicator.dart'; import '../widgets/step_indicator.dart'; /// [Onboarding] screen to select and save signing certificate into -/// [ISettings.signingCertificate]. +/// [Settings.signingCertificate]. /// This screen can be skipped. /// /// Uses [SelectSigningCertificateCubit]. /// -/// Consumes [ISettings]. +/// Consumes [Settings]. class OnboardingSelectSigningCertificateScreen extends StatelessWidget { final ValueSetter onCertificateSelected; final ValueSetter? onSkipRequested; @@ -46,7 +46,7 @@ class OnboardingSelectSigningCertificateScreen extends StatelessWidget { Widget _buildBody(BuildContext context) { return BlocProvider( create: (context) { - final settings = context.read(); + final settings = context.read(); final signingCertificate = settings.signingCertificate; return GetIt.instance.get( @@ -86,7 +86,7 @@ class OnboardingSelectSigningCertificateScreen extends StatelessWidget { BuildContext context, Certificate certificate, ) { - context.read().signingCertificate.value = certificate; + context.read().signingCertificate.value = certificate; onCertificateSelected.call(context); } } diff --git a/lib/ui/screens/open_document_screen.dart b/lib/ui/screens/open_document_screen.dart index 3287c3c..2689330 100644 --- a/lib/ui/screens/open_document_screen.dart +++ b/lib/ui/screens/open_document_screen.dart @@ -28,7 +28,7 @@ class OpenDocumentScreen extends StatelessWidget { Widget build(BuildContext context) { final body = BlocProvider( create: (context) { - final settings = context.read(); + final settings = context.read(); final pdfSigningOption = settings.signingPdfContainer.value; return GetIt.instance.get( diff --git a/lib/ui/screens/preview_document_screen.dart b/lib/ui/screens/preview_document_screen.dart index 01104de..ed08e0c 100644 --- a/lib/ui/screens/preview_document_screen.dart +++ b/lib/ui/screens/preview_document_screen.dart @@ -119,7 +119,7 @@ class _Body extends StatelessWidget { onSignRequested: onSignRequested, ), PreviewDocumentErrorState state => ErrorContent( - title: context.strings.previewDocumentErrorTitle, + title: context.strings.previewDocumentErrorHeading, error: state.error, ), }; diff --git a/lib/ui/screens/select_certificate_screen.dart b/lib/ui/screens/select_certificate_screen.dart index f4b91aa..aefa393 100644 --- a/lib/ui/screens/select_certificate_screen.dart +++ b/lib/ui/screens/select_certificate_screen.dart @@ -1,25 +1,31 @@ import 'package:eidmsdk/types.dart' show Certificate; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:get_it/get_it.dart'; import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; +import '../../bloc/get_document_signature_type_cubit.dart'; import '../../bloc/select_signing_certificate_cubit.dart'; import '../../certificate_extensions.dart'; import '../../data/document_signing_type.dart'; import '../../data/settings.dart'; import '../../data/signature_type.dart'; +import '../../di.dart'; import '../../oids.dart'; import '../../strings_context.dart'; import '../app_theme.dart'; import '../fragment/select_signing_certificate_fragment.dart'; +import '../widgets/error_content.dart'; +import '../widgets/loading_content.dart'; import '../widgets/signature_type_picker.dart'; import 'sign_document_screen.dart'; -/// Screen for selecting the signature type using [SignatureTypePicker]. -/// Expecting to have at most 1 QES [Certificate]. +/// Screen for +/// - loading and presenting [Certificate] +/// - and then selecting the [SignatureType] using [SignatureTypePicker]. /// -/// Uses [SelectSigningCertificateCubit]. +/// Uses [SelectSigningCertificateCubit] and [GetDocumentSignatureTypeCubit]. +/// +/// Consumes [Settings]. /// /// Navigates next to [SignDocumentScreen]. class SelectCertificateScreen extends StatelessWidget { @@ -36,10 +42,10 @@ class SelectCertificateScreen extends StatelessWidget { Widget build(BuildContext context) { return BlocProvider( create: (context) { - final settings = context.read(); + final settings = context.read(); final signingCertificate = settings.signingCertificate; - return GetIt.instance.get( + return getIt.get( param1: signingCertificate, )..getCertificates(); }, @@ -57,6 +63,8 @@ class SelectCertificateScreen extends StatelessWidget { ), body: _Body( state: state, + signingType: signingType, + documentId: documentId, onSignDocumentRequested: (certificate, signatureType) { _onSignDocumentRequested( context: context, @@ -79,7 +87,7 @@ class SelectCertificateScreen extends StatelessWidget { required Certificate certificate, required SignatureType signatureType, }) { - context.read().signingCertificate.value = certificate; + context.read().signingCertificate.value = certificate; final screen = SignDocumentScreen( documentId: documentId, @@ -102,12 +110,17 @@ class SelectCertificateScreen extends StatelessWidget { /// [SelectCertificateScreen] body. class _Body extends StatelessWidget { final SelectSigningCertificateState state; + + final DocumentSigningType signingType; + final String documentId; final void Function(Certificate certificate, SignatureType signatureType)? onSignDocumentRequested; final VoidCallback? onReloadCertificatesRequested; const _Body({ required this.state, + this.signingType = DocumentSigningType.local, + this.documentId = '', this.onSignDocumentRequested, this.onReloadCertificatesRequested, }); @@ -126,6 +139,8 @@ class _Body extends StatelessWidget { successBuilder: (context, certificate) { return _SelectSignatureTypeContent( certificate: certificate, + signingType: signingType, + documentId: documentId, onSignDocumentRequested: (final SignatureType signatureType) { onSignDocumentRequested?.call(certificate, signatureType); }, @@ -136,13 +151,25 @@ class _Body extends StatelessWidget { } } +/// Success content where [Certificate] was loaded. +/// Only thing is to determine [SignatureType] for given Document, either when: +/// - [DocumentSigningType.local] - from [Settings.signatureType] +/// - [DocumentSigningType.remote] - by calling [GetDocumentSignatureTypeCubit.getDocumentSignatureType] +/// +/// Uses [GetDocumentSignatureTypeCubit]. +/// +/// Consumes [Settings]. class _SelectSignatureTypeContent extends StatefulWidget { final String? subject; + final DocumentSigningType signingType; + final String documentId; final ValueSetter? onSignDocumentRequested; final VoidCallback? onReloadCertificatesRequested; _SelectSignatureTypeContent({ required Certificate certificate, + required this.signingType, + required this.documentId, required this.onSignDocumentRequested, required this.onReloadCertificatesRequested, }) : subject = certificate.tbsCertificate.subject[X500Oids.cn]; @@ -160,24 +187,68 @@ class _SelectSignatureTypeContentState void initState() { super.initState(); - _signatureType = context.read().signatureType.value; + if (widget.signingType == DocumentSigningType.local) { + _signatureType = context.read().signatureType.value; + } } @override Widget build(BuildContext context) { final strings = context.strings; + final body = BlocProvider( + create: (context) { + final cubit = getIt.get(); + + switch (widget.signingType) { + // TODO Refactor this flow; Need to work with Settings in cubit ctor + // And refactor this widget to be Stateful and work only with Cubit state + case DocumentSigningType.local: + cubit.setSignatureType(_signatureType); + break; + + case DocumentSigningType.remote: + cubit.getDocumentSignatureType(widget.documentId); + break; + } + + return cubit; + }, + child: BlocConsumer( + listener: (context, state) { + if (state is GetDocumentSignatureTypeSuccessState) { + setState(() { + _signatureType = + (state.signatureType ?? SignatureType.withoutTimestamp); + }); + } + }, + builder: (context, state) { + return switch (state) { + GetDocumentSignatureTypeInitialState _ => const LoadingContent(), + GetDocumentSignatureTypeLoadingState _ => const LoadingContent(), + GetDocumentSignatureTypeErrorState state => ErrorContent( + title: strings.signatureTypeErrorHeading, + error: state.error, + ), + GetDocumentSignatureTypeSuccessState state => SignatureTypePicker( + value: _signatureType, + canChange: (widget.signingType == DocumentSigningType.local), + onValueChanged: (final SignatureType value) { + setState(() { + _signatureType = value; + }); + }, + ), + }; + }, + ), + ); return Column( children: [ Expanded( - child: SignatureTypePicker( - value: _signatureType, - onValueChanged: (final SignatureType value) { - setState(() { - _signatureType = value; - }); - }, - ), + child: body, ), // Primary button diff --git a/lib/ui/screens/settings_screen.dart b/lib/ui/screens/settings_screen.dart index 90fc8ea..2cb3a96 100644 --- a/lib/ui/screens/settings_screen.dart +++ b/lib/ui/screens/settings_screen.dart @@ -6,7 +6,7 @@ import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; import '../../certificate_extensions.dart'; import '../../data/pdf_signing_option.dart'; -import '../../data/settings.dart' show ISettings; +import '../../data/settings.dart'; import '../../data/signature_type.dart'; import '../../oids.dart'; import '../../strings_context.dart'; @@ -16,12 +16,12 @@ import '../widgets/option_picker.dart'; import '../widgets/preference_tile.dart'; import 'paired_device_list_screen.dart'; -/// App setting screen for editing [ISettings]. +/// App setting screen for editing [Settings]. /// /// Contains: -/// - editor for [ISettings.signingPdfContainer] -/// - display for [ISettings.signingCertificate] -/// - editor for [ISettings.signatureType] +/// - editor for [Settings.signingPdfContainer] +/// - display for [Settings.signingCertificate] +/// - editor for [Settings.signatureType] /// - navigate to [PairedDeviceListScreen] class SettingsScreen extends StatelessWidget { const SettingsScreen({super.key}); @@ -45,7 +45,7 @@ class SettingsScreen extends StatelessWidget { /// [SettingsScreen] body. class _Body extends StatelessWidget { - final ISettings settings; + final Settings settings; const _Body({required this.settings}); @@ -87,7 +87,7 @@ class _Body extends StatelessWidget { Widget _buildSettingsList(BuildContext context) { final strings = context.strings; - // ISettings.signingPdfContainer + // Settings.signingPdfContainer final signingPdfContainerSetting = _ValueListenableBoundTile( setting: settings.signingPdfContainer, @@ -96,7 +96,7 @@ class _Body extends StatelessWidget { summaryGetter: (value) => value.label, ); - // ISettings.signingCertificate + // Settings.signingCertificate final signingCertificate = PreferenceTile( title: strings.signingCertificateTitle, summary: () { @@ -116,7 +116,11 @@ class _Body extends StatelessWidget { final signatureType = _ValueListenableBoundTile( setting: settings.signatureType, - values: SignatureType.values, + values: const [ + SignatureType.unset, + SignatureType.withTimestamp, + SignatureType.withoutTimestamp, + ], title: strings.signatureTypeTitle, summaryGetter: (value) => strings.signatureTypeSummary(value.name), ); @@ -140,8 +144,8 @@ class _Body extends StatelessWidget { divider, signatureType, divider, - pairedDevices, - divider, + // pairedDevices, TODO: uncomment when pairing works + // divider, ], ); } @@ -261,8 +265,8 @@ Widget previewSettingsScreen(BuildContext context) { ); } -/// Mock [ISettings] impl. for preview. -class _MockSettings implements ISettings { +/// Mock [Settings] impl. for preview. +class _MockSettings implements Settings { @override late final ValueNotifier acceptedPrivacyPolicyVersion = ValueNotifier(null); @@ -283,6 +287,10 @@ class _MockSettings implements ISettings { late final ValueNotifier signingCertificate = ValueNotifier(null); + @override + late final ValueNotifier remoteDocumentSigningOnboardingPassed = + ValueNotifier(false); + @override Future clear() { final props = [ @@ -291,6 +299,7 @@ class _MockSettings implements ISettings { signingPdfContainer, signatureType, signingCertificate, + remoteDocumentSigningOnboardingPassed ]; for (final prop in props) { diff --git a/lib/ui/screens/start_remote_document_signing_screen.dart b/lib/ui/screens/start_remote_document_signing_screen.dart index abf59bc..05cf7fc 100644 --- a/lib/ui/screens/start_remote_document_signing_screen.dart +++ b/lib/ui/screens/start_remote_document_signing_screen.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart' show Logger; +import 'package:provider/provider.dart' show ReadContext; import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; -import '../../app_service.dart'; -import '../../di.dart'; +import '../../data/settings.dart'; import '../../strings_context.dart'; import '../app_theme.dart' show kPrimaryButtonMinimumSize, kScreenMargin; import 'qr_code_scanner_screen.dart'; @@ -67,14 +67,17 @@ class StartRemoteDocumentSigningScreen extends StatelessWidget { } Future _startQrCodeScanner(BuildContext context) async { + // Mark that already seen this screen + context.read().remoteDocumentSigningOnboardingPassed.value = true; + const screen = QRCodeScannerScreen(); final route = MaterialPageRoute(builder: (_) => screen); final result = await Navigator.of(context).push(route); _logger.info('QR code scanner result: "$result".'); - if (result is String) { - getIt.get().newIncomingUri(result); + if (context.mounted) { + Navigator.of(context).maybePop(result); } } } diff --git a/lib/ui/widgets/signature_type_picker.dart b/lib/ui/widgets/signature_type_picker.dart index 509d34a..fc48990 100644 --- a/lib/ui/widgets/signature_type_picker.dart +++ b/lib/ui/widgets/signature_type_picker.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; import 'package:widgetbook_annotation/widgetbook_annotation.dart' as widgetbook; import '../../data/signature_type.dart'; @@ -6,17 +7,22 @@ import '../../strings_context.dart'; import '../app_theme.dart'; import 'certificate_picker.dart'; -/// Displays two options to select the [SignatureType] options. +/// Displays two options to select the [SignatureType] options; either +/// [SignatureType.withTimestamp] or [SignatureType.withoutTimestamp]. +/// +/// When [canChange] is `false`, value selection by user is disabled. /// /// See also: /// - [CertificatePicker] class SignatureTypePicker extends StatelessWidget { final SignatureType? value; + final bool canChange; final ValueChanged onValueChanged; const SignatureTypePicker({ super.key, required this.value, + this.canChange = true, required this.onValueChanged, }); @@ -36,6 +42,7 @@ class SignatureTypePicker extends StatelessWidget { Widget _listItem(SignatureType value) { return _ListItem( value: value, // value from param + canSelect: canChange, selectedValue: this.value, // value from Widget onSelected: () { onValueChanged(value); @@ -47,11 +54,13 @@ class SignatureTypePicker extends StatelessWidget { /// [SignatureTypePicker] - [ListView] item. class _ListItem extends StatelessWidget { final SignatureType value; + final bool canSelect; final SignatureType? selectedValue; final VoidCallback onSelected; const _ListItem({ required this.value, + required this.canSelect, required this.selectedValue, required this.onSelected, }); @@ -62,24 +71,29 @@ class _ListItem extends StatelessWidget { final titleText = strings.signatureTypeValueTitle(value.name); final subtitleText = strings.signatureTypeValueSubtitle(value.name); - // NOT using RadioListTile because need to scale-up and style Radio + final enabled = (canSelect || value == selectedValue); + final radio = Radio( + value: value, + groupValue: selectedValue, + onChanged: enabled + ? (final SignatureType? value) { + if (value != null) { + if (selectedValue != value) { + onSelected(); + } + } + } + : null, + activeColor: kRadioActiveColor, + ); + // NOT using RadioListTile because need to scale-up and style Radio return ListTile( - onTap: onSelected, + onTap: (canSelect ? onSelected : null), + enabled: enabled, leading: Transform.scale( scale: kRadioScale, - child: Radio( - value: value, - groupValue: selectedValue, - onChanged: (final SignatureType? value) { - if (value != null) { - if (selectedValue != value) { - onSelected(); - } - } - }, - activeColor: kRadioActiveColor, - ), + child: radio, ), title: Text( titleText, @@ -92,16 +106,24 @@ class _ListItem extends StatelessWidget { @widgetbook.UseCase( path: '[Lists]', - name: 'SignatureTypePicker', + name: '', type: SignatureTypePicker, ) Widget previewSignatureTypePicker(BuildContext context) { - SignatureType? selectedValue; + final bool canChange = context.knobs.boolean( + label: "Can change", + initialValue: true, + ); + SignatureType? selectedValue = context.knobs.listOrNull( + label: "Signature type", + options: [SignatureType.withTimestamp, SignatureType.withoutTimestamp], + ); return StatefulBuilder( builder: (context, setState) { return SignatureTypePicker( value: selectedValue, + canChange: canChange, onValueChanged: (value) { setState(() => selectedValue = value); }, diff --git a/lib/use_case/get_document_signature_type_use_case.dart b/lib/use_case/get_document_signature_type_use_case.dart new file mode 100644 index 0000000..4da08cd --- /dev/null +++ b/lib/use_case/get_document_signature_type_use_case.dart @@ -0,0 +1,26 @@ +import 'package:autogram_sign/autogram_sign.dart' + show IAutogramService, SigningParametersLevel; +import 'package:injectable/injectable.dart' show lazySingleton; + +import '../data/signature_type.dart'; + +/// Gets the Document [SignatureType]. +@lazySingleton +class GetDocumentSignatureTypeUseCase { + final IAutogramService autogramService; + + GetDocumentSignatureTypeUseCase(this.autogramService); + + /// Gets the [SignatureType] of Document with given [documentId]. + Future call(String documentId) async { + final params = await autogramService.getDocumentParameters(documentId); + + return switch (params.level) { + null => null, + SigningParametersLevel.cadesBaselineT => SignatureType.withTimestamp, + SigningParametersLevel.padesBaselineT => SignatureType.withTimestamp, + SigningParametersLevel.xadesBaselineT => SignatureType.withTimestamp, + _ => SignatureType.withoutTimestamp + }; + } +} diff --git a/lib/use_case/get_document_version_use_case.dart b/lib/use_case/get_document_version_use_case.dart index 63838c7..4604d0d 100644 --- a/lib/use_case/get_document_version_use_case.dart +++ b/lib/use_case/get_document_version_use_case.dart @@ -5,11 +5,10 @@ import 'package:html/parser.dart' as html show parse; import 'package:http/http.dart' as http; import 'package:injectable/injectable.dart'; -/// Gets the document - Privacy Policy or Terms of Service - version. -/// -/// Impl. reads value from "version" `meta` tag. +/// Gets the HTML document version by reading value +/// from "version" HTML `meta` tag. @lazySingleton -class GetDocumentVersionUseCase { +class GetHtmlDocumentVersionUseCase { static final http.Client _client = http.Client(); /// Gets the document version on this [url]. @@ -21,17 +20,7 @@ class GetDocumentVersionUseCase { url, "url", "GET on URL yields no text content."); } - final document = html.parse(text); - final meta = - document.querySelector('html > head > meta[name="version"][content]'); - final version = (meta?.attributes["content"]?.trim() ?? ''); - - if (version.isEmpty) { - throw ArgumentError.value( - url, "url", "Required meta tag is not present."); - } - - return version; + return parseVersion(text); } /// Gets the HTML content from given [url]. diff --git a/lib/widgetbook_app.directories.g.dart b/lib/widgetbook_app.directories.g.dart index 4b83093..c752a52 100644 --- a/lib/widgetbook_app.directories.g.dart +++ b/lib/widgetbook_app.directories.g.dart @@ -278,7 +278,7 @@ final directories = <_i1.WidgetbookNode>[ _i1.WidgetbookLeafComponent( name: 'SignatureTypePicker', useCase: _i1.WidgetbookUseCase( - name: 'SignatureTypePicker', + name: '', builder: _i23.previewSignatureTypePicker, ), ), diff --git a/pubspec.lock b/pubspec.lock index fd31bbd..59de53e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: archive - sha256: "6bd38d335f0954f5fad9c79e614604fbf03a0e5b975923dd001b6ea965ef5b4b" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.6.0" + version: "3.6.1" args: dependency: transitive description: @@ -63,7 +63,7 @@ packages: path: "../autogram_sign" relative: true source: path - version: "0.4.0" + version: "0.4.1" barcode: dependency: transitive description: @@ -347,10 +347,10 @@ packages: dependency: transitive description: name: firebase_core_platform_interface - sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 + sha256: "1003a5a03a61fc9a22ef49f37cbcb9e46c86313a7b2e7029b9390cf8c6fc32cb" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.0" firebase_core_web: dependency: transitive description: @@ -392,10 +392,10 @@ packages: dependency: "direct main" description: name: flutter_bloc - sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2 + sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a url: "https://pub.dev" source: hosted - version: "8.1.5" + version: "8.1.6" flutter_hooks: dependency: "direct main" description: @@ -791,10 +791,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -847,10 +847,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.3.0" qr: dependency: transitive description: @@ -1132,10 +1132,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.2.6" + version: "6.3.0" url_launcher_android: dependency: transitive description: @@ -1308,10 +1308,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: "2282ba2320af34b2bd5320156c664d73f3f022341ed78847bc87723bf88c142f" + sha256: "0d21cfc3bfdd2e30ab2ebeced66512b91134b39e72e97b43db2d47dda1c4e53a" url: "https://pub.dev" source: hosted - version: "3.16.2" + version: "3.16.3" webview_flutter_platform_interface: dependency: transitive description: @@ -1340,10 +1340,10 @@ packages: dependency: "direct main" description: name: widgetbook - sha256: be0588cd864a70bd2817ca2fab0dbd147c460b712cfe53fd13e6fd46b5c3f368 + sha256: "872e7e9065ef6e85a1e93b3b41830f90af575c5a898b6c573acdc972fad0fb29" url: "https://pub.dev" source: hosted - version: "3.7.1" + version: "3.8.0" widgetbook_annotation: dependency: "direct main" description: @@ -1356,10 +1356,10 @@ packages: dependency: "direct dev" description: name: widgetbook_generator - sha256: d81512780bb4d137d59f0694328f446433fe5228cf8191e918046cc4f09c128b + sha256: "7a5baf68bb76cbd8aeb093050172f99bb83b44360bf2a72c2b19b3785e3d29d0" url: "https://pub.dev" source: hosted - version: "3.7.0" + version: "3.8.0" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3525c3f..fa2cca1 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.0+25 +version: 1.0.2+33 environment: sdk: '>=3.2.3 <4.0.0' @@ -15,16 +15,16 @@ dependencies: path: ../eidmsdk_flutter autogram_sign: path: ../autogram_sign + package_info_plus: + share_plus: + wakelock_plus: flutter_svg: - package_info_plus: ^5.0.1 http: chopper: '>=7.4.0 <8.0.0' injectable: ^2.2.0 get_it: ^7.6.1 flutter_hooks: ^0.20.5 localstorage: ^4.0.1+4 - share_plus: ^7.2.2 - wakelock_plus: pdf: printing: ^5.12.0 webview_flutter: diff --git a/test/use_case/get_document_version_use_case_test.dart b/test/use_case/get_html_document_version_use_case_test.dart similarity index 79% rename from test/use_case/get_document_version_use_case_test.dart rename to test/use_case/get_html_document_version_use_case_test.dart index 07f406b..c53a1d7 100644 --- a/test/use_case/get_document_version_use_case_test.dart +++ b/test/use_case/get_html_document_version_use_case_test.dart @@ -1,17 +1,17 @@ import 'package:autogram/use_case/get_document_version_use_case.dart'; import 'package:test/test.dart'; -/// Tests for the [GetDocumentVersionUseCase] class. +/// Tests for the [GetHtmlDocumentVersionUseCase] class. void main() { group('GetDocumentVersionUseCase', () { test('parseVersion throws ArgumentError for empty HTML', () { expect( - () => GetDocumentVersionUseCase.parseVersion(""), + () => GetHtmlDocumentVersionUseCase.parseVersion(""), throwsA(predicate((e) => e is ArgumentError && e.name == 'text')), ); expect( - () => GetDocumentVersionUseCase.parseVersion(''), + () => GetHtmlDocumentVersionUseCase.parseVersion(''), throwsA(predicate((e) => e is ArgumentError && e.name == 'text')), ); }); @@ -20,14 +20,14 @@ void main() { 'parseVersion throws ArgumentError for HTML with missing or invalid meta', () { expect( - () => GetDocumentVersionUseCase.parseVersion( + () => GetHtmlDocumentVersionUseCase.parseVersion( '</head></html>', ), throwsA(predicate((e) => e is ArgumentError && e.name == 'text')), ); expect( - () => GetDocumentVersionUseCase.parseVersion(""" + () => GetHtmlDocumentVersionUseCase.parseVersion(""" <html lang="en"> <head> <title /> @@ -38,7 +38,7 @@ void main() { throwsA(predicate((e) => e is ArgumentError && e.name == 'text')), ); expect( - () => GetDocumentVersionUseCase.parseVersion(""" + () => GetHtmlDocumentVersionUseCase.parseVersion(""" <html lang="en"> <head> <title /> @@ -49,7 +49,7 @@ void main() { throwsA(predicate((e) => e is ArgumentError && e.name == 'text')), ); expect( - () => GetDocumentVersionUseCase.parseVersion(""" + () => GetHtmlDocumentVersionUseCase.parseVersion(""" <html lang="en"> <head> <title /> @@ -63,14 +63,14 @@ void main() { test('parseVersion returns value for valid meta with value', () { expect( - () => GetDocumentVersionUseCase.parseVersion( + () => GetHtmlDocumentVersionUseCase.parseVersion( '<html lang="en"><head><title>', ), throwsA(predicate((e) => e is ArgumentError && e.name == 'text')), ); expect( - () => GetDocumentVersionUseCase.parseVersion(""" + () => GetHtmlDocumentVersionUseCase.parseVersion(""" @@ -81,7 +81,7 @@ void main() { throwsA(predicate((e) => e is ArgumentError && e.name == 'text')), ); expect( - GetDocumentVersionUseCase.parseVersion(""" + GetHtmlDocumentVersionUseCase.parseVersion("""