flutter_dev_panel 0.0.1 copy "flutter_dev_panel: ^0.0.1" to clipboard
flutter_dev_panel: ^0.0.1 copied to clipboard

A comprehensive Flutter debug panel for development with network monitoring, performance tracking, and environment switching.

example/lib/main.dart

import 'dart:async';
import 'dart:developer' as developer;
import 'package:flutter/material.dart';
import 'package:flutter_dev_panel/flutter_dev_panel.dart';
import 'package:flutter_dev_panel_console/flutter_dev_panel_console.dart';
import 'package:flutter_dev_panel_network/flutter_dev_panel_network.dart';
import 'package:flutter_dev_panel_device/flutter_dev_panel_device.dart';
import 'package:flutter_dev_panel_performance/flutter_dev_panel_performance.dart';
import 'package:dio/dio.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:logger/logger.dart';

// Create a global logger instance
final logger = Logger(
  printer: PrettyPrinter(
    methodCount: 0,
    errorMethodCount: 5,
    lineLength: 50,
    colors: true,
    printEmojis: true,
    dateTimeFormat: DateTimeFormat.none,
  ),
);

void main() async {
  // Run the entire app in a Zone that intercepts print
  runZonedGuarded(() async {
    WidgetsFlutterBinding.ensureInitialized();

    // 初始化Dio
    final dio = Dio();
    NetworkModule.attachToDio(dio);

    // 初始化GraphQL客户端
    const graphQLEndpoint = 'https://countries.trevorblades.com/';
    final graphQLClient = GraphQLClient(
      link: HttpLink(graphQLEndpoint),
      cache: GraphQLCache(),
    );

    // 添加GraphQL监控(会自动检测endpoint,也可以手动传入)
    final monitoredGraphQLClient = NetworkModule.attachToGraphQL(
      graphQLClient,
      endpoint: graphQLEndpoint, // 可选:手动指定以确保显示正确
    );

    // Initialize environment configurations
    // Priority: .env files > code configuration > saved configuration
    await EnvironmentManager.instance.initialize(
      loadFromEnvFiles: true, // Try to load from .env files first
      environments: [
        // These are fallback configurations if .env files are not found
        const EnvironmentConfig(
          name: 'Development',
          variables: {
            'api_url': 'https://dev-api.example.com',
            'socket_url': 'wss://dev-socket.example.com',
            'graphql_endpoint': 'https://countries.trevorblades.com/',
            'debug': true,
            'log_level': 'verbose',
            'timeout': 30000,
          },
          isDefault: true,
        ),
        const EnvironmentConfig(
          name: 'Production',
          variables: {
            'api_url': 'https://api.example.com',
            'socket_url': 'wss://socket.example.com',
            'graphql_endpoint': 'https://graphql.example.com/',
            'debug': false,
            'log_level': 'error',
            'timeout': 15000,
          },
        ),
      ],
      defaultEnvironment: 'Development',
    );

    // Initialize Flutter Dev Panel with log capture
    FlutterDevPanel.initialize(
      config: const DevPanelConfig(
        enabled: true,
        triggerModes: {TriggerMode.fab, TriggerMode.shake},
        showInProduction: false,
      ),
      modules: [
        const ConsoleModule(), // Console 第一个显示
        NetworkModule(),
        const DeviceModule(),
        const PerformanceModule(),
      ],
      enableLogCapture: true,
    );
    
    // 配置日志捕获(可选,默认使用 development 模式)
    DevLogger.instance.updateConfig(
      const LogCaptureConfig.development(), // 开发模式:捕获应用和库日志
    );

    runApp(MyApp(
      dio: dio,
      graphQLClient: monitoredGraphQLClient,
    ));
  }, (error, stack) {
    // Errors will be captured by DevLogger
    DevLogger.instance.error(
      'Uncaught Error in Zone',
      error: error.toString(),
      stackTrace: stack.toString(),
    );
  }, zoneSpecification: ZoneSpecification(
    print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
      // Capture print statements
      DevLogger.instance.info(line);
      // Still print to console
      parent.print(zone, line);
    },
  ));
}

