feat: split legacy/payment apps via APP_MODE flag

This commit is contained in:
2026-04-07 00:09:48 +02:00
parent 3a375044b2
commit c263e4227e
10 changed files with 197 additions and 66 deletions

View File

@@ -0,0 +1,20 @@
/// Compile-time constant that controls which app the splash screen
/// navigates to when the app starts.
///
/// Set via `--dart-define=APP_MODE=payment` (or `legacy`) at launch time.
/// Defaults to `legacy` to preserve historical behavior when no flag is
/// passed (e.g. `flutter run` from CLI without arguments).
///
/// Used only for local development to switch between the legacy app
/// (watch/device control) and the payment app (Treezor wallet) without
/// needing separate flavors or entry points.
const String appMode = String.fromEnvironment(
'APP_MODE',
defaultValue: 'legacy',
);
/// Whether the app should boot into the payment (Treezor wallet) flow.
bool get isPaymentMode => appMode == 'payment';
/// Whether the app should boot into the legacy (watch/device) flow.
bool get isLegacyMode => appMode == 'legacy';

View File

@@ -7,6 +7,7 @@ import 'package:design_system/design_system.dart';
import 'package:sca_treezor/sca_treezor.dart';
import 'package:sf_app_platform/config/env/environment_enum.dart';
import 'package:sf_app_platform/config/env/questia_env_config.dart';
import 'package:sf_app_platform/core/config/app_mode.dart';
import 'package:sf_app_platform/navigation/app_router.dart';
import 'package:sf_app_platform/save_family_app.dart';
import 'package:navigation/navigation.dart';
@@ -30,9 +31,11 @@ Future<void> initApp(EnvironmentEnum env) async {
await configureDependencies(
QuestiaEnvConfig(),
log: env.isDevelopment || kDebugMode,
onTokenExpired: () => appRouter.go(
AppRoutes.legacyLogin,
), //change to payments app to AppRoutes.scaTreezor
// Treezor-specific detection (message + 500) runs in both modes;
// only the destination route differs based on the active app mode.
onTokenExpired: isPaymentMode
? () => appRouter.go(AppRoutes.scaTreezor)
: () => appRouter.go(AppRoutes.legacyLogin),
onUnauthorized: () async {
final currentLocation =
appRouter.routerDelegate.currentConfiguration.uri.path;
@@ -41,7 +44,7 @@ Future<void> initApp(EnvironmentEnum env) async {
await GetIt.I<TreezorWalletConnectionService>().logout();
} catch (_) {}
await clearSessionData();
appRouter.go(AppRoutes.legacyLogin);
appRouter.go(isPaymentMode ? AppRoutes.login : AppRoutes.legacyLogin);
},
);

View File

@@ -17,13 +17,33 @@ import 'package:notifications/notifications.dart';
import 'package:payments/payments.dart';
import 'package:profile/profile.dart';
import 'package:settings/settings.dart';
import 'package:sf_app_platform/core/config/app_mode.dart';
import 'package:splash/splash.dart';
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
late final GoRouter appRouter;
/// Maps the splash's session check result to the destination route based
/// on the active [appMode]. Set `--dart-define=APP_MODE=payment` (or use
/// the `(Payment)` launch configurations) to boot into the payment app.
const _legacySplashRouteMap = <InitialRoute, String>{
InitialRoute.onboarding: AppRoutes.legacyOnboarding,
InitialRoute.login: AppRoutes.legacyLogin,
InitialRoute.home: AppRoutes.controlPanel,
};
const _paymentSplashRouteMap = <InitialRoute, String>{
InitialRoute.onboarding: AppRoutes.onboarding,
InitialRoute.login: AppRoutes.login,
InitialRoute.home: AppRoutes.dashboardHome,
};
void configureAppRouter() {
final splashRouteMap = isPaymentMode
? _paymentSplashRouteMap
: _legacySplashRouteMap;
appRouter = GoRouter(
navigatorKey: rootNavigatorKey,
initialLocation: AppRoutes.splash,
@@ -32,7 +52,7 @@ void configureAppRouter() {
GoRoute(
path: AppRoutes.splash,
name: 'splash',
pageBuilder: SplashBuilder().buildPage,
pageBuilder: SplashBuilder(routeMap: splashRouteMap).buildPage,
),
StatefulShellRoute.indexedStack(
builder: (context, state, navShell) {

View File

@@ -2,6 +2,7 @@ import 'package:auth/auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sf_app_platform/core/config/app_mode.dart';
import 'package:sf_app_platform/navigation/app_router.dart';
import 'package:navigation/navigation.dart';
import 'package:sf_app_platform/providers/app_state_provider.dart';
@@ -24,48 +25,58 @@ class SaveFamilyApp extends ConsumerStatefulWidget {
class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
with WidgetsBindingObserver {
late final WalletHeartbeatService walletHeartbeat;
late final LegacyHeartbeatService legacyHeartbeat;
WalletHeartbeatService? _walletHeartbeat;
LegacyHeartbeatService? _legacyHeartbeat;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
walletHeartbeat = WalletHeartbeatService(
repository: ref.read(treezorRepositoryProvider),
sessionLocal: SessionLocalDatasourceImpl(),
onError: () => appRouter.go(
AppRoutes.legacyLogin,
), //change to payments app to AppRoutes.scaTreezor
);
legacyHeartbeat = LegacyHeartbeatService(
repository: GetIt.I<QuestiaRepository>(),
onUnauthorized: () {
clearSessionData();
appRouter.go(AppRoutes.legacyLogin);
},
);
if (isPaymentMode) {
_walletHeartbeat = WalletHeartbeatService(
repository: ref.read(treezorRepositoryProvider),
sessionLocal: SessionLocalDatasourceImpl(),
onError: () => appRouter.go(AppRoutes.scaTreezor),
);
}
if (isLegacyMode) {
_legacyHeartbeat = LegacyHeartbeatService(
repository: GetIt.I<QuestiaRepository>(),
onUnauthorized: () {
clearSessionData();
appRouter.go(AppRoutes.legacyLogin);
},
);
appRouter.routerDelegate.addListener(_onRouteChanged);
}
onBeforeSessionCleared = () {
walletHeartbeat.stop();
legacyHeartbeat.stop();
_walletHeartbeat?.stop();
_legacyHeartbeat?.stop();
};
appRouter.routerDelegate.addListener(_onRouteChanged);
}
void _onRouteChanged() {
final heartbeat = _legacyHeartbeat;
if (heartbeat == null) return;
final location = appRouter.routerDelegate.currentConfiguration.uri.path;
if (location.startsWith(AppRoutes.legacyDashboard)) {
legacyHeartbeat.start();
heartbeat.start();
} else {
legacyHeartbeat.stop();
heartbeat.stop();
}
}
@override
void dispose() {
appRouter.routerDelegate.removeListener(_onRouteChanged);
walletHeartbeat.stop();
legacyHeartbeat.stop();
if (isLegacyMode) {
appRouter.routerDelegate.removeListener(_onRouteChanged);
}
_walletHeartbeat?.stop();
_legacyHeartbeat?.stop();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@@ -75,12 +86,14 @@ class SaveFamilyAppState extends ConsumerState<SaveFamilyApp>
debugPrint('State: $state');
ref.read(appLifecycleStateProvider.notifier).setState(state);
if (state == AppLifecycleState.resumed) {
// walletHeartbeat.start();
_onRouteChanged();
_walletHeartbeat?.start();
if (isLegacyMode) {
_onRouteChanged();
}
ref.read(permissionsProvider.notifier).checkPermissions();
} else if (state == AppLifecycleState.paused) {
// walletHeartbeat.stop();
legacyHeartbeat.stop();
_walletHeartbeat?.stop();
_legacyHeartbeat?.stop();
}
super.didChangeAppLifecycleState(state);
}