flutter_device_specs 0.1.0 copy "flutter_device_specs: ^0.1.0" to clipboard
flutter_device_specs: ^0.1.0 copied to clipboard

A Flutter plugin for deep Android hardware inspection, including advertised commercial RAM (Android 14+), CPU, battery, display, network, and sensors.

example/lib/main.dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';

import 'package:flutter_device_specs/flutter_device_specs.dart';

// Palette de couleur personnalisée
const Color _emerald = Color(0xFF10B981);

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
    statusBarColor: Colors.transparent,
    statusBarIconBrightness: Brightness.light,
  ));
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PHONE CAPACITY',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        brightness: Brightness.dark,
        scaffoldBackgroundColor: const Color(0xFF0F172A), // Slate 900
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF6366F1), // Indigo
          brightness: Brightness.dark,
          primary: const Color(0xFF6366F1),
          secondary: _emerald,
          surface: const Color(0xFF1E293B), // Slate 800
        ),
        cardTheme: const CardThemeData(
          color: Color(0xFF1E293B),
          elevation: 2,
          margin: EdgeInsets.symmetric(vertical: 8),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(16)),
          ),
        ),
        appBarTheme: const AppBarTheme(
          backgroundColor: Color(0xFF0F172A),
          elevation: 0,
          centerTitle: true,
          titleTextStyle: TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.w700,
            color: Colors.white,
            letterSpacing: 0.5,
          ),
        ),
      ),
      home: const DeviceInfoScreen(),
    );
  }
}

class DeviceInfoScreen extends StatefulWidget {
  const DeviceInfoScreen({super.key});

  @override
  State<DeviceInfoScreen> createState() => _DeviceInfoScreenState();
}

class _DeviceInfoScreenState extends State<DeviceInfoScreen> with SingleTickerProviderStateMixin {
  final _plugin = FlutterDeviceSpecs();
  late TabController _tabController;

  DeviceInfo? _deviceInfo;
  BatteryInfo? _batteryInfo;
  SensorInfo? _sensorInfo;
  NetworkInfo? _networkInfo;
  String? _error;
  bool _isLoading = true;

