Initial commit: Pezkuwi Wallet Android

Complete rebrand of Nova Wallet for Pezkuwichain ecosystem.

## Features
- Full Pezkuwichain support (HEZ & PEZ tokens)
- Polkadot ecosystem compatibility
- Staking, Governance, DeFi, NFTs
- XCM cross-chain transfers
- Hardware wallet support (Ledger, Polkadot Vault)
- WalletConnect v2
- Push notifications

## Languages
- English, Turkish, Kurmanci (Kurdish), Spanish, French, German, Russian, Japanese, Chinese, Korean, Portuguese, Vietnamese

Based on Nova Wallet by Novasama Technologies GmbH
© Dijital Kurdistan Tech Institute 2026
This commit is contained in:
2026-01-23 01:31:12 +03:00
commit 31c8c5995f
7621 changed files with 425838 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# http://editorconfig.org
root = true
[{*.kt, *.kts}]
#Ktlint configuration
max_line_length = 160
insert_final_newline = true
indent_size = 4
ktlint_disabled_rules = import-ordering, package-name, trailing-comma-on-call-site, trailing-comma-on-declaration-site, filename
+72
View File
@@ -0,0 +1,72 @@
import os
import sys
import re
from datetime import datetime, timezone
import requests
ALLOWED_SEVERITIES = {"Major", "Critical", "Normal"}
# Matches: "Release severity: <value>" (case-insensitive, flexible spaces)
SEVERITY_LINE_RE = re.compile(r"^release\s+severity\s*:\s*(.+)$", re.IGNORECASE)
def parse_base_params(comment_link: str) -> None:
if not comment_link:
print("COMMENT_LINK is not set. Provide a valid PR comment API URL in env var COMMENT_LINK.")
sys.exit(1)
env_file = os.getenv("GITHUB_ENV")
if not env_file:
print("GITHUB_ENV is not set. This script expects GitHub Actions environment.")
sys.exit(1)
try:
resp = requests.get(comment_link, timeout=10)
resp.raise_for_status()
payload = resp.json()
except requests.RequestException as e:
print(f"Failed to fetch PR comment: {e}")
sys.exit(1)
except ValueError:
print("Response is not valid JSON.")
sys.exit(1)
body = payload.get("body")
if not isinstance(body, str) or not body.strip():
print("PR comment body is empty. Add 'Release severity: Major | Critical | Normal'.")
sys.exit(1)
lines = [line.strip() for line in body.splitlines()]
severity_raw = ""
for line in lines:
m = SEVERITY_LINE_RE.match(line)
if m:
severity_raw = m.group(1).strip()
break
if not severity_raw:
print("Release severity is missing. Add a line 'Release severity: Major | Critical | Normal'.")
sys.exit(1)
if severity_raw not in ALLOWED_SEVERITIES:
print(f"Invalid severity '{severity_raw}'. Allowed values: Major, Critical, Normal.")
sys.exit(1)
severity = severity_raw
time_iso = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
try:
with open(env_file, "a", encoding="utf-8") as f:
f.write(f"TIME={time_iso}\n")
f.write(f"SEVERITY={severity}\n")
except OSError as e:
print(f"Failed to write to GITHUB_ENV: {e}")
sys.exit(1)
if __name__ == "__main__":
parse_base_params(os.getenv("COMMENT_LINK"))
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
adb devices
# Install debug app
adb -s emulator-5554 install app/debug/app-debug.apk
# Install instrumental tests
adb -s emulator-5554 install app/androidTest/debug/app-debug-androidTest.apk
# Run tests
adb logcat -c &&
python - <<END
import os
import re
import subprocess as sp
import sys
import threading
import time
done = False
def update():
# prevent CI from killing the process for inactivity
while not done:
time.sleep(5)
print ("Running...")
t = threading.Thread(target=update)
t.dameon = True
t.start()
def run():
os.system('adb wait-for-device')
p = sp.Popen('adb shell am instrument -w -m -e debug false -e class "io.novafoundation.nova.balances.BalancesIntegrationTest" io.novafoundation.nova.debug.test/io.qameta.allure.android.runners.AllureAndroidJUnitRunner',
shell=True, stdout=sp.PIPE, stderr=sp.PIPE, stdin=sp.PIPE)
return p.communicate()
success = re.compile(r'OK \(\d+ tests\)')
stdout, stderr = run()
stdout = stdout.decode('ISO-8859-1')
stderr = stderr.decode('ISO-8859-1')
done = True
print (stderr)
print (stdout)
if success.search(stderr + stdout):
sys.exit(0)
else:
sys.exit(1) # make sure we fail if the tests fail
END
EXIT_CODE=$?
adb logcat -d '*:E'
# Export results
adb exec-out run-as io.novafoundation.nova.debug sh -c 'cd /data/data/io.novafoundation.nova.debug/files && tar cf - allure-results' > allure-results.tar
exit $EXIT_CODE
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
adb devices
# Install debug app
adb -s emulator-5554 install app/debug/app-debug.apk
# Install instrumental tests
adb -s emulator-5554 install test-app/androidTest/debug/app-debug-androidTest.apk
# Run tests
adb logcat -c &&
python - <<END
import os
import re
import subprocess as sp
import sys
import threading
import time
done = False
def update():
# prevent CI from killing the process for inactivity
while not done:
time.sleep(5)
print ("Running...")
t = threading.Thread(target=update)
t.dameon = True
t.start()
def run():
os.system('adb wait-for-device')
p = sp.Popen('adb shell am instrument -w -m -e notClass io.novafoundation.nova.balances.BalancesIntegrationTest -e package io.novafoundation.nova.debug io.novafoundation.nova.debug.test/io.qameta.allure.android.runners.AllureAndroidJUnitRunner',
shell=True, stdout=sp.PIPE, stderr=sp.PIPE, stdin=sp.PIPE)
return p.communicate()
success = re.compile(r'OK \(\d+ tests\)')
stdout, stderr = run()
stdout = stdout.decode('ISO-8859-1')
stderr = stderr.decode('ISO-8859-1')
done = True
print (stderr)
print (stdout)
if success.search(stderr + stdout):
sys.exit(0)
else:
sys.exit(1) # make sure we fail if the tests fail
END
EXIT_CODE=$?
adb logcat -d '*:E'
# Export results
adb exec-out run-as io.novafoundation.nova.debug sh -c 'cd /data/data/io.novafoundation.nova.debug/files && tar cf - allure-results' > allure-results.tar
exit $EXIT_CODE
+196
View File
@@ -0,0 +1,196 @@
name: Reusable workflow for build Android
on:
workflow_call:
inputs:
branch:
required: true
default: develop
type: string
gradlew-command:
required: false
type: string
default: "false"
run-tests:
required: false
type: boolean
default: true
keystore-file-name:
required: false
type: string
default: "false"
keystore-file-base64:
required: false
type: string
default: "false"
upload-name:
required: false
type: string
default: "apk"
build-debug-tests:
required: false
type: boolean
default: false
secrets:
ACALA_PROD_AUTH_TOKEN:
required: true
ACALA_TEST_AUTH_TOKEN:
required: true
MOONBEAM_PROD_AUTH_TOKEN:
required: true
MOONBEAM_TEST_AUTH_TOKEN:
required: true
MOONPAY_PRODUCTION_SECRET:
required: true
MOONPAY_TEST_SECRET:
required: true
EHTERSCAN_API_KEY_MOONBEAM:
required: true
EHTERSCAN_API_KEY_MOONRIVER:
required: true
EHTERSCAN_API_KEY_ETHEREUM:
required: true
INFURA_API_KEY:
required: true
DWELLIR_API_KEY:
required: true
WALLET_CONNECT_PROJECT_ID:
required: true
DEBUG_GOOGLE_OAUTH_ID:
required: true
RELEASE_GOOGLE_OAUTH_ID:
required: true
# Special secrets for signing:
CI_MARKET_KEYSTORE_PASS:
required: false
CI_MARKET_KEYSTORE_KEY_ALIAS:
required: false
CI_MARKET_KEYSTORE_KEY_PASS:
required: false
CI_MARKET_KEY_FILE:
required: false
CI_KEYSTORE_PASS:
required: false
CI_KEYSTORE_KEY_ALIAS:
required: false
CI_KEYSTORE_KEY_PASS:
required: false
CI_GITHUB_KEYSTORE_PASS:
required: false
CI_GITHUB_KEYSTORE_KEY_ALIAS:
required: false
CI_GITHUB_KEYSTORE_KEY_PASS:
required: false
# Secrets for google-services:
CI_DEVELOP_GOOGLE_SERVICES:
required: true
CI_PRODUCTION_GOOGLE_SERVICES:
required: true
env:
ACALA_PROD_AUTH_TOKEN: ${{ secrets.ACALA_PROD_AUTH_TOKEN }}
ACALA_TEST_AUTH_TOKEN: ${{ secrets.ACALA_TEST_AUTH_TOKEN }}
MOONBEAM_PROD_AUTH_TOKEN: ${{ secrets.MOONBEAM_PROD_AUTH_TOKEN }}
MOONBEAM_TEST_AUTH_TOKEN: ${{ secrets.MOONBEAM_TEST_AUTH_TOKEN }}
MOONPAY_PRODUCTION_SECRET: ${{ secrets.MOONPAY_PRODUCTION_SECRET }}
MOONPAY_TEST_SECRET: ${{ secrets.MOONPAY_TEST_SECRET }}
MERCURYO_PRODUCTION_SECRET: ${{ secrets.MERCURYO_PRODUCTION_SECRET }}
MERCURYO_TEST_SECRET: ${{ secrets.MERCURYO_TEST_SECRET }}
EHTERSCAN_API_KEY_MOONBEAM: ${{ secrets.EHTERSCAN_API_KEY_MOONBEAM }}
EHTERSCAN_API_KEY_MOONRIVER: ${{ secrets.EHTERSCAN_API_KEY_MOONRIVER }}
EHTERSCAN_API_KEY_ETHEREUM: ${{ secrets.EHTERSCAN_API_KEY_ETHEREUM }}
INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }}
DWELLIR_API_KEY: ${{ secrets.DWELLIR_API_KEY }}
WALLET_CONNECT_PROJECT_ID: ${{ secrets.WALLET_CONNECT_PROJECT_ID }}
DEBUG_GOOGLE_OAUTH_ID: ${{ secrets.DEBUG_GOOGLE_OAUTH_ID }}
RELEASE_GOOGLE_OAUTH_ID: ${{ secrets.RELEASE_GOOGLE_OAUTH_ID }}
CI_MARKET_KEYSTORE_PASS: ${{ secrets.CI_MARKET_KEYSTORE_PASS }}
CI_MARKET_KEYSTORE_KEY_ALIAS: ${{ secrets.CI_MARKET_KEYSTORE_KEY_ALIAS }}
CI_MARKET_KEYSTORE_KEY_PASS: ${{ secrets.CI_MARKET_KEYSTORE_KEY_PASS }}
CI_MARKET_KEY_FILE: ${{ secrets.RELEASE_MARKET_KEY_FILE }}
CI_KEYSTORE_PASS: ${{ secrets.CI_KEYSTORE_PASS }}
CI_KEYSTORE_KEY_ALIAS: ${{ secrets.CI_KEYSTORE_KEY_ALIAS }}
CI_KEYSTORE_KEY_PASS: ${{ secrets.CI_KEYSTORE_KEY_PASS }}
CI_GITHUB_KEYSTORE_PASS: ${{ secrets.CI_GITHUB_KEYSTORE_PASS }}
CI_GITHUB_KEYSTORE_KEY_ALIAS: ${{ secrets.CI_GITHUB_KEYSTORE_KEY_ALIAS }}
CI_GITHUB_KEYSTORE_KEY_PASS: ${{ secrets.CI_GITHUB_KEYSTORE_KEY_PASS }}
CI_GITHUB_KEYSTORE_KEY_FILE: ${{ secrets.BASE64_GITHUB_KEYSTORE_FILE }}
CI_DEVELOP_GOOGLE_SERVICES_FILE: ${{ secrets.CI_DEVELOP_GOOGLE_SERVICES }}
CI_PRODUCTION_GOOGLE_SERVICES_FILE: ${{ secrets.CI_PRODUCTION_GOOGLE_SERVICES }}
POLKASSEMBLY_SUMMARY_API_KEY: ${{ secrets.POLKASSEMBLY_SUMMARY_API_KEY }}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{inputs.upload-name}}
cancel-in-progress: true
jobs:
build-app:
name: Build app and test
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
- name: Checkout particualr branch
uses: actions/checkout@v2
with:
ref: ${{ inputs.branch }}
- name: 📂 Set up DEV Google Services
uses: davidSchuppa/base64Secret-toFile-action@v3
with:
secret: ${{ env.CI_DEVELOP_GOOGLE_SERVICES_FILE }}
filename: google-services.json
destination-path: ./app/
- name: 📂 Set up PROD Google Services
uses: davidSchuppa/base64Secret-toFile-action@v3
with:
secret: ${{ env.CI_PRODUCTION_GOOGLE_SERVICES_FILE }}
filename: google-services.json
destination-path: ./app/src/release/
- name: 🔧 Install dependencies
uses: ./.github/workflows/install/
- name: 🧪 Run tests
if: ${{ inputs.run-tests }}
run: ./gradlew runTest
- name: 🔐 Getting github sign key
if: ${{ startsWith(inputs.keystore-file-name, 'github_key.jks') }}
uses: timheuer/base64-to-file@v1.1
with:
fileName: ${{ inputs.keystore-file-name }}
fileDir: './app/'
encodedString: ${{ env.CI_GITHUB_KEYSTORE_KEY_FILE }}
- name: 🔐 Getting market sign key
if: ${{ startsWith(inputs.keystore-file-name, 'market_key.jks') }}
uses: timheuer/base64-to-file@v1.1
with:
fileName: ${{ inputs.keystore-file-name }}
fileDir: './app/'
encodedString: ${{ env.CI_MARKET_KEY_FILE }}
- name: 🏗 Build app
if: ${{ !startsWith(inputs.gradlew-command, 'false') }}
run: ./gradlew ${{ inputs.gradlew-command }}
- name: 🏗 Build debug tests
if: ${{ inputs.build-debug-tests }}
run: ./gradlew assembleDebugAndroidTest
- name: 🧹 Delete key after building
if: ${{ !startsWith(inputs.keystore-file-name, 'false') }}
run: rm ./app/${{ inputs.keystore-file-name }}
- name: ➡️ Upload build artifacts
if: ${{ !startsWith(inputs.gradlew-command, 'false') }}
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.upload-name }}
path: app/build/outputs/apk/
+68
View File
@@ -0,0 +1,68 @@
name: Appium Mobile Tests
on:
workflow_call:
inputs:
app_url:
type: string
description: URL to download the app from
required: true
test_grep:
type: string
description: Test pattern to run (pytest marker or test name)
required: false
default: "android"
allure_job_run_id:
type: string
description: ALLURE_JOB_RUN_ID service parameter. Leave blank.
required: false
default: ""
allure_username:
type: string
description: ALLURE_USERNAME service parameter. Leave blank.
required: false
default: ""
secrets:
WORKFLOW_TOKEN:
required: true
ALLURE_TOKEN:
required: false
env:
PYTHON_VERSION: '3.9'
CI: true
ALLURE_ENDPOINT: https://pezkuwi.testops.cloud/
ALLURE_PROJECT_ID: 103
jobs:
trigger-tests:
runs-on: ubuntu-latest
steps:
- name: Trigger mobile tests in test repository
uses: actions/github-script@v7
with:
github-token: ${{ secrets.WORKFLOW_TOKEN }}
script: |
const response = await github.rest.actions.createWorkflowDispatch({
owner: 'pezkuwichain',
repo: 'appium-mobile-tests',
workflow_id: 'browserstack-tests.yml',
ref: 'master',
inputs: {
app_url: '${{ inputs.app_url }}',
ALLURE_JOB_RUN_ID: '${{ inputs.allure_job_run_id }}',
ALLURE_USERNAME: '${{ inputs.allure_username }}'
}
});
console.log('Mobile tests triggered successfully');
console.log('App URL:', '${{ inputs.app_url }}');
- name: Wait for test completion (optional)
if: false
uses: actions/github-script@v7
with:
github-token: ${{ secrets.WORKFLOW_TOKEN }}
script: |
console.log('Waiting for test completion...');
+93
View File
@@ -0,0 +1,93 @@
name: Run balances tests
on:
workflow_dispatch:
schedule:
- cron: '0 */8 * * *'
jobs:
build-app:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@develop
with:
branch: ${{github.head_ref}}
gradlew-command: assembleDebug
upload-name: develop-apk
run-tests: false
build-debug-tests: true
secrets: inherit
run-tests:
needs: [build-app]
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: develop-apk
path: app
- name: Debug path
run: |
ls -laR app
- name: Add permissions
run: chmod +x .github/scripts/run_balances_test.sh
- name: Run tests
uses: reactivecircus/android-emulator-runner@v2
with:
disable-animations: true
profile: Nexus 6
api-level: 29
script: .github/scripts/run_balances_test.sh
- uses: actions/upload-artifact@v4
if: always()
with:
name: anroid-results
path: ./allure-results.tar
report:
needs: [run-tests]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download artifact
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Unzip results
run: |
find artifacts -name allure-results.tar -exec tar -xvf {} \;
- name: Debug path
run: |
ls -laR
- name: Generate report
uses: ./.github/workflows/report/
with:
token: ${{ secrets.ACTIONS_DEPLOY_KEY }}
keep-reports-history: 30
telegram-notification:
needs: [report]
runs-on: ubuntu-latest
if: failure()
steps:
- name: Notify Telegram channel
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TELEGRAM_TO }}
token: ${{ secrets.TELEGRAM_TOKEN }}
format: html
message: |
💸 Balances tests failed.
Test Results: https://pezkuwichain.github.io/balances_test_result/${{ github.run_number }}/index.html
Github run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
@@ -0,0 +1,57 @@
name: Manual Firebase distribution
on:
workflow_dispatch:
inputs:
app_version:
description: 'Version of application'
required: true
default: v*.*.*
branch:
description: 'From which branch the application will be built'
required: true
default: develop
jobs:
build:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@develop
with:
branch: ${{ github.event.inputs.branch }}
gradlew-command: assembleReleaseMarket
keystore-file-name: market_key.jks
keystore-file-base64: ${{ secrets.RELEASE_MARKET_KEY_FILE }}
secrets: inherit
upload:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v2
- name: Set Environment Variables
uses: tw3lveparsecs/github-actions-setvars@v0.1
with:
envFilePath: .github/workflows/variables/android.env
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: apk
path: app
- name: Rename artifacts
run: mv app/releaseMarket/app-releaseMarket.apk app/releaseMarket/pezkuwi-wallet-android-${{ github.event.inputs.app_version }}.apk
- name: Market publication
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} # The contents of your service-account.json
packageName: io.pezkuwichain.wallet
releaseFiles: app/releaseMarket/pezkuwi-wallet-android-${{ github.event.inputs.app_version }}.apk
track: production # One of production, beta, alpha, internalsharing, internal, or a custom track name (case sensitive)
status: draft # One of "completed", "inProgress", "halted", "draft"
inAppUpdatePriority: 2
userFraction: 1.0 # Percentage of users who should get the staged version of the app. Defaults to 1.0
whatsNewDirectory: distribution/whatsnew # The directory of localized "whats new" files to upload as the release notes. The files contained in the whatsNewDirectory MUST use the pattern whatsnew-<LOCALE> where LOCALE is using the BCP 47 format
mappingFile: app/build/outputs/mapping/release/mapping.txt # The mapping.txt file used to de-obfuscate your stack traces from crash reports
debugSymbols: app/intermediates/merged_native_libs/release/out/lib
+40
View File
@@ -0,0 +1,40 @@
name: Install dependencies for Android build
description: Contains all dependencies for Android build
runs:
using: "composite"
steps:
- name: ☕️ Install Java
uses: actions/setup-java@v4.0.0
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
cmdline-tools-version: 12266719
- name: Install NDK
run: echo "y" | sudo ${ANDROID_SDK_ROOT}/cmdline-tools/16.0/bin/sdkmanager --install "ndk;26.1.10909125" --sdk_root=${ANDROID_SDK_ROOT}
shell: bash
- name: Set ndk.dir in local.properties
run: echo "ndk.dir=${{ steps.setup-ndk.outputs.ndk-path }}" >> local.properties
shell: bash
- name: 🦀 Install Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Add targets
run: |
rustup target add armv7-linux-androideabi
rustup target add i686-linux-android
rustup target add x86_64-linux-android
rustup target add aarch64-linux-android
shell: bash
@@ -0,0 +1,42 @@
name: Manual Firebase distribution
on:
workflow_dispatch:
inputs:
firebase_group:
description: 'Firebase group'
required: true
default: dev-team
branch:
description: 'From which branch the application will be built'
required: true
default: develop
jobs:
build:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@develop
with:
branch: ${{ github.event.inputs.branch }}
gradlew-command: assembleDevelop
secrets: inherit
upload:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v2
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: apk
path: app
- name: 🗳 Upload to Firebase
uses: ./.github/workflows/upload-to-firebase
with:
appId: ${{ secrets.ANDROID_DEVELOP_FIREBASE_APP_ID }}
firebase-token: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
releaseNotes: ${{ github.event.head_commit.message }}
test-groups: ${{ github.event.inputs.firebase_group }}
upload-file: app/develop/app-develop.apk
+76
View File
@@ -0,0 +1,76 @@
name: PR Workflow
on:
pull_request:
branches:
- 'master'
pull_request_review_comment:
types: [created, edited, deleted]
jobs:
checkRef:
if: github.event.pull_request.base.ref == 'master' || github.event_name == 'pull_request'
runs-on: ubuntu-latest
outputs:
is_rc: ${{ steps.check_ref.outputs.ref_contains_rc }}
steps:
- uses: actions/checkout@v2
- name: Check if "rc" or "hotfix" is present in github.ref
id: check_ref
run: |
echo ${{ github.head_ref || github.ref_name }}
if [[ "${{ github.head_ref || github.ref_name }}" == "rc/"* || "${{ github.head_ref || github.ref_name }}" == "hotfix/"* ]]; then
echo "ref_contains_rc=1" >> $GITHUB_OUTPUT
else
echo "ref_contains_rc=0" >> $GITHUB_OUTPUT
fi
- name: Output check result
run: |
echo "Output: ${{ steps.check_ref.outputs.ref_contains_rc }}"
make-or-update-pr:
runs-on: ubuntu-latest
permissions: write-all
needs: checkRef
if: needs.checkRef.outputs.is_rc == '1'
steps:
- uses: actions/checkout@v4
- name: Find Comment
uses: peter-evans/find-comment@v2
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: Release notes
- name: Create comment link
id: create_link
run: |
echo "COMMENT_LINK=https://api.github.com/repos/${{ github.repository }}/issues/comments/${{ steps.fc.outputs.comment-id }}" >> $GITHUB_ENV
shell: bash
- name: Extract version from branch name
id: extract_version
run: |
VERSION=${{ github.head_ref }}
VERSION=${VERSION/hotfix/rc} # Replace "hotfix" with "rc"
echo "::set-output name=version::${VERSION#*rc/}"
- uses: tibdex/github-app-token@v1
id: generate-token
with:
app_id: ${{ secrets.PR_APP_ID }}
private_key: ${{ secrets.PR_APP_TOKEN }}
- name: Run Python script
run: python .github/scripts/pr_comment_extract_data.py
- name: Create new branch and file in pezkuwi-wallet-android-releases repo
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ steps.generate-token.outputs.token }}
repository: pezkuwichain/pezkuwi-wallet-android-releases
event-type: create-pr
client-payload: '{"version": "${{ steps.extract_version.outputs.version }}", "comment_link": "${{ env.COMMENT_LINK }}", "time": "${{ env.TIME }}", "severity": "${{ env.SEVERITY }}"}'
@@ -0,0 +1,44 @@
name: Publish GitHub release
on:
push:
tags:
- '*'
permissions:
contents: write
jobs:
build:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@develop
with:
branch: master
gradlew-command: assembleReleaseGithub
keystore-file-name: github_key.jks
secrets: inherit
create-release:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v2
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: apk
path: app
- name: Rename artifacts
run: mv app/releaseGithub/app-releaseGithub.apk app/releaseGithub/pezkuwi-wallet-android-${{ github.ref_name }}-github.apk
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v1
with:
name: Release ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
generate_release_notes: true
draft: true
files: app/releaseGithub/pezkuwi-wallet-android-${{ github.ref_name }}-github.apk
+14
View File
@@ -0,0 +1,14 @@
name: Pull request
on:
pull_request:
jobs:
test:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@develop
with:
branch: ${{github.head_ref}}
gradlew-command: assembleDevelop
build-debug-tests: false # TODO: Enable this, when debug build will be fixed for tests
secrets: inherit
+76
View File
@@ -0,0 +1,76 @@
name: Build test and deploy develop apk
on:
push:
branches: [develop]
jobs:
build:
uses: pezkuwichain/pezkuwi-wallet-android/.github/workflows/android_build.yml@develop
with:
branch: develop
gradlew-command: assembleDevelop
secrets: inherit
upload-to-firebase:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v2
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: apk
path: app
- name: 🗳 Upload to Firebase
uses: ./.github/workflows/upload-to-firebase
with:
appId: ${{ secrets.ANDROID_DEVELOP_FIREBASE_APP_ID }}
firebase-token: ${{ secrets.CREDENTIAL_FILE_CONTENT }}
releaseNotes: ${{ github.event.head_commit.message }}
test-groups: dev-team
upload-file: app/develop/app-develop.apk
upload-to-s3:
runs-on: ubuntu-latest
needs: build
outputs:
s3_url: ${{ steps.s3_upload.outputs.s3_url }}
env:
S3_BUCKET: s3://pezkuwi-wallet-android-app
S3_REGION: nl-ams
steps:
- uses: actions/checkout@v2
- name: Download built artifact
uses: actions/download-artifact@v4
with:
name: apk
path: app
- name: ⚙️ Upload to S3
id: s3_upload
uses: ./.github/workflows/upload-to-s3
with:
s3_region: ${{ env.S3_REGION }}
s3_access_key: ${{ secrets.SCW_ACCESS_KEY }}
s3_secret_key: ${{ secrets.SCW_SECRET_KEY }}
s3_bucket: ${{ env.S3_BUCKET }}
upload_file: app/develop/app-develop.apk
- name: Show S3 URL
run: |
echo "App uploaded to: ${{ steps.s3_upload.outputs.s3_url }}"
appium-mobile-tests:
needs: [upload-to-s3]
uses: ./.github/workflows/appium-mobile-tests.yml
with:
app_url: ${{ needs.upload-to-s3.outputs.s3_url }}
test_grep: "android"
allure_job_run_id: ""
secrets:
WORKFLOW_TOKEN: ${{ secrets.PAT_TOKEN }}
ALLURE_TOKEN: ${{ secrets.ALLURE_TOKEN }}
+40
View File
@@ -0,0 +1,40 @@
name: Publish report to gh-pages
description: That workflow will publish report to the github-pages
inputs:
keep-reports-history:
description: "History storage depth, integer"
required: true
token:
description: "Github PAT"
required: true
runs:
using: "composite"
steps:
- name: Get Allure history
uses: actions/checkout@v4
if: always()
continue-on-error: true
with:
repository: pezkuwichain/balances_test_result
ref: gh-pages
path: gh-pages
- name: Allure Report action
uses: simple-elf/allure-report-action@master
if: always()
with:
allure_results: allure-results
allure_history: allure-history
keep_reports: ${{ inputs.keep-reports-history }}
github_repo: balances_test_result
github_repo_owner: pezkuwichain
- name: Deploy report to Github Pages
if: always()
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ inputs.token }}
publish_branch: gh-pages
publish_dir: allure-history
external_repository: pezkuwichain/balances_test_result
+46
View File
@@ -0,0 +1,46 @@
name: Bump app version
on:
push:
branches:
['master']
jobs:
update-tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
token: ${{ secrets.WRITE_SECRET_PAT }}
- name: Version in build.gradle
run: |
versionName=$(grep "versionName" build.gradle | grep -o "'.*'")
versionName=${versionName//\'/}
echo Version in gradle file: $versionName
echo "GRADLE_APP_VERSION=$versionName" >> "$GITHUB_ENV"
- name: Was version changed?
id: version
run: |
if [[ ${{ env.GRADLE_APP_VERSION }} == ${{ secrets.ANDROID_APP_VERSION }} ]]; then
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "changed=true" >> $GITHUB_OUTPUT
fi
- uses: rickstaa/action-create-tag@v1
if: steps.version.outputs.changed == 'true'
with:
tag: 'v${{ env.GRADLE_APP_VERSION }}'
message: Release v${{ env.GRADLE_APP_VERSION }}
- name: Write app version to secrets
if: steps.version.outputs.changed == 'true'
uses: hmanzur/actions-set-secret@v2.0.0
with:
name: 'ANDROID_APP_VERSION'
value: ${{ env.GRADLE_APP_VERSION }}
repository: pezkuwichain/pezkuwi-wallet-android
token: ${{ secrets.WRITE_SECRET_PAT }}
@@ -0,0 +1,49 @@
name: Deploy to Firebase
description: Deploy artifacts by provided path to firebase for provided groups
inputs:
appId:
description: 'Firebase AppID'
required: true
firebase-token:
description: 'Token from firebase CLI'
required: true
releaseNotes:
description: 'Notes which will attach to version'
required: true
default: 'update'
test-groups:
description: 'Groups which will receive the version'
required: true
upload-file:
description: 'File to uploading'
required: true
runs:
using: "composite"
steps:
- name: Upload artifact to Firebase App Distribution
id: upload
continue-on-error: true
uses: wzieba/Firebase-Distribution-Github-Action@v1.7.0
with:
appId: ${{ inputs.appId }}
serviceCredentialsFileContent: ${{ inputs.firebase-token }}
releaseNotes: ${{ inputs.releaseNotes }}
groups: ${{ inputs.test-groups }}
file: ${{ inputs.upload-file }}
- name: Sleep for 60 seconds
uses: whatnick/wait-action@master
if: steps.upload.outcome=='failure'
with:
time: '60s'
- name: Retry upload artifacts
if: steps.upload.outcome=='failure'
uses: wzieba/Firebase-Distribution-Github-Action@v1.7.0
with:
appId: ${{ inputs.appId }}
serviceCredentialsFileContent: ${{ inputs.firebase-token }}
releaseNotes: ${{ inputs.releaseNotes }}
groups: ${{ inputs.test-groups }}
file: ${{ inputs.upload-file }}
+52
View File
@@ -0,0 +1,52 @@
name: Upload to s3
description: Upload artifacts to s3
inputs:
s3_region:
description: 'S3 region'
required: true
s3_access_key:
description: 'S3 access key'
required: true
s3_secret_key:
description: 'S3 secret key'
required: true
s3_bucket:
description: 'S3 bucket'
required: true
upload_file:
description: 'File to uploading'
required: true
outputs:
s3_url:
description: 'URL of the uploaded file'
value: ${{ steps.interact_with_storage.outputs.s3_url }}
runs:
using: "composite"
steps:
- name: Set up S3cmd cli tool
uses: s3-actions/s3cmd@v1.6.1
with:
provider: scaleway
region: ${{ inputs.s3_region }}
secret_key: ${{ inputs.s3_secret_key }}
access_key: ${{ inputs.s3_access_key }}
- name: List available S3 buckets
run: s3cmd ls
shell: bash
- name: Interact with object storage
id: interact_with_storage
run: |
file="${{ inputs.upload_file }}"
destination_s3="${{ inputs.s3_bucket }}"
filename=$(basename "$file")
s3cmd sync "$file" "${destination_s3}/${filename}" --acl-public
bucket_name=$(echo "${{ inputs.s3_bucket }}" | sed 's|s3://||')
s3_url="https://${bucket_name}.s3.${{ inputs.s3_region }}.scw.cloud/${filename}"
echo "s3_url=${s3_url}" >> $GITHUB_OUTPUT
echo "Uploaded file URL: ${s3_url}"
shell: bash
+29
View File
@@ -0,0 +1,29 @@
*.iml
.gradle
/local.properties
.DS_Store
/build
*/build
/captures
.externalNativeBuild
app/src/main/aidl/
app/*.apk
/.idea/
# ignore jacoco coverage reports
/coverage
*.jks
!develop_key.jks
.java-version
# ignore database schemas
/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/*.json
# database schemas exceptions
!/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/1.json
!/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/2.json
!/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/8.json
!/core-db/schemas/io.novafoundation.nova.core_db.AppDatabase/9.json
google-services.json
+6
View File
@@ -0,0 +1,6 @@
[submodule "nova-wallet-dapp-js"]
path = nova-wallet-dapp-js
url = git@github.com:nova-wallet/nova-wallet-dapp-js.git
[submodule "nova-wallet-metamask-js"]
path = nova-wallet-metamask-js
url = git@github.com:nova-wallet/nova-wallet-metamask-js.git
+176
View File
@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+9
View File
@@ -0,0 +1,9 @@
Nova - Polkadot, Kusama wallet
Copyright 2022-2025 Novasama Technologies PTE. LTD.
This product includes software developed at Novasama Technologies PTE. LTD.
Some parts of this product are derived from https://github.com/soramitsu/fearless-Android, which belongs to Soramitsu K.K. and was mostly developed by our team of developers from May 1, 2020, to October 5, 2021.
Copyright 2021, Soramitsu Helvetia AG, all rights reserved.
License Rights transferred from Novasama Technologies PTE. LTD to Novasama Technologies GmbH starting from 1st of April 2023
+118
View File
@@ -0,0 +1,118 @@
# Pezkuwi Wallet Android
Next generation mobile wallet for Pezkuwichain and the Polkadot ecosystem.
[![](https://img.shields.io/twitter/follow/pezkuwichain?label=Follow&style=social)](https://twitter.com/pezkuwichain)
## About
Pezkuwi Wallet is a next-generation mobile application for the Pezkuwichain and Polkadot ecosystem. It provides a transparent, community-oriented wallet experience with convenient UX/UI, fast performance, and strong security.
**Key Features:**
- Full Pezkuwichain support (HEZ & PEZ tokens)
- Full Polkadot ecosystem compatibility
- Staking, Governance, DeFi
- NFT support
- Cross-chain transfers (XCM)
- Hardware wallet support (Ledger, Polkadot Vault)
- WalletConnect v2
- Push notifications
## Native Tokens
| Token | Network | Description |
|-------|---------|-------------|
| HEZ | Relay Chain | Native token for fees and staking |
| PEZ | Asset Hub | Governance token |
## Build Instructions
### Clone Repository
```bash
git clone git@github.com:pezkuwichain/pezkuwi-wallet-android.git
```
### Install NDK
Install NDK version `26.1.10909125` from SDK Manager:
Tools -> SDK Manager -> SDK Tools -> NDK (Side by Side)
### Install Rust
Install Rust by following [official instructions](https://www.rust-lang.org/tools/install).
Add Android build targets:
```bash
rustup target add armv7-linux-androideabi
rustup target add i686-linux-android
rustup target add x86_64-linux-android
rustup target add aarch64-linux-android
```
### Update local.properties
Add the following lines to your `local.properties`:
```properties
ACALA_PROD_AUTH_TOKEN=mock
ACALA_TEST_AUTH_TOKEN=mock
CI_KEYSTORE_KEY_ALIAS=mock
CI_KEYSTORE_KEY_PASS=mock
CI_KEYSTORE_PASS=mock
DEBUG_GOOGLE_OAUTH_ID=mock
RELEASE_GOOGLE_OAUTH_ID=mock
DWELLIR_API_KEY=mock
EHTERSCAN_API_KEY_ETHEREUM=mock
EHTERSCAN_API_KEY_MOONBEAM=mock
EHTERSCAN_API_KEY_MOONRIVER=mock
INFURA_API_KEY=mock
MERCURYO_PRODUCTION_SECRET=mock
MERCURYO_TEST_SECRET=mock
MOONBEAM_PROD_AUTH_TOKEN=mock
MOONBEAM_TEST_AUTH_TOKEN=mock
MOONPAY_PRODUCTION_SECRET=mock
MOONPAY_TEST_SECRET=mock
WALLET_CONNECT_PROJECT_ID=mock
```
**Note:** Firebase and Google-related features (Notifications, Cloud Backups) require proper configuration.
### Build Types
- `debug`: Uses fixed keystore for Google services
- `debugLocal`: Uses your local debug keystore
- `release`: Production build
## Supported Languages
- English
- Turkish (Türkçe)
- Kurdish Kurmanji (Kurmancî)
- Spanish (Español)
- French (Français)
- German (Deutsch)
- Russian (Русский)
- Japanese (日本語)
- Chinese (中文)
- Korean (한국어)
- Portuguese (Português)
- Vietnamese (Tiếng Việt)
- And more...
## Resources
- Website: https://pezkuwichain.io
- Documentation: https://docs.pezkuwichain.io
- Telegram: https://t.me/pezkuwichain
- Twitter: https://twitter.com/pezkuwichain
- GitHub: https://github.com/pezkuwichain
## License
Pezkuwi Wallet Android is available under the Apache 2.0 license. See the LICENSE file for more info.
Based on Nova Wallet (https://novawallet.io) - © Novasama Technologies GmbH
© Dijital Kurdistan Tech Institute 2026
+37
View File
@@ -0,0 +1,37 @@
def APP_MODULE = 'app'
subprojects {
if (!file("$projectDir/build.gradle").exists()) {
return
}
if (APP_MODULE == project.name) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply plugin: 'kotlin-android'
apply plugin: "com.google.devtools.ksp"
android {
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
freeCompilerArgs = ["-Xcontext-receivers"]
}
}
}
+5
View File
@@ -0,0 +1,5 @@
/build
/release*
src/release*/google-services.json
!src/release/google-services.json
+341
View File
@@ -0,0 +1,341 @@
apply plugin: 'com.google.gms.google-services'
apply plugin: 'kotlin-parcelize'
apply plugin: 'com.google.firebase.appdistribution'
apply plugin: "com.github.triplet.play"
apply from: "../scripts/versions.gradle"
apply from: "../scripts/secrets.gradle"
android {
defaultConfig {
applicationId rootProject.applicationId
versionCode computeVersionCode()
versionName computeVersionName()
}
signingConfigs {
dev {
storeFile file('develop_key.jks')
storePassword readRawSecretOrNull('CI_KEYSTORE_PASS')
keyAlias readRawSecretOrNull('CI_KEYSTORE_KEY_ALIAS')
keyPassword readRawSecretOrNull('CI_KEYSTORE_KEY_PASS')
}
debugFixedKey {
storeFile file('develop_key.jks')
storePassword readRawSecretOrNull('CI_KEYSTORE_PASS')
keyAlias readRawSecretOrNull('CI_KEYSTORE_KEY_ALIAS')
keyPassword readRawSecretOrNull('CI_KEYSTORE_KEY_PASS')
}
market {
storeFile file('market_key.jks')
storePassword readRawSecretOrNull('CI_MARKET_KEYSTORE_PASS')
keyAlias readRawSecretOrNull('CI_MARKET_KEYSTORE_KEY_ALIAS')
keyPassword readRawSecretOrNull('CI_MARKET_KEYSTORE_KEY_PASS')
}
github {
storeFile file('github_key.jks')
storePassword readRawSecretOrNull('CI_GITHUB_KEYSTORE_PASS')
keyAlias readRawSecretOrNull('CI_GITHUB_KEYSTORE_KEY_ALIAS')
keyPassword readRawSecretOrNull('CI_GITHUB_KEYSTORE_KEY_PASS')
}
}
buildTypes {
debug {
signingConfig signingConfigs.debugFixedKey
applicationIdSuffix '.debug'
versionNameSuffix '-debug'
buildConfigField "String", "BuildType", "\"debug\""
}
debugLocal {
initWith buildTypes.debug
matchingFallbacks = ['debug']
signingConfig signingConfigs.debug
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField "String", "BuildType", "\"release\""
}
releaseTest {
initWith buildTypes.release
matchingFallbacks = ['release']
signingConfig signingConfigs.debug
versionNameSuffix '-releaseTest'
applicationIdSuffix '.releaseTest'
buildConfigField "String", "BuildType", "\"releaseTest\""
}
releaseMarket {
initWith buildTypes.release
matchingFallbacks = ['release']
signingConfig signingConfigs.market
versionNameSuffix "-${releaseApplicationSuffix}"
applicationIdSuffix ".${releaseApplicationSuffix}"
buildConfigField "String", "BuildType", "\"releaseMarket\""
}
releaseGithub {
initWith buildTypes.release
matchingFallbacks = ['release']
signingConfig signingConfigs.github
versionNameSuffix '-github'
applicationIdSuffix '.github'
buildConfigField "String", "BuildType", "\"releaseGithub\""
}
develop {
signingConfig signingConfigs.dev
matchingFallbacks = ['debug']
versionNameSuffix '-develop'
applicationIdSuffix '.dev'
//Init firebase
def localReleaseNotes = releaseNotes()
def localFirebaseGroup = firebaseGroup()
firebaseAppDistribution {
releaseNotes = localReleaseNotes
groups = localFirebaseGroup
}
buildConfigField "String", "BuildType", "\"develop\""
}
instrumentialTest {
initWith buildTypes.debug
matchingFallbacks = ['debug']
defaultConfig.testInstrumentationRunner "io.qameta.allure.android.runners.AllureAndroidJUnitRunner"
buildConfigField "String", "BuildType", "\"instrumentalTest\""
}
}
sourceSets {
releaseGithub {
res.srcDirs = ['src/release/res']
}
releaseMarket {
res.srcDirs = ['src/release/res']
}
releaseTest {
res.srcDirs = ['src/release/res']
}
}
bundle {
language {
enableSplit = false
}
}
applicationVariants.all { variant ->
String name = variant.buildType.name
if (name != "release" && name.startsWith("release")) {
createBindReleaseFileTask(variant.buildType.name)
}
}
packagingOptions {
resources.excludes.add("META-INF/versions/9/previous-compilation-data.bin")
resources.excludes.add("META-INF/DEPENDENCIES")
resources.excludes.add("META-INF/NOTICE.md")
}
buildFeatures {
viewBinding true
}
namespace 'io.novafoundation.nova.app'
}
void createBindReleaseFileTask(String destination) {
String taskName = "bind${destination.capitalize()}GithubGoogleServicesToRelease"
Task task = task(taskName, type: Copy) {
description = "Switches to RELEASE google-services.json for ${destination}"
from "src/release"
include "google-services.json"
into "src/${destination}"
}
afterEvaluate {
def capitalizedDestination = destination.capitalize()
def dependentTasks = [
"process${capitalizedDestination}GoogleServices".toString(),
"merge${capitalizedDestination}JniLibFolders".toString(),
"merge${capitalizedDestination}StartupProfile".toString(),
"merge${capitalizedDestination}Shaders".toString(),
"merge${capitalizedDestination}ArtProfile".toString()
]
dependentTasks.forEach {
tasks.getByName(it).dependsOn(task)
}
}
}
play {
serviceAccountCredentials = file(System.env.CI_PLAY_KEY ?: "../key/fake.json")
track = "beta"
releaseStatus = "completed"
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':core-db')
implementation project(':common')
implementation project(':feature-splash')
implementation project(':feature-onboarding-api')
implementation project(':feature-onboarding-impl')
implementation project(':feature-ledger-api')
implementation project(':feature-ledger-core')
implementation project(':feature-ledger-impl')
implementation project(':feature-account-api')
implementation project(':feature-account-impl')
implementation project(':feature-account-migration')
implementation project(':feature-wallet-api')
implementation project(':feature-wallet-impl')
implementation project(':runtime')
implementation project(':web3names')
implementation project(':feature-staking-api')
implementation project(':feature-staking-impl')
implementation project(':feature-crowdloan-api')
implementation project(':feature-crowdloan-impl')
implementation project(':feature-dapp-api')
implementation project(':feature-dapp-impl')
implementation project(':feature-nft-api')
implementation project(':feature-nft-impl')
implementation project(':feature-currency-api')
implementation project(':feature-currency-impl')
implementation project(':feature-governance-api')
implementation project(':feature-governance-impl')
implementation project(':feature-assets')
implementation project(':feature-vote')
implementation project(':feature-versions-api')
implementation project(':feature-versions-impl')
implementation project(':caip')
implementation project(':feature-external-sign-api')
implementation project(':feature-external-sign-impl')
implementation project(':feature-wallet-connect-api')
implementation project(':feature-wallet-connect-impl')
implementation project(':feature-proxy-api')
implementation project(':feature-proxy-impl')
implementation project(':feature-settings-api')
implementation project(':feature-settings-impl')
implementation project(":feature-swap-core")
implementation project(':feature-swap-api')
implementation project(':feature-swap-impl')
implementation project(":feature-buy-api")
implementation project(":feature-buy-impl")
implementation project(':feature-push-notifications')
implementation project(':feature-deep-linking')
implementation project(':feature-cloud-backup-api')
implementation project(':feature-cloud-backup-impl')
implementation project(':feature-banners-api')
implementation project(':feature-banners-impl')
implementation project(':feature-ahm-api')
implementation project(':feature-ahm-impl')
implementation project(':feature-gift-api')
implementation project(':feature-gift-impl')
implementation project(':bindings:metadata_shortener')
implementation project(":feature-xcm:impl")
implementation project(":feature-multisig:operations")
implementation project(':test-shared')
implementation kotlinDep
implementation biometricDep
implementation androidDep
implementation constraintDep
implementation zXingEmbeddedDep
implementation navigationFragmentDep
implementation navigationUiDep
implementation roomDep
implementation substrateSdkDep
implementation daggerDep
ksp daggerCompiler
implementation lifecycleDep
ksp lifecycleCompiler
implementation lifeCycleKtxDep
implementation retrofitDep
implementation gsonConvertedDep
implementation gifDep
compileOnly wsDep
implementation coroutinesDep
testImplementation project(':test-shared')
implementation insetterDep
implementation liveDataKtxDep
implementation platform(firebaseBomDep)
implementation firestoreDep
implementation firebaseCloudMessagingDep
implementation firebaseAppCheck
implementation walletConnectCoreDep, withoutTransitiveAndroidX
implementation walletConnectWalletDep, withoutTransitiveAndroidX
kspAndroidTest daggerCompiler
androidTestImplementation androidTestRunnerDep
androidTestImplementation androidTestRulesDep
androidTestImplementation androidJunitDep
androidTestImplementation allureKotlinModel
androidTestImplementation allureKotlinCommons
androidTestImplementation allureKotlinJunit4
androidTestImplementation allureKotlinAndroid
}
task printVersion {
doLast {
println "versionName:${computeVersionName()}"
}
}
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,50 @@
package io.novafoundation.nova
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.withChildScope
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeComponent
import io.novafoundation.nova.runtime.ext.Geneses
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
open class BaseIntegrationTest {
protected val context: Context = ApplicationProvider.getApplicationContext()
protected val runtimeApi = FeatureUtils.getFeature<RuntimeComponent>(context, RuntimeApi::class.java)
val chainRegistry = runtimeApi.chainRegistry()
private val externalRequirementFlow = runtimeApi.externalRequirementFlow()
@Before
fun setup() = runBlocking {
externalRequirementFlow.emit(ChainConnection.ExternalRequirement.ALLOWED)
}
protected fun runTest(action: suspend CoroutineScope.() -> Unit) {
runBlocking {
withChildScope {
action()
}
}
}
protected suspend fun ChainRegistry.polkadot(): Chain {
return getChain(Chain.Geneses.POLKADOT)
}
protected suspend fun ChainRegistry.polkadotAssetHub(): Chain {
return getChain(Chain.Geneses.POLKADOT_ASSET_HUB)
}
}
@@ -0,0 +1,60 @@
package io.novafoundation.nova
import android.content.Context
import android.util.Log
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.novafoundation.nova.common.data.network.runtime.binding.bindEventRecords
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.common.utils.system
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeComponent
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import io.novasama.substrate_sdk_android.runtime.metadata.storageKey
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class BlockParsingIntegrationTest {
private val chainGenesis = "f1cf9022c7ebb34b162d5b5e34e705a5a740b2d0ecc1009fb89023e62a488108" // Shiden
private val runtimeApi = FeatureUtils.getFeature<RuntimeComponent>(
ApplicationProvider.getApplicationContext<Context>(),
RuntimeApi::class.java
)
private val chainRegistry = runtimeApi.chainRegistry()
private val externalRequirementFlow = runtimeApi.externalRequirementFlow()
private val rpcCalls = runtimeApi.rpcCalls()
private val remoteStorage = runtimeApi.remoteStorageSource()
@Test
fun testBlockParsing() = runBlocking {
externalRequirementFlow.emit(ChainConnection.ExternalRequirement.ALLOWED)
val chain = chainRegistry.getChain(chainGenesis)
val block = rpcCalls.getBlock(chain.id)
val logTag = this@BlockParsingIntegrationTest.LOG_TAG
Log.d(logTag, block.block.header.number.toString())
val events = remoteStorage.query(
chainId = chain.id,
keyBuilder = { it.metadata.system().storage("Events").storageKey() },
binding = { scale, runtime ->
Log.d(logTag, scale!!)
bindEventRecords(scale)
}
)
// val eventsRaw = "0x0800000000000000000000000000000002000000010000000000585f8f0900000000020000"
// val type = bindEventRecords(eventsRaw, chainRegistry.getRuntime(chain.id))
}
}
@@ -0,0 +1,129 @@
package io.novafoundation.nova
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.gson.Gson
import androidx.test.platform.app.InstrumentationRegistry
import dagger.Component
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.core_db.model.chain.ChainLocal
import io.novafoundation.nova.core_db.model.chain.NodeSelectionPreferencesLocal
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.RemoteToDomainChainMapperFacade
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainAssetToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainExplorerToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainExternalApiToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainLocalToChain
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainNodeToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapChainToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapExternalApisToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapNodeSelectionPreferencesToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapRemoteAssetToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapRemoteChainToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapRemoteExplorersToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.mappers.mapRemoteNodesToLocal
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.remote.ChainFetcher
import io.novafoundation.nova.runtime.multiNetwork.chain.remote.model.ChainRemote
import io.novafoundation.nova.test_shared.assertAllItemsEquals
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@Component(
dependencies = [
CommonApi::class,
RuntimeApi::class
]
)
interface MappingTestAppComponent {
fun inject(test: ChainMappingIntegrationTest)
}
@RunWith(AndroidJUnit4::class)
class ChainMappingIntegrationTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
private val featureContainer = context as FeatureContainer
@Inject
lateinit var networkApiCreator: NetworkApiCreator
@Inject
lateinit var remoteToDomainChainMapperFacade: RemoteToDomainChainMapperFacade
lateinit var chainFetcher: ChainFetcher
private val gson = Gson()
@Before
fun prepare() {
val component = DaggerMappingTestAppComponent.builder()
.commonApi(featureContainer.commonApi())
.runtimeApi(featureContainer.getFeature(RuntimeApi::class.java))
.build()
component.inject(this)
chainFetcher = networkApiCreator.create(ChainFetcher::class.java)
}
@Test
fun testChainMappingIsMatch() {
runBlocking {
val chainsRemote = chainFetcher.getChains()
val remoteToDomain = chainsRemote.map { mapRemoteToDomain(it) }
val remoteToLocalToDomain = chainsRemote.map { mapRemoteToLocalToDomain(it) }
val domainToLocalToDomain = remoteToDomain.map { mapDomainToLocalToDomain(it) }
assertAllItemsEquals(listOf(remoteToDomain, remoteToLocalToDomain, domainToLocalToDomain))
}
}
private fun mapRemoteToLocalToDomain(chainRemote: ChainRemote): Chain {
val chainLocal = mapRemoteChainToLocal(chainRemote, null, ChainLocal.Source.DEFAULT, gson)
val assetsLocal = chainRemote.assets.map { mapRemoteAssetToLocal(chainRemote, it, gson, isEnabled = true) }
val nodesLocal = mapRemoteNodesToLocal(chainRemote)
val explorersLocal = mapRemoteExplorersToLocal(chainRemote)
val externalApisLocal = mapExternalApisToLocal(chainRemote)
return mapChainLocalToChain(
chainLocal = chainLocal,
nodesLocal = nodesLocal,
nodeSelectionPreferences = NodeSelectionPreferencesLocal(chainLocal.id, autoBalanceEnabled = true, selectedNodeUrl = null),
assetsLocal = assetsLocal,
explorersLocal = explorersLocal,
externalApisLocal = externalApisLocal,
gson = gson
)
}
private fun mapRemoteToDomain(chainRemote: ChainRemote): Chain {
return remoteToDomainChainMapperFacade.mapRemoteChainToDomain(chainRemote, Chain.Source.DEFAULT)
}
private fun mapDomainToLocalToDomain(chain: Chain): Chain {
val chainLocal = mapChainToLocal(chain, gson)
val nodesLocal = chain.nodes.nodes.map { mapChainNodeToLocal(it) }
val nodeSelectionPreferencesLocal = mapNodeSelectionPreferencesToLocal(chain)
val assetsLocal = chain.assets.map { mapChainAssetToLocal(it, gson) }
val explorersLocal = chain.explorers.map { mapChainExplorerToLocal(it) }
val externalApisLocal = chain.externalApis.map { mapChainExternalApiToLocal(gson, chain.id, it) }
return mapChainLocalToChain(
chainLocal = chainLocal,
nodesLocal = nodesLocal,
nodeSelectionPreferences = nodeSelectionPreferencesLocal,
assetsLocal = assetsLocal,
explorersLocal = explorersLocal,
externalApisLocal = externalApisLocal,
gson = gson
)
}
}
@@ -0,0 +1,61 @@
package io.novafoundation.nova
import androidx.room.Room
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.google.gson.Gson
import dagger.Component
import io.novafoundation.nova.common.data.network.NetworkApiCreator
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.core_db.AppDatabase
import io.novafoundation.nova.runtime.multiNetwork.chain.ChainSyncService
import io.novafoundation.nova.runtime.multiNetwork.chain.remote.ChainFetcher
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import javax.inject.Inject
@Component(
dependencies = [
CommonApi::class,
]
)
interface TestAppComponent {
fun inject(test: ChainSyncServiceIntegrationTest)
}
@RunWith(AndroidJUnit4::class)
class ChainSyncServiceIntegrationTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
private val featureContainer = context as FeatureContainer
@Inject
lateinit var networkApiCreator: NetworkApiCreator
lateinit var chainSyncService: ChainSyncService
@Before
fun setup() {
val component = DaggerTestAppComponent.builder()
.commonApi(featureContainer.commonApi())
.build()
component.inject(this)
val chainDao = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java)
.build()
.chainDao()
chainSyncService = ChainSyncService(chainDao, networkApiCreator.create(ChainFetcher::class.java), Gson())
}
@Test
fun shouldFetchAndStoreRealChains() = runBlocking {
chainSyncService.syncUp()
}
}
@@ -0,0 +1,98 @@
package io.novafoundation.nova
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import io.novafoundation.nova.common.address.intoKey
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.emptySubstrateAccountId
import io.novafoundation.nova.feature_account_api.data.fee.FeePaymentCurrency
import io.novafoundation.nova.feature_account_api.domain.model.toDefaultSubstrateAddress
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransferBase
import io.novafoundation.nova.feature_wallet_api.data.repository.getXcmChain
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.feature_wallet_api.domain.model.xcm.legacy.transferConfiguration
import io.novafoundation.nova.feature_wallet_api.domain.model.xcm.transferConfiguration
import io.novafoundation.nova.runtime.ext.addressOf
import io.novafoundation.nova.runtime.ext.emptyAccountId
import io.novafoundation.nova.runtime.ext.normalizeTokenSymbol
import io.novafoundation.nova.runtime.multiNetwork.findChain
import kotlinx.coroutines.runBlocking
import org.junit.Test
import java.math.BigInteger
class CrossChainTransfersIntegrationTest : BaseIntegrationTest() {
private val walletApi = FeatureUtils.getFeature<WalletFeatureApi>(
ApplicationProvider.getApplicationContext<Context>(),
WalletFeatureApi::class.java
)
private val chainTransfersRepository = walletApi.crossChainTransfersRepository
private val crossChainWeigher = walletApi.crossChainWeigher
private val parachainInfoRepository = runtimeApi.parachainInfoRepository
@Test
fun testParachainToParachain() = performFeeTest(
from = "Moonriver",
what = "xcKAR",
to = "Karura"
)
@Test
fun testRelaychainToParachain() = performFeeTest(
from = "Kusama",
what = "KSM",
to = "Moonriver"
)
@Test
fun testParachainToRelaychain() = performFeeTest(
from = "Moonriver",
what = "xcKSM",
to = "Kusama"
)
@Test
fun testParachainToParachainNonReserve() = performFeeTest(
from = "Karura",
what = "BNC",
to = "Moonriver"
)
private fun performFeeTest(
from: String,
to: String,
what: String
) {
runBlocking {
val originChain = chainRegistry.findChain { it.name == from }!!
val asssetInOrigin = originChain.assets.first { it.symbol.value == what }
val destinationChain = chainRegistry.findChain { it.name == to }!!
val asssetInDestination = destinationChain.assets.first { normalizeTokenSymbol(it.symbol.value) == normalizeTokenSymbol(what) }
val crossChainConfig = chainTransfersRepository.getConfiguration()
val crossChainTransfer = crossChainConfig.transferConfiguration(
originChain = parachainInfoRepository.getXcmChain(originChain),
originAsset = asssetInOrigin,
destinationChain = parachainInfoRepository.getXcmChain(destinationChain),
)!!
val transfer = AssetTransferBase(
recipient = originChain.addressOf(originChain.emptyAccountId()),
originChain = originChain,
originChainAsset = asssetInOrigin,
destinationChain = destinationChain,
destinationChainAsset = asssetInDestination,
feePaymentCurrency = FeePaymentCurrency.Native,
amountPlanks = BigInteger.ZERO
)
val crossChainFeeResult = runCatching { crossChainWeigher.estimateFee(transfer, crossChainTransfer) }
check(crossChainFeeResult.isSuccess)
}
}
}
@@ -0,0 +1,100 @@
package io.novafoundation.nova
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import io.novafoundation.nova.common.address.intoKey
import io.novafoundation.nova.common.data.network.runtime.binding.toResult
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.composeCall
import io.novafoundation.nova.common.utils.xcmPalletName
import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount
import io.novafoundation.nova.feature_xcm_api.asset.MultiAsset
import io.novafoundation.nova.feature_xcm_api.asset.MultiAssets
import io.novafoundation.nova.feature_xcm_api.di.XcmFeatureApi
import io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model.OriginCaller
import io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model.getByLocation
import io.novafoundation.nova.feature_xcm_api.multiLocation.Junctions
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Junction.ParachainId
import io.novafoundation.nova.feature_xcm_api.multiLocation.asLocation
import io.novafoundation.nova.feature_xcm_api.multiLocation.toMultiLocation
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
import io.novafoundation.nova.feature_xcm_api.versions.toEncodableInstance
import io.novafoundation.nova.feature_xcm_api.versions.versionedXcm
import io.novafoundation.nova.feature_xcm_api.weight.WeightLimit
import io.novafoundation.nova.runtime.ext.utilityAsset
import io.novafoundation.nova.runtime.multiNetwork.getRuntime
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
import org.junit.Test
import java.math.BigDecimal
import java.math.BigInteger
class DryRunIntegrationTest : BaseIntegrationTest() {
private val xcmApi = FeatureUtils.getFeature<XcmFeatureApi>(
ApplicationProvider.getApplicationContext<Context>(),
XcmFeatureApi::class.java
)
private val dryRunApi = xcmApi.dryRunApi
@Test
fun testDryRunXcmTeleport() = runTest {
val polkadot = chainRegistry.polkadot()
val polkadotAssetHub = chainRegistry.polkadotAssetHub()
val polkadotRuntime = chainRegistry.getRuntime(polkadot.id)
val polkadotLocation = MultiLocation.Interior.Here.asLocation()
val polkadotAssetHubLocation = Junctions(ParachainId(1000)).asLocation()
val dotLocation = polkadotLocation.toRelative()
val amount = polkadot.utilityAsset.planksFromAmount(BigDecimal.ONE)
val assets = MultiAsset.from(dotLocation, amount)
val origin = "16WWmr2Xqgy5fna35GsNHXMU7vDBM12gzHCFGibQjSmKpAN".toAccountId().intoKey()
val beneficiary = origin.toMultiLocation()
val xcmVersion = XcmVersion.V4
val pahVersionedLocation = polkadotAssetHubLocation.toRelative().versionedXcm(xcmVersion)
// Compose limited_teleport_assets call to execute on Polkadot
val call = polkadotRuntime.composeCall(
moduleName = polkadotRuntime.metadata.xcmPalletName(),
callName = "limited_teleport_assets",
arguments = mapOf(
"dest" to pahVersionedLocation.toEncodableInstance(),
"beneficiary" to beneficiary.versionedXcm(xcmVersion).toEncodableInstance(),
"assets" to MultiAssets(assets).versionedXcm(xcmVersion).toEncodableInstance(),
"fee_asset_item" to BigInteger.ZERO,
"weight_limit" to WeightLimit.Unlimited.toEncodableInstance()
)
)
// Dry run call execution
val dryRunEffects = dryRunApi.dryRunCall(
originCaller = OriginCaller.System.Signed(origin),
call = call,
chainId = polkadot.id,
xcmResultsVersion = XcmVersion.V4
)
.getOrThrow()
.toResult().getOrThrow()
// Find xcm forwarded to Polkadot Asset Hub
val forwardedXcm = dryRunEffects.forwardedXcms.getByLocation(pahVersionedLocation).first()
println(forwardedXcm)
// Dry run execution of forwarded message on Polkadot Asset Hub
val xcmDryRunEffects = dryRunApi.dryRunXcm(
xcm = forwardedXcm,
originLocation = polkadotLocation.fromPointOfViewOf(polkadotAssetHubLocation).versionedXcm(xcmVersion),
chainId = polkadotAssetHub.id
)
.getOrThrow()
.toResult().getOrThrow()
println(xcmDryRunEffects.emittedEvents)
}
}
@@ -0,0 +1,52 @@
package io.novafoundation.nova
import android.util.Log
import io.novafoundation.nova.common.utils.average
import io.novafoundation.nova.common.utils.divideToDecimal
import io.novafoundation.nova.runtime.ethereum.gas.LegacyGasPriceProvider
import io.novafoundation.nova.runtime.ethereum.gas.MaxPriorityFeeGasProvider
import io.novafoundation.nova.runtime.ext.Ids
import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take
import org.junit.Test
import java.math.BigInteger
class GasPriceProviderIntegrationTest : BaseIntegrationTest() {
@Test
fun compareLegacyAndImprovedGasPriceEstimations() = runTest {
val api = chainRegistry.getCallEthereumApiOrThrow(Chain.Ids.MOONBEAM)
val legacy = LegacyGasPriceProvider(api)
val improved = MaxPriorityFeeGasProvider(api)
val legacyStats = mutableSetOf<BigInteger>()
val improvedStats = mutableSetOf<BigInteger>()
api.newHeadsFlow().map {
legacyStats.add(legacy.getGasPrice())
improvedStats.add(improved.getGasPrice())
}
.take(1000)
.collect()
legacyStats.printStats("Legacy")
improvedStats.printStats("Improved")
}
private fun Set<BigInteger>.printStats(name: String) {
val min = min()
val max = max()
Log.d("GasPriceProviderIntegrationTest", """
Stats for $name source
Min: $min
Max: $max
Avg: ${average()}
Max/Min ratio: ${max.divideToDecimal(min)}
""")
}
}
@@ -0,0 +1,247 @@
package io.novafoundation.nova
import android.util.Log
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.common.utils.firstLoaded
import io.novafoundation.nova.common.utils.inBackground
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId
import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.VoteType
import io.novafoundation.nova.feature_governance_api.data.source.SupportedGovernanceOption
import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi
import io.novafoundation.nova.feature_governance_api.domain.referendum.filters.ReferendumType
import io.novafoundation.nova.feature_governance_api.domain.referendum.filters.ReferendumTypeFilter
import io.novafoundation.nova.feature_governance_impl.data.RealGovernanceAdditionalState
import io.novafoundation.nova.runtime.ext.externalApi
import io.novafoundation.nova.runtime.ext.utilityAsset
import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import org.junit.Test
import java.math.BigInteger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
class GovernanceIntegrationTest : BaseIntegrationTest() {
private val accountApi = FeatureUtils.getFeature<AccountFeatureApi>(context, AccountFeatureApi::class.java)
private val governanceApi = FeatureUtils.getFeature<GovernanceFeatureApi>(context, GovernanceFeatureApi::class.java)
@Test
fun shouldRetrieveOnChainReferenda() = runTest {
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val onChainReferendaRepository = source(selectedGovernance).referenda
val referenda = onChainReferendaRepository.getAllOnChainReferenda(chain.id)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, referenda.toString())
}
@Test
fun shouldRetrieveConvictionVotes() = runTest {
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val convictionVotingRepository = source(selectedGovernance).convictionVoting
val accountId = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".toAccountId()
val votes = convictionVotingRepository.votingFor(accountId, chain.id)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, votes.toString())
}
@Test
fun shouldRetrieveTrackLocks() = runTest {
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val convictionVotingRepository = source(selectedGovernance).convictionVoting
val accountId = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".toAccountId()
val fullChainAssetId = FullChainAssetId(chain.id, chain.utilityAsset.id)
val trackLocks = convictionVotingRepository.trackLocksFlow(accountId, fullChainAssetId).first()
Log.d(this@GovernanceIntegrationTest.LOG_TAG, trackLocks.toString())
}
@Test
fun shouldRetrieveReferendaTracks() = runTest {
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val onChainReferendaRepository = source(selectedGovernance).referenda
val tracks = onChainReferendaRepository.getTracks(chain.id)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, tracks.toString())
}
@Test
fun shouldRetrieveDomainReferendaPreviews() = runTest {
val accountRepository = accountApi.provideAccountRepository()
val referendaListInteractor = governanceApi.referendaListInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val metaAccount = accountRepository.getSelectedMetaAccount()
val accountId = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".toAccountId()
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val filterFlow: Flow<ReferendumTypeFilter> = flow {
val referendaFilter = ReferendumTypeFilter(ReferendumType.ALL)
emit(referendaFilter)
}
val referendaByGroup = referendaListInteractor.referendaListStateFlow(metaAccount, accountId, selectedGovernance, this, filterFlow).firstLoaded()
val referenda = referendaByGroup.groupedReferenda.values.flatten()
Log.d(this@GovernanceIntegrationTest.LOG_TAG, referenda.joinToString("\n"))
}
@Test
fun shouldRetrieveDomainReferendumDetails() = runTest {
val referendumDetailsInteractor = governanceApi.referendumDetailsInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val accountId = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".toAccountId()
val referendumId = ReferendumId(BigInteger.ZERO)
val chain = chain()
val selectedGovernance = supportedGovernanceOption(chain, Chain.Governance.V1)
val referendumDetails = referendumDetailsInteractor.referendumDetailsFlow(referendumId, selectedGovernance, accountId, CoroutineScope(Dispatchers.Main))
.first()
Log.d(this@GovernanceIntegrationTest.LOG_TAG, referendumDetails.toString())
val callDetails = referendumDetailsInteractor.detailsFor(
preImage = referendumDetails?.onChainMetadata!!.preImage!!,
chain = chain
)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, callDetails.toString())
}
@Test
fun shouldRetrieveVoters() = runTest {
val interactor = governanceApi.referendumVotersInteractor
val referendumId = ReferendumId(BigInteger.ZERO)
val referendumVoters = interactor.votersFlow(referendumId, VoteType.AYE)
.first()
Log.d(this@GovernanceIntegrationTest.LOG_TAG, referendumVoters.toString())
}
@Test
fun shouldFetchDelegatesList() = runTest {
val interactor = governanceApi.delegateListInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val chain = kusama()
val delegates = interactor.getDelegates(
governanceOption = supportedGovernanceOption(chain, Chain.Governance.V2),
scope = this
)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, delegates.toString())
}
@Test
fun shouldFetchDelegateDetails() = runTest {
val delegateAccountId = "DCZyhphXsRLcW84G9WmWEXtAA8DKGtVGSFZLJYty8Ajjyfa".toAccountId() // ChaosDAO
val interactor = governanceApi.delegateDetailsInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val delegate = interactor.delegateDetailsFlow(delegateAccountId)
Log.d(this@GovernanceIntegrationTest.LOG_TAG, delegate.toString())
}
@Test
fun shouldFetchChooseTrackData() = runTest {
val interactor = governanceApi.newDelegationChooseTrackInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val trackData = interactor.observeNewDelegationTrackData().first()
Log.d(
this@GovernanceIntegrationTest.LOG_TAG,
"""
Available: ${trackData.trackPartition.available.size}
Already voted: ${trackData.trackPartition.alreadyVoted.size}
Already delegated: ${trackData.trackPartition.alreadyDelegated.size}
Presets: ${trackData.presets}
""".trimIndent()
)
}
@Test
fun shouldFetchDelegators() = runTest {
val delegateAddress = "DCZyhphXsRLcW84G9WmWEXtAA8DKGtVGSFZLJYty8Ajjyfa" // ChaosDAO
val interactor = governanceApi.delegateDelegatorsInteractor
val updateSystem = governanceApi.governanceUpdateSystem
updateSystem.start()
.inBackground()
.launchIn(this)
val delegators = interactor.delegatorsFlow(delegateAddress.toAccountId()).first()
Log.d(this@GovernanceIntegrationTest.LOG_TAG, delegators.toString())
}
private suspend fun source(supportedGovernance: SupportedGovernanceOption) = governanceApi.governanceSourceRegistry.sourceFor(supportedGovernance)
private fun supportedGovernanceOption(chain: Chain, governance: Chain.Governance) =
SupportedGovernanceOption(
ChainWithAsset(chain, chain.utilityAsset),
RealGovernanceAdditionalState(
governance,
false
)
)
private suspend fun chain(): Chain = chainRegistry.currentChains.map { chains ->
chains.find { it.governance.isNotEmpty() }
}
.filterNotNull()
.first()
private suspend fun kusama(): Chain = chainRegistry.currentChains.mapNotNull { chains ->
chains.find { it.externalApi<Chain.ExternalApi.GovernanceDelegations>() != null }
}.first()
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,60 @@
package io.novafoundation.nova
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_nft_api.NftFeatureApi
import io.novafoundation.nova.feature_nft_api.data.model.isFullySynced
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeComponent
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.runBlocking
import org.junit.Test
class NftFullSyncIntegrationTest {
private val nftApi = FeatureUtils.getFeature<NftFeatureApi>(
ApplicationProvider.getApplicationContext<Context>(),
NftFeatureApi::class.java
)
private val accountApi = FeatureUtils.getFeature<AccountFeatureApi>(
ApplicationProvider.getApplicationContext<Context>(),
AccountFeatureApi::class.java
)
private val runtimeApi = FeatureUtils.getFeature<RuntimeComponent>(
ApplicationProvider.getApplicationContext<Context>(),
RuntimeApi::class.java
)
private val externalRequirementFlow = runtimeApi.externalRequirementFlow()
@Test
fun testFullSyncIntegration(): Unit = runBlocking {
externalRequirementFlow.emit(ChainConnection.ExternalRequirement.ALLOWED)
val metaAccount = accountApi.accountUseCase().getSelectedMetaAccount()
val nftRepository = nftApi.nftRepository
nftRepository.initialNftSync(metaAccount, true)
nftRepository.allNftFlow(metaAccount)
.map { nfts -> nfts.filter { !it.isFullySynced } }
.takeWhile { it.isNotEmpty() }
.onEach { unsyncedNfts ->
unsyncedNfts.forEach { nftRepository.fullNftSync(it) }
}
.onCompletion {
print("Full sync done")
}
.launchIn(this)
}
}
@@ -0,0 +1,181 @@
package io.novafoundation.nova
import android.content.Context
import android.util.Log
import androidx.test.core.app.ApplicationProvider
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId
import io.novafoundation.nova.common.data.network.runtime.binding.bindBoolean
import io.novafoundation.nova.common.data.network.runtime.binding.bindString
import io.novafoundation.nova.common.data.network.runtime.binding.cast
import io.novafoundation.nova.common.data.network.runtime.binding.getTyped
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.common.utils.uniques
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeComponent
import io.novafoundation.nova.runtime.ext.addressOf
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder
import io.novafoundation.nova.runtime.storage.source.query.multi
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Test
import java.math.BigInteger
data class UniquesClass(
val id: BigInteger,
val metadata: Metadata?,
val details: Details
) {
data class Metadata(
val deposit: BigInteger,
val data: String,
)
data class Details(
val instances: BigInteger,
val frozen: Boolean
)
}
data class UniquesInstance(
val collection: UniquesClass,
val id: BigInteger,
val metadata: Metadata?,
val details: Details
) {
data class Metadata(
val data: String,
)
data class Details(
val owner: String,
val frozen: Boolean,
)
}
class NftUniquesIntegrationTest {
private val chainGenesis = "48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a"
private val runtimeApi = FeatureUtils.getFeature<RuntimeComponent>(
ApplicationProvider.getApplicationContext<Context>(),
RuntimeApi::class.java
)
private val chainRegistry = runtimeApi.chainRegistry()
private val externalRequirementFlow = runtimeApi.externalRequirementFlow()
private val storageRemoteSource = runtimeApi.remoteStorageSource()
@Test
fun testUniquesIntegration(): Unit = runBlocking {
chainRegistry.currentChains.first() // wait till chains are ready
externalRequirementFlow.emit(ChainConnection.ExternalRequirement.ALLOWED)
val chain = chainRegistry.getChain(chainGenesis)
val accountId = "JGKSibhyZgzY7jEe5a9gdybDEbqNNRSxYyJJmeeycbCbQ5v".toAccountId()
val instances = storageRemoteSource.query(chainGenesis) {
val classesWithInstances = runtime.metadata.uniques().storage("Account").keys(accountId)
.map { (_: AccountId, collection: BigInteger, instance: BigInteger) ->
listOf(collection, instance)
}
val classesIds = classesWithInstances.map { (collection, _) -> collection }.distinct()
val classDetailsDescriptor: MultiQueryBuilder.Descriptor<BigInteger, UniquesClass.Details>
val classMetadatasDescriptor: MultiQueryBuilder.Descriptor<BigInteger, UniquesClass.Metadata?>
val instancesDetailsDescriptor: MultiQueryBuilder.Descriptor<Pair<BigInteger, BigInteger>, UniquesInstance.Details>
val instancesMetadataDescriptor: MultiQueryBuilder.Descriptor<Pair<BigInteger, BigInteger>, UniquesInstance.Metadata?>
val multiQueryResults = multi {
classDetailsDescriptor = runtime.metadata.uniques().storage("Class").querySingleArgKeys(
keysArgs = classesIds,
keyExtractor = { it.component1<BigInteger>() },
binding = { parsedValue ->
val classDetailsStruct = parsedValue.cast<Struct.Instance>()
UniquesClass.Details(
instances = classDetailsStruct.getTyped("instances"),
frozen = classDetailsStruct.getTyped("isFrozen")
)
}
)
classMetadatasDescriptor = runtime.metadata.uniques().storage("ClassMetadataOf").querySingleArgKeys(
keysArgs = classesIds,
keyExtractor = { it.component1<BigInteger>() },
binding = { parsedValue ->
parsedValue?.cast<Struct.Instance>()?.let { classMetadataStruct ->
UniquesClass.Metadata(
deposit = classMetadataStruct.getTyped("deposit"),
data = bindString(classMetadataStruct["data"])
)
}
}
)
instancesDetailsDescriptor = runtime.metadata.uniques().storage("Asset").queryKeys(
keysArgs = classesWithInstances,
keyExtractor = { it.component1<BigInteger>() to it.component2<BigInteger>() },
binding = { parsedValue ->
val instanceDetailsStruct = parsedValue.cast<Struct.Instance>()
UniquesInstance.Details(
owner = chain.addressOf(bindAccountId(instanceDetailsStruct["owner"])),
frozen = bindBoolean(instanceDetailsStruct["isFrozen"])
)
}
)
instancesMetadataDescriptor = runtime.metadata.uniques().storage("InstanceMetadataOf").queryKeys(
keysArgs = classesWithInstances,
keyExtractor = { it.component1<BigInteger>() to it.component2<BigInteger>() },
binding = { parsedValue ->
parsedValue?.cast<Struct.Instance>()?.let {
UniquesInstance.Metadata(
data = bindString(it["data"])
)
}
}
)
}
val classDetails = multiQueryResults[classDetailsDescriptor]
val classMetadatas = multiQueryResults[classMetadatasDescriptor]
val instancesDetails = multiQueryResults[instancesDetailsDescriptor]
val instancesMetadatas = multiQueryResults[instancesMetadataDescriptor]
val classes = classesIds.associateWith { classId ->
UniquesClass(
id = classId,
metadata = classMetadatas[classId],
details = classDetails.getValue(classId)
)
}
classesWithInstances.map { (collectionId, instanceId) ->
val instanceKey = collectionId to instanceId
UniquesInstance(
collection = classes.getValue(collectionId),
id = instanceId,
metadata = instancesMetadatas[instanceKey],
details = instancesDetails.getValue(instanceKey)
)
}
}
Log.d(LOG_TAG, instances.toString())
}
}
@@ -0,0 +1,49 @@
package io.novafoundation.nova
import android.util.Log
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.Fraction
import io.novafoundation.nova.common.utils.Perbill
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.feature_staking_api.domain.nominationPool.model.PoolId
import io.novafoundation.nova.feature_staking_impl.data.StakingOption
import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState.OptionAdditionalData
import io.novafoundation.nova.feature_staking_impl.di.StakingFeatureComponent
import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.rewards.NominationPoolRewardCalculator
import io.novafoundation.nova.runtime.ext.utilityAsset
import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Asset.StakingType
import kotlinx.coroutines.flow.launchIn
import org.junit.Test
class NominationPoolsRewardCalculatorIntegrationTest : BaseIntegrationTest() {
private val stakingFeatureComponent = FeatureUtils.getFeature<StakingFeatureComponent>(context, StakingFeatureApi::class.java)
private val nominationPoolRewardCalculatorFactory = stakingFeatureComponent.nominationPoolRewardCalculatorFactory
private val stakingUpdateSystem = stakingFeatureComponent.stakingUpdateSystem
private val stakingSharedState = stakingFeatureComponent.stakingSharedState
@Test
fun testRewardCalculator() = runTest {
val polkadot = chainRegistry.polkadot()
val stakingOption = StakingOption(
assetWithChain = ChainWithAsset(polkadot, polkadot.utilityAsset),
additional = OptionAdditionalData(StakingType.NOMINATION_POOLS)
)
stakingSharedState.setSelectedOption(stakingOption)
stakingUpdateSystem.start()
.launchIn(this)
val rewardCalculator = nominationPoolRewardCalculatorFactory.create(stakingOption, sharedComputationScope = this)
Log.d("NominationPoolsRewardCalculatorIntegrationTest", "Max APY: ${rewardCalculator.maxAPY}")
Log.d("NominationPoolsRewardCalculatorIntegrationTest", "APY for Nova Pool: ${rewardCalculator.apyFor(54)}")
}
private fun NominationPoolRewardCalculator.apyFor(poolId: Int): Fraction? {
return apyFor(PoolId(poolId))
}
}
@@ -0,0 +1,71 @@
package io.novafoundation.nova
import android.util.Log
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.domain.ExtendedLoadingState
import io.novafoundation.nova.common.utils.inBackground
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.AggregatedStakingDashboardOption
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.StakingDashboard
import io.novafoundation.nova.feature_staking_api.domain.dashboard.model.isSyncing
import kotlinx.coroutines.flow.launchIn
import org.junit.Test
import java.lang.reflect.Type
class StakingDashboardIntegrationTest: BaseIntegrationTest() {
private val stakingApi = FeatureUtils.getFeature<StakingFeatureApi>(context, StakingFeatureApi::class.java)
private val interactor = stakingApi.dashboardInteractor
private val updateSystem = stakingApi.dashboardUpdateSystem
private val gson = GsonBuilder()
.registerTypeHierarchyAdapter(AggregatedStakingDashboardOption::class.java, AggregatedStakingDashboardOptionDesirializer())
.create()
@Test
fun syncStakingDashboard() = runTest {
updateSystem.start()
.inBackground()
.launchIn(this)
interactor.stakingDashboardFlow()
.inBackground()
.collect(::logDashboard)
}
private fun logDashboard(dashboard: ExtendedLoadingState<StakingDashboard>) {
if (dashboard !is ExtendedLoadingState.Loaded) return
val serialized = gson.toJson(dashboard)
val message = """
Dashboard state:
Syncing items: ${dashboard.data.syncingItemsCount()}
$serialized
""".trimIndent()
Log.d("StakingDashboardIntegrationTest", message)
}
private class AggregatedStakingDashboardOptionDesirializer : JsonSerializer<AggregatedStakingDashboardOption<*>> {
override fun serialize(src: AggregatedStakingDashboardOption<*>, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return JsonObject().apply {
add("chain", JsonPrimitive(src.chain.name))
add("stakingState", context.serialize(src.stakingState))
add("syncing", context.serialize(src.syncingStage))
}
}
}
private fun StakingDashboard.syncingItemsCount(): Int {
return withoutStake.count { it.syncingStage.isSyncing() } + hasStake.count { it.syncingStage.isSyncing() }
}
}
@@ -0,0 +1,46 @@
package io.novafoundation.nova
import android.util.Log
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.feature_staking_api.data.parachainStaking.turing.repository.AutomationAction
import io.novafoundation.nova.feature_staking_api.data.parachainStaking.turing.repository.OptimalAutomationRequest
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.findChain
import kotlinx.coroutines.runBlocking
import org.junit.Test
import java.math.BigInteger
class TuringAutomationIntegrationTest : BaseIntegrationTest() {
private val stakingApi = FeatureUtils.getFeature<StakingFeatureApi>(context, StakingFeatureApi::class.java)
private val automationTasksRepository = stakingApi.turingAutomationRepository
@Test
fun calculateOptimalAutoCompounding(){
runBlocking {
val chain = chainRegistry.findTuringChain()
val request = OptimalAutomationRequest(
collator = "6AEG2WKRVvZteWWT3aMkk2ZE21FvURqiJkYpXimukub8Zb9C",
amount = BigInteger("1000000000000")
)
val response = automationTasksRepository.calculateOptimalAutomation(chain.id, request)
Log.d(LOG_TAG, response.toString())
}
}
@Test
fun calculateAutoCompoundExecutionFees(){
runBlocking {
val chain = chainRegistry.findTuringChain()
val fees = automationTasksRepository.getTimeAutomationFees(chain.id, AutomationAction.AUTO_COMPOUND_DELEGATED_STAKE, executions = 1)
Log.d(LOG_TAG, fees.toString())
}
}
private suspend fun ChainRegistry.findTuringChain() = findChain { it.name == "Turing" }!!
}
@@ -0,0 +1,137 @@
package io.novafoundation.nova
import android.util.Log
import io.novafoundation.nova.common.utils.LOG_TAG
import io.novafoundation.nova.common.utils.second
import io.novafoundation.nova.core.ethereum.Web3Api
import io.novafoundation.nova.core.ethereum.log.Topic
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
import io.novafoundation.nova.runtime.ethereum.contract.base.querySingle
import io.novafoundation.nova.runtime.ethereum.contract.erc20.Erc20Queries
import io.novafoundation.nova.runtime.ethereum.contract.erc20.Erc20Standard
import io.novafoundation.nova.runtime.ethereum.sendSuspend
import io.novafoundation.nova.runtime.multiNetwork.getEthereumApiOrThrow
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.web3j.abi.EventEncoder
import org.web3j.abi.TypeEncoder
import org.web3j.abi.datatypes.Address
import org.web3j.protocol.core.DefaultBlockParameterName
import java.math.BigInteger
class Erc20Transfer(
val txHash: String,
val blockNumber: String,
val from: String,
val to: String,
val contract: String,
val amount: BigInteger,
)
class Web3jServiceIntegrationTest : BaseIntegrationTest() {
@Test
fun shouldFetchBalance(): Unit = runBlocking {
val web3j = moonbeamWeb3j()
val balance = web3j.ethGetBalance("0xf977814e90da44bfa03b6295a0616a897441acec", DefaultBlockParameterName.LATEST).sendSuspend()
Log.d(LOG_TAG, balance.balance.toString())
}
@Test
fun shouldFetchComplexStructure(): Unit = runBlocking {
val web3j = moonbeamWeb3j()
val block = web3j.ethGetBlockByNumber(DefaultBlockParameterName.LATEST, true).sendSuspend()
Log.d(LOG_TAG, block.block.hash)
}
@Test
fun shouldSubscribeToNewHeadEvents(): Unit = runBlocking {
val web3j = moonbeamWeb3j()
val newHead = web3j.newHeadsNotifications().asFlow().first()
Log.d(LOG_TAG, "New head appended to chain: ${newHead.params.result.hash}")
}
@Test
fun shouldSubscribeBalances(): Unit = runBlocking {
val web3j = moonbeamWeb3j()
val accountAddress = "0x4A43C16107591AE5Ec904e584ed4Bb05386F98f7"
val moonbeamUsdc = "0x818ec0a7fe18ff94269904fced6ae3dae6d6dc0b"
val balanceUpdates = web3j.erc20BalanceFlow(accountAddress, moonbeamUsdc).take(2).toList()
error("Initial balance: ${balanceUpdates.first()}, new balance: ${balanceUpdates.second()}")
}
private fun Web3Api.erc20BalanceFlow(account: String, contract: String): Flow<Balance> {
return flow {
val erc20 = Erc20Standard().querySingle(contract, web3j = this@erc20BalanceFlow)
val initialBalance = erc20.balanceOfAsync(account).await()
emit(initialBalance)
val changes = accountErcTransfersFlow(account).map {
erc20.balanceOfAsync(account).await()
}
emitAll(changes)
}
}
private fun Web3Api.accountErcTransfersFlow(address: String): Flow<Erc20Transfer> {
val addressTopic = TypeEncoder.encode(Address(address))
val transferEvent = Erc20Queries.TRANSFER_EVENT
val transferEventSignature = EventEncoder.encode(transferEvent)
val contractAddresses = emptyList<String>() // everything
val erc20SendTopic = listOf(
Topic.Single(transferEventSignature), // zero-th topic is event signature
Topic.AnyOf(addressTopic), // our account as `from`
)
val erc20ReceiveTopic = listOf(
Topic.Single(transferEventSignature), // zero-th topic is event signature
Topic.Any, // anyone is `from`
Topic.AnyOf(addressTopic) // out account as `to`
)
val receiveTransferNotifications = logsNotifications(contractAddresses, erc20ReceiveTopic)
val sendTransferNotifications = logsNotifications(contractAddresses, erc20SendTopic)
val transferNotifications = merge(receiveTransferNotifications, sendTransferNotifications)
return transferNotifications.map { logNotification ->
val log = logNotification.params.result
val contract = log.address
val event = Erc20Queries.parseTransferEvent(log)
Erc20Transfer(
txHash = log.transactionHash,
blockNumber = log.blockNumber,
from = event.from.value,
to = event.to.value,
contract = contract,
amount = event.amount.value,
)
}
}
private suspend fun moonbeamWeb3j(): Web3Api {
val moonbeamChainId = "fe58ea77779b7abda7da4ec526d14db9b1e9cd40a217c34892af80a9b332b76d"
return chainRegistry.getEthereumApiOrThrow(moonbeamChainId, Chain.Node.ConnectionType.WSS)
}
}
@@ -0,0 +1,164 @@
package io.novafoundation.nova.balances
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.google.gson.Gson
import io.novafoundation.nova.common.data.network.runtime.binding.AccountInfo
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountInfo
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.fromJson
import io.novafoundation.nova.common.utils.hasModule
import io.novafoundation.nova.common.utils.system
import io.novafoundation.nova.core.model.CryptoType
import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal
import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
import io.novafoundation.nova.feature_account_impl.di.AccountFeatureComponent
import io.novafoundation.nova.feature_account_impl.domain.account.model.DefaultMetaAccount
import io.novafoundation.nova.runtime.BuildConfig.TEST_CHAINS_URL
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeComponent
import io.novafoundation.nova.runtime.extrinsic.systemRemark
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import io.novafoundation.nova.runtime.multiNetwork.getSocket
import io.novasama.substrate_sdk_android.extensions.fromHex
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import io.novasama.substrate_sdk_android.runtime.metadata.storageKey
import io.novasama.substrate_sdk_android.wsrpc.networkStateFlow
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.math.BigInteger
import java.math.BigInteger.ZERO
import java.net.URL
import kotlin.time.Duration.Companion.seconds
@RunWith(Parameterized::class)
class BalancesIntegrationTest(
private val testChainId: String,
private val testChainName: String,
private val testAccount: String
) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun data(): List<Array<String?>> {
val arrayOfNetworks: Array<TestData> = Gson().fromJson(URL(TEST_CHAINS_URL).readText())
return arrayOfNetworks.map { arrayOf(it.chainId, it.name, it.account) }
}
class TestData(
val chainId: String,
val name: String,
val account: String?
)
}
private val maxAmount = BigInteger.valueOf(10).pow(30)
private val runtimeApi = FeatureUtils.getFeature<RuntimeComponent>(
ApplicationProvider.getApplicationContext<Context>(),
RuntimeApi::class.java
)
private val accountApi = FeatureUtils.getFeature<AccountFeatureComponent>(
ApplicationProvider.getApplicationContext<Context>(),
AccountFeatureApi::class.java
)
private val chainRegistry = runtimeApi.chainRegistry()
private val externalRequirementFlow = runtimeApi.externalRequirementFlow()
private val remoteStorage = runtimeApi.remoteStorageSource()
private val extrinsicService = accountApi.extrinsicService()
@Before
fun before() = runBlocking {
externalRequirementFlow.emit(ChainConnection.ExternalRequirement.ALLOWED)
}
@Test
fun testBalancesLoading() = runBlocking(Dispatchers.Default) {
val chains = chainRegistry.getChain(testChainId)
val freeBalance = testBalancesInChainAsync(chains, testAccount)?.data?.free ?: error("Balance was null")
assertTrue("Free balance: $freeBalance is less than $maxAmount", maxAmount > freeBalance)
assertTrue("Free balance: $freeBalance is greater than 0", ZERO < freeBalance)
}
@Test
fun testFeeLoading() = runBlocking(Dispatchers.Default) {
val chains = chainRegistry.getChain(testChainId)
testFeeLoadingAsync(chains)
Unit
}
private suspend fun testBalancesInChainAsync(chain: Chain, currentAccount: String): AccountInfo? {
return coroutineScope {
try {
withTimeout(80.seconds) {
remoteStorage.query(
chainId = chain.id,
keyBuilder = { it.metadata.system().storage("Account").storageKey(it, currentAccount.fromHex()) },
binding = { scale, runtime -> scale?.let { bindAccountInfo(scale, runtime) } }
)
}
} catch (e: Exception) {
throw Exception("Socket state: ${chainRegistry.getSocket(chain.id).networkStateFlow().first()}, error: ${e.message}", e)
}
}
}
private suspend fun testFeeLoadingAsync(chain: Chain) {
return coroutineScope {
withTimeout(80.seconds) {
extrinsicService.estimateFee(chain, testTransactionOrigin()) {
systemRemark(byteArrayOf(0))
val haveBatch = runtime.metadata.hasModule("Utility")
if (haveBatch) {
systemRemark(byteArrayOf(0))
}
}
}
}
}
private fun testTransactionOrigin(): TransactionOrigin = TransactionOrigin.Wallet(
createTestMetaAccount()
)
private fun createTestMetaAccount(): MetaAccount {
val metaAccount = DefaultMetaAccount(
id = 0,
globallyUniqueId = MetaAccountLocal.generateGloballyUniqueId(),
substratePublicKey = testAccount.fromHex(),
substrateCryptoType = CryptoType.SR25519,
substrateAccountId = testAccount.fromHex(),
ethereumAddress = testAccount.fromHex(),
ethereumPublicKey = testAccount.fromHex(),
isSelected = true,
name = "Test",
type = LightMetaAccount.Type.WATCH_ONLY,
status = LightMetaAccount.Status.ACTIVE,
chainAccounts = emptyMap(),
parentMetaId = null
)
return metaAccount
}
}
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">[Debug] Pezkuwi</string>
</resources>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">[Dev] Pezkuwi</string>
</resources>
+234
View File
@@ -0,0 +1,234 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="com.google.android.gms.permission.AD_ID"
tools:node="remove" />
<uses-permission
android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission
android:name="android.permission.BLUETOOTH_SCAN"
tools:targetApi="s" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application
android:name="io.novafoundation.nova.app.App"
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:supportsRtl="true"
tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules"
tools:targetApi="s">
<activity
android:name="io.novafoundation.nova.app.root.presentation.RootActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:theme="@style/Theme.NovaFoundation.Nova"
android:windowSoftInputMode="adjustResize">
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/json" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<data
android:host="buy-success"
android:scheme="pezkuwi" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<data
android:host="@string/deep_linking_host"
android:scheme="@string/deep_linking_scheme" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:autoVerify="true">
<data android:host="request" />
<data android:scheme="pezkuwiwallet" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:autoVerify="true">
<data android:host="wc" />
<data android:scheme="pezkuwiwallet" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:autoVerify="true">
<data
android:pathPattern="/.*@2"
android:scheme="wc" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:scheme="http" />
<data android:host="app.pezkuwichain.io" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"/>
<data android:host="@string/branch_io_link_host"/>
<data android:host="@string/branch_io_link_host_alternate"/>
</intent-filter>
</activity>
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="fullSensor"
android:theme="@style/Theme.NovaFoundation.Nova"
tools:replace="android:theme,screenOrientation" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<receiver
android:name="io.novafoundation.nova.feature_account_impl.presentation.exporting.json.ShareCompletedReceiver"
android:enabled="true"
android:exported="false" />
<receiver
android:name="io.novafoundation.nova.feature_ledger_impl.sdk.connection.usb.UsbLedgerConnection$UsbPermissionReceiver"
android:exported="true"
android:permission="android.permission.BROADCAST_USB" />
<service
android:name="io.novafoundation.nova.feature_push_notifications.NovaFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@drawable/ic_pezkuwi" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/android_system_accent" />
<meta-data
android:name="firebase_messaging_auto_init_enabled"
android:value="false" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
android:value="@string/default_notification_channel_id" />
<meta-data
android:name="io.novafoundation.nova.transactions_notification_channel_id"
android:value="@string/transactions_notification_channel_id" />
<meta-data
android:name="io.novafoundation.nova.governance_notification_channel_id"
android:value="@string/governance_notification_channel_id" />
<meta-data
android:name="io.novafoundation.nova.staking_notification_channel_id"
android:value="@string/staking_notification_channel_id" />
<meta-data
android:name="io.novafoundation.nova.multisigs_notification_channel_id"
android:value="@string/multisigs_notification_channel_id" />
<meta-data
android:name="io.branch.sdk.BranchKey"
android:value="key_live_dsxlmUqNhbtOYX6e7cfgrpkbqsjGPYBf" />
<meta-data android:name="io.branch.sdk.TestMode" android:value="false" />
</application>
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="text/plain" />
</intent>
<!-- Allow Google Pay feature in WebView -->
<intent>
<action android:name="org.chromium.intent.action.PAY" />
</intent>
<intent>
<action android:name="org.chromium.intent.action.IS_READY_TO_PAY" />
</intent>
<intent>
<action android:name="org.chromium.intent.action.UPDATE_PAYMENT_DETAILS" />
</intent>
</queries>
</manifest>
@@ -0,0 +1,100 @@
package io.novafoundation.nova.app
import android.app.Application
import android.content.Context
import android.content.res.Configuration
import com.walletconnect.android.Core
import com.walletconnect.android.CoreClient
import com.walletconnect.android.relay.ConnectionType
import com.walletconnect.web3.wallet.client.Wallet
import com.walletconnect.web3.wallet.client.Web3Wallet
import io.novafoundation.nova.app.di.app.AppComponent
import io.novafoundation.nova.app.di.deps.FeatureHolderManager
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.common.resources.LanguagesHolder
import io.novafoundation.nova.common.utils.coroutines.RootScope
import io.novafoundation.nova.feature_deep_linking.presentation.handling.branchIo.BranchIOLinkHandler
import io.novafoundation.nova.feature_wallet_connect_impl.BuildConfig
import javax.inject.Inject
private const val WC_REDIRECT_URL = "pezkuwiwallet://request"
open class App : Application(), FeatureContainer {
@Inject
lateinit var featureHolderManager: FeatureHolderManager
private lateinit var appComponent: AppComponent
private val languagesHolder: LanguagesHolder = LanguagesHolder()
// App global scope using for processes that should work while app is alive
private val rootScope = RootScope()
override fun attachBaseContext(base: Context) {
val contextManager = ContextManager.getInstanceOrInit(base, languagesHolder)
super.attachBaseContext(contextManager.setLocale(base))
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val contextManager = ContextManager.getInstanceOrInit(this, languagesHolder)
contextManager.setLocale(this)
}
override fun onCreate() {
super.onCreate()
val contextManger = ContextManager.getInstanceOrInit(this, languagesHolder)
appComponent = io.novafoundation.nova.app.di.app.DaggerAppComponent
.builder()
.application(this)
.contextManager(contextManger)
.rootScope(rootScope)
.build()
appComponent.inject(this)
BranchIOLinkHandler.Initializer.init(this)
initializeWalletConnect()
}
override fun <T> getFeature(key: Class<*>): T {
return featureHolderManager.getFeature<T>(key)!!
}
override fun releaseFeature(key: Class<*>) {
featureHolderManager.releaseFeature(key)
}
override fun commonApi(): CommonApi {
return appComponent
}
private fun initializeWalletConnect() {
val projectId = BuildConfig.WALLET_CONNECT_PROJECT_ID
val relayUrl = "relay.walletconnect.com"
val serverUrl = "wss://$relayUrl?projectId=$projectId"
val connectionType = ConnectionType.MANUAL
val appMetaData = Core.Model.AppMetaData(
name = "Pezkuwi Wallet",
description = "Next-gen wallet for Pezkuwichain and Polkadot ecosystem",
url = "https://pezkuwichain.io/",
icons = listOf("https://raw.githubusercontent.com/pezkuwichain/branding/master/logos/Pezkuwi_Wallet_Sun_Color.png"),
redirect = WC_REDIRECT_URL
)
CoreClient.initialize(relayServerUrl = serverUrl, connectionType = connectionType, application = this, metaData = appMetaData) { error ->
// TODO maybe re-initialize client
}
val initParams = Wallet.Params.Init(core = CoreClient)
Web3Wallet.initialize(initParams) { error ->
// TODO maybe re-initialize client
}
}
}
@@ -0,0 +1,44 @@
package io.novafoundation.nova.app.di.app
import dagger.BindsInstance
import dagger.Component
import io.novafoundation.nova.app.App
import io.novafoundation.nova.app.di.app.navigation.NavigationModule
import io.novafoundation.nova.app.di.deps.ComponentHolderModule
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.modules.CommonModule
import io.novafoundation.nova.common.di.modules.NetworkModule
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.common.utils.coroutines.RootScope
@ApplicationScope
@Component(
modules = [
AppModule::class,
CommonModule::class,
NetworkModule::class,
NavigationModule::class,
ComponentHolderModule::class,
FeatureManagerModule::class
]
)
interface AppComponent : CommonApi {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: App): Builder
@BindsInstance
fun contextManager(contextManager: ContextManager): Builder
@BindsInstance
fun rootScope(rootScope: RootScope): Builder
fun build(): AppComponent
}
fun inject(app: App)
}
@@ -0,0 +1,29 @@
package io.novafoundation.nova.app.di.app
import android.content.Context
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.App
import io.novafoundation.nova.app.root.presentation.common.RealBuildTypeProvider
import io.novafoundation.nova.app.root.presentation.common.RootActivityIntentProvider
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.interfaces.ActivityIntentProvider
import io.novafoundation.nova.common.interfaces.BuildTypeProvider
@Module
class AppModule {
@ApplicationScope
@Provides
fun provideContext(application: App): Context {
return application
}
@Provides
@ApplicationScope
fun provideRootActivityIntentProvider(context: Context): ActivityIntentProvider = RootActivityIntentProvider(context)
@Provides
@ApplicationScope
fun provideBuildTypeProvider(): BuildTypeProvider = RealBuildTypeProvider()
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.app.di.app
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.di.deps.FeatureHolderManager
import io.novafoundation.nova.common.di.FeatureApiHolder
import io.novafoundation.nova.common.di.scope.ApplicationScope
@Module
class FeatureManagerModule {
@ApplicationScope
@Provides
fun provideFeatureHolderManager(featureApiHolderMap: @JvmSuppressWildcards Map<Class<*>, FeatureApiHolder>): FeatureHolderManager {
return FeatureHolderManager(featureApiHolderMap)
}
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.accountmigration.AccountMigrationNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_account_migration.presentation.AccountMigrationRouter
@Module
class AccountMigrationNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry
): AccountMigrationRouter = AccountMigrationNavigator(
navigationHoldersRegistry = navigationHoldersRegistry
)
}
@@ -0,0 +1,103 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.account.PolkadotVaultVariantSignCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.account.ScanSeedCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.account.SelectAddressCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.account.SelectMultipleWalletsCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.account.SelectSingleWalletCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.account.SelectWalletCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.ChangeBackupPasswordCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.RestoreBackupPasswordCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.SyncWalletsBackupPasswordCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.pincode.PinCodeTwoFactorVerificationCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.sequrity.verification.PinCodeTwoFactorVerificationCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectMultipleWalletsCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.changePassword.ChangeBackupPasswordCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.changePassword.RestoreBackupPasswordCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletCommunicator
import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator
import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter
import io.novafoundation.nova.feature_account_api.presenatation.cloudBackup.createPassword.SyncWalletsBackupPasswordCommunicator
import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectSingleWallet.SelectSingleWalletCommunicator
import io.novafoundation.nova.feature_account_impl.presentation.seedScan.ScanSeedCommunicator
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
@Module
class AccountNavigationModule {
@Provides
@ApplicationScope
fun providePinCodeTwoFactorVerificationCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry
): PinCodeTwoFactorVerificationCommunicator = PinCodeTwoFactorVerificationCommunicatorImpl(navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideSelectWalletCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry
): SelectWalletCommunicator = SelectWalletCommunicatorImpl(navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideParitySignerCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry
): PolkadotVaultVariantSignCommunicator = PolkadotVaultVariantSignCommunicatorImpl(navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideSelectAddressCommunicator(
router: AssetsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): SelectAddressCommunicator = SelectAddressCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideScanSeedCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry
): ScanSeedCommunicator = ScanSeedCommunicatorImpl(navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideSelectSingleWalletCommunicator(
router: AssetsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): SelectSingleWalletCommunicator = SelectSingleWalletCommunicatorImpl(router)
@Provides
@ApplicationScope
fun provideSelectMultipleWalletsCommunicator(
router: AssetsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): SelectMultipleWalletsCommunicator = SelectMultipleWalletsCommunicatorImpl(router, navigationHoldersRegistry)
@ApplicationScope
@Provides
fun provideAccountRouter(navigator: Navigator): AccountRouter = navigator
@Provides
@ApplicationScope
fun providePushGovernanceSettingsCommunicator(
router: AccountRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): SyncWalletsBackupPasswordCommunicator = SyncWalletsBackupPasswordCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideChangeBackupPasswordCommunicator(
router: AccountRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): ChangeBackupPasswordCommunicator = ChangeBackupPasswordCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideRestoreBackupPasswordCommunicator(
router: AccountRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): RestoreBackupPasswordCommunicator = RestoreBackupPasswordCommunicatorImpl(router, navigationHoldersRegistry)
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.topup.TopUpAddressCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_assets.presentation.topup.TopUpAddressCommunicator
@Module
class AssetNavigationModule {
@ApplicationScope
@Provides
fun provideTopUpAddressCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): TopUpAddressCommunicator {
return TopUpAddressCommunicatorImpl(navigationHoldersRegistry)
}
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.buy.BuyNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_buy_impl.presentation.BuyRouter
@Module
class BuyNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): BuyRouter =
BuyNavigator(navigationHoldersRegistry)
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.chainMigration.ChainMigrationNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_ahm_impl.presentation.ChainMigrationRouter
@Module
class ChainMigrationNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): ChainMigrationRouter =
ChainMigrationNavigator(navigationHoldersRegistry)
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.cloudBackup.CloudBackupNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_cloud_backup_impl.presentation.CloudBackupRouter
@Module
class CloudBackupNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): CloudBackupRouter =
CloudBackupNavigator(navigationHoldersRegistry)
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.wallet.CurrencyNavigator
import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_currency_api.presentation.CurrencyRouter
@Module
class CurrencyNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
rootRouter: RootRouter,
navigationHoldersRegistry: NavigationHoldersRegistry,
): CurrencyRouter = CurrencyNavigator(rootRouter, navigationHoldersRegistry)
}
@@ -0,0 +1,26 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.dApp.DAppNavigator
import io.novafoundation.nova.app.root.navigation.navigators.dApp.DAppSearchCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter
import io.novafoundation.nova.feature_dapp_impl.presentation.search.DAppSearchCommunicator
@Module
class DAppNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry
): DAppRouter = DAppNavigator(navigationHoldersRegistry)
@ApplicationScope
@Provides
fun provideSearchDappCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): DAppSearchCommunicator {
return DAppSearchCommunicatorImpl(navigationHoldersRegistry)
}
}
@@ -0,0 +1,29 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.externalSign.ExternalSignCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.externalSign.ExternalSignNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
import io.novafoundation.nova.feature_external_sign_api.model.ExternalSignCommunicator
import io.novafoundation.nova.feature_external_sign_impl.ExternalSignRouter
@Module
class ExternalSignNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): ExternalSignRouter =
ExternalSignNavigator(navigationHoldersRegistry)
@ApplicationScope
@Provides
fun provideSignExtrinsicCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry,
automaticInteractionGate: AutomaticInteractionGate,
): ExternalSignCommunicator {
return ExternalSignCommunicatorImpl(navigationHoldersRegistry, automaticInteractionGate)
}
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.gift.GiftNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_gift_impl.presentation.GiftRouter
@Module
class GiftNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(commonDelegate: Navigator, navigationHoldersRegistry: NavigationHoldersRegistry): GiftRouter =
GiftNavigator(commonDelegate, navigationHoldersRegistry)
}
@@ -0,0 +1,42 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.governance.GovernanceNavigator
import io.novafoundation.nova.app.root.navigation.navigators.governance.SelectTracksCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.governance.TinderGovVoteCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectTracksCommunicator
import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter
import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter
import io.novafoundation.nova.feature_governance_impl.presentation.referenda.vote.setup.tindergov.TinderGovVoteCommunicator
@Module
class GovernanceNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
commonNavigator: Navigator,
contextManager: ContextManager,
dAppRouter: DAppRouter
): GovernanceRouter = GovernanceNavigator(navigationHoldersRegistry, commonNavigator, contextManager, dAppRouter)
@Provides
@ApplicationScope
fun provideSelectTracksCommunicator(
router: GovernanceRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): SelectTracksCommunicator = SelectTracksCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun provideTinderGovVoteCommunicator(
router: GovernanceRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): TinderGovVoteCommunicator = TinderGovVoteCommunicatorImpl(router, navigationHoldersRegistry)
}
@@ -0,0 +1,34 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.ledger.LedgerNavigator
import io.novafoundation.nova.app.root.navigation.navigators.ledger.LedgerSignCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.ledger.SelectLedgerAddressCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator
import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter
import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter
import io.novafoundation.nova.feature_ledger_impl.presentation.account.connect.legacy.SelectLedgerAddressInterScreenCommunicator
@Module
class LedgerNavigationModule {
@ApplicationScope
@Provides
fun provideSelectLedgerAddressCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectLedgerAddressInterScreenCommunicator {
return SelectLedgerAddressCommunicatorImpl(navigationHoldersRegistry)
}
@Provides
@ApplicationScope
fun provideLedgerSignerCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry
): LedgerSignCommunicator = LedgerSignCommunicatorImpl(navigationHoldersRegistry)
@ApplicationScope
@Provides
fun provideRouter(router: AccountRouter, navigationHoldersRegistry: NavigationHoldersRegistry): LedgerRouter =
LedgerNavigator(router, navigationHoldersRegistry)
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.multisig.MultisigOperationsNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_multisig_operations.presentation.MultisigOperationsRouter
@Module
class MultisigNavigationModule {
@ApplicationScope
@Provides
fun provideOperationsRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
commonDelegate: Navigator
): MultisigOperationsRouter = MultisigOperationsNavigator(navigationHoldersRegistry, commonDelegate)
}
@@ -0,0 +1,100 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.di.app.navigation.staking.StakingNavigationModule
import io.novafoundation.nova.app.root.navigation.holders.RootNavigationHolder
import io.novafoundation.nova.app.root.navigation.holders.SplitScreenNavigationHolder
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.navigation.DelayedNavigationRouter
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
import io.novafoundation.nova.feature_crowdloan_impl.presentation.CrowdloanRouter
import io.novafoundation.nova.feature_onboarding_impl.OnboardingRouter
import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter
import io.novafoundation.nova.splash.SplashRouter
@Module(
includes = [
AccountNavigationModule::class,
AssetNavigationModule::class,
DAppNavigationModule::class,
NftNavigationModule::class,
StakingNavigationModule::class,
LedgerNavigationModule::class,
CurrencyNavigationModule::class,
GovernanceNavigationModule::class,
WalletConnectNavigationModule::class,
VoteNavigationModule::class,
VersionsNavigationModule::class,
ExternalSignNavigationModule::class,
SettingsNavigationModule::class,
SwapNavigationModule::class,
BuyNavigationModule::class,
PushNotificationsNavigationModule::class,
CloudBackupNavigationModule::class,
AssetNavigationModule::class,
AccountMigrationNavigationModule::class,
MultisigNavigationModule::class,
ChainMigrationNavigationModule::class,
WalletNavigationModule::class,
GiftNavigationModule::class
]
)
class NavigationModule {
@ApplicationScope
@Provides
fun provideMainNavigatorHolder(
contextManager: ContextManager
): SplitScreenNavigationHolder = SplitScreenNavigationHolder(contextManager)
@ApplicationScope
@Provides
fun provideDappNavigatorHolder(
contextManager: ContextManager
): RootNavigationHolder = RootNavigationHolder(contextManager)
@ApplicationScope
@Provides
fun provideNavigationHoldersRegistry(
rootNavigatorHolder: RootNavigationHolder,
splitScreenNavigationHolder: SplitScreenNavigationHolder,
): NavigationHoldersRegistry {
return NavigationHoldersRegistry(splitScreenNavigationHolder, rootNavigatorHolder)
}
@ApplicationScope
@Provides
fun provideNavigator(
navigationHoldersRegistry: NavigationHoldersRegistry,
walletConnectRouter: WalletConnectRouter
): Navigator = Navigator(navigationHoldersRegistry, walletConnectRouter)
@Provides
@ApplicationScope
fun provideRootRouter(navigator: Navigator): RootRouter = navigator
@ApplicationScope
@Provides
fun provideSplashRouter(navigator: Navigator): SplashRouter = navigator
@ApplicationScope
@Provides
fun provideOnboardingRouter(navigator: Navigator): OnboardingRouter = navigator
@ApplicationScope
@Provides
fun provideAssetsRouter(navigator: Navigator): AssetsRouter = navigator
@ApplicationScope
@Provides
fun provideCrowdloanRouter(navigator: Navigator): CrowdloanRouter = navigator
@ApplicationScope
@Provides
fun provideDelayedNavigationRouter(navigator: Navigator): DelayedNavigationRouter = navigator
}
@@ -0,0 +1,17 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.nft.NftNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_nft_impl.NftRouter
@Module
class NftNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): NftRouter =
NftNavigator(navigationHoldersRegistry)
}
@@ -0,0 +1,44 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.push.PushGovernanceSettingsCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.push.PushMultisigSettingsCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.push.PushNotificationsNavigator
import io.novafoundation.nova.app.root.navigation.navigators.push.PushStakingSettingsCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_push_notifications.PushNotificationsRouter
import io.novafoundation.nova.feature_push_notifications.presentation.governance.PushGovernanceSettingsCommunicator
import io.novafoundation.nova.feature_push_notifications.presentation.multisigs.PushMultisigSettingsCommunicator
import io.novafoundation.nova.feature_push_notifications.presentation.staking.PushStakingSettingsCommunicator
@Module
class PushNotificationsNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): PushNotificationsRouter =
PushNotificationsNavigator(navigationHoldersRegistry)
@Provides
@ApplicationScope
fun providePushGovernanceSettingsCommunicator(
router: PushNotificationsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): PushGovernanceSettingsCommunicator = PushGovernanceSettingsCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun providePushStakingSettingsCommunicator(
router: PushNotificationsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): PushStakingSettingsCommunicator = PushStakingSettingsCommunicatorImpl(router, navigationHoldersRegistry)
@Provides
@ApplicationScope
fun providePushMultisigSettingsCommunicator(
router: PushNotificationsRouter,
navigationHoldersRegistry: NavigationHoldersRegistry
): PushMultisigSettingsCommunicator = PushMultisigSettingsCommunicatorImpl(router, navigationHoldersRegistry)
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.settings.SettingsNavigator
import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_settings_impl.SettingsRouter
import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter
@Module
class SettingsNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
rootRouter: RootRouter,
navigationHoldersRegistry: NavigationHoldersRegistry,
walletConnectRouter: WalletConnectRouter,
navigator: Navigator,
): SettingsRouter = SettingsNavigator(navigationHoldersRegistry, rootRouter, walletConnectRouter, navigator)
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.swap.SwapNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_swap_impl.presentation.SwapRouter
@Module
class SwapNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
commonDelegate: Navigator
): SwapRouter = SwapNavigator(navigationHoldersRegistry, commonDelegate)
}
@@ -0,0 +1,22 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.versions.VersionsNavigator
import io.novafoundation.nova.common.data.network.AppLinksProvider
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.feature_versions_api.presentation.VersionsRouter
@Module
class VersionsNavigationModule {
@Provides
@ApplicationScope
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
contextManager: ContextManager,
appLinksProvider: AppLinksProvider
): VersionsRouter = VersionsNavigator(navigationHoldersRegistry, contextManager, appLinksProvider.storeUrl)
}
@@ -0,0 +1,16 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.vote.VoteNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_vote.presentation.VoteRouter
@Module
class VoteNavigationModule {
@Provides
@ApplicationScope
fun provideVoteRouter(navigator: Navigator): VoteRouter = VoteNavigator(navigator)
}
@@ -0,0 +1,27 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.walletConnect.ApproveSessionCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.walletConnect.WalletConnectNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
import io.novafoundation.nova.feature_wallet_connect_impl.WalletConnectRouter
import io.novafoundation.nova.feature_wallet_connect_impl.presentation.sessions.approve.ApproveSessionCommunicator
@Module
class WalletConnectNavigationModule {
@Provides
@ApplicationScope
fun provideApproveSessionCommunicator(
navigationHoldersRegistry: NavigationHoldersRegistry,
automaticInteractionGate: AutomaticInteractionGate,
): ApproveSessionCommunicator = ApproveSessionCommunicatorImpl(navigationHoldersRegistry, automaticInteractionGate)
@ApplicationScope
@Provides
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry): WalletConnectRouter =
WalletConnectNavigator(navigationHoldersRegistry)
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.di.app.navigation
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.wallet.WalletNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_wallet_impl.presentation.WalletRouter
@Module
class WalletNavigationModule {
@ApplicationScope
@Provides
fun provideRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
commonDelegate: Navigator
): WalletRouter = WalletNavigator(commonDelegate, navigationHoldersRegistry)
}
@@ -0,0 +1,38 @@
package io.novafoundation.nova.app.di.app.navigation.staking
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.staking.mythos.MythosStakingNavigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.mythos.SelectMythCollatorSettingsInterScreenCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.staking.mythos.SelectMythosCollatorInterScreenCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_staking_impl.presentation.MythosStakingRouter
import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter
import io.novafoundation.nova.feature_staking_impl.presentation.mythos.SelectMythosInterScreenCommunicator
import io.novafoundation.nova.feature_staking_impl.presentation.mythos.start.selectCollatorSettings.SelectMythCollatorSettingsInterScreenCommunicator
@Module
class MythosStakingNavigationModule {
@Provides
@ApplicationScope
fun provideMythosStakingRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
stakingDashboardRouter: StakingDashboardRouter,
): MythosStakingRouter {
return MythosStakingNavigator(navigationHoldersRegistry, stakingDashboardRouter)
}
@Provides
@ApplicationScope
fun provideSelectCollatorCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectMythosInterScreenCommunicator {
return SelectMythosCollatorInterScreenCommunicatorImpl(navigationHoldersRegistry)
}
@Provides
@ApplicationScope
fun provideSelectSettingsCollatorCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectMythCollatorSettingsInterScreenCommunicator {
return SelectMythCollatorSettingsInterScreenCommunicatorImpl(navigationHoldersRegistry)
}
}
@@ -0,0 +1,19 @@
package io.novafoundation.nova.app.di.app.navigation.staking
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.nominationPools.NominationPoolsStakingNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_staking_impl.presentation.NominationPoolsRouter
@Module
class NominationPoolsStakingNavigationModule {
@Provides
@ApplicationScope
fun provideRouter(navigationHoldersRegistry: NavigationHoldersRegistry, navigator: Navigator): NominationPoolsRouter {
return NominationPoolsStakingNavigator(navigationHoldersRegistry, navigator)
}
}
@@ -0,0 +1,38 @@
package io.novafoundation.nova.app.di.app.navigation.staking
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.parachain.ParachainStakingNavigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.parachain.SelectCollatorInterScreenCommunicatorImpl
import io.novafoundation.nova.app.root.navigation.navigators.staking.parachain.SelectCollatorSettingsInterScreenCommunicatorImpl
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_staking_impl.presentation.ParachainStakingRouter
import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.common.SelectCollatorInterScreenCommunicator
import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.settings.SelectCollatorSettingsInterScreenCommunicator
@Module
class ParachainStakingNavigationModule {
@Provides
@ApplicationScope
fun provideParachainStakingRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
navigator: Navigator
): ParachainStakingRouter {
return ParachainStakingNavigator(navigationHoldersRegistry, navigator)
}
@Provides
@ApplicationScope
fun provideSelectCollatorCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectCollatorInterScreenCommunicator {
return SelectCollatorInterScreenCommunicatorImpl(navigationHoldersRegistry)
}
@Provides
@ApplicationScope
fun provideSelectCollatorSettingsCommunicator(navigationHoldersRegistry: NavigationHoldersRegistry): SelectCollatorSettingsInterScreenCommunicator {
return SelectCollatorSettingsInterScreenCommunicatorImpl(navigationHoldersRegistry)
}
}
@@ -0,0 +1,26 @@
package io.novafoundation.nova.app.di.app.navigation.staking
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.relaychain.RelayStakingNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter
import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter
import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter
@Module
class RelayStakingNavigationModule {
@Provides
@ApplicationScope
fun provideRelayStakingRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
navigator: Navigator,
dashboardRouter: StakingDashboardRouter,
dAppRouter: DAppRouter
): StakingRouter {
return RelayStakingNavigator(navigationHoldersRegistry, navigator, dashboardRouter, dAppRouter)
}
}
@@ -0,0 +1,42 @@
package io.novafoundation.nova.app.di.app.navigation.staking
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.StakingDashboardNavigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.StartMultiStakingNavigator
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter
import io.novafoundation.nova.feature_staking_impl.presentation.StartMultiStakingRouter
@Module(
includes = [
ParachainStakingNavigationModule::class,
RelayStakingNavigationModule::class,
NominationPoolsStakingNavigationModule::class,
MythosStakingNavigationModule::class
]
)
class StakingNavigationModule {
@Provides
@ApplicationScope
fun provideStakingDashboardNavigator(navigationHoldersRegistry: NavigationHoldersRegistry): StakingDashboardNavigator {
return StakingDashboardNavigator(navigationHoldersRegistry)
}
@Provides
@ApplicationScope
fun provideStakingDashboardRouter(relayStakingNavigator: StakingDashboardNavigator): StakingDashboardRouter = relayStakingNavigator
@Provides
@ApplicationScope
fun provideStartMultiStakingRouter(
navigationHoldersRegistry: NavigationHoldersRegistry,
dashboardRouter: StakingDashboardRouter,
commonNavigator: Navigator
): StartMultiStakingRouter {
return StartMultiStakingNavigator(navigationHoldersRegistry, dashboardRouter, commonNavigator)
}
}
@@ -0,0 +1,306 @@
package io.novafoundation.nova.app.di.deps
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import io.novafoundation.nova.app.App
import io.novafoundation.nova.app.root.di.RootApi
import io.novafoundation.nova.app.root.di.RootFeatureHolder
import io.novafoundation.nova.caip.di.CaipApi
import io.novafoundation.nova.caip.di.CaipFeatureHolder
import io.novafoundation.nova.common.di.FeatureApiHolder
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.core_db.di.DbApi
import io.novafoundation.nova.core_db.di.DbHolder
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_account_impl.di.AccountFeatureHolder
import io.novafoundation.nova.feature_account_migration.di.AccountMigrationFeatureApi
import io.novafoundation.nova.feature_account_migration.di.AccountMigrationFeatureHolder
import io.novafoundation.nova.feature_ahm_api.di.ChainMigrationFeatureApi
import io.novafoundation.nova.feature_ahm_impl.di.ChainMigrationFeatureHolder
import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
import io.novafoundation.nova.feature_assets.di.AssetsFeatureHolder
import io.novafoundation.nova.feature_banners_api.di.BannersFeatureApi
import io.novafoundation.nova.feature_banners_impl.di.BannersFeatureHolder
import io.novafoundation.nova.feature_buy_api.di.BuyFeatureApi
import io.novafoundation.nova.feature_buy_impl.di.BuyFeatureHolder
import io.novafoundation.nova.feature_cloud_backup_api.di.CloudBackupFeatureApi
import io.novafoundation.nova.feature_cloud_backup_impl.di.CloudBackupFeatureHolder
import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi
import io.novafoundation.nova.feature_crowdloan_impl.di.CrowdloanFeatureHolder
import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi
import io.novafoundation.nova.feature_currency_impl.di.CurrencyFeatureHolder
import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi
import io.novafoundation.nova.feature_dapp_impl.di.DAppFeatureHolder
import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi
import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureHolder
import io.novafoundation.nova.feature_external_sign_api.di.ExternalSignFeatureApi
import io.novafoundation.nova.feature_external_sign_impl.di.ExternalSignFeatureHolder
import io.novafoundation.nova.feature_gift_api.di.GiftFeatureApi
import io.novafoundation.nova.feature_gift_impl.di.GiftFeatureHolder
import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi
import io.novafoundation.nova.feature_governance_impl.di.GovernanceFeatureHolder
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
import io.novafoundation.nova.feature_ledger_core.LedgerCoreHolder
import io.novafoundation.nova.feature_ledger_core.di.LedgerCoreApi
import io.novafoundation.nova.feature_ledger_impl.di.LedgerFeatureHolder
import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureApi
import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureHolder
import io.novafoundation.nova.feature_nft_api.NftFeatureApi
import io.novafoundation.nova.feature_nft_impl.di.NftFeatureHolder
import io.novafoundation.nova.feature_onboarding_api.di.OnboardingFeatureApi
import io.novafoundation.nova.feature_onboarding_impl.di.OnboardingFeatureHolder
import io.novafoundation.nova.feature_proxy_api.di.ProxyFeatureApi
import io.novafoundation.nova.feature_proxy_impl.di.ProxyFeatureHolder
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureHolder
import io.novafoundation.nova.feature_settings_api.SettingsFeatureApi
import io.novafoundation.nova.feature_settings_impl.di.SettingsFeatureHolder
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.feature_staking_impl.di.StakingFeatureHolder
import io.novafoundation.nova.feature_swap_api.di.SwapFeatureApi
import io.novafoundation.nova.feature_swap_core.di.SwapCoreHolder
import io.novafoundation.nova.feature_swap_core_api.di.SwapCoreApi
import io.novafoundation.nova.feature_swap_impl.di.SwapFeatureHolder
import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi
import io.novafoundation.nova.feature_versions_impl.di.VersionsFeatureHolder
import io.novafoundation.nova.feature_vote.di.VoteFeatureApi
import io.novafoundation.nova.feature_vote.di.VoteFeatureHolder
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.feature_wallet_connect_api.di.WalletConnectFeatureApi
import io.novafoundation.nova.feature_wallet_connect_impl.di.WalletConnectFeatureHolder
import io.novafoundation.nova.feature_wallet_impl.di.WalletFeatureHolder
import io.novafoundation.nova.feature_xcm_api.di.XcmFeatureApi
import io.novafoundation.nova.feature_xcm_impl.di.XcmFeatureHolder
import io.novafoundation.nova.runtime.di.RuntimeApi
import io.novafoundation.nova.runtime.di.RuntimeHolder
import io.novafoundation.nova.splash.di.SplashFeatureApi
import io.novafoundation.nova.splash.di.SplashFeatureHolder
import io.novafoundation.nova.web3names.di.Web3NamesApi
import io.novafoundation.nova.web3names.di.Web3NamesHolder
@Module
interface ComponentHolderModule {
@ApplicationScope
@Binds
fun provideFeatureContainer(application: App): FeatureContainer
@ApplicationScope
@Binds
@ClassKey(SplashFeatureApi::class)
@IntoMap
fun provideSplashFeatureHolder(splashFeatureHolder: SplashFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(DbApi::class)
@IntoMap
fun provideDbFeature(dbHolder: DbHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(OnboardingFeatureApi::class)
@IntoMap
fun provideOnboardingFeature(onboardingFeatureHolder: OnboardingFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(DAppFeatureApi::class)
@IntoMap
fun provideDAppFeature(dAppFeatureHolder: DAppFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(LedgerFeatureApi::class)
@IntoMap
fun provideLedgerFeature(accountFeatureHolder: LedgerFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(LedgerCoreApi::class)
@IntoMap
fun provideLedgerCore(accountFeatureHolder: LedgerCoreHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(GovernanceFeatureApi::class)
@IntoMap
fun provideGovernanceFeature(accountFeatureHolder: GovernanceFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(AccountFeatureApi::class)
@IntoMap
fun provideAccountFeature(accountFeatureHolder: AccountFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(AssetsFeatureApi::class)
@IntoMap
fun provideAssetsFeature(holder: AssetsFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(VoteFeatureApi::class)
@IntoMap
fun provideVoteFeature(holder: VoteFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(WalletFeatureApi::class)
@IntoMap
fun provideWalletFeature(walletFeatureHolder: WalletFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(CurrencyFeatureApi::class)
@IntoMap
fun provideCurrencyFeature(currencyFeatureHolder: CurrencyFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(RootApi::class)
@IntoMap
fun provideMainFeature(accountFeatureHolder: RootFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(StakingFeatureApi::class)
@IntoMap
fun provideStakingFeature(holder: StakingFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(RuntimeApi::class)
@IntoMap
fun provideRuntimeFeature(runtimeHolder: RuntimeHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(Web3NamesApi::class)
@IntoMap
fun provideWeb3Names(web3NamesHolder: Web3NamesHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(CrowdloanFeatureApi::class)
@IntoMap
fun provideCrowdloanFeature(holder: CrowdloanFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(NftFeatureApi::class)
@IntoMap
fun provideNftFeature(holder: NftFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(VersionsFeatureApi::class)
@IntoMap
fun provideVersionsFeature(holder: VersionsFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(CaipApi::class)
@IntoMap
fun provideCaipFeature(holder: CaipFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(ExternalSignFeatureApi::class)
@IntoMap
fun provideExternalSignFeature(holder: ExternalSignFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(WalletConnectFeatureApi::class)
@IntoMap
fun provideWalletConnectFeature(holder: WalletConnectFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(SettingsFeatureApi::class)
@IntoMap
fun provideSettingsFeature(holder: SettingsFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(SwapFeatureApi::class)
@IntoMap
fun provideSwapFeature(holder: SwapFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(BuyFeatureApi::class)
@IntoMap
fun provideBuyFeature(holder: BuyFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(PushNotificationsFeatureApi::class)
@IntoMap
fun providePushNotificationsFeature(holder: PushNotificationsFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(ProxyFeatureApi::class)
@IntoMap
fun provideProxyFeature(holder: ProxyFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(DeepLinkingFeatureApi::class)
@IntoMap
fun provideDeepLinkingFeatureHolder(holder: DeepLinkingFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(CloudBackupFeatureApi::class)
@IntoMap
fun provideCloudBackupFeatureHolder(holder: CloudBackupFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(SwapCoreApi::class)
@IntoMap
fun provideSwapCoreFeatureHolder(holder: SwapCoreHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(BannersFeatureApi::class)
@IntoMap
fun provideBannersFeatureApi(holder: BannersFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(XcmFeatureApi::class)
@IntoMap
fun provideXcmFeatureHolder(holder: XcmFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(MultisigOperationsFeatureApi::class)
@IntoMap
fun provideMultisigOperationsFeatureHolder(holder: MultisigOperationsFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(AccountMigrationFeatureApi::class)
@IntoMap
fun provideAccountMigrationFeatureHolder(holder: AccountMigrationFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(ChainMigrationFeatureApi::class)
@IntoMap
fun provideChainMigrationFeatureHolder(holder: ChainMigrationFeatureHolder): FeatureApiHolder
@ApplicationScope
@Binds
@ClassKey(GiftFeatureApi::class)
@IntoMap
fun provideGiftFeature(giftFeatureHolder: GiftFeatureHolder): FeatureApiHolder
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.app.di.deps
import io.novafoundation.nova.common.di.FeatureApiHolder
class FeatureHolderManager(
private val mFeatureHolders: Map<Class<*>, FeatureApiHolder>
) {
fun <T> getFeature(key: Class<*>): T? {
val featureApiHolder = mFeatureHolders[key] ?: throw IllegalStateException()
return featureApiHolder.getFeatureApi<T>()
}
fun releaseFeature(key: Class<*>) {
val featureApiHolder = mFeatureHolders[key] ?: throw IllegalStateException()
featureApiHolder.releaseFeatureApi()
}
}
@@ -0,0 +1,30 @@
package io.novafoundation.nova.app.root.di
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
import io.novafoundation.nova.app.root.presentation.common.FirebaseServiceInitializer
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.interfaces.CompoundExternalServiceInitializer
import io.novafoundation.nova.common.interfaces.ExternalServiceInitializer
@Module
class ExternalServiceInitializersModule {
@Provides
@IntoSet
fun provideFirebaseServiceInitializer(
context: Context
): ExternalServiceInitializer {
return FirebaseServiceInitializer(context)
}
@Provides
@FeatureScope
fun provideCompoundExternalServiceInitializer(
initializers: Set<@JvmSuppressWildcards ExternalServiceInitializer>
): ExternalServiceInitializer {
return CompoundExternalServiceInitializer(initializers)
}
}
@@ -0,0 +1,3 @@
package io.novafoundation.nova.app.root.di
interface RootApi
@@ -0,0 +1,103 @@
package io.novafoundation.nova.app.root.di
import dagger.BindsInstance
import dagger.Component
import io.novafoundation.nova.app.root.navigation.holders.RootNavigationHolder
import io.novafoundation.nova.app.root.navigation.holders.SplitScreenNavigationHolder
import io.novafoundation.nova.app.root.navigation.navigators.staking.StakingDashboardNavigator
import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.app.root.presentation.di.RootActivityComponent
import io.novafoundation.nova.app.root.presentation.main.di.MainFragmentComponent
import io.novafoundation.nova.app.root.presentation.splitScreen.di.SplitScreenFragmentComponent
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.navigation.DelayedNavigationRouter
import io.novafoundation.nova.core_db.di.DbApi
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter
import io.novafoundation.nova.feature_account_migration.di.AccountMigrationFeatureApi
import io.novafoundation.nova.feature_ahm_api.di.ChainMigrationFeatureApi
import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
import io.novafoundation.nova.feature_buy_api.di.BuyFeatureApi
import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi
import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi
import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi
import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter
import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi
import io.novafoundation.nova.feature_gift_api.di.GiftFeatureApi
import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi
import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureApi
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter
import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.feature_wallet_connect_api.di.WalletConnectFeatureApi
import io.novafoundation.nova.runtime.di.RuntimeApi
@Component(
dependencies = [
RootDependencies::class
],
modules = [
RootFeatureModule::class
]
)
@FeatureScope
interface RootComponent {
fun mainActivityComponentFactory(): RootActivityComponent.Factory
fun splitScreenFragmentComponentFactory(): SplitScreenFragmentComponent.Factory
fun mainFragmentComponentFactory(): MainFragmentComponent.Factory
@Component.Factory
interface Factory {
fun create(
@BindsInstance splitScreenNavigationHolder: SplitScreenNavigationHolder,
@BindsInstance rootNavigationHolder: RootNavigationHolder,
@BindsInstance rootRouter: RootRouter,
@BindsInstance governanceRouter: GovernanceRouter,
@BindsInstance dAppRouter: DAppRouter,
@BindsInstance assetsRouter: AssetsRouter,
@BindsInstance accountRouter: AccountRouter,
@BindsInstance stakingRouter: StakingRouter,
@BindsInstance stakingDashboardNavigator: StakingDashboardNavigator,
@BindsInstance delayedNavigationRouter: DelayedNavigationRouter,
deps: RootDependencies
): RootComponent
}
@Component(
dependencies = [
AccountFeatureApi::class,
WalletFeatureApi::class,
StakingFeatureApi::class,
CrowdloanFeatureApi::class,
AssetsFeatureApi::class,
CurrencyFeatureApi::class,
GovernanceFeatureApi::class,
DAppFeatureApi::class,
DbApi::class,
CommonApi::class,
RuntimeApi::class,
VersionsFeatureApi::class,
WalletConnectFeatureApi::class,
PushNotificationsFeatureApi::class,
DeepLinkingFeatureApi::class,
LedgerFeatureApi::class,
BuyFeatureApi::class,
DeepLinkingFeatureApi::class,
AccountMigrationFeatureApi::class,
MultisigOperationsFeatureApi::class,
ChainMigrationFeatureApi::class,
GiftFeatureApi::class
]
)
interface RootFeatureDependenciesComponent : RootDependencies
}
@@ -0,0 +1,192 @@
package io.novafoundation.nova.app.root.di
import android.content.Context
import coil.ImageLoader
import io.novafoundation.nova.common.data.network.AppLinksProvider
import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin
import io.novafoundation.nova.common.mixin.api.NetworkStateMixin
import io.novafoundation.nova.common.resources.ContextManager
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.sequrity.SafeModeService
import io.novafoundation.nova.common.utils.DialogMessageManager
import io.novafoundation.nova.common.utils.ToastMessageManager
import io.novafoundation.nova.common.utils.coroutines.RootScope
import io.novafoundation.nova.common.utils.network.DeviceNetworkStateObserver
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
import io.novafoundation.nova.common.utils.sequrity.BackgroundAccessObserver
import io.novafoundation.nova.common.utils.systemCall.SystemCallExecutor
import io.novafoundation.nova.common.view.bottomSheet.action.ActionBottomSheetLauncher
import io.novafoundation.nova.common.view.bottomSheet.action.ActionBottomSheetLauncherFactory
import io.novafoundation.nova.core_db.dao.BrowserTabsDao
import io.novafoundation.nova.feature_account_api.data.events.MetaAccountChangesEventBus
import io.novafoundation.nova.feature_account_api.data.externalAccounts.ExternalAccountsSyncService
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
import io.novafoundation.nova.feature_account_api.data.multisig.validation.MultisigExtrinsicValidationRequestBus
import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus
import io.novafoundation.nova.feature_account_api.di.deeplinks.AccountDeepLinks
import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults
import io.novafoundation.nova.feature_account_api.domain.cloudBackup.ApplyLocalSnapshotToCloudBackupUseCase
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_account_migration.di.deeplinks.AccountMigrationDeepLinks
import io.novafoundation.nova.feature_ahm_api.data.repository.ChainMigrationRepository
import io.novafoundation.nova.feature_ahm_api.data.repository.MigrationInfoRepository
import io.novafoundation.nova.feature_ahm_api.di.deeplinks.ChainMigrationDeepLinks
import io.novafoundation.nova.feature_ahm_api.domain.ChainMigrationDetailsSelectToShowUseCase
import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem
import io.novafoundation.nova.feature_assets.di.modules.deeplinks.AssetDeepLinks
import io.novafoundation.nova.feature_buy_api.di.deeplinks.BuyDeepLinks
import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository
import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsInteractor
import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor
import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserTabExternalRepository
import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository
import io.novafoundation.nova.feature_dapp_api.di.deeplinks.DAppDeepLinks
import io.novafoundation.nova.feature_deep_linking.presentation.handling.PendingDeepLinkProvider
import io.novafoundation.nova.feature_deep_linking.presentation.handling.branchIo.BranchIoLinkConverter
import io.novafoundation.nova.feature_deep_linking.presentation.handling.common.DeepLinkingPreferences
import io.novafoundation.nova.feature_gift_api.di.GiftDeepLinks
import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState
import io.novafoundation.nova.feature_governance_api.di.deeplinks.GovernanceDeepLinks
import io.novafoundation.nova.feature_multisig_operations.di.deeplink.MultisigDeepLinks
import io.novafoundation.nova.feature_push_notifications.domain.interactor.PushNotificationsInteractor
import io.novafoundation.nova.feature_push_notifications.domain.interactor.WelcomePushNotificationsInteractor
import io.novafoundation.nova.feature_push_notifications.presentation.multisigsWarning.MultisigPushNotificationsAlertMixinFactory
import io.novafoundation.nova.feature_staking_api.di.deeplinks.StakingDeepLinks
import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository
import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInteractor
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
import io.novafoundation.nova.feature_wallet_api.domain.validation.MultisigExtrinsicValidationFactory
import io.novafoundation.nova.feature_wallet_api.domain.validation.ProxyHaveEnoughFeeValidationFactory
import io.novafoundation.nova.feature_wallet_connect_api.di.deeplinks.WalletConnectDeepLinks
import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase
import io.novafoundation.nova.feature_wallet_connect_api.presentation.WalletConnectService
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection
import kotlinx.coroutines.flow.MutableStateFlow
interface RootDependencies {
val stakingDeepLinks: StakingDeepLinks
val accountDeepLinks: AccountDeepLinks
val dAppDeepLinks: DAppDeepLinks
val governanceDeepLinks: GovernanceDeepLinks
val buyDeepLinks: BuyDeepLinks
val assetDeepLinks: AssetDeepLinks
val giftDeepLinks: GiftDeepLinks
val chainMigrationDeepLinks: ChainMigrationDeepLinks
val walletConnectDeepLinks: WalletConnectDeepLinks
val systemCallExecutor: SystemCallExecutor
val contextManager: ContextManager
val walletConnectService: WalletConnectService
val imageLoader: ImageLoader
val automaticInteractionGate: AutomaticInteractionGate
val walletConnectSessionsUseCase: WalletConnectSessionsUseCase
val pushNotificationsInteractor: PushNotificationsInteractor
val welcomePushNotificationsInteractor: WelcomePushNotificationsInteractor
val applyLocalSnapshotToCloudBackupUseCase: ApplyLocalSnapshotToCloudBackupUseCase
val actionBottomSheetLauncherFactory: ActionBottomSheetLauncherFactory
val tabsDao: BrowserTabsDao
val balancesUpdateSystem: BalancesUpdateSystem
val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory
val browserTabExternalRepository: BrowserTabExternalRepository
val externalAccountsSyncService: ExternalAccountsSyncService
val multisigPendingOperationsService: MultisigPendingOperationsService
val accountMigrationDeepLinks: AccountMigrationDeepLinks
val multisigDeepLinks: MultisigDeepLinks
val deepLinkingPreferences: DeepLinkingPreferences
val branchIoLinkConverter: BranchIoLinkConverter
val pendingDeepLinkProvider: PendingDeepLinkProvider
val multisigExtrinsicValidationRequestBus: MultisigExtrinsicValidationRequestBus
val multisigExtrinsicValidationFactory: MultisigExtrinsicValidationFactory
val actionBottomSheetLauncher: ActionBottomSheetLauncher
val multisigPushNotificationsAlertMixinFactory: MultisigPushNotificationsAlertMixinFactory
val chainMigrationDetailsSelectToShowUseCase: ChainMigrationDetailsSelectToShowUseCase
val deviceNetworkStateObserver: DeviceNetworkStateObserver
fun updateNotificationsInteractor(): UpdateNotificationsInteractor
fun contributionsInteractor(): ContributionsInteractor
fun crowdloanRepository(): CrowdloanRepository
fun networkStateMixin(): NetworkStateMixin
fun externalRequirementsFlow(): MutableStateFlow<ChainConnection.ExternalRequirement>
fun accountRepository(): AccountRepository
fun walletRepository(): WalletRepository
fun appLinksProvider(): AppLinksProvider
fun resourceManager(): ResourceManager
fun currencyInteractor(): CurrencyInteractor
fun stakingRepository(): StakingRepository
fun chainRegistry(): ChainRegistry
fun backgroundAccessObserver(): BackgroundAccessObserver
fun safeModeService(): SafeModeService
fun rootScope(): RootScope
fun governanceStateUpdater(): MutableGovernanceState
fun dappMetadataRepository(): DAppMetadataRepository
fun encryptionDefaults(): EncryptionDefaults
fun proxyExtrinsicValidationRequestBus(): ProxyExtrinsicValidationRequestBus
fun metaAccountChangesRequestBus(): MetaAccountChangesEventBus
fun proxyHaveEnoughFeeValidationFactory(): ProxyHaveEnoughFeeValidationFactory
fun context(): Context
fun toastMessageManager(): ToastMessageManager
fun dialogMessageManager(): DialogMessageManager
fun chainMigrationRepository(): ChainMigrationRepository
fun migrationInfoRepository(): MigrationInfoRepository
}
@@ -0,0 +1,94 @@
package io.novafoundation.nova.app.root.di
import io.novafoundation.nova.app.root.navigation.holders.RootNavigationHolder
import io.novafoundation.nova.app.root.navigation.holders.SplitScreenNavigationHolder
import io.novafoundation.nova.app.root.navigation.navigators.Navigator
import io.novafoundation.nova.app.root.navigation.navigators.staking.StakingDashboardNavigator
import io.novafoundation.nova.common.di.FeatureApiHolder
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.common.navigation.DelayedNavigationRouter
import io.novafoundation.nova.core_db.di.DbApi
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter
import io.novafoundation.nova.feature_account_migration.di.AccountMigrationFeatureApi
import io.novafoundation.nova.feature_ahm_api.di.ChainMigrationFeatureApi
import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
import io.novafoundation.nova.feature_buy_api.di.BuyFeatureApi
import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi
import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi
import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi
import io.novafoundation.nova.feature_dapp_impl.presentation.DAppRouter
import io.novafoundation.nova.feature_deep_linking.di.DeepLinkingFeatureApi
import io.novafoundation.nova.feature_gift_api.di.GiftFeatureApi
import io.novafoundation.nova.feature_governance_api.di.GovernanceFeatureApi
import io.novafoundation.nova.feature_governance_impl.presentation.GovernanceRouter
import io.novafoundation.nova.feature_ledger_api.di.LedgerFeatureApi
import io.novafoundation.nova.feature_multisig_operations.di.MultisigOperationsFeatureApi
import io.novafoundation.nova.feature_push_notifications.di.PushNotificationsFeatureApi
import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi
import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter
import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi
import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi
import io.novafoundation.nova.feature_wallet_connect_api.di.WalletConnectFeatureApi
import io.novafoundation.nova.runtime.di.RuntimeApi
import javax.inject.Inject
@ApplicationScope
class RootFeatureHolder @Inject constructor(
private val splitScreenNavigationHolder: SplitScreenNavigationHolder,
private val rootNavigationHolder: RootNavigationHolder,
private val navigator: Navigator,
private val governanceRouter: GovernanceRouter,
private val dAppRouter: DAppRouter,
private val accountRouter: AccountRouter,
private val assetsRouter: AssetsRouter,
private val stakingRouter: StakingRouter,
private val stakingDashboardNavigator: StakingDashboardNavigator,
private val delayedNavRouter: DelayedNavigationRouter,
featureContainer: FeatureContainer
) : FeatureApiHolder(featureContainer) {
override fun initializeDependencies(): Any {
val rootFeatureDependencies = DaggerRootComponent_RootFeatureDependenciesComponent.builder()
.commonApi(commonApi())
.dbApi(getFeature(DbApi::class.java))
.accountFeatureApi(getFeature(AccountFeatureApi::class.java))
.walletFeatureApi(getFeature(WalletFeatureApi::class.java))
.stakingFeatureApi(getFeature(StakingFeatureApi::class.java))
.assetsFeatureApi(getFeature(AssetsFeatureApi::class.java))
.currencyFeatureApi(getFeature(CurrencyFeatureApi::class.java))
.crowdloanFeatureApi(getFeature(CrowdloanFeatureApi::class.java))
.governanceFeatureApi(getFeature(GovernanceFeatureApi::class.java))
.dAppFeatureApi(getFeature(DAppFeatureApi::class.java))
.runtimeApi(getFeature(RuntimeApi::class.java))
.versionsFeatureApi(getFeature(VersionsFeatureApi::class.java))
.walletConnectFeatureApi(getFeature(WalletConnectFeatureApi::class.java))
.pushNotificationsFeatureApi(getFeature(PushNotificationsFeatureApi::class.java))
.deepLinkingFeatureApi(getFeature(DeepLinkingFeatureApi::class.java))
.ledgerFeatureApi(getFeature(LedgerFeatureApi::class.java))
.buyFeatureApi(getFeature(BuyFeatureApi::class.java))
.deepLinkingFeatureApi(getFeature(DeepLinkingFeatureApi::class.java))
.accountMigrationFeatureApi(getFeature(AccountMigrationFeatureApi::class.java))
.multisigOperationsFeatureApi(getFeature(MultisigOperationsFeatureApi::class.java))
.chainMigrationFeatureApi(getFeature(ChainMigrationFeatureApi::class.java))
.giftFeatureApi(getFeature(GiftFeatureApi::class.java))
.build()
return DaggerRootComponent.factory()
.create(
splitScreenNavigationHolder,
rootNavigationHolder,
navigator,
governanceRouter,
dAppRouter,
assetsRouter,
accountRouter,
stakingRouter,
stakingDashboardNavigator,
delayedNavRouter,
rootFeatureDependencies
)
}
}
@@ -0,0 +1,47 @@
package io.novafoundation.nova.app.root.di
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.app.root.di.busHandler.RequestBusHandlerModule
import io.novafoundation.nova.app.root.di.deeplink.DeepLinksModule
import io.novafoundation.nova.app.root.domain.RootInteractor
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_account_api.data.externalAccounts.ExternalAccountsSyncService
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_ahm_api.data.repository.ChainMigrationRepository
import io.novafoundation.nova.feature_ahm_api.data.repository.MigrationInfoRepository
import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
@Module(
includes = [
RequestBusHandlerModule::class,
ExternalServiceInitializersModule::class,
DeepLinksModule::class
]
)
class RootFeatureModule {
@Provides
@FeatureScope
fun provideRootInteractor(
walletRepository: WalletRepository,
accountRepository: AccountRepository,
balancesUpdateSystem: BalancesUpdateSystem,
multisigPendingOperationsService: MultisigPendingOperationsService,
externalAccountsSyncService: ExternalAccountsSyncService,
chainMigrationRepository: ChainMigrationRepository,
migrationInfoRepository: MigrationInfoRepository
): RootInteractor {
return RootInteractor(
updateSystem = balancesUpdateSystem,
walletRepository = walletRepository,
accountRepository = accountRepository,
multisigPendingOperationsService = multisigPendingOperationsService,
externalAccountsSyncService = externalAccountsSyncService,
chainMigrationRepository = chainMigrationRepository,
migrationInfoRepository = migrationInfoRepository
)
}
}
@@ -0,0 +1,98 @@
package io.novafoundation.nova.app.root.di.busHandler
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
import io.novafoundation.nova.app.root.presentation.RootRouter
import io.novafoundation.nova.app.root.presentation.requestBusHandler.CloudBackupSyncRequestBusHandler
import io.novafoundation.nova.app.root.presentation.requestBusHandler.CompoundRequestBusHandler
import io.novafoundation.nova.app.root.presentation.requestBusHandler.MultisigExtrinsicValidationRequestBusHandler
import io.novafoundation.nova.app.root.presentation.requestBusHandler.ProxyExtrinsicValidationRequestBusHandler
import io.novafoundation.nova.app.root.presentation.requestBusHandler.PushSettingsSyncRequestBusHandler
import io.novafoundation.nova.app.root.presentation.requestBusHandler.RequestBusHandler
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
import io.novafoundation.nova.common.view.bottomSheet.action.ActionBottomSheetLauncher
import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus
import io.novafoundation.nova.feature_account_api.data.events.MetaAccountChangesEventBus
import io.novafoundation.nova.feature_account_api.data.multisig.validation.MultisigExtrinsicValidationRequestBus
import io.novafoundation.nova.feature_account_api.domain.cloudBackup.ApplyLocalSnapshotToCloudBackupUseCase
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_push_notifications.domain.interactor.PushNotificationsInteractor
import io.novafoundation.nova.feature_wallet_api.domain.validation.MultisigExtrinsicValidationFactory
import io.novafoundation.nova.feature_wallet_api.domain.validation.ProxyHaveEnoughFeeValidationFactory
@Module
class RequestBusHandlerModule {
@Provides
@FeatureScope
@IntoSet
fun providePushSettingsSyncRequestBusHandler(
metaAccountChangesEventBus: MetaAccountChangesEventBus,
pushNotificationsInteractor: PushNotificationsInteractor
): RequestBusHandler {
return PushSettingsSyncRequestBusHandler(
metaAccountChangesEventBus,
pushNotificationsInteractor
)
}
@Provides
@FeatureScope
@IntoSet
fun provideProxyExtrinsicValidationRequestBusHandler(
proxyProxyExtrinsicValidationRequestBus: ProxyExtrinsicValidationRequestBus,
proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory
): RequestBusHandler {
return ProxyExtrinsicValidationRequestBusHandler(
proxyProxyExtrinsicValidationRequestBus,
proxyHaveEnoughFeeValidationFactory
)
}
@Provides
@FeatureScope
@IntoSet
fun provideMultisigExtrinsicValidationRequestBusHandler(
multisigExtrinsicValidationRequestBus: MultisigExtrinsicValidationRequestBus,
multisigExtrinsicValidationFactory: MultisigExtrinsicValidationFactory
): RequestBusHandler {
return MultisigExtrinsicValidationRequestBusHandler(
multisigExtrinsicValidationRequestBus,
multisigExtrinsicValidationFactory
)
}
@Provides
@FeatureScope
@IntoSet
fun provideCloudBackupSyncRequestBusHandler(
rootRouter: RootRouter,
resourceManager: ResourceManager,
metaAccountChangesEventBus: MetaAccountChangesEventBus,
applyLocalSnapshotToCloudBackupUseCase: ApplyLocalSnapshotToCloudBackupUseCase,
accountRepository: AccountRepository,
actionBottomSheetLauncher: ActionBottomSheetLauncher,
automaticInteractionGate: AutomaticInteractionGate
): RequestBusHandler {
return CloudBackupSyncRequestBusHandler(
rootRouter,
resourceManager,
metaAccountChangesEventBus,
applyLocalSnapshotToCloudBackupUseCase,
accountRepository,
actionBottomSheetLauncher,
automaticInteractionGate
)
}
@Provides
@FeatureScope
fun provideCompoundRequestBusHandler(
handlers: Set<@JvmSuppressWildcards RequestBusHandler>
): CompoundRequestBusHandler {
return CompoundRequestBusHandler(handlers)
}
}
@@ -0,0 +1,75 @@
package io.novafoundation.nova.app.root.di.deeplink
import dagger.Module
import dagger.Provides
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_account_api.di.deeplinks.AccountDeepLinks
import io.novafoundation.nova.feature_account_migration.di.deeplinks.AccountMigrationDeepLinks
import io.novafoundation.nova.feature_ahm_api.di.deeplinks.ChainMigrationDeepLinks
import io.novafoundation.nova.feature_assets.di.modules.deeplinks.AssetDeepLinks
import io.novafoundation.nova.feature_buy_api.di.deeplinks.BuyDeepLinks
import io.novafoundation.nova.feature_dapp_api.di.deeplinks.DAppDeepLinks
import io.novafoundation.nova.feature_deep_linking.presentation.handling.DeepLinkHandler
import io.novafoundation.nova.feature_deep_linking.presentation.handling.PendingDeepLinkProvider
import io.novafoundation.nova.feature_deep_linking.presentation.handling.RootDeepLinkHandler
import io.novafoundation.nova.feature_deep_linking.presentation.handling.branchIo.BranchIOLinkHandler
import io.novafoundation.nova.feature_deep_linking.presentation.handling.branchIo.BranchIoLinkConverter
import io.novafoundation.nova.feature_gift_api.di.GiftDeepLinks
import io.novafoundation.nova.feature_governance_api.di.deeplinks.GovernanceDeepLinks
import io.novafoundation.nova.feature_multisig_operations.di.deeplink.MultisigDeepLinks
import io.novafoundation.nova.feature_staking_api.di.deeplinks.StakingDeepLinks
import io.novafoundation.nova.feature_wallet_connect_api.di.deeplinks.WalletConnectDeepLinks
@Module
class DeepLinksModule {
@Provides
@FeatureScope
fun provideDeepLinkHandlers(
stakingDeepLinks: StakingDeepLinks,
accountDeepLinks: AccountDeepLinks,
dAppDeepLinks: DAppDeepLinks,
governanceDeepLinks: GovernanceDeepLinks,
buyDeepLinks: BuyDeepLinks,
assetDeepLinks: AssetDeepLinks,
walletConnectDeepLinks: WalletConnectDeepLinks,
accountMigrationDeepLinks: AccountMigrationDeepLinks,
multisigDeepLinks: MultisigDeepLinks,
chainMigrationDeepLinks: ChainMigrationDeepLinks,
giftDeepLinks: GiftDeepLinks
): List<@JvmWildcard DeepLinkHandler> {
return buildList {
addAll(stakingDeepLinks.deepLinkHandlers)
addAll(accountDeepLinks.deepLinkHandlers)
addAll(dAppDeepLinks.deepLinkHandlers)
addAll(governanceDeepLinks.deepLinkHandlers)
addAll(buyDeepLinks.deepLinkHandlers)
addAll(assetDeepLinks.deepLinkHandlers)
addAll(walletConnectDeepLinks.deepLinkHandlers)
addAll(accountMigrationDeepLinks.deepLinkHandlers)
addAll(multisigDeepLinks.deepLinkHandlers)
addAll(chainMigrationDeepLinks.deepLinkHandlers)
addAll(giftDeepLinks.deepLinkHandlers)
}
}
@Provides
@FeatureScope
fun provideRootDeepLinkHandler(
pendingDeepLinkProvider: PendingDeepLinkProvider,
nestedHandlers: @JvmWildcard List<DeepLinkHandler>
): RootDeepLinkHandler {
return RootDeepLinkHandler(
pendingDeepLinkProvider,
nestedHandlers
)
}
@Provides
@FeatureScope
fun provideBranchIOLinkHandler(
branchIoLinkConverter: BranchIoLinkConverter
): BranchIOLinkHandler {
return BranchIOLinkHandler(branchIoLinkConverter)
}
}
@@ -0,0 +1,56 @@
package io.novafoundation.nova.app.root.domain
import io.novafoundation.nova.common.data.memory.ComputationalScope
import io.novafoundation.nova.core.updater.Updater
import io.novafoundation.nova.feature_account_api.data.externalAccounts.ExternalAccountsSyncService
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_ahm_api.data.repository.ChainMigrationRepository
import io.novafoundation.nova.feature_ahm_api.data.repository.MigrationInfoRepository
import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem
import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository
import kotlinx.coroutines.flow.Flow
class RootInteractor(
private val updateSystem: BalancesUpdateSystem,
private val walletRepository: WalletRepository,
private val accountRepository: AccountRepository,
private val multisigPendingOperationsService: MultisigPendingOperationsService,
private val externalAccountsSyncService: ExternalAccountsSyncService,
private val chainMigrationRepository: ChainMigrationRepository,
private val migrationInfoRepository: MigrationInfoRepository
) {
fun runBalancesUpdate(): Flow<Updater.SideEffect> = updateSystem.start()
suspend fun updatePhishingAddresses() {
runCatching {
walletRepository.updatePhishingAddresses()
}
}
suspend fun isAccountSelected(): Boolean {
return accountRepository.isAccountSelected()
}
suspend fun isPinCodeSet(): Boolean {
return accountRepository.isCodeSet()
}
fun syncExternalAccounts() {
externalAccountsSyncService.sync()
}
context(ComputationalScope)
fun syncPendingMultisigOperations(): Flow<Unit> {
return multisigPendingOperationsService.performMultisigOperationsSync()
}
suspend fun cacheBalancesForChainMigrationDetection() {
chainMigrationRepository.cacheBalancesForChainMigrationDetection()
}
suspend fun loadMigrationDetailsConfigs() {
migrationInfoRepository.loadConfigs()
}
}
@@ -0,0 +1,23 @@
package io.novafoundation.nova.app.root.domain
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.feature_dapp_api.data.model.SimpleTabModel
import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserTabExternalRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
class SplitScreenInteractor(
private val repository: BrowserTabExternalRepository,
private val accountRepository: AccountRepository
) {
fun observeTabNamesById(): Flow<List<SimpleTabModel>> {
return accountRepository.selectedMetaAccountFlow()
.flatMapLatest { repository.observeTabsWithNames(it.id) }
}
suspend fun removeAllTabs() {
val metaAccount = accountRepository.getSelectedMetaAccount()
repository.removeTabsForMetaAccount(metaAccount.id)
}
}
@@ -0,0 +1,39 @@
package io.novafoundation.nova.app.root.navigation
import android.annotation.SuppressLint
import android.os.Bundle
import androidx.annotation.IdRes
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.NavGraph
import io.novafoundation.nova.app.R
import io.novafoundation.nova.app.root.navigation.delayedNavigation.NavComponentDelayedNavigation
import io.novafoundation.nova.app.root.navigation.navigators.BaseNavigator
import io.novafoundation.nova.app.root.presentation.splitScreen.SplitScreenFragment
import io.novafoundation.nova.app.root.presentation.splitScreen.SplitScreenPayload
@SuppressLint("RestrictedApi")
fun NavController.getBackStackEntryBefore(@IdRes id: Int): NavBackStackEntry {
val initial = getBackStackEntry(id)
val backStack = backStack.toList()
val initialIndex = backStack.indexOf(initial)
var previousIndex = initialIndex - 1
// ignore nav graphs
while (previousIndex > 0 && backStack[previousIndex].destination is NavGraph) {
previousIndex--
}
return backStack[previousIndex]
}
fun BaseNavigator.openSplitScreenWithInstantAction(actionId: Int, nestedActionExtras: Bundle? = null) {
val delayedNavigation = NavComponentDelayedNavigation(actionId, nestedActionExtras)
val splitScreenPayload = SplitScreenPayload.InstantNavigationOnAttach(delayedNavigation)
navigationBuilder().action(R.id.action_open_split_screen)
.setArgs(SplitScreenFragment.createPayload(splitScreenPayload))
.navigateInRoot()
}
@@ -0,0 +1,46 @@
package io.novafoundation.nova.app.root.navigation
import io.novafoundation.nova.common.navigation.InterScreenCommunicator
import io.novafoundation.nova.common.utils.singleReplaySharedFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
abstract class FlowInterScreenCommunicator<I : Any, O : Any> :
InterScreenCommunicator<I, O>,
CoroutineScope by CoroutineScope(Dispatchers.Main) {
private var response: O? = null
override val responseFlow = singleReplaySharedFlow<O>()
private var _request: I? = null
override val latestResponse: O?
get() = response
override val lastState: O?
get() = latestResponse
override val lastInput: I?
get() = _request
abstract fun dispatchRequest(request: I)
@OptIn(ExperimentalCoroutinesApi::class)
override fun openRequest(request: I) {
_request = request
response = null
responseFlow.resetReplayCache()
dispatchRequest(request)
}
override fun respond(response: O) {
launch {
this@FlowInterScreenCommunicator.response = response
responseFlow.emit(response)
}
}
}
@@ -0,0 +1,78 @@
package io.novafoundation.nova.app.root.navigation
import android.os.Parcelable
import androidx.annotation.CallSuper
import androidx.lifecycle.asFlow
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import io.novafoundation.nova.app.root.navigation.navigators.NavigationHoldersRegistry
import io.novafoundation.nova.app.root.navigation.navigators.builder.NavigationBuilderRegistry
import io.novafoundation.nova.app.root.navigation.navigators.navigationBuilder
import io.novafoundation.nova.common.navigation.InterScreenCommunicator
import kotlinx.coroutines.flow.Flow
import java.util.UUID
abstract class NavStackInterScreenCommunicator<I : Parcelable, O : Parcelable>(
private val navigationHoldersRegistry: NavigationHoldersRegistry
) : InterScreenCommunicator<I, O> {
private val responseKey = UUID.randomUUID().toString()
private val requestKey = UUID.randomUUID().toString()
protected val navController: NavController
get() = navigationHoldersRegistry.firstAttachedNavController!!
// from requester - retrieve from current entry
override val latestResponse: O?
get() = navController.currentBackStackEntry!!.savedStateHandle
.get(responseKey)
// from responder - retrieve from previous (requester) entry
override val lastState: O?
get() = navController.previousBackStackEntry!!.savedStateHandle
.get(responseKey)
override val responseFlow: Flow<O>
get() = createResponseFlow()
// from responder - retrieve from previous (requester) entry
override val lastInput: I?
get() = navController.previousBackStackEntry!!.savedStateHandle
.get(requestKey)
@CallSuper
override fun openRequest(request: I) {
saveRequest(request)
}
fun clearedResponseFlow(): Flow<O> {
navController.currentBackStackEntry!!.savedStateHandle.apply {
remove<O>(requestKey)
remove<O>(responseKey)
}
return createResponseFlow()
}
override fun respond(response: O) {
// previousBackStackEntry since we want to report to previous screen
saveResultTo(navController.previousBackStackEntry!!, response)
}
protected fun saveResultTo(backStackEntry: NavBackStackEntry, response: O) {
backStackEntry.savedStateHandle.set(responseKey, response)
}
private fun saveRequest(request: I) {
navController.currentBackStackEntry!!.savedStateHandle.set(requestKey, request)
}
private fun createResponseFlow(): Flow<O> {
return navController.currentBackStackEntry!!.savedStateHandle
.getLiveData<O>(responseKey)
.asFlow()
}
protected fun navigationBuilder(): NavigationBuilderRegistry {
return navigationHoldersRegistry.navigationBuilder()
}
}
@@ -0,0 +1,7 @@
package io.novafoundation.nova.app.root.navigation.delayedNavigation
import io.novafoundation.nova.common.navigation.DelayedNavigation
import kotlinx.parcelize.Parcelize
@Parcelize
object BackDelayedNavigation : DelayedNavigation
@@ -0,0 +1,8 @@
package io.novafoundation.nova.app.root.navigation.delayedNavigation
import android.os.Bundle
import io.novafoundation.nova.common.navigation.DelayedNavigation
import kotlinx.parcelize.Parcelize
@Parcelize
class NavComponentDelayedNavigation(val globalActionId: Int, val extras: Bundle? = null) : DelayedNavigation
@@ -0,0 +1,46 @@
package io.novafoundation.nova.app.root.navigation.holders
import androidx.navigation.NavController
import io.novafoundation.nova.common.resources.ContextManager
abstract class NavigationHolder(val contextManager: ContextManager) {
var navController: NavController? = null
private set
fun isControllerAttached(): Boolean {
return navController != null
}
fun attach(navController: NavController) {
this.navController = navController
}
/**
* Detaches the current navController only if it matches the one provided.
* This check ensures that if a new screen with a navController is attached,
* it doesn't lose its navController when the previous screen calls detach.
* By verifying equality, we prevent unintended detachment.
*/
fun detachNavController(navController: NavController) {
if (this.navController == navController) {
this.navController = null
}
}
fun detach() {
navController = null
}
fun finishApp() {
contextManager.getActivity()?.finish()
}
fun executeBack() {
val popped = navController!!.popBackStack()
if (!popped) {
contextManager.getActivity()!!.finish()
}
}
}
@@ -0,0 +1,5 @@
package io.novafoundation.nova.app.root.navigation.holders
import io.novafoundation.nova.common.resources.ContextManager
class RootNavigationHolder(contextManager: ContextManager) : NavigationHolder(contextManager)
@@ -0,0 +1,5 @@
package io.novafoundation.nova.app.root.navigation.holders
import io.novafoundation.nova.common.resources.ContextManager
class SplitScreenNavigationHolder(contextManager: ContextManager) : NavigationHolder(contextManager)
@@ -0,0 +1,8 @@
package io.novafoundation.nova.app.root.navigation.navigationFragment
import io.novafoundation.nova.app.R
class MainNavHostFragment : NovaNavHostFragment() {
override val containerId: Int = R.id.mainNavHost
}
@@ -0,0 +1,20 @@
package io.novafoundation.nova.app.root.navigation.navigationFragment
import android.annotation.SuppressLint
import androidx.navigation.NavController
import androidx.navigation.fragment.DialogFragmentNavigator
import androidx.navigation.fragment.NavHostFragment
import io.novafoundation.nova.app.root.navigation.navigators.AddFragmentNavigator
abstract class NovaNavHostFragment : NavHostFragment() {
abstract val containerId: Int
@SuppressLint("MissingSuperCall")
override fun onCreateNavController(navController: NavController) {
navController.navigatorProvider.addNavigator(DialogFragmentNavigator(requireContext(), childFragmentManager))
val addFragmentNavigator = AddFragmentNavigator(requireContext(), childFragmentManager, containerId)
navController.navigatorProvider.addNavigator(addFragmentNavigator)
}
}

Some files were not shown because too many files have changed in this diff Show More