mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
feat(demo): DEMO_MODE — boot straight to an unlocked wallet for capture
Adds a debug-only capture mode so screenshots/video never depend on the
onboarding/PIN flow, whose synthetic taps are unreliable under the headless
emulator (both flutter_test and adb input drop taps on the PIN confirm screen).
lib/entities/demo_mode.dart: on launch, when kDebugMode AND
--dart-define=DEMO_MODE=true, create a fresh throwaway Wownero wallet through
the real WalletNewVM.create() (so walletInfo/derivation/paths/persistence and
changeCurrentWallet all happen exactly as in the UI), then set the PIN and
allow auth — landing on the dashboard. Reuses the wallet on later launches.
Both halves are compile-time const/kDebugMode, so this is tree-shaken out of
release: DEMO_MODE cannot be enabled in a store binary.
lib/main.dart: call maybeSetupDemoWallet() during bootstrap and force the
initial route to the dashboard in demo mode.
tools/demo/{build-apk,capture}.sh: DEMO=1 bakes the define into a local build;
flutter drive passes it so scenarios start at the dashboard.
Verified end to end: demo build boots to a live Wownero wallet and captured a
clean 1080x1920 store set (dashboard/receive/send/swap/settings) with no PIN.
This also implements the SCREENSHOT_MODE the iOS UITest scaffold already
references (store/SCREENSHOTS.md Path B).
Note: the API-35 emulator needs -gpu host (real GPU) for deep-page rendering;
-gpu software hard-crashes qemu on the Receive page. Stills use screencap
(screenrecord's encoder is unrelated).
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hash_wallet/core/auth_service.dart';
|
||||
import 'package:hash_wallet/core/execution_state.dart';
|
||||
import 'package:hash_wallet/core/new_wallet_arguments.dart';
|
||||
import 'package:hash_wallet/di.dart';
|
||||
import 'package:hash_wallet/entities/load_current_wallet.dart';
|
||||
import 'package:hash_wallet/entities/preferences_key.dart';
|
||||
import 'package:hash_wallet/entities/seed_type.dart';
|
||||
import 'package:hash_wallet/store/authentication_store.dart';
|
||||
import 'package:hash_wallet/view_model/wallet_new_vm.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
/// Whether this build was launched in capture/demo mode.
|
||||
///
|
||||
/// Compile-time const AND gated on [kDebugMode], so in a release build both
|
||||
/// halves fold to false and every demo path below is tree-shaken out. It is
|
||||
/// impossible to enable DEMO_MODE in a store binary.
|
||||
///
|
||||
/// Enable for a local capture build with:
|
||||
/// flutter run --dart-define=DEMO_MODE=true
|
||||
const bool _demoModeDefine = bool.fromEnvironment('DEMO_MODE');
|
||||
bool get isDemoMode => kDebugMode && _demoModeDefine;
|
||||
|
||||
/// PIN the demo build "unlocks" with.
|
||||
const String _demoPin = String.fromEnvironment('DEMO_PIN', defaultValue: '0801');
|
||||
|
||||
/// Name of the throwaway wallet the demo build creates on first launch.
|
||||
const String _demoWalletName = 'Demo Wallet';
|
||||
|
||||
/// Bring the app up straight into a usable, unlocked wallet so the capture
|
||||
/// harness never has to drive onboarding or the PIN pad (whose synthetic taps
|
||||
/// are unreliable under the headless emulator).
|
||||
///
|
||||
/// First launch: create a fresh Wownero wallet through the real new-wallet
|
||||
/// view model — so walletInfo, derivation, paths, persistence and
|
||||
/// changeCurrentWallet all happen exactly as they do in the UI flow, just
|
||||
/// without the screens. No seed import, no network sync: instant, deterministic,
|
||||
/// zero balance. Later launches: reuse the wallet already on disk.
|
||||
///
|
||||
/// This is a THROWAWAY wallet. Never fund it. To shoot balances/history, point
|
||||
/// a demo build at a funded wallet instead (out of scope here).
|
||||
Future<void> maybeSetupDemoWallet() async {
|
||||
if (!isDemoMode) return;
|
||||
|
||||
try {
|
||||
final authStore = getIt.get<AuthenticationStore>();
|
||||
final authService = getIt.get<AuthService>();
|
||||
final prefs = getIt.get<SharedPreferences>();
|
||||
|
||||
await authService.setPassword(_demoPin);
|
||||
|
||||
final existing = prefs.getString(PreferencesKey.currentWalletName);
|
||||
if (existing != null) {
|
||||
// A demo wallet already exists from a prior run — just load it.
|
||||
await loadCurrentWallet();
|
||||
authStore.allowed();
|
||||
printV('[demo] loaded existing wallet "$existing"');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fresh Wownero wallet via the real create flow. WalletNewVM.create builds
|
||||
// the WalletInfo, derivation, and paths, creates + saves the wallet, calls
|
||||
// AppStore.changeCurrentWallet (persisting currentWalletName/type) and
|
||||
// AuthenticationStore.allowedCreate() on success.
|
||||
final vm = getIt.get<WalletNewVM>(
|
||||
param1: NewWalletArguments(type: WalletType.wownero));
|
||||
vm.name = _demoWalletName;
|
||||
await vm.create(options: <dynamic>['English', MoneroSeedType.polyseed]);
|
||||
|
||||
if (vm.state is! ExecutedSuccessfullyState) {
|
||||
throw Exception('wallet create did not finish (${vm.state.runtimeType})');
|
||||
}
|
||||
printV('[demo] created fresh Wownero wallet "$_demoWalletName"');
|
||||
} catch (e, s) {
|
||||
// Never let a demo-only path crash the app; fall back to normal onboarding
|
||||
// so the failure is visible rather than fatal.
|
||||
printV('[demo] setup failed, falling back to onboarding: $e\n$s');
|
||||
}
|
||||
}
|
||||
+13
-5
@@ -13,6 +13,7 @@ import 'package:hash_wallet/core/trade_monitor.dart';
|
||||
import 'package:hash_wallet/di.dart';
|
||||
import 'package:hash_wallet/entities/contact.dart';
|
||||
import 'package:hash_wallet/entities/default_settings_migration.dart';
|
||||
import 'package:hash_wallet/entities/demo_mode.dart';
|
||||
import 'package:hash_wallet/entities/get_encryption_key.dart';
|
||||
import 'package:hash_wallet/entities/language_service.dart';
|
||||
import 'package:hash_wallet/entities/template.dart';
|
||||
@@ -162,6 +163,11 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {
|
||||
ProxyWrapper.logger = MemoryProxyLogger();
|
||||
}
|
||||
|
||||
// Capture/demo builds only (kDebugMode + --dart-define=DEMO_MODE): come up
|
||||
// straight into an unlocked wallet so screenshots/video never depend on the
|
||||
// onboarding/PIN flow. No-op in release.
|
||||
await maybeSetupDemoWallet();
|
||||
|
||||
// Basically when we're running a test
|
||||
if (topLevelKey != null) {
|
||||
runApp(
|
||||
@@ -376,11 +382,13 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
|
||||
final settingsStore = appStore.settingsStore;
|
||||
final statusBarColor = Colors.transparent;
|
||||
final authenticationStore = getIt.get<AuthenticationStore>();
|
||||
final initialRoute = authenticationStore.state == AuthenticationState.uninitialized
|
||||
? Routes.welcome
|
||||
: settingsStore.currentBuiltinTor
|
||||
? Routes.startTor
|
||||
: Routes.login;
|
||||
final initialRoute = isDemoMode
|
||||
? Routes.dashboard
|
||||
: authenticationStore.state == AuthenticationState.uninitialized
|
||||
? Routes.welcome
|
||||
: settingsStore.currentBuiltinTor
|
||||
? Routes.startTor
|
||||
: Routes.login;
|
||||
final currentTheme = appStore.themeStore.currentTheme;
|
||||
final statusBarBrightness =
|
||||
currentTheme.type == currentTheme.isDark ? Brightness.light : Brightness.dark;
|
||||
|
||||
+11
-1
@@ -45,6 +45,8 @@ docker run --rm \
|
||||
-e SKIP_DEPS="$SKIP_DEPS" \
|
||||
-e BUILD_MODE="$BUILD_MODE" \
|
||||
-e TARGET_PLATFORM="$TARGET_PLATFORM" \
|
||||
-e DEMO="${DEMO:-0}" \
|
||||
-e DEMO_PIN="${DEMO_PIN:-0801}" \
|
||||
-e HOST_UID="$HOST_UID" \
|
||||
-e HOST_GID="$HOST_GID" \
|
||||
"$IMAGE" bash -c '
|
||||
@@ -214,7 +216,15 @@ step "compile svg assets"
|
||||
step "build apk ($BUILD_MODE / ${TARGET_PLATFORM:-universal})"
|
||||
EXTRA=""
|
||||
[[ -n "$TARGET_PLATFORM" ]] && EXTRA="--target-platform=$TARGET_PLATFORM"
|
||||
flutter build apk --dart-define-from-file=env.json --$BUILD_MODE $EXTRA
|
||||
# DEMO=1 bakes in DEMO_MODE so the app boots straight to an unlocked demo
|
||||
# wallet (kDebugMode-gated, impossible in release). Lets capture skip the
|
||||
# onboarding/PIN flow entirely.
|
||||
DEMO_DEFINES=""
|
||||
if [[ "${DEMO:-0}" == "1" ]]; then
|
||||
DEMO_DEFINES="--dart-define=DEMO_MODE=true --dart-define=DEMO_PIN=${DEMO_PIN:-0801}"
|
||||
echo "DEMO_MODE baked in"
|
||||
fi
|
||||
flutter build apk --dart-define-from-file=env.json --$BUILD_MODE $EXTRA $DEMO_DEFINES
|
||||
|
||||
echo
|
||||
echo "==> artifacts:"
|
||||
|
||||
@@ -272,6 +272,8 @@ cmd_record() {
|
||||
--target=integration_test/demo/scenarios/'"$scenario"'.dart \
|
||||
-d "$SERIAL" \
|
||||
--dart-define-from-file=env.json \
|
||||
--dart-define=DEMO_MODE=true \
|
||||
--dart-define=DEMO_PIN='"${DEMO_PIN:-0801}"' \
|
||||
--dart-define=DEMO_WALLET_SEED="$DEMO_SEED" \
|
||||
--dart-define=DEMO_SEND_ADDRESS="$DEMO_SEND_ADDR"
|
||||
' 2>&1 | tee "$RAW_DIR/$scenario.drive.log"
|
||||
|
||||
Reference in New Issue
Block a user