Files
rippr/lib/main.dart
Dylan a3bcd014c0 T22/T24/T26: uploader, integration tests, and release readiness
T22 -- TelemetryUploader on package:http with MockClient standing in for
MockWebServer, tested against a real in-memory Drift database rather than a fake
DAO. Upload runs on its own timer, injected as a callback so the engine has no
opinion about HTTP and tests need no network. Config on shared_preferences with
a hand-rolled UUID v4. Still no UI for the endpoint, exactly as in the native
app. 7 tests.

T24 -- integration_test/app_test.dart, 4 tests passing on the iOS simulator.
These cover what widget tests cannot: Drift opening against real platform
storage, plugin registration, go_router driving a real Navigator, cold start.

T26 -- RELEASE-IOS.md. Usage strings are specific rather than generic, which is
the leading Guideline 5.1.1 rejection cause; privacy-label answers decided; a
pre-submission list covering the bundle-id switch back to com.rippr, the still
default app icon, a release build, and a demo video for review notes.

T25 written up as REAL-RIDE-CHECKLIST.md but outstanding by nature. Two items
decide real things: force-stopping mid-ride on Android confirms the 111 km
crash-gap bug is fixed, and parking 15 minutes mid-recording on iOS decides
whether geolocator is sufficient or the paid engine is needed.

Two prefer_initializing_formals lints suppressed with a reason: Dart forbids a
named parameter beginning with an underscore, so the suggested fix will not
compile.

178 tests passing, analyze clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 22:20:39 -05:00

43 lines
1.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'src/app/providers.dart';
import 'src/config/config.dart';
import 'src/ui/router.dart';
import 'src/ui/theme.dart';
void main() {
runApp(const ProviderScope(child: RipprApp()));
}
class RipprApp extends ConsumerStatefulWidget {
const RipprApp({super.key});
@override
ConsumerState<RipprApp> createState() => _RipprAppState();
}
class _RipprAppState extends ConsumerState<RipprApp> {
final _router = buildRouter();
@override
void initState() {
super.initState();
// Re-attach to a ride that was in progress when the process died. Must run before any
// user interaction, and the database is the only thing that knows a ride was open.
WidgetsBinding.instance.addPostFrameCallback((_) async {
// Preferences first: the uploader and the map toggle both read from them.
ref.read(configProvider.notifier).state = await Config.load();
await ref.read(recordingEngineProvider).restoreAfterProcessDeath();
});
}
@override
Widget build(BuildContext context) => MaterialApp.router(
title: 'Rippr',
debugShowCheckedModeBanner: false,
theme: ripprTheme(),
routerConfig: _router,
);
}