From 4d2a9443101522004f6ed4f4fb048a1e03ad1d97 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Thu, 9 May 2024 17:08:37 +0800 Subject: [PATCH] feat: custom server logo --- lib/data/model/server/custom.dart | 21 ++++++++++-- lib/data/model/server/custom.g.dart | 7 ++-- lib/data/model/server/server.dart | 12 +++---- lib/data/model/server/server.ext.dart | 14 ++++---- lib/data/provider/server.dart | 48 +++++++++++++-------------- lib/view/page/server/detail/view.dart | 31 +++++++++++++++-- lib/view/page/server/edit.dart | 14 ++++++++ lib/view/page/server/tab.dart | 24 +++++++------- pubspec.lock | 24 ++++++++++++++ pubspec.yaml | 1 + 10 files changed, 140 insertions(+), 56 deletions(-) diff --git a/lib/data/model/server/custom.dart b/lib/data/model/server/custom.dart index 69f04fa4..50b34044 100644 --- a/lib/data/model/server/custom.dart +++ b/lib/data/model/server/custom.dart @@ -16,6 +16,8 @@ final class ServerCustom { final Map? cmds; @HiveField(4) final String? preferTempDev; + @HiveField(5) + final String? logoUrl; const ServerCustom({ //this.temperature, @@ -23,6 +25,7 @@ final class ServerCustom { this.pveIgnoreCert = false, this.cmds, this.preferTempDev, + this.logoUrl, }); static ServerCustom fromJson(Map json) { @@ -31,12 +34,14 @@ final class ServerCustom { final pveIgnoreCert = json["pveIgnoreCert"] as bool; final cmds = json["cmds"] as Map?; final preferTempDev = json["preferTempDev"] as String?; + final logoUrl = json["logoUrl"] as String?; return ServerCustom( //temperature: temperature, pveAddr: pveAddr, pveIgnoreCert: pveIgnoreCert, cmds: cmds?.cast(), preferTempDev: preferTempDev, + logoUrl: logoUrl, ); } @@ -56,6 +61,9 @@ final class ServerCustom { if (preferTempDev != null) { json["preferTempDev"] = preferTempDev; } + if (logoUrl != null) { + json["logoUrl"] = logoUrl; + } return json; } @@ -71,9 +79,16 @@ final class ServerCustom { other.pveAddr == pveAddr && other.pveIgnoreCert == pveIgnoreCert && other.cmds == cmds && - other.preferTempDev == preferTempDev; + other.preferTempDev == preferTempDev && + other.logoUrl == logoUrl; } - + @override - int get hashCode => toString().hashCode; + int get hashCode => + //temperature.hashCode ^ + pveAddr.hashCode ^ + pveIgnoreCert.hashCode ^ + cmds.hashCode ^ + preferTempDev.hashCode ^ + logoUrl.hashCode; } diff --git a/lib/data/model/server/custom.g.dart b/lib/data/model/server/custom.g.dart index d74ba30c..a8fe4cd7 100644 --- a/lib/data/model/server/custom.g.dart +++ b/lib/data/model/server/custom.g.dart @@ -21,13 +21,14 @@ class ServerCustomAdapter extends TypeAdapter { pveIgnoreCert: fields[2] == null ? false : fields[2] as bool, cmds: (fields[3] as Map?)?.cast(), preferTempDev: fields[4] as String?, + logoUrl: fields[5] as String?, ); } @override void write(BinaryWriter writer, ServerCustom obj) { writer - ..writeByte(4) + ..writeByte(5) ..writeByte(1) ..write(obj.pveAddr) ..writeByte(2) @@ -35,7 +36,9 @@ class ServerCustomAdapter extends TypeAdapter { ..writeByte(3) ..write(obj.cmds) ..writeByte(4) - ..write(obj.preferTempDev); + ..write(obj.preferTempDev) + ..writeByte(5) + ..write(obj.logoUrl); } @override diff --git a/lib/data/model/server/server.dart b/lib/data/model/server/server.dart index b96959ee..46b0c623 100644 --- a/lib/data/model/server/server.dart +++ b/lib/data/model/server/server.dart @@ -22,12 +22,12 @@ class Server implements TagPickable { ServerPrivateInfo spi; ServerStatus status; SSHClient? client; - ServerState state; + ServerConn conn; Server( this.spi, this.status, - this.state, { + this.conn, { this.client, }); @@ -39,9 +39,9 @@ class Server implements TagPickable { @override String get tagName => spi.id; - bool get needGenClient => state < ServerState.connecting; + bool get needGenClient => conn < ServerConn.connecting; - bool get canViewDetails => state == ServerState.finished; + bool get canViewDetails => conn == ServerConn.finished; String get id => spi.id; } @@ -80,7 +80,7 @@ class ServerStatus { }); } -enum ServerState { +enum ServerConn { failed, disconnected, connecting, @@ -94,5 +94,5 @@ enum ServerState { /// Status parsing finished finished; - operator <(ServerState other) => index < other.index; + operator <(ServerConn other) => index < other.index; } diff --git a/lib/data/model/server/server.ext.dart b/lib/data/model/server/server.ext.dart index 11b06164..1363ef58 100644 --- a/lib/data/model/server/server.ext.dart +++ b/lib/data/model/server/server.ext.dart @@ -2,10 +2,10 @@ part of 'server.dart'; extension ServerX on Server { String getTopRightStr(ServerPrivateInfo spi) { - switch (state) { - case ServerState.disconnected: + switch (conn) { + case ServerConn.disconnected: return l10n.disconnected; - case ServerState.finished: + case ServerConn.finished: // Highest priority of temperature display final cmdTemp = () { final val = status.customCmds['server_card_top_right']; @@ -46,13 +46,13 @@ extension ServerX on Server { final str = items.where((e) => e != null && e.isNotEmpty).join(' | '); if (str.isEmpty) return l10n.noResult; return str; - case ServerState.loading: + case ServerConn.loading: return l10n.serverTabLoading; - case ServerState.connected: + case ServerConn.connected: return l10n.connected; - case ServerState.connecting: + case ServerConn.connecting: return l10n.serverTabConnecting; - case ServerState.failed: + case ServerConn.failed: return status.err ?? l10n.serverTabFailed; } } diff --git a/lib/data/provider/server.dart b/lib/data/provider/server.dart index 11eccac0..1fdaca36 100644 --- a/lib/data/provider/server.dart +++ b/lib/data/provider/server.dart @@ -52,7 +52,7 @@ class ServerProvider extends ChangeNotifier { /// Issues #258 /// If not [shouldReconnect], then keep the old state. if (originServer != null && !originServer.spi.shouldReconnect(spi)) { - newServer.state = originServer.state; + newServer.conn = originServer.conn; } _servers[spi.id] = newServer; } @@ -115,7 +115,7 @@ class ServerProvider extends ChangeNotifier { } Server genServer(ServerPrivateInfo spi) { - return Server(spi, InitStatus.status, ServerState.disconnected); + return Server(spi, InitStatus.status, ServerConn.disconnected); } /// if [spi] is specificed then only refresh this server @@ -134,7 +134,7 @@ class ServerProvider extends ChangeNotifier { Future _connectFn(Server s, bool onlyFailed) async { if (onlyFailed) { - if (s.state != ServerState.failed) return; + if (s.conn != ServerConn.failed) return; TryLimiter.reset(s.spi.id); } @@ -144,7 +144,7 @@ class ServerProvider extends ChangeNotifier { /// If no this, the server will only refresh once by clicking refresh button. /// /// If [spi.autoConnect] is true, then refresh. - if (!(s.spi.autoConnect ?? true) && s.state == ServerState.disconnected) { + if (!(s.spi.autoConnect ?? true) && s.conn == ServerConn.disconnected) { return; } return await _getData(s.spi); @@ -174,7 +174,7 @@ class ServerProvider extends ChangeNotifier { void setDisconnected() { for (final s in _servers.values) { - s.state = ServerState.disconnected; + s.conn = ServerConn.disconnected; } //TryLimiter.clear(); notifyListeners(); @@ -251,8 +251,8 @@ class ServerProvider extends ChangeNotifier { } } - void _setServerState(Server s, ServerState ss) { - s.state = ss; + void _setServerState(Server s, ServerConn ss) { + s.conn = ss; notifyListeners(); } @@ -263,8 +263,8 @@ class ServerProvider extends ChangeNotifier { if (s == null) return; if (!TryLimiter.canTry(sid)) { - if (s.state != ServerState.failed) { - _setServerState(s, ServerState.failed); + if (s.conn != ServerConn.failed) { + _setServerState(s, ServerConn.failed); } return; } @@ -272,7 +272,7 @@ class ServerProvider extends ChangeNotifier { s.status.err = null; if (s.needGenClient || (s.client?.isClosed ?? true)) { - _setServerState(s, ServerState.connecting); + _setServerState(s, ServerConn.connecting); try { final time1 = DateTime.now(); @@ -291,14 +291,14 @@ class ServerProvider extends ChangeNotifier { } catch (e) { TryLimiter.inc(sid); s.status.err = e.toString(); - _setServerState(s, ServerState.failed); + _setServerState(s, ServerConn.failed); /// In order to keep privacy, print [spi.name] instead of [spi.id] Loggers.app.warning('Connect to ${spi.name} failed', e); return; } - _setServerState(s, ServerState.connected); + _setServerState(s, ServerConn.connected); // Write script to server // by ssh @@ -314,12 +314,12 @@ class ServerProvider extends ChangeNotifier { } on SSHAuthAbortError catch (e) { TryLimiter.inc(sid); s.status.err = e.toString(); - _setServerState(s, ServerState.failed); + _setServerState(s, ServerConn.failed); return; } on SSHAuthFailError catch (e) { TryLimiter.inc(sid); s.status.err = e.toString(); - _setServerState(s, ServerState.failed); + _setServerState(s, ServerConn.failed); return; } catch (e) { Loggers.app.warning('Write script to ${spi.name} by shell', e); @@ -347,7 +347,7 @@ class ServerProvider extends ChangeNotifier { } catch (e) { TryLimiter.inc(sid); s.status.err = e.toString(); - _setServerState(s, ServerState.failed); + _setServerState(s, ServerConn.failed); Loggers.app.warning('Write script to ${spi.name} by sftp', e); return; } finally { @@ -356,13 +356,13 @@ class ServerProvider extends ChangeNotifier { } } - if (s.state == ServerState.connecting) return; + if (s.conn == ServerConn.connecting) return; /// Keep [finished] state, or the UI will be refreshed to [loading] state /// instead of the '$Temp | $Uptime'. /// eg: '32C | 7 days' - if (s.state != ServerState.finished) { - _setServerState(s, ServerState.loading); + if (s.conn != ServerConn.finished) { + _setServerState(s, ServerConn.loading); } List? segments; @@ -374,19 +374,19 @@ class ServerProvider extends ChangeNotifier { if (raw == null || raw.isEmpty || segments == null || segments.isEmpty) { if (Stores.setting.keepStatusWhenErr.fetch()) { // Keep previous server status when err occurs - if (s.state != ServerState.failed && s.status.more.isNotEmpty) { + if (s.conn != ServerConn.failed && s.status.more.isNotEmpty) { return; } } TryLimiter.inc(sid); s.status.err = 'Seperate segments failed, raw:\n$raw'; - _setServerState(s, ServerState.failed); + _setServerState(s, ServerConn.failed); return; } } catch (e) { TryLimiter.inc(sid); s.status.err = e.toString(); - _setServerState(s, ServerState.failed); + _setServerState(s, ServerConn.failed); Loggers.app.warning('Get status from ${spi.name} failed', e); return; } @@ -399,7 +399,7 @@ class ServerProvider extends ChangeNotifier { final actual = segments.length; final err = 'Segments: expect $expected, got $actual, raw:\n\n$raw'; s.status.err = err; - _setServerState(s, ServerState.failed); + _setServerState(s, ServerConn.failed); return; } s.status.system = systemType; @@ -419,13 +419,13 @@ class ServerProvider extends ChangeNotifier { } catch (e, trace) { TryLimiter.inc(sid); s.status.err = 'Parse failed: $e\n\n$raw'; - _setServerState(s, ServerState.failed); + _setServerState(s, ServerConn.failed); Loggers.parse.warning('Server status', e, trace); return; } /// Call this every time for setting [Server.isBusy] to false - _setServerState(s, ServerState.finished); + _setServerState(s, ServerConn.finished); // reset try times only after prepared successfully TryLimiter.reset(sid); } diff --git a/lib/view/page/server/detail/view.dart b/lib/view/page/server/detail/view.dart index c49dd1d6..5e9f6cb5 100644 --- a/lib/view/page/server/detail/view.dart +++ b/lib/view/page/server/detail/view.dart @@ -1,3 +1,4 @@ +import 'package:extended_image/extended_image.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:icons_plus/icons_plus.dart'; @@ -11,6 +12,7 @@ import 'package:toolbox/data/model/app/shell_func.dart'; import 'package:toolbox/data/model/server/battery.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/net_speed.dart'; import 'package:toolbox/data/model/server/nvdia.dart'; import 'package:toolbox/data/model/server/sensors.dart'; @@ -100,6 +102,13 @@ class _ServerDetailPageState extends State Widget _buildMainPage(Server si) { final buildFuncs = !Stores.setting.moveOutServerTabFuncBtns.fetch(); + final logoUrl = si.spi.custom?.logoUrl; + final listLen = () { + var len = _cardsOrder.length; + if (buildFuncs) len++; + if (logoUrl != null) len++; + return len; + }(); return Scaffold( appBar: CustomAppBar( title: Text(si.spi.name, style: UIs.text18), @@ -121,11 +130,15 @@ class _ServerDetailPageState extends State right: 13, bottom: _media.padding.bottom + 77, ), - itemCount: buildFuncs ? _cardsOrder.length + 1 : _cardsOrder.length, + itemCount: listLen, itemBuilder: (context, index) { - if (index == 0 && buildFuncs) { + if (index == 0 && logoUrl != null) { + return _buildLogo(logoUrl, si.status.more[StatusCmdType.sys]?.dist); + } + if (index == 1 && buildFuncs) { return ServerFuncBtns(spi: widget.spi); } + if (logoUrl != null) index--; if (buildFuncs) index--; return _cardBuildMap[_cardsOrder[index]]?.call(si.status); }, @@ -133,6 +146,20 @@ class _ServerDetailPageState extends State ); } + Widget _buildLogo(String logoUrl, Dist? dist) { + if (dist != null) { + logoUrl = logoUrl.replaceFirst('{DIST}', dist.name); + } + return Padding( + padding: const EdgeInsets.symmetric(vertical: 13), + child: ExtendedImage.network( + logoUrl, + cache: true, + height: _media.size.height * 0.2, + ), + ); + } + Widget _buildAbout(ServerStatus ss) { return CardX( child: ExpandTile( diff --git a/lib/view/page/server/edit.dart b/lib/view/page/server/edit.dart index b5ad649e..2ee3d262 100644 --- a/lib/view/page/server/edit.dart +++ b/lib/view/page/server/edit.dart @@ -43,6 +43,7 @@ class _ServerEditPageState extends State { final _pveAddrCtrl = TextEditingController(); final _customCmdCtrl = TextEditingController(); final _preferTempDevCtrl = TextEditingController(); + final _logoUrlCtrl = TextEditingController(); final _nameFocus = FocusNode(); final _ipFocus = FocusNode(); @@ -96,6 +97,7 @@ class _ServerEditPageState extends State { } } catch (_) {} _preferTempDevCtrl.text = custom.preferTempDev ?? ''; + _logoUrlCtrl.text = custom.logoUrl ?? ''; } } } @@ -117,6 +119,7 @@ class _ServerEditPageState extends State { _pveAddrCtrl.dispose(); _customCmdCtrl.dispose(); _preferTempDevCtrl.dispose(); + _logoUrlCtrl.dispose(); } @override @@ -367,6 +370,16 @@ class _ServerEditPageState extends State { return ExpandTile( title: Text(l10n.more), children: [ + const Text('Logo', style: UIs.text13Grey), + UIs.height7, + Input( + controller: _logoUrlCtrl, + type: TextInputType.url, + icon: Icons.image, + label: 'Url', + hint: 'https://example.com/logo.png', + ), + UIs.height7, const Text('PVE', style: UIs.text13Grey), UIs.height7, Input( @@ -528,6 +541,7 @@ class _ServerEditPageState extends State { pveIgnoreCert: _pveIgnoreCert.value, cmds: customCmds, preferTempDev: _preferTempDevCtrl.text.selfIfNotNullEmpty, + logoUrl: _logoUrlCtrl.text.selfIfNotNullEmpty, ); final spi = ServerPrivateInfo( diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index 57fa512c..6572e918 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -281,7 +281,7 @@ class _ServerPageState extends State } }, onLongPress: () { - if (srv.state == ServerState.finished) { + if (srv.conn == ServerConn.finished) { final id = srv.spi.id; final cardStatus = _getCardNoti(id); cardStatus.value = cardStatus.value.copyWith( @@ -319,7 +319,7 @@ class _ServerPageState extends State listenable: cardStatus, builder: (_, __) { final List children = [title]; - if (srv.state == ServerState.finished) { + if (srv.conn == ServerConn.finished) { if (cardStatus.value.flip) { children.addAll(_buildFlippedCard(srv)); } else { @@ -330,7 +330,7 @@ class _ServerPageState extends State return AnimatedContainer( duration: const Duration(milliseconds: 377), curve: Curves.fastEaseInToSlowEaseOut, - height: _calcCardHeight(srv.state, cardStatus.value.flip), + height: _calcCardHeight(srv.conn, cardStatus.value.flip), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, @@ -461,10 +461,10 @@ class _ServerPageState extends State } Widget _buildTopRightWidget(Server s) { - return switch (s.state) { - ServerState.connecting || - ServerState.loading || - ServerState.connected => + return switch (s.conn) { + ServerConn.connecting || + ServerConn.loading || + ServerConn.connected => Padding( padding: const EdgeInsets.symmetric(horizontal: 7), child: SizedBox( @@ -476,7 +476,7 @@ class _ServerPageState extends State ), ), ), - ServerState.failed => InkWell( + ServerConn.failed => InkWell( onTap: () { TryLimiter.reset(s.spi.id); Pros.server.refresh(spi: s.spi); @@ -490,7 +490,7 @@ class _ServerPageState extends State ), ), ), - ServerState.disconnected when !(s.spi.autoConnect ?? true) => InkWell( + ServerConn.disconnected when !(s.spi.autoConnect ?? true) => InkWell( onTap: () => Pros.server.refresh(spi: s.spi), child: const Padding( padding: EdgeInsets.symmetric(horizontal: 7), @@ -508,7 +508,7 @@ class _ServerPageState extends State } Widget _buildTopRightText(Server s) { - final hasErr = s.state == ServerState.failed && s.status.err != null; + final hasErr = s.conn == ServerConn.failed && s.status.err != null; return GestureDetector( onTap: () { if (!hasErr) return; @@ -636,9 +636,9 @@ class _ServerPageState extends State _tag == null || (pro.pick(id: e)?.spi.tags?.contains(_tag) ?? false)) .toList(); - double? _calcCardHeight(ServerState cs, bool flip) { + double? _calcCardHeight(ServerConn cs, bool flip) { if (_textFactorDouble != 1.0) return null; - if (cs != ServerState.finished) { + if (cs != ServerConn.finished) { return 23.0; } if (flip) { diff --git a/pubspec.lock b/pubspec.lock index 5bb46b2d..fb8b383c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -308,6 +308,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.5" + extended_image: + dependency: "direct main" + description: + name: extended_image + sha256: d7f091d068fcac7246c4b22a84b8dac59a62e04d29a5c172710c696e67a22f94 + url: "https://pub.dev" + source: hosted + version: "8.2.0" + extended_image_library: + dependency: transitive + description: + name: extended_image_library + sha256: c9caee8fe9b6547bd41c960c4f2d1ef8e34321804de6a1777f1d614a24247ad6 + url: "https://pub.dev" + source: hosted + version: "4.0.4" fake_async: dependency: transitive description: @@ -544,6 +560,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + http_client_helper: + dependency: transitive + description: + name: http_client_helper + sha256: "8a9127650734da86b5c73760de2b404494c968a3fd55602045ffec789dac3cb1" + url: "https://pub.dev" + source: hosted + version: "3.0.0" http_multi_server: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5a4daf24..4f237514 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -77,6 +77,7 @@ dependencies: permission_handler: ^11.3.1 fl_chart: ^0.67.0 wakelock_plus: ^1.2.4 + extended_image: ^8.2.0 dev_dependencies: flutter_native_splash: ^2.1.6