  // Search logic
  final TextEditingController _searchController = TextEditingController();
  String _searchQuery = '';
  bool _isSearching = false;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
    _loadAll();
  }

  @override
  void dispose() {
    _tabController.dispose();
    _searchController.dispose();
    super.dispose();
  }

  Future<void> _loadAll() async {
    setState(() => _isLoading = true);
    try {
      final results = await Future.wait([
        _plugin.getDeviceInfo(),
        _plugin.getSensorInfo(),
        _plugin.getNetworkInfo(),
      ]);

      final battery = await _plugin.getBatteryInfo();

      setState(() {
        _deviceInfo = results[0] as DeviceInfo;
        _sensorInfo = results[1] as SensorInfo;
        _networkInfo = results[2] as NetworkInfo;
        _batteryInfo = battery;
        _error = null;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _error = e.toString();
        _isLoading = false;
      });
    }
  }

  void _copyToClipboard(String text, String message) {
    Clipboard.setData(ClipboardData(text: text));
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Row(
          children: [
            const Icon(Icons.check_circle, color: Colors.white),
            const SizedBox(width: 8),
            Text(message),
          ],
        ),
        behavior: SnackBarBehavior.floating,
        backgroundColor: Theme.of(context).colorScheme.secondary,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
        duration: const Duration(seconds: 2),
      ),
    );
  }

  String _generateRawJson() {
    if (_deviceInfo == null) return '{}';
    final map = _deviceInfo!.toMap();
    if (_batteryInfo != null) map['batteryInfo'] = _batteryInfo!.toMap();
    if (_sensorInfo != null) map['sensorInfo'] = _sensorInfo!.toMap();
    if (_networkInfo != null) map['networkInfo'] = _networkInfo!.toMap();
    return const JsonEncoder.withIndent('  ').convert(map);
  }

  // Get flat list of all parameters for search functionality
  List<_SearchItem> _getFlatSearchItems() {
    if (_deviceInfo == null) return [];
    final items = <_SearchItem>[];

    // Identity
    items.add(_SearchItem('Modèle / Model', _deviceInfo!.model, 'Identité / Général', Icons.phone_android));
    items.add(_SearchItem('Fabricant / Manufacturer', _deviceInfo!.manufacturer, 'Identité / Général', Icons.business));
    items.add(_SearchItem('Marque / Brand', _deviceInfo!.brand, 'Identité / Général', Icons.label));
    items.add(_SearchItem('OS / Operating System', _deviceInfo!.operatingSystem, 'Identité / Général', Icons.android));
    items.add(_SearchItem('Version Android / OS Version', _deviceInfo!.systemVersion, 'Identité / Général', Icons.info_outline));
    items.add(_SearchItem('Numéro de build / Build Number', _deviceInfo!.buildNumber, 'Identité / Général', Icons.build));
    items.add(_SearchItem('Version Kernel / Kernel', _deviceInfo!.kernelVersion, 'Identité / Général', Icons.settings_ethernet));

    // Processor
    items.add(_SearchItem('Processeur / Processor', _deviceInfo!.processorInfo.processorName, 'Performances / Matériel', Icons.memory));
    items.add(_SearchItem('Architecture / Architecture', _deviceInfo!.processorInfo.architecture, 'Performances / Matériel', Icons.architecture));
    items.add(_SearchItem('Cœurs CPU / CPU Cores', '${_deviceInfo!.processorInfo.coreCount}', 'Performances / Matériel', Icons.numbers));
    items.add(_SearchItem('Fréquence Max / Max Freq', _deviceInfo!.processorInfo.maxFrequency > 0 ? '${_deviceInfo!.processorInfo.maxFrequency} MHz' : 'N/A', 'Performances / Matériel', Icons.speed));
    items.add(_SearchItem('Fonctionnalités CPU / Features', _deviceInfo!.processorInfo.features.join(', '), 'Performances / Matériel', Icons.featured_play_list_outlined));

    // RAM & Storage
    final mem = _deviceInfo!.memoryInfo;
    items.add(_SearchItem('RAM totale (Commerciale) / Total RAM', '${mem.totalPhysicalMemoryGB} Go', 'Performances / Matériel', Icons.align_horizontal_left));
    items.add(_SearchItem('RAM réelle système / Real RAM', '${(mem.realPhysicalMemory / (1024 * 1024 * 1024)).toStringAsFixed(2)} Go', 'Performances / Matériel', Icons.account_tree_outlined));
    items.add(_SearchItem('RAM disponible / Available RAM', '${(mem.availablePhysicalMemory / (1024 * 1024 * 1024)).toStringAsFixed(2)} Go', 'Performances / Matériel', Icons.storage));
    items.add(_SearchItem('Utilisation RAM / Memory Usage', '${mem.memoryUsagePercentage.toStringAsFixed(1)} %', 'Performances / Matériel', Icons.pie_chart));
    items.add(_SearchItem('Stockage total / Total Storage', '${mem.totalStorageSpaceGB} Go', 'Performances / Matériel', Icons.sd_card));
    items.add(_SearchItem('Stockage disponible / Free Storage', '${mem.availableStorageSpaceGB} Go', 'Performances / Matériel', Icons.sd_card_outlined));

    // Screen
    final scr = _deviceInfo!.displayInfo;
    items.add(_SearchItem('Résolution écran / Resolution', '${scr.screenWidth} x ${scr.screenHeight} px', 'Écran / Général', Icons.screenshot));
    items.add(_SearchItem('Taille écran / Screen Size', '${scr.screenSizeInches.toStringAsFixed(1)} pouces', 'Écran / Général', Icons.photo_size_select_actual_outlined));
    items.add(_SearchItem('Taux de rafraîchissement / Refresh Rate', '${scr.refreshRate} Hz', 'Écran / Général', Icons.refresh));
    items.add(_SearchItem('Densité écran / Density', '${scr.pixelDensity}x', 'Écran / Général', Icons.blur_on));
    items.add(_SearchItem('Support HDR / HDR', scr.isHdr ? 'Oui' : 'Non', 'Écran / Général', Icons.hdr_on));
    items.add(_SearchItem('Orientation / Orientation', scr.orientation, 'Écran / Général', Icons.screen_rotation));

    // Security
    final sec = _deviceInfo!.securityInfo;
    items.add(_SearchItem('Verrouillage actif / Screen Lock', sec.isDeviceSecure ? 'Oui' : 'Non', 'Sécurité / Général', Icons.lock_open));
    items.add(_SearchItem('Empreinte digitale / Fingerprint', sec.hasFingerprint ? 'Oui' : 'Non', 'Sécurité / Général', Icons.fingerprint));
    items.add(_SearchItem('Reconnaissance faciale / Face Unlock', sec.hasFaceUnlock ? 'Oui' : 'Non', 'Sécurité / Général', Icons.face));
    items.add(_SearchItem('Chiffrement stockage / Encryption', sec.encryptionStatus, 'Sécurité / Général', Icons.enhanced_encryption));

    // Battery
    if (_batteryInfo != null) {
      items.add(_SearchItem('Niveau batterie / Battery Level', '${_batteryInfo!.batteryLevel} %', 'Statut / Système', Icons.battery_charging_full));
      items.add(_SearchItem('Statut de charge / Charging Status', _batteryInfo!.chargingStatus, 'Statut / Système', Icons.power));
      items.add(_SearchItem('Santé batterie / Battery Health', _batteryInfo!.batteryHealth, 'Statut / Système', Icons.healing));
      items.add(_SearchItem('Température batterie / Temperature', '${_batteryInfo!.batteryTemperature} °C', 'Statut / Système', Icons.thermostat));
      items.add(_SearchItem('Tension batterie / Voltage', '${_batteryInfo!.batteryVoltage} V', 'Statut / Système', Icons.bolt));
      items.add(_SearchItem('Capacité batterie / Capacity', _batteryInfo!.batteryCapacity > 0 ? '${_batteryInfo!.batteryCapacity} mAh' : 'N/A', 'Statut / Système', Icons.battery_saver));
    }

    // Network
    if (_networkInfo != null) {
      items.add(_SearchItem('Type de connexion / Network Type', _networkInfo!.connectionType, 'Statut / Système', Icons.settings_input_antenna));
      items.add(_SearchItem('Connecté à internet / Connected', _networkInfo!.isConnected ? 'Oui' : 'Non', 'Statut / Système', Icons.wifi));
      items.add(_SearchItem('Vitesse estimée / Speed', _networkInfo!.networkSpeed, 'Statut / Système', Icons.speed_outlined));
      items.add(_SearchItem('Adresse IP locale / IP Address', _networkInfo!.ipAddress, 'Statut / Système', Icons.language));
      items.add(_SearchItem('Adresse MAC / MAC Address', _networkInfo!.macAddress, 'Statut / Système', Icons.vibration));
    }

    // Filter list by query
    if (_searchQuery.trim().isNotEmpty) {
      final query = _searchQuery.toLowerCase();
      return items.where((item) =>
          item.label.toLowerCase().contains(query) ||
          item.value.toLowerCase().contains(query) ||
          item.category.toLowerCase().contains(query)).toList();
    }

    return items;
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Scaffold(
      backgroundColor: theme.scaffoldBackgroundColor,
      appBar: AppBar(
        title: _isSearching
            ? TextField(
                controller: _searchController,
                autofocus: true,
                style: const TextStyle(color: Colors.white, fontSize: 18),
                decoration: const InputDecoration(
                  hintText: 'Rechercher un paramètre...',
                  hintStyle: TextStyle(color: Colors.white54),
                  border: InputBorder.none,
                ),
                onChanged: (val) {
                  setState(() {
                    _searchQuery = val;
                  });
                },
              )
            : const Text('PHONE CAPACITY'),
        actions: [
          IconButton(
            icon: Icon(_isSearching ? Icons.close : Icons.search),
            onPressed: () {
              setState(() {
                _isSearching = !_isSearching;
                if (!_isSearching) {
                  _searchController.clear();
                  _searchQuery = '';
                }
              });
            },
          ),
          if (!_isSearching) ...[
            IconButton(
              icon: const Icon(Icons.copy_all),
              tooltip: 'Copier JSON brut',
              onPressed: () {
                final json = _generateRawJson();
                _copyToClipboard(json, 'JSON brut copié avec succès !');
              },
            ),
            IconButton(
              icon: const Icon(Icons.refresh),
              tooltip: 'Actualiser',
              onPressed: _loadAll,
            ),
          ]
        ],
        bottom: !_isSearching && !_isLoading && _error == null
            ? TabBar(
                controller: _tabController,
                indicatorColor: theme.colorScheme.primary,
                indicatorWeight: 3,
                labelColor: Colors.white,
                unselectedLabelColor: Colors.white54,
                tabs: const [
                  Tab(icon: Icon(Icons.phone_android), text: 'Général'),
                  Tab(icon: Icon(Icons.memory), text: 'Matériel'),
                  Tab(icon: Icon(Icons.health_and_safety), text: 'Statut'),
                ],
              )
            : null,
      ),
      body: RefreshIndicator(
        onRefresh: _loadAll,
        color: theme.colorScheme.primary,
        child: _isLoading
            ? const Center(child: CircularProgressIndicator())
            : _error != null
                ? Center(
                    child: Padding(
                      padding: const EdgeInsets.all(24),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          Icon(Icons.error_outline, size: 64, color: theme.colorScheme.error),
                          const SizedBox(height: 16),
                          Text('Une erreur est survenue', style: theme.textTheme.titleMedium),
                          const SizedBox(height: 8),
                          Text(_error!, style: theme.textTheme.bodySmall, textAlign: TextAlign.center),
                          const SizedBox(height: 24),
                          ElevatedButton.icon(
                            onPressed: _loadAll,
                            icon: const Icon(Icons.refresh),
                            label: const Text('Réessayer'),
                          )
                        ],
                      ),
                    ),
                  )
                : AnimatedSwitcher(
                    duration: const Duration(milliseconds: 300),
                    child: _isSearching
                        ? _buildSearchResults()
                        : TabBarView(
                            controller: _tabController,
                            children: [
                              _buildGeneralTab(),
                              _buildHardwareTab(),
                              _buildStatusTab(),
                            ],
                          ),
                  ),
      ),
    );
  }

  // --- SEARCH RESULTS VIEW ---
  Widget _buildSearchResults() {
    final searchResults = _getFlatSearchItems();

    if (searchResults.isEmpty) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.search_off, size: 64, color: Colors.grey),
            const SizedBox(height: 16),
            Text(
              'Aucun résultat pour "$_searchQuery"',
              style: const TextStyle(fontSize: 16, color: Colors.grey),
            ),
          ],
        ),
      );
    }

    return ListView.builder(
      padding: const EdgeInsets.all(16),
      itemCount: searchResults.length,
      itemBuilder: (context, index) {
        final item = searchResults[index];
        return Card(
          child: ListTile(
            leading: Icon(item.icon, color: Theme.of(context).colorScheme.primary),
            title: Text(item.label, style: const TextStyle(fontWeight: FontWeight.bold)),
            subtitle: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(item.value, style: const TextStyle(color: Colors.white, fontSize: 16)),
                const SizedBox(height: 4),
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
                  decoration: BoxDecoration(
                    color: Colors.white10,
                    borderRadius: BorderRadius.circular(4),
                  ),
                  child: Text(
                    item.category.toUpperCase(),
                    style: const TextStyle(fontSize: 9, color: Colors.grey, fontWeight: FontWeight.bold),
                  ),
                ),
              ],
            ),
            trailing: IconButton(
              icon: const Icon(Icons.copy, size: 20),
              onPressed: () => _copyToClipboard(item.value, '${item.label} copié !'),
            ),
          ),
        );
      },
    );
  }

  // --- TAB 1: GENERAL ---
  Widget _buildGeneralTab() {
    final info = _deviceInfo!;
    final scr = info.displayInfo;
    final sec = info.securityInfo;

    return ListView(
      physics: const AlwaysScrollableScrollPhysics(),
      padding: const EdgeInsets.all(16),
      children: [
        _buildHeroDeviceCard(info),
        const SizedBox(height: 8),
        _buildSectionCard(
          title: 'Identité Appareil',
          icon: Icons.assignment_ind,
          children: [
            _buildDetailRow('Modèle', info.model, Icons.phone_android),
            _buildDetailRow('Fabricant', info.manufacturer, Icons.business),
            _buildDetailRow('Marque', info.brand, Icons.label_important_outline),
            _buildDetailRow('Android', '${info.systemVersion} (Build ${info.buildNumber})', Icons.android),
            _buildDetailRow('Noyau Kernel', info.kernelVersion, Icons.settings_ethernet),
            _buildDetailRow('ID Unique', info.deviceId, Icons.fingerprint, obscure: true),
          ],
        ),
        _buildSectionCard(
          title: 'Affichage & Écran',
          icon: Icons.desktop_windows,
          children: [
            _buildDetailRow('Résolution', '${scr.screenWidth} x ${scr.screenHeight} px', Icons.screenshot),
            _buildDetailRow('Taille diagonale', '${scr.screenSizeInches.toStringAsFixed(1)} pouces', Icons.photo_size_select_actual_outlined),
            _buildDetailRow('Taux de rafraîchissement', '${scr.refreshRate} Hz', Icons.refresh),
            _buildDetailRow('Densité de pixels', '${scr.pixelDensity}x (x160 DPI)', Icons.blur_on),
            _buildDetailRow('Support HDR', scr.isHdr ? 'Disponible' : 'Non supporté', Icons.hdr_on, isBoolean: true, boolValue: scr.isHdr),
            _buildDetailRow('Orientation actuelle', scr.orientation.toUpperCase(), Icons.screen_rotation),
          ],
        ),
        _buildSectionCard(
          title: 'Sécurité & Chiffrement',
          icon: Icons.shield,
          children: [
            _buildDetailRow('Verrouillage écran actif', sec.isDeviceSecure ? 'Sécurisé' : 'Non configuré', Icons.lock, isBoolean: true, boolValue: sec.isDeviceSecure),
            _buildDetailRow('Lecteur d\'empreinte', sec.hasFingerprint ? 'Présent' : 'Absent', Icons.fingerprint, isBoolean: true, boolValue: sec.hasFingerprint),
            _buildDetailRow('Reconnaissance faciale', sec.hasFaceUnlock ? 'Présente' : 'Non détectée', Icons.face, isBoolean: true, boolValue: sec.hasFaceUnlock),
            _buildDetailRow('Chiffrement stockage', sec.encryptionStatus.toUpperCase(), Icons.security, statusColor: sec.encryptionStatus == 'encrypted' ? _emerald : Colors.amber),
          ],
        ),
      ],
    );
  }

  // --- TAB 2: HARDWARE ---
  Widget _buildHardwareTab() {
    final info = _deviceInfo!;
    final cpu = info.processorInfo;
    final mem = info.memoryInfo;

    return ListView(
      physics: const AlwaysScrollableScrollPhysics(),
      padding: const EdgeInsets.all(16),
      children: [
        _buildMemoryUsageCard(mem),
        _buildStorageUsageCard(mem),
        _buildSectionCard(
          title: 'Spécifications Processeur (CPU)',
          icon: Icons.developer_board,
          children: [
            _buildDetailRow('Processeur', cpu.processorName, Icons.memory),
            _buildDetailRow('Architecture', cpu.architecture, Icons.architecture),
            _buildDetailRow('Nombre de cœurs', '${cpu.coreCount}', Icons.tag),
            _buildDetailRow(
              'Fréquence Max',
              cpu.maxFrequency > 0 ? '${cpu.maxFrequency} MHz' : 'Non accessible',
              Icons.speed,
            ),
            const SizedBox(height: 12),
            const Padding(
              padding: EdgeInsets.symmetric(horizontal: 16),
              child: Text(
                'Instructions CPU supportées :',
                style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white70),
              ),
            ),
            const SizedBox(height: 6),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 16),
              child: Wrap(
                spacing: 6,
                runSpacing: 4,
                children: cpu.features.map((feature) {
                  return Chip(
                    label: Text(feature, style: const TextStyle(fontSize: 10, fontWeight: FontWeight.bold)),
                    backgroundColor: Colors.indigo.withAlpha(38),
                    side: const BorderSide(color: Colors.indigo),
                    padding: EdgeInsets.zero,
                    visualDensity: VisualDensity.compact,
                  );
                }).toList(),
              ),
            ),
            const SizedBox(height: 12),
          ],
        ),
      ],
    );
  }

  // --- TAB 3: STATUS ---
  Widget _buildStatusTab() {
    return ListView(
      physics: const AlwaysScrollableScrollPhysics(),
      padding: const EdgeInsets.all(16),
      children: [
        if (_batteryInfo != null) _buildBatteryCard(_batteryInfo!),
        if (_networkInfo != null)
          _buildSectionCard(
            title: 'État Réseau & Connexion',
            icon: Icons.wifi,
            children: [
              _buildDetailRow('Type de connexion', _networkInfo!.connectionType.toUpperCase(), Icons.network_check),
              _buildDetailRow('Connecté à Internet', _networkInfo!.isConnected ? 'Oui' : 'Non', Icons.signal_cellular_alt, isBoolean: true, boolValue: _networkInfo!.isConnected),
              _buildDetailRow('Vitesse estimée', _networkInfo!.networkSpeed, Icons.speed),
              _buildDetailRow('Adresse IP locale', _networkInfo!.ipAddress, Icons.language),
              _buildDetailRow('Adresse MAC', _networkInfo!.macAddress, Icons.vibration),
            ],
          ),
        if (_sensorInfo != null)
          _buildSectionCard(
            title: 'Capteurs Matériels',
            icon: Icons.sensors,
            children: [
              const Padding(
                padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                child: Text(
                  'Liste des capteurs détectés sur l\'appareil :',
                  style: TextStyle(fontSize: 12, color: Colors.white70),
                ),
              ),
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                child: Wrap(
                  spacing: 8,
                  runSpacing: 8,
                  children: [
                    _buildSensorChip('Accéléromètre', _sensorInfo!.has('accelerometer')),
                    _buildSensorChip('Gyroscope', _sensorInfo!.has('gyroscope')),
                    _buildSensorChip('Magnétomètre', _sensorInfo!.has('magnetometer')),
                    _buildSensorChip('Capteur de proximité', _sensorInfo!.has('proximity')),
                    _buildSensorChip('Luminosité', _sensorInfo!.has('light')),
                    _buildSensorChip('Baromètre', _sensorInfo!.has('barometer')),
                    _buildSensorChip('Température', _sensorInfo!.has('temperature')),
                    _buildSensorChip('Humidité', _sensorInfo!.has('humidity')),
                    _buildSensorChip('Compteur pas', _sensorInfo!.has('stepCounter')),
                    _buildSensorChip('Cardiofréquencemètre', _sensorInfo!.has('heartRate')),
                    _buildSensorChip('Gravité', _sensorInfo!.has('gravity')),
                  ],
                ),
              ),
              const SizedBox(height: 12),
            ],
          ),
      ],
    );
  }

  // --- CUSTOM STYLISH COMPONENTS ---

  Widget _buildHeroDeviceCard(DeviceInfo info) {
    final theme = Theme.of(context);
    return Card(
      child: Container(
        decoration: BoxDecoration(
          borderRadius: const BorderRadius.all(Radius.circular(16)),
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              theme.colorScheme.primary,
              theme.colorScheme.primary.withAlpha(120),
            ],
          ),
        ),
        padding: const EdgeInsets.all(20),
        child: Row(
          children: [
            Container(
              padding: const EdgeInsets.all(12),
              decoration: const BoxDecoration(
                color: Colors.white12,
                shape: BoxShape.circle,
              ),
              child: const Icon(Icons.phone_iphone, size: 48, color: Colors.white),
            ),
            const SizedBox(width: 16),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    info.deviceName,
                    style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white),
                    overflow: TextOverflow.ellipsis,
                  ),
                  const SizedBox(height: 4),
                  Text(
                    '${info.manufacturer.toUpperCase()} ${info.model}',
                    style: const TextStyle(fontSize: 14, color: Colors.white70),
                  ),
                  const SizedBox(height: 8),
                  Container(
                    padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                    decoration: BoxDecoration(
                      color: Colors.black26,
                      borderRadius: BorderRadius.circular(20),
                    ),
                    child: Text(
                      'Android ${info.systemVersion}',
                      style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white),
                    ),
                  ),
                ],
              ),
            )
          ],
        ),
      ),
    );
  }

  Widget _buildSectionCard({
    required String title,
    required IconData icon,
    required List<Widget> children,
  }) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
              child: Row(
                children: [
                  Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary),
                  const SizedBox(width: 8),
                  Text(
                    title,
                    style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white),
                  ),
                ],
              ),
            ),
            const Divider(color: Colors.white12, height: 12),
            ...children,
          ],
        ),
      ),
    );
  }

  Widget _buildDetailRow(
    String label,
    String value,
    IconData icon, {
    bool obscure = false,
    bool isBoolean = false,
    bool? boolValue,
    Color? statusColor,
  }) {
    return InkWell(
      onTap: () => _copyToClipboard(value, '$label copié !'),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
        child: Row(
          children: [
            Icon(icon, size: 18, color: Colors.white38),
            const SizedBox(width: 12),
            Expanded(
              flex: 3,
              child: Text(label, style: const TextStyle(color: Colors.white70, fontSize: 14)),
            ),
            const SizedBox(width: 8),
            Expanded(
              flex: 4,
              child: Align(
                alignment: Alignment.centerRight,
                child: isBoolean
                    ? Container(
                        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
                        decoration: BoxDecoration(
                          color: (boolValue ?? false) ? _emerald.withAlpha(26) : Colors.red.withAlpha(26),
                          borderRadius: BorderRadius.circular(12),
                          border: Border.all(
                            color: (boolValue ?? false) ? _emerald : Colors.red,
                            width: 1,
                          ),
                        ),
                        child: Text(
                          value,
                          style: TextStyle(
                            color: (boolValue ?? false) ? _emerald : Colors.red,
                            fontSize: 12,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      )
                    : Text(
                        obscure ? '••••••••' : value,
                        style: TextStyle(
                          color: statusColor ?? Colors.white,
                          fontSize: 14,
                          fontWeight: FontWeight.w500,
                        ),
                        textAlign: TextAlign.end,
                        overflow: TextOverflow.ellipsis,
                        maxLines: 1,
                      ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildMemoryUsageCard(MemoryInfo mem) {
    final theme = Theme.of(context);
    final usagePct = mem.memoryUsagePercentage / 100.0;
    
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Row(
                  children: [
                    Icon(Icons.align_horizontal_left, color: theme.colorScheme.primary),
                    const SizedBox(width: 8),
                    const Text('Mémoire vive (RAM)', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
                  ],
                ),
                IconButton(
                  icon: const Icon(Icons.info_outline, size: 20),
                  onPressed: () => _showRamExplanationDialog(mem),
                ),
              ],
            ),
            const SizedBox(height: 8),
            Row(
              children: [
                Expanded(
                  child: ClipRRect(
                    borderRadius: BorderRadius.circular(8),
                    child: LinearProgressIndicator(
                      value: usagePct,
                      minHeight: 12,
                      backgroundColor: Colors.white10,
                      valueColor: AlwaysStoppedAnimation<Color>(
                        mem.memoryUsagePercentage > 85 ? Colors.red : theme.colorScheme.primary,
                      ),
                    ),
                  ),
                ),
                const SizedBox(width: 16),
                Text(
                  '${mem.memoryUsagePercentage.toStringAsFixed(1)}%',
                  style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
                )
              ],
            ),
            const SizedBox(height: 12),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                _buildMinicard(' RAM Totale', '${mem.totalPhysicalMemoryGB} Go', Colors.indigo),
                _buildMinicard(' RAM Libre', '${(mem.availablePhysicalMemory / (1024 * 1024 * 1024)).toStringAsFixed(2)} Go', _emerald),
              ],
            ),
            const SizedBox(height: 6),
            Center(
              child: Text(
                mem.isAdvertisedMemory
                    ? 'Affichage de la RAM marketing (Android 14+)'
                    : 'Affichage de la RAM système (Android < 14)',
                style: const TextStyle(fontSize: 10, color: Colors.white54, fontStyle: FontStyle.italic),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildStorageUsageCard(MemoryInfo mem) {
    final theme = Theme.of(context);
    final usagePct = mem.usedStorageSpace.toDouble() / mem.totalStorageSpace.toDouble();
    final usedGB = mem.totalStorageSpaceGB - mem.availableStorageSpaceGB;

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(Icons.sd_card, color: theme.colorScheme.secondary),
                const SizedBox(width: 8),
                const Text('Espace de stockage', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
              ],
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: ClipRRect(
                    borderRadius: BorderRadius.circular(8),
                    child: LinearProgressIndicator(
                      value: usagePct,
                      minHeight: 12,
                      backgroundColor: Colors.white10,
                      valueColor: AlwaysStoppedAnimation<Color>(
                        usagePct > 0.9 ? Colors.red : theme.colorScheme.secondary,
                      ),
                    ),
                  ),
                ),
                const SizedBox(width: 16),
                Text(
                  '${(usagePct * 100).toStringAsFixed(1)}%',
                  style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
                )
              ],
            ),
            const SizedBox(height: 12),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                _buildMinicard(' Total', '${mem.totalStorageSpaceGB} Go', Colors.teal),
                _buildMinicard(' Utilisé', '${usedGB.toStringAsFixed(1)} Go', Colors.deepOrange),
                _buildMinicard(' Libre', '${mem.availableStorageSpaceGB} Go', _emerald),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildMinicard(String label, String value, Color color) {
    return Expanded(
      child: Container(
        margin: const EdgeInsets.symmetric(horizontal: 4),
        padding: const EdgeInsets.all(8),
        decoration: BoxDecoration(
          color: Colors.white.withAlpha(10),
          borderRadius: BorderRadius.circular(8),
          border: Border(left: BorderSide(color: color, width: 3)),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(label, style: const TextStyle(fontSize: 10, color: Colors.white54)),
            const SizedBox(height: 2),
            Text(value, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold)),
          ],
        ),
      ),
    );
  }

  Widget _buildBatteryCard(BatteryInfo battery) {
    final theme = Theme.of(context);
    final levelPct = battery.batteryLevel / 100.0;
    
    // Choose battery icon based on charging status
    final isCharging = battery.chargingStatus == 'charging';
    final batteryIcon = isCharging ? Icons.battery_charging_full : Icons.battery_std;
    
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            Row(
              children: [
                Icon(batteryIcon, color: theme.colorScheme.secondary),
                const SizedBox(width: 8),
                const Text('État de la Batterie', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
              ],
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                // Battery Gauge visualization
                Container(
                  width: 80,
                  height: 80,
                  alignment: Alignment.center,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: Colors.white.withAlpha(8),
                    border: Border.all(color: Colors.white12, width: 2),
                  ),
                  child: Stack(
                    alignment: Alignment.center,
                    children: [
                      CircularProgressIndicator(
                        value: levelPct,
                        strokeWidth: 6,
                        backgroundColor: Colors.white10,
                        valueColor: AlwaysStoppedAnimation<Color>(
                          battery.batteryLevel > 20 ? theme.colorScheme.secondary : Colors.red,
                        ),
                      ),
                      Text(
                        '${battery.batteryLevel}%',
                        style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
                      ),
                    ],
                  ),
                ),
                const SizedBox(width: 20),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        isCharging ? 'En cours de charge' : 'Sur batterie',
                        style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        'Santé : ${battery.batteryHealth.toUpperCase()}',
                        style: TextStyle(
                          color: battery.batteryHealth == 'good' ? _emerald : Colors.orange,
                          fontWeight: FontWeight.bold,
                          fontSize: 12,
                        ),
                      ),
                      const SizedBox(height: 2),
                      Text('Température : ${battery.batteryTemperature} °C', style: const TextStyle(fontSize: 12, color: Colors.white70)),
                      Text('Tension : ${battery.batteryVoltage} V', style: const TextStyle(fontSize: 12, color: Colors.white70)),
                      if (battery.batteryCapacity > 0)
                        Text('Capacité : ${battery.batteryCapacity} mAh', style: const TextStyle(fontSize: 12, color: Colors.white70)),
                    ],
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSensorChip(String name, bool isAvailable) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
      decoration: BoxDecoration(
        color: isAvailable ? _emerald.withAlpha(26) : Colors.white.withAlpha(8),
        borderRadius: BorderRadius.circular(20),
        border: Border.all(
          color: isAvailable ? _emerald : Colors.white12,
          width: 1,
        ),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(
            isAvailable ? Icons.check_circle : Icons.cancel_outlined,
            size: 14,
            color: isAvailable ? _emerald : Colors.white38,
          ),
          const SizedBox(width: 6),
          Text(
            name,
            style: TextStyle(
              color: isAvailable ? Colors.white : Colors.white38,
              fontSize: 11,
              fontWeight: isAvailable ? FontWeight.bold : FontWeight.normal,
            ),
          ),
        ],
      ),
    );
  }

  void _showRamExplanationDialog(MemoryInfo mem) {
    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text('Comprendre la RAM commerciale'),
          content: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'Sur Android, la RAM disponible pour le système est toujours inférieure à la RAM annoncée par le constructeur.',
                style: TextStyle(fontSize: 14),
              ),
              const SizedBox(height: 12),
              _buildBulletPoint('RAM Commerciale', '${mem.totalPhysicalMemoryGB} Go (Arrondie, ex: 8.0 Go). Sur Android 14+, le système fournit cette valeur directement via advertisedMem.'),
              const SizedBox(height: 8),
              _buildBulletPoint('RAM Réelle Système', '${(mem.realPhysicalMemory / (1024 * 1024 * 1024)).toStringAsFixed(2)} Go (RAM physique après ponction du GPU, modem, etc.).'),
              const SizedBox(height: 8),
              _buildBulletPoint('RAM Libre instantanée', '${(mem.availablePhysicalMemory / (1024 * 1024 * 1024)).toStringAsFixed(2)} Go.'),
            ],
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('Fermer'),
            )
          ],
        );
      },
    );
  }

  Widget _buildBulletPoint(String title, String description) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('• ', style: TextStyle(fontWeight: FontWeight.bold)),
        Expanded(
          child: RichText(
            text: TextSpan(
              style: const TextStyle(fontSize: 13, color: Colors.white),
              children: [
                TextSpan(text: '$title : ', style: const TextStyle(fontWeight: FontWeight.bold)),
                TextSpan(text: description, style: const TextStyle(color: Colors.white70)),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

class _SearchItem {
  final String label;
  final String value;
  final String category;
  final IconData icon;

  _SearchItem(this.label, this.value, this.category, this.icon);
}
6
likes
140
points
117
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for deep Android hardware inspection, including advertised commercial RAM (Android 14+), CPU, battery, display, network, and sensors.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on flutter_device_specs

Packages that implement flutter_device_specs