Merge branch 'main' into feature/redesign
# Conflicts: # CHANGELOG.md # app/src/main/java/co/electriccoin/zcash/app/ZcashApplication.kt # settings.gradle.kts # ui-lib/build.gradle.kts # ui-lib/src/main/java/co/electriccoin/zcash/di/UseCaseModule.kt # ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/HistoryView.kt # ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/settings/viewmodel/SettingsViewModel.kt
|
@ -11,9 +11,6 @@
|
|||
# FIREBASE_DEBUG_JSON_BASE64 - Optional JSON to enable Firebase (e.g. Crashlytics) for debug builds
|
||||
# FIREBASE_RELEASE_JSON_BASE64 - Optional JSON to enable Firebase (e.g. Crashlytics) for release builds
|
||||
|
||||
# Expected variables
|
||||
# SUPPORT_EMAIL_ADDRESS - Contact email address for sending requests from the app
|
||||
|
||||
name: Deploy
|
||||
|
||||
on:
|
||||
|
@ -142,7 +139,6 @@ jobs:
|
|||
- name: Upload to Play Store
|
||||
timeout-minutes: 25
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_ZCASH_SUPPORT_EMAIL_ADDRESS: ${{ vars.SUPPORT_EMAIL_ADDRESS }}
|
||||
# TODO [#1033]: Use token-based authorization on Google Play for automated deployment
|
||||
# TODO [#1033]: https://github.com/Electric-Coin-Company/zashi-android/issues/1033
|
||||
# Note that these properties are not currently used due to #1033
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
# Expected variables
|
||||
# FIREBASE_TEST_LAB_PROJECT - Firebase Test Lab project name
|
||||
# SUPPORT_EMAIL_ADDRESS - Contact email address for sending requests from the app
|
||||
|
||||
name: Pull Request
|
||||
|
||||
|
@ -33,6 +32,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
# Gradle Wrapper validation can be flaky
|
||||
# https://github.com/gradle/wrapper-validation-action/issues/40
|
||||
- name: Gradle Wrapper Validation
|
||||
|
@ -83,6 +84,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
|
@ -106,6 +109,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
|
@ -146,6 +151,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
|
@ -186,6 +193,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
|
@ -201,7 +210,7 @@ jobs:
|
|||
# Disable minify, since it makes lint run faster
|
||||
ORG_GRADLE_PROJECT_IS_MINIFY_ENABLED: false
|
||||
run: |
|
||||
./gradlew :app:lintZcashmainnetRelease
|
||||
./gradlew :app:lintZcashmainnetStoreRelease
|
||||
- name: Collect Artifacts
|
||||
if: ${{ always() }}
|
||||
timeout-minutes: 1
|
||||
|
@ -229,6 +238,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
|
@ -273,6 +284,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
|
@ -285,7 +298,7 @@ jobs:
|
|||
- name: Build
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
./gradlew assembleDebug assembleAndroidTest assembleZcashmainnetDebug assembleZcashtestnetDebug
|
||||
./gradlew assembleZcashmainnetStoreDebug assembleZcashtestnetStoreDebug assembleAndroidTest
|
||||
- name: Authenticate to Google Cloud for Firebase Test Lab
|
||||
id: auth_test_lab
|
||||
uses: google-github-actions/auth@71f986410dfbc7added4569d411d040a91dc6935
|
||||
|
@ -298,7 +311,6 @@ jobs:
|
|||
- name: Test
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_ZCASH_SUPPORT_EMAIL_ADDRESS: ${{ vars.SUPPORT_EMAIL_ADDRESS }}
|
||||
# Force blank suffix for screenshot tests
|
||||
ORG_GRADLE_PROJECT_ZCASH_DEBUG_APP_NAME_SUFFIX: ""
|
||||
# Used by Flank, since the temporary token is missing the project name
|
||||
|
@ -338,6 +350,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
|
@ -350,7 +364,6 @@ jobs:
|
|||
- name: Build and test
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_ZCASH_SUPPORT_EMAIL_ADDRESS: ${{ vars.SUPPORT_EMAIL_ADDRESS }}
|
||||
# Force blank suffix for screenshot tests
|
||||
ORG_GRADLE_PROJECT_ZCASH_DEBUG_APP_NAME_SUFFIX: ""
|
||||
ORG_GRADLE_PROJECT_ZCASH_EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }}
|
||||
|
@ -358,7 +371,7 @@ jobs:
|
|||
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew testDebugWithEmulatorWtf :ui-integration-test:testZcashmainnetDebugWithEmulatorWtf
|
||||
./gradlew testDebugWithEmulatorWtf :ui-integration-test:testZcashmainnetStoreDebugWithEmulatorWtf
|
||||
- name: Collect Artifacts
|
||||
if: ${{ always() }}
|
||||
timeout-minutes: 1
|
||||
|
@ -387,6 +400,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
|
@ -399,7 +414,6 @@ jobs:
|
|||
- name: Build and test
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_ZCASH_SUPPORT_EMAIL_ADDRESS: ${{ vars.SUPPORT_EMAIL_ADDRESS }}
|
||||
# Force blank suffix for screenshot tests
|
||||
ORG_GRADLE_PROJECT_ZCASH_DEBUG_APP_NAME_SUFFIX: ""
|
||||
ORG_GRADLE_PROJECT_ZCASH_EMULATOR_WTF_API_KEY: ${{ secrets.EMULATOR_WTF_API_KEY }}
|
||||
|
@ -407,7 +421,7 @@ jobs:
|
|||
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew :app:testZcashmainnetDebugWithEmulatorWtf :ui-screenshot-test:testZcashmainnetDebugWithEmulatorWtf
|
||||
./gradlew :app:testZcashmainnetStoreDebugWithEmulatorWtf :ui-screenshot-test:testZcashmainnetStoreDebugWithEmulatorWtf
|
||||
- name: Collect Artifacts
|
||||
if: ${{ always() }}
|
||||
timeout-minutes: 1
|
||||
|
@ -439,6 +453,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
|
@ -462,12 +478,11 @@ jobs:
|
|||
- name: Build
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_ZCASH_SUPPORT_EMAIL_ADDRESS: ${{ vars.SUPPORT_EMAIL_ADDRESS }}
|
||||
ORG_GRADLE_PROJECT_IS_CRASH_ON_STRICT_MODE_VIOLATION: true
|
||||
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew :app:assembleDebug
|
||||
./gradlew :app:assembleZcashmainnetStoreDebug :app:assembleZcashtestnetStoreDebug
|
||||
- name: Authenticate to Google Cloud for Firebase Test Lab
|
||||
id: auth_test_lab
|
||||
uses: google-github-actions/auth@71f986410dfbc7added4569d411d040a91dc6935
|
||||
|
@ -497,6 +512,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
|
@ -531,7 +548,6 @@ jobs:
|
|||
- name: Build
|
||||
timeout-minutes: 25
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_ZCASH_SUPPORT_EMAIL_ADDRESS: ${{ vars.SUPPORT_EMAIL_ADDRESS }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PATH: ${{ format('{0}/release.jks', env.home) }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PASSWORD: android
|
||||
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: androiddebugkey
|
||||
|
@ -539,7 +555,7 @@ jobs:
|
|||
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew :app:assembleDebug :app:bundleRelease :app:packageZcashmainnetReleaseUniversalApk
|
||||
./gradlew :app:assembleZcashmainnetStoreDebug :app:assembleZcashtestnetStoreDebug :app:bundleZcashmainnetStoreRelease :app:bundleZcashtestnetStoreRelease :app:packageZcashmainnetStoreReleaseUniversalApk
|
||||
- name: Collect Artifacts
|
||||
timeout-minutes: 1
|
||||
env:
|
||||
|
@ -549,8 +565,8 @@ jobs:
|
|||
COMPOSE_METRICS_ZIP_PATH: ${{ format('{0}/artifacts/compose_metrics.zip', env.home) }}
|
||||
run: |
|
||||
mkdir ${ARTIFACTS_DIR_PATH}
|
||||
zip -r ${BINARIES_ZIP_PATH} . -i app/build/outputs/apk/\*/\*.apk app/build/outputs/apk_from_bundle/\*/\*.apk app/build/outputs/bundle/\*/\*.aab
|
||||
zip -r ${MAPPINGS_ZIP_PATH} . -i *app/build/outputs/mapping/*/mapping.txt
|
||||
zip -r ${BINARIES_ZIP_PATH} . -i app/build/outputs/apk/\*/\*/\*.apk app/build/outputs/apk_from_bundle/\*/\*.apk app/build/outputs/bundle/\*/\*.aab
|
||||
zip -r ${MAPPINGS_ZIP_PATH} . -i *app/build/outputs/mapping/\*/\*/mapping.txt
|
||||
zip -r ${COMPOSE_METRICS_ZIP_PATH} . -i \*/build/compose-metrics/\* \*/build/compose-reports/\*
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08
|
||||
|
@ -572,6 +588,8 @@ jobs:
|
|||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published # Runs only when a release is published
|
||||
|
||||
permissions:
|
||||
contents: write # Grant write permissions to GITHUB_TOKEN
|
||||
|
||||
jobs:
|
||||
validate_gradle_wrapper:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
timeout-minutes: 1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
# Gradle Wrapper validation can be flaky
|
||||
# https://github.com/gradle/wrapper-validation-action/issues/40
|
||||
- name: Gradle Wrapper Validation
|
||||
timeout-minutes: 1
|
||||
uses: gradle/wrapper-validation-action@f9c9c575b8b21b6485636a91ffecd10e558c62f6
|
||||
|
||||
check_secrets:
|
||||
environment: deployment
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
has-secrets: ${{ steps.check_secrets.outputs.defined }}
|
||||
steps:
|
||||
- id: check_secrets
|
||||
env:
|
||||
COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
|
||||
if: "${{
|
||||
env.COINBASE_APP_ID != ''
|
||||
}}"
|
||||
run: echo "defined=true" >> $GITHUB_OUTPUT
|
||||
|
||||
release:
|
||||
environment: deployment
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # to fetch all commits
|
||||
- name: Set up Google Cloud SDK
|
||||
uses: google-github-actions/auth@v1
|
||||
with:
|
||||
credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
|
||||
|
||||
- name: Configure gsutil
|
||||
run: gcloud auth activate-service-account --key-file <(echo '${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}')
|
||||
|
||||
- name: Download file from GCS
|
||||
run: gsutil -q cp gs://${{ secrets.GCP_PROJECT_ID_PROD }}-apt-packages/encrypted_gpg.kms encrypted_gpg.kms
|
||||
|
||||
- name: Decrypt file using KMS
|
||||
run: |
|
||||
gcloud kms decrypt \
|
||||
--key gpg \
|
||||
--keyring gpg \
|
||||
--location global \
|
||||
--plaintext-file private.pgp \
|
||||
--ciphertext-file encrypted_gpg.kms
|
||||
|
||||
- name: Import GPG
|
||||
run: |
|
||||
gpg --import private.pgp
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12
|
||||
timeout-minutes: 1
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
- name: Set up Gradle
|
||||
uses: gradle/gradle-build-action@ac2d340dc04d9e1113182899e983b5400c17cda1
|
||||
timeout-minutes: 10
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
- name: Export Google Services JSON
|
||||
env:
|
||||
FIREBASE_DEBUG_JSON_BASE64: ${{ secrets.FIREBASE_DEBUG_JSON_BASE64 }}
|
||||
FIREBASE_RELEASE_JSON_BASE64: ${{ secrets.FIREBASE_RELEASE_JSON_BASE64 }}
|
||||
if: "${{ env.FIREBASE_DEBUG_JSON_BASE64 != '' && env.FIREBASE_RELEASE_JSON_BASE64 != '' }}"
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p app/src/debug/
|
||||
mkdir -p app/src/release/
|
||||
echo ${FIREBASE_DEBUG_JSON_BASE64} | base64 --decode > app/src/debug/google-services.json
|
||||
echo ${FIREBASE_RELEASE_JSON_BASE64} | base64 --decode > app/src/release/google-services.json
|
||||
- name: Set Env
|
||||
shell: bash
|
||||
run: |
|
||||
echo "home=${HOME}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Export Signing Key
|
||||
env:
|
||||
# The upload key must be exported using `base64 -w 0 <filename.jks>` for use
|
||||
# as a Github Secrets value; if the key is exported with standard wrapping,
|
||||
# it will fail to import correctly.
|
||||
# NOTE: This is the upload signing key, which may be replaced at will, not
|
||||
# the application signing key which is escrowed by Google and may only be
|
||||
# replaced once a year (and has a bunch of additional hassles associated with
|
||||
# replacing it.)
|
||||
SIGNING_KEYSTORE_BASE_64: ${{ secrets.UPLOAD_KEYSTORE_BASE_64 }}
|
||||
SIGNING_KEY_PATH: ${{ format('{0}/release.jks', env.home) }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo ${SIGNING_KEYSTORE_BASE_64} | base64 --decode > ${SIGNING_KEY_PATH}
|
||||
|
||||
- name: Build Store APK
|
||||
timeout-minutes: 25
|
||||
env:
|
||||
# TODO [#1033]: Use token-based authorization on Google Play for automated deployment
|
||||
# TODO [#1033]: https://github.com/Electric-Coin-Company/zashi-android/issues/1033
|
||||
# Note that these properties are not currently used due to #1033
|
||||
# ORG_GRADLE_PROJECT_ZCASH_GOOGLE_PLAY_SERVICE_ACCOUNT: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }}
|
||||
# ORG_GRADLE_PROJECT_ZCASH_GOOGLE_PLAY_SERVICE_KEY_FILE_PATH: ${{ steps.auth_google_play.outputs.credentials_file_path }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_GOOGLE_PLAY_SERVICE_ACCOUNT_KEY: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_KEY }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_GOOGLE_PLAY_PUBLISHER_API_KEY: ${{ secrets.GOOGLE_PLAY_PUBLISHER_API_KEY }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_GOOGLE_PLAY_DEPLOY_TRACK: internal
|
||||
ORG_GRADLE_PROJECT_ZCASH_GOOGLE_PLAY_DEPLOY_STATUS: completed
|
||||
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PATH: ${{ format('{0}/release.jks', env.home) }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: ${{ secrets.UPLOAD_KEY_ALIAS }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: ${{ secrets.UPLOAD_KEY_ALIAS_PASSWORD }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew :app:bundleZcashmainnetStoreRelease :app:packageZcashmainnetStoreReleaseUniversalApk
|
||||
|
||||
- name: Prepare Store Artifacts
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
mkdir artifacts/
|
||||
mv app/build/outputs/apk_from_bundle/*/* artifacts/
|
||||
|
||||
- name: Strip non-Foss libraries
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
rm app/src/debug/google-services.json
|
||||
rm app/src/release/google-services.json
|
||||
rm -rf build-conventions-secant/build/
|
||||
rm -rf build-conventions-secant/.gradle/
|
||||
rm -rf buildSrc/build/
|
||||
rm -rf buildSrc/.gradle/
|
||||
rm -rf ui-screenshot-test
|
||||
rm -rf .gradle
|
||||
rm -rf build-conventions-secant/src/main/kotlin/secant.emulator-wtf-conventions.gradle.kts
|
||||
sed -i '/\/\/ start wtf maven/,/\/\/ end wtf maven/d' settings.gradle.kts
|
||||
find . -type f -name "build.gradle.kts" -exec sed -i -e '/wtf.emulator.gradle/d' {} +
|
||||
find . -type f -name "build.gradle.kts" -exec sed -i -e '/secant.emulator-wtf-conventions/d' {} +
|
||||
find . -type f -name "gradle.lockfile" -exec sed -i -e '/wtf.emulator/d' {} +
|
||||
find . -type f -name "buildscript-gradle.lockfile" -exec sed -i -e '/wtf.emulator/d' {} +
|
||||
find . -type f -name "gradle.lockfile" -exec sed -i -e '/com.vdurmont/d' {} +
|
||||
find . -type f -name "buildscript-gradle.lockfile" -exec sed -i -e '/com.vdurmont/d' {} +
|
||||
find . -type f -name "gradle.lockfile" -exec sed -i -e '/org.json:json/d' {} +
|
||||
find . -type f -name "buildscript-gradle.lockfile" -exec sed -i -e '/org.json:json/d' {} +
|
||||
sed -i -e '/include("ui-screenshot-test")/d' settings.gradle.kts
|
||||
sed -i -e '/com.google.gms/d' -e '/com.google.android.gms/d' -e '/com.google.firebase/d' -e '/crashlyticsVersion/d' build.gradle.kts
|
||||
sed -i -e '/libs.google.services/d' -e '/libs.firebase/d' build.gradle.kts
|
||||
sed -i -e '/com.google.gms/d' -e '/com.google.android.gms/d' -e '/com.google.firebase/d' buildscript-gradle.lockfile
|
||||
sed -i -e '/libs.google.services/d' -e '/libs.firebase/d' buildscript-gradle.lockfile
|
||||
sed -i -e '/com.google.gms.google-services/d' -e '/com.google.firebase.crashlytics/d' */build.gradle.kts
|
||||
./gradlew clean
|
||||
|
||||
- name: Build Foss APK
|
||||
timeout-minutes: 25
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PATH: ${{ format('{0}/release.jks', env.home) }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS: ${{ secrets.UPLOAD_KEY_ALIAS }}
|
||||
ORG_GRADLE_PROJECT_ZCASH_RELEASE_KEY_ALIAS_PASSWORD: ${{ secrets.UPLOAD_KEY_ALIAS_PASSWORD }}
|
||||
# TODO [#1789] Re-enable Coinbase and Flexa integrations for FOSS variant
|
||||
# ORG_GRADLE_PROJECT_ZCASH_COINBASE_APP_ID: ${{ secrets.COINBASE_APP_ID }}
|
||||
# ORG_GRADLE_PROJECT_ZCASH_FLEXA_KEY: ${{ secrets.FLEXA_PUBLISHABLE_KEY }}
|
||||
run: |
|
||||
./gradlew :app:assembleZcashmainnetFossRelease
|
||||
|
||||
- name: Prepare Foss artifacts
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
mv app/build/outputs/apk/zcashmainnetFoss/release/app-zcashmainnet-foss-release.apk artifacts/
|
||||
|
||||
- name: Prepare Signature artifacts
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
cd artifacts/
|
||||
TAG=$(git describe --tags --abbrev=0)
|
||||
VERSION_NAME=$(echo "$TAG" | cut -d'-' -f1)
|
||||
VERSION_CODE=$(echo "$TAG" | cut -d'-' -f2)
|
||||
echo $VERSION_NAME > version_code.txt
|
||||
echo $VERSION_CODE > version_code.txt
|
||||
gpg -u sysadmin@z.cash --armor --digest-algo SHA256 --detach-sign *foss*.apk
|
||||
gpg -u sysadmin@z.cash --armor --digest-algo SHA256 --detach-sign *store*.apk
|
||||
|
||||
- name: Upload to Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: artifacts/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -7,10 +7,14 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
|
|||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- The new `Foss` build dimension has been added that suits for Zashi build that follows FOSS principles
|
||||
- The `release.yaml` has been added. It provides us with ability to build and deploy Zashi to GitHub Releases and
|
||||
F-Droid store.
|
||||
- Confirm the rejection of a Keystone transaction dialog added.
|
||||
|
||||
### Changed
|
||||
- `Flexa` version has been bumped to 1.0.11
|
||||
- Several non-FOSS dependencies has been removed for the new FOSS Zashi build type
|
||||
- Keystone flows swapped the buttons for the better UX, the main CTA is the closes button for a thumb.
|
||||
|
||||
## [1.3.3 (839)] - 2025-01-23
|
||||
|
|
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021-2023 Zcash
|
||||
Copyright (c) 2021-2025 Zcash
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -48,8 +48,8 @@ the project, these steps are not necessary.)
|
|||
1. See `ZCASH_RELEASE_APP_NAME`
|
||||
1. Change the package name under [app/build.gradle.kts](app/build.gradle.kts)
|
||||
1. See `ZCASH_RELEASE_PACKAGE_NAME`
|
||||
1. Change the support email address under [gradle.properties](gradle.properties)
|
||||
1. See `ZCASH_SUPPORT_EMAIL_ADDRESS`
|
||||
1. Change the support email address under [strings.xml](ui-lib/src/main/res/ui/non_translatable/values/strings.xml)
|
||||
1. See `support_email_address`
|
||||
1. Remove any copyrighted ZCash or Electric Coin Company icons, logos, or assets
|
||||
1. ui-lib/src/main/res/common/ - All of the the ic_launcher assets
|
||||
1. Optional
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import co.electriccoin.zcash.Git
|
||||
import com.android.build.api.variant.BuildConfigField
|
||||
import com.android.build.api.variant.ResValue
|
||||
import model.BuildType
|
||||
import model.DistributionDimension
|
||||
import model.NetworkDimension
|
||||
import java.util.Locale
|
||||
|
||||
plugins {
|
||||
|
@ -65,21 +68,34 @@ android {
|
|||
buildConfig = true
|
||||
}
|
||||
|
||||
flavorDimensions.add("network")
|
||||
flavorDimensions += listOf(NetworkDimension.DIMENSION_NAME, DistributionDimension.DIMENSION_NAME)
|
||||
|
||||
val testNetFlavorName = "zcashtestnet"
|
||||
productFlavors {
|
||||
// would rather name them "testnet" and "mainnet" but product flavor names cannot start with the word "test"
|
||||
create(testNetFlavorName) {
|
||||
dimension = "network"
|
||||
applicationId = "$packageName.testnet" // allow to be installed alongside mainnet
|
||||
matchingFallbacks.addAll(listOf("zcashtestnet", "debug"))
|
||||
create(NetworkDimension.TESTNET.value) {
|
||||
dimension = NetworkDimension.DIMENSION_NAME
|
||||
applicationId = packageName
|
||||
applicationIdSuffix = ".testnet"
|
||||
matchingFallbacks.addAll(listOf(NetworkDimension.TESTNET.value, BuildType.DEBUG.value))
|
||||
}
|
||||
|
||||
create("zcashmainnet") {
|
||||
dimension = "network"
|
||||
create(NetworkDimension.MAINNET.value) {
|
||||
dimension = NetworkDimension.DIMENSION_NAME
|
||||
applicationId = packageName
|
||||
matchingFallbacks.addAll(listOf("zcashmainnet", "release"))
|
||||
matchingFallbacks.addAll(listOf(NetworkDimension.MAINNET.value, BuildType.RELEASE.value))
|
||||
}
|
||||
|
||||
create(DistributionDimension.STORE.value) {
|
||||
dimension = DistributionDimension.DIMENSION_NAME
|
||||
applicationId = packageName
|
||||
matchingFallbacks.addAll(listOf(DistributionDimension.STORE.value, BuildType.RELEASE.value))
|
||||
}
|
||||
|
||||
create(DistributionDimension.FOSS.value) {
|
||||
dimension = DistributionDimension.DIMENSION_NAME
|
||||
applicationId = packageName
|
||||
matchingFallbacks.addAll(listOf(DistributionDimension.FOSS.value, BuildType.RELEASE.value))
|
||||
versionNameSuffix = "-foss"
|
||||
applicationIdSuffix = ".foss"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +114,7 @@ android {
|
|||
signingConfigs {
|
||||
if (isReleaseSigningConfigured) {
|
||||
// If this block doesn't execute, the output will be unsigned
|
||||
create("release").apply {
|
||||
create(BuildType.RELEASE.value).apply {
|
||||
storeFile = File(releaseKeystorePath)
|
||||
storePassword = releaseKeystorePassword
|
||||
keyAlias = releaseKeyAlias
|
||||
|
@ -108,16 +124,16 @@ android {
|
|||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("debug").apply {
|
||||
getByName(BuildType.DEBUG.value).apply {
|
||||
// Note that the build-conventions defines the res configs
|
||||
isPseudoLocalesEnabled = true
|
||||
|
||||
// Suffixing app package name and version to avoid collisions with other installed Zcash
|
||||
// Suffixing app package name and version to avoid collisions with other installed Zashi
|
||||
// apps (e.g. from Google Play)
|
||||
versionNameSuffix = "-debug"
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
getByName("release").apply {
|
||||
getByName(BuildType.RELEASE.value).apply {
|
||||
isMinifyEnabled = project.property("IS_MINIFY_ENABLED").toString().toBoolean()
|
||||
isShrinkResources = project.property("IS_MINIFY_ENABLED").toString().toBoolean()
|
||||
ndk.debugSymbolLevel = project.property("NDK_DEBUG_SYMBOL_LEVEL").toString()
|
||||
|
@ -134,10 +150,10 @@ android {
|
|||
val isSignReleaseBuildWithDebugKey = project.property("IS_SIGN_RELEASE_BUILD_WITH_DEBUG_KEY")
|
||||
.toString().toBoolean()
|
||||
if (isReleaseSigningConfigured) {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
signingConfig = signingConfigs.getByName(BuildType.RELEASE.value)
|
||||
} else if (isSignReleaseBuildWithDebugKey) {
|
||||
// Warning: in this case is the release build signed with the debug key
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
signingConfig = signingConfigs.getByName(BuildType.DEBUG.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,22 +162,44 @@ android {
|
|||
applicationVariants.all {
|
||||
val defaultAppName = project.property("ZCASH_RELEASE_APP_NAME").toString()
|
||||
val debugAppNameSuffix = project.property("ZCASH_DEBUG_APP_NAME_SUFFIX").toString()
|
||||
val supportEmailAddress = project.property("ZCASH_SUPPORT_EMAIL_ADDRESS").toString()
|
||||
val fossAppNameSuffix = project.property("ZCASH_FOSS_APP_NAME_SUFFIX").toString()
|
||||
when (this.name) {
|
||||
"zcashtestnetDebug" -> {
|
||||
resValue("string", "app_name", "$defaultAppName ($testnetNetworkName)$debugAppNameSuffix")
|
||||
"zcashtestnetStoreDebug" -> {
|
||||
resValue("string", "app_name", "$defaultAppName $debugAppNameSuffix $testnetNetworkName")
|
||||
}
|
||||
"zcashmainnetDebug" -> {
|
||||
resValue("string", "app_name", "$defaultAppName$debugAppNameSuffix")
|
||||
"zcashmainnetStoreDebug" -> {
|
||||
resValue("string", "app_name", "$defaultAppName $debugAppNameSuffix")
|
||||
}
|
||||
"zcashtestnetRelease" -> {
|
||||
resValue("string", "app_name", "$defaultAppName ($testnetNetworkName)")
|
||||
"zcashtestnetStoreRelease" -> {
|
||||
resValue("string", "app_name", "$defaultAppName $testnetNetworkName")
|
||||
}
|
||||
"zcashmainnetRelease" -> {
|
||||
"zcashmainnetStoreRelease" -> {
|
||||
resValue("string", "app_name", defaultAppName)
|
||||
}
|
||||
"zcashtestnetFossDebug" -> {
|
||||
resValue(
|
||||
"string",
|
||||
"app_name",
|
||||
"$defaultAppName $fossAppNameSuffix $debugAppNameSuffix $testnetNetworkName"
|
||||
)
|
||||
}
|
||||
"zcashmainnetFossDebug" -> {
|
||||
resValue("string", "app_name", "$defaultAppName $fossAppNameSuffix $debugAppNameSuffix")
|
||||
}
|
||||
"zcashtestnetFossRelease" -> {
|
||||
resValue("string", "app_name", "$defaultAppName $fossAppNameSuffix $testnetNetworkName")
|
||||
}
|
||||
"zcashmainnetFossRelease" -> {
|
||||
resValue("string", "app_name", defaultAppName)
|
||||
}
|
||||
}
|
||||
resValue("string", "support_email_address", supportEmailAddress)
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
// Disables dependency metadata when building APKs
|
||||
includeInApk = false
|
||||
// Disables dependency metadata when building Android App Bundles
|
||||
includeInBundle = false
|
||||
}
|
||||
|
||||
testCoverage {
|
||||
|
@ -222,30 +260,13 @@ androidComponents {
|
|||
ResValue(value = hasFirebaseApiKeys.toString())
|
||||
)
|
||||
|
||||
if (project.property("ZCASH_GOOGLE_PLAY_SERVICE_ACCOUNT_KEY").toString().isNotEmpty() &&
|
||||
project.property("ZCASH_GOOGLE_PLAY_PUBLISHER_API_KEY").toString().isNotEmpty()
|
||||
if ((project.property("ZCASH_GOOGLE_PLAY_SERVICE_ACCOUNT_KEY").toString().isNotEmpty() &&
|
||||
project.property("ZCASH_GOOGLE_PLAY_PUBLISHER_API_KEY").toString().isNotEmpty()) ||
|
||||
variant.productFlavors.any { it.second == DistributionDimension.FOSS.value }
|
||||
) {
|
||||
// Update the versionName to reflect bumps in versionCode
|
||||
|
||||
val versionCodeOffset = 0 // Change this to zero the final digit of the versionName
|
||||
|
||||
val processedVersionCode = output.versionCode.map { playVersionCode ->
|
||||
val defaultVersionName = project.property("ZCASH_VERSION_NAME").toString()
|
||||
// Version names will look like `myCustomVersionName.123`
|
||||
@Suppress("UNNECESSARY_SAFE_CALL")
|
||||
playVersionCode?.let {
|
||||
val delta = it - versionCodeOffset
|
||||
if (delta < 0) {
|
||||
defaultVersionName
|
||||
} else {
|
||||
"$defaultVersionName ($delta)"
|
||||
}
|
||||
} ?: defaultVersionName
|
||||
}
|
||||
|
||||
output.versionName.set(processedVersionCode)
|
||||
|
||||
val gitInfo = Git.newInfo(Git.MAIN, parent!!.projectDir)
|
||||
val defaultVersionName = project.property("ZCASH_VERSION_NAME").toString()
|
||||
output.versionName.set(defaultVersionName)
|
||||
val gitInfo = Git.newInfo(Git.MAIN, rootDir)
|
||||
output.versionCode.set(gitInfo.commitCount)
|
||||
}
|
||||
}
|
||||
|
@ -255,7 +276,7 @@ androidComponents {
|
|||
))
|
||||
|
||||
// The fixed Locale.US is intended
|
||||
if (variant.name.lowercase(Locale.US).contains("release")) {
|
||||
if (variant.name.lowercase(Locale.US).contains(BuildType.RELEASE.value)) {
|
||||
variant.packaging.resources.excludes.addAll(listOf(
|
||||
"**/*.kotlin_metadata",
|
||||
"DebugProbesKt.bin",
|
||||
|
@ -318,7 +339,7 @@ fladle {
|
|||
|
||||
debugApk.set(
|
||||
project.provider {
|
||||
"${buildDirectory}/outputs/apk/zcashmainnet/debug/app-zcashmainnet-debug.apk"
|
||||
"${buildDirectory}/outputs/apk/zcashmainnetStore/debug/app-zcashmainnet-store-debug.apk"
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -337,7 +358,7 @@ fladle {
|
|||
debugApk.set(
|
||||
project.provider {
|
||||
"$buildDirectory" +
|
||||
"/outputs/apk_from_bundle/zcashmainnetRelease/app-zcashmainnet-release-universal.apk"
|
||||
"/outputs/apk_from_bundle/zcashmainnetStoreRelease/app-zcashmainnet-store-release-universal.apk"
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ package co.electriccoin.zcash.app
|
|||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import co.electriccoin.zcash.crash.android.GlobalCrashReporter
|
||||
import co.electriccoin.zcash.crash.android.di.CrashReportersProvider
|
||||
import co.electriccoin.zcash.crash.android.di.crashProviderModule
|
||||
import co.electriccoin.zcash.di.addressBookModule
|
||||
import co.electriccoin.zcash.di.coreModule
|
||||
import co.electriccoin.zcash.di.dataSourceModule
|
||||
|
@ -28,6 +30,7 @@ class ZcashApplication : CoroutineApplication() {
|
|||
private val standardPreferenceProvider by inject<StandardPreferenceProvider>()
|
||||
private val flexaRepository by inject<FlexaRepository>()
|
||||
private val applicationStateProvider: ApplicationStateProvider by inject()
|
||||
private val getAvailableCrashReporters: CrashReportersProvider by inject()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
@ -42,6 +45,7 @@ class ZcashApplication : CoroutineApplication() {
|
|||
modules(
|
||||
coreModule,
|
||||
providerModule,
|
||||
crashProviderModule,
|
||||
dataSourceModule,
|
||||
repositoryModule,
|
||||
addressBookModule,
|
||||
|
@ -83,7 +87,7 @@ class ZcashApplication : CoroutineApplication() {
|
|||
}
|
||||
|
||||
private fun configureAnalytics() {
|
||||
if (GlobalCrashReporter.register(this)) {
|
||||
if (GlobalCrashReporter.register(this, getAvailableCrashReporters())) {
|
||||
applicationScope.launch {
|
||||
StandardPreferenceKeys.IS_ANALYTICS_ENABLED.observe(standardPreferenceProvider()).collect {
|
||||
if (it) {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:name="co.electriccoin.zcash.app.ZcashApplication"
|
||||
android:icon="@mipmap/ic_launcher_square"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name">
|
||||
|
||||
<provider
|
||||
android:name="co.electriccoin.zcash.global.ShareFileProvider"
|
||||
android:authorities="co.electriccoin.zcash.foss.debug.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"
|
||||
tools:replace="android:authorities" >
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/share_file_provider_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:name="co.electriccoin.zcash.app.ZcashApplication"
|
||||
android:icon="@mipmap/ic_launcher_square"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name">
|
||||
|
||||
<provider
|
||||
android:name="co.electriccoin.zcash.global.ShareFileProvider"
|
||||
android:authorities="co.electriccoin.zcash.foss.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"
|
||||
tools:replace="android:authorities" >
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/share_file_provider_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application
|
||||
android:name="co.electriccoin.zcash.app.ZcashApplication"
|
||||
android:icon="@mipmap/ic_launcher_square"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:label="@string/app_name">
|
||||
|
||||
<provider
|
||||
android:name="co.electriccoin.zcash.global.ShareFileProvider"
|
||||
android:authorities="co.electriccoin.zcash.foss.provider.testnet"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"
|
||||
tools:replace="android:authorities" >
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/share_file_provider_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,7 @@
|
|||
package model
|
||||
|
||||
enum class BuildType(val value: String) {
|
||||
DEBUG("debug"),
|
||||
RELEASE("release"),
|
||||
BENCHMARK("benchmark")
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package model
|
||||
|
||||
enum class NetworkDimension(val value: String) {
|
||||
MAINNET("zcashmainnet"),
|
||||
TESTNET("zcashtestnet");
|
||||
|
||||
companion object {
|
||||
const val DIMENSION_NAME = "network"
|
||||
}
|
||||
}
|
||||
|
||||
enum class DistributionDimension(val value: String) {
|
||||
STORE("store"),
|
||||
FOSS("foss");
|
||||
|
||||
companion object {
|
||||
const val DIMENSION_NAME = "distribution"
|
||||
}
|
||||
}
|
|
@ -182,7 +182,7 @@ abstract class PublishToGooglePlay @Inject constructor(
|
|||
val packageName = project.property("ZCASH_RELEASE_PACKAGE_NAME").toString()
|
||||
|
||||
// Walk through the build directory and find the prepared release aab file
|
||||
val apkFile = File("app/build/outputs/bundle/").walk()
|
||||
val apkFile = File("app/build/outputs/bundle/zcashmainnetStoreRelease/").walk()
|
||||
.filter { it.name.endsWith("release.aab") }
|
||||
.firstOrNull() ?: error("Universal release apk not found")
|
||||
|
||||
|
@ -408,11 +408,6 @@ tasks {
|
|||
PublishStatus.new(it)
|
||||
}
|
||||
|
||||
// The new task [publishToGooglePlay] runs [assembleDebug] and [bundleZcashmainnetRelease] as its
|
||||
// dependencies.
|
||||
|
||||
// Note that we need to convert these Enums to Strings as enums are not assignable via Kotlin DSL to Gradle
|
||||
// custom task, although it would be better to work with more type-safe Enums furthermore.
|
||||
register<PublishToGooglePlay>(
|
||||
"publishToGooglePlay", // $NON-NLS-1$
|
||||
googlePlayServiceAccountKey,
|
||||
|
@ -420,7 +415,7 @@ tasks {
|
|||
deployTrack.toGooglePlayIdentifier(),
|
||||
deployStatus.toGooglePlayIdentifier()
|
||||
)
|
||||
.dependsOn(":app:assembleDebug")
|
||||
.dependsOn(":app:bundleZcashmainnetRelease")
|
||||
.dependsOn(":app:packageZcashmainnetReleaseUniversalApk")
|
||||
.dependsOn(":app:assembleZcashmainnetStoreDebug")
|
||||
.dependsOn(":app:bundleZcashmainnetStoreRelease")
|
||||
.dependsOn(":app:packageZcashmainnetStoreReleaseUniversalApk")
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import com.android.build.api.dsl.CommonExtension
|
||||
import com.android.build.api.dsl.ManagedVirtualDevice
|
||||
import model.BuildType
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
|
||||
|
||||
pluginManager.withPlugin("com.android.application") {
|
||||
|
@ -97,7 +98,7 @@ fun com.android.build.gradle.BaseExtension.configureBaseExtension() {
|
|||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("debug").apply {
|
||||
getByName(BuildType.DEBUG.value).apply {
|
||||
val coverageEnabled =
|
||||
project.property("IS_ANDROID_INSTRUMENTATION_TEST_COVERAGE_ENABLED").toString().toBoolean()
|
||||
isTestCoverageEnabled = coverageEnabled
|
||||
|
@ -111,7 +112,7 @@ fun com.android.build.gradle.BaseExtension.configureBaseExtension() {
|
|||
val isExplicitDebugSigningEnabled = !debugKeystorePath.isNullOrBlank()
|
||||
if (isExplicitDebugSigningEnabled) {
|
||||
// If this block doesn't execute, the output will still be signed with the default keystore
|
||||
getByName("debug").apply {
|
||||
getByName(BuildType.DEBUG.value).apply {
|
||||
storeFile = File(debugKeystorePath)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ private val gitCommitCountKey = "gitCommitCount"
|
|||
private val releaseNotesEn = "releaseNotesEn"
|
||||
private val releaseNotesEs = "releaseNotesEs"
|
||||
|
||||
private val releaseNotesEnPath = "docs/whatsNew/WHATS_NEW_EN.md"
|
||||
private val releaseNotesEsPath = "docs/whatsNew/WHATS_NEW_ES.md"
|
||||
private val releaseNotesEnPath = "${project.rootDir}/docs/whatsNew/WHATS_NEW_EN.md"
|
||||
private val releaseNotesEsPath = "${project.rootDir}/docs/whatsNew/WHATS_NEW_ES.md"
|
||||
|
||||
|
||||
// Injects build information
|
||||
// Note timestamp is not currently injected because it effectively disables the cache since it
|
||||
|
@ -25,9 +26,9 @@ private val releaseNotesEsPath = "docs/whatsNew/WHATS_NEW_ES.md"
|
|||
val generateBuildConfigTask = tasks.create("buildConfig") {
|
||||
val generatedDir = layout.buildDirectory.dir("generated").get().asFile
|
||||
|
||||
val gitInfo = co.electriccoin.zcash.Git.newInfo(
|
||||
val gitInfo = Git.newInfo(
|
||||
Git.HEAD,
|
||||
parent!!.projectDir
|
||||
rootDir
|
||||
)
|
||||
|
||||
inputs.property(gitShaKey, gitInfo.sha)
|
||||
|
|
|
@ -62,9 +62,6 @@ plugins {
|
|||
id("secant.rosetta-conventions")
|
||||
}
|
||||
|
||||
val uiIntegrationModuleName: String = projects.uiIntegrationTest.name
|
||||
val uiScreenshotModuleName: String = projects.uiScreenshotTest.name
|
||||
|
||||
tasks {
|
||||
withType<com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask> {
|
||||
gradleReleaseChannel = "current"
|
||||
|
@ -101,7 +98,6 @@ tasks {
|
|||
|
||||
"ZCASH_RELEASE_APP_NAME" to "Zashi",
|
||||
"ZCASH_RELEASE_PACKAGE_NAME" to "co.electriccoin.zcash",
|
||||
"ZCASH_SUPPORT_EMAIL_ADDRESS" to "support@electriccoin.co",
|
||||
"IS_SECURE_SCREEN_PROTECTION_ACTIVE" to "true",
|
||||
"IS_SCREEN_ROTATION_ENABLED" to "false",
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ object Git {
|
|||
val git = Git.open(workingDirectory)
|
||||
val repository = git.repository
|
||||
|
||||
val head: ObjectId = repository.resolve(branch)
|
||||
val head = repository.resolve(branch)
|
||||
val count = git.log().call().count()
|
||||
|
||||
return GitInfo(ObjectId.toString(head), count)
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import model.DistributionDimension
|
||||
import model.NetworkDimension
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
|
@ -21,17 +24,39 @@ android {
|
|||
testOptions {
|
||||
execution = "ANDROIDX_TEST_ORCHESTRATOR"
|
||||
}
|
||||
|
||||
flavorDimensions += listOf(NetworkDimension.DIMENSION_NAME, DistributionDimension.DIMENSION_NAME)
|
||||
|
||||
productFlavors {
|
||||
create(NetworkDimension.TESTNET.value) {
|
||||
dimension = NetworkDimension.DIMENSION_NAME
|
||||
}
|
||||
|
||||
create(NetworkDimension.MAINNET.value) {
|
||||
dimension = NetworkDimension.DIMENSION_NAME
|
||||
}
|
||||
|
||||
create(DistributionDimension.STORE.value) {
|
||||
dimension = DistributionDimension.DIMENSION_NAME
|
||||
}
|
||||
|
||||
create(DistributionDimension.FOSS.value) {
|
||||
dimension = DistributionDimension.DIMENSION_NAME
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(libs.androidx.annotation)
|
||||
api(projects.crashLib)
|
||||
|
||||
implementation(platform(libs.firebase.bom))
|
||||
api(libs.bundles.koin)
|
||||
|
||||
"storeImplementation"(platform(libs.firebase.bom))
|
||||
"storeImplementation"(libs.firebase.crashlytics)
|
||||
"storeImplementation"(libs.firebase.crashlytics.ndk)
|
||||
"storeImplementation"(libs.firebase.installations)
|
||||
|
||||
implementation(libs.firebase.crashlytics)
|
||||
implementation(libs.firebase.crashlytics.ndk)
|
||||
implementation(libs.firebase.installations)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
implementation(libs.kotlinx.datetime)
|
||||
implementation(projects.spackleAndroidLib)
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package co.electriccoin.zcash.crash.android.internal
|
||||
|
||||
import android.content.Context
|
||||
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter
|
||||
|
||||
class ListCrashReportersImpl : ListCrashReporters {
|
||||
override fun provideReporters(context: Context): List<CrashReporter> {
|
||||
return listOfNotNull(
|
||||
LocalCrashReporter.getInstance(context),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,32 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application>
|
||||
|
||||
<!-- For improved user privacy, don't allow Firebase to collect advertising IDs -->
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
android:value="false" />
|
||||
|
||||
<!-- We want better control over the timing of Firebase initialization -->
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:authorities="${applicationId}.firebaseinitprovider"
|
||||
tools:node="remove" />
|
||||
|
||||
<provider
|
||||
android:name=".internal.local.CrashProcessNameContentProvider"
|
||||
android:authorities="${applicationId}.co.electriccoin.zcash.crash"
|
||||
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
|
||||
android:exported="false"
|
||||
android:process=":crash" />
|
||||
|
||||
<receiver
|
||||
android:name=".internal.local.ExceptionReceiver"
|
||||
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
|
||||
android:exported="false"
|
||||
android:process=":crash" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -4,8 +4,7 @@ import android.content.Context
|
|||
import androidx.annotation.AnyThread
|
||||
import androidx.annotation.MainThread
|
||||
import co.electriccoin.zcash.crash.android.internal.CrashReporter
|
||||
import co.electriccoin.zcash.crash.android.internal.firebase.FirebaseCrashReporter
|
||||
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter
|
||||
import co.electriccoin.zcash.crash.android.internal.ListCrashReporters
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.spackle.process.ProcessNameCompat
|
||||
import java.util.Collections
|
||||
|
@ -24,7 +23,10 @@ object GlobalCrashReporter {
|
|||
* @return True if registration occurred and false if registration was skipped.
|
||||
*/
|
||||
@MainThread
|
||||
fun register(context: Context): Boolean {
|
||||
fun register(
|
||||
context: Context,
|
||||
reporters: ListCrashReporters
|
||||
): Boolean {
|
||||
if (isCrashProcess(context)) {
|
||||
Twig.debug { "Skipping registration for $CRASH_PROCESS_NAME_SUFFIX process" } // $NON-NLS
|
||||
return false
|
||||
|
@ -34,15 +36,7 @@ object GlobalCrashReporter {
|
|||
if (registeredCrashReporters == null) {
|
||||
registeredCrashReporters =
|
||||
Collections.synchronizedList(
|
||||
// To prevent a race condition, register the LocalCrashReporter first.
|
||||
// FirebaseCrashReporter does some asynchronous registration internally, while
|
||||
// LocalCrashReporter uses AndroidUncaughtExceptionHandler which needs to read
|
||||
// and write the default UncaughtExceptionHandler. The only way to ensure
|
||||
// interleaving doesn't happen is to register the LocalCrashReporter first.
|
||||
listOfNotNull(
|
||||
LocalCrashReporter.getInstance(context),
|
||||
FirebaseCrashReporter(context),
|
||||
)
|
||||
reporters.provideReporters(context)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package co.electriccoin.zcash.crash.android.di
|
||||
|
||||
import co.electriccoin.zcash.crash.android.internal.ListCrashReportersImpl
|
||||
|
||||
class CrashReportersProvider {
|
||||
operator fun invoke() = ListCrashReportersImpl()
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package co.electriccoin.zcash.crash.android.di
|
||||
|
||||
import org.koin.core.module.dsl.factoryOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val crashProviderModule =
|
||||
module {
|
||||
factoryOf(::CrashReportersProvider)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package co.electriccoin.zcash.crash.android.internal
|
||||
|
||||
import android.content.Context
|
||||
|
||||
interface ListCrashReporters {
|
||||
fun provideReporters(context: Context): List<CrashReporter>
|
||||
}
|
|
@ -1,7 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="co_electriccoin_zcash_crash_is_use_secondary_process">true</bool>
|
||||
<!-- Expected to be overridden by a resource overlay in the app module, generated
|
||||
based on the presence of the Firebase API keys -->
|
||||
<bool name="co_electriccoin_zcash_crash_is_firebase_enabled">false</bool>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application>
|
||||
|
||||
<!-- For improved user privacy, don't allow Firebase to collect advertising IDs -->
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
android:value="false" />
|
||||
|
||||
<!-- We want better control over the timing of Firebase initialization -->
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:authorities="${applicationId}.firebaseinitprovider"
|
||||
tools:node="remove" />
|
||||
|
||||
<provider
|
||||
android:name=".internal.local.CrashProcessNameContentProvider"
|
||||
android:authorities="${applicationId}.co.electriccoin.zcash.crash"
|
||||
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
|
||||
android:exported="false"
|
||||
android:process=":crash" />
|
||||
|
||||
<receiver
|
||||
android:name=".internal.local.ExceptionReceiver"
|
||||
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
|
||||
android:exported="false"
|
||||
android:process=":crash" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,17 @@
|
|||
package co.electriccoin.zcash.crash.android.internal
|
||||
|
||||
import android.content.Context
|
||||
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter
|
||||
|
||||
class ListCrashReportersImpl : ListCrashReporters {
|
||||
override fun provideReporters(context: Context): List<CrashReporter> {
|
||||
// To prevent a race condition, register the LocalCrashReporter first.
|
||||
// FirebaseCrashReporter does some asynchronous registration internally, while
|
||||
// LocalCrashReporter uses AndroidUncaughtExceptionHandler which needs to read
|
||||
// and write the default UncaughtExceptionHandler. The only way to ensure
|
||||
// interleaving doesn't happen is to register the LocalCrashReporter first.
|
||||
return listOfNotNull(
|
||||
LocalCrashReporter.getInstance(context),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="co_electriccoin_zcash_crash_is_use_secondary_process">true</bool>
|
||||
<!-- Expected to be overridden by a resource overlay in the app module, generated
|
||||
based on the presence of the Firebase API keys -->
|
||||
<bool name="co_electriccoin_zcash_crash_is_firebase_enabled">false</bool>
|
||||
</resources>
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application>
|
||||
|
||||
<!-- For improved user privacy, don't allow Firebase to collect advertising IDs -->
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
android:value="false" />
|
||||
|
||||
<!-- We want better control over the timing of Firebase initialization -->
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:authorities="${applicationId}.firebaseinitprovider"
|
||||
tools:node="remove" />
|
||||
|
||||
<provider
|
||||
android:name=".internal.local.CrashProcessNameContentProvider"
|
||||
android:authorities="${applicationId}.co.electriccoin.zcash.crash"
|
||||
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
|
||||
android:exported="false"
|
||||
android:process=":crash" />
|
||||
|
||||
<receiver
|
||||
android:name=".internal.local.ExceptionReceiver"
|
||||
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
|
||||
android:exported="false"
|
||||
android:process=":crash" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,19 @@
|
|||
package co.electriccoin.zcash.crash.android.internal
|
||||
|
||||
import android.content.Context
|
||||
import co.electriccoin.zcash.crash.android.internal.firebase.FirebaseCrashReporter
|
||||
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter
|
||||
|
||||
class ListCrashReportersImpl : ListCrashReporters {
|
||||
override fun provideReporters(context: Context): List<CrashReporter> {
|
||||
// To prevent a race condition, register the LocalCrashReporter first.
|
||||
// FirebaseCrashReporter does some asynchronous registration internally, while
|
||||
// LocalCrashReporter uses AndroidUncaughtExceptionHandler which needs to read
|
||||
// and write the default UncaughtExceptionHandler. The only way to ensure
|
||||
// interleaving doesn't happen is to register the LocalCrashReporter first.
|
||||
return listOfNotNull(
|
||||
LocalCrashReporter.getInstance(context),
|
||||
FirebaseCrashReporter(context),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package co.electriccoin.zcash.crash.android.internal.firebase
|
||||
|
||||
import android.content.Context
|
||||
import com.google.firebase.FirebaseApp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object FirebaseAppCache {
|
||||
private val mutex = Mutex()
|
||||
|
||||
@Volatile
|
||||
private var cachedFirebaseApp: FirebaseAppContainer? = null
|
||||
|
||||
fun peekFirebaseApp(): FirebaseApp? = cachedFirebaseApp?.firebaseApp
|
||||
|
||||
suspend fun getFirebaseApp(context: Context): FirebaseApp? {
|
||||
mutex.withLock {
|
||||
peekFirebaseApp()?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val firebaseAppContainer = getFirebaseAppContainer(context)
|
||||
|
||||
cachedFirebaseApp = firebaseAppContainer
|
||||
}
|
||||
|
||||
return peekFirebaseApp()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getFirebaseAppContainer(context: Context): FirebaseAppContainer =
|
||||
withContext(Dispatchers.IO) {
|
||||
val firebaseApp = FirebaseApp.initializeApp(context)
|
||||
FirebaseAppContainer(firebaseApp)
|
||||
}
|
||||
|
||||
private class FirebaseAppContainer(val firebaseApp: FirebaseApp?)
|
|
@ -0,0 +1,135 @@
|
|||
@file:JvmName("FirebaseCrashReporterKt")
|
||||
|
||||
package co.electriccoin.zcash.crash.android.internal.firebase
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.AnyThread
|
||||
import co.electriccoin.zcash.crash.android.R
|
||||
import co.electriccoin.zcash.crash.android.internal.CrashReporter
|
||||
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
|
||||
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
|
||||
import co.electriccoin.zcash.spackle.SuspendingLazy
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.google.firebase.installations.FirebaseInstallations
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
/**
|
||||
* Registers an exception handler with Firebase Crashlytics.
|
||||
*/
|
||||
internal class FirebaseCrashReporter(
|
||||
context: Context
|
||||
) : CrashReporter {
|
||||
@OptIn(kotlinx.coroutines.DelicateCoroutinesApi::class)
|
||||
private val analyticsScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
private val initFirebaseJob: Deferred<CrashReporter?> =
|
||||
analyticsScope.async {
|
||||
FirebaseCrashReporterImpl.getInstance(context)
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
override fun reportCaughtException(exception: Throwable) {
|
||||
initFirebaseJob.invokeOnCompletionWithResult {
|
||||
it?.reportCaughtException(exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun enable() {
|
||||
initFirebaseJob.invokeOnCompletionWithResult {
|
||||
it?.enable()
|
||||
}
|
||||
}
|
||||
|
||||
override fun disableAndDelete() {
|
||||
initFirebaseJob.invokeOnCompletionWithResult {
|
||||
it?.disableAndDelete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun <T> Deferred<T>.invokeOnCompletionWithResult(handler: (T) -> Unit) {
|
||||
invokeOnCompletion {
|
||||
handler(this.getCompleted())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an exception handler with Firebase Crashlytics.
|
||||
*/
|
||||
private class FirebaseCrashReporterImpl(
|
||||
private val firebaseCrashlytics: FirebaseCrashlytics,
|
||||
private val firebaseInstallations: FirebaseInstallations
|
||||
) : CrashReporter {
|
||||
@AnyThread
|
||||
override fun reportCaughtException(exception: Throwable) {
|
||||
error(
|
||||
"Although most of the sensitive model objects implement custom [toString] methods to redact information" +
|
||||
" if they were to be logged (which includes exceptions), we're encouraged to disable caught exception" +
|
||||
" reporting to the remote Crashlytics service due to its security risk. Use the the local variant of" +
|
||||
" the reporter to report caught exception - [LocalCrashReporter]."
|
||||
)
|
||||
}
|
||||
|
||||
override fun enable() {
|
||||
firebaseCrashlytics.setCrashlyticsCollectionEnabled(true)
|
||||
}
|
||||
|
||||
override fun disableAndDelete() {
|
||||
firebaseCrashlytics.setCrashlyticsCollectionEnabled(false)
|
||||
firebaseCrashlytics.deleteUnsentReports()
|
||||
firebaseInstallations.delete()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/*
|
||||
* Note there is a tradeoff with the suspending implementation. In order to avoid disk IO
|
||||
* on the main thread, there is a brief timing gap during application startup where very
|
||||
* early crashes may be missed. This is a tradeoff we are willing to make in order to avoid
|
||||
* ANRs.
|
||||
*/
|
||||
private val lazyWithArgument =
|
||||
SuspendingLazy<Context, CrashReporter?> {
|
||||
if (it.resources.getBoolean(R.bool.co_electriccoin_zcash_crash_is_firebase_enabled)) {
|
||||
|
||||
// Workaround for disk IO on main thread in Firebase initialization
|
||||
val firebaseApp = FirebaseAppCache.getFirebaseApp(it)
|
||||
if (firebaseApp == null) {
|
||||
Twig.warn { "Unable to initialize Crashlytics. FirebaseApp is null" }
|
||||
return@SuspendingLazy null
|
||||
}
|
||||
|
||||
val firebaseInstallations = FirebaseInstallations.getInstance(firebaseApp)
|
||||
val firebaseCrashlytics =
|
||||
FirebaseCrashlytics.getInstance().apply {
|
||||
setCustomKey(
|
||||
CrashlyticsUserProperties.IS_TEST,
|
||||
EmulatorWtfUtil.isEmulatorWtf(it) || FirebaseTestLabUtil.isFirebaseTestLab(it)
|
||||
)
|
||||
}
|
||||
|
||||
FirebaseCrashReporterImpl(firebaseCrashlytics, firebaseInstallations)
|
||||
} else {
|
||||
Twig.warn { "Unable to initialize Crashlytics. Configure API keys in the app module" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getInstance(context: Context): CrashReporter? {
|
||||
return lazyWithArgument.getInstance(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal object CrashlyticsUserProperties {
|
||||
/**
|
||||
* Flags a crash as occurring in a test environment. Set automatically to detect Firebase Test Lab and emulator.wtf
|
||||
*/
|
||||
const val IS_TEST = "is_test" // $NON-NLS
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="co_electriccoin_zcash_crash_is_use_secondary_process">true</bool>
|
||||
<!-- Expected to be overridden by a resource overlay in the app module, generated
|
||||
based on the presence of the Firebase API keys -->
|
||||
<bool name="co_electriccoin_zcash_crash_is_firebase_enabled">false</bool>
|
||||
</resources>
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application>
|
||||
|
||||
<!-- For improved user privacy, don't allow Firebase to collect advertising IDs -->
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
android:value="false" />
|
||||
|
||||
<!-- We want better control over the timing of Firebase initialization -->
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:authorities="${applicationId}.firebaseinitprovider"
|
||||
tools:node="remove" />
|
||||
|
||||
<provider
|
||||
android:name=".internal.local.CrashProcessNameContentProvider"
|
||||
android:authorities="${applicationId}.co.electriccoin.zcash.crash"
|
||||
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
|
||||
android:exported="false"
|
||||
android:process=":crash" />
|
||||
|
||||
<receiver
|
||||
android:name=".internal.local.ExceptionReceiver"
|
||||
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
|
||||
android:exported="false"
|
||||
android:process=":crash" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,17 @@
|
|||
package co.electriccoin.zcash.crash.android.internal
|
||||
|
||||
import android.content.Context
|
||||
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter
|
||||
|
||||
class ListCrashReportersImpl : ListCrashReporters {
|
||||
override fun provideReporters(context: Context): List<CrashReporter> {
|
||||
// To prevent a race condition, register the LocalCrashReporter first.
|
||||
// FirebaseCrashReporter does some asynchronous registration internally, while
|
||||
// LocalCrashReporter uses AndroidUncaughtExceptionHandler which needs to read
|
||||
// and write the default UncaughtExceptionHandler. The only way to ensure
|
||||
// interleaving doesn't happen is to register the LocalCrashReporter first.
|
||||
return listOfNotNull(
|
||||
LocalCrashReporter.getInstance(context),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package co.electriccoin.zcash.crash.android.internal.firebase
|
||||
|
||||
import android.content.Context
|
||||
import com.google.firebase.FirebaseApp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object FirebaseAppCache {
|
||||
private val mutex = Mutex()
|
||||
|
||||
@Volatile
|
||||
private var cachedFirebaseApp: FirebaseAppContainer? = null
|
||||
|
||||
fun peekFirebaseApp(): FirebaseApp? = cachedFirebaseApp?.firebaseApp
|
||||
|
||||
suspend fun getFirebaseApp(context: Context): FirebaseApp? {
|
||||
mutex.withLock {
|
||||
peekFirebaseApp()?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val firebaseAppContainer = getFirebaseAppContainer(context)
|
||||
|
||||
cachedFirebaseApp = firebaseAppContainer
|
||||
}
|
||||
|
||||
return peekFirebaseApp()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getFirebaseAppContainer(context: Context): FirebaseAppContainer =
|
||||
withContext(Dispatchers.IO) {
|
||||
val firebaseApp = FirebaseApp.initializeApp(context)
|
||||
FirebaseAppContainer(firebaseApp)
|
||||
}
|
||||
|
||||
private class FirebaseAppContainer(val firebaseApp: FirebaseApp?)
|
|
@ -0,0 +1,135 @@
|
|||
@file:JvmName("FirebaseCrashReporterKt")
|
||||
|
||||
package co.electriccoin.zcash.crash.android.internal.firebase
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.AnyThread
|
||||
import co.electriccoin.zcash.crash.android.R
|
||||
import co.electriccoin.zcash.crash.android.internal.CrashReporter
|
||||
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
|
||||
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
|
||||
import co.electriccoin.zcash.spackle.SuspendingLazy
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.google.firebase.installations.FirebaseInstallations
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
/**
|
||||
* Registers an exception handler with Firebase Crashlytics.
|
||||
*/
|
||||
internal class FirebaseCrashReporter(
|
||||
context: Context
|
||||
) : CrashReporter {
|
||||
@OptIn(kotlinx.coroutines.DelicateCoroutinesApi::class)
|
||||
private val analyticsScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
private val initFirebaseJob: Deferred<CrashReporter?> =
|
||||
analyticsScope.async {
|
||||
FirebaseCrashReporterImpl.getInstance(context)
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
override fun reportCaughtException(exception: Throwable) {
|
||||
initFirebaseJob.invokeOnCompletionWithResult {
|
||||
it?.reportCaughtException(exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun enable() {
|
||||
initFirebaseJob.invokeOnCompletionWithResult {
|
||||
it?.enable()
|
||||
}
|
||||
}
|
||||
|
||||
override fun disableAndDelete() {
|
||||
initFirebaseJob.invokeOnCompletionWithResult {
|
||||
it?.disableAndDelete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun <T> Deferred<T>.invokeOnCompletionWithResult(handler: (T) -> Unit) {
|
||||
invokeOnCompletion {
|
||||
handler(this.getCompleted())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an exception handler with Firebase Crashlytics.
|
||||
*/
|
||||
private class FirebaseCrashReporterImpl(
|
||||
private val firebaseCrashlytics: FirebaseCrashlytics,
|
||||
private val firebaseInstallations: FirebaseInstallations
|
||||
) : CrashReporter {
|
||||
@AnyThread
|
||||
override fun reportCaughtException(exception: Throwable) {
|
||||
error(
|
||||
"Although most of the sensitive model objects implement custom [toString] methods to redact information" +
|
||||
" if they were to be logged (which includes exceptions), we're encouraged to disable caught exception" +
|
||||
" reporting to the remote Crashlytics service due to its security risk. Use the the local variant of" +
|
||||
" the reporter to report caught exception - [LocalCrashReporter]."
|
||||
)
|
||||
}
|
||||
|
||||
override fun enable() {
|
||||
firebaseCrashlytics.setCrashlyticsCollectionEnabled(true)
|
||||
}
|
||||
|
||||
override fun disableAndDelete() {
|
||||
firebaseCrashlytics.setCrashlyticsCollectionEnabled(false)
|
||||
firebaseCrashlytics.deleteUnsentReports()
|
||||
firebaseInstallations.delete()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/*
|
||||
* Note there is a tradeoff with the suspending implementation. In order to avoid disk IO
|
||||
* on the main thread, there is a brief timing gap during application startup where very
|
||||
* early crashes may be missed. This is a tradeoff we are willing to make in order to avoid
|
||||
* ANRs.
|
||||
*/
|
||||
private val lazyWithArgument =
|
||||
SuspendingLazy<Context, CrashReporter?> {
|
||||
if (it.resources.getBoolean(R.bool.co_electriccoin_zcash_crash_is_firebase_enabled)) {
|
||||
|
||||
// Workaround for disk IO on main thread in Firebase initialization
|
||||
val firebaseApp = FirebaseAppCache.getFirebaseApp(it)
|
||||
if (firebaseApp == null) {
|
||||
Twig.warn { "Unable to initialize Crashlytics. FirebaseApp is null" }
|
||||
return@SuspendingLazy null
|
||||
}
|
||||
|
||||
val firebaseInstallations = FirebaseInstallations.getInstance(firebaseApp)
|
||||
val firebaseCrashlytics =
|
||||
FirebaseCrashlytics.getInstance().apply {
|
||||
setCustomKey(
|
||||
CrashlyticsUserProperties.IS_TEST,
|
||||
EmulatorWtfUtil.isEmulatorWtf(it) || FirebaseTestLabUtil.isFirebaseTestLab(it)
|
||||
)
|
||||
}
|
||||
|
||||
FirebaseCrashReporterImpl(firebaseCrashlytics, firebaseInstallations)
|
||||
} else {
|
||||
Twig.warn { "Unable to initialize Crashlytics. Configure API keys in the app module" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getInstance(context: Context): CrashReporter? {
|
||||
return lazyWithArgument.getInstance(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal object CrashlyticsUserProperties {
|
||||
/**
|
||||
* Flags a crash as occurring in a test environment. Set automatically to detect Firebase Test Lab and emulator.wtf
|
||||
*/
|
||||
const val IS_TEST = "is_test" // $NON-NLS
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="co_electriccoin_zcash_crash_is_use_secondary_process">true</bool>
|
||||
<!-- Expected to be overridden by a resource overlay in the app module, generated
|
||||
based on the presence of the Firebase API keys -->
|
||||
<bool name="co_electriccoin_zcash_crash_is_firebase_enabled">false</bool>
|
||||
</resources>
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<application>
|
||||
|
||||
<!-- For improved user privacy, don't allow Firebase to collect advertising IDs -->
|
||||
<meta-data
|
||||
android:name="google_analytics_adid_collection_enabled"
|
||||
android:value="false" />
|
||||
|
||||
<!-- We want better control over the timing of Firebase initialization -->
|
||||
<provider
|
||||
android:name="com.google.firebase.provider.FirebaseInitProvider"
|
||||
android:authorities="${applicationId}.firebaseinitprovider"
|
||||
tools:node="remove" />
|
||||
|
||||
<provider
|
||||
android:name=".internal.local.CrashProcessNameContentProvider"
|
||||
android:authorities="${applicationId}.co.electriccoin.zcash.crash"
|
||||
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
|
||||
android:exported="false"
|
||||
android:process=":crash" />
|
||||
|
||||
<receiver
|
||||
android:name=".internal.local.ExceptionReceiver"
|
||||
android:enabled="@bool/co_electriccoin_zcash_crash_is_use_secondary_process"
|
||||
android:exported="false"
|
||||
android:process=":crash" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,19 @@
|
|||
package co.electriccoin.zcash.crash.android.internal
|
||||
|
||||
import android.content.Context
|
||||
import co.electriccoin.zcash.crash.android.internal.firebase.FirebaseCrashReporter
|
||||
import co.electriccoin.zcash.crash.android.internal.local.LocalCrashReporter
|
||||
|
||||
class ListCrashReportersImpl : ListCrashReporters {
|
||||
override fun provideReporters(context: Context): List<CrashReporter> {
|
||||
// To prevent a race condition, register the LocalCrashReporter first.
|
||||
// FirebaseCrashReporter does some asynchronous registration internally, while
|
||||
// LocalCrashReporter uses AndroidUncaughtExceptionHandler which needs to read
|
||||
// and write the default UncaughtExceptionHandler. The only way to ensure
|
||||
// interleaving doesn't happen is to register the LocalCrashReporter first.
|
||||
return listOfNotNull(
|
||||
LocalCrashReporter.getInstance(context),
|
||||
FirebaseCrashReporter(context),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package co.electriccoin.zcash.crash.android.internal.firebase
|
||||
|
||||
import android.content.Context
|
||||
import com.google.firebase.FirebaseApp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
object FirebaseAppCache {
|
||||
private val mutex = Mutex()
|
||||
|
||||
@Volatile
|
||||
private var cachedFirebaseApp: FirebaseAppContainer? = null
|
||||
|
||||
fun peekFirebaseApp(): FirebaseApp? = cachedFirebaseApp?.firebaseApp
|
||||
|
||||
suspend fun getFirebaseApp(context: Context): FirebaseApp? {
|
||||
mutex.withLock {
|
||||
peekFirebaseApp()?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
val firebaseAppContainer = getFirebaseAppContainer(context)
|
||||
|
||||
cachedFirebaseApp = firebaseAppContainer
|
||||
}
|
||||
|
||||
return peekFirebaseApp()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getFirebaseAppContainer(context: Context): FirebaseAppContainer =
|
||||
withContext(Dispatchers.IO) {
|
||||
val firebaseApp = FirebaseApp.initializeApp(context)
|
||||
FirebaseAppContainer(firebaseApp)
|
||||
}
|
||||
|
||||
private class FirebaseAppContainer(val firebaseApp: FirebaseApp?)
|
|
@ -0,0 +1,135 @@
|
|||
@file:JvmName("FirebaseCrashReporterKt")
|
||||
|
||||
package co.electriccoin.zcash.crash.android.internal.firebase
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.AnyThread
|
||||
import co.electriccoin.zcash.crash.android.R
|
||||
import co.electriccoin.zcash.crash.android.internal.CrashReporter
|
||||
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
|
||||
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
|
||||
import co.electriccoin.zcash.spackle.SuspendingLazy
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.google.firebase.installations.FirebaseInstallations
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
/**
|
||||
* Registers an exception handler with Firebase Crashlytics.
|
||||
*/
|
||||
internal class FirebaseCrashReporter(
|
||||
context: Context
|
||||
) : CrashReporter {
|
||||
@OptIn(kotlinx.coroutines.DelicateCoroutinesApi::class)
|
||||
private val analyticsScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
private val initFirebaseJob: Deferred<CrashReporter?> =
|
||||
analyticsScope.async {
|
||||
FirebaseCrashReporterImpl.getInstance(context)
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
override fun reportCaughtException(exception: Throwable) {
|
||||
initFirebaseJob.invokeOnCompletionWithResult {
|
||||
it?.reportCaughtException(exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun enable() {
|
||||
initFirebaseJob.invokeOnCompletionWithResult {
|
||||
it?.enable()
|
||||
}
|
||||
}
|
||||
|
||||
override fun disableAndDelete() {
|
||||
initFirebaseJob.invokeOnCompletionWithResult {
|
||||
it?.disableAndDelete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun <T> Deferred<T>.invokeOnCompletionWithResult(handler: (T) -> Unit) {
|
||||
invokeOnCompletion {
|
||||
handler(this.getCompleted())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an exception handler with Firebase Crashlytics.
|
||||
*/
|
||||
private class FirebaseCrashReporterImpl(
|
||||
private val firebaseCrashlytics: FirebaseCrashlytics,
|
||||
private val firebaseInstallations: FirebaseInstallations
|
||||
) : CrashReporter {
|
||||
@AnyThread
|
||||
override fun reportCaughtException(exception: Throwable) {
|
||||
error(
|
||||
"Although most of the sensitive model objects implement custom [toString] methods to redact information" +
|
||||
" if they were to be logged (which includes exceptions), we're encouraged to disable caught exception" +
|
||||
" reporting to the remote Crashlytics service due to its security risk. Use the the local variant of" +
|
||||
" the reporter to report caught exception - [LocalCrashReporter]."
|
||||
)
|
||||
}
|
||||
|
||||
override fun enable() {
|
||||
firebaseCrashlytics.setCrashlyticsCollectionEnabled(true)
|
||||
}
|
||||
|
||||
override fun disableAndDelete() {
|
||||
firebaseCrashlytics.setCrashlyticsCollectionEnabled(false)
|
||||
firebaseCrashlytics.deleteUnsentReports()
|
||||
firebaseInstallations.delete()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/*
|
||||
* Note there is a tradeoff with the suspending implementation. In order to avoid disk IO
|
||||
* on the main thread, there is a brief timing gap during application startup where very
|
||||
* early crashes may be missed. This is a tradeoff we are willing to make in order to avoid
|
||||
* ANRs.
|
||||
*/
|
||||
private val lazyWithArgument =
|
||||
SuspendingLazy<Context, CrashReporter?> {
|
||||
if (it.resources.getBoolean(R.bool.co_electriccoin_zcash_crash_is_firebase_enabled)) {
|
||||
|
||||
// Workaround for disk IO on main thread in Firebase initialization
|
||||
val firebaseApp = FirebaseAppCache.getFirebaseApp(it)
|
||||
if (firebaseApp == null) {
|
||||
Twig.warn { "Unable to initialize Crashlytics. FirebaseApp is null" }
|
||||
return@SuspendingLazy null
|
||||
}
|
||||
|
||||
val firebaseInstallations = FirebaseInstallations.getInstance(firebaseApp)
|
||||
val firebaseCrashlytics =
|
||||
FirebaseCrashlytics.getInstance().apply {
|
||||
setCustomKey(
|
||||
CrashlyticsUserProperties.IS_TEST,
|
||||
EmulatorWtfUtil.isEmulatorWtf(it) || FirebaseTestLabUtil.isFirebaseTestLab(it)
|
||||
)
|
||||
}
|
||||
|
||||
FirebaseCrashReporterImpl(firebaseCrashlytics, firebaseInstallations)
|
||||
} else {
|
||||
Twig.warn { "Unable to initialize Crashlytics. Configure API keys in the app module" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getInstance(context: Context): CrashReporter? {
|
||||
return lazyWithArgument.getInstance(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal object CrashlyticsUserProperties {
|
||||
/**
|
||||
* Flags a crash as occurring in a test environment. Set automatically to detect Firebase Test Lab and emulator.wtf
|
||||
*/
|
||||
const val IS_TEST = "is_test" // $NON-NLS
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="co_electriccoin_zcash_crash_is_use_secondary_process">true</bool>
|
||||
<!-- Expected to be overridden by a resource overlay in the app module, generated
|
||||
based on the presence of the Firebase API keys -->
|
||||
<bool name="co_electriccoin_zcash_crash_is_firebase_enabled">false</bool>
|
||||
</resources>
|
|
@ -14,7 +14,6 @@ To enhance security, [OpenID Connect](https://docs.github.com/en/actions/deploym
|
|||
|
||||
### Pull request
|
||||
* Variables
|
||||
* `ZCASH_SUPPORT_EMAIL_ADDRESS` - Email address for user support requests.
|
||||
* `FIREBASE_TEST_LAB_PROJECT` - Firebase Test Lab project name.
|
||||
* Secrets
|
||||
* `EMULATOR_WTF_API_KEY` - API key for [Emulator.wtf](https://emulator.wtf)
|
||||
|
@ -36,8 +35,6 @@ Note that pull requests will create a "release" build with a temporary fake sign
|
|||
Note that `FIREBASE_DEBUG_JSON_BASE64` and `FIREBASE_RELEASE_JSON_BASE64` are not truly considered secret, as they contain API keys that are embedded in the application. However we are not including them in the repository to reduce accidental pollution of our crash report data from repository forks.
|
||||
|
||||
### Release deployment
|
||||
* Variables
|
||||
* `ZCASH_SUPPORT_EMAIL_ADDRESS` - Email address for user support requests.
|
||||
* Secrets
|
||||
* `GOOGLE_PLAY_CLOUD_PROJECT` - Google Cloud project associated with Google Play.
|
||||
* `GOOGLE_PLAY_SERVICE_ACCOUNT` - Email address of service account.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Happy Birthday FOSS Zashi! Welcome to F-droid!
|
|
@ -0,0 +1,10 @@
|
|||
Zashi is a Zcash wallet that keeps your transaction and message history private.
|
||||
|
||||
Built and maintained by Electric Coin Co. (ECC), the inventor of Zcash, Zashi is the easiest way to use $ZEC.
|
||||
|
||||
Why use the Zashi Zcash wallet?
|
||||
* You can send and receive ZEC and private memos. (Zashi your mom! Zashi your barista!)
|
||||
* It’s reliable and fast, and it includes the ability to Spend before Sync. (Funds are available before sync completes.)
|
||||
* The UI is fully updated — simple and clean.
|
||||
* It supports Sapling and Orchard pools, plus Unified Addresses, a single address type that works across all Zcash pools, transparent and shielded.
|
||||
* It has built-in mechanisms for user support and developer feedback.
|
After Width: | Height: | Size: 263 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 461 KiB |
After Width: | Height: | Size: 535 KiB |
After Width: | Height: | Size: 546 KiB |
After Width: | Height: | Size: 649 KiB |
After Width: | Height: | Size: 549 KiB |
|
@ -0,0 +1 @@
|
|||
Zashi is a Zcash wallet that keeps your transaction and message history private
|
|
@ -0,0 +1 @@
|
|||
Zashi: Zcash Wallet
|
|
@ -0,0 +1 @@
|
|||
¡Feliz Cumpleaños FLOSS Zashi! Bienvenido a F-droid!
|
|
@ -0,0 +1,10 @@
|
|||
Zashi es una billetera de Zcash que mantiene privado el historial de tus transacciones y mensajes.
|
||||
|
||||
Creada y gestionada por Electric Coin Co. (ECC), los creadores de Zcash, Zashi es la manera más sencilla de usar $ZEC.
|
||||
|
||||
¿Por qué usar la billetera Zashi de Zcash?
|
||||
* Puedes enviar y recibir ZEC y mensajes privados. (¡Envía Zashi a tu mamá! ¡Envía Zashi a tu barista!)
|
||||
* Es confiable y rápida, e incluye la función "Gastar antes de Sincronizar" (los fondos están disponibles antes de que se complete la sincronización).
|
||||
* La interfaz está completamente actualizada: simple y clara.
|
||||
* Soporta los pools Sapling y Orchard, además de Direcciones Unificadas, un tipo de dirección única que funciona en todos los pools de Zcash, tanto transparentes como protegidos.
|
||||
* Cuenta con funciones integradas para soporte al usuario y feedback de desarrolladores.
|
After Width: | Height: | Size: 280 KiB |
After Width: | Height: | Size: 470 KiB |
After Width: | Height: | Size: 534 KiB |
After Width: | Height: | Size: 562 KiB |
After Width: | Height: | Size: 897 KiB |
After Width: | Height: | Size: 549 KiB |
|
@ -0,0 +1 @@
|
|||
Zashi: Una billetera de Zcash que mantiene tus transacciones y mensajes privados
|
|
@ -0,0 +1 @@
|
|||
Zashi: Billetera de Zcash
|
|
@ -68,8 +68,8 @@ ZCASH_VERSION_NAME=1.3.3
|
|||
# available on Google Play. This is useful for testing, or for a forked version of the app.
|
||||
ZCASH_RELEASE_APP_NAME=Zashi
|
||||
ZCASH_RELEASE_PACKAGE_NAME=co.electriccoin.zcash
|
||||
ZCASH_DEBUG_APP_NAME_SUFFIX=" (D)"
|
||||
ZCASH_SUPPORT_EMAIL_ADDRESS=support@electriccoin.co
|
||||
ZCASH_DEBUG_APP_NAME_SUFFIX="Debug"
|
||||
ZCASH_FOSS_APP_NAME_SUFFIX="Foss"
|
||||
|
||||
# In-app update priority of the release. It can take values in the range [0, 5], with 5 being the highest priority.
|
||||
# Defaults to 0. We treat all the values the same, except 5, which is evaluated as [Priority.HIGH],
|
||||
|
@ -155,9 +155,11 @@ ANDROID_GRADLE_PLUGIN_VERSION=8.5.0
|
|||
DETEKT_VERSION=1.23.6
|
||||
DETEKT_COMPOSE_RULES_VERSION=0.3.15
|
||||
EMULATOR_WTF_GRADLE_PLUGIN_VERSION=0.16.2
|
||||
# Handled
|
||||
FIREBASE_CRASHLYTICS_BUILD_TOOLS_VERSION=2.9.9
|
||||
FLANK_VERSION=23.10.1
|
||||
FULLADLE_VERSION=0.17.4
|
||||
# Handled
|
||||
GOOGLE_PLAY_SERVICES_GRADLE_PLUGIN_VERSION=4.4.1
|
||||
GRADLE_VERSIONS_PLUGIN_VERSION=0.51.0
|
||||
JGIT_VERSION=6.4.0.202211300538-r
|
||||
|
@ -193,7 +195,9 @@ ANDROIDX_UI_AUTOMATOR_VERSION=2.3.0
|
|||
ANDROIDX_WORK_MANAGER_VERSION=2.9.0
|
||||
ANDROIDX_BROWSER_VERSION=1.8.0
|
||||
CORE_LIBRARY_DESUGARING_VERSION=2.1.2
|
||||
# Handled
|
||||
FIREBASE_BOM_VERSION_MATCHER=33.1.1
|
||||
# Handled
|
||||
GOOGLE_AUTH_LIB_JAVA_VERSION=1.18.0
|
||||
JACOCO_VERSION=0.8.12
|
||||
KEYSTONE_VERSION=0.7.10
|
||||
|
@ -205,9 +209,11 @@ KOTLINX_SERIALIZABLE_JSON_VERSION=1.6.3
|
|||
KOVER_VERSION=0.7.3
|
||||
LOTTIE_VERSION=6.5.0
|
||||
MARKDOWN_VERSION=0.7.3
|
||||
# Should we handle?
|
||||
MLKIT_SCANNING_VERSION=17.3.0
|
||||
PLAY_APP_UPDATE_VERSION=2.1.0
|
||||
# We should handle
|
||||
PLAY_APP_UPDATE_KTX_VERSION=2.1.0
|
||||
# We should handle
|
||||
PLAY_PUBLISHER_API_VERSION=v3-rev20231030-2.0.0
|
||||
TINK_VERSION=1.15.0
|
||||
ZCASH_ANDROID_WALLET_PLUGINS_VERSION=1.0.0
|
||||
|
|
|
@ -130,6 +130,7 @@ dependencyResolutionManagement {
|
|||
}
|
||||
}
|
||||
}
|
||||
// start wtf maven
|
||||
maven("https://maven.emulator.wtf/releases/") {
|
||||
if (isRepoRestrictionEnabled) {
|
||||
content {
|
||||
|
@ -137,6 +138,7 @@ dependencyResolutionManagement {
|
|||
}
|
||||
}
|
||||
}
|
||||
// end wtf maven
|
||||
maven("https://jitpack.io")
|
||||
}
|
||||
|
||||
|
@ -183,8 +185,6 @@ dependencyResolutionManagement {
|
|||
val lottieVersion = extra["LOTTIE_VERSION"].toString()
|
||||
val markdownVersion = extra["MARKDOWN_VERSION"].toString()
|
||||
val mlkitScanningVersion = extra["MLKIT_SCANNING_VERSION"].toString()
|
||||
val playAppUpdateVersion = extra["PLAY_APP_UPDATE_VERSION"].toString()
|
||||
val playAppUpdateKtxVersion = extra["PLAY_APP_UPDATE_KTX_VERSION"].toString()
|
||||
val tinkVersion = extra["TINK_VERSION"].toString()
|
||||
val zcashBip39Version = extra["ZCASH_BIP39_VERSION"].toString()
|
||||
val zcashSdkVersion = extra["ZCASH_SDK_VERSION"].toString()
|
||||
|
@ -250,8 +250,6 @@ dependencyResolutionManagement {
|
|||
library("lottie", "com.airbnb.android:lottie-compose:$lottieVersion")
|
||||
library("markdown", "org.jetbrains:markdown:$markdownVersion")
|
||||
library("mlkit-scanning", "com.google.mlkit:barcode-scanning:$mlkitScanningVersion")
|
||||
library("play-update", "com.google.android.play:app-update:$playAppUpdateVersion")
|
||||
library("play-update-ktx", "com.google.android.play:app-update-ktx:$playAppUpdateKtxVersion")
|
||||
library("tink", "com.google.crypto.tink:tink-android:$tinkVersion")
|
||||
library("zcash-sdk", "cash.z.ecc.android:zcash-android-sdk:$zcashSdkVersion")
|
||||
library("zcash-sdk-incubator", "cash.z.ecc.android:zcash-android-sdk-incubator:$zcashSdkVersion")
|
||||
|
@ -335,13 +333,6 @@ dependencyResolutionManagement {
|
|||
"koin-compose",
|
||||
)
|
||||
)
|
||||
bundle(
|
||||
"play-update",
|
||||
listOf(
|
||||
"play-update",
|
||||
"play-update-ktx",
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import model.DistributionDimension
|
||||
import model.BuildType
|
||||
import model.NetworkDimension
|
||||
|
||||
plugins {
|
||||
id("com.android.test")
|
||||
kotlin("android")
|
||||
|
@ -14,20 +18,22 @@ android {
|
|||
// to enable benchmarking for emulators, although only a physical device gives real results
|
||||
testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "EMULATOR"
|
||||
// To simplify module variants, we assume to run benchmarking against mainnet only
|
||||
missingDimensionStrategy("network", "zcashmainnet")
|
||||
missingDimensionStrategy(NetworkDimension.DIMENSION_NAME, NetworkDimension.MAINNET.value)
|
||||
missingDimensionStrategy(DistributionDimension.DIMENSION_NAME, DistributionDimension.STORE.value)
|
||||
missingDimensionStrategy(DistributionDimension.DIMENSION_NAME, DistributionDimension.FOSS.value)
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
create("release") {
|
||||
create(BuildType.RELEASE.value) {
|
||||
// To provide compatibility with other modules
|
||||
}
|
||||
create("benchmark") {
|
||||
create(BuildType.BENCHMARK.value) {
|
||||
// We provide the extra benchmark build variants for benchmarking. We still need to support debug
|
||||
// variants to be compatible with debug variants in other modules, although benchmarking does not allow
|
||||
// not minified build variants - benchmarking with the debug build variants will fail.
|
||||
isDebuggable = true
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
matchingFallbacks += listOf("release")
|
||||
signingConfig = signingConfigs.getByName(BuildType.DEBUG.value)
|
||||
matchingFallbacks += listOf(BuildType.RELEASE.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import model.DistributionDimension
|
||||
import model.BuildType
|
||||
import model.NetworkDimension
|
||||
|
||||
plugins {
|
||||
id("com.android.test")
|
||||
kotlin("android")
|
||||
|
@ -26,17 +30,24 @@ android {
|
|||
}
|
||||
|
||||
// Define the same flavors as in app module
|
||||
flavorDimensions.add("network")
|
||||
flavorDimensions += listOf(NetworkDimension.DIMENSION_NAME, DistributionDimension.DIMENSION_NAME)
|
||||
|
||||
productFlavors {
|
||||
create("zcashtestnet") {
|
||||
dimension = "network"
|
||||
create(NetworkDimension.TESTNET.value) {
|
||||
dimension = NetworkDimension.DIMENSION_NAME
|
||||
}
|
||||
create("zcashmainnet") {
|
||||
dimension = "network"
|
||||
create(NetworkDimension.MAINNET.value) {
|
||||
dimension = NetworkDimension.DIMENSION_NAME
|
||||
}
|
||||
create(DistributionDimension.STORE.value) {
|
||||
dimension = DistributionDimension.DIMENSION_NAME
|
||||
}
|
||||
create(DistributionDimension.FOSS.value) {
|
||||
dimension = DistributionDimension.DIMENSION_NAME
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
create("release") {
|
||||
create(BuildType.RELEASE.value) {
|
||||
// to align with the benchmark module requirement - run against minified application
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +77,6 @@ dependencies {
|
|||
|
||||
implementation(libs.bundles.androidx.test)
|
||||
implementation(libs.bundles.androidx.compose.core)
|
||||
implementation(libs.bundles.play.update)
|
||||
|
||||
implementation(libs.androidx.compose.test.junit)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.integration.test.screen.update.viewmodel
|
||||
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.test.filters.MediumTest
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.fixture.UpdateInfoFixture
|
||||
import co.electriccoin.zcash.ui.integration.test.common.IntegrationTestingActivity
|
||||
import co.electriccoin.zcash.ui.screen.update.AppUpdateChecker
|
||||
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerMock
|
||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateInfo
|
||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
||||
import co.electriccoin.zcash.ui.screen.update.viewmodel.UpdateViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.collectIndexed
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class UpdateViewModelTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createAndroidComposeRule<IntegrationTestingActivity>()
|
||||
|
||||
private lateinit var viewModel: UpdateViewModel
|
||||
private lateinit var checker: AppUpdateCheckerMock
|
||||
private lateinit var initialUpdateInfo: UpdateInfo
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
checker = AppUpdateCheckerMock()
|
||||
|
||||
initialUpdateInfo =
|
||||
UpdateInfoFixture.new(
|
||||
appUpdateInfo = null,
|
||||
state = UpdateState.Prepared,
|
||||
priority = AppUpdateChecker.Priority.LOW,
|
||||
force = false
|
||||
)
|
||||
|
||||
viewModel =
|
||||
UpdateViewModel(
|
||||
composeTestRule.activity.application,
|
||||
initialUpdateInfo,
|
||||
checker
|
||||
)
|
||||
}
|
||||
|
||||
@After
|
||||
fun cleanup() {
|
||||
viewModel.viewModelScope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun validate_result_of_update_methods_calls() =
|
||||
runTest {
|
||||
viewModel.checkForAppUpdate()
|
||||
|
||||
// Although this test does not copy the real world situation, as the initial and result objects
|
||||
// should be mostly the same, we test VM proper functionality. VM emits the initial object
|
||||
// defined in this class, then we expect the result object from the AppUpdateCheckerMock class
|
||||
// and a newly acquired AppUpdateInfo object.
|
||||
viewModel.updateInfo.take(4).collectIndexed { index, incomingInfo ->
|
||||
when (index) {
|
||||
0 -> {
|
||||
// checkForAppUpdate initial callback
|
||||
incomingInfo.also {
|
||||
assertNull(it.appUpdateInfo)
|
||||
|
||||
assertEquals(initialUpdateInfo.state, it.state)
|
||||
assertEquals(initialUpdateInfo.appUpdateInfo, it.appUpdateInfo)
|
||||
assertEquals(initialUpdateInfo.priority, it.priority)
|
||||
assertEquals(initialUpdateInfo.state, it.state)
|
||||
assertEquals(initialUpdateInfo.isForce, it.isForce)
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
// checkForAppUpdate result callback
|
||||
incomingInfo.also {
|
||||
assertNotNull(it.appUpdateInfo)
|
||||
|
||||
assertEquals(AppUpdateCheckerMock.resultUpdateInfo.state, it.state)
|
||||
assertEquals(AppUpdateCheckerMock.resultUpdateInfo.priority, it.priority)
|
||||
assertEquals(AppUpdateCheckerMock.resultUpdateInfo.isForce, it.isForce)
|
||||
}
|
||||
|
||||
// now we can start the update
|
||||
viewModel.goForUpdate(composeTestRule.activity, incomingInfo.appUpdateInfo!!)
|
||||
}
|
||||
2 -> {
|
||||
// goForUpdate initial callback
|
||||
assertNotNull(incomingInfo.appUpdateInfo)
|
||||
assertEquals(UpdateState.Running, incomingInfo.state)
|
||||
}
|
||||
3 -> {
|
||||
// goForUpdate result callback
|
||||
assertNotNull(incomingInfo.appUpdateInfo)
|
||||
assertEquals(UpdateState.Done, incomingInfo.state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
import com.android.build.api.variant.BuildConfigField
|
||||
import model.DistributionDimension
|
||||
import model.NetworkDimension
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
|
@ -81,6 +83,26 @@ android {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions += listOf(NetworkDimension.DIMENSION_NAME, DistributionDimension.DIMENSION_NAME)
|
||||
|
||||
productFlavors {
|
||||
create(NetworkDimension.TESTNET.value) {
|
||||
dimension = NetworkDimension.DIMENSION_NAME
|
||||
}
|
||||
|
||||
create(NetworkDimension.MAINNET.value) {
|
||||
dimension = NetworkDimension.DIMENSION_NAME
|
||||
}
|
||||
|
||||
create(DistributionDimension.STORE.value) {
|
||||
dimension = DistributionDimension.DIMENSION_NAME
|
||||
}
|
||||
|
||||
create(DistributionDimension.FOSS.value) {
|
||||
dimension = DistributionDimension.DIMENSION_NAME
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
|
@ -136,7 +158,6 @@ dependencies {
|
|||
implementation(libs.bundles.androidx.compose.core)
|
||||
implementation(libs.bundles.androidx.compose.extended)
|
||||
api(libs.bundles.koin)
|
||||
implementation(libs.bundles.play.update)
|
||||
implementation(libs.kotlin.stdlib)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
implementation(libs.kotlinx.coroutines.core)
|
||||
|
@ -144,7 +165,7 @@ dependencies {
|
|||
implementation(libs.kotlinx.datetime)
|
||||
implementation(libs.kotlinx.immutable)
|
||||
implementation(libs.kotlinx.serializable.json)
|
||||
implementation(libs.mlkit.scanning)
|
||||
"storeImplementation"(libs.mlkit.scanning)
|
||||
api(libs.zcash.sdk)
|
||||
implementation(libs.zcash.sdk.incubator)
|
||||
implementation(libs.zcash.bip39)
|
||||
|
|
|
@ -41,7 +41,6 @@ class BalancesTestSetup(
|
|||
showStatusDialog = null,
|
||||
onStatusClick = {},
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
isUpdateAvailable = false,
|
||||
isShowingErrorDialog = false,
|
||||
setShowErrorDialog = {},
|
||||
onContactSupport = {},
|
||||
|
|
|
@ -31,7 +31,6 @@ class WalletDisplayValuesTest {
|
|||
WalletDisplayValues.getNextValues(
|
||||
context = getAppContext(),
|
||||
walletSnapshot = walletSnapshot,
|
||||
isUpdateAvailable = false
|
||||
)
|
||||
|
||||
assertNotNull(values)
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.update.fixture
|
||||
|
||||
import androidx.test.filters.SmallTest
|
||||
import co.electriccoin.zcash.ui.fixture.UpdateInfoFixture
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class UpdateInfoFixtureTest {
|
||||
companion object {
|
||||
val updateInfo = UpdateInfoFixture.new(appUpdateInfo = null)
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
fun fixture_result_test() {
|
||||
updateInfo.also {
|
||||
assertEquals(it.priority, UpdateInfoFixture.INITIAL_PRIORITY)
|
||||
assertEquals(it.isForce, UpdateInfoFixture.INITIAL_FORCE)
|
||||
assertEquals(it.state, UpdateInfoFixture.INITIAL_STATE)
|
||||
assertEquals(it.appUpdateInfo, null)
|
||||
}
|
||||
}
|
||||
}
|