import 'package:flutter/material.dart'; import 'features/app_shell.dart'; import 'features/devices/devices_page.dart'; import 'services/adb_service.dart'; import 'services/artifact_staging_service.dart'; import 'services/jenkins_artifact_session.dart'; import 'services/jenkins_client.dart'; import 'services/token_store.dart'; import 'theme/app_theme.dart'; class AppSokApp extends StatefulWidget { const AppSokApp({ super.key, this.tokenStore, this.jenkinsClient, this.artifactStagingService, this.session, this.adbDeviceLoader, this.adbInstaller, this.adbDiagnosticsLoader, this.webLoginLauncher, this.onWebLoginCompleted, this.adbLogcatStarter, }); final TokenStore? tokenStore; final JenkinsClient? jenkinsClient; final ArtifactStagingService? artifactStagingService; final JenkinsArtifactSession? session; final AdbDeviceLoader? adbDeviceLoader; final AdbInstaller? adbInstaller; final AdbDiagnosticsLoader? adbDiagnosticsLoader; final JenkinsWebLoginLauncher? webLoginLauncher; final void Function(JenkinsWebLoginResult result)? onWebLoginCompleted; final AdbLogcatSessionStarter? adbLogcatStarter; @override State createState() => _AppSokAppState(); } class _AppSokAppState extends State { late final JenkinsArtifactSession _session = widget.session ?? JenkinsArtifactSession( store: widget.tokenStore ?? TokenStore(), client: widget.jenkinsClient ?? JenkinsClient(), stager: widget.artifactStagingService ?? ArtifactStagingService(), ); late final AdbService _adbService = AdbService(); // Mutable so that _onSessionSaved and _retryRestore can trigger re-restore. late Future _startup = _session.restoreDetailed(); void _onSessionSaved() { if (!mounted) return; setState(() { _startup = _session.restoreDetailed(); }); } Future _clearSession() async { await _session.clear(); if (!mounted) return; setState(() { _startup = Future.value( const JenkinsSessionRestoreResult( kind: JenkinsSessionRestoreKind.missing, ), ); }); } void _retryRestore() { if (!mounted) return; setState(() { _startup = _session.restoreDetailed(); }); } Future _saveToken( String baseUrl, String username, String apiToken, ) async { final store = _session.store; final baseUri = Uri.tryParse(baseUrl); if (baseUri == null || (baseUri.scheme != 'http' && baseUri.scheme != 'https')) { return false; } try { await store.saveSession( JenkinsSessionConfig( baseUrl: baseUri, credentials: JenkinsCredentials( username: username, apiToken: apiToken, ), ), ); final result = await _session.restoreDetailed(); if (!result.isRestored) { await store.clearSession(); return false; } return true; } catch (_) { await store.clearSession(); return false; } } JenkinsConnectionStatus? _connectionStatus(JenkinsSessionRestoreResult r) { if (r.isRestored) return null; return switch (r.kind) { JenkinsSessionRestoreKind.reauthRequired => JenkinsConnectionStatus.reauthRequired, JenkinsSessionRestoreKind.networkFailure => JenkinsConnectionStatus.networkFailure, JenkinsSessionRestoreKind.serverFailure => JenkinsConnectionStatus.serverFailure, JenkinsSessionRestoreKind.missing || JenkinsSessionRestoreKind.restored => JenkinsConnectionStatus.missing, }; } @override Widget build(BuildContext context) { return FutureBuilder( future: _startup, builder: (context, snapshot) { final result = snapshot.data; final isReady = result?.isRestored ?? false; final connStatus = result != null ? _connectionStatus(result) : null; final canRetry = connStatus == JenkinsConnectionStatus.networkFailure || connStatus == JenkinsConnectionStatus.serverFailure; return MaterialApp( debugShowCheckedModeBanner: false, title: 'AppSok', theme: AppTheme.light(), home: AppSokShell( jobLoader: isReady ? _session.loadJobs : null, buildLoader: isReady ? _session.loadBuilds : null, artifactDownloader: isReady ? _session.downloadArtifact : null, artifactStager: isReady ? _session.stageApk : null, artifactCleaner: isReady ? _session.cleanup : null, deviceLoader: widget.adbDeviceLoader ?? _adbService.listDevices, installer: widget.adbInstaller ?? (device, pending) => _adbService.installApk( serial: device.serial, apkPath: pending.apkPath, ), adbDiagnosticsLoader: widget.adbDiagnosticsLoader ?? _adbService.collectDiagnostics, logcatStarter: widget.adbLogcatStarter ?? _adbService.startLogcat, webLoginLauncher: widget.webLoginLauncher, onWebLoginCompleted: widget.onWebLoginCompleted, onSessionSaved: _onSessionSaved, tokenSaver: _saveToken, sessionClearer: _clearSession, connectionStatus: connStatus, onRetryConnection: canRetry ? _retryRestore : null, ), ); }, ); } }