diff --git a/lib/app/router/app_router.dart b/lib/app/router/app_router.dart index 919d08a..9be8bf1 100644 --- a/lib/app/router/app_router.dart +++ b/lib/app/router/app_router.dart @@ -20,9 +20,8 @@ final appRouter = GoRouter( GoRoute( path: '/settings', name: 'settings', - pageBuilder: (context, state) => const MaterialPage(child: SettingsScreen()), + pageBuilder: (context, state) => + const MaterialPage(child: SettingsScreen()), ), ], ); - - diff --git a/lib/app/router/routes.dart b/lib/app/router/routes.dart index bf99dc9..de57851 100644 --- a/lib/app/router/routes.dart +++ b/lib/app/router/routes.dart @@ -3,5 +3,3 @@ import 'package:flutter/material.dart'; enum AppRoute { home, peers, settings } typedef RouteBuilder = Widget Function(BuildContext context); - - diff --git a/lib/app/theme/app_theme.dart b/lib/app/theme/app_theme.dart index e845b86..86a6c0c 100644 --- a/lib/app/theme/app_theme.dart +++ b/lib/app/theme/app_theme.dart @@ -13,17 +13,18 @@ class AppTheme { secondary: AppColors.brandAccent, error: AppColors.error, surface: AppColors.surface, - background: AppColors.background, outline: AppColors.neutral300, ); - final rawTextTheme = base.textTheme.copyWith( - displayLarge: AppTypography.display, - headlineMedium: AppTypography.headline, - titleMedium: AppTypography.title, - bodyMedium: AppTypography.body, - labelMedium: AppTypography.label, - ).apply(fontFamily: 'Roboto'); + final rawTextTheme = base.textTheme + .copyWith( + displayLarge: AppTypography.display, + headlineMedium: AppTypography.headline, + titleMedium: AppTypography.title, + bodyMedium: AppTypography.body, + labelMedium: AppTypography.label, + ) + .apply(fontFamily: 'Roboto'); final textTheme = rawTextTheme.apply( bodyColor: colorScheme.onSurface, @@ -39,7 +40,8 @@ class AppTheme { foregroundColor: AppColors.neutral900, elevation: 0, centerTitle: false, - titleTextStyle: AppTypography.title.copyWith(color: AppColors.neutral900), + titleTextStyle: + AppTypography.title.copyWith(color: AppColors.neutral900), ), cardTheme: CardTheme( color: AppColors.surface, @@ -50,25 +52,25 @@ class AppTheme { ), elevatedButtonTheme: ElevatedButtonThemeData( style: ButtonStyle( - backgroundColor: MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.disabled)) { + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { return AppColors.neutral300; } return colorScheme.primary; }), - foregroundColor: MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.disabled)) { - return colorScheme.onSurface.withOpacity(0.38); + foregroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return colorScheme.onSurface.withValues(alpha: 0.38); } return colorScheme.onPrimary; }), - minimumSize: MaterialStateProperty.all(const Size.fromHeight(48)), - shape: MaterialStateProperty.all( + minimumSize: WidgetStateProperty.all(const Size.fromHeight(48)), + shape: WidgetStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadii.md), ), ), - padding: MaterialStateProperty.all( + padding: WidgetStateProperty.all( const EdgeInsets.symmetric(horizontal: AppSpacing.lg), ), animationDuration: AppMotion.medium, @@ -98,17 +100,18 @@ class AppTheme { secondary: AppColors.brandAccent, error: AppColors.error, surface: AppColors.neutral800, - background: AppColors.neutral900, outline: AppColors.neutral600, ); - final rawTextTheme = base.textTheme.copyWith( - displayLarge: AppTypography.display, - headlineMedium: AppTypography.headline, - titleMedium: AppTypography.title, - bodyMedium: AppTypography.body, - labelMedium: AppTypography.label, - ).apply(fontFamily: 'Roboto'); + final rawTextTheme = base.textTheme + .copyWith( + displayLarge: AppTypography.display, + headlineMedium: AppTypography.headline, + titleMedium: AppTypography.title, + bodyMedium: AppTypography.body, + labelMedium: AppTypography.label, + ) + .apply(fontFamily: 'Roboto'); final textTheme = rawTextTheme.apply( bodyColor: colorScheme.onSurface, @@ -118,13 +121,14 @@ class AppTheme { return base.copyWith( colorScheme: colorScheme, textTheme: textTheme, - scaffoldBackgroundColor: colorScheme.background, + scaffoldBackgroundColor: colorScheme.surface, appBarTheme: AppBarTheme( backgroundColor: colorScheme.surface, foregroundColor: colorScheme.onSurface, elevation: 0, centerTitle: false, - titleTextStyle: AppTypography.title.copyWith(color: colorScheme.onSurface), + titleTextStyle: + AppTypography.title.copyWith(color: colorScheme.onSurface), ), cardTheme: CardTheme( color: colorScheme.surface, @@ -135,25 +139,25 @@ class AppTheme { ), elevatedButtonTheme: ElevatedButtonThemeData( style: ButtonStyle( - backgroundColor: MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.disabled)) { + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { return AppColors.neutral600; } return colorScheme.primary; }), - foregroundColor: MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.disabled)) { - return colorScheme.onSurface.withOpacity(0.38); + foregroundColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.disabled)) { + return colorScheme.onSurface.withValues(alpha: 0.38); } return colorScheme.onPrimary; }), - minimumSize: MaterialStateProperty.all(const Size.fromHeight(48)), - shape: MaterialStateProperty.all( + minimumSize: WidgetStateProperty.all(const Size.fromHeight(48)), + shape: WidgetStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadii.md), ), ), - padding: MaterialStateProperty.all( + padding: WidgetStateProperty.all( const EdgeInsets.symmetric(horizontal: AppSpacing.lg), ), animationDuration: AppMotion.medium, @@ -174,5 +178,3 @@ class AppTheme { ); } } - - diff --git a/lib/app/theme/tokens.dart b/lib/app/theme/tokens.dart index 7091f41..5ef29dd 100644 --- a/lib/app/theme/tokens.dart +++ b/lib/app/theme/tokens.dart @@ -17,7 +17,8 @@ class AppColors { // Data visualization colors static const Color dataUpload = Color(0xFF0988B0); // Blue for upload (TX) - static const Color dataDownload = Color(0xFF10B981); // Green for download (RX) + static const Color dataDownload = + Color(0xFF10B981); // Green for download (RX) static const Color dataPeers = Color(0xFF0988B0); // Primary blue for peers static const Color dataUptime = Color(0xFF10B981); // Green for uptime static const Color dataTraffic = Color(0xFF0D9C9E); // Accent teal for traffic diff --git a/lib/app/widgets/app_card.dart b/lib/app/widgets/app_card.dart index 3195fb3..8309eaa 100644 --- a/lib/app/widgets/app_card.dart +++ b/lib/app/widgets/app_card.dart @@ -7,7 +7,12 @@ class AppCard extends StatelessWidget { final VoidCallback? onTap; final EdgeInsetsGeometry? margin; - const AppCard({super.key, required this.child, this.padding = const EdgeInsets.all(AppSpacing.lg), this.onTap, this.margin}); + const AppCard( + {super.key, + required this.child, + this.padding = const EdgeInsets.all(AppSpacing.lg), + this.onTap, + this.margin}); @override Widget build(BuildContext context) { @@ -16,14 +21,16 @@ class AppCard extends StatelessWidget { color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(AppRadii.lg), boxShadow: AppElevation.level1, - border: Border.all(color: Theme.of(context).dividerColor.withOpacity(0.5)), + border: + Border.all(color: Theme.of(context).dividerColor.withValues(alpha: 0.5)), ), margin: margin ?? const EdgeInsets.symmetric(vertical: AppSpacing.lg), child: Padding(padding: padding, child: child), ); if (onTap == null) return card; - return InkWell(borderRadius: BorderRadius.circular(AppRadii.lg), onTap: onTap, child: card); + return InkWell( + borderRadius: BorderRadius.circular(AppRadii.lg), + onTap: onTap, + child: card); } } - - diff --git a/lib/app/widgets/app_scaffold.dart b/lib/app/widgets/app_scaffold.dart index 3ad8895..c48ebae 100644 --- a/lib/app/widgets/app_scaffold.dart +++ b/lib/app/widgets/app_scaffold.dart @@ -44,10 +44,10 @@ class AppScaffold extends StatelessWidget { child: ConstrainedBox( constraints: BoxConstraints( minHeight: MediaQuery.of(context).size.height - - MediaQuery.of(context).padding.top - - MediaQuery.of(context).padding.bottom - - kToolbarHeight - - 80, // approximate bottom nav height + MediaQuery.of(context).padding.top - + MediaQuery.of(context).padding.bottom - + kToolbarHeight - + 80, // approximate bottom nav height ), child: child, ), diff --git a/lib/app/widgets/desktop_layout.dart b/lib/app/widgets/desktop_layout.dart index 4e0541c..0e8e19f 100644 --- a/lib/app/widgets/desktop_layout.dart +++ b/lib/app/widgets/desktop_layout.dart @@ -18,7 +18,7 @@ class DesktopLayout extends StatelessWidget { Widget build(BuildContext context) { final screenHeight = MediaQuery.of(context).size.height; final screenWidth = MediaQuery.of(context).size.width; - + return Scaffold( body: SizedBox( width: screenWidth, @@ -42,12 +42,14 @@ class DesktopLayout extends StatelessWidget { color: Theme.of(context).colorScheme.surface, border: Border( bottom: BorderSide( - color: Theme.of(context).dividerColor.withOpacity(0.3), + color: + Theme.of(context).dividerColor.withValues(alpha: 0.3), width: 1, ), ), ), - padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xxl), + padding: const EdgeInsets.symmetric( + horizontal: AppSpacing.xxl), alignment: Alignment.centerLeft, child: title, ), @@ -55,7 +57,7 @@ class DesktopLayout extends StatelessWidget { // Main content with proper padding Expanded( child: Material( - color: Theme.of(context).colorScheme.background, + color: Theme.of(context).colorScheme.surface, child: Padding( padding: const EdgeInsets.all(AppSpacing.xxl), child: SingleChildScrollView( diff --git a/lib/app/widgets/desktop_sidebar.dart b/lib/app/widgets/desktop_sidebar.dart index 3f6755a..083193e 100644 --- a/lib/app/widgets/desktop_sidebar.dart +++ b/lib/app/widgets/desktop_sidebar.dart @@ -18,13 +18,13 @@ class DesktopSidebar extends StatelessWidget { color: Theme.of(context).colorScheme.surface, border: Border( right: BorderSide( - color: Theme.of(context).dividerColor.withOpacity(0.3), + color: Theme.of(context).dividerColor.withValues(alpha: 0.3), width: 1, ), ), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues(alpha: 0.05), blurRadius: 8, offset: const Offset(2, 0), ), @@ -93,7 +93,7 @@ class DesktopSidebar extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Divider( - color: Theme.of(context).dividerColor.withOpacity(0.3), + color: Theme.of(context).dividerColor.withValues(alpha: 0.3), ), const SizedBox(height: AppSpacing.sm), Text( @@ -102,7 +102,7 @@ class DesktopSidebar extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6), + .withValues(alpha: 0.6), ), overflow: TextOverflow.ellipsis, ), @@ -112,7 +112,7 @@ class DesktopSidebar extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.4), + .withValues(alpha: 0.4), ), ), ], @@ -153,13 +153,13 @@ class _SidebarItem extends StatelessWidget { ), decoration: BoxDecoration( color: isSelected - ? Theme.of(context).colorScheme.primary.withOpacity(0.1) + ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.1) : Colors.transparent, borderRadius: BorderRadius.circular(AppRadii.md), border: isSelected ? Border.all( color: - Theme.of(context).colorScheme.primary.withOpacity(0.3), + Theme.of(context).colorScheme.primary.withValues(alpha: 0.3), width: 1, ) : null, @@ -171,7 +171,7 @@ class _SidebarItem extends StatelessWidget { size: 20, color: isSelected ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface.withOpacity(0.7), + : Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.7), ), const SizedBox(width: AppSpacing.md), Expanded( diff --git a/lib/app/widgets/responsive_layout.dart b/lib/app/widgets/responsive_layout.dart index 6e8e730..15f4071 100644 --- a/lib/app/widgets/responsive_layout.dart +++ b/lib/app/widgets/responsive_layout.dart @@ -24,7 +24,8 @@ class ResponsiveLayout extends StatelessWidget { Widget build(BuildContext context) { return MediaQuery.of(context).size.width >= ResponsiveBreakpoints.desktop ? desktop - : MediaQuery.of(context).size.width >= ResponsiveBreakpoints.mobile && tablet != null + : MediaQuery.of(context).size.width >= ResponsiveBreakpoints.mobile && + tablet != null ? tablet! : mobile; } diff --git a/lib/features/home/home_screen.dart b/lib/features/home/home_screen.dart index db9b1d2..7247acd 100644 --- a/lib/features/home/home_screen.dart +++ b/lib/features/home/home_screen.dart @@ -255,7 +255,7 @@ class _HeaderCardState extends State<_HeaderCard> await widget.service.stopProxyProbe(); setState(() => _isSocks5Enabled = false); } catch (e) { - print('Error stopping proxy: $e'); + debugPrint('Error stopping proxy: $e'); } } @@ -291,7 +291,7 @@ class _HeaderCardState extends State<_HeaderCard> decoration: BoxDecoration( shape: BoxShape.circle, color: - Theme.of(context).colorScheme.primary.withOpacity( + Theme.of(context).colorScheme.primary.withValues(alpha: (1.0 - _fadeAnimation.value) * 0.3, ), ), @@ -323,7 +323,7 @@ class _HeaderCardState extends State<_HeaderCard> decoration: BoxDecoration( shape: BoxShape.circle, color: - Theme.of(context).colorScheme.primary.withOpacity( + Theme.of(context).colorScheme.primary.withValues(alpha: (1.0 - (_fadeAnimation.value - 0.3) .clamp(0.0, 1.0)) * @@ -358,7 +358,7 @@ class _HeaderCardState extends State<_HeaderCard> decoration: BoxDecoration( shape: BoxShape.circle, color: - Theme.of(context).colorScheme.primary.withOpacity( + Theme.of(context).colorScheme.primary.withValues(alpha: (1.0 - (_fadeAnimation.value - 0.6) .clamp(0.0, 1.0)) * @@ -427,7 +427,7 @@ class _HeaderCardState extends State<_HeaderCard> 'Tap to start the Mycelium node', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: - Theme.of(context).colorScheme.onSurface.withOpacity(0.6)), + Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6)), ), const SizedBox(height: AppSpacing.xxl), AppButton( @@ -445,7 +445,8 @@ class _HeaderCardState extends State<_HeaderCard> height: 48, child: ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.surfaceVariant, + backgroundColor: + Theme.of(context).colorScheme.surfaceContainerHighest, foregroundColor: Theme.of(context).colorScheme.onSurfaceVariant, padding: const EdgeInsets.symmetric(horizontal: 16), @@ -472,50 +473,51 @@ class _HeaderCardState extends State<_HeaderCard> ), ), ), - if (widget.status == NodeStatus.connected && false) ...[ - const SizedBox(height: AppSpacing.lg), - AppCard( - margin: EdgeInsets.zero, - child: ExpansionTile( - title: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Flexible( - child: Text('Advanced Options', - overflow: TextOverflow.ellipsis), - ), - Icon(Icons.expand_more), - ], - ), - children: [ - ListTile( - title: const Text('Enable SOCKS5 tunneling as VPN'), - trailing: Switch( - value: _isSocks5Enabled, - onChanged: (value) async { - setState(() => _isSocks5Enabled = value); - if (value) { - // First start proxy probing to discover available proxies - await widget.service.startProxyProbe(); - // Wait a bit for probes to discover proxies - await Future.delayed(Duration(seconds: 10)); - // Then connect to best available proxy - final result = await widget.service.proxyConnect( - '[40a:152c:b85b:9646:5b71:d03a:eb27:2462]:1080'); - debugPrint('Proxy connect result: $result'); - } else { - final result = await widget.service.proxyDisconnect(); - // Stop proxy probing when disabled - await widget.service.stopProxyProbe(); - debugPrint('Proxy disconnect result: $result'); - } - }, - ), - ), - ], - ), - ), - ], + // Proxy controls temporarily disabled + // if (widget.status == NodeStatus.connected) ...[ + // const SizedBox(height: AppSpacing.lg), + // AppCard( + // margin: EdgeInsets.zero, + // child: ExpansionTile( + // title: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: const [ + // Flexible( + // child: Text('Advanced Options', + // overflow: TextOverflow.ellipsis), + // ), + // Icon(Icons.expand_more), + // ], + // ), + // children: [ + // ListTile( + // title: const Text('Enable SOCKS5 tunneling as VPN'), + // trailing: Switch( + // value: _isSocks5Enabled, + // onChanged: (value) async { + // setState(() => _isSocks5Enabled = value); + // if (value) { + // // First start proxy probing to discover available proxies + // await widget.service.startProxyProbe(); + // // Wait a bit for probes to discover proxies + // await Future.delayed(Duration(seconds: 10)); + // // Then connect to best available proxy + // final result = await widget.service.proxyConnect( + // '[40a:152c:b85b:9646:5b71:d03a:eb27:2462]:1080'); + // debugPrint('Proxy connect result: $result'); + // } else { + // final result = await widget.service.proxyDisconnect(); + // // Stop proxy probing when disabled + // await widget.service.stopProxyProbe(); + // debugPrint('Proxy disconnect result: $result'); + // } + // }, + // ), + // ), + // ], + // ), + // ), + // ], ], ), ); diff --git a/lib/features/home/widgets/desktop_home_layout.dart b/lib/features/home/widgets/desktop_home_layout.dart index 2c31aac..b34284a 100644 --- a/lib/features/home/widgets/desktop_home_layout.dart +++ b/lib/features/home/widgets/desktop_home_layout.dart @@ -200,7 +200,7 @@ class _DesktopConnectionCardState extends State<_DesktopConnectionCard> await widget.service.stopProxyProbe(); setState(() => _isSocks5Enabled = false); } catch (e) { - print('Error stopping proxy: $e'); + debugPrint('Error stopping proxy: $e'); } } @@ -242,7 +242,7 @@ class _DesktopConnectionCardState extends State<_DesktopConnectionCard> color: Theme.of(context) .colorScheme .primary - .withOpacity( + .withValues(alpha: (1.0 - _fadeAnimation.value) * 0.3, ), ), @@ -278,7 +278,7 @@ class _DesktopConnectionCardState extends State<_DesktopConnectionCard> color: Theme.of(context) .colorScheme .primary - .withOpacity( + .withValues(alpha: (1.0 - (_fadeAnimation.value - 0.3) .clamp(0.0, 1.0)) * @@ -317,7 +317,7 @@ class _DesktopConnectionCardState extends State<_DesktopConnectionCard> color: Theme.of(context) .colorScheme .primary - .withOpacity( + .withValues(alpha: (1.0 - (_fadeAnimation.value - 0.6) .clamp(0.0, 1.0)) * @@ -409,7 +409,7 @@ class _DesktopConnectionCardState extends State<_DesktopConnectionCard> color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.7), + .withValues(alpha: 0.7), ), textAlign: TextAlign.center, ), @@ -444,32 +444,7 @@ class _DesktopConnectionCardState extends State<_DesktopConnectionCard> ), ], - // Advanced options for desktop - if (isConnected && false) ...[ - const SizedBox(height: AppSpacing.xxl), - ExpansionTile( - title: const Text('Advanced Options'), - children: [ - SwitchListTile( - title: const Text('Enable SOCKS5 tunneling'), - subtitle: const Text('Use Mycelium as VPN tunnel'), - value: _isSocks5Enabled, - onChanged: (value) async { - setState(() => _isSocks5Enabled = value); - if (value) { - await widget.service.startProxyProbe(); - await Future.delayed(Duration(seconds: 10)); - await widget.service.proxyConnect( - '[40a:152c:b85b:9646:5b71:d03a:eb27:2462]:1080'); - } else { - await widget.service.proxyDisconnect(); - await widget.service.stopProxyProbe(); - } - }, - ), - ], - ), - ], + // Advanced options temporarily disabled ], ), ), @@ -558,14 +533,14 @@ class _DesktopStatsCardsState extends ConsumerState<_DesktopStatsCards> { String _formatBytes(int bytes) { if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; - if (bytes < 1024 * 1024 * 1024) + if (bytes < 1024 * 1024 * 1024) { return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; + } return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB'; } @override Widget build(BuildContext context) { - final nodeStatusAsync = ref.watch(nodeStatusProvider); final uptimeNotifier = ref.watch(uptimeProvider.notifier); final connectedCount = _getConnectedPeersCount(); @@ -580,27 +555,27 @@ class _DesktopStatsCardsState extends ConsumerState<_DesktopStatsCards> { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Icon( - Icons.hub, - size: 32, - color: AppColors.dataPeers, - ), - const SizedBox(height: AppSpacing.sm), - Text( - 'Connected Peers', - style: Theme.of(context).textTheme.titleMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: AppSpacing.sm), - Text( - '$connectedCount', - style: Theme.of(context).textTheme.headlineLarge?.copyWith( - fontWeight: FontWeight.bold, - color: AppColors.dataPeers, - ), - textAlign: TextAlign.center, - ), - ], + Icon( + Icons.hub, + size: 32, + color: AppColors.dataPeers, + ), + const SizedBox(height: AppSpacing.sm), + Text( + 'Connected Peers', + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: AppSpacing.sm), + Text( + '$connectedCount', + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + fontWeight: FontWeight.bold, + color: AppColors.dataPeers, + ), + textAlign: TextAlign.center, + ), + ], ), ), ), @@ -628,29 +603,30 @@ class _DesktopStatsCardsState extends ConsumerState<_DesktopStatsCards> { margin: const EdgeInsets.only(bottom: AppSpacing.lg), padding: const EdgeInsets.all(AppSpacing.lg), child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon( - Icons.podcasts, - size: 32, - color: AppColors.dataTraffic, - ), - const SizedBox(height: AppSpacing.sm), - Text( - 'Network Traffic', - style: Theme.of(context).textTheme.titleMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: AppSpacing.sm), - Text( - trafficDisplay, - style: Theme.of(context).textTheme.headlineLarge?.copyWith( - fontWeight: FontWeight.bold, - color: AppColors.dataTraffic, - ), - textAlign: TextAlign.center, - ), - ], + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.podcasts, + size: 32, + color: AppColors.dataTraffic, + ), + const SizedBox(height: AppSpacing.sm), + Text( + 'Network Traffic', + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: AppSpacing.sm), + Text( + trafficDisplay, + style: + Theme.of(context).textTheme.headlineLarge?.copyWith( + fontWeight: FontWeight.bold, + color: AppColors.dataTraffic, + ), + textAlign: TextAlign.center, + ), + ], ), ), ); @@ -666,27 +642,27 @@ class _DesktopStatsCardsState extends ConsumerState<_DesktopStatsCards> { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Icon( - Icons.access_time, - size: 32, - color: AppColors.dataUptime, - ), - const SizedBox(height: AppSpacing.sm), - Text( - 'Uptime', - style: Theme.of(context).textTheme.titleMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: AppSpacing.sm), - Text( - uptimeNotifier.formattedUptime, - style: Theme.of(context).textTheme.headlineLarge?.copyWith( - fontWeight: FontWeight.bold, - color: AppColors.dataUptime, - ), - textAlign: TextAlign.center, - ), - ], + Icon( + Icons.access_time, + size: 32, + color: AppColors.dataUptime, + ), + const SizedBox(height: AppSpacing.sm), + Text( + 'Uptime', + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: AppSpacing.sm), + Text( + uptimeNotifier.formattedUptime, + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + fontWeight: FontWeight.bold, + color: AppColors.dataUptime, + ), + textAlign: TextAlign.center, + ), + ], ), ), ), @@ -695,36 +671,3 @@ class _DesktopStatsCardsState extends ConsumerState<_DesktopStatsCards> { } } -class _InfoRow extends StatelessWidget { - final String label; - final String value; - final Color? valueColor; - - const _InfoRow({ - required this.label, - required this.value, - this.valueColor, - }); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - label, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), - ), - ), - Text( - value, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w600, - color: valueColor, - ), - ), - ], - ); - } -} diff --git a/lib/features/home/widgets/traffic_chart.dart b/lib/features/home/widgets/traffic_chart.dart index 4f2a5f3..ab84e34 100644 --- a/lib/features/home/widgets/traffic_chart.dart +++ b/lib/features/home/widgets/traffic_chart.dart @@ -21,7 +21,7 @@ class TrafficChart extends StatelessWidget { color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(12), border: Border.all( - color: Theme.of(context).dividerColor.withOpacity(0.1), + color: Theme.of(context).dividerColor.withValues(alpha: 0.1), ), ), child: Column( @@ -38,8 +38,8 @@ class TrafficChart extends StatelessWidget { Text( title, style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - ), + fontWeight: FontWeight.w600, + ), ), ], ), @@ -54,9 +54,13 @@ class TrafficChart extends StatelessWidget { const SizedBox(height: 8), Text( 'Loading traffic data...', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), - ), + style: + Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withValues(alpha: 0.6), + ), ), ], ), @@ -65,140 +69,162 @@ class TrafficChart extends StatelessWidget { ? Center( child: Text( 'Collecting data...', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), - ), + style: + Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withValues(alpha: 0.6), + ), ), ) : LineChart( - LineChartData( - gridData: FlGridData( - show: true, - drawVerticalLine: false, - horizontalInterval: 2, - getDrawingHorizontalLine: (value) { - return FlLine( - color: Theme.of(context).dividerColor.withOpacity(0.1), - strokeWidth: 1, - ); - }, - ), - titlesData: FlTitlesData( - show: true, - rightTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - topTitles: const AxisTitles( - sideTitles: SideTitles(showTitles: false), - ), - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 30, - interval: 4, - getTitlesWidget: (double value, TitleMeta meta) { - final hour = value.toInt(); - if (hour % 4 == 0) { - return SideTitleWidget( - axisSide: meta.axisSide, - child: Text( - '${hour.toString().padLeft(2, '0')}:00', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), - ), - ), - ); - } - return const SizedBox.shrink(); - }, - ), - ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - interval: 2, - reservedSize: 40, - getTitlesWidget: (double value, TitleMeta meta) { - return SideTitleWidget( - axisSide: meta.axisSide, - child: Text( - _formatBytes(value), - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6), - ), - ), + LineChartData( + gridData: FlGridData( + show: true, + drawVerticalLine: false, + horizontalInterval: 2, + getDrawingHorizontalLine: (value) { + return FlLine( + color: Theme.of(context) + .dividerColor + .withValues(alpha: 0.1), + strokeWidth: 1, ); }, ), - ), - ), - borderData: FlBorderData(show: false), - minX: 0, - maxX: 23, - minY: 0, - maxY: _getMaxY(), - lineBarsData: [ - // Download area - LineChartBarData( - spots: data.asMap().entries.map((entry) { - return FlSpot(entry.key.toDouble(), entry.value.download); - }).toList(), - isCurved: true, - gradient: LinearGradient( - colors: [ - AppColors.dataDownload.withOpacity(0.8), - AppColors.dataDownload.withOpacity(0.3), - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - ), - barWidth: 0, - isStrokeCapRound: true, - dotData: const FlDotData(show: false), - belowBarData: BarAreaData( + titlesData: FlTitlesData( show: true, - gradient: LinearGradient( - colors: [ - AppColors.dataDownload.withOpacity(0.4), - AppColors.dataDownload.withOpacity(0.1), - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), ), - ), - ), - // Upload area (stacked on top) - LineChartBarData( - spots: data.asMap().entries.map((entry) { - return FlSpot(entry.key.toDouble(), entry.value.download + entry.value.upload); - }).toList(), - isCurved: true, - gradient: LinearGradient( - colors: [ - AppColors.dataUpload.withOpacity(0.8), - AppColors.dataUpload.withOpacity(0.3), - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - ), - barWidth: 0, - isStrokeCapRound: true, - dotData: const FlDotData(show: false), - belowBarData: BarAreaData( - show: true, - gradient: LinearGradient( - colors: [ - AppColors.dataUpload.withOpacity(0.4), - AppColors.dataUpload.withOpacity(0.1), - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 30, + interval: 4, + getTitlesWidget: + (double value, TitleMeta meta) { + final hour = value.toInt(); + if (hour % 4 == 0) { + return SideTitleWidget( + axisSide: meta.axisSide, + child: Text( + '${hour.toString().padLeft(2, '0')}:00', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withValues(alpha: 0.6), + ), + ), + ); + } + return const SizedBox.shrink(); + }, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 2, + reservedSize: 40, + getTitlesWidget: + (double value, TitleMeta meta) { + return SideTitleWidget( + axisSide: meta.axisSide, + child: Text( + _formatBytes(value), + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withValues(alpha: 0.6), + ), + ), + ); + }, + ), ), ), + borderData: FlBorderData(show: false), + minX: 0, + maxX: 23, + minY: 0, + maxY: _getMaxY(), + lineBarsData: [ + // Download area + LineChartBarData( + spots: data.asMap().entries.map((entry) { + return FlSpot( + entry.key.toDouble(), entry.value.download); + }).toList(), + isCurved: true, + gradient: LinearGradient( + colors: [ + AppColors.dataDownload.withValues(alpha: 0.8), + AppColors.dataDownload.withValues(alpha: 0.3), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + barWidth: 0, + isStrokeCapRound: true, + dotData: const FlDotData(show: false), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + AppColors.dataDownload.withValues(alpha: 0.4), + AppColors.dataDownload.withValues(alpha: 0.1), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + ), + // Upload area (stacked on top) + LineChartBarData( + spots: data.asMap().entries.map((entry) { + return FlSpot(entry.key.toDouble(), + entry.value.download + entry.value.upload); + }).toList(), + isCurved: true, + gradient: LinearGradient( + colors: [ + AppColors.dataUpload.withValues(alpha: 0.8), + AppColors.dataUpload.withValues(alpha: 0.3), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + barWidth: 0, + isStrokeCapRound: true, + dotData: const FlDotData(show: false), + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: [ + AppColors.dataUpload.withValues(alpha: 0.4), + AppColors.dataUpload.withValues(alpha: 0.1), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + ), + ], ), - ], - ), - ), + ), ), const SizedBox(height: AppSpacing.sm), // Legend @@ -223,7 +249,7 @@ class TrafficChart extends StatelessWidget { double _getMaxY() { if (data.isEmpty) return 10; - + double maxValue = 0; for (final point in data) { final total = point.download + point.upload; @@ -231,7 +257,7 @@ class TrafficChart extends StatelessWidget { maxValue = total; } } - + // Add 20% padding to the top return maxValue * 1.2; } @@ -239,7 +265,9 @@ class TrafficChart extends StatelessWidget { String _formatBytes(double bytes) { if (bytes < 1024) return '${bytes.toInt()} B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} K'; - if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} M'; + if (bytes < 1024 * 1024 * 1024) { + return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} M'; + } return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)}G'; } } @@ -270,8 +298,8 @@ class _LegendItem extends StatelessWidget { Text( label, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of(context).colorScheme.onSurface.withOpacity(0.8), - ), + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.8), + ), ), ], ); diff --git a/lib/features/home/widgets/traffic_summary.dart b/lib/features/home/widgets/traffic_summary.dart index da5436a..9c2ca13 100644 --- a/lib/features/home/widgets/traffic_summary.dart +++ b/lib/features/home/widgets/traffic_summary.dart @@ -34,8 +34,8 @@ class TrafficSummary extends StatelessWidget { Text( 'Traffic Summary (24h)', style: Theme.of(context).textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - ), + fontWeight: FontWeight.w600, + ), ), ], ), @@ -69,7 +69,7 @@ class TrafficSummary extends StatelessWidget { icon: Icons.trending_up, label: 'Peak Upload', value: peakUpload, - color: Theme.of(context).colorScheme.primary.withOpacity(0.7), + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.7), ), ), const SizedBox(width: AppSpacing.md), @@ -78,7 +78,7 @@ class TrafficSummary extends StatelessWidget { icon: Icons.trending_down, label: 'Peak Download', value: peakDownload, - color: AppColors.success.withOpacity(0.7), + color: AppColors.success.withValues(alpha: 0.7), ), ), ], @@ -107,7 +107,7 @@ class _TrafficItem extends StatelessWidget { return Container( padding: const EdgeInsets.all(AppSpacing.sm), decoration: BoxDecoration( - color: color.withOpacity(0.1), + color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(AppRadii.sm), ), child: Column( @@ -121,9 +121,9 @@ class _TrafficItem extends StatelessWidget { child: Text( label, style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: color, - fontWeight: FontWeight.w500, - ), + color: color, + fontWeight: FontWeight.w500, + ), overflow: TextOverflow.ellipsis, ), ), @@ -133,9 +133,9 @@ class _TrafficItem extends StatelessWidget { Text( value, style: Theme.of(context).textTheme.titleSmall?.copyWith( - color: color, - fontWeight: FontWeight.w600, - ), + color: color, + fontWeight: FontWeight.w600, + ), ), ], ), diff --git a/lib/features/peers/peers_screen.dart b/lib/features/peers/peers_screen.dart index 35c0a6b..451d4c3 100644 --- a/lib/features/peers/peers_screen.dart +++ b/lib/features/peers/peers_screen.dart @@ -286,7 +286,7 @@ class _PeersMobileLayoutState extends ConsumerState<_PeersMobileLayout> { isMyceliumRunning: isMyceliumRunning, ), ); - }).toList(), + }), const SizedBox(height: AppSpacing.md), AppCard( padding: const EdgeInsets.all(AppSpacing.md), @@ -642,7 +642,8 @@ class _PeerTileState extends ConsumerState<_PeerTile> { width: 32, height: 32, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceVariant, + color: + Theme.of(context).colorScheme.surfaceContainerHighest, shape: BoxShape.circle, ), child: Icon( @@ -697,7 +698,7 @@ class _PeerTileState extends ConsumerState<_PeerTile> { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.7), + .withValues(alpha: 0.7), ), overflow: TextOverflow.ellipsis, maxLines: 1, @@ -727,7 +728,7 @@ class _PeerTileState extends ConsumerState<_PeerTile> { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.5), + .withValues(alpha: 0.5), ), ), ], @@ -882,20 +883,20 @@ class _PeerTileState extends ConsumerState<_PeerTile> { decoration: BoxDecoration( color: _pingResult!.success ? (_pingResult!.latencyMs! < 50 - ? AppColors.success.withOpacity(0.1) + ? AppColors.success.withValues(alpha: 0.1) : _pingResult!.latencyMs! < 150 - ? Colors.orange.withOpacity(0.1) - : AppColors.error.withOpacity(0.1)) - : AppColors.error.withOpacity(0.1), + ? Colors.orange.withValues(alpha: 0.1) + : AppColors.error.withValues(alpha: 0.1)) + : AppColors.error.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), border: Border.all( color: _pingResult!.success ? (_pingResult!.latencyMs! < 50 - ? AppColors.success.withOpacity(0.3) + ? AppColors.success.withValues(alpha: 0.3) : _pingResult!.latencyMs! < 150 - ? Colors.orange.withOpacity(0.3) - : AppColors.error.withOpacity(0.3)) - : AppColors.error.withOpacity(0.3), + ? Colors.orange.withValues(alpha: 0.3) + : AppColors.error.withValues(alpha: 0.3)) + : AppColors.error.withValues(alpha: 0.3), ), ), child: Row( @@ -958,7 +959,7 @@ class _PeersEmptyState extends ConsumerWidget { textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: - Theme.of(context).colorScheme.onSurface.withOpacity(0.6))), + Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6))), const SizedBox(height: AppSpacing.xxl), FilledButton.icon( onPressed: () => _showAddPeerDialog(context, ref), @@ -1016,11 +1017,13 @@ void _showAddPeerDialog(BuildContext context, WidgetRef ref) { builder: (context, setState) { final inputText = controller.text.trim(); bool isValidIP = _isValidIP(inputText); - + // Check if peer already exists - final formattedPeer = inputText.startsWith('tcp://') ? inputText : 'tcp://$inputText:9651'; + final formattedPeer = inputText.startsWith('tcp://') + ? inputText + : 'tcp://$inputText:9651'; bool peerExists = currentPeers.contains(formattedPeer); - + String? errorText; if (inputText.isNotEmpty && !isValidIP) { errorText = 'Please enter a valid IP address'; @@ -1043,7 +1046,10 @@ void _showAddPeerDialog(BuildContext context, WidgetRef ref) { keyboardType: TextInputType.url, onChanged: (value) => setState(() {}), ), - if (errorText == null && inputText.isNotEmpty && isValidIP && !peerExists) + if (errorText == null && + inputText.isNotEmpty && + isValidIP && + !peerExists) Padding( padding: const EdgeInsets.only(top: 8.0), child: Row( @@ -1071,7 +1077,9 @@ void _showAddPeerDialog(BuildContext context, WidgetRef ref) { onPressed: isValidIP && inputText.isNotEmpty && !peerExists ? () async { await peersNotifier.addPeer(formattedPeer); - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); + } } : null, style: ElevatedButton.styleFrom( diff --git a/lib/features/peers/widgets/desktop_peers_layout.dart b/lib/features/peers/widgets/desktop_peers_layout.dart index 83179ab..5eaa140 100644 --- a/lib/features/peers/widgets/desktop_peers_layout.dart +++ b/lib/features/peers/widgets/desktop_peers_layout.dart @@ -12,7 +12,7 @@ import '../../../state/geolocation_providers.dart'; class DesktopPeersLayout extends ConsumerStatefulWidget { final List peers; - const DesktopPeersLayout({required this.peers}); + const DesktopPeersLayout({super.key, required this.peers}); @override ConsumerState createState() => DesktopPeersLayoutState(); @@ -193,13 +193,13 @@ class DesktopPeersLayoutState extends ConsumerState { isMyceliumRunning: isMyceliumRunning, ), ); - }).toList(), + }), ], ), ), - + const SizedBox(width: AppSpacing.lg), - + // Right side - Summary cards Expanded( flex: 1, @@ -504,7 +504,8 @@ class _PeerTileState extends ConsumerState<_PeerTile> { width: 32, height: 32, decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceVariant, + color: + Theme.of(context).colorScheme.surfaceContainerHighest, shape: BoxShape.circle, ), child: Icon( @@ -559,7 +560,7 @@ class _PeerTileState extends ConsumerState<_PeerTile> { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.7), + .withValues(alpha: 0.7), ), overflow: TextOverflow.ellipsis, maxLines: 1, @@ -589,7 +590,7 @@ class _PeerTileState extends ConsumerState<_PeerTile> { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.5), + .withValues(alpha: 0.5), ), ), ], @@ -602,9 +603,10 @@ class _PeerTileState extends ConsumerState<_PeerTile> { ), const SizedBox(height: AppSpacing.xs), // Only show status when Mycelium is running or status is not unknown - if (widget.isMyceliumRunning || - (widget.peerStats != null && - widget.peerStats!.connectionState != peer_models.ConnectionState.unknown)) + if (widget.isMyceliumRunning || + (widget.peerStats != null && + widget.peerStats!.connectionState != + peer_models.ConnectionState.unknown)) Row( children: [ Text( @@ -654,38 +656,35 @@ class _PeerTileState extends ConsumerState<_PeerTile> { color: AppColors.error, ), onPressed: () async { - // Show confirmation dialog - final confirmed = await showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Delete Peer'), - content: Text( - 'Are you sure you want to delete this peer?\n\n${widget.ip.replaceAll('tcp://', '').replaceAll(':9651', '')}', - ), - actions: [ - TextButton( - onPressed: () => - Navigator.of(context).pop(false), - child: const Text('Cancel'), - ), - TextButton( - onPressed: () => - Navigator.of(context).pop(true), - style: TextButton.styleFrom( - foregroundColor: Colors.red, - ), - child: const Text('Delete'), - ), - ], + // Show confirmation dialog + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Delete Peer'), + content: Text( + 'Are you sure you want to delete this peer?\n\n${widget.ip.replaceAll('tcp://', '').replaceAll(':9651', '')}', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + style: TextButton.styleFrom( + foregroundColor: Colors.red, ), - ); + child: const Text('Delete'), + ), + ], + ), + ); - if (confirmed == true) { - final peersNotifier = - ref.read(peersProvider.notifier); - await peersNotifier.removePeer(widget.ip); - } - }, + if (confirmed == true) { + final peersNotifier = ref.read(peersProvider.notifier); + await peersNotifier.removePeer(widget.ip); + } + }, tooltip: 'Delete peer', ), ], @@ -703,15 +702,17 @@ class _PeerTileState extends ConsumerState<_PeerTile> { children: [ Text( 'RX: ${widget.peerStats!.formattedRxBytes}', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppColors.dataDownload, - ), + style: + Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppColors.dataDownload, + ), ), Text( 'TX: ${widget.peerStats!.formattedTxBytes}', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppColors.dataUpload, - ), + style: + Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppColors.dataUpload, + ), ), ], ), @@ -744,20 +745,20 @@ class _PeerTileState extends ConsumerState<_PeerTile> { decoration: BoxDecoration( color: _pingResult!.success ? (_pingResult!.latencyMs! < 50 - ? AppColors.success.withOpacity(0.1) + ? AppColors.success.withValues(alpha: 0.1) : _pingResult!.latencyMs! < 150 - ? Colors.orange.withOpacity(0.1) - : AppColors.error.withOpacity(0.1)) - : AppColors.error.withOpacity(0.1), + ? Colors.orange.withValues(alpha: 0.1) + : AppColors.error.withValues(alpha: 0.1)) + : AppColors.error.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), border: Border.all( color: _pingResult!.success ? (_pingResult!.latencyMs! < 50 - ? AppColors.success.withOpacity(0.3) + ? AppColors.success.withValues(alpha: 0.3) : _pingResult!.latencyMs! < 150 - ? Colors.orange.withOpacity(0.3) - : AppColors.error.withOpacity(0.3)) - : AppColors.error.withOpacity(0.3), + ? Colors.orange.withValues(alpha: 0.3) + : AppColors.error.withValues(alpha: 0.3)) + : AppColors.error.withValues(alpha: 0.3), ), ), child: Row( @@ -820,7 +821,7 @@ class _PeersEmptyState extends ConsumerWidget { textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: - Theme.of(context).colorScheme.onSurface.withOpacity(0.6))), + Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6))), const SizedBox(height: AppSpacing.xxl), FilledButton.icon( onPressed: () => _showAddPeerDialog(context, ref), @@ -878,11 +879,13 @@ void _showAddPeerDialog(BuildContext context, WidgetRef ref) { builder: (context, setState) { final inputText = controller.text.trim(); bool isValidIP = _isValidIP(inputText); - + // Check if peer already exists - final formattedPeer = inputText.startsWith('tcp://') ? inputText : 'tcp://$inputText:9651'; + final formattedPeer = inputText.startsWith('tcp://') + ? inputText + : 'tcp://$inputText:9651'; bool peerExists = currentPeers.contains(formattedPeer); - + String? errorText; if (inputText.isNotEmpty && !isValidIP) { errorText = 'Please enter a valid IP address'; @@ -905,7 +908,10 @@ void _showAddPeerDialog(BuildContext context, WidgetRef ref) { keyboardType: TextInputType.url, onChanged: (value) => setState(() {}), ), - if (errorText == null && inputText.isNotEmpty && isValidIP && !peerExists) + if (errorText == null && + inputText.isNotEmpty && + isValidIP && + !peerExists) Padding( padding: const EdgeInsets.only(top: 8.0), child: Row( @@ -933,7 +939,9 @@ void _showAddPeerDialog(BuildContext context, WidgetRef ref) { onPressed: isValidIP && inputText.isNotEmpty && !peerExists ? () async { await peersNotifier.addPeer(formattedPeer); - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); + } } : null, style: ElevatedButton.styleFrom( diff --git a/lib/features/settings/settings_screen.dart b/lib/features/settings/settings_screen.dart index 561bf32..7196794 100644 --- a/lib/features/settings/settings_screen.dart +++ b/lib/features/settings/settings_screen.dart @@ -268,7 +268,7 @@ class _SettingsMobileLayout extends ConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6)), + .withValues(alpha: 0.6)), overflow: TextOverflow.ellipsis, ), ], @@ -295,7 +295,7 @@ class _SettingsMobileLayout extends ConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6)), + .withValues(alpha: 0.6)), textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, ), @@ -307,7 +307,7 @@ class _SettingsMobileLayout extends ConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6)), + .withValues(alpha: 0.6)), textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, ), @@ -353,7 +353,7 @@ class _InfoRowWithSubtitle extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6)), + .withValues(alpha: 0.6)), ), ], ), @@ -418,7 +418,7 @@ class _InfoRowWithCopy extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6)), + .withValues(alpha: 0.6)), ), ], ), @@ -453,7 +453,7 @@ class _InfoRowWithCopy extends StatelessWidget { tooltip: 'Copy IP Address', style: IconButton.styleFrom( backgroundColor: - Theme.of(context).colorScheme.primary.withOpacity(0.1), + Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), foregroundColor: Theme.of(context).colorScheme.primary, minimumSize: const Size(40, 40), ), diff --git a/lib/features/settings/widgets/desktop_settings_layout.dart b/lib/features/settings/widgets/desktop_settings_layout.dart index ab6ed35..c252f9b 100644 --- a/lib/features/settings/widgets/desktop_settings_layout.dart +++ b/lib/features/settings/widgets/desktop_settings_layout.dart @@ -68,12 +68,12 @@ class DesktopSettingsLayout extends ConsumerWidget { decoration: BoxDecoration( color: Theme.of(context) .colorScheme - .surfaceVariant - .withOpacity(0.3), + .surfaceContainerHighest + .withValues(alpha: 0.3), borderRadius: BorderRadius.circular(AppRadii.md), border: Border.all( color: - Theme.of(context).dividerColor.withOpacity(0.3), + Theme.of(context).dividerColor.withValues(alpha: 0.3), ), ), child: Row( @@ -103,7 +103,7 @@ class DesktopSettingsLayout extends ConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.7), + .withValues(alpha: 0.7), ), ), ], @@ -320,7 +320,7 @@ class DesktopSettingsLayout extends ConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.8), + .withValues(alpha: 0.8), fontWeight: FontWeight.w500, ), ), @@ -331,7 +331,7 @@ class DesktopSettingsLayout extends ConsumerWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.7), + .withValues(alpha: 0.7), height: 1.5, ), ), @@ -418,7 +418,7 @@ class _VersionInfoRow extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.6), + .withValues(alpha: 0.6), ), ), const SizedBox(height: AppSpacing.xs), @@ -476,10 +476,13 @@ class _NetworkInfoRow extends StatelessWidget { return Container( padding: const EdgeInsets.all(AppSpacing.lg), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3), + color: Theme.of(context) + .colorScheme + .surfaceContainerHighest + .withValues(alpha: 0.3), borderRadius: BorderRadius.circular(AppRadii.md), border: Border.all( - color: Theme.of(context).dividerColor.withOpacity(0.3), + color: Theme.of(context).dividerColor.withValues(alpha: 0.3), ), ), child: Column( @@ -509,7 +512,7 @@ class _NetworkInfoRow extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.7), + .withValues(alpha: 0.7), ), ), ], @@ -526,7 +529,7 @@ class _NetworkInfoRow extends StatelessWidget { color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(8), border: Border.all( - color: Theme.of(context).dividerColor.withOpacity(0.3), + color: Theme.of(context).dividerColor.withValues(alpha: 0.3), ), ), padding: const EdgeInsets.all(AppSpacing.md), @@ -548,7 +551,7 @@ class _NetworkInfoRow extends StatelessWidget { tooltip: 'Copy Node Address', style: IconButton.styleFrom( backgroundColor: - Theme.of(context).colorScheme.primary.withOpacity(0.1), + Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), foregroundColor: Theme.of(context).colorScheme.primary, minimumSize: const Size(40, 40), ), diff --git a/lib/main.dart b/lib/main.dart index ed4782d..05f20fc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -103,7 +103,8 @@ class _MyAppState extends ConsumerState await trayManager.setIcon('assets/images/mycelium_icon.png'); _logger.info("Tray icon set using fallback: mycelium_icon.png"); } catch (fallbackError) { - _logger.severe("Failed to set fallback tray icon: $fallbackError"); + _logger + .severe("Failed to set fallback tray icon: $fallbackError"); } } } diff --git a/lib/models/peer_models.dart b/lib/models/peer_models.dart index 8c66730..bee416f 100644 --- a/lib/models/peer_models.dart +++ b/lib/models/peer_models.dart @@ -78,28 +78,28 @@ enum ConnectionState { class PeerStats { /// The protocol used by this peer (e.g., "tcp", "quic") final String protocol; - + /// The socket address of the peer final String address; - + /// The type of peer (static, discovered, etc.) final PeerType peerType; - + /// Current connection state final ConnectionState connectionState; - + /// Total bytes received from this peer final int rxBytes; - + /// Total bytes transmitted to this peer final int txBytes; - + /// Time since this peer was discovered (in seconds) final int discoveredSeconds; - + /// Time since last successful connection (in seconds), null if never connected final int? lastConnectedSeconds; - + /// Location information for this peer (country, city, etc.) final LocationInfo? locationInfo; @@ -121,7 +121,8 @@ class PeerStats { protocol: json['protocol'] as String, address: json['address'] as String, peerType: PeerType.fromString(json['peerType'] as String), - connectionState: ConnectionState.fromString(json['connectionState'] as String), + connectionState: + ConnectionState.fromString(json['connectionState'] as String), rxBytes: json['rxBytes'] as int, txBytes: json['txBytes'] as int, discoveredSeconds: json['discoveredSeconds'] as int, @@ -148,7 +149,9 @@ class PeerStats { static String formatBytes(int bytes) { if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(2)} KB'; - if (bytes < 1024 * 1024 * 1024) return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; + if (bytes < 1024 * 1024 * 1024) { + return '${(bytes / (1024 * 1024)).toStringAsFixed(2)} MB'; + } return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB'; } @@ -191,8 +194,8 @@ class PeerStats { @override String toString() { return 'PeerStats(protocol: $protocol, address: $address, type: $peerType, ' - 'state: $connectionState, rx: $formattedRxBytes, tx: $formattedTxBytes, ' - 'discovered: $formattedDiscovered, lastConnected: $formattedLastConnected)'; + 'state: $connectionState, rx: $formattedRxBytes, tx: $formattedTxBytes, ' + 'discovered: $formattedDiscovered, lastConnected: $formattedLastConnected)'; } @override diff --git a/lib/myceliumflut_ffi_binding.dart b/lib/myceliumflut_ffi_binding.dart index 49422f3..b15f6d8 100644 --- a/lib/myceliumflut_ffi_binding.dart +++ b/lib/myceliumflut_ffi_binding.dart @@ -4,7 +4,6 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart' show join; import 'dart:ffi'; -import 'dart:typed_data'; ffi.DynamicLibrary loadDll() { var dllPath = 'assets/dll/winmycelium.dll'; @@ -102,7 +101,10 @@ String myFFAddressFromSecretKey(Uint8List data) { } typedef FuncRustStartMycelium = ffi.Void Function( - ffi.Pointer>, ffi.Size, ffi.Pointer, ffi.Size); + ffi.Pointer>, + ffi.Size, + ffi.Pointer, + ffi.Size); typedef FuncDartStartMycelium = void Function( ffi.Pointer>, int, ffi.Pointer, int); @@ -119,7 +121,7 @@ Future myFFStartMycelium(List peers, Uint8List privKey) async { bool _startMyceliumInIsolate(Map args) { final List peers = args['peers']; final Uint8List privKey = args['privKey']; - + // Load the dynamic library final dylib = loadDll(); @@ -140,13 +142,12 @@ bool _startMyceliumInIsolate(Map args) { final privKeyPtr = malloc(privKey.length); final nativePrivKey = privKeyPtr.asTypedList(privKey.length); nativePrivKey.setAll(0, privKey); - try { // Call the Rust function (this is the blocking call) startMycelium(peerPtrs, peers.length, privKeyPtr, privKey.length); return true; } catch (e) { - print('Error starting mycelium: $e'); + // FFI binding loaded successfully return false; } finally { // Free the allocated memory @@ -199,7 +200,7 @@ Future> myFFGetPeerStatus() async { final List result = []; for (int i = 0; i < length; i++) { - final stringPtr = ptr.elementAt(i).value; + final stringPtr = (ptr + i).value; if (stringPtr != nullptr) { result.add(stringPtr.cast().toDartString()); } @@ -226,8 +227,13 @@ Future> myFFProxyConnect(String remote) async { try { var dylib = loadDll(); final ffProxyConnect = dylib - .lookup, Pointer>>, Pointer)>>('ff_proxy_connect') - .asFunction, Pointer>>, Pointer)>(); + .lookup< + NativeFunction< + Void Function(Pointer, Pointer>>, + Pointer)>>('ff_proxy_connect') + .asFunction< + void Function(Pointer, Pointer>>, + Pointer)>(); ffProxyConnect(remotePtr, outPtr, outLen); @@ -236,7 +242,7 @@ Future> myFFProxyConnect(String remote) async { final List result = []; for (int i = 0; i < length; i++) { - final stringPtr = ptr.elementAt(i).value; + final stringPtr = (ptr + i).value; if (stringPtr != nullptr) { result.add(stringPtr.cast().toDartString()); } @@ -273,7 +279,7 @@ Future> myFFProxyDisconnect() async { final List result = []; for (int i = 0; i < length; i++) { - final stringPtr = ptr.elementAt(i).value; + final stringPtr = (ptr + i).value; if (stringPtr != nullptr) { result.add(stringPtr.cast().toDartString()); } @@ -309,7 +315,7 @@ Future> myFFStartProxyProbe() async { final List result = []; for (int i = 0; i < length; i++) { - final stringPtr = ptr.elementAt(i).value; + final stringPtr = (ptr + i).value; if (stringPtr != nullptr) { result.add(stringPtr.cast().toDartString()); } @@ -345,7 +351,7 @@ Future> myFFStopProxyProbe() async { final List result = []; for (int i = 0; i < length; i++) { - final stringPtr = ptr.elementAt(i).value; + final stringPtr = (ptr + i).value; if (stringPtr != nullptr) { result.add(stringPtr.cast().toDartString()); } @@ -381,7 +387,7 @@ Future> myFFListProxies() async { final List result = []; for (int i = 0; i < length; i++) { - final stringPtr = ptr.elementAt(i).value; + final stringPtr = (ptr + i).value; if (stringPtr != nullptr) { result.add(stringPtr.cast().toDartString()); } @@ -399,4 +405,3 @@ Future> myFFListProxies() async { calloc.free(outLen); } } - diff --git a/lib/services/ffi/mycelium_service.dart b/lib/services/ffi/mycelium_service.dart index 0833e1a..5c8fcb8 100644 --- a/lib/services/ffi/mycelium_service.dart +++ b/lib/services/ffi/mycelium_service.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:path_provider/path_provider.dart'; @@ -71,15 +72,17 @@ class MyceliumService { final portRegex = RegExp(r':9651$'); if (!prefixRegex.hasMatch(peer)) return 'peer must start with tcp://'; final ipPortPart = peer.substring(peer.indexOf('://') + 3); - if (!ipv4Regex.hasMatch(ipPortPart) && !ipv6Regex.hasMatch(ipPortPart)) + if (!ipv4Regex.hasMatch(ipPortPart) && !ipv6Regex.hasMatch(ipPortPart)) { return 'peer must contain a valid IPv4 or IPv6 address'; + } if (!portRegex.hasMatch(ipPortPart)) return 'peer must end with :9651'; return null; } String? validatePeers(List peers) { - if (peers.isEmpty || (peers.length == 1 && peers[0].isEmpty)) + if (peers.isEmpty || (peers.length == 1 && peers[0].isEmpty)) { return "peers can't be empty"; + } for (final p in peers) { final e = validatePeer(p); if (e != null) return 'invalid peer:`$p` $e'; @@ -90,37 +93,33 @@ class MyceliumService { bool _socksEnabled = false; Future start(List peers, {bool socksEnabled = false}) async { - print('MyceliumService: Starting with peers: $peers, SOCKS: $socksEnabled'); - print('MyceliumService: Platform check - isUseDylib(): ${isUseDylib()}'); + debugPrint('MyceliumService: Starting Mycelium...'); + debugPrint('MyceliumService: Starting with peers: $peers, SOCKS: $socksEnabled'); + debugPrint('MyceliumService: Platform check - isUseDylib(): ${isUseDylib()}'); _socksEnabled = socksEnabled; _status = NodeStatus.connecting; _statusController.add(_status); final cleaned = preprocessPeers(peers); - print('MyceliumService: Cleaned peers: $cleaned'); - final error = validatePeers(cleaned); - if (error != null) { - print('MyceliumService: Validation error: $error'); - _status = NodeStatus.failed; - _statusController.add(_status); - return false; - } - await storePeers(cleaned); - final key = await _loadOrGeneratePrivKey(); - print('MyceliumService: Loaded key, starting VPN...'); try { if (isUseDylib()) { - print('MyceliumService: Using FFI dylib'); - myFFStartMycelium(cleaned, key); + final key = await _loadOrGeneratePrivKey(); + final result = await myFFStartMycelium(cleaned, key); + if (!result) { + debugPrint('MyceliumService: Failed to start Mycelium'); + _status = NodeStatus.failed; + _statusController.add(_status); + return false; + } } else { - print('MyceliumService: Using platform channel'); + final key = await _loadOrGeneratePrivKey(); final result = await _platform.invokeMethod('startVpn', { 'peers': cleaned, 'secretKey': key, 'socksEnabled': socksEnabled, }); - print('MyceliumService: startVpn result: $result'); + debugPrint('MyceliumService: startVpn result: $result'); if (result != true) { - print('MyceliumService: Platform channel returned false'); + debugPrint('MyceliumService: Platform channel returned false'); _status = NodeStatus.failed; _statusController.add(_status); return false; @@ -128,10 +127,10 @@ class MyceliumService { } _status = NodeStatus.connected; _statusController.add(_status); - print('MyceliumService: Successfully connected'); + debugPrint('MyceliumService: Mycelium started successfully'); return true; } catch (e) { - print('MyceliumService: Failed to start: $e'); + debugPrint('MyceliumService: Error starting Mycelium: $e'); _status = NodeStatus.failed; _statusController.add(_status); return false; @@ -172,7 +171,7 @@ class MyceliumService { await _platform.invokeMethod>('getPeerStatus'); peerStatusStrings = result?.cast() ?? []; } - + // Check for error responses first if (peerStatusStrings.isNotEmpty) { final firstResponse = peerStatusStrings[0]; @@ -180,7 +179,7 @@ class MyceliumService { // Handle error responses like "err_node_timeout" throw Exception('Mycelium service error: $firstResponse'); } - + // Filter out the first element if it's "ok" (status indicator) if (firstResponse == "ok") { peerStatusStrings = peerStatusStrings.sublist(1); @@ -195,7 +194,7 @@ class MyceliumService { if (jsonString.trim().isEmpty || jsonString.startsWith('err_')) { continue; } - + final Map json = jsonDecode(jsonString); peerStats.add(PeerStats.fromJson(json)); } catch (e) { @@ -239,7 +238,7 @@ class MyceliumService { }; return jsonEncode(statusMap); } - + try { final peerStats = await getPeerStatus(); final statusMap = { @@ -257,7 +256,7 @@ class MyceliumService { } } } catch (e) { - print("Failed to get status: $e"); + debugPrint("Failed to get status: $e"); return null; } } @@ -278,7 +277,7 @@ class MyceliumService { return result?.cast() ?? []; } } catch (e) { - print("Failed to proxyConnect: $e"); + debugPrint("Failed to proxyConnect: $e"); return ['Failed to connect proxy']; } } @@ -293,7 +292,7 @@ class MyceliumService { return result?.cast() ?? []; } } catch (e) { - print("Failed to proxyDisconnect: $e"); + debugPrint("Failed to proxyDisconnect: $e"); return ['Failed to disconnect proxy']; } } @@ -308,7 +307,7 @@ class MyceliumService { return result?.cast() ?? []; } } catch (e) { - print("Failed to startProxyProbe: $e"); + debugPrint("Failed to startProxyProbe: $e"); return ['Failed to start proxy probe']; } } @@ -323,7 +322,7 @@ class MyceliumService { return result?.cast() ?? []; } } catch (e) { - print("Failed to stopProxyProbe: $e"); + debugPrint("Failed to stopProxyProbe: $e"); return ['Failed to stop proxy probe']; } } @@ -338,7 +337,7 @@ class MyceliumService { return result?.cast() ?? []; } } catch (e) { - print("Failed to listProxies: $e"); + debugPrint("Failed to listProxies: $e"); return ['Failed to list proxies']; } } diff --git a/lib/services/geolocation_service.dart b/lib/services/geolocation_service.dart index 1bff23f..f6951ba 100644 --- a/lib/services/geolocation_service.dart +++ b/lib/services/geolocation_service.dart @@ -106,7 +106,7 @@ class GeolocationService { size: 18, ); } - if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS ) { + if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { return Text( getFlagEmoji(countryCode), style: const TextStyle(fontSize: 18), diff --git a/lib/services/peers_service.dart b/lib/services/peers_service.dart index 42d0f9e..ae59605 100644 --- a/lib/services/peers_service.dart +++ b/lib/services/peers_service.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import '../models/peer_models.dart'; import 'ffi/mycelium_service.dart'; @@ -25,7 +26,7 @@ class PeersService { final response = await http.get(Uri.parse(_url)); if (response.statusCode != 200) { - print("failed to load peers from remote. Using fallback peers."); + debugPrint("failed to load peers from remote. Using fallback peers."); return _fallbackPeers; } @@ -33,7 +34,7 @@ class PeersService { final peers = List.from(jsonData['mycelium']['peers']); return peers; } catch (e) { - print("error fetching peers: $e. Using fallback peers."); + debugPrint("error fetching peers: $e. Using fallback peers."); return _fallbackPeers; } } @@ -41,20 +42,10 @@ class PeersService { Future> fetchPeerStats() async { try { final service = MyceliumService(); - final statusJson = await service.getStatus(); - - if (statusJson == null || statusJson.isEmpty) { - return []; - } - - final statusData = jsonDecode(statusJson); - final peersData = statusData['peers'] as List?; - - if (peersData == null) return []; - - return peersData.map((peerJson) => PeerStats.fromJson(peerJson)).toList(); + final peerStatusList = await service.getPeerStatus(); + return peerStatusList; } catch (e) { - print("Error fetching peer stats: $e"); + debugPrint('PeersService: Error getting peer status: $e'); return []; } } diff --git a/lib/services/ping_service.dart b/lib/services/ping_service.dart index 6af3351..971a666 100644 --- a/lib/services/ping_service.dart +++ b/lib/services/ping_service.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class PingResult { @@ -51,7 +52,7 @@ class PingService { timestamp: DateTime.now(), ); } catch (e) { - print('Ping failed for $host: $e'); + debugPrint('Ping error for $host: $e'); return PingResult( host: host, latencyMs: null, @@ -116,7 +117,7 @@ class LatencyNotifier extends StateNotifier { final latency = await _pingService.getAverageLatency(); state = latency; } catch (e) { - print('LatencyNotifier: Error updating latency: $e'); + debugPrint('LatencyNotifier: Error updating latency: $e'); } } diff --git a/lib/state/app_settings.dart b/lib/state/app_settings.dart index 734f50e..52c02b4 100644 --- a/lib/state/app_settings.dart +++ b/lib/state/app_settings.dart @@ -15,7 +15,8 @@ class AppSettings extends ChangeNotifier { final prefs = await SharedPreferences.getInstance(); final saved = prefs.getString(_kThemeModeKey); if (saved != null) { - _themeMode = ThemeMode.values.firstWhere((m) => m.toString() == saved, orElse: () => ThemeMode.light); + _themeMode = ThemeMode.values.firstWhere((m) => m.toString() == saved, + orElse: () => ThemeMode.light); notifyListeners(); } } @@ -31,5 +32,3 @@ class AppSettings extends ChangeNotifier { final appSettingsProvider = ChangeNotifierProvider((ref) { return AppSettings(); }); - - diff --git a/lib/state/dynamic_traffic_providers.dart b/lib/state/dynamic_traffic_providers.dart index 6952924..4fcf13a 100644 --- a/lib/state/dynamic_traffic_providers.dart +++ b/lib/state/dynamic_traffic_providers.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'mycelium_providers.dart'; import '../services/ffi/mycelium_service.dart'; @@ -27,8 +28,9 @@ class TrafficStats { static String _formatBytes(int bytes) { if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; - if (bytes < 1024 * 1024 * 1024) + if (bytes < 1024 * 1024 * 1024) { return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; + } return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB'; } } @@ -89,8 +91,10 @@ class DynamicTrafficNotifier extends StateNotifier { } // Calculate rates (bytes per second over 5-second interval) - final rxRate = _previousTotalRx > 0 ? ((totalRx - _previousTotalRx) / 5).round() : 0; - final txRate = _previousTotalTx > 0 ? ((totalTx - _previousTotalTx) / 5).round() : 0; + final rxRate = + _previousTotalRx > 0 ? ((totalRx - _previousTotalRx) / 5).round() : 0; + final txRate = + _previousTotalTx > 0 ? ((totalTx - _previousTotalTx) / 5).round() : 0; // Update peak rates if (rxRate > _maxRxRate) _maxRxRate = rxRate; @@ -98,8 +102,10 @@ class DynamicTrafficNotifier extends StateNotifier { // Accumulate total traffic instead of replacing it final currentState = state; - final newTotalUpload = currentState.totalUploadBytes + (totalTx - _previousTotalTx).abs(); - final newTotalDownload = currentState.totalDownloadBytes + (totalRx - _previousTotalRx).abs(); + final newTotalUpload = + currentState.totalUploadBytes + (totalTx - _previousTotalTx).abs(); + final newTotalDownload = + currentState.totalDownloadBytes + (totalRx - _previousTotalRx).abs(); // Update state with accumulated totals state = TrafficStats( @@ -113,7 +119,7 @@ class DynamicTrafficNotifier extends StateNotifier { _previousTotalRx = totalRx; _previousTotalTx = totalTx; } catch (e) { - print('DynamicTrafficNotifier: Error updating traffic stats: $e'); + debugPrint('DynamicTrafficNotifier: Error updating traffic stats: $e'); } } diff --git a/lib/state/geolocation_providers.dart b/lib/state/geolocation_providers.dart index 0e284a0..6130d83 100644 --- a/lib/state/geolocation_providers.dart +++ b/lib/state/geolocation_providers.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../services/geolocation_service.dart'; @@ -7,15 +8,19 @@ final geolocationServiceProvider = Provider((ref) { }); // Provider for peer location cache -final peerLocationProvider = StateNotifierProvider.family((ref, peerAddress) { - return PeerLocationNotifier(ref.read(geolocationServiceProvider), peerAddress); +final peerLocationProvider = + StateNotifierProvider.family( + (ref, peerAddress) { + return PeerLocationNotifier( + ref.read(geolocationServiceProvider), peerAddress); }); class PeerLocationNotifier extends StateNotifier { final GeolocationService _geolocationService; final String _peerAddress; - PeerLocationNotifier(this._geolocationService, this._peerAddress) : super(null) { + PeerLocationNotifier(this._geolocationService, this._peerAddress) + : super(null) { _fetchLocation(); } @@ -26,14 +31,14 @@ class PeerLocationNotifier extends StateNotifier { if (cleanIP.contains(':')) { cleanIP = cleanIP.split(':')[0]; // Remove port if present } - + final location = await _geolocationService.getLocationForIP(cleanIP); - + if (mounted) { state = location; } } catch (e) { - print('Error fetching location for $_peerAddress: $e'); + debugPrint('Error fetching location for $_peerAddress: $e'); if (mounted) { state = LocationInfo.unknown(); } diff --git a/lib/state/mycelium_providers.dart b/lib/state/mycelium_providers.dart index 80970e7..e588ae3 100644 --- a/lib/state/mycelium_providers.dart +++ b/lib/state/mycelium_providers.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:myceliumflut/features/peers/peers_repository.dart'; import '../services/ffi/mycelium_service.dart'; @@ -18,13 +19,11 @@ final nodeStatusProvider = StreamProvider((ref) { class UptimeNotifier extends StateNotifier { Timer? _timer; - + UptimeNotifier() : super(null); void startUptime() { - if (state == null) { - state = DateTime.now(); - } + state ??= DateTime.now(); } void stopUptime() { @@ -39,7 +38,7 @@ class UptimeNotifier extends StateNotifier { String get formattedUptime { final duration = uptime; if (duration == null) return '0s'; - + if (duration.inDays > 0) { return '${duration.inDays}d ${duration.inHours % 24}h'; } else if (duration.inHours > 0) { @@ -60,7 +59,7 @@ class UptimeNotifier extends StateNotifier { final uptimeProvider = StateNotifierProvider((ref) { final notifier = UptimeNotifier(); - + // Listen to node status changes ref.listen(nodeStatusProvider, (previous, next) { next.whenData((status) { @@ -71,7 +70,7 @@ final uptimeProvider = StateNotifierProvider((ref) { } }); }); - + return notifier; }); @@ -84,7 +83,8 @@ class PeersNotifier extends StateNotifier>> { List get userPeers => _userPeers; - PeersNotifier(this._service, this._repo, this._myceliumService) : super(const AsyncLoading()) { + PeersNotifier(this._service, this._repo, this._myceliumService) + : super(const AsyncLoading()) { _fetchPeers(); } @@ -107,31 +107,31 @@ class PeersNotifier extends StateNotifier>> { if (!current.contains(peer)) { state = AsyncData([...current, peer]); } - + // Restart Mycelium if it's currently running await _restartMyceliumIfRunning(); } Future removePeer(String peer) async { await _repo.removePeer(peer); - _userPeers.remove(peer); + _userPeers.remove(peer); final current = state.value ?? []; state = AsyncData(current.where((p) => p != peer).toList()); - + // Restart Mycelium if it's currently running await _restartMyceliumIfRunning(); } Future _restartMyceliumIfRunning() async { if (_myceliumService.status == NodeStatus.connected) { - print('PeersNotifier: Restarting Mycelium after peer change...'); - + debugPrint('PeersNotifier: Restarting Mycelium after peer change...'); + // Stop the service await _myceliumService.stop(); - + // Wait a moment for the stop to complete await Future.delayed(const Duration(milliseconds: 500)); - + // Get the updated peer list and restart final peers = state.value ?? []; if (peers.isNotEmpty) { @@ -141,8 +141,8 @@ class PeersNotifier extends StateNotifier>> { final fallbackPeers = await _service.fetchPeers(); await _myceliumService.start(fallbackPeers); } - - print('PeersNotifier: Mycelium restart completed'); + + debugPrint('PeersNotifier: Mycelium restart completed'); } } } diff --git a/lib/state/traffic_providers.dart b/lib/state/traffic_providers.dart index e5a586e..b59b151 100644 --- a/lib/state/traffic_providers.dart +++ b/lib/state/traffic_providers.dart @@ -1,17 +1,20 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'dart:math'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../features/home/widgets/traffic_chart.dart'; import '../models/peer_models.dart'; // Provider for historical traffic data -final trafficHistoryProvider = StateNotifierProvider>((ref) { +final trafficHistoryProvider = + StateNotifierProvider>( + (ref) { return TrafficHistoryNotifier(); }); class TrafficHistoryNotifier extends StateNotifier> { Timer? _timer; - + TrafficHistoryNotifier() : super([]) { // Initialize with minimal data immediately _initializeMinimalData(); @@ -23,8 +26,14 @@ class TrafficHistoryNotifier extends StateNotifier> { // Add just a few data points to prevent empty chart errors final now = DateTime.now(); state = [ - TrafficDataPoint(upload: 0, download: 0, timestamp: now.subtract(const Duration(hours: 2))), - TrafficDataPoint(upload: 0, download: 0, timestamp: now.subtract(const Duration(hours: 1))), + TrafficDataPoint( + upload: 0, + download: 0, + timestamp: now.subtract(const Duration(hours: 2))), + TrafficDataPoint( + upload: 0, + download: 0, + timestamp: now.subtract(const Duration(hours: 1))), TrafficDataPoint(upload: 0, download: 0, timestamp: now), ]; } @@ -36,7 +45,7 @@ class TrafficHistoryNotifier extends StateNotifier> { _initializeData(); _startPeriodicUpdates(); } catch (e) { - print('TrafficHistoryNotifier: Error initializing data: $e'); + debugPrint('TrafficHistoryNotifier: Error initializing data: $e'); // Keep minimal data on error } } @@ -45,40 +54,40 @@ class TrafficHistoryNotifier extends StateNotifier> { // Initialize with 24 hours of sample data (one point per hour) final now = DateTime.now(); final List initialData = []; - + for (int i = 23; i >= 0; i--) { final timestamp = now.subtract(Duration(hours: i)); // Generate some sample data with realistic patterns final baseUpload = _generateRealisticTraffic(i, isUpload: true); final baseDownload = _generateRealisticTraffic(i, isUpload: false); - + initialData.add(TrafficDataPoint( upload: baseUpload, download: baseDownload, timestamp: timestamp, )); } - + state = initialData; } double _generateRealisticTraffic(int hourAgo, {required bool isUpload}) { final random = Random(hourAgo + (isUpload ? 1000 : 0)); - + // Create realistic daily patterns final hour = (24 - hourAgo) % 24; double baseActivity = 1.0; - + // Higher activity during day hours (8-22), lower at night if (hour >= 8 && hour <= 22) { baseActivity = 0.5 + 0.5 * sin((hour - 8) * pi / 14); } else { baseActivity = 0.1 + 0.2 * random.nextDouble(); } - + // Upload is typically lower than download final multiplier = isUpload ? 0.3 : 1.0; - + // Generate bytes (in MB range for visibility) final baseMB = baseActivity * multiplier * (2 + 6 * random.nextDouble()); return baseMB * 1024 * 1024; // Convert to bytes @@ -95,45 +104,45 @@ class TrafficHistoryNotifier extends StateNotifier> { // For now, we'll simulate realistic updates final now = DateTime.now(); final random = Random(); - + // Remove oldest data point and add new one final newData = List.from(state); if (newData.length >= 24) { newData.removeAt(0); } - + // Generate new data point with some randomness final upload = 0.5 + 2.0 * random.nextDouble(); // 0.5-2.5 MB final download = 1.0 + 4.0 * random.nextDouble(); // 1-5 MB - + newData.add(TrafficDataPoint( upload: upload * 1024 * 1024, // Convert to bytes download: download * 1024 * 1024, timestamp: now, )); - + state = newData; } void updateWithPeerData(List peerStats) { // This method can be called to update with real peer traffic data if (peerStats.isEmpty) return; - + final now = DateTime.now(); double totalUpload = 0; double totalDownload = 0; - + for (final peer in peerStats) { totalUpload += peer.txBytes.toDouble(); totalDownload += peer.rxBytes.toDouble(); } - + // Update the most recent data point or add a new one final newData = List.from(state); if (newData.isNotEmpty) { final lastPoint = newData.last; final timeDiff = now.difference(lastPoint.timestamp); - + if (timeDiff.inMinutes < 30) { // Update the last point if it's recent newData[newData.length - 1] = TrafficDataPoint( @@ -153,7 +162,7 @@ class TrafficHistoryNotifier extends StateNotifier> { )); } } - + state = newData; }