Fix onboarding UX and add replaceable journey analytics

Make recipient changes explicit, prioritize verification retry, and label simulated outcomes prominently. Add shared typed analytics with bounded dummy capture, correlated service events and documented PostHog adapter contract. Validate privacy, retries, abandonment, keyboard recovery and accessibility. Related to #12.
This commit is contained in:
Stephan D committed 2026-09-12 08:36:45 +02:00
1 parent bf7778f312
commit f1cbf64282
28 files changed
+1158 -64

No files matched your search

+36 -6
View File
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sxchnge_analytics/sxchnge_analytics.dart';
import 'package:sxchnge_auth/sxchnge_auth.dart';
import 'package:sxchnge_theme/sxchnge_theme.dart';
@@ -21,11 +23,13 @@ class SxchngeApp extends StatefulWidget {
const SxchngeApp({
super.key,
this.localeProvider,
this.analyticsService,
this.loginService = const DummyLoginService(),
this.signupService = const DummySignupService(),
this.verificationService = const DummyVerificationService(),
});
final LocaleProvider? localeProvider;
final AnalyticsService? analyticsService;
final LoginService loginService;
final SignupService signupService;
final VerificationService verificationService;
@@ -37,11 +41,38 @@ class SxchngeApp extends StatefulWidget {
class _SxchngeAppState extends State<SxchngeApp> {
late final _locale = widget.localeProvider ?? LocaleProvider();
late final _router = createAppRouter();
late final _journeys = JourneyRecorder(
widget.analyticsService ?? DummyAnalyticsService(),
);
late final _services = InstrumentedAuthServices(
recorder: _journeys,
loginService: widget.loginService,
signupService: widget.signupService,
verificationService: widget.verificationService,
);
late final _router = createAppRouter()
..routeInformationProvider.addListener(_routeChanged);
String _previousPath = '/';
@override
void initState() {
super.initState();
_previousPath = _router.routeInformationProvider.value.uri.path;
}
void _routeChanged() {
final path = _router.routeInformationProvider.value.uri.path;
if (path == '/' && _previousPath != '/') {
_journeys.abandon(JourneyExit.navigation);
}
_previousPath = path;
}
@override
void dispose() {
_router.routeInformationProvider.removeListener(_routeChanged);
_router.dispose();
_journeys.dispose();
_locale.dispose();
super.dispose();
}
@@ -49,12 +80,11 @@ class _SxchngeAppState extends State<SxchngeApp> {
@override
Widget build(BuildContext context) => MultiProvider(
providers: [
Provider<JourneyRecorder>.value(value: _journeys),
ChangeNotifierProvider.value(value: _locale),
Provider<VerificationService>.value(value: widget.verificationService),
ChangeNotifierProvider(create: (_) => LoginProvider(widget.loginService)),
ChangeNotifierProvider(
create: (_) => SignupProvider(widget.signupService),
),
Provider<VerificationService>.value(value: _services),
ChangeNotifierProvider(create: (_) => LoginProvider(_services)),
ChangeNotifierProvider(create: (_) => SignupProvider(_services)),
],
child: Consumer<LocaleProvider>(
builder: (context, locale, _) => MaterialApp.router(