mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
#48 display all support devices temperature
This commit is contained in:
@@ -443,6 +443,12 @@ abstract class S {
|
|||||||
/// **'Files'**
|
/// **'Files'**
|
||||||
String get files;
|
String get files;
|
||||||
|
|
||||||
|
/// No description provided for @font.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Font'**
|
||||||
|
String get font;
|
||||||
|
|
||||||
/// No description provided for @fontSize.
|
/// No description provided for @fontSize.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -1007,11 +1013,11 @@ abstract class S {
|
|||||||
/// **'Are you sure to delete server [{server}]?'**
|
/// **'Are you sure to delete server [{server}]?'**
|
||||||
String sureToDeleteServer(Object server);
|
String sureToDeleteServer(Object server);
|
||||||
|
|
||||||
/// No description provided for @termTheme.
|
/// No description provided for @theme.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Terminal theme'**
|
/// **'Theme'**
|
||||||
String get termTheme;
|
String get theme;
|
||||||
|
|
||||||
/// No description provided for @themeMode.
|
/// No description provided for @themeMode.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -192,6 +192,9 @@ class SDe extends S {
|
|||||||
@override
|
@override
|
||||||
String get files => 'Dateien';
|
String get files => 'Dateien';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get font => 'Schriftarten';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Schriftgröße';
|
String get fontSize => 'Schriftgröße';
|
||||||
|
|
||||||
@@ -495,7 +498,7 @@ class SDe extends S {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get termTheme => 'Farbschema des Terminals';
|
String get theme => 'Themen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get themeMode => 'Thememodus';
|
String get themeMode => 'Thememodus';
|
||||||
|
|||||||
@@ -192,6 +192,9 @@ class SEn extends S {
|
|||||||
@override
|
@override
|
||||||
String get files => 'Files';
|
String get files => 'Files';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get font => 'Font';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => 'Font size';
|
String get fontSize => 'Font size';
|
||||||
|
|
||||||
@@ -495,7 +498,7 @@ class SEn extends S {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get termTheme => 'Terminal theme';
|
String get theme => 'Theme';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get themeMode => 'Theme mode';
|
String get themeMode => 'Theme mode';
|
||||||
|
|||||||
@@ -192,6 +192,9 @@ class SZh extends S {
|
|||||||
@override
|
@override
|
||||||
String get files => '文件';
|
String get files => '文件';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get font => '字体';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get fontSize => '字体大小';
|
String get fontSize => '字体大小';
|
||||||
|
|
||||||
@@ -495,7 +498,7 @@ class SZh extends S {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get termTheme => '终端主题';
|
String get theme => '主题';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get themeMode => '主题模式';
|
String get themeMode => '主题模式';
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ English | [简体中文](README_zh.md)
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
A Flutter project which provide charts to display server status and tools to manage server.
|
A Flutter project which provide charts to display Linux[](../../issues/43) server status and tools to manage server.
|
||||||
<br>
|
<br>
|
||||||
Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>.
|
Especially thanks to <a href="https://github.com/TerminalStudio/dartssh2">dartssh2</a> & <a href="https://github.com/TerminalStudio/xterm.dart">xterm.dart</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
class CpuStatus {
|
class CpuStatus {
|
||||||
List<OneTimeCpuStatus> _pre;
|
List<OneTimeCpuStatus> _pre;
|
||||||
List<OneTimeCpuStatus> _now;
|
List<OneTimeCpuStatus> _now;
|
||||||
String temp;
|
CpuStatus(this._pre, this._now);
|
||||||
CpuStatus(this._pre, this._now, this.temp);
|
|
||||||
|
|
||||||
double usedPercent({int coreIdx = 0}) {
|
double usedPercent({int coreIdx = 0}) {
|
||||||
if (_now.length != _pre.length) return 0;
|
if (_now.length != _pre.length) return 0;
|
||||||
@@ -12,10 +11,9 @@ class CpuStatus {
|
|||||||
return used.isNaN ? 0 : 100 - used * 100;
|
return used.isNaN ? 0 : 100 - used * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(List<OneTimeCpuStatus> newStatus, String newTemp) {
|
void update(List<OneTimeCpuStatus> newStatus) {
|
||||||
_pre = _now;
|
_pre = _now;
|
||||||
_now = newStatus;
|
_now = newStatus;
|
||||||
temp = newTemp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int get coresCount => _now.length;
|
int get coresCount => _now.length;
|
||||||
@@ -96,27 +94,3 @@ List<OneTimeCpuStatus> parseCPU(String raw) {
|
|||||||
}
|
}
|
||||||
return cpus;
|
return cpus;
|
||||||
}
|
}
|
||||||
|
|
||||||
final cpuTempReg = RegExp(r'(x86_pkg_temp|cpu_thermal)');
|
|
||||||
|
|
||||||
String parseCPUTemp(String type, String value) {
|
|
||||||
const noMatch = "/sys/class/thermal/thermal_zone*/type";
|
|
||||||
// Not support to get CPU temperature
|
|
||||||
if (type.contains(noMatch) || value.isEmpty || type.isEmpty) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
final split = type.split('\n');
|
|
||||||
// if no match, use idx 0
|
|
||||||
int idx = 0;
|
|
||||||
for (var item in split) {
|
|
||||||
if (item.contains(cpuTempReg)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
final valueSplited = value.split('\n');
|
|
||||||
if (idx >= valueSplited.length) return '';
|
|
||||||
final temp = int.tryParse(valueSplited[idx].trim());
|
|
||||||
if (temp == null) return '';
|
|
||||||
return '${(temp / 1000).toStringAsFixed(1)}°C';
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'package:toolbox/data/model/server/temp.dart';
|
||||||
|
|
||||||
import 'cpu_status.dart';
|
import 'cpu_status.dart';
|
||||||
import 'disk_info.dart';
|
import 'disk_info.dart';
|
||||||
import 'memory.dart';
|
import 'memory.dart';
|
||||||
@@ -13,6 +15,7 @@ class ServerStatus {
|
|||||||
List<DiskInfo> disk;
|
List<DiskInfo> disk;
|
||||||
ConnStatus tcp;
|
ConnStatus tcp;
|
||||||
NetSpeed netSpeed;
|
NetSpeed netSpeed;
|
||||||
|
Temperatures temps;
|
||||||
String? failedInfo;
|
String? failedInfo;
|
||||||
|
|
||||||
ServerStatus({
|
ServerStatus({
|
||||||
@@ -24,6 +27,7 @@ class ServerStatus {
|
|||||||
required this.tcp,
|
required this.tcp,
|
||||||
required this.netSpeed,
|
required this.netSpeed,
|
||||||
required this.swap,
|
required this.swap,
|
||||||
|
required this.temps,
|
||||||
this.failedInfo,
|
this.failedInfo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ extension _SegmentsExt on List<String> {
|
|||||||
Future<ServerStatus> getStatus(ServerStatusUpdateReq req) async {
|
Future<ServerStatus> getStatus(ServerStatusUpdateReq req) async {
|
||||||
final net = parseNetSpeed(req.segments.at(CmdType.net));
|
final net = parseNetSpeed(req.segments.at(CmdType.net));
|
||||||
req.ss.netSpeed.update(net);
|
req.ss.netSpeed.update(net);
|
||||||
|
|
||||||
final sys = _parseSysVer(
|
final sys = _parseSysVer(
|
||||||
req.segments.at(CmdType.sys),
|
req.segments.at(CmdType.sys),
|
||||||
req.segments.at(CmdType.host),
|
req.segments.at(CmdType.host),
|
||||||
@@ -32,22 +33,29 @@ Future<ServerStatus> getStatus(ServerStatusUpdateReq req) async {
|
|||||||
if (sys != null) {
|
if (sys != null) {
|
||||||
req.ss.sysVer = sys;
|
req.ss.sysVer = sys;
|
||||||
}
|
}
|
||||||
|
|
||||||
final cpus = parseCPU(req.segments.at(CmdType.cpu));
|
final cpus = parseCPU(req.segments.at(CmdType.cpu));
|
||||||
final cpuTemp = parseCPUTemp(
|
req.ss.cpu.update(cpus);
|
||||||
|
|
||||||
|
req.ss.temps.parse(
|
||||||
req.segments.at(CmdType.tempType),
|
req.segments.at(CmdType.tempType),
|
||||||
req.segments.at(CmdType.tempVal),
|
req.segments.at(CmdType.tempVal),
|
||||||
);
|
);
|
||||||
req.ss.cpu.update(cpus, cpuTemp);
|
|
||||||
final tcp = parseConn(req.segments.at(CmdType.conn));
|
final tcp = parseConn(req.segments.at(CmdType.conn));
|
||||||
if (tcp != null) {
|
if (tcp != null) {
|
||||||
req.ss.tcp = tcp;
|
req.ss.tcp = tcp;
|
||||||
}
|
}
|
||||||
|
|
||||||
req.ss.disk = parseDisk(req.segments.at(CmdType.disk));
|
req.ss.disk = parseDisk(req.segments.at(CmdType.disk));
|
||||||
|
|
||||||
req.ss.mem = parseMem(req.segments.at(CmdType.mem));
|
req.ss.mem = parseMem(req.segments.at(CmdType.mem));
|
||||||
|
|
||||||
final uptime = _parseUpTime(req.segments.at(CmdType.uptime));
|
final uptime = _parseUpTime(req.segments.at(CmdType.uptime));
|
||||||
if (uptime != null) {
|
if (uptime != null) {
|
||||||
req.ss.uptime = uptime;
|
req.ss.uptime = uptime;
|
||||||
}
|
}
|
||||||
|
|
||||||
req.ss.swap = parseSwap(req.segments.at(CmdType.mem));
|
req.ss.swap = parseSwap(req.segments.at(CmdType.mem));
|
||||||
return req.ss;
|
return req.ss;
|
||||||
}
|
}
|
||||||
|
|||||||
61
lib/data/model/server/temp.dart
Normal file
61
lib/data/model/server/temp.dart
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
class Temperatures {
|
||||||
|
final Map<String, double> _map = {};
|
||||||
|
|
||||||
|
Temperatures();
|
||||||
|
|
||||||
|
void parse(String type, String value) {
|
||||||
|
const noMatch = "/sys/class/thermal/thermal_zone*/type";
|
||||||
|
// Not support to get CPU temperature
|
||||||
|
if (type.contains(noMatch) || value.isEmpty || type.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final typeSplited = type.split('\n');
|
||||||
|
final valueSplited = value.split('\n');
|
||||||
|
if (typeSplited.length != valueSplited.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < typeSplited.length; i++) {
|
||||||
|
final t = typeSplited[i];
|
||||||
|
final v = valueSplited[i];
|
||||||
|
if (t.isEmpty || v.isEmpty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final name = t.split('/').last;
|
||||||
|
final temp = double.tryParse(v);
|
||||||
|
if (temp == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_map[name] = temp / 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(String name, double value) {
|
||||||
|
_map[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
double? get(String name) {
|
||||||
|
return _map[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<String> get devices {
|
||||||
|
return _map.keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isEmpty {
|
||||||
|
return _map.isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
double? get first {
|
||||||
|
if (_map.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (final key in _map.keys) {
|
||||||
|
if (cpuTempReg.hasMatch(key)) {
|
||||||
|
return _map[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _map.values.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final cpuTempReg = RegExp(r'(x86_pkg_temp|cpu_thermal)');
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:toolbox/data/res/ui.dart';
|
||||||
|
|
||||||
import '../../data/res/misc.dart';
|
import '../../data/res/misc.dart';
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ class DebugProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
void _addWidget(Widget widget) {
|
void _addWidget(Widget widget) {
|
||||||
widgets.add(widget);
|
widgets.add(widget);
|
||||||
widgets.add(const SizedBox(height: 13));
|
widgets.add(height13);
|
||||||
if (widgets.length > maxDebugLogLines) {
|
if (widgets.length > maxDebugLogLines) {
|
||||||
widgets.removeRange(0, widgets.length - maxDebugLogLines);
|
widgets.removeRange(0, widgets.length - maxDebugLogLines);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'package:toolbox/data/model/server/temp.dart';
|
||||||
|
|
||||||
import '../model/server/cpu_status.dart';
|
import '../model/server/cpu_status.dart';
|
||||||
import '../model/server/disk_info.dart';
|
import '../model/server/disk_info.dart';
|
||||||
import '../model/server/memory.dart';
|
import '../model/server/memory.dart';
|
||||||
@@ -24,7 +26,6 @@ OneTimeCpuStatus get _initOneTimeCpuStatus => OneTimeCpuStatus(
|
|||||||
CpuStatus get initCpuStatus => CpuStatus(
|
CpuStatus get initCpuStatus => CpuStatus(
|
||||||
[_initOneTimeCpuStatus],
|
[_initOneTimeCpuStatus],
|
||||||
[_initOneTimeCpuStatus],
|
[_initOneTimeCpuStatus],
|
||||||
'',
|
|
||||||
);
|
);
|
||||||
NetSpeedPart get _initNetSpeedPart => NetSpeedPart(
|
NetSpeedPart get _initNetSpeedPart => NetSpeedPart(
|
||||||
'',
|
'',
|
||||||
@@ -50,4 +51,5 @@ ServerStatus get initStatus => ServerStatus(
|
|||||||
tcp: ConnStatus(0, 0, 0, 0),
|
tcp: ConnStatus(0, 0, 0, 0),
|
||||||
netSpeed: initNetSpeed,
|
netSpeed: initNetSpeed,
|
||||||
swap: _initSwap,
|
swap: _initSwap,
|
||||||
|
temps: Temperatures(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const roundRectCardPadding = EdgeInsets.symmetric(horizontal: 17, vertical: 13);
|
|||||||
|
|
||||||
/// SizedBox
|
/// SizedBox
|
||||||
|
|
||||||
|
const placeholder = SizedBox.shrink();
|
||||||
const height13 = SizedBox(height: 13);
|
const height13 = SizedBox(height: 13);
|
||||||
const width13 = SizedBox(width: 13);
|
const width13 = SizedBox(width: 13);
|
||||||
const width7 = SizedBox(width: 7);
|
const width7 = SizedBox(width: 7);
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"fileNotExist": "{file} existiert nicht",
|
"fileNotExist": "{file} existiert nicht",
|
||||||
"fileTooLarge": "Datei '{file}' ist zu groß {size}, max {sizeMax}",
|
"fileTooLarge": "Datei '{file}' ist zu groß {size}, max {sizeMax}",
|
||||||
"files": "Dateien",
|
"files": "Dateien",
|
||||||
|
"font": "Schriftarten",
|
||||||
"fontSize": "Schriftgröße",
|
"fontSize": "Schriftgröße",
|
||||||
"foundNUpdate": "Update {count} gefunden",
|
"foundNUpdate": "Update {count} gefunden",
|
||||||
"getPushTokenFailed": "Push-Token kann nicht abgerufen werden",
|
"getPushTokenFailed": "Push-Token kann nicht abgerufen werden",
|
||||||
@@ -151,7 +152,7 @@
|
|||||||
"sureDirEmpty": "Stelle sicher, dass der Ordner leer ist.",
|
"sureDirEmpty": "Stelle sicher, dass der Ordner leer ist.",
|
||||||
"sureNoPwd": "Bist du sicher, dass du kein Passwort verwenden willst?",
|
"sureNoPwd": "Bist du sicher, dass du kein Passwort verwenden willst?",
|
||||||
"sureToDeleteServer": "Bist du sicher, dass du [{server}] löschen willst?",
|
"sureToDeleteServer": "Bist du sicher, dass du [{server}] löschen willst?",
|
||||||
"termTheme": "Farbschema des Terminals",
|
"theme": "Themen",
|
||||||
"themeMode": "Thememodus",
|
"themeMode": "Thememodus",
|
||||||
"times": "x",
|
"times": "x",
|
||||||
"ttl": "ttl",
|
"ttl": "ttl",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"fileNotExist": "{file} not exist",
|
"fileNotExist": "{file} not exist",
|
||||||
"fileTooLarge": "File '{file}' too large {size}, max {sizeMax}",
|
"fileTooLarge": "File '{file}' too large {size}, max {sizeMax}",
|
||||||
"files": "Files",
|
"files": "Files",
|
||||||
|
"font": "Font",
|
||||||
"fontSize": "Font size",
|
"fontSize": "Font size",
|
||||||
"foundNUpdate": "Found {count} update",
|
"foundNUpdate": "Found {count} update",
|
||||||
"getPushTokenFailed": "Can't fetch push token",
|
"getPushTokenFailed": "Can't fetch push token",
|
||||||
@@ -151,7 +152,7 @@
|
|||||||
"sureDirEmpty": "Make sure dir is empty.",
|
"sureDirEmpty": "Make sure dir is empty.",
|
||||||
"sureNoPwd": "Are you sure to use no password?",
|
"sureNoPwd": "Are you sure to use no password?",
|
||||||
"sureToDeleteServer": "Are you sure to delete server [{server}]?",
|
"sureToDeleteServer": "Are you sure to delete server [{server}]?",
|
||||||
"termTheme": "Terminal theme",
|
"theme": "Theme",
|
||||||
"themeMode": "Theme mode",
|
"themeMode": "Theme mode",
|
||||||
"times": "Times",
|
"times": "Times",
|
||||||
"ttl": "ttl",
|
"ttl": "ttl",
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"fileNotExist": "{file} 不存在",
|
"fileNotExist": "{file} 不存在",
|
||||||
"fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}",
|
"fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}",
|
||||||
"files": "文件",
|
"files": "文件",
|
||||||
|
"font": "字体",
|
||||||
"fontSize": "字体大小",
|
"fontSize": "字体大小",
|
||||||
"foundNUpdate": "找到 {count} 个更新",
|
"foundNUpdate": "找到 {count} 个更新",
|
||||||
"getPushTokenFailed": "未能获取到推送token",
|
"getPushTokenFailed": "未能获取到推送token",
|
||||||
@@ -151,7 +152,7 @@
|
|||||||
"sureDirEmpty": "请确保文件夹为空",
|
"sureDirEmpty": "请确保文件夹为空",
|
||||||
"sureNoPwd": "确认使用无密码?",
|
"sureNoPwd": "确认使用无密码?",
|
||||||
"sureToDeleteServer": "你确定要删除服务器 [{server}] 吗?",
|
"sureToDeleteServer": "你确定要删除服务器 [{server}] 吗?",
|
||||||
"termTheme": "终端主题",
|
"theme": "主题",
|
||||||
"themeMode": "主题模式",
|
"themeMode": "主题模式",
|
||||||
"times": "次",
|
"times": "次",
|
||||||
"ttl": "缓存时间",
|
"ttl": "缓存时间",
|
||||||
|
|||||||
@@ -55,9 +55,7 @@ class BackupPage extends StatelessWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(height: 107),
|
||||||
height: 107,
|
|
||||||
),
|
|
||||||
_buildCard(s.restore, Icons.download, media, () async {
|
_buildCard(s.restore, Icons.download, media, () async {
|
||||||
final path = await pickOneFile();
|
final path = await pickOneFile();
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
@@ -72,12 +70,12 @@ class BackupPage extends StatelessWidget {
|
|||||||
final text = await file.readAsString();
|
final text = await file.readAsString();
|
||||||
_import(text, context, s);
|
_import(text, context, s);
|
||||||
}),
|
}),
|
||||||
const SizedBox(height: 17),
|
height13,
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 37,
|
width: 37,
|
||||||
child: Divider(),
|
child: Divider(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 17),
|
height13,
|
||||||
_buildCard(
|
_buildCard(
|
||||||
s.backup,
|
s.backup,
|
||||||
Icons.file_upload,
|
Icons.file_upload,
|
||||||
@@ -122,7 +120,7 @@ class BackupPage extends StatelessWidget {
|
|||||||
icon,
|
icon,
|
||||||
color: textColor,
|
color: textColor,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 7),
|
width7,
|
||||||
Text(text, style: TextStyle(color: textColor)),
|
Text(text, style: TextStyle(color: textColor)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:toolbox/data/res/ui.dart';
|
||||||
|
|
||||||
import '../../core/utils/ui.dart';
|
import '../../core/utils/ui.dart';
|
||||||
import '../widget/input_field.dart';
|
import '../widget/input_field.dart';
|
||||||
@@ -48,7 +49,7 @@ class _ConvertPageState extends State<ConvertPage>
|
|||||||
controller: ScrollController(),
|
controller: ScrollController(),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 13),
|
height13,
|
||||||
_buildInputTop(),
|
_buildInputTop(),
|
||||||
_buildMiddleBtns(),
|
_buildMiddleBtns(),
|
||||||
_buildResult(),
|
_buildResult(),
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
|
|
||||||
Widget _buildImages() {
|
Widget _buildImages() {
|
||||||
if (_docker.images == null) {
|
if (_docker.images == null) {
|
||||||
return const SizedBox();
|
return placeholder;
|
||||||
}
|
}
|
||||||
final items = _docker.images!
|
final items = _docker.images!
|
||||||
.map(
|
.map(
|
||||||
@@ -336,7 +336,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildLoading() {
|
Widget _buildLoading() {
|
||||||
if (!_docker.isBusy) return const SizedBox();
|
if (!_docker.isBusy) return placeholder;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(17),
|
padding: const EdgeInsets.all(17),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -344,7 +344,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
const Center(
|
const Center(
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 17),
|
height13,
|
||||||
Text(_docker.runLog ?? '...'),
|
Text(_docker.runLog ?? '...'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -353,7 +353,7 @@ class _DockerManagePageState extends State<DockerManagePage> {
|
|||||||
|
|
||||||
Widget _buildEditHost() {
|
Widget _buildEditHost() {
|
||||||
if (_docker.items!.isNotEmpty || _docker.images!.isNotEmpty) {
|
if (_docker.items!.isNotEmpty || _docker.images!.isNotEmpty) {
|
||||||
return const SizedBox();
|
return placeholder;
|
||||||
}
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(17, 17, 17, 0),
|
padding: const EdgeInsets.fromLTRB(17, 17, 17, 0),
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class _PingPageState extends State<PingPage>
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 7),
|
padding: const EdgeInsets.symmetric(horizontal: 7),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 13),
|
height13,
|
||||||
Input(
|
Input(
|
||||||
controller: _textEditingController,
|
controller: _textEditingController,
|
||||||
hint: s.inputDomainHere,
|
hint: s.inputDomainHere,
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class _PkgManagePageState extends State<PkgManagePage>
|
|||||||
|
|
||||||
Widget _buildFAB(PkgProvider pkg) {
|
Widget _buildFAB(PkgProvider pkg) {
|
||||||
if (pkg.isBusy || (pkg.upgradeable?.isEmpty ?? true)) {
|
if (pkg.isBusy || (pkg.upgradeable?.isEmpty ?? true)) {
|
||||||
return const SizedBox();
|
return placeholder;
|
||||||
}
|
}
|
||||||
return FloatingActionButton(
|
return FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
|
|||||||
late PrivateKeyProvider _provider;
|
late PrivateKeyProvider _provider;
|
||||||
late S _s;
|
late S _s;
|
||||||
|
|
||||||
Widget _loading = const SizedBox();
|
Widget _loading = placeholder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_provider = locator<PrivateKeyProvider>();
|
_provider = locator<PrivateKeyProvider>();
|
||||||
_loading = const SizedBox();
|
_loading = placeholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -79,7 +79,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
|
|||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.delete))
|
icon: const Icon(Icons.delete))
|
||||||
: const SizedBox()
|
: placeholder
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage>
|
|||||||
rethrow;
|
rethrow;
|
||||||
} finally {
|
} finally {
|
||||||
setState(() {
|
setState(() {
|
||||||
_loading = const SizedBox();
|
_loading = placeholder;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (widget.info != null) {
|
if (widget.info != null) {
|
||||||
|
|||||||
@@ -60,47 +60,38 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
body: ListView(
|
body: ListView(
|
||||||
padding: const EdgeInsets.all(13),
|
padding: const EdgeInsets.all(13),
|
||||||
children: [
|
children: [
|
||||||
...(_buildLinuxIcon(si.status.sysVer) ?? []),
|
_buildLinuxIcon(si.status.sysVer),
|
||||||
_buildUpTimeAndSys(si.status),
|
_buildUpTimeAndSys(si.status),
|
||||||
_buildCPUView(si.status),
|
_buildCPUView(si.status),
|
||||||
_buildMemView(si.status),
|
_buildMemView(si.status),
|
||||||
_buildSwapView(si.status),
|
_buildSwapView(si.status),
|
||||||
_buildDiskView(si.status),
|
_buildDiskView(si.status),
|
||||||
_buildNetView(si.status.netSpeed),
|
_buildNetView(si.status.netSpeed),
|
||||||
// avoid the hieght of navigation bar
|
_buildTemperature(si.status),
|
||||||
|
// height of navigation bar
|
||||||
SizedBox(height: _media.padding.bottom),
|
SizedBox(height: _media.padding.bottom),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget>? _buildLinuxIcon(String sysVer) {
|
Widget _buildLinuxIcon(String sysVer) {
|
||||||
if (!_showDistLogo) return null;
|
if (!_showDistLogo) return placeholder;
|
||||||
final iconPath = sysVer.dist?.iconPath;
|
final iconPath = sysVer.dist?.iconPath;
|
||||||
if (iconPath == null) return null;
|
if (iconPath == null) return placeholder;
|
||||||
return [
|
return ConstrainedBox(
|
||||||
SizedBox(height: _media.size.height * 0.03),
|
constraints: BoxConstraints(
|
||||||
ConstrainedBox(
|
maxHeight: _media.size.height * 0.13,
|
||||||
constraints: BoxConstraints(
|
maxWidth: _media.size.width * 0.6,
|
||||||
maxHeight: _media.size.height * 0.13,
|
|
||||||
maxWidth: _media.size.width * 0.6,
|
|
||||||
),
|
|
||||||
child: Image.asset(
|
|
||||||
iconPath,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: _media.size.height * 0.03),
|
child: Image.asset(
|
||||||
];
|
iconPath,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCPUView(ServerStatus ss) {
|
Widget _buildCPUView(ServerStatus ss) {
|
||||||
final tempWidget = ss.cpu.temp.isEmpty
|
|
||||||
? const SizedBox()
|
|
||||||
: Text(
|
|
||||||
ss.cpu.temp,
|
|
||||||
style: textSize13Grey,
|
|
||||||
);
|
|
||||||
return RoundRectCard(
|
return RoundRectCard(
|
||||||
Padding(
|
Padding(
|
||||||
padding: roundRectCardPadding,
|
padding: roundRectCardPadding,
|
||||||
@@ -108,16 +99,10 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Text(
|
||||||
children: [
|
'${ss.cpu.usedPercent(coreIdx: 0).toInt()}%',
|
||||||
Text(
|
style: textSize27,
|
||||||
'${ss.cpu.usedPercent(coreIdx: 0).toInt()}%',
|
textScaleFactor: 1.0,
|
||||||
style: textSize27,
|
|
||||||
textScaleFactor: 1.0,
|
|
||||||
),
|
|
||||||
width7,
|
|
||||||
tempWidget
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -244,7 +229,7 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSwapView(ServerStatus ss) {
|
Widget _buildSwapView(ServerStatus ss) {
|
||||||
if (ss.swap.total == 0) return const SizedBox();
|
if (ss.swap.total == 0) return placeholder;
|
||||||
final used = ss.swap.usedPercent * 100;
|
final used = ss.swap.usedPercent * 100;
|
||||||
final cached = ss.swap.cached / ss.swap.total * 100;
|
final cached = ss.swap.cached / ss.swap.total * 100;
|
||||||
return RoundRectCard(
|
return RoundRectCard(
|
||||||
@@ -396,5 +381,40 @@ class _ServerDetailPageState extends State<ServerDetailPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildTemperature(ServerStatus ss) {
|
||||||
|
if (ss.temps.isEmpty) {
|
||||||
|
return placeholder;
|
||||||
|
}
|
||||||
|
final List<Widget> children = [
|
||||||
|
const Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.device_hub, size: 17),
|
||||||
|
Icon(Icons.arrow_downward, size: 17),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Padding(padding: EdgeInsets.symmetric(vertical: 3), child: Divider(height: 7),),
|
||||||
|
];
|
||||||
|
children.addAll(ss.temps.devices.map((key) => Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
key,
|
||||||
|
style: textSize11,
|
||||||
|
textScaleFactor: 1.0,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'${ss.temps.get(key)}°C',
|
||||||
|
style: textSize11,
|
||||||
|
textScaleFactor: 1.0,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)));
|
||||||
|
return RoundRectCard(Padding(
|
||||||
|
padding: roundRectCardPadding,
|
||||||
|
child: Column(children: children),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
static const _ignorePath = ['udev', 'tmpfs', 'devtmpfs'];
|
static const _ignorePath = ['udev', 'tmpfs', 'devtmpfs'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
return AppBar(
|
return AppBar(
|
||||||
title: Text(_s.edit, style: textSize18),
|
title: Text(_s.edit, style: textSize18),
|
||||||
actions: [
|
actions: [
|
||||||
widget.spi != null ? delBtn : const SizedBox(),
|
widget.spi != null ? delBtn : placeholder,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -165,8 +165,8 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
|||||||
hint: _s.pwd,
|
hint: _s.pwd,
|
||||||
onSubmitted: (_) => {},
|
onSubmitted: (_) => {},
|
||||||
)
|
)
|
||||||
: const SizedBox(),
|
: placeholder,
|
||||||
usePublicKey ? _buildKeyAuth() : const SizedBox()
|
usePublicKey ? _buildKeyAuth() : placeholder
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -108,20 +108,20 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
|
|
||||||
Widget _buildEachServerCard(Server? si) {
|
Widget _buildEachServerCard(Server? si) {
|
||||||
if (si == null) {
|
if (si == null) {
|
||||||
return const SizedBox();
|
return placeholder;
|
||||||
}
|
}
|
||||||
return RoundRectCard(
|
return GestureDetector(
|
||||||
GestureDetector(
|
key: Key(si.spi.id),
|
||||||
child: Padding(
|
onTap: () => AppRoute(
|
||||||
|
ServerDetailPage(si.spi.id),
|
||||||
|
'server detail page',
|
||||||
|
).go(context),
|
||||||
|
child: RoundRectCard(
|
||||||
|
Padding(
|
||||||
padding: const EdgeInsets.all(13),
|
padding: const EdgeInsets.all(13),
|
||||||
child: _buildRealServerCard(si.status, si.state, si.spi),
|
child: _buildRealServerCard(si.status, si.state, si.spi),
|
||||||
),
|
),
|
||||||
onTap: () => AppRoute(
|
|
||||||
ServerDetailPage(si.spi.id),
|
|
||||||
'server detail page',
|
|
||||||
).go(context),
|
|
||||||
),
|
),
|
||||||
key: Key(si.spi.id),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,9 +136,7 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildServerCardTitle(ss, cs, spi),
|
_buildServerCardTitle(ss, cs, spi),
|
||||||
const SizedBox(
|
height13,
|
||||||
height: 17,
|
|
||||||
),
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
@@ -149,7 +147,7 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
'Total:\n${rootDisk.size}', 'Used:\n${rootDisk.usedPercent}%')
|
'Total:\n${rootDisk.size}', 'Used:\n${rootDisk.usedPercent}%')
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 13),
|
height13,
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
@@ -169,9 +167,6 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
ServerState cs,
|
ServerState cs,
|
||||||
ServerPrivateInfo spi,
|
ServerPrivateInfo spi,
|
||||||
) {
|
) {
|
||||||
final topRightStr =
|
|
||||||
getTopRightStr(cs, ss.cpu.temp, ss.uptime, ss.failedInfo);
|
|
||||||
final hasError = cs == ServerState.failed && ss.failedInfo != null;
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 7),
|
padding: const EdgeInsets.symmetric(horizontal: 7),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -194,32 +189,8 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
hasError
|
_buildTopRightText(ss, cs),
|
||||||
? GestureDetector(
|
width7,
|
||||||
onTap: () => showRoundDialog(
|
|
||||||
context: context,
|
|
||||||
title: Text(_s.error),
|
|
||||||
child: Text(ss.failedInfo ?? _s.unknownError),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => copy2Clipboard(
|
|
||||||
ss.failedInfo ?? _s.unknownError),
|
|
||||||
child: Text(_s.copy),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
_s.viewErr,
|
|
||||||
style: textSize12Grey,
|
|
||||||
textScaleFactor: 1.0,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Text(
|
|
||||||
topRightStr,
|
|
||||||
style: textSize12Grey,
|
|
||||||
textScaleFactor: 1.0,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 9),
|
|
||||||
_buildSSHBtn(spi),
|
_buildSSHBtn(spi),
|
||||||
_buildMoreBtn(spi),
|
_buildMoreBtn(spi),
|
||||||
],
|
],
|
||||||
@@ -229,6 +200,41 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildTopRightText(ServerStatus ss, ServerState cs) {
|
||||||
|
final topRightStr = getTopRightStr(
|
||||||
|
cs,
|
||||||
|
ss.temps.first,
|
||||||
|
ss.uptime,
|
||||||
|
ss.failedInfo,
|
||||||
|
);
|
||||||
|
final hasError = cs == ServerState.failed && ss.failedInfo != null;
|
||||||
|
return hasError
|
||||||
|
? GestureDetector(
|
||||||
|
onTap: () => showRoundDialog(
|
||||||
|
context: context,
|
||||||
|
title: Text(_s.error),
|
||||||
|
child: Text(ss.failedInfo ?? _s.unknownError),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () =>
|
||||||
|
copy2Clipboard(ss.failedInfo ?? _s.unknownError),
|
||||||
|
child: Text(_s.copy),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
_s.viewErr,
|
||||||
|
style: textSize12Grey,
|
||||||
|
textScaleFactor: 1.0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
topRightStr,
|
||||||
|
style: textSize12Grey,
|
||||||
|
textScaleFactor: 1.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildSSHBtn(ServerPrivateInfo spi) {
|
Widget _buildSSHBtn(ServerPrivateInfo spi) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: const Icon(
|
child: const Icon(
|
||||||
@@ -311,24 +317,18 @@ class _ServerPageState extends State<ServerPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
String getTopRightStr(
|
String getTopRightStr(
|
||||||
ServerState cs, String temp, String upTime, String? failedInfo) {
|
ServerState cs,
|
||||||
|
double? temp,
|
||||||
|
String upTime,
|
||||||
|
String? failedInfo,
|
||||||
|
) {
|
||||||
switch (cs) {
|
switch (cs) {
|
||||||
case ServerState.disconnected:
|
case ServerState.disconnected:
|
||||||
return _s.disconnected;
|
return _s.disconnected;
|
||||||
case ServerState.connected:
|
case ServerState.connected:
|
||||||
if (temp == '') {
|
final tempStr = temp == null ? '' : '${temp.toStringAsFixed(1)}°C';
|
||||||
if (upTime == '') {
|
final items = [tempStr, upTime];
|
||||||
return _s.serverTabLoading;
|
return items.where((element) => element.isNotEmpty).join(' | ');
|
||||||
} else {
|
|
||||||
return upTime;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (upTime == '') {
|
|
||||||
return temp;
|
|
||||||
} else {
|
|
||||||
return '$temp | $upTime';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ServerState.connecting:
|
case ServerState.connecting:
|
||||||
return _s.serverTabConnecting;
|
return _s.serverTabConnecting;
|
||||||
case ServerState.failed:
|
case ServerState.failed:
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
.toList();
|
.toList();
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
_s.termTheme,
|
_s.theme,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
termThemeKey.currentState?.showButtonMenu();
|
termThemeKey.currentState?.showButtonMenu();
|
||||||
@@ -465,7 +465,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
Widget _buildFont() {
|
Widget _buildFont() {
|
||||||
final fontName = getFileName(_setting.fontPath.fetch());
|
final fontName = getFileName(_setting.fontPath.fetch());
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(_s.choose),
|
title: Text(_s.font),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
fontName ?? _s.notSelected,
|
fontName ?? _s.notSelected,
|
||||||
style: textSize15,
|
style: textSize15,
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ class _SFTPPageState extends State<SFTPPage> {
|
|||||||
title: Text(_s.download),
|
title: Text(_s.download),
|
||||||
onTap: () => download(context, file),
|
onTap: () => download(context, file),
|
||||||
)
|
)
|
||||||
: const SizedBox()
|
: placeholder
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class _SnippetEditPageState extends State<SnippetEditPage>
|
|||||||
},
|
},
|
||||||
tooltip: _s.delete,
|
tooltip: _s.delete,
|
||||||
icon: const Icon(Icons.delete))
|
icon: const Icon(Icons.delete))
|
||||||
: const SizedBox()
|
: placeholder
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: _buildBody(),
|
body: _buildBody(),
|
||||||
|
|||||||
Reference in New Issue
Block a user