From cc99542fc0e9ed3b6d2ca2bbb05328e00b4d8c01 Mon Sep 17 00:00:00 2001
From: Jonny Luca <320529100+JonnyLuca@users.noreply.github.com>
Date: Sat, 29 Aug 2026 17:23:22 +0000
Subject: [PATCH 01/55] Add referral programme and promo-code client.
---
.github/workflows/handbook-build-check.yaml | 8 +-
.maestro/handbook/11-dashboard.yaml | 6 +
.maestro/handbook/12-settings.yaml | 10 +
.../handbook/20-settings-legal-documents.yaml | 17 +
android/app/build.gradle | 6 +-
android/app/proguard-rules.pro | 5 +-
android/app/src/debug/AndroidManifest.xml | 28 +
android/app/src/main/AndroidManifest.xml | 46 +
.../kotlin/swiss/dfx/realunit/MainActivity.kt | 61 +-
android/app/src/profile/AndroidManifest.xml | 25 +
android/gradle.properties | 4 +
.../gradle/wrapper/gradle-wrapper.properties | 2 +-
assets/languages/strings_de.arb | 49 +
assets/languages/strings_en.arb | 49 +
assets/legal/referral_terms_de.md | 47 +
assets/legal/referral_terms_en.md | 47 +
docs/handbook/README.md | 13 +-
docs/handbook/de/index.html | 453 ++++++
docs/referral-programme.md | 548 +++++++
docs/screens.md | 22 +-
ios/Runner.xcodeproj/project.pbxproj | 4 +-
ios/Runner/Runner.entitlements | 5 +
ios/Runner/RunnerDebug.entitlements | 16 +
lib/models/transaction.dart | 23 +-
lib/packages/io/install_referrer_adapter.dart | 30 +
lib/packages/io/install_referrer_port.dart | 10 +
lib/packages/io/normalize_referral_code.dart | 809 ++++++++++
lib/packages/io/parse_install_referrer.dart | 100 ++
.../repository/transaction_repository.dart | 114 +-
.../dto/referral_bind_result_dto.dart | 88 +
.../dto/referral_code_lookup_dto.dart | 85 +
.../dto/referral_created_invite_dto.dart | 48 +
.../referral/dto/referral_invite_dto.dart | 102 ++
.../referral/dto/referral_payout_dto.dart | 59 +
.../referral/dto/referral_summary_dto.dart | 68 +
.../referral/dto/referral_terms_dto.dart | 31 +
.../dfx/models/referral/locale_text.dart | 8 +
.../models/referral/referral_json_list.dart | 230 +++
.../dfx/models/referral/referral_kind.dart | 25 +
.../dfx/real_unit_referral_service.dart | 278 ++++
.../service/dfx/referral_lookup_status.dart | 38 +
.../service/referral_payout_merger.dart | 54 +
.../service/transaction_history_service.dart | 177 +-
.../storage/dfx_transaction_storage.dart | 17 +
lib/packages/storage/transaction_storage.dart | 19 +-
lib/screens/dashboard/dashboard_page.dart | 2 +
.../dashboard/widgets/transaction_row.dart | 110 ++
.../kyc_registration_step_cubit.dart | 3 +-
.../kyc_registration_step_state.dart | 6 +-
.../registration/kyc_registration_page.dart | 35 +-
.../stash_resolved_referral_code.dart | 11 +
.../steps/kyc_registration_referral_step.dart | 135 ++
.../widgets/referral_code_field.dart | 612 +++++++
.../referral/cubit/referral_cubit.dart | 241 +++
.../cubit/referral_eligibility_cubit.dart | 63 +
.../cubit/referral_eligibility_state.dart | 35 +
.../referral/cubit/referral_state.dart | 139 ++
lib/screens/referral/format_frozen_chf.dart | 38 +
lib/screens/referral/frozen_chf_label.dart | 30 +
lib/screens/referral/load_referral_terms.dart | 30 +
.../referral/open_referral_create.dart | 35 +
lib/screens/referral/open_referral_terms.dart | 12 +
.../referral/referral_create_page.dart | 387 +++++
.../referral/referral_error_message.dart | 104 ++
lib/screens/referral/referral_limits.dart | 24 +
.../referral/referral_overview_page.dart | 406 +++++
lib/screens/referral/referral_page.dart | 139 ++
lib/screens/referral/referral_share_text.dart | 34 +
lib/screens/referral/referral_terms_page.dart | 338 ++++
.../referral/share_referral_invite.dart | 35 +
.../widgets/referral_copy_invite_button.dart | 112 ++
.../widgets/referral_eligibility_resume.dart | 116 ++
.../referral/widgets/referral_entry_card.dart | 62 +
.../widgets/referral_loading_status.dart | 33 +
.../widgets/referral_share_invite_button.dart | 107 ++
lib/screens/settings/settings_page.dart | 300 ++--
.../settings_legal_documents_page.dart | 10 +
.../transaction_history_download_button.dart | 9 +-
.../widgets/transaction_history_row.dart | 113 +-
lib/setup/di.dart | 11 +
lib/setup/routing/boot_navigation.dart | 7 +
.../routing/capture_install_referrer.dart | 42 +
lib/setup/routing/referral_bind.dart | 303 ++++
lib/setup/routing/referral_pending_code.dart | 73 +
lib/setup/routing/router_config.dart | 23 +
lib/setup/routing/routes/app_link_entry.dart | 228 ++-
lib/setup/routing/routes/legal_routes.dart | 1 +
lib/setup/routing/routes/settings_routes.dart | 2 +
lib/widgets/buttons/app_filled_button.dart | 12 +-
lib/widgets/form/labeled_text_field.dart | 42 +-
pubspec.lock | 16 +
pubspec.yaml | 1 +
scripts/assemble-handbook-legal.py | 21 +-
scripts/assemble-handbook-screenshots.sh | 19 +-
scripts/build-legal-downloads.sh | 8 +-
scripts/run-handbook-flows.sh | 6 +-
.../dashboard/dashboard_golden_test.dart | 4 +
.../dashboard_states_golden_test.dart | 56 +
.../dashboard_referral_entry_and_payout.png | Bin 0 -> 63880 bytes
...kyc_registration_referral_step_default.png | Bin 0 -> 25695 bytes
.../kyc_registration_referral_step_promo.png | Bin 0 -> 76279 bytes
..._registration_referral_step_recognized.png | Bin 0 -> 34443 bytes
.../kyc/kyc_registration_golden_test.dart | 2 +
..._registration_page_states_golden_test.dart | 16 +-
...egistration_referral_step_golden_test.dart | 103 ++
.../referral_create_page_invite_created.png | Bin 0 -> 29679 bytes
.../macos/referral_create_page_name_entry.png | Bin 0 -> 18082 bytes
.../macos/referral_entry_card_eligible.png | Bin 0 -> 11636 bytes
.../macos/referral_gate_not_eligible.png | Bin 0 -> 16817 bytes
.../macos/referral_overview_page_default.png | Bin 0 -> 52650 bytes
...eferral_payout_transaction_row_default.png | Bin 0 -> 15484 bytes
.../macos/referral_terms_page_default.png | Bin 0 -> 59999 bytes
.../macos/referral_terms_page_readonly.png | Bin 0 -> 49357 bytes
.../referral_overview_golden_test.dart | 85 +
.../referral_surfaces_golden_test.dart | 252 +++
.../macos/settings_page_referral_eligible.png | Bin 0 -> 48336 bytes
.../settings/settings_golden_test.dart | 34 +
...gs_legal_documents_page_referral_terms.png | Bin 0 -> 58971 bytes
.../settings_legal_documents_golden_test.dart | 16 +
...ansaction_history_page_referral_payout.png | Bin 0 -> 21740 bytes
...ransaction_history_states_golden_test.dart | 31 +
test/helper/golden_mocks.dart | 3 +
test/helper/pump_app.dart | 2 +
test/helper/responsive_surface_catalog.dart | 20 +-
test/models/transaction_test.dart | 39 +-
.../config/legal_documents_config_test.dart | 1 +
.../io/normalize_referral_code_test.dart | 955 +++++++++++
.../io/parse_install_referrer_test.dart | 242 +++
.../transaction_repository_test.dart | 334 +++-
.../models/referral/referral_dtos_test.dart | 920 +++++++++++
.../referral/referral_json_list_test.dart | 274 ++++
.../registration_user_type_test.dart | 1 +
.../dfx/real_unit_referral_service_test.dart | 678 ++++++++
.../dfx/referral_lookup_status_test.dart | 61 +
.../service/referral_payout_merger_test.dart | 220 +++
...transaction_history_service_sync_test.dart | 472 +++++-
.../storage/dfx_transaction_storage_test.dart | 9 +
.../widgets/payment_action_button_test.dart | 1 +
.../referral_payout_transaction_row_test.dart | 100 ++
.../kyc/steps/kyc_email_page_test.dart | 2 +
.../kyc/steps/kyc_registration_page_test.dart | 93 +-
.../kyc_registration_step_cubit_test.dart | 22 +-
.../stash_resolved_referral_code_test.dart | 23 +
.../kyc_registration_referral_step_test.dart | 443 +++++
.../widgets/referral_code_field_test.dart | 1364 ++++++++++++++++
.../referral/format_frozen_chf_test.dart | 45 +
.../referral/load_referral_terms_test.dart | 81 +
.../referral/open_referral_create_test.dart | 208 +++
.../referral/open_referral_terms_test.dart | 46 +
.../referral_copy_invite_button_test.dart | 319 ++++
.../referral/referral_create_page_test.dart | 1081 +++++++++++++
.../screens/referral/referral_cubit_test.dart | 920 +++++++++++
.../referral_eligibility_cubit_test.dart | 236 +++
.../referral/referral_entry_card_test.dart | 347 ++++
.../referral/referral_error_message_test.dart | 241 +++
.../referral/referral_limits_test.dart | 20 +
.../referral/referral_overview_page_test.dart | 1430 +++++++++++++++++
test/screens/referral/referral_page_test.dart | 354 ++++
.../referral_responsive_matrix_test.dart | 235 +++
.../referral_share_invite_button_test.dart | 231 +++
.../referral/referral_share_text_test.dart | 112 ++
.../referral/referral_terms_page_test.dart | 579 +++++++
.../referral/share_referral_invite_test.dart | 67 +
...sell_bank_account_selection_page_test.dart | 1 +
.../widgets/sell_executed_sheet_test.dart | 1 +
.../settings_referral_entry_test.dart | 111 ++
.../settings_contact_page_test.dart | 2 +
.../settings_legal_documents_page_test.dart | 52 +-
.../settings_security_page_test.dart | 2 +
.../support_email_capture_page_test.dart | 5 +
.../support_chat_message_bubble_test.dart | 1 +
...nsaction_history_download_button_test.dart | 160 ++
...transaction_history_referral_row_test.dart | 175 ++
test/setup/routing/app_link_entry_test.dart | 649 ++++++--
.../capture_install_referrer_test.dart | 89 +
test/setup/routing/pay_route_test.dart | 1 +
test/setup/routing/pay_send_routes_test.dart | 1 +
test/setup/routing/referral_bind_test.dart | 843 ++++++++++
.../routing/referral_pending_code_test.dart | 66 +
test/widgets/buttons/app_buttons_test.dart | 35 +-
test/widgets/form/form_fields_test.dart | 81 +
181 files changed, 23810 insertions(+), 460 deletions(-)
create mode 100644 assets/legal/referral_terms_de.md
create mode 100644 assets/legal/referral_terms_en.md
create mode 100644 docs/referral-programme.md
create mode 100644 ios/Runner/RunnerDebug.entitlements
create mode 100644 lib/packages/io/install_referrer_adapter.dart
create mode 100644 lib/packages/io/install_referrer_port.dart
create mode 100644 lib/packages/io/normalize_referral_code.dart
create mode 100644 lib/packages/io/parse_install_referrer.dart
create mode 100644 lib/packages/service/dfx/models/referral/dto/referral_bind_result_dto.dart
create mode 100644 lib/packages/service/dfx/models/referral/dto/referral_code_lookup_dto.dart
create mode 100644 lib/packages/service/dfx/models/referral/dto/referral_created_invite_dto.dart
create mode 100644 lib/packages/service/dfx/models/referral/dto/referral_invite_dto.dart
create mode 100644 lib/packages/service/dfx/models/referral/dto/referral_payout_dto.dart
create mode 100644 lib/packages/service/dfx/models/referral/dto/referral_summary_dto.dart
create mode 100644 lib/packages/service/dfx/models/referral/dto/referral_terms_dto.dart
create mode 100644 lib/packages/service/dfx/models/referral/locale_text.dart
create mode 100644 lib/packages/service/dfx/models/referral/referral_json_list.dart
create mode 100644 lib/packages/service/dfx/models/referral/referral_kind.dart
create mode 100644 lib/packages/service/dfx/real_unit_referral_service.dart
create mode 100644 lib/packages/service/dfx/referral_lookup_status.dart
create mode 100644 lib/packages/service/referral_payout_merger.dart
create mode 100644 lib/screens/kyc/steps/registration/stash_resolved_referral_code.dart
create mode 100644 lib/screens/kyc/steps/registration/steps/kyc_registration_referral_step.dart
create mode 100644 lib/screens/kyc/steps/registration/widgets/referral_code_field.dart
create mode 100644 lib/screens/referral/cubit/referral_cubit.dart
create mode 100644 lib/screens/referral/cubit/referral_eligibility_cubit.dart
create mode 100644 lib/screens/referral/cubit/referral_eligibility_state.dart
create mode 100644 lib/screens/referral/cubit/referral_state.dart
create mode 100644 lib/screens/referral/format_frozen_chf.dart
create mode 100644 lib/screens/referral/frozen_chf_label.dart
create mode 100644 lib/screens/referral/load_referral_terms.dart
create mode 100644 lib/screens/referral/open_referral_create.dart
create mode 100644 lib/screens/referral/open_referral_terms.dart
create mode 100644 lib/screens/referral/referral_create_page.dart
create mode 100644 lib/screens/referral/referral_error_message.dart
create mode 100644 lib/screens/referral/referral_limits.dart
create mode 100644 lib/screens/referral/referral_overview_page.dart
create mode 100644 lib/screens/referral/referral_page.dart
create mode 100644 lib/screens/referral/referral_share_text.dart
create mode 100644 lib/screens/referral/referral_terms_page.dart
create mode 100644 lib/screens/referral/share_referral_invite.dart
create mode 100644 lib/screens/referral/widgets/referral_copy_invite_button.dart
create mode 100644 lib/screens/referral/widgets/referral_eligibility_resume.dart
create mode 100644 lib/screens/referral/widgets/referral_entry_card.dart
create mode 100644 lib/screens/referral/widgets/referral_loading_status.dart
create mode 100644 lib/screens/referral/widgets/referral_share_invite_button.dart
create mode 100644 lib/setup/routing/capture_install_referrer.dart
create mode 100644 lib/setup/routing/referral_bind.dart
create mode 100644 lib/setup/routing/referral_pending_code.dart
create mode 100644 test/goldens/screens/dashboard/goldens/macos/dashboard_referral_entry_and_payout.png
create mode 100644 test/goldens/screens/kyc/goldens/macos/kyc_registration_referral_step_default.png
create mode 100644 test/goldens/screens/kyc/goldens/macos/kyc_registration_referral_step_promo.png
create mode 100644 test/goldens/screens/kyc/goldens/macos/kyc_registration_referral_step_recognized.png
create mode 100644 test/goldens/screens/kyc/kyc_registration_referral_step_golden_test.dart
create mode 100644 test/goldens/screens/referral/goldens/macos/referral_create_page_invite_created.png
create mode 100644 test/goldens/screens/referral/goldens/macos/referral_create_page_name_entry.png
create mode 100644 test/goldens/screens/referral/goldens/macos/referral_entry_card_eligible.png
create mode 100644 test/goldens/screens/referral/goldens/macos/referral_gate_not_eligible.png
create mode 100644 test/goldens/screens/referral/goldens/macos/referral_overview_page_default.png
create mode 100644 test/goldens/screens/referral/goldens/macos/referral_payout_transaction_row_default.png
create mode 100644 test/goldens/screens/referral/goldens/macos/referral_terms_page_default.png
create mode 100644 test/goldens/screens/referral/goldens/macos/referral_terms_page_readonly.png
create mode 100644 test/goldens/screens/referral/referral_overview_golden_test.dart
create mode 100644 test/goldens/screens/referral/referral_surfaces_golden_test.dart
create mode 100644 test/goldens/screens/settings/goldens/macos/settings_page_referral_eligible.png
create mode 100644 test/goldens/screens/settings_legal_documents/goldens/macos/settings_legal_documents_page_referral_terms.png
create mode 100644 test/goldens/screens/transaction_history/goldens/macos/transaction_history_page_referral_payout.png
create mode 100644 test/packages/io/normalize_referral_code_test.dart
create mode 100644 test/packages/io/parse_install_referrer_test.dart
create mode 100644 test/packages/service/dfx/models/referral/referral_dtos_test.dart
create mode 100644 test/packages/service/dfx/models/referral/referral_json_list_test.dart
create mode 100644 test/packages/service/dfx/real_unit_referral_service_test.dart
create mode 100644 test/packages/service/dfx/referral_lookup_status_test.dart
create mode 100644 test/packages/service/referral_payout_merger_test.dart
create mode 100644 test/screens/dashboard/widgets/referral_payout_transaction_row_test.dart
create mode 100644 test/screens/kyc/steps/registration/stash_resolved_referral_code_test.dart
create mode 100644 test/screens/kyc/steps/registration/steps/kyc_registration_referral_step_test.dart
create mode 100644 test/screens/kyc/steps/registration/widgets/referral_code_field_test.dart
create mode 100644 test/screens/referral/format_frozen_chf_test.dart
create mode 100644 test/screens/referral/load_referral_terms_test.dart
create mode 100644 test/screens/referral/open_referral_create_test.dart
create mode 100644 test/screens/referral/open_referral_terms_test.dart
create mode 100644 test/screens/referral/referral_copy_invite_button_test.dart
create mode 100644 test/screens/referral/referral_create_page_test.dart
create mode 100644 test/screens/referral/referral_cubit_test.dart
create mode 100644 test/screens/referral/referral_eligibility_cubit_test.dart
create mode 100644 test/screens/referral/referral_entry_card_test.dart
create mode 100644 test/screens/referral/referral_error_message_test.dart
create mode 100644 test/screens/referral/referral_limits_test.dart
create mode 100644 test/screens/referral/referral_overview_page_test.dart
create mode 100644 test/screens/referral/referral_page_test.dart
create mode 100644 test/screens/referral/referral_responsive_matrix_test.dart
create mode 100644 test/screens/referral/referral_share_invite_button_test.dart
create mode 100644 test/screens/referral/referral_share_text_test.dart
create mode 100644 test/screens/referral/referral_terms_page_test.dart
create mode 100644 test/screens/referral/share_referral_invite_test.dart
create mode 100644 test/screens/settings/settings_referral_entry_test.dart
create mode 100644 test/screens/transaction_history/transaction_history_download_button_test.dart
create mode 100644 test/screens/transaction_history/transaction_history_referral_row_test.dart
create mode 100644 test/setup/routing/capture_install_referrer_test.dart
create mode 100644 test/setup/routing/referral_bind_test.dart
create mode 100644 test/setup/routing/referral_pending_code_test.dart
diff --git a/.github/workflows/handbook-build-check.yaml b/.github/workflows/handbook-build-check.yaml
index 788b7a7e5..6ea8dcfd5 100644
--- a/.github/workflows/handbook-build-check.yaml
+++ b/.github/workflows/handbook-build-check.yaml
@@ -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
@@ -173,11 +173,11 @@ jobs:
exit 1
fi
- # Screenshots dir must contain all 284 PNGs assembled from Goldens.
+ # Screenshots dir must contain all 299 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
+ 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
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.
diff --git a/.maestro/handbook/11-dashboard.yaml b/.maestro/handbook/11-dashboard.yaml
index a24a2bd08..85b04f5b7 100644
--- a/.maestro/handbook/11-dashboard.yaml
+++ b/.maestro/handbook/11-dashboard.yaml
@@ -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
diff --git a/.maestro/handbook/12-settings.yaml b/.maestro/handbook/12-settings.yaml
index 2491e4850..f33254f54 100644
--- a/.maestro/handbook/12-settings.yaml
+++ b/.maestro/handbook/12-settings.yaml
@@ -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
diff --git a/.maestro/handbook/20-settings-legal-documents.yaml b/.maestro/handbook/20-settings-legal-documents.yaml
index d75298723..e11b27811 100644
--- a/.maestro/handbook/20-settings-legal-documents.yaml
+++ b/.maestro/handbook/20-settings-legal-documents.yaml
@@ -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
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 0a1b23b87..d6e33b5cb 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -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
@@ -64,3 +64,7 @@ android {
flutter {
source = "../.."
}
+
+dependencies {
+ implementation "com.android.installreferrer:installreferrer:2.2"
+}
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
index 53960b220..d6b437275 100644
--- a/android/app/proguard-rules.pro
+++ b/android/app/proguard-rules.pro
@@ -18,4 +18,7 @@
# SumSub SDK general keep rules
-keep class com.sumsub.** { *; }
--dontwarn com.sumsub.**
+-dontwarn com.sumsub.**
+
+# Play Install Referrer (invite code across a fresh Android install)
+-keep class com.android.installreferrer.** { *; }
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
index 399f6981d..2aa4793da 100644
--- a/android/app/src/debug/AndroidManifest.xml
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -4,4 +4,32 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
+ Verifizierte Aktionäre mit ausreichendem Bestand sehen auf dem Dashboard
+ die Karte Empfehlungen («Erhalte 20 REALU pro Weiterempfehlung»)
+ und denselben Einstieg in den Einstellungen. Sichtbarkeit, Mindestbestand
+ und Auszahlung entscheidet die DFX API; die App rendert
+ eligible 1:1. Nach einem fehlgeschlagenen ersten
+ Summary-GET (unmounted, 503, Timeout) blendet die Karte aus;
+ solange die Route aktuell und die App im Vordergrund ist, wird
+ die Gate erneut geladen, ohne eine bereits sichtbare Karte zu
+ verstecken. Ein 200 mit eligible: false wird nicht
+ gepollt.
+
+ Einladung erstellen: Name der eingeladenen Person (eine Zeile,
+ Zeilenumbrüche werden zu Leerzeichen, höchstens 80 Zeichen;
+ Absenden räumt den Feldtext auf; Autofokus), Checkbox
+ Ich habe die Teilnahmebedingungen gelesen und akzeptiert
+ (Checkbox erst nach geladenem TB-Text; Button erst danach aktiv;
+ der TB-Text ist markierbar), persönlicher Link von der API.
+ Der Einladungslink ist immer
+ https://realunit.app/invite/…
+ (http://, //realunit.app und
+ www.realunit.app werden auf den Apex gefaltet;
+ ein API-Pfad /promo/… bleibt Promo).
+ Nach dem Erstellen und in der Übersicht erscheint der personalisierte
+ Share-Text unter Persönlicher Einladungslink; Kopieren und
+ Versenden schicken denselben Text.
+ Nach der Annahme bleiben die Teilnahmebedingungen über den
+ Übersichtstitel und Einstellungen → Rechtliche Dokumente
+ (letzter Eintrag) lesbar. Kopieren bestätigt mit
+ Kopiert für zwei Sekunden nur nach erfolgreichem Schreiben
+ in die Zwischenablage und liest den Share-Text vor; ein erneutes
+ Tippen kopiert wieder.
+ Versenden setzt Titel und Betreff Persönlicher Einladungslink
+ und verankert das iPad/Mac-Share-Popover am Button (sonst am
+ Bildschirm). Kopiert-Status fällt zurück, wenn der Share-Text
+ wechselt. Übersicht zeigt Anzahl offen /
+ gutgeschrieben (vorgelesen «3 Offen») sowie Summe REALU und Frankenwert; offene Einladungen
+ lassen sich erneut kopieren und versenden, ohne den Status der
+ eingeladenen Person. Offene Einladungen verfallen nach 3 Monaten.
+ Kachel-Beschriftung
+ Aktienkurs. Jede Gutschrift in der Historie zeigt ganze REALU,
+ Datum und den im Zeitpunkt der Gutschrift fixierten Frankenwert
+ (eine Vorlese-Zeile: Titel, Datum, CHF, Anzahl) —
+ kein späteres Nachziehen auf den aktuellen Kurs.
+
+ Promo-Code und Einladungscode teilen dasselbe optionale
+ Registrierungs-Schritt (Überspringen erlaubt) und denselben Deep Link.
+ Während der Prüfung zeigt das Feld Code wird geprüft….
+ Nach der Prüfung erscheint bei einem Promo-Code der
+ Aktionstext in einem eigenen Fenster (nur Schließen, kein Tippen
+ daneben); bei einer Einladung die Erkennung
+ mit dem Namen des Empfehlers. Ein eingefügter
+ realunit.app/invite|promo/…-Link oder eine Share-Nachricht
+ wird auf den Code reduziert (Zero-Width- und Bidi-Zeichen aus
+ Messenger-Copy werden entfernt, auch im Play-Referrer und direkt
+ im Eingabefeld; Einfügen ignoriert eine Zwischenablage nur aus
+ Formatzeichen); das Feld hat Autofokus und fügt einen von der
+ Landing kopierten Code selbst ein, wenn es leer ist
+ (Deeplink-Stash geht vor). Weiter/Überspringen
+ sperren das Feld, bis sie durch sind, und brechen eine
+ ausstehende Code-Prüfung ab (Weiter wartet auf die Prüfung;
+ Überspringen setzt Weiter nicht auf Laden). Überspringen
+ verwirft einen getippten Code und lässt einen
+ Deeplink-Stash stehen. Ein abgelehnter Bind (4xx) zeigt
+ Link ungültig oder abgelaufen einmal in einem Dialog. 20 REALU als Promo-Zugabe nur beim ersten erfolgreichen
+ Kauf von mindestens N REALU (Vorgabe 200, durch die API gesetzt).
+ Einlösung und Kumulierungssperre sind Server-Pflicht. Universal Links:
+ https://realunit.app/invite/… und
+ /promo/… öffnen die App (auch auf www.realunit.app,
+ HTTP 200, kein 301). Nach Neuinstallation auf iOS den
+ Link erneut antippen oder den Code von der Landing-Seite
+ (App-Store-Tap kopiert ihn) bei der Registrierung einfügen. Android trägt den
+ Code als Play-Store-Install-Referrer, schon während die Landing
+ den Code prüft; ein ungültiger Lookup entfernt den Referrer.
+ Auf iOS führt der App-Store-Badge, auf Android Play. Die
+ In-App-CTA bleibt bis zum Lookup-Ende verborgen und gilt nur mobil.
+ Landing und Registrierung zeigen den Code in Grossbuchstaben
+ und höchstens 32 Zeichen (wie die API); ein angehängtes
+ Satzzeichen (`!`, `?`, `/`) fällt weg. Steckt
+ invite/ oder promo/ im Token,
+ bleibt nur der Code (wie die API). Unsichtbare
+ Steuerzeichen (ZWSP) und Vollbreite-Zeichen aus Messenger-URLs
+ entfernt die Landing-HTML schon in den Bytes, die Safari für
+ den Smart App Banner ausliest. Dasselbe gilt für Play/Ads-
+ utm_content=invite= und referrer auf
+ der Landing-URL, nicht nur für /invite/{code}
+ — auch ohne https:// (realunit.app/invite/…).
+ Facebook u=, Google q=, Outlook
+ url= und E-Mail link= auf der
+ Landing-URL ebenso, ein bloßes
+ u=hello oder eine fremde https://-URL
+ nicht. Ein Kampagnenname in utm_content verdeckt
+ keinen späteren Wrapper-Schlüssel, und eine Tracking-URL, die
+ nur realunit.app nennt, auch nicht. Einfügen und App
+ Links verwenden dieselben Schlüssel. Ein leeres oder fremdes
+ code= verdeckt kein späteres invite=.
+ Proofpoint URL Defense, Outlook Safe Links und kurze
+ ios-app://-Alternate-Links um eine
+ RealUnit-Landing werden entpackt.
+ Safari erkennt den Code und das Aktionsdatum nicht als Telefon- oder
+ Kalender-Link (format-detection
+ telephone=no, date=no;
+ x-apple-data-detectors="false" am Code und
+ Aktionstext, weil Lookup-JS den Text nachlädt).
+ Tab-Titel, Open-Graph, Twitter und die Standard-Beschreibung folgen
+ Laden / ungültig / nicht erreichbar; ein Pfad ohne Code gilt als
+ ungültig. Ungültige Überschrift und Text sind statusrot;
+ die Browser-Themenfarbe folgt (`#E02523` ungültig, sonst Markenblau).
+ og:locale:alternate nennt die andere Sprache (de_CH / en_GB).
+ Crawler lesen Smart-App-Banner app-argument,
+ og:image:alt / twitter:image:alt,
+ og:site_name (RealUnit),
+ Facebook App Links und Twitter App Cards aus den HTML-Bytes (ohne JS):
+ al:ios:url, al:android:url und
+ twitter:app:url:iphone / ipad /
+ googleplay sind
+ realunit-wallet://invite|promo/…,
+ al:android:class ist
+ swiss.realunit.app.MainActivity,
+ al:web:url die HTTPS-Landing,
+ twitter:app:country ist CH.
+ «Erneut versuchen» prüft den Code erneut ohne Seiten-Reload
+ (der Button wird vom Fehlertext beschrieben) und setzt den Fokus auf
+ «Code wird geprüft…»; nach dem Ergebnis auf die Ergebnis-Überschrift.
+ «Kopiert» erscheint nur, wenn Code oder Link wirklich in der
+ Zwischenablage liegt, und wird über den Status-Hinweis vorgelesen.
+
+ Automatisierte Oberflächen-Tests: Responsive-Matrix auf + Teilnahmebedingungen, Übersicht und Erstellseite, Cubit- und + API-Vertrags-Tests, Deeplink-Parser. Visual-Regression: Übersicht + (`276`), Registrierungs-Schritt (`277`), Dashboard-Karte (`278`), + Nicht-berechtigt (`279`), Einladung erstellt (`280`), + Teilnahmebedingungen (`281`), Prämie in der Historie (`282`), + TB nach Annahme (`283`), Namenseingabe (`284`), Einstellungen-Karte + (`285`), Rechtliche Dokumente letzter Eintrag (`286`), Einladung + erkannt (`287`), Historie mit Prämie (`288`), Dashboard-Einstieg + (`289`), Promo-Aktionstext (`290`). +
+
+
+
+ eligible setzt.
+
+
+
+
+
+
+
+ eligible setzt. Kartentext «Erhalte 20 REALU pro
+ Weiterempfehlung».
+
+
+
+
+
+ realunit.hbs: der Mailer sendet HTML aus dem Klartext;
+ GET /admin/emails bleibt der Klartext.