import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:provider/provider.dart'; import 'package:toolbox/core/extension/order.dart'; import 'package:toolbox/data/model/server/cpu.dart'; import 'package:toolbox/data/model/server/disk.dart'; import 'package:toolbox/data/model/server/dist.dart'; import 'package:toolbox/data/model/server/memory.dart'; import 'package:toolbox/data/model/server/temp.dart'; import '../../../core/extension/numx.dart'; import '../../../data/model/server/net_speed.dart'; import '../../../data/model/server/server.dart'; import '../../../data/model/server/server_status.dart'; import '../../../data/provider/server.dart'; import '../../../data/res/color.dart'; import '../../../data/res/default.dart'; import '../../../data/res/ui.dart'; import '../../../data/store/setting.dart'; import '../../../locator.dart'; import '../../widget/round_rect_card.dart'; class ServerDetailPage extends StatefulWidget { const ServerDetailPage(this.id, {Key? key}) : super(key: key); final String id; @override _ServerDetailPageState createState() => _ServerDetailPageState(); } class _ServerDetailPageState extends State with SingleTickerProviderStateMixin { late MediaQueryData _media; late S _s; final StringOrder _cardsOrder = []; final _setting = locator(); @override void didChangeDependencies() { super.didChangeDependencies(); _media = MediaQuery.of(context); _s = S.of(context)!; } @override void initState() { super.initState(); _cardsOrder.addAll(_setting.detailCardOrder.fetch()!); } @override Widget build(BuildContext context) { return Consumer(builder: (_, provider, __) { final s = provider.servers[widget.id]; if (s == null) { return Scaffold( body: Center( child: Text(_s.noClient), ), ); } return _buildMainPage(s); }); } Widget _buildMainPage(Server si) { return Scaffold( appBar: AppBar( title: Text(si.spi.name, style: textSize18), ), body: ReorderableListView( padding: EdgeInsets.only( left: 13, right: 13, top: 13, bottom: _media.padding.bottom), onReorder: (int oldIndex, int newIndex) { setState(() { _cardsOrder.move(oldIndex, newIndex, _setting.detailCardOrder); }); }, header: _buildLinuxIcon(si.status.sysVer), footer: height13, children: _buildMainList(si.status), ), ); } List _buildMainList(ServerStatus ss) { final map = Map.fromIterables( defaultDetailCardOrder, [ _buildUpTimeAndSys(ss.sysVer, ss.uptime), _buildCPUView(ss.cpu), _buildMemView(ss.mem), _buildSwapView(ss.swap), _buildDiskView(ss.disk), _buildNetView(ss.netSpeed), _buildTemperature(ss.temps), ], ); return _cardsOrder .map((e) => SizedBox(key: ValueKey(e), child: map[e])) .toList(); } Widget _buildLinuxIcon(String sysVer) { if (!_setting.showDistLogo.fetch()!) return placeholder; final iconPath = sysVer.dist?.iconPath; if (iconPath == null) return placeholder; return ConstrainedBox( constraints: BoxConstraints( maxHeight: _media.size.height * 0.13, maxWidth: _media.size.width * 0.6, ), child: Image.asset( iconPath, fit: BoxFit.contain, semanticLabel: _s.showDistLogo, ), ); } Widget _buildCPUView(Cpus cs) { return RoundRectCard( Padding( padding: roundRectCardPadding, child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '${cs.usedPercent(coreIdx: 0).toInt()}%', style: textSize27, textScaleFactor: 1.0, ), Row( children: [ _buildDetailPercent(cs.user, 'user'), width13, _buildDetailPercent(cs.sys, 'sys'), width13, _buildDetailPercent(cs.iowait, 'io'), width13, _buildDetailPercent(cs.idle, 'idle') ], ) ], ), height13, _buildCPUProgress(cs) ]), ), ); } Widget _buildDetailPercent(double percent, String timeType) { return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${percent.toStringAsFixed(1)}%', style: const TextStyle(fontSize: 13), textScaleFactor: 1.0, ), Text( timeType, style: const TextStyle(fontSize: 10, color: Colors.grey), textScaleFactor: 1.0, ), ], ); } Widget _buildCPUProgress(Cpus cs) { final children = []; for (var i = 0; i < cs.coresCount; i++) { if (i == 0) continue; children.add( Padding( padding: const EdgeInsets.all(2), child: _buildProgress(cs.usedPercent(coreIdx: i)), ), ); } return Column(children: children); } Widget _buildProgress(double percent) { if (percent > 100) percent = 100; final percentWithinOne = percent / 100; return LinearProgressIndicator( value: percentWithinOne, minHeight: 7, backgroundColor: progressColor.resolve(context), color: primaryColor, ); } Widget _buildUpTimeAndSys(String sysVer, String uptime) { return RoundRectCard( Padding( padding: roundRectCardPadding, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(sysVer, style: textSize11, textScaleFactor: 1.0), Text( uptime, style: textSize11, textScaleFactor: 1.0, ), ], ), ), ); } Widget _buildMemView(Memory mem) { final free = mem.free / mem.total * 100; final avail = mem.availPercent * 100; final used = mem.usedPercent * 100; return RoundRectCard( Padding( padding: roundRectCardPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Text('${used.toStringAsFixed(0)}%', style: textSize27), width7, Text('of ${(mem.total * 1024).convertBytes}', style: textSize13Grey) ], ), Row( children: [ _buildDetailPercent(free, 'free'), width13, _buildDetailPercent(avail, 'avail'), ], ), ], ), height13, _buildProgress(used) ], ), ), ); } Widget _buildSwapView(Swap swap) { if (swap.total == 0) return placeholder; final used = swap.usedPercent * 100; final cached = swap.cached / swap.total * 100; return RoundRectCard( Padding( padding: roundRectCardPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Text('${used.toStringAsFixed(0)}%', style: textSize27), width7, Text('of ${(swap.total * 1024).convertBytes} ', style: textSize13Grey) ], ), _buildDetailPercent(cached, 'cached'), ], ), height13, _buildProgress(used) ], ), ), ); } Widget _buildDiskView(List disk) { disk.removeWhere((e) { for (final ingorePath in _setting.diskIgnorePath.fetch()!) { if (e.path.startsWith(ingorePath)) return true; } return false; }); final children = disk .map((disk) => Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '${disk.usedPercent}% of ${disk.size}', style: textSize11, textScaleFactor: 1.0, ), Text(disk.path, style: textSize11, textScaleFactor: 1.0) ], ), _buildProgress(disk.usedPercent.toDouble()) ], ), )) .toList(); return RoundRectCard( Padding( padding: roundRectCardPadding, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: children, ), ), ); } Widget _buildNetView(NetSpeed ns) { final children = [ _buildNetSpeedTop(), const Divider( height: 7, ) ]; if (ns.devices.isEmpty) { children.add(Center( child: Text( _s.noInterface, style: const TextStyle(color: Colors.grey, fontSize: 13), ), )); } else { children.addAll(ns.devices.map((e) => _buildNetSpeedItem(ns, e))); } return RoundRectCard( Padding( padding: roundRectCardPadding, child: Column( children: children, ), ), ); } Widget _buildNetSpeedTop() { return const Padding( padding: EdgeInsets.only(bottom: 3), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon(Icons.device_hub, size: 17), Icon(Icons.arrow_downward, size: 17), Icon(Icons.arrow_upward, size: 17), ], ), ); } Widget _buildNetSpeedItem(NetSpeed ns, String device) { final width = (_media.size.width - 34 - 34) / 2.9; return Padding( padding: const EdgeInsets.symmetric(vertical: 3), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ SizedBox( width: width / 1.2, child: Text( device, style: textSize11, textScaleFactor: 1.0, ), ), SizedBox( width: width, child: Text( '${ns.speedIn(device: device)} | ${ns.totalIn(device: device)}', style: textSize11, textAlign: TextAlign.center, textScaleFactor: 0.87, ), ), SizedBox( width: width, child: Text( '${ns.speedOut(device: device)} | ${ns.totalOut(device: device)}', style: textSize11, textAlign: TextAlign.right, textScaleFactor: 0.87, ), ) ], ), ); } Widget _buildTemperature(Temperatures temps) { if (temps.isEmpty) { return placeholder; } final List children = [ const Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon(Icons.device_hub, size: 17), Icon(Icons.ac_unit, size: 17), ], ), const Padding( padding: EdgeInsets.symmetric(vertical: 3), child: Divider(height: 7), ), ]; children.addAll(temps.devices.map((key) => Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( key, style: textSize11, textScaleFactor: 1.0, ), Text( '${temps.get(key)}°C', style: textSize11, textScaleFactor: 1.0, ), ], ))); return RoundRectCard(Padding( padding: roundRectCardPadding, child: Column(children: children), )); } }