diff --git a/.github/workflows/bank-api-library.check.yml b/.github/workflows/bank-api-library.check.yml index 52fe96e79c..f80564c298 100644 --- a/.github/workflows/bank-api-library.check.yml +++ b/.github/workflows/bank-api-library.check.yml @@ -72,7 +72,7 @@ jobs: script: > adb uninstall net.gini.android.bank.api.test ; ./gradlew bank-api-library:library:connectedCheck - -PtestClientId="gini-mobile-test" + -PtestClientId="gini-mobile-ci" -PtestClientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PtestApiUri="https://pay-api.gini.net" -PtestUserCenterUri="https://user.gini.net" diff --git a/.github/workflows/bank-sdk.check.yml b/.github/workflows/bank-sdk.check.yml index f718d66ab0..c79997d549 100644 --- a/.github/workflows/bank-sdk.check.yml +++ b/.github/workflows/bank-sdk.check.yml @@ -108,7 +108,7 @@ jobs: script: > adb uninstall net.gini.android.bank.sdk.test ; ./gradlew bank-sdk:sdk:connectedCheck - -PtestClientId="gini-mobile-test" + -PtestClientId="gini-mobile-ci" -PtestClientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PtestApiUri="https://pay-api.gini.net" -PtestUserCenterUri="https://user.gini.net" @@ -132,7 +132,7 @@ jobs: script: > adb uninstall net.gini.android.bank.sdk.exampleapp.test ; ./gradlew bank-sdk:example-app:connectedDevExampleAppDebugAndroidTest - -PclientId="gini-mobile-test" + -PclientId="gini-mobile-ci" -PclientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" - name: archive instrumented test results @@ -157,7 +157,7 @@ jobs: - name: build release example app for QA run: > ./gradlew bank-sdk:example-app:assembleQaExampleAppRelease - -PclientId="gini-mobile-test" + -PclientId="gini-mobile-ci" -PclientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PreleaseKeystoreFile="screen_api_example.jks" -PreleaseKeystorePassword='${{ secrets.BANK_SDK_EXAMPLE_APP_KEYSTORE_PASSWORD }}' @@ -173,7 +173,7 @@ jobs: - name: build release example app for production run: > ./gradlew bank-sdk:example-app:assembleProdExampleAppRelease - -PclientId="gini-mobile-test" + -PclientId="gini-mobile-ci" -PclientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PreleaseKeystoreFile="screen_api_example.jks" -PreleaseKeystorePassword='${{ secrets.BANK_SDK_EXAMPLE_APP_KEYSTORE_PASSWORD }}' diff --git a/.github/workflows/bank-sdk.publish.firebase.example.yml b/.github/workflows/bank-sdk.publish.firebase.example.yml index 045de85ae7..476fc4b4c2 100644 --- a/.github/workflows/bank-sdk.publish.firebase.example.yml +++ b/.github/workflows/bank-sdk.publish.firebase.example.yml @@ -47,7 +47,7 @@ jobs: - name: build release example app for QA run: > ./gradlew bank-sdk:example-app:assembleQaExampleAppRelease - -PclientId="gini-mobile-test" + -PclientId="gini-mobile-ci" -PclientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PreleaseKeystoreFile="screen_api_example.jks" -PreleaseKeystorePassword='${{ secrets.BANK_SDK_EXAMPLE_APP_KEYSTORE_PASSWORD }}' diff --git a/.github/workflows/capture-sdk.check.yml b/.github/workflows/capture-sdk.check.yml index 9f239efcc0..5f96701b74 100644 --- a/.github/workflows/capture-sdk.check.yml +++ b/.github/workflows/capture-sdk.check.yml @@ -203,7 +203,7 @@ jobs: script: > adb uninstall net.gini.android.capture.network.test ; ./gradlew capture-sdk:default-network:connectedCheck - -PtestClientId="gini-mobile-test" + -PtestClientId="gini-mobile-ci" -PtestClientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PtestApiUri="https://pay-api.gini.net" -PtestUserCenterUri="https://user.gini.net" diff --git a/.github/workflows/gpc.publish.firebase.example.yml b/.github/workflows/gpc.publish.firebase.example.yml index 7e513ccd64..f142a17f62 100644 --- a/.github/workflows/gpc.publish.firebase.example.yml +++ b/.github/workflows/gpc.publish.firebase.example.yml @@ -32,7 +32,7 @@ jobs: - name: build release example app for QA run: > ./gradlew bank-sdk:example-app:assembleQaPaymentProvider3Release - -PclientId="gini-mobile-test" + -PclientId="gini-mobile-ci" -PclientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PreleaseKeystoreFile="screen_api_example.jks" -PreleaseKeystorePassword='${{ secrets.BANK_SDK_EXAMPLE_APP_KEYSTORE_PASSWORD }}' diff --git a/.github/workflows/health-api-library.check.yml b/.github/workflows/health-api-library.check.yml index 6bb2fe0c6e..a2b2d49686 100644 --- a/.github/workflows/health-api-library.check.yml +++ b/.github/workflows/health-api-library.check.yml @@ -118,7 +118,7 @@ jobs: script: > adb uninstall net.gini.android.health.api.test ; ./gradlew -Pandroid.testInstrumentationRunnerArguments.apiEnv=${{ inputs.healthApiEnvironment }} health-api-library:library:connectedCheck - -PtestClientId='gini-mobile-test' + -PtestClientId='gini-mobile-ci' -PtestClientSecret='${{ inputs.healthApiEnvironment == 'staging' && secrets.STAGING_GINI_MOBILE_TEST_CLIENT_SECRET || secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}' -PtestApiUri='${{ inputs.healthApiEnvironment == 'staging' && 'https://health-api.stage.gini.net' || 'https://health-api.gini.net' }}' -PtestUserCenterUri='${{ inputs.healthApiEnvironment == 'staging' && 'https://user.stage.gini.net' || 'https://user.gini.net' }}' diff --git a/.github/workflows/health-sdk.check.yml b/.github/workflows/health-sdk.check.yml index 6a8ae441ed..8dabcb63e2 100644 --- a/.github/workflows/health-sdk.check.yml +++ b/.github/workflows/health-sdk.check.yml @@ -59,7 +59,7 @@ jobs: - name: build release example app for QA run: > ./gradlew health-sdk:example-app:assembleQaRelease - -PclientId="gini-mobile-test" + -PclientId="gini-mobile-ci" -PclientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PreleaseKeystoreFile="health_sdk_example.jks" -PreleaseKeystorePassword='${{ secrets.HEALTH_SDK_EXAMPLE_APP_KEYSTORE_PASSWORD }}' @@ -75,7 +75,7 @@ jobs: - name: build release example app for production run: > ./gradlew health-sdk:example-app:assembleProdRelease - -PclientId="gini-mobile-test" + -PclientId="gini-mobile-ci" -PclientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PreleaseKeystoreFile="health_sdk_example.jks" -PreleaseKeystorePassword='${{ secrets.HEALTH_SDK_EXAMPLE_APP_KEYSTORE_PASSWORD }}' diff --git a/.github/workflows/health-sdk.publish.firebase.example.yml b/.github/workflows/health-sdk.publish.firebase.example.yml index 290caa9163..2f58e7a82c 100644 --- a/.github/workflows/health-sdk.publish.firebase.example.yml +++ b/.github/workflows/health-sdk.publish.firebase.example.yml @@ -46,7 +46,7 @@ jobs: - name: build release example app for QA run: > ./gradlew health-sdk:example-app:assembleQaRelease - -PclientId="gini-mobile-test" + -PclientId="gini-mobile-ci" -PclientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PreleaseKeystoreFile="health_sdk_example.jks" -PreleaseKeystorePassword='${{ secrets.HEALTH_SDK_EXAMPLE_APP_KEYSTORE_PASSWORD }}' diff --git a/.github/workflows/merchant-sdk.check.yml b/.github/workflows/merchant-sdk.check.yml index 0e658653cf..259e236ee4 100644 --- a/.github/workflows/merchant-sdk.check.yml +++ b/.github/workflows/merchant-sdk.check.yml @@ -60,7 +60,7 @@ jobs: - name: build release example app for QA run: > ./gradlew merchant-sdk:example-app:assembleQaRelease - -PclientId="gini-mobile-test" + -PclientId="gini-mobile-ci" -PclientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PreleaseKeystoreFile="merchant_sdk_example.jks" -PreleaseKeystorePassword='${{ secrets.MERCHANT_SDK_EXAMPLE_APP_KEYSTORE_PASSWORD }}' @@ -76,7 +76,7 @@ jobs: - name: build release example app for production run: > ./gradlew merchant-sdk:example-app:assembleProdRelease - -PclientId="gini-mobile-test" + -PclientId="gini-mobile-ci" -PclientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PreleaseKeystoreFile="merchant_sdk_example.jks" -PreleaseKeystorePassword='${{ secrets.MERCHANT_SDK_EXAMPLE_APP_KEYSTORE_PASSWORD }}' diff --git a/.github/workflows/merchant-sdk.publish.firebase.example.yml b/.github/workflows/merchant-sdk.publish.firebase.example.yml index 146e97b40e..3fce0a0c15 100644 --- a/.github/workflows/merchant-sdk.publish.firebase.example.yml +++ b/.github/workflows/merchant-sdk.publish.firebase.example.yml @@ -46,7 +46,7 @@ jobs: - name: build release example app for QA run: > ./gradlew merchant-sdk:example-app:assembleQaRelease - -PclientId="gini-mobile-test" + -PclientId="gini-mobile-ci" -PclientSecret="${{ secrets.GINI_MOBILE_TEST_CLIENT_SECRET }}" -PreleaseKeystoreFile="merchant_sdk_example.jks" -PreleaseKeystorePassword='${{ secrets.MERCHANT_SDK_EXAMPLE_APP_KEYSTORE_PASSWORD }}' diff --git a/bank-api-library/library/src/androidTest/assets/result_Gini_invoice_example.json b/bank-api-library/library/src/androidTest/assets/result_Gini_invoice_example.json index 78d3b8876d..74bd86f6cc 100644 --- a/bank-api-library/library/src/androidTest/assets/result_Gini_invoice_example.json +++ b/bank-api-library/library/src/androidTest/assets/result_Gini_invoice_example.json @@ -1,57 +1,107 @@ { "extractions": { + "docType": { + "entity": "doctype", + "value": "Invoice" + }, + "amountToPay": { + "entity": "amount", + "value": "995.00:EUR", + "candidates": "amounts" + }, "paymentRecipient": { "entity": "companyname", "value": "Fahrrad Rückenwind", + "box": { + "top": 446.55, + "left": 280.55, + "width": 432.99999999999994, + "height": 39.0, + "page": 1 + }, "candidates": "paymentRecipients" }, + "paymentState": { + "entity": "paymentstate", + "value": "ToBePaid" + }, + "instantPayment": { + "entity": "text", + "value": "false" + }, "paymentPurpose": { - "entity": "reference", + "entity": "text", "value": "RE-20210512-02" }, "bic": { "entity": "bic", "value": "BYLADEM1001", + "box": { + "top": 2690.5, + "left": 280.5, + "width": 350.0, + "height": 35.0, + "page": 1 + }, "candidates": "bics" }, - "amountToPay": { - "entity": "amount", - "value": "995.00:EUR", - "candidates": "amounts" - }, "iban": { "entity": "iban", "value": "DE02120300000000202051", + "box": { + "top": 2795.15, + "left": 429.15, + "width": 464.0, + "height": 28.0, + "page": 1 + }, "candidates": "ibans" - }, - "docType": { - "entity": "doctype", - "value": "Invoice" } }, "candidates": { - "paymentRecipients": [ + "amounts": [ { - "entity": "companyname", - "value": "Fahrrad Rückenwind", + "entity": "amount", + "value": "995.00:EUR" + } + ], + "ibans": [ + { + "entity": "iban", + "value": "DE02120300000000202051", "box": { - "top": 103.69, - "left": 66.25, - "width": 107.0, - "height": 12.0, + "top": 2795.15, + "left": 429.15, + "width": 464.0, + "height": 28.0, "page": 1 } }, { - "entity": "companyname", - "value": "Fahrrad Rückenwind", + "entity": "iban", + "value": "DE02120300000000202051", + "box": { + "top": 2795.15, + "left": 274.2, + "width": 618.95, + "height": 470.0499999999997, + "page": 1 + } + }, + { + "entity": "iban", + "value": "DE02120300000000202051", "box": { - "top": 617.53, - "left": 66.25, - "width": 192.95999999999998, - "height": 11.040000000000077, + "top": 3362.55, + "left": 1285.55, + "width": 339.0, + "height": 25.0, "page": 1 } + }, + { + "entity": "iban", + "value": "DE02120300000000202051" } ], "bics": [ @@ -59,10 +109,10 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 794.41, - "left": 282.25, - "width": 62.420000000000016, - "height": 7.920000000000073, + "top": 2690.5, + "left": 280.5, + "width": 350.0, + "height": 35.0, "page": 1 } }, @@ -70,58 +120,42 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 642.49, - "left": 66.25, - "width": 86.03, - "height": 11.039999999999964, + "top": 3320.55, + "left": 1177.55, + "width": 258.0, + "height": 25.0, "page": 1 } } ], - "amounts": [ + "paymentRecipients": [ { - "entity": "amount", - "value": "995.00:EUR", - "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, - "page": 1 - } + "entity": "companyname", + "value": "Fahrrad Rückenwind" }, { - "entity": "amount", - "value": "995.00:EUR", + "entity": "companyname", + "value": "Fahrrad Rückenwind", "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, + "top": 446.55, + "left": 280.55, + "width": 432.99999999999994, + "height": 39.0, "page": 1 } - } - ], - "ibans": [ + }, { - "entity": "iban", - "value": "DE02120300000000202051", - "box": { - "top": 804.49, - "left": 302.17, - "width": 88.56, - "height": 7.919999999999959, - "page": 1 - } + "entity": "companyname", + "value": "FAHRRADÜCKENWIND GMBH" }, { - "entity": "iban", - "value": "DE02120300000000202051", + "entity": "companyname", + "value": "Fahrrad Rückenwind", "box": { - "top": 666.49, - "left": 93.65, - "width": 121.82999999999998, - "height": 11.039999999999964, + "top": 2586.55, + "left": 281.55, + "width": 795.0, + "height": 39.0, "page": 1 } } diff --git a/bank-api-library/library/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json b/bank-api-library/library/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json index 1857e73949..af7317f118 100644 --- a/bank-api-library/library/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json +++ b/bank-api-library/library/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json @@ -5,51 +5,67 @@ "value": "Fahrrad Rückenwind", "candidates": "paymentRecipients" }, - "paymentPurpose": { - "entity": "reference", - "value": "RE-20210512-02" - }, - "bic": { - "entity": "bic", - "value": "BYLADEM1001", - "candidates": "bics" + "iban": { + "entity": "iban", + "value": "DE02120300000000202051", + "candidates": "ibans" }, "amountToPay": { "entity": "amount", "value": "950.00:EUR", "candidates": "amounts" }, - "iban": { - "entity": "iban", - "value": "DE02120300000000202051", - "candidates": "ibans" + "paymentPurpose": { + "entity": "text", + "value": "RE-20210512-02" + }, + "instantPayment": { + "entity": "text", + "value": "false" }, "docType": { "entity": "doctype", "value": "Invoice" + }, + "paymentState": { + "entity": "paymentstate", + "value": "ToBePaid" + }, + "bic": { + "entity": "bic", + "value": "BYLADEM1001", + "candidates": "bics" } }, "candidates": { "paymentRecipients": [ + { + "entity": "companyname", + "value": "Fahrrad Rückenwind" + }, { "entity": "companyname", "value": "Fahrrad Rückenwind", "box": { - "top": 103.69, - "left": 66.25, - "width": 107.0, - "height": 12.0, + "top": 446.55, + "left": 280.55, + "width": 432.99999999999994, + "height": 39, "page": 1 } }, + { + "entity": "companyname", + "value": "FAHRRADÜCKENWIND GMBH" + }, { "entity": "companyname", "value": "Fahrrad Rückenwind", "box": { - "top": 617.53, - "left": 66.25, - "width": 192.95999999999998, - "height": 11.040000000000077, + "top": 2586.55, + "left": 281.55, + "width": 795, + "height": 39, "page": 1 } } @@ -59,10 +75,10 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 794.41, - "left": 282.25, - "width": 62.420000000000016, - "height": 7.920000000000073, + "top": 2690.5, + "left": 280.5, + "width": 350, + "height": 35, "page": 1 } }, @@ -70,10 +86,10 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 642.49, - "left": 66.25, - "width": 86.03, - "height": 11.039999999999964, + "top": 3320.55, + "left": 1177.55, + "width": 258, + "height": 25, "page": 1 } } @@ -81,36 +97,29 @@ "amounts": [ { "entity": "amount", - "value": "995.00:EUR", - "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, - "page": 1 - } - }, + "value": "995.00:EUR" + } + ], + "ibans": [ { - "entity": "amount", - "value": "995.00:EUR", + "entity": "iban", + "value": "DE02120300000000202051", "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, + "top": 2795.15, + "left": 429.15, + "width": 464, + "height": 28, "page": 1 } - } - ], - "ibans": [ + }, { "entity": "iban", "value": "DE02120300000000202051", "box": { - "top": 804.49, - "left": 302.17, - "width": 88.56, - "height": 7.919999999999959, + "top": 2795.15, + "left": 274.2, + "width": 618.95, + "height": 470.0499999999997, "page": 1 } }, @@ -118,12 +127,16 @@ "entity": "iban", "value": "DE02120300000000202051", "box": { - "top": 666.49, - "left": 93.65, - "width": 121.82999999999998, - "height": 11.039999999999964, + "top": 3362.55, + "left": 1285.55, + "width": 339, + "height": 25, "page": 1 } + }, + { + "entity": "iban", + "value": "DE02120300000000202051" } ] } diff --git a/bank-sdk/sdk/src/androidTest/assets/result_Gini_invoice_example.json b/bank-sdk/sdk/src/androidTest/assets/result_Gini_invoice_example.json index 78d3b8876d..74bd86f6cc 100644 --- a/bank-sdk/sdk/src/androidTest/assets/result_Gini_invoice_example.json +++ b/bank-sdk/sdk/src/androidTest/assets/result_Gini_invoice_example.json @@ -1,57 +1,107 @@ { "extractions": { + "docType": { + "entity": "doctype", + "value": "Invoice" + }, + "amountToPay": { + "entity": "amount", + "value": "995.00:EUR", + "candidates": "amounts" + }, "paymentRecipient": { "entity": "companyname", "value": "Fahrrad Rückenwind", + "box": { + "top": 446.55, + "left": 280.55, + "width": 432.99999999999994, + "height": 39.0, + "page": 1 + }, "candidates": "paymentRecipients" }, + "paymentState": { + "entity": "paymentstate", + "value": "ToBePaid" + }, + "instantPayment": { + "entity": "text", + "value": "false" + }, "paymentPurpose": { - "entity": "reference", + "entity": "text", "value": "RE-20210512-02" }, "bic": { "entity": "bic", "value": "BYLADEM1001", + "box": { + "top": 2690.5, + "left": 280.5, + "width": 350.0, + "height": 35.0, + "page": 1 + }, "candidates": "bics" }, - "amountToPay": { - "entity": "amount", - "value": "995.00:EUR", - "candidates": "amounts" - }, "iban": { "entity": "iban", "value": "DE02120300000000202051", + "box": { + "top": 2795.15, + "left": 429.15, + "width": 464.0, + "height": 28.0, + "page": 1 + }, "candidates": "ibans" - }, - "docType": { - "entity": "doctype", - "value": "Invoice" } }, "candidates": { - "paymentRecipients": [ + "amounts": [ { - "entity": "companyname", - "value": "Fahrrad Rückenwind", + "entity": "amount", + "value": "995.00:EUR" + } + ], + "ibans": [ + { + "entity": "iban", + "value": "DE02120300000000202051", "box": { - "top": 103.69, - "left": 66.25, - "width": 107.0, - "height": 12.0, + "top": 2795.15, + "left": 429.15, + "width": 464.0, + "height": 28.0, "page": 1 } }, { - "entity": "companyname", - "value": "Fahrrad Rückenwind", + "entity": "iban", + "value": "DE02120300000000202051", + "box": { + "top": 2795.15, + "left": 274.2, + "width": 618.95, + "height": 470.0499999999997, + "page": 1 + } + }, + { + "entity": "iban", + "value": "DE02120300000000202051", "box": { - "top": 617.53, - "left": 66.25, - "width": 192.95999999999998, - "height": 11.040000000000077, + "top": 3362.55, + "left": 1285.55, + "width": 339.0, + "height": 25.0, "page": 1 } + }, + { + "entity": "iban", + "value": "DE02120300000000202051" } ], "bics": [ @@ -59,10 +109,10 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 794.41, - "left": 282.25, - "width": 62.420000000000016, - "height": 7.920000000000073, + "top": 2690.5, + "left": 280.5, + "width": 350.0, + "height": 35.0, "page": 1 } }, @@ -70,58 +120,42 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 642.49, - "left": 66.25, - "width": 86.03, - "height": 11.039999999999964, + "top": 3320.55, + "left": 1177.55, + "width": 258.0, + "height": 25.0, "page": 1 } } ], - "amounts": [ + "paymentRecipients": [ { - "entity": "amount", - "value": "995.00:EUR", - "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, - "page": 1 - } + "entity": "companyname", + "value": "Fahrrad Rückenwind" }, { - "entity": "amount", - "value": "995.00:EUR", + "entity": "companyname", + "value": "Fahrrad Rückenwind", "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, + "top": 446.55, + "left": 280.55, + "width": 432.99999999999994, + "height": 39.0, "page": 1 } - } - ], - "ibans": [ + }, { - "entity": "iban", - "value": "DE02120300000000202051", - "box": { - "top": 804.49, - "left": 302.17, - "width": 88.56, - "height": 7.919999999999959, - "page": 1 - } + "entity": "companyname", + "value": "FAHRRADÜCKENWIND GMBH" }, { - "entity": "iban", - "value": "DE02120300000000202051", + "entity": "companyname", + "value": "Fahrrad Rückenwind", "box": { - "top": 666.49, - "left": 93.65, - "width": 121.82999999999998, - "height": 11.039999999999964, + "top": 2586.55, + "left": 281.55, + "width": 795.0, + "height": 39.0, "page": 1 } } diff --git a/bank-sdk/sdk/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json b/bank-sdk/sdk/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json index 1857e73949..af7317f118 100644 --- a/bank-sdk/sdk/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json +++ b/bank-sdk/sdk/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json @@ -5,51 +5,67 @@ "value": "Fahrrad Rückenwind", "candidates": "paymentRecipients" }, - "paymentPurpose": { - "entity": "reference", - "value": "RE-20210512-02" - }, - "bic": { - "entity": "bic", - "value": "BYLADEM1001", - "candidates": "bics" + "iban": { + "entity": "iban", + "value": "DE02120300000000202051", + "candidates": "ibans" }, "amountToPay": { "entity": "amount", "value": "950.00:EUR", "candidates": "amounts" }, - "iban": { - "entity": "iban", - "value": "DE02120300000000202051", - "candidates": "ibans" + "paymentPurpose": { + "entity": "text", + "value": "RE-20210512-02" + }, + "instantPayment": { + "entity": "text", + "value": "false" }, "docType": { "entity": "doctype", "value": "Invoice" + }, + "paymentState": { + "entity": "paymentstate", + "value": "ToBePaid" + }, + "bic": { + "entity": "bic", + "value": "BYLADEM1001", + "candidates": "bics" } }, "candidates": { "paymentRecipients": [ + { + "entity": "companyname", + "value": "Fahrrad Rückenwind" + }, { "entity": "companyname", "value": "Fahrrad Rückenwind", "box": { - "top": 103.69, - "left": 66.25, - "width": 107.0, - "height": 12.0, + "top": 446.55, + "left": 280.55, + "width": 432.99999999999994, + "height": 39, "page": 1 } }, + { + "entity": "companyname", + "value": "FAHRRADÜCKENWIND GMBH" + }, { "entity": "companyname", "value": "Fahrrad Rückenwind", "box": { - "top": 617.53, - "left": 66.25, - "width": 192.95999999999998, - "height": 11.040000000000077, + "top": 2586.55, + "left": 281.55, + "width": 795, + "height": 39, "page": 1 } } @@ -59,10 +75,10 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 794.41, - "left": 282.25, - "width": 62.420000000000016, - "height": 7.920000000000073, + "top": 2690.5, + "left": 280.5, + "width": 350, + "height": 35, "page": 1 } }, @@ -70,10 +86,10 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 642.49, - "left": 66.25, - "width": 86.03, - "height": 11.039999999999964, + "top": 3320.55, + "left": 1177.55, + "width": 258, + "height": 25, "page": 1 } } @@ -81,36 +97,29 @@ "amounts": [ { "entity": "amount", - "value": "995.00:EUR", - "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, - "page": 1 - } - }, + "value": "995.00:EUR" + } + ], + "ibans": [ { - "entity": "amount", - "value": "995.00:EUR", + "entity": "iban", + "value": "DE02120300000000202051", "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, + "top": 2795.15, + "left": 429.15, + "width": 464, + "height": 28, "page": 1 } - } - ], - "ibans": [ + }, { "entity": "iban", "value": "DE02120300000000202051", "box": { - "top": 804.49, - "left": 302.17, - "width": 88.56, - "height": 7.919999999999959, + "top": 2795.15, + "left": 274.2, + "width": 618.95, + "height": 470.0499999999997, "page": 1 } }, @@ -118,12 +127,16 @@ "entity": "iban", "value": "DE02120300000000202051", "box": { - "top": 666.49, - "left": 93.65, - "width": 121.82999999999998, - "height": 11.039999999999964, + "top": 3362.55, + "left": 1285.55, + "width": 339, + "height": 25, "page": 1 } + }, + { + "entity": "iban", + "value": "DE02120300000000202051" } ] } diff --git a/capture-sdk/default-network/src/androidTest/assets/result_Gini_invoice_example.json b/capture-sdk/default-network/src/androidTest/assets/result_Gini_invoice_example.json index 78d3b8876d..74bd86f6cc 100644 --- a/capture-sdk/default-network/src/androidTest/assets/result_Gini_invoice_example.json +++ b/capture-sdk/default-network/src/androidTest/assets/result_Gini_invoice_example.json @@ -1,57 +1,107 @@ { "extractions": { + "docType": { + "entity": "doctype", + "value": "Invoice" + }, + "amountToPay": { + "entity": "amount", + "value": "995.00:EUR", + "candidates": "amounts" + }, "paymentRecipient": { "entity": "companyname", "value": "Fahrrad Rückenwind", + "box": { + "top": 446.55, + "left": 280.55, + "width": 432.99999999999994, + "height": 39.0, + "page": 1 + }, "candidates": "paymentRecipients" }, + "paymentState": { + "entity": "paymentstate", + "value": "ToBePaid" + }, + "instantPayment": { + "entity": "text", + "value": "false" + }, "paymentPurpose": { - "entity": "reference", + "entity": "text", "value": "RE-20210512-02" }, "bic": { "entity": "bic", "value": "BYLADEM1001", + "box": { + "top": 2690.5, + "left": 280.5, + "width": 350.0, + "height": 35.0, + "page": 1 + }, "candidates": "bics" }, - "amountToPay": { - "entity": "amount", - "value": "995.00:EUR", - "candidates": "amounts" - }, "iban": { "entity": "iban", "value": "DE02120300000000202051", + "box": { + "top": 2795.15, + "left": 429.15, + "width": 464.0, + "height": 28.0, + "page": 1 + }, "candidates": "ibans" - }, - "docType": { - "entity": "doctype", - "value": "Invoice" } }, "candidates": { - "paymentRecipients": [ + "amounts": [ { - "entity": "companyname", - "value": "Fahrrad Rückenwind", + "entity": "amount", + "value": "995.00:EUR" + } + ], + "ibans": [ + { + "entity": "iban", + "value": "DE02120300000000202051", "box": { - "top": 103.69, - "left": 66.25, - "width": 107.0, - "height": 12.0, + "top": 2795.15, + "left": 429.15, + "width": 464.0, + "height": 28.0, "page": 1 } }, { - "entity": "companyname", - "value": "Fahrrad Rückenwind", + "entity": "iban", + "value": "DE02120300000000202051", + "box": { + "top": 2795.15, + "left": 274.2, + "width": 618.95, + "height": 470.0499999999997, + "page": 1 + } + }, + { + "entity": "iban", + "value": "DE02120300000000202051", "box": { - "top": 617.53, - "left": 66.25, - "width": 192.95999999999998, - "height": 11.040000000000077, + "top": 3362.55, + "left": 1285.55, + "width": 339.0, + "height": 25.0, "page": 1 } + }, + { + "entity": "iban", + "value": "DE02120300000000202051" } ], "bics": [ @@ -59,10 +109,10 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 794.41, - "left": 282.25, - "width": 62.420000000000016, - "height": 7.920000000000073, + "top": 2690.5, + "left": 280.5, + "width": 350.0, + "height": 35.0, "page": 1 } }, @@ -70,58 +120,42 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 642.49, - "left": 66.25, - "width": 86.03, - "height": 11.039999999999964, + "top": 3320.55, + "left": 1177.55, + "width": 258.0, + "height": 25.0, "page": 1 } } ], - "amounts": [ + "paymentRecipients": [ { - "entity": "amount", - "value": "995.00:EUR", - "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, - "page": 1 - } + "entity": "companyname", + "value": "Fahrrad Rückenwind" }, { - "entity": "amount", - "value": "995.00:EUR", + "entity": "companyname", + "value": "Fahrrad Rückenwind", "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, + "top": 446.55, + "left": 280.55, + "width": 432.99999999999994, + "height": 39.0, "page": 1 } - } - ], - "ibans": [ + }, { - "entity": "iban", - "value": "DE02120300000000202051", - "box": { - "top": 804.49, - "left": 302.17, - "width": 88.56, - "height": 7.919999999999959, - "page": 1 - } + "entity": "companyname", + "value": "FAHRRADÜCKENWIND GMBH" }, { - "entity": "iban", - "value": "DE02120300000000202051", + "entity": "companyname", + "value": "Fahrrad Rückenwind", "box": { - "top": 666.49, - "left": 93.65, - "width": 121.82999999999998, - "height": 11.039999999999964, + "top": 2586.55, + "left": 281.55, + "width": 795.0, + "height": 39.0, "page": 1 } } diff --git a/capture-sdk/default-network/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json b/capture-sdk/default-network/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json index 1857e73949..af7317f118 100644 --- a/capture-sdk/default-network/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json +++ b/capture-sdk/default-network/src/androidTest/assets/result_Gini_invoice_example_after_feedback.json @@ -5,51 +5,67 @@ "value": "Fahrrad Rückenwind", "candidates": "paymentRecipients" }, - "paymentPurpose": { - "entity": "reference", - "value": "RE-20210512-02" - }, - "bic": { - "entity": "bic", - "value": "BYLADEM1001", - "candidates": "bics" + "iban": { + "entity": "iban", + "value": "DE02120300000000202051", + "candidates": "ibans" }, "amountToPay": { "entity": "amount", "value": "950.00:EUR", "candidates": "amounts" }, - "iban": { - "entity": "iban", - "value": "DE02120300000000202051", - "candidates": "ibans" + "paymentPurpose": { + "entity": "text", + "value": "RE-20210512-02" + }, + "instantPayment": { + "entity": "text", + "value": "false" }, "docType": { "entity": "doctype", "value": "Invoice" + }, + "paymentState": { + "entity": "paymentstate", + "value": "ToBePaid" + }, + "bic": { + "entity": "bic", + "value": "BYLADEM1001", + "candidates": "bics" } }, "candidates": { "paymentRecipients": [ + { + "entity": "companyname", + "value": "Fahrrad Rückenwind" + }, { "entity": "companyname", "value": "Fahrrad Rückenwind", "box": { - "top": 103.69, - "left": 66.25, - "width": 107.0, - "height": 12.0, + "top": 446.55, + "left": 280.55, + "width": 432.99999999999994, + "height": 39, "page": 1 } }, + { + "entity": "companyname", + "value": "FAHRRADÜCKENWIND GMBH" + }, { "entity": "companyname", "value": "Fahrrad Rückenwind", "box": { - "top": 617.53, - "left": 66.25, - "width": 192.95999999999998, - "height": 11.040000000000077, + "top": 2586.55, + "left": 281.55, + "width": 795, + "height": 39, "page": 1 } } @@ -59,10 +75,10 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 794.41, - "left": 282.25, - "width": 62.420000000000016, - "height": 7.920000000000073, + "top": 2690.5, + "left": 280.5, + "width": 350, + "height": 35, "page": 1 } }, @@ -70,10 +86,10 @@ "entity": "bic", "value": "BYLADEM1001", "box": { - "top": 642.49, - "left": 66.25, - "width": 86.03, - "height": 11.039999999999964, + "top": 3320.55, + "left": 1177.55, + "width": 258, + "height": 25, "page": 1 } } @@ -81,36 +97,29 @@ "amounts": [ { "entity": "amount", - "value": "995.00:EUR", - "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, - "page": 1 - } - }, + "value": "995.00:EUR" + } + ], + "ibans": [ { - "entity": "amount", - "value": "995.00:EUR", + "entity": "iban", + "value": "DE02120300000000202051", "box": { - "top": 464.89, - "left": 496.45, - "width": 29.439999999999998, - "height": 10.080000000000041, + "top": 2795.15, + "left": 429.15, + "width": 464, + "height": 28, "page": 1 } - } - ], - "ibans": [ + }, { "entity": "iban", "value": "DE02120300000000202051", "box": { - "top": 804.49, - "left": 302.17, - "width": 88.56, - "height": 7.919999999999959, + "top": 2795.15, + "left": 274.2, + "width": 618.95, + "height": 470.0499999999997, "page": 1 } }, @@ -118,12 +127,16 @@ "entity": "iban", "value": "DE02120300000000202051", "box": { - "top": 666.49, - "left": 93.65, - "width": 121.82999999999998, - "height": 11.039999999999964, + "top": 3362.55, + "left": 1285.55, + "width": 339, + "height": 25, "page": 1 } + }, + { + "entity": "iban", + "value": "DE02120300000000202051" } ] } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9acdc4e8ee..61f98343c7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,8 +45,8 @@ androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-view androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" } androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4" androidx-viewpager = "androidx.viewpager:viewpager:1.0.0" -androidx-viewpager2 = "androidx.viewpager2:viewpager2:1.0.0" -androidx-recyclerview = "androidx.recyclerview:recyclerview:1.3.2" +androidx-viewpager2 = "androidx.viewpager2:viewpager2:1.1.0" +androidx-recyclerview = "androidx.recyclerview:recyclerview:1.4.0" androidx-cardview = "androidx.cardview:cardview:1.0.0" androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" } androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" } diff --git a/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewFragment.kt b/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewFragment.kt index 39fcea31d8..06cb29a1b0 100644 --- a/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewFragment.kt +++ b/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewFragment.kt @@ -19,6 +19,7 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.doOnLayout +import androidx.core.view.doOnPreDraw import androidx.core.view.isGone import androidx.core.view.isInvisible import androidx.core.view.isVisible @@ -29,6 +30,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayoutMediator @@ -37,7 +39,6 @@ import dev.chrisbanes.insetter.windowInsetTypesOf import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.gini.android.health.sdk.GiniHealth -import net.gini.android.health.sdk.R as HealthR import net.gini.android.internal.payment.R as InternalPaymentR import net.gini.android.health.sdk.databinding.GhsFragmentReviewBinding import net.gini.android.health.sdk.integratedFlow.PaymentFlowConfiguration @@ -60,6 +61,7 @@ import net.gini.android.internal.payment.utils.extensions.onKeyboardAction import net.gini.android.internal.payment.utils.extensions.wrappedWithGiniPaymentThemeAndLocale import net.gini.android.internal.payment.utils.showKeyboard import org.jetbrains.annotations.VisibleForTesting +import kotlin.math.abs /** * Listener for [ReviewFragment] events. @@ -81,18 +83,6 @@ internal interface ReviewFragmentListener { fun onToTheBankButtonClicked(paymentProviderName: String, paymentDetails: PaymentDetails) } -/** - * Delay duration (in milliseconds) used to allow the view to settle down before requesting focus. - * - * A value of 200ms was chosen based on observed behaviour on Android 10 devices and below, where - * immediately requesting keyboard focus after view creation can result in the keyboard not - * appearing. - * This delay helps ensure that the keyboard is reliably shown when the field requests focus. - */ -private const val VIEW_SETTLE_DELAY_MS = 200L -private const val KEY_IME_WAS_VISIBLE = "ime_was_visible" -private const val KEY_FOCUSED_ID = "focused_view_id" -private const val KEYBOARD_VISIBILITY_RATIO = 0.25f /** * The [ReviewFragment] displays an invoice’s pages and payment information extractions. It also lets users pay the * invoice with the bank they selected in the [BankSelectionBottomSheet]. @@ -105,17 +95,24 @@ class ReviewFragment private constructor( constructor() : this(null) - private val viewModel: ReviewViewModel by viewModels{ + private val viewModel: ReviewViewModel by viewModels { viewModelFactory ?: object : ViewModelProvider.Factory {} } - private var imeVisibleNow: Boolean = false private var preRKeyboardTracker: ViewTreeObserver.OnGlobalLayoutListener? = null private var binding: GhsFragmentReviewBinding by autoCleared() private var documentPageAdapter: DocumentPageAdapter by autoCleared() private var isKeyboardShown = false + private var imeWasVisible = false private var errorSnackbar: Snackbar? = null + private var ranPostRotateFix = false + private var mediator: TabLayoutMediator? = null + private val pageCallback = object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + viewModel.reviewPagerPosition = position + } + } @VisibleForTesting internal val reviewViewListener = object : ReviewViewListener { @@ -169,14 +166,33 @@ class ReviewFragment private constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val documentPagerHeight = savedInstanceState?.getInt(PAGER_HEIGHT, -1) ?: -1 viewModel.userPreferences = UserPreferences(requireContext()) + + + imeWasVisible = savedInstanceState?.getBoolean(KEY_IME_WAS_VISIBLE) ?: false + ranPostRotateFix = false + + + // Fire once on the first portrait layout pass where IME is hidden. + if (imeWasVisible) { + ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets -> + val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) + if (!imeVisible && !ranPostRotateFix && resources.isLandscapeOrientation().not()) { + ranPostRotateFix = true + v.post { restorePagerAndImeAfterRotation() } + } + insets + } + } else if (resources.isLandscapeOrientation().not()) { + restorePagerAndImeAfterRotation() + } + with(binding) { ghsPaymentDetails.reviewComponent = viewModel.reviewComponent setStateListeners() setKeyboardAnimation() - removePagerConstraintAndSetPreviousHeightIfNeeded(documentPagerHeight) } + // Set info bar bottom margin programmatically to reuse radius dimension with negative sign binding.paymentDetailsInfoBar.updateLayoutParams { bottomMargin = @@ -189,15 +205,39 @@ class ReviewFragment private constructor( // handling keyboard in Version <= Q (Pie and below) after orientation change if (preQ()) { - startPreRKeyboardTracker(view) + startKeyboardTracker(view) restoreImeIfNeeded(view, savedInstanceState) } } + private fun restorePagerAndImeAfterRotation() { + binding.pager.doOnPreDraw { + applyPagerConstraintFromCurrentSize() + } + } + + private fun applyPagerConstraintFromCurrentSize() { + val h = when { + viewModel.pagerHeight > 0 -> viewModel.pagerHeight + binding.pager.height > 0 -> binding.pager.height + else -> ViewGroup.LayoutParams.WRAP_CONTENT + } + + viewModel.pagerHeight = h + ConstraintSet().apply { + clone(binding.constraintRoot) + constrainHeight(binding.pager.id, h) + clear(binding.pager.id, ConstraintSet.BOTTOM) // if that’s part of your logic + applyTo(binding.constraintRoot) + } + } + private fun restoreImeIfNeeded(root: View, savedInstanceState: Bundle?) { val focusedId = savedInstanceState?.getInt(KEY_FOCUSED_ID) ?: View.NO_ID val imeWasVisible = savedInstanceState?.getBoolean(KEY_IME_WAS_VISIBLE) ?: false - if (focusedId == View.NO_ID || !imeWasVisible) return + if (focusedId == View.NO_ID || !imeWasVisible || + binding.ghsPaymentDetails.reviewComponent?.getReviewViewStateInLandscapeMode() == ReviewViewStateLandscape.COLLAPSED + ) return root.post { val et = root.findViewById(focusedId) @@ -254,12 +294,19 @@ class ReviewFragment private constructor( documentPageAdapter.submitList(documentPagesResult.pagesList.also { pages -> indicator.isVisible = true indicator.importantForAccessibility = - if (pages.size > 1) View.IMPORTANT_FOR_ACCESSIBILITY_YES else View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + if (pages.size > 1) View.IMPORTANT_FOR_ACCESSIBILITY_YES + else View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS indicator.focusable = if (pages.size > 1) View.FOCUSABLE else View.NOT_FOCUSABLE pager.isUserInputEnabled = pages.size > 1 indicator.isEnabled = pages.size > 1 indicator.alpha = if (pages.size > 1) 1f else 0f - }) + }) { + // <- runs after the adapter has items + val pos = viewModel.reviewPagerPosition + .coerceIn(0, (documentPageAdapter.itemCount - 1).coerceAtLeast(0)) + if (pager.currentItem != pos) pager.setCurrentItem(pos, false) + } + } is DocumentPagesResult.Error -> { @@ -285,12 +332,24 @@ class ReviewFragment private constructor( private fun GhsFragmentReviewBinding.configureOrientation() { pager.isVisible = true pager.adapter = documentPageAdapter - val mediator = TabLayoutMediator(indicator, pager) { tab, _ -> - tab.view.isFocusable = documentPageAdapter.itemCount > 1 - tab.view.isClickable = true - } - mediator.attach() + + (pager.adapter as RecyclerView.Adapter<*>).stateRestorationPolicy = + RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY + + pager.registerOnPageChangeCallback(pageCallback) + + if (documentPageAdapter.itemCount > 0) { + val pos = viewModel.reviewPagerPosition + .coerceIn(0, documentPageAdapter.itemCount - 1) + if (pager.currentItem != pos) pager.setCurrentItem(pos, false) + } + + mediator = TabLayoutMediator(indicator, pager) { tab, _ -> + tab.view.isFocusable = documentPageAdapter.itemCount > 1 + tab.view.isClickable = true + }.also { it.attach() } } + private fun GhsFragmentReviewBinding.handleError(text: String, onRetry: () -> Unit) { if (viewModel.configuration.handleErrorsInternally) { showSnackbar(text, onRetry) @@ -298,7 +357,9 @@ class ReviewFragment private constructor( } private fun GhsFragmentReviewBinding.showSnackbar(text: String, onRetry: () -> Unit) { - val context = requireContext().wrappedWithGiniPaymentThemeAndLocale(viewModel.paymentComponent.getGiniPaymentLanguage(requireContext())) + val context = requireContext().wrappedWithGiniPaymentThemeAndLocale( + viewModel.paymentComponent.getGiniPaymentLanguage(requireContext()) + ) errorSnackbar?.dismiss() errorSnackbar = Snackbar.make(context, root, text, Snackbar.LENGTH_INDEFINITE).apply { if (context.getFontScale() < 1.5) { @@ -333,23 +394,6 @@ class ReviewFragment private constructor( } } - private fun GhsFragmentReviewBinding.removePagerConstraintAndSetPreviousHeightIfNeeded(savedHeight: Int) { - if (resources.isLandscapeOrientation()) return - root.post { - if (savedHeight == 0) return@post - ConstraintSet().apply { - clone(constraintRoot) - constrainHeight(HealthR.id.pager, pager.height) - clear(HealthR.id.pager, ConstraintSet.BOTTOM) - applyTo(constraintRoot) - } - if (savedHeight > 0) { - val pagerLayoutParams = binding.pager.layoutParams - pagerLayoutParams.height = savedHeight - binding.pager.layoutParams = pagerLayoutParams - } - } - } private fun GhsFragmentReviewBinding.setKeyboardAnimation() { ViewCompat.setWindowInsetsAnimationCallback( @@ -416,13 +460,15 @@ class ReviewFragment private constructor( }) } - private fun startPreRKeyboardTracker(root: View) { + private fun startKeyboardTracker(root: View) { val listener = ViewTreeObserver.OnGlobalLayoutListener { val r = android.graphics.Rect() root.getWindowVisibleDisplayFrame(r) val visible = r.height() val heightDiff = root.rootView.height - visible - imeVisibleNow = heightDiff > root.rootView.height * KEYBOARD_VISIBILITY_RATIO // keyboard threshold + imeWasVisible = + imeWasVisible || (heightDiff > root.rootView.height * KEYBOARD_VISIBILITY_RATIO) // keyboard threshold + } root.viewTreeObserver.addOnGlobalLayoutListener(listener) preRKeyboardTracker = listener @@ -493,6 +539,19 @@ class ReviewFragment private constructor( binding.root.post { setupConstraintsForTabLayout((dragHandle?.height ?: 0) + bottomLayout.height) } + binding.root.doOnPreDraw { + ConstraintSet().apply { + clone(binding.constraintRoot) + constrainHeight( + binding.pager.id, + abs( + binding.pager.measuredHeight - ((dragHandle?.height + ?: 0) + bottomLayout.height) + ) + ) + applyTo(binding.constraintRoot) + } + } dragHandle?.setOnClickListener { fieldsLayout.alpha = if (it.isVisible) 0f else 1f val currentState = @@ -527,27 +586,48 @@ class ReviewFragment private constructor( } override fun onSaveInstanceState(outState: Bundle) { - val height = view?.findViewById(HealthR.id.pager)?.layoutParams?.height ?: -1 - outState.putInt(PAGER_HEIGHT, height) + val visibleNow: Boolean = if (!preQ()) { + ViewCompat.getRootWindowInsets(requireView()) + ?.isVisible(WindowInsetsCompat.Type.ime()) ?: imeWasVisible + } else { + imeWasVisible // kept up to date by startKeyboardTracker() + } + outState.putBoolean(KEY_IME_WAS_VISIBLE, visibleNow) if (preQ()) { val focusedId = view?.findFocus()?.id ?: View.NO_ID outState.putInt(KEY_FOCUSED_ID, focusedId) - outState.putBoolean(KEY_IME_WAS_VISIBLE, imeVisibleNow) } super.onSaveInstanceState(outState) } + private fun preQ() = Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q override fun onDestroyView() { + mediator?.detach() + mediator = null + preRKeyboardTracker?.let { view?.viewTreeObserver?.removeOnGlobalLayoutListener(it) } preRKeyboardTracker = null super.onDestroyView() } + + internal companion object { - private const val PAGER_HEIGHT = "pager_height" + /** + * Delay duration (in milliseconds) used to allow the view to settle down before requesting focus. + * + * A value of 200ms was chosen based on observed behaviour on Android 10 devices and below, where + * immediately requesting keyboard focus after view creation can result in the keyboard not + * appearing. + * This delay helps ensure that the keyboard is reliably shown when the field requests focus. + */ + private const val VIEW_SETTLE_DELAY_MS = 200L + private const val KEY_IME_WAS_VISIBLE = "ime_was_visible" + private const val KEY_FOCUSED_ID = "focused_view_id" + private const val KEYBOARD_VISIBILITY_RATIO = 0.25f fun newInstance( giniHealth: GiniHealth, @@ -558,7 +638,14 @@ class ReviewFragment private constructor( paymentFlowConfiguration: PaymentFlowConfiguration ): ReviewFragment { // Store non-Parcelable dependencies in holder - val viewModelFactory: ViewModelProvider.Factory = ReviewViewModel.Factory(giniHealth, configuration, paymentComponent, documentId, paymentFlowConfiguration, listener) + val viewModelFactory: ViewModelProvider.Factory = ReviewViewModel.Factory( + giniHealth, + configuration, + paymentComponent, + documentId, + paymentFlowConfiguration, + listener + ) return ReviewFragment(viewModelFactory) } diff --git a/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewViewModel.kt b/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewViewModel.kt index 20ea300965..bea834eefb 100644 --- a/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewViewModel.kt +++ b/health-sdk/sdk/src/main/java/net/gini/android/health/sdk/review/ReviewViewModel.kt @@ -34,6 +34,9 @@ internal class ReviewViewModel( val reviewFragmentListener: ReviewFragmentListener ) : ViewModel() { + var reviewPagerPosition: Int = 0 + var pagerHeight = 0 + internal var userPreferences: UserPreferences? = null val reviewComponent: ReviewComponent = ReviewComponent( diff --git a/health-sdk/sdk/src/main/res/layout/ghs_fragment_review.xml b/health-sdk/sdk/src/main/res/layout/ghs_fragment_review.xml index 21d1c54db3..99fabf0cea 100644 --- a/health-sdk/sdk/src/main/res/layout/ghs_fragment_review.xml +++ b/health-sdk/sdk/src/main/res/layout/ghs_fragment_review.xml @@ -122,6 +122,7 @@ android:layout_height="wrap_content" android:nextFocusUp="@id/close" android:importantForAccessibility="no" + app:layout_constrainedHeight="true" android:focusable="false" android:nextFocusDown="@id/ghs_payment_details" android:nextFocusForward="@id/ghs_payment_details"