import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:package_info_plus/package_info_plus.dart'; import '../../../common/config/shorebird_service.dart'; import '../../../common/network/auth_interceptor.dart'; import '../../../common/network/dio_provider.dart'; import '../../../common/router/app_router.dart'; import '../../../common/services/device_header_service.dart'; import '../../../common/storage/storage_keys.dart'; import '../../../common/storage/storage_service.dart'; import '../../../common/theme/theme_controller.dart'; class DebugInfoPage extends ConsumerWidget { const DebugInfoPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final appConfig = ref.watch(appConfigProvider); final dio = ref.watch(dioProvider); final themeMode = ref.watch(themeModeControllerProvider); return Scaffold( appBar: AppBar( leading: IconButton( onPressed: () { if (context.canPop()) { context.pop(); } else { context.go(AppRoutes.debug); } }, icon: const Icon(Icons.arrow_back_ios_new), ), title: const Text('Info'), ), body: FutureBuilder<_DebugInfoSnapshot>( future: _loadSnapshot( apiBaseUrl: appConfig.apiBaseUrl, themeMode: themeMode.name, dio: dio, ), builder: (context, snapshot) { final text = snapshot.data?.text ?? 'loading...'; return Padding( padding: const EdgeInsets.all(16), child: SelectableText(text, style: const TextStyle(fontSize: 12)), ); }, ), ); } } Future<_DebugInfoSnapshot> _loadSnapshot({ required String apiBaseUrl, required String themeMode, required Dio dio, }) async { await DeviceHeaderService.to.ensureInitialized(); final packageInfo = await PackageInfo.fromPlatform(); final deviceInfo = await _readDeviceInfo(); final storage = StorageService.to; final jwt = await loadJwt(); final commonHeaders = DeviceHeaderService.to.commonHeaders(); final platformHeaders = DeviceHeaderService.to.platformIdentityHeaders(); final headers = { ...commonHeaders, ...platformHeaders, ...dio.options.headers, }; final gitVersion = const String.fromEnvironment( 'GITVERSION', defaultValue: 'unknown', ); final patch = ShorebirdService.instance.currentPatch; final jwtLine = jwt?.isNotEmpty == true ? 'Bearer ' : ''; final text = StringBuffer() ..writeln('[build]') ..writeln('app_name: ${packageInfo.appName}') ..writeln('mode: ${kReleaseMode ? 'release' : 'debug'}') ..writeln('package_name: ${packageInfo.packageName}') ..writeln('version: ${packageInfo.version}') ..writeln('build_number: ${packageInfo.buildNumber}') ..writeln('theme_mode: $themeMode') ..writeln('platform: ${defaultTargetPlatform.name}') ..writeln('is_web: $kIsWeb') ..writeln() ..writeln('[environment]') ..writeln('api_base: $apiBaseUrl') ..writeln( 'connect_timeout_ms: ${dio.options.connectTimeout?.inMilliseconds}', ) ..writeln( 'receive_timeout_ms: ${dio.options.receiveTimeout?.inMilliseconds}', ) ..writeln('response_type: ${dio.options.responseType.name}') ..writeln('git_version: $gitVersion') ..writeln() ..writeln('[storage]') ..writeln('privacy_agreed: ${storage.getBool(storagePrivacyAgreed)}') ..writeln('ios_startup_seen: ${storage.getBool(storageIosStartupSeen)}') ..writeln('device_id: ${DeviceHeaderService.to.deviceId}') ..writeln('android_id: ${storage.getString(storageAndroidId) ?? ''}') ..writeln('device_token: ${storage.getString(storageDeviceToken) ?? ''}') ..writeln('android_oaid: ${storage.getString(storageAndroidOaid) ?? ''}') ..writeln('android_imei: ${storage.getString(storageAndroidImei) ?? ''}') ..writeln('android_mac: ${storage.getString(storageAndroidMac) ?? ''}') ..writeln('ios_idfa: ${storage.getString(storageIosIdfa) ?? ''}') ..writeln('ios_paid: ${storage.getString(storageIosPaid) ?? ''}') ..writeln() ..writeln('[auth]') ..writeln('jwt: $jwtLine') ..writeln() ..writeln('[headers]') ..writeln('common: $commonHeaders') ..writeln('platform: $platformHeaders') ..writeln('dio: $headers') ..writeln() ..writeln('[shorebird]') ..writeln('current_patch: ${patch?.number ?? 'none'}') ..writeln('patch: ${patch ?? 'none'}') ..writeln() ..writeln('[device]') ..writeln(deviceInfo); return _DebugInfoSnapshot(text.toString()); } Future _readDeviceInfo() async { try { if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android && !StorageService.to.getBool(storagePrivacyAgreed)) { return 'privacy policy not accepted; device info not read'; } final plugin = DeviceInfoPlugin(); if (kIsWeb) { final info = await plugin.webBrowserInfo; return info.data.toString(); } switch (defaultTargetPlatform) { case TargetPlatform.android: final info = await plugin.androidInfo; return info.data.toString(); case TargetPlatform.iOS: final info = await plugin.iosInfo; return info.data.toString(); case TargetPlatform.macOS: final info = await plugin.macOsInfo; return info.data.toString(); case TargetPlatform.windows: final info = await plugin.windowsInfo; return info.data.toString(); case TargetPlatform.linux: final info = await plugin.linuxInfo; return info.data.toString(); case TargetPlatform.fuchsia: return 'fuchsia'; } } catch (error) { return 'unavailable: $error'; } } class _DebugInfoSnapshot { const _DebugInfoSnapshot(this.text); final String text; }