flutter_device_specs 0.1.0
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.
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);
}