Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
005c39f
Add referral programme and promo-code client.
JonnyLuca Aug 29, 2026
13bf537
Cover shareReferralInviteOrigin fallback when the box is empty or mis…
JonnyLuca Aug 29, 2026
2048691
Cover ReferralCreatedInviteDto.fromJson when code is missing or blank.
JonnyLuca Aug 29, 2026
ce99963
Cover ReferralEntryCard tap through to the referral route.
JonnyLuca Aug 29, 2026
38de29e
Cover ReferralEligibilityCubit.reload timeout and initial-failure paths.
JonnyLuca Aug 29, 2026
5453b11
Cover ReferralCodeLookupDto.campaignTextLang when there is no copy.
JonnyLuca Aug 29, 2026
eca1011
Cover ReferralBindResultDto.campaignTextLang when there is no copy.
JonnyLuca Aug 29, 2026
1bb7afc
Cover ReferralInviteDto EN copy text and missing-code throw.
JonnyLuca Aug 29, 2026
d45d355
Cover localizedReferralError tokens for eligibility, terms, and bind.
JonnyLuca Aug 29, 2026
9f5fa58
Cover ReferralCopyInviteButton unmount during an in-flight clipboard …
JonnyLuca Aug 29, 2026
fc47c73
Cover ReferralPage load and terms-accepting create-invite listener.
JonnyLuca Aug 29, 2026
7322e33
Cover parseReferralDecimal mixed thousands and decimal separators.
JonnyLuca Aug 29, 2026
95e82a8
Cover RealUnitReferralService ApiException throws on non-success HTTP…
JonnyLuca Aug 29, 2026
375c2db
Cover scheduleReferralBind, deferred unavailable Retry, and restash p…
JonnyLuca Aug 29, 2026
8be56e8
Cover ReferralCubit non-ApiException catch paths after accept, create…
JonnyLuca Aug 29, 2026
a058288
Cover ReferralTermsPage SettingsBloc language and in-app terms link t…
JonnyLuca Aug 29, 2026
b04e11d
Cover ReferralCreatePage load, overview submit, and in-flight create …
JonnyLuca Aug 29, 2026
9f8cc99
Cover ReferralOverviewPage invite-list loading and create CTA open lock.
JonnyLuca Aug 29, 2026
b117f0f
Cover ReferralOverviewPage non-const constructor for suite lcov.
JonnyLuca Aug 29, 2026
3d19dbd
Cover ReferralCodeField stash races, clipboard, GetIt lookup, and pro…
JonnyLuca Aug 29, 2026
da88459
Cover remaining normalize_referral_code paste wrappers and HTML entit…
JonnyLuca Aug 29, 2026
641ec08
Add integration_test for the referral Einstiegskarte and macos runner.
JonnyLuca Aug 29, 2026
f82199f
Add integration_test for the referral terms checkbox gate.
JonnyLuca Aug 29, 2026
5f06ee8
Add integration_test for creating a named referral invite.
JonnyLuca Aug 29, 2026
bc3b900
Add integration_test for copying and sharing a referral invite.
JonnyLuca Aug 29, 2026
854ba9d
Add integration_test for the referral overview counts and share price.
JonnyLuca Aug 29, 2026
2e2630a
Add integration_test for the referral prize in transaction history.
JonnyLuca Aug 29, 2026
0bdefb9
Add integration_test for the registration invite and promo code step.
JonnyLuca Aug 29, 2026
7812791
Add integration_test for invite and promo App Link deeplinks.
JonnyLuca Aug 29, 2026
d7c05a5
Add integration_test for invalid code, not-eligible, and prize-limit …
JonnyLuca Aug 29, 2026
6752f70
Add KYC referral golden for the invalid-code lookup error.
JonnyLuca Aug 29, 2026
02fb5fc
Hide ReferralEntryCard when the referral service is not registered.
JonnyLuca Aug 29, 2026
c03e576
test(goldens): regenerate baselines on the self-hosted runner
github-actions[bot] Aug 30, 2026
984a69a
Add referral overview golden for the empty invite list.
JonnyLuca Aug 30, 2026
be49eb1
test(goldens): regenerate baselines on the self-hosted runner
github-actions[bot] Aug 30, 2026
cb29993
Add remaining referral and promo screen-state goldens.
JonnyLuca Aug 30, 2026
fa17ab0
Clear the KYC referral loading golden lookup timeout after the frame.
JonnyLuca Aug 30, 2026
fddf0a1
test(goldens): regenerate baselines on the self-hosted runner
github-actions[bot] Aug 30, 2026
eb05d60
Fix referral entry integration tap on macOS
JonnyLuca Aug 30, 2026
0453045
Set the macOS integration test view dimensions
JonnyLuca Aug 30, 2026
8de65a5
Commit iOS and macOS build migrations
JonnyLuca Aug 30, 2026
8326bf0
Stabilize pointer E2E tests on headless macOS
JonnyLuca Aug 30, 2026
b9e46ab
Pin referral lookup name fields
JonnyLuca Aug 30, 2026
a215c49
Pin localized referral invite responses
JonnyLuca Aug 30, 2026
6d5e6ec
Guard visible referral invite states
JonnyLuca Aug 30, 2026
344a3b8
Pin referral summary count contract
JonnyLuca Aug 30, 2026
edff12a
Pin referral running valuation contract
JonnyLuca Aug 30, 2026
04ec624
Pin live referral terms contract
JonnyLuca Aug 30, 2026
94e7527
Format referral terms contract test
JonnyLuca Aug 30, 2026
1442127
Send accepted referral terms version
JonnyLuca Aug 30, 2026
620701b
Format referral terms version contract
JonnyLuca Aug 30, 2026
0994ae9
Align invite creation request contract
JonnyLuca Aug 30, 2026
0bf5eb1
Restore iOS framework deployment target
JonnyLuca Aug 30, 2026
955f8fd
fix(referral): correctness fixes from review (part 1)
JonnyLuca Sep 4, 2026
35b0b0f
fix(referral): conformity fixes from review (part 2)
JonnyLuca Sep 4, 2026
0312c7c
test(goldens): regenerate baselines on the self-hosted runner
github-actions[bot] Sep 4, 2026
11e7c1f
refactor(referral): drop the orphaned getPayouts service method
JonnyLuca Sep 4, 2026
9589344
fix(referral): address review findings (part 3)
JonnyLuca Sep 4, 2026
aa9f054
fix(referral): tie terms text to its version and fix an import order
JonnyLuca Sep 4, 2026
a167ffc
style(referral): clear the analyzer lints in the referral surfaces
JonnyLuca Sep 4, 2026
76701ba
style(referral): drop a stray blank line before a closing brace
JonnyLuca Sep 4, 2026
b20172a
fix(referral): restore flaky handbook baselines and close two coverag…
JonnyLuca Sep 5, 2026
3269aef
test(referral): allowlist the install-referrer port for the visibilit…
JonnyLuca Sep 5, 2026
ec9946c
fix(dashboard): pin the clock so the handbook persona goldens are det…
JonnyLuca Sep 5, 2026
a3a5a9f
fix(dashboard): evaluate the persona fixtures inside the pinned clock…
JonnyLuca Sep 5, 2026
055423b
chore(referral): align dashboard chart internals with develop
JonnyLuca Sep 5, 2026
6713a14
test(goldens): pin the wall clock for the handbook persona charts
JonnyLuca Sep 5, 2026
0a4ab85
test(goldens): keep persona fixtures static, pin only the chart axis …
JonnyLuca Sep 5, 2026
cd9bb0c
fix(referral): guard eligibility emits and gate the KYC deeplink prefill
JonnyLuca Sep 5, 2026
9e0d1eb
style(referral): sort the frozen_chf_label import into the widgets group
JonnyLuca Sep 5, 2026
cba1b56
fix(referral): reject foreign invite hosts and infer promo from perso…
JonnyLuca Sep 7, 2026
f349e62
style(referral): use theme text styles and sort pending-code imports.
JonnyLuca Sep 7, 2026
2df0c9d
fix(referral): drop same-code restash after bind and stop inventing m…
JonnyLuca Sep 7, 2026
02eaf9f
fix(referral): stash only during KYC so submit cannot rebind the same…
JonnyLuca Sep 7, 2026
e902351
fix(referral): defer bind on pushed KYC and discard leftover stash at…
JonnyLuca Sep 7, 2026
c4f0e1b
fix(referral): bind a KYC-deferred stash when the KYC route is popped.
JonnyLuca Sep 7, 2026
ddef568
fix(referral): serialize stash writes and return create NEEDS_TERMS t…
JonnyLuca Sep 7, 2026
50d31e8
fix(referral): consume the Play install referrer only after a success…
JonnyLuca Sep 7, 2026
51eeac3
fix(referral): bind a code stashed during an empty take.
JonnyLuca Sep 7, 2026
fddd0cb
style(referral): use theme text styles for error copy.
JonnyLuca Sep 7, 2026
316e06c
fix(referral): drop stale prefs writes after take and re-read discard.
JonnyLuca Sep 7, 2026
94f932d
fix(referral): treat a missing payout status as not settled.
JonnyLuca Sep 7, 2026
fd84fcc
fix(history): do not swallow referral-payout parse and repo errors.
JonnyLuca Sep 7, 2026
e509552
fix(referral): bind pending codes without waiting for a restored rout…
JonnyLuca Sep 7, 2026
9cede3f
fix(referral): treat FormatException as a terminal bind failure.
JonnyLuca Sep 7, 2026
6e4e775
fix(referral): do not emit ReferralCubit states after close.
JonnyLuca Sep 7, 2026
0e450ea
fix(kyc): keep checkKyc running if referral stash I/O fails.
JonnyLuca Sep 7, 2026
c442adb
fix(kyc): stash a resolved referral code before submit.
JonnyLuca Sep 7, 2026
bdb909e
fix(history): drop the redundant referral-payout hash lookup.
JonnyLuca Sep 7, 2026
6bbe5a5
fix(referral): treat only true/1 as a JSON boolean true.
JonnyLuca Sep 7, 2026
76c95e4
style(i18n): sort referral ARB keys case-insensitively.
JonnyLuca Sep 7, 2026
f810180
style(referral): hide referralTermsOpensInApp from production API.
JonnyLuca Sep 7, 2026
f13b9dc
test(referral): drop the tautological overview constructor check.
JonnyLuca Sep 7, 2026
73c4764
test(referral): abort stalled referral HTTP with fakeAsync.
JonnyLuca Sep 7, 2026
1209167
docs(referral): drop the stray credited fragment and fix guillemets.
JonnyLuca Sep 7, 2026
9b207d6
ci(handbook): probe screenshot files on disk, not via 401.
JonnyLuca Sep 7, 2026
5f2b79f
test(referral): treat FormatException as a terminal bind failure.
JonnyLuca Sep 7, 2026
03bd66c
test(referral): cover concurrent take and discard of a pending code.
JonnyLuca Sep 7, 2026
268309d
test(referral): do not emit ReferralCubit states after close.
JonnyLuca Sep 7, 2026
aa56139
test(referral): bind a restored /settings route without waiting for pop.
JonnyLuca Sep 7, 2026
89b9725
test(referral): run referral e2e specs under flutter test.
JonnyLuca Sep 7, 2026
d04f3c9
style(test): sort imports in new referral test files.
JonnyLuca Sep 7, 2026
c8bc281
fix(kyc): discard skipped typed codes and stash before submit.
JonnyLuca Sep 7, 2026
20ac0ef
fix(referral): treat /pay over /kyc as still in KYC.
JonnyLuca Sep 7, 2026
9e55d6c
fix(history): surface referral-payout parse errors after writing vali…
JonnyLuca Sep 7, 2026
d8c6082
chore(pubspec): drop integration_test from the lockfile.
JonnyLuca Sep 7, 2026
9156e15
test(referral): require concurrent take/discard to empty the stash.
JonnyLuca Sep 7, 2026
966927a
fix(kyc): guard the registration submit path as a whole.
JonnyLuca Sep 8, 2026
8078d54
fix(home): surface history sync failures to the user.
JonnyLuca Sep 8, 2026
c0db466
fix(history): skip non-settled payouts before the strict parse.
JonnyLuca Sep 8, 2026
3b027a4
test(referral): fail if concurrent take/discard leaves a claimable code.
JonnyLuca Sep 8, 2026
d5441d2
style(referral): conformity nits from the runde-3 review.
JonnyLuca Sep 8, 2026
9cb8115
chore(pubspec): re-resolve the lockfile with Flutter 3.41.6.
JonnyLuca Sep 8, 2026
0f2345d
fix(referral): close runde-4 parse, normalize, and scheme-filter holes.
JonnyLuca Sep 8, 2026
5f28d03
fix(kyc): hold the registration submit guard until navigation.
JonnyLuca Sep 8, 2026
f0baaaa
fix(home): reset historySyncFailed when a new sync starts.
JonnyLuca Sep 8, 2026
49317ff
fix(referral): fail closed on status, kind, 404, and create races.
JonnyLuca Sep 8, 2026
8d39b35
fix(ci): verify referral legal files and allow macOS release network.
JonnyLuca Sep 8, 2026
8da37ed
fix(referral): fail summary load when a credited prize has no sum.
JonnyLuca Sep 8, 2026
3c3e397
fix(referral): round frozen CHF half-up from the decimal string.
JonnyLuca Sep 8, 2026
f33bdf9
fix(history): serialize mixed-case tx inserts in one isolate.
JonnyLuca Sep 8, 2026
6cced15
fix(settings): indent the Scaffold after ReferralEligibilityResumeRel…
JonnyLuca Sep 8, 2026
14440d5
test(referral): dispose GoRouters and tap buttons instead of onPressed.
JonnyLuca Sep 8, 2026
dd9b2ec
docs(referral): close the runde-4 lane A copy and dartdoc gaps.
JonnyLuca Sep 8, 2026
702a6b6
fix(kyc): persist the typed code via a production stash-current check.
JonnyLuca Sep 8, 2026
de89306
fix(android): keep pathless open and opaque wallet scheme URIs matching.
JonnyLuca Sep 8, 2026
949aefd
fix(history): type the referral payout response so analyze passes
TaprootFreakAI Sep 8, 2026
e070340
fix(kyc): catch the missing submit cubit instead of FlutterError
TaprootFreakAI Sep 8, 2026
e574f33
test(referral): stub createInvite with the idempotency key
TaprootFreakAI Sep 8, 2026
1bb89cf
test(referral): pump the router and localizations in the KYC bind tests
TaprootFreakAI Sep 8, 2026
c4639e8
fix(home): only emit when a history-sync failure is actually cleared
TaprootFreakAI Sep 8, 2026
cc6b4b8
test(routing): give the boot-navigation bind test its localizations
TaprootFreakAI Sep 8, 2026
90a1cb6
fix(referral): let the promo dialog be left without confirming
TaprootFreakAI Sep 8, 2026
64f9f26
fix(referral): make a hung invite share abandonable
TaprootFreakAI Sep 8, 2026
18653d3
fix(referral): tie the share timeout to its own attempt
TaprootFreakAI Sep 8, 2026
88dd0de
docs(referral): describe the campaign dialog as leavable
TaprootFreakAI Sep 8, 2026
db45a08
test(referral): cover negative frozen CHF amounts
TaprootFreakAI Sep 8, 2026
982979b
fix(referral): drop a pending code when the wallet is reset
TaprootFreakAI Sep 8, 2026
b961ee6
test(goldens): regenerate baselines on the self-hosted runner
github-actions[bot] Sep 8, 2026
51aab7e
fix(referral): mint a fresh idempotency key when the guest name changes
TaprootFreakAI Sep 8, 2026
f7b20f0
test(referral): cover the share-sheet timeout path
TaprootFreakAI Sep 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coverage-visibility-allowlist
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# Abstract interfaces / ports (implementations live elsewhere and are tested):
lib/packages/io/backup_exclusion_port.dart
lib/packages/io/documents_directory_port.dart
lib/packages/io/install_referrer_port.dart
lib/packages/service/biometric/biometric_port.dart
lib/packages/service/price_service.dart
lib/screens/kyc/steps/ident/cubits/kyc_ident/sumsub_ident_port.dart
Expand Down
25 changes: 11 additions & 14 deletions .github/workflows/handbook-build-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ jobs:
set -euo pipefail
bash scripts/assemble-handbook-screenshots.sh /tmp/handbook-shots
count=$(ls -1 /tmp/handbook-shots/*.png | wc -l | tr -d ' ')
if [ "$count" != "284" ]; then
echo "expected 284 screenshots, got $count" >&2
if [ "$count" != "299" ]; then
echo "expected 299 screenshots, got $count" >&2
exit 1
fi

Expand Down Expand Up @@ -173,15 +173,13 @@ jobs:
exit 1
fi

# Screenshots dir must contain all 284 PNGs assembled from Goldens.
# Hit one of them through the auth gate to verify wiring end-to-end.
# Mix of the original 01-61 range and the 62-269 batch (every Golden
# baseline) so a regression in either half surfaces here.
for name in 01-welcome 11-dashboard 26-terms 35-dashboard-with-balance 46-buy-kyc-required 52-sell-unknown-error 53-buy-payment-details 61-kyc-registration-tax-tin-error 62-welcome-page-android 219-settings-security-page-default 268-phone-number-field-default 269-dashboard-insider-unlocked 270-handbook-persona-dca 275-handbook-persona-exit-1j; do
code=$(curl -s -o /dev/null -w '%{http_code}' -u "${HANDBOOK_USER:-x}:${HANDBOOK_PASS:-x}" "http://127.0.0.1:8080/screenshots/${name}.png")
# 200 (auth happens to match) or 401 (auth fails but file exists)
# both prove the file is on disk. 404 means it was not assembled.
if [ "$code" = "404" ]; then
# Spot-check handbook screenshots assembled from Goldens (299 PNGs).
# Mix of original 01-61 slots, later Golden batches, and referral
# baselines so a regression in any range surfaces here.
for name in 01-welcome 11-dashboard 26-terms 35-dashboard-with-balance 46-buy-kyc-required 52-sell-unknown-error 53-buy-payment-details 61-kyc-registration-tax-tin-error 62-welcome-page-android 219-settings-security-page-default 268-phone-number-field-default 269-dashboard-insider-unlocked 270-handbook-persona-dca 275-handbook-persona-exit-1j 276-referral-overview 282-referral-payout-row 283-referral-terms-readonly 287-kyc-registration-referral-recognized 288-transaction-history-referral-payout 289-dashboard-referral-entry-and-payout 290-kyc-registration-referral-promo; do
# Auth is checked before the file exists: a missing PNG also
# returns 401, so HTTP status cannot prove assembly. Look on disk.
if ! docker exec handbook test -f "/usr/share/nginx/html/screenshots/${name}.png"; then
echo "screenshot ${name}.png missing from /usr/share/nginx/html/screenshots/" >&2
docker logs handbook
exit 1
Expand All @@ -191,9 +189,8 @@ jobs:
# Legal downloads must be present (built by the legal-docs-builder
# stage via pandoc). Same 200/401-vs-404 logic: 404 means the PDF/DOCX
# was not produced. Covers PDF + DOCX and both languages.
for f in legal/privacy_policy_de.pdf legal/privacy_policy_de.docx legal/terms_of_use_en.pdf legal/registration_agreement_de.docx; do
code=$(curl -s -o /dev/null -w '%{http_code}' -u "${HANDBOOK_USER:-x}:${HANDBOOK_PASS:-x}" "http://127.0.0.1:8080/${f}")
if [ "$code" = "404" ]; then
for f in legal/privacy_policy_de.pdf legal/privacy_policy_de.docx legal/terms_of_use_en.pdf legal/registration_agreement_de.docx legal/referral_terms_de.pdf legal/referral_terms_en.pdf legal/referral_terms_de.docx legal/referral_terms_en.docx; do
if ! docker exec handbook test -f "/usr/share/nginx/html/${f}"; then
echo "legal download ${f} missing from /usr/share/nginx/html/legal/" >&2
docker logs handbook
exit 1
Expand Down
6 changes: 6 additions & 0 deletions .maestro/handbook/11-dashboard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ appId: swiss.realunit.app
- extendedWaitUntil:
visible: 'RealUnit kaufen'
timeout: 30000
# Fresh handbook wallet is not a verified shareholder with 70 REALU
# (TB Ziff. 2, analog Verkaufen). The dashboard card must stay off this
# screen; 289-dashboard-referral-entry-and-payout is golden-only.
- extendedWaitUntil:
notVisible: 'Erhalte 20 REALU pro Weiterempfehlung'
timeout: 10000
10 changes: 10 additions & 0 deletions .maestro/handbook/12-settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,13 @@ appId: swiss.realunit.app
- extendedWaitUntil:
visible: 'Einstellungen'
timeout: 30000
# Same gate as the dashboard card. Wait for a settings tile that does not
# depend on the referral API, then assert Empfehlungen is still absent so
# a late summary 200 cannot have opened the tile on a fresh wallet.
- extendedWaitUntil:
visible:
text: '.*Rechtsdokumente.*'
timeout: 15000
- extendedWaitUntil:
notVisible: 'Empfehlungen'
timeout: 8000
17 changes: 17 additions & 0 deletions .maestro/handbook/20-settings-legal-documents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,20 @@ appId: swiss.realunit.app
visible:
text: '.*Dokumente der Aktionariat AG.*'
timeout: 30000
# Last tile is the 14.08 Teilnahmebedingungen (Ziff. 2–11). On a phone it can
# sit below the fold, so scroll it into view, then restore Aktionariat so
# flow 21 can tap that entry.
- scrollUntilVisible:
element:
text: '.*Teilnahmebedingungen Referral-Programm.*'
direction: DOWN
timeout: 15000
- extendedWaitUntil:
visible:
text: '.*Teilnahmebedingungen Referral-Programm.*'
timeout: 10000
- scrollUntilVisible:
element:
text: '.*Dokumente der Aktionariat AG.*'
direction: UP
timeout: 15000
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ Non-BitBox code only needs Tier 0 + widget tests; Tier 1+ are reserved for hardw
| Coverage | `flutter test --coverage` | Writes `coverage/lcov.info`. CI narrows it to the activated surface and hard-fails when scoped coverage drops below the floor in `.coverage-floor-lines` / `.coverage-floor-functions`. See "Coverage infrastructure roadmap" above for the ratchet protocol. |
| Analyzer | `flutter analyze` | Dart static analysis per `analysis_options.yaml` |

Tier 1 specs live under `test/integration/**` and run inside the same `flutter test --coverage` invocation as Tier 0 — no separate `integration_test/` harness today (that Flutter-convention directory is reserved for on-device runs that are not yet wired up). Tier 3 handbook flows (iOS Simulator) are wired via [`tier3-handbook.yaml`](.github/workflows/tier3-handbook.yaml); the BitBox02 hardware variant remains deferred.
Tier 1 specs live under `test/integration/**` and run inside the same `flutter test --coverage` invocation as Tier 0 — no separate `integration_test/` harness today (that Flutter-convention directory is reserved for on-device runs that are not yet wired up). Referral widget flows live under `test/screens/referral/` (including the former `integration_test/referral_*_e2e_test.dart` specs, now at `test/screens/referral/flows/`) and run in that same coverage invocation. Tier 3 handbook flows (iOS Simulator) are wired via [`tier3-handbook.yaml`](.github/workflows/tier3-handbook.yaml); the BitBox02 hardware variant remains deferred.

## CI/CD

Expand Down
6 changes: 6 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ linter:
avoid_print: true
prefer_const_constructors: true
prefer_single_quotes: true
analyzer:
exclude:
- build/**
- android/**
- ios/**
- macos/**
6 changes: 5 additions & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ if (keystorePropertiesFile.exists()) {
android {
namespace = "swiss.realunit.app"
compileSdk = 36
ndkVersion = "29.0.13113456 rc1"
ndkVersion = "29.0.13113456"

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
Expand Down Expand Up @@ -64,3 +64,7 @@ android {
flutter {
source = "../.."
}

dependencies {
implementation "com.android.installreferrer:installreferrer:2.2"
}
7 changes: 5 additions & 2 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@
-keep class com.google.android.gms.tasks.** { *; }

# SumSub SDK general keep rules
-keep class com.sumsub.** { *; }
-dontwarn com.sumsub.**
-keep class com.sumsub.** { *; }
-dontwarn com.sumsub.**

# Play Install Referrer (invite code across a fresh Android install)
-keep class com.android.installreferrer.** { *; }
28 changes: 28 additions & 0 deletions android/app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,32 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- Staging App Links. Kept out of the release autoVerify filter so a
missing https://dev.realunit.app/.well-known/assetlinks.json cannot
fail Play verification for realunit.app. -->
<application>
<activity android:name=".MainActivity">
<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"
android:host="dev.realunit.app"
android:pathPrefix="/invite/"/>
<data
android:scheme="https"
android:host="dev.realunit.app"
android:pathPrefix="/promo/"/>
<data
android:scheme="https"
android:host="dev.realunit.app"
android:path="/invite"/>
<data
android:scheme="https"
android:host="dev.realunit.app"
android:path="/promo"/>
</intent-filter>
</activity>
</application>
</manifest>
59 changes: 58 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,70 @@
realunit-wallet://open). Opening the app via the scheme only
foregrounds it; go_router redirects any scheme URL to the normal
entry. A custom scheme needs no autoVerify / Digital Asset
Links. -->
Links. Multiple <data> in one filter are sets (scheme × host ×
path), not tuples: a pathPrefix here would require a path and
drop realunit-wallet://open. Opaque URIs (lightning, invite/code
without //) have no host and need a scheme-only filter. -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="realunit-wallet" android:host="open"/>
<data android:scheme="realunit-wallet" android:host="invite"/>
<data android:scheme="realunit-wallet" android:host="promo"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="realunit-wallet"/>
</intent-filter>
<!-- HTTPS App Links for invite and promo codes (verified against
Digital Asset Links). autoVerify checks every host in this
filter: a missing assetlinks.json on any of them fails the
whole filter, so release only lists realunit.app / www.
Trailing slash on pathPrefix is required: "/invite" as a
prefix would also match "/invitee". Exact android:path
covers bare /invite?code=. Custom-scheme filter above still
covers realunit-wallet://invite|promo/{code}. Staging
host lives in debug/profile manifests. -->
<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"
android:host="realunit.app"
android:pathPrefix="/invite/"/>
<data
android:scheme="https"
android:host="realunit.app"
android:pathPrefix="/promo/"/>
<data
android:scheme="https"
android:host="realunit.app"
android:path="/invite"/>
<data
android:scheme="https"
android:host="realunit.app"
android:path="/promo"/>
<data
android:scheme="https"
android:host="www.realunit.app"
android:pathPrefix="/invite/"/>
<data
android:scheme="https"
android:host="www.realunit.app"
android:pathPrefix="/promo/"/>
<data
android:scheme="https"
android:host="www.realunit.app"
android:path="/invite"/>
<data
android:scheme="https"
android:host="www.realunit.app"
android:path="/promo"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
Expand Down
61 changes: 60 additions & 1 deletion android/app/src/main/kotlin/swiss/dfx/realunit/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,64 @@
package swiss.realunit.app

import android.os.Handler
import android.os.Looper
import com.android.installreferrer.api.InstallReferrerClient
import com.android.installreferrer.api.InstallReferrerStateListener
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterFragmentActivity()
class MainActivity : FlutterFragmentActivity() {
private val installReferrerChannel = "swiss.realunit.app/install_referrer"

override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, installReferrerChannel)
.setMethodCallHandler { call, result ->
if (call.method != "readInstallReferrer") {
result.notImplemented()
return@setMethodCallHandler
}
readInstallReferrer(result)
}
}

private fun readInstallReferrer(result: MethodChannel.Result) {
val client = InstallReferrerClient.newBuilder(this).build()
val main = Handler(Looper.getMainLooper())
var replied = false

fun reply(value: String?) {
if (replied) return
replied = true
result.success(value)
try {
client.endConnection()
} catch (_: Exception) {
}
}

main.postDelayed({ reply(null) }, 3000)
try {
client.startConnection(
object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
if (responseCode != InstallReferrerClient.InstallReferrerResponse.OK) {
reply(null)
return
}
try {
reply(client.installReferrer.installReferrer)
} catch (_: Exception) {
reply(null)
}
}

override fun onInstallReferrerServiceDisconnected() {}
},
)
} catch (_: Exception) {
reply(null)
}
}
}
25 changes: 25 additions & 0 deletions android/app/src/profile/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,29 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<application>
<activity android:name=".MainActivity">
<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"
android:host="dev.realunit.app"
android:pathPrefix="/invite/"/>
<data
android:scheme="https"
android:host="dev.realunit.app"
android:pathPrefix="/promo/"/>
<data
android:scheme="https"
android:host="dev.realunit.app"
android:path="/invite"/>
<data
android:scheme="https"
android:host="dev.realunit.app"
android:path="/promo"/>
</intent-filter>
</activity>
</application>
</manifest>
4 changes: 4 additions & 0 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
# This builtInKotlin flag was added automatically by Flutter migrator
android.builtInKotlin=false
# This newDsl flag was added automatically by Flutter migrator
android.newDsl=false
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
Loading
Loading