class MyApp extends StatelessWidget {
  final Dio dio;
  final GraphQLClient graphQLClient;

  const MyApp({
    super.key,
    required this.dio,
    required this.graphQLClient,
  });

  @override
  Widget build(BuildContext context) {
    // Listen to theme changes
    return ListenableBuilder(
      listenable: ThemeManager.instance,
      builder: (context, _) {
        final themeManager = ThemeManager.instance;
        
        return GraphQLProvider(
          client: ValueNotifier(graphQLClient),
          child: MaterialApp(
            title: 'Flutter Dev Panel Example',
            // Theme configuration from ThemeManager
            themeMode: themeManager.currentTheme.mode,
            theme: themeManager.getThemeData(
              context,
              baseTheme: ThemeData(
                colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
                useMaterial3: true,
              ),
            ) ?? ThemeData(
              colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
              useMaterial3: true,
            ),
            darkTheme: ThemeData(
              colorScheme: ColorScheme.fromSeed(
                seedColor: Colors.deepPurple,
                brightness: Brightness.dark,
              ),
              useMaterial3: true,
            ),
            home: DevPanelWrapper(
              child: MyHomePage(dio: dio),
            ),
          ),
        );
      },
    );
  }
}

class MyHomePage extends StatefulWidget {
  final Dio dio;

  const MyHomePage({super.key, required this.dio});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;
  String _responseText = 'Click button to send request';
  bool _isLoading = false;
  
  // Environment variables
  String _currentEnv = '';
  String _apiUrl = '';

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this);
    
    // Initialize environment values
    _updateEnvironmentValues();
    
    // Listen to environment changes
    EnvironmentManager.instance.addListener(_updateEnvironmentValues);
  }
  
  void _updateEnvironmentValues() {
    setState(() {
      _currentEnv = EnvironmentManager.instance.currentEnvironment?.name ?? 'None';
      _apiUrl = EnvironmentManager.instance.getVariable<String>('api_url') ?? 'Not configured';
    });
  }

  @override
  void dispose() {
    _tabController.dispose();
    EnvironmentManager.instance.removeListener(_updateEnvironmentValues);
    super.dispose();
  }

  // REST API 请求方法
  Future<void> _sendRequest() async {
    setState(() {
      _isLoading = true;
      _responseText = 'Sending request...';
    });

    try {
      final response =
          await widget.dio.get('https://jsonplaceholder.typicode.com/posts/1');

      setState(() {
        _responseText = 'Request successful!';
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _responseText = 'Error: $e';
        _isLoading = false;
      });
    }
  }

  Future<void> _sendMultipleRequests() async {
    setState(() {
      _isLoading = true;
      _responseText = 'Sending multiple requests...';
    });

    try {
      await Future.wait([
        widget.dio.get('https://jsonplaceholder.typicode.com/posts/1'),
        widget.dio.get('https://jsonplaceholder.typicode.com/posts/2'),
        widget.dio.get('https://jsonplaceholder.typicode.com/posts/3'),
        widget.dio.get('https://jsonplaceholder.typicode.com/users/1'),
        widget.dio.get('https://jsonplaceholder.typicode.com/comments/1'),
      ]);

      setState(() {
        _responseText = 'Successfully sent 5 requests!';
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _responseText = 'Error: $e';
        _isLoading = false;
      });
    }
  }

  Future<void> _sendErrorRequest() async {
    setState(() {
      _isLoading = true;
      _responseText = 'Sending error request...';
    });

    try {
      await widget.dio.get('https://httpstat.us/500');
      setState(() {
        _responseText = 'Request successful';
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _responseText = 'Expected Error: $e';
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Flutter Dev Panel Example'),
        actions: [
          IconButton(
            icon: const Icon(Icons.bug_report),
            onPressed: () => FlutterDevPanel.open(context),
            tooltip: 'Open Dev Panel',
          ),
        ],
        bottom: TabBar(
          controller: _tabController,
          tabs: const [
            Tab(text: 'Network', icon: Icon(Icons.cloud)),
            Tab(text: 'Modules', icon: Icon(Icons.dashboard)),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          // Network testing page (includes REST API and GraphQL)
          _buildNetworkTab(),
          // Modules showcase page
          _buildModulesTab(),
        ],
      ),
    );
  }

  Widget _buildNetworkTab() {
    return DefaultTabController(
      length: 2,
      child: Column(
        children: [
          // Network module header
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(16),
            color: Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.3),
            child: Column(
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Icon(
                      Icons.network_check,
                      color: Theme.of(context).colorScheme.primary,
                      size: 28,
                    ),
                    const SizedBox(width: 12),
                    Text(
                      'Network Monitor Module',
                      style: TextStyle(
                        fontSize: 22,
                        fontWeight: FontWeight.bold,
                        color: Theme.of(context).colorScheme.primary,
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 8),
                Text(
                  'Test HTTP requests, GraphQL queries, and monitor network traffic',
                  style: TextStyle(
                    fontSize: 13,
                    color: Theme.of(context).colorScheme.onSurfaceVariant,
                  ),
                ),
              ],
            ),
          ),
          Container(
            color: Theme.of(context).colorScheme.surfaceContainerHighest,
            child: const TabBar(
              tabs: [
                Tab(text: 'REST API'),
                Tab(text: 'GraphQL'),
              ],
            ),
          ),
          Expanded(
            child: TabBarView(
              children: [
                _buildRestApiSection(),
                const GraphQLTestPage(),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildRestApiSection() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Center(
        child: Column(
          children: <Widget>[
            const SizedBox(height: 20),
            const Text(
              'REST API Testing',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),
            Wrap(
              spacing: 10,
              runSpacing: 10,
              children: [
                ElevatedButton.icon(
                  onPressed: _isLoading ? null : _sendRequest,
                  icon: const Icon(Icons.send),
                  label: const Text('Send Single Request'),
                ),
                ElevatedButton.icon(
                  onPressed: _isLoading ? null : _sendMultipleRequests,
                  icon: const Icon(Icons.dynamic_feed),
                  label: const Text('Send Multiple Requests'),
                ),
                ElevatedButton.icon(
                  onPressed: _isLoading ? null : _sendErrorRequest,
                  icon: const Icon(Icons.error),
                  label: const Text('Send Error Request'),
                ),
              ],
            ),
            const SizedBox(height: 20),
            _buildResponseCard(),
          ],
        ),
      ),
    );
  }

  Widget _buildModulesTab() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const SizedBox(height: 20),
          const Center(
            child: Text(
              'Flutter Dev Panel Modules',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
          ),
          const SizedBox(height: 10),
          const Center(
            child: Text(
              'Explore all available debugging modules',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
          ),
          const SizedBox(height: 30),
          
          // Network Module Card
          _buildModuleCard(
            icon: Icons.network_check,
            title: 'Network Monitor',
            description: 'Track HTTP requests, GraphQL queries, and WebSocket connections',
            features: [
              'Real-time request tracking',
              'Request/Response inspection',
              'GraphQL support',
              'Error monitoring',
            ],
            color: Colors.blue,
          ),
          
          const SizedBox(height: 16),
          
          // Device Module Card
          _buildModuleCard(
            icon: Icons.phone_android,
            title: 'Device Information',
            description: 'View device specifications and system information',
            features: [
              'Device model & OS version',
              'Screen dimensions & PPI',
              'Platform information',
              'App package details',
            ],
            color: Colors.green,
          ),
          
          const SizedBox(height: 16),
          
          // Performance Module Card
          _buildModuleCard(
            icon: Icons.speed,
            title: 'Performance Monitor',
            description: 'Monitor app performance metrics in real-time',
            features: [
              'FPS monitoring with charts',
              'Memory usage & peak tracking',
              'Dropped frames detection',
              'Memory trend analysis',
            ],
            color: Colors.orange,
          ),
          
          const SizedBox(height: 16),
          
          // Console Module Card
          _buildModuleCard(
            icon: Icons.terminal,
            title: 'Console / Logs',
            description: 'View and filter application logs and errors',
            features: [
              'Real-time log capture',
              'Log level filtering',
              'Search functionality',
              'Error tracking',
            ],
            color: Colors.purple,
          ),
          
          const SizedBox(height: 20),
          
          // Console Test Actions
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Row(
                    children: [
                      Icon(Icons.bug_report, color: Colors.purple),
                      SizedBox(width: 8),
                      Text(
                        'Console Test Actions',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  Wrap(
                    spacing: 8,
                    runSpacing: 8,
                    children: [
                      ElevatedButton.icon(
                        onPressed: () {
                          DevLogger.instance.verbose('Verbose log message');
                          DevLogger.instance.debug('Debug information');
                          DevLogger.instance.info('Info: Action completed');
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(content: Text('Logs added')),
                          );
                        },
                        icon: const Icon(Icons.add, size: 16),
                        label: const Text('Add Normal Logs'),
                      ),
                      ElevatedButton.icon(
                        onPressed: () {
                          DevLogger.instance.warning('Warning: Resource usage high');
                          DevLogger.instance.error(
                            'Error: Failed to load resource',
                            error: 'FileNotFoundException',
                            stackTrace: 'at loadFile() line 42',
                          );
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(content: Text('Errors added')),
                          );
                        },
                        icon: const Icon(Icons.error_outline, size: 16),
                        label: const Text('Add Errors'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.red,
                          foregroundColor: Colors.white,
                        ),
                      ),
                      ElevatedButton.icon(
                        onPressed: () {
                          ConsoleModule.addTestLogs();
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(content: Text('Test logs added')),
                          );
                        },
                        icon: const Icon(Icons.text_snippet, size: 16),
                        label: const Text('Add Test Logs'),
                      ),
                      ElevatedButton.icon(
                        onPressed: () {
                          // Test print interception
                          print('This is a print statement');
                          print('Debug: User clicked test button');
                          print('Info: Testing print interception');
                          debugPrint('This is a debugPrint statement');
                          
                          // Test developer.log
                          developer.log('Developer log message', name: 'TestApp');
                          developer.log('Complex object', error: {'key': 'value'});
                          
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(content: Text('Print statements captured')),
                          );
                        },
                        icon: const Icon(Icons.print, size: 16),
                        label: const Text('Test Print'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.blueGrey,
                          foregroundColor: Colors.white,
                        ),
                      ),
                      ElevatedButton.icon(
                        onPressed: () {
                          // Test Logger package
                          logger.t('Trace log from Logger package');
                          logger.d('Debug log from Logger package');
                          logger.i('Info log from Logger package');
                          logger.w('Warning log from Logger package');
                          logger.e('Error log from Logger package', 
                            error: Exception('Test exception'),
                            stackTrace: StackTrace.current,
                          );
                          
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(content: Text('Logger package logs sent')),
                          );
                        },
                        icon: const Icon(Icons.library_books, size: 16),
                        label: const Text('Test Logger'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.indigo,
                          foregroundColor: Colors.white,
                        ),
                      ),
                      ElevatedButton.icon(
                        onPressed: () {
                          // Test error throwing
                          try {
                            throw Exception('Test exception for console');
                          } catch (e, stack) {
                            DevLogger.instance.error(
                              'Caught exception',
                              error: e.toString(),
                              stackTrace: stack.toString(),
                            );
                          }
                          
                          // Test async error
                          Future.delayed(const Duration(milliseconds: 100), () {
                            // This will be caught by the Zone error handler
                            DevLogger.instance.warning('Simulating async error...');
                            // Uncomment to test actual error catching:
                            // throw StateError('Async error test');
                          });
                          
                          ScaffoldMessenger.of(context).showSnackBar(
                            const SnackBar(content: Text('Errors thrown and caught')),
                          );
                        },
                        icon: const Icon(Icons.warning, size: 16),
                        label: const Text('Test Errors'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.deepOrange,
                          foregroundColor: Colors.white,
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
          
          const SizedBox(height: 16),
          
          // Current Environment Info
          Card(
            color: Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.3),
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      const Icon(Icons.dns, color: Colors.green),
                      const SizedBox(width: 8),
                      Text(
                        'Current Environment: $_currentEnv',
                        style: const TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 8),
                  Text(
                    'API URL: $_apiUrl',
                    style: TextStyle(
                      fontSize: 14,
                      color: Theme.of(context).colorScheme.onSurfaceVariant,
                      fontFamily: 'monospace',
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    'This value updates automatically when you switch environments in Dev Panel',
                    style: TextStyle(
                      fontSize: 12,
                      color: Theme.of(context).colorScheme.onSurfaceVariant,
                      fontStyle: FontStyle.italic,
                    ),
                  ),
                ],
              ),
            ),
          ),
          
          const SizedBox(height: 16),
          
          // How to use section
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Row(
                    children: [
                      Icon(Icons.info_outline, color: Colors.blue),
                      SizedBox(width: 8),
                      Text(
                        'How to Access Dev Panel',
                        style: TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  _buildAccessMethod(
                    icon: Icons.touch_app,
                    method: 'Floating Button',
                    description: 'Tap the floating debug button',
                  ),
                  const SizedBox(height: 8),
                  _buildAccessMethod(
                    icon: Icons.vibration,
                    method: 'Shake Device',
                    description: 'Shake your device to open',
                  ),
                  const SizedBox(height: 8),
                  _buildAccessMethod(
                    icon: Icons.code,
                    method: 'Programmatically',
                    description: 'Call FlutterDevPanel.open(context)',
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
  
  Widget _buildModuleCard({
    required IconData icon,
    required String title,
    required String description,
    required List<String> features,
    required Color color,
  }) {
    return Card(
      elevation: 2,
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: color.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(icon, color: color, size: 32),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        title,
                        style: const TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        description,
                        style: TextStyle(
                          fontSize: 12,
                          color: Colors.grey[600],
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            const Text(
              'Features:',
              style: TextStyle(
                fontWeight: FontWeight.w600,
                fontSize: 14,
              ),
            ),
            const SizedBox(height: 8),
            ...features.map((feature) => Padding(
              padding: const EdgeInsets.only(left: 16, bottom: 4),
              child: Row(
                children: [
                  Icon(Icons.check, size: 16, color: color),
                  const SizedBox(width: 8),
                  Text(
                    feature,
                    style: const TextStyle(fontSize: 13),
                  ),
                ],
              ),
            )),
          ],
        ),
      ),
    );
  }
  
  Widget _buildAccessMethod({
    required IconData icon,
    required String method,
    required String description,
  }) {
    return Row(
      children: [
        Icon(icon, size: 20, color: Colors.blue),
        const SizedBox(width: 12),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                method,
                style: const TextStyle(
                  fontWeight: FontWeight.w600,
                ),
              ),
              Text(
                description,
                style: TextStyle(
                  fontSize: 12,
                  color: Colors.grey[600],
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildResponseCard() {
    final theme = Theme.of(context);
    final isDark = theme.brightness == Brightness.dark;
    
    return Container(
      padding: const EdgeInsets.all(16),
      constraints: const BoxConstraints(
        maxHeight: 200,
      ),
      decoration: BoxDecoration(
        color: isDark 
          ? theme.colorScheme.surfaceContainerHighest
          : Colors.grey[200],
        borderRadius: BorderRadius.circular(8),
        border: Border.all(
          color: theme.colorScheme.outline.withValues(alpha: 0.2),
        ),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(
            'Response:',
            style: TextStyle(
              fontWeight: FontWeight.bold,
              color: theme.colorScheme.onSurface,
            ),
          ),
          const SizedBox(height: 8),
          if (_isLoading)
            const CircularProgressIndicator()
          else
            Flexible(
              child: SingleChildScrollView(
                child: Text(
                  _responseText,
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    color: _responseText.startsWith('Error')
                        ? theme.colorScheme.error
                        : Colors.green.shade700,
                    fontSize: 14,
                  ),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

// GraphQL 测试页面
class GraphQLTestPage extends StatefulWidget {
  const GraphQLTestPage({super.key});

  @override
  State<GraphQLTestPage> createState() => _GraphQLTestPageState();
}

class _GraphQLTestPageState extends State<GraphQLTestPage> {
  final String _countriesQuery = r'''
    query GetCountries {
      countries {
        code
        name
        emoji
        capital
        currency
      }
    }
  ''';

  final String _continentsQuery = r'''
    query GetContinents {
      continents {
        code
        name
        countries {
          name
        }
      }
    }
  ''';

  String _selectedQuery = 'countries';
  bool _isLoading = false;
  QueryResult? _queryResult;

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16.0),
      child: Column(
        children: [
          const SizedBox(height: 20),
          const Text(
            'GraphQL Testing',
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 10),
          const Text(
            'Using countries.trevorblades.com API',
            style: TextStyle(fontSize: 14, color: Colors.grey),
          ),
          const SizedBox(height: 20),

          // 查询选择器
          Card(
            child: Padding(
              padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Select Query Type:',
                    style: TextStyle(fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 8),
                  Row(
                    children: [
                      Expanded(
                        child: RadioListTile<String>(
                          contentPadding: EdgeInsets.zero,
                          dense: true,
                          title: const Text('Countries', style: TextStyle(fontSize: 14)),
                          value: 'countries',
                          groupValue: _selectedQuery,
                          onChanged: (value) {
                            setState(() {
                              _selectedQuery = value!;
                              _queryResult = null; // Clear previous result when switching
                            });
                          },
                        ),
                      ),
                      Expanded(
                        child: RadioListTile<String>(
                          contentPadding: EdgeInsets.zero,
                          dense: true,
                          title: const Text('Continents', style: TextStyle(fontSize: 14)),
                          value: 'continents',
                          groupValue: _selectedQuery,
                          onChanged: (value) {
                            setState(() {
                              _selectedQuery = value!;
                              _queryResult = null; // Clear previous result when switching
                            });
                          },
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),

          const SizedBox(height: 20),

          // Execute button
          ElevatedButton.icon(
            onPressed: _isLoading ? null : _executeQuery,
            icon: _isLoading 
              ? const SizedBox(
                  width: 16,
                  height: 16,
                  child: CircularProgressIndicator(strokeWidth: 2),
                )
              : const Icon(Icons.send),
            label: Text(
              _isLoading 
                ? 'Loading...' 
                : 'Execute ${_selectedQuery == 'countries' ? 'Countries' : 'Continents'} Query'
            ),
          ),
          
          const SizedBox(height: 20),
          
          // Query result
          if (_queryResult != null)
            _buildQueryResult(_queryResult!),
        ],
      ),
    );
  }

  Future<void> _executeQuery() async {
    setState(() {
      _isLoading = true;
      _queryResult = null;
    });
    
    try {
      final client = GraphQLProvider.of(context).value;
      final QueryOptions options = QueryOptions(
        document: gql(_selectedQuery == 'countries' ? _countriesQuery : _continentsQuery),
        fetchPolicy: FetchPolicy.noCache,
      );
      
      final result = await client.query(options);
      
      setState(() {
        _queryResult = result;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _isLoading = false;
      });
      debugPrint('GraphQL query error: $e');
    }
  }

  Widget _buildQueryResult(QueryResult result) {
    if (result.isLoading) {
      return const Card(
        child: Padding(
          padding: EdgeInsets.all(32.0),
          child: Center(child: CircularProgressIndicator()),
        ),
      );
    }

    if (result.hasException) {
      final theme = Theme.of(context);
      return Card(
        color: theme.colorScheme.errorContainer.withValues(alpha: 0.3),
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              Icon(
                Icons.error, 
                color: theme.colorScheme.error, 
                size: 48,
              ),
              const SizedBox(height: 16),
              Text(
                'GraphQL Error:\n${result.exception}',
                style: TextStyle(color: theme.colorScheme.error),
                textAlign: TextAlign.center,
              ),
            ],
          ),
        ),
      );
    }

    if (_selectedQuery == 'countries') {
      final countries = result.data?['countries'] as List? ?? [];
      return Card(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                'Found ${countries.length} countries:',
                style: const TextStyle(fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 10),
              ...countries.take(5).map((country) => ListTile(
                    leading: Text(
                      country['emoji'] ?? '',
                      style: const TextStyle(fontSize: 24),
                    ),
                    title: Text(country['name'] ?? 'Unknown'),
                    subtitle: Text(
                      'Code: ${country['code']}, Capital: ${country['capital'] ?? 'N/A'}',
                    ),
                    dense: true,
                  )),
              if (countries.length > 5)
                const Padding(
                  padding: EdgeInsets.all(8.0),
                  child: Text(
                    '... More results in Network Monitor panel',
                    style: TextStyle(fontStyle: FontStyle.italic),
                  ),
                ),
            ],
          ),
        ),
      );
    } else {
      final continents = result.data?['continents'] as List? ?? [];
      return Card(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                'Found ${continents.length} continents:',
                style: const TextStyle(fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 10),
              ...continents.map((continent) => ListTile(
                    title: Text(continent['name'] ?? 'Unknown'),
                    subtitle: Text(
                      'Code: ${continent['code']}, ${(continent['countries'] as List).length} countries',
                    ),
                    dense: true,
                  )),
            ],
          ),
        ),
      );
    }
  }
}

// Mutation 示例组件
class GraphQLMutationExample extends StatelessWidget {
  const GraphQLMutationExample({super.key});

  @override
  Widget build(BuildContext context) {
    // 这是一个模拟的mutation,实际API可能不支持
    const String addCountryMutation = r'''
      mutation AddCountry($name: String!, $code: String!) {
        addCountry(name: $name, code: $code) {
          name
          code
        }
      }
    ''';

    return Mutation(
      options: MutationOptions(
        document: gql(addCountryMutation),
        onCompleted: (data) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Mutation completed!')),
          );
        },
        onError: (error) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content:
                  Text('Mutation error: ${error?.graphqlErrors.first.message}'),
              backgroundColor: Colors.red,
            ),
          );
        },
      ),
      builder: (runMutation, result) {
        return ElevatedButton.icon(
          onPressed: () {
            runMutation({
              'name': 'Test Country',
              'code': 'TC',
            });
          },
          icon: result?.isLoading == true
              ? const SizedBox(
                  width: 16,
                  height: 16,
                  child: CircularProgressIndicator(strokeWidth: 2),
                )
              : const Icon(Icons.add),
          label: const Text('Execute Mutation (Mock)'),
        );
      },
    );
  }
}
3
likes
0
points
214
downloads

Publisher

unverified uploader

Weekly Downloads

A comprehensive Flutter debug panel for development with network monitoring, performance tracking, and environment switching.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_dotenv, sensors_plus, shared_preferences

More

Packages that depend on flutter_dev_panel