From 0ddfc4ec75d6071c2c6dae6eaa7515db3e75160f Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Thu, 8 Jun 2023 22:17:26 +0800 Subject: [PATCH] new: `full screen` --- .dart_tool/flutter_gen/gen_l10n/l10n.dart | 6 + .dart_tool/flutter_gen/gen_l10n/l10n_de.dart | 3 + .dart_tool/flutter_gen/gen_l10n/l10n_en.dart | 3 + .dart_tool/flutter_gen/gen_l10n/l10n_zh.dart | 3 + lib/app.dart | 4 +- lib/core/utils/ui.dart | 7 +- lib/data/store/setting.dart | 5 +- lib/l10n/app_en.arb | 1 + lib/l10n/app_zh.arb | 1 + lib/view/page/full_screen.dart | 327 +++++++++++++++++++ lib/view/page/server/tab.dart | 3 +- lib/view/page/setting.dart | 12 + 12 files changed, 370 insertions(+), 5 deletions(-) create mode 100644 lib/view/page/full_screen.dart diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n.dart b/.dart_tool/flutter_gen/gen_l10n/l10n.dart index adc9b83d..bc333f24 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n.dart @@ -480,6 +480,12 @@ abstract class S { /// **'Found {count} update'** String foundNUpdate(Object count); + /// No description provided for @fullScreen. + /// + /// In en, this message translates to: + /// **'Full screen mode'** + String get fullScreen; + /// No description provided for @getPushTokenFailed. /// /// In en, this message translates to: diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart index d07883a2..a4aa1ab6 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart @@ -212,6 +212,9 @@ class SDe extends S { return 'Update $count gefunden'; } + @override + String get fullScreen => 'Full screen mode'; + @override String get getPushTokenFailed => 'Push-Token kann nicht abgerufen werden'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart index 470a6be2..2ff4305b 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart @@ -212,6 +212,9 @@ class SEn extends S { return 'Found $count update'; } + @override + String get fullScreen => 'Full screen mode'; + @override String get getPushTokenFailed => 'Can\'t fetch push token'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart index aeb07020..ba6b40f2 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart @@ -212,6 +212,9 @@ class SZh extends S { return '找到 $count 个更新'; } + @override + String get fullScreen => '全屏模式'; + @override String get getPushTokenFailed => '未能获取到推送token'; diff --git a/lib/app.dart b/lib/app.dart index 4aa74d8f..ce23e7dd 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:toolbox/core/extension/locale.dart'; +import 'package:toolbox/view/page/full_screen.dart'; import 'core/utils/ui.dart'; import 'data/res/build_data.dart'; @@ -18,6 +19,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { setTransparentNavigationBar(context); primaryColor = Color(_setting.primaryColor.fetch()!); + final fullScreen = _setting.fullScreen.fetch()!; return ValueListenableBuilder( valueListenable: _setting.themeMode.listenable(), @@ -75,7 +77,7 @@ class MyApp extends StatelessWidget { color: Colors.black, ), ), - home: const HomePage(), + home: fullScreen ? const FullScreenPage() : const HomePage(), ); }, ); diff --git a/lib/core/utils/ui.dart b/lib/core/utils/ui.dart index 9368aee4..864a7f6d 100644 --- a/lib/core/utils/ui.dart +++ b/lib/core/utils/ui.dart @@ -74,7 +74,7 @@ Future showRoundDialog({ Widget buildSwitch( BuildContext context, StoreProperty prop, { - Function(bool)? func, + void Function(bool)? func, }) { return ValueListenableBuilder( valueListenable: prop.listenable(), @@ -167,3 +167,8 @@ void showSnippetDialog( ], ); } + +void hideStatusBar() { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky, + overlays: []); +} diff --git a/lib/data/store/setting.dart b/lib/data/store/setting.dart index 0cc23557..06584277 100644 --- a/lib/data/store/setting.dart +++ b/lib/data/store/setting.dart @@ -6,7 +6,7 @@ import '../res/default.dart'; class SettingStore extends PersistentStore { StoreProperty get primaryColor => property( 'primaryColor', - defaultValue: defaultPrimaryColor.value, + defaultValue: 4287106639, ); StoreProperty get serverStatusUpdateInterval => property( @@ -66,4 +66,7 @@ class SettingStore extends PersistentStore { // Editor theme StoreProperty get editorTheme => property('editorTheme', defaultValue: defaultEditorTheme); + + StoreProperty get fullScreen => + property('fullScreen', defaultValue: false); } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index a918a8a1..003ffbfd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -64,6 +64,7 @@ "font": "Font", "fontSize": "Font size", "foundNUpdate": "Found {count} update", + "fullScreen": "Full screen mode", "getPushTokenFailed": "Can't fetch push token", "gettingToken": "Getting token...", "goto": "Go to", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9d7d770c..4b82811b 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -64,6 +64,7 @@ "font": "字体", "fontSize": "字体大小", "foundNUpdate": "找到 {count} 个更新", + "fullScreen": "全屏模式", "getPushTokenFailed": "未能获取到推送token", "gettingToken": "正在获取Token...", "goto": "前往", diff --git a/lib/view/page/full_screen.dart b/lib/view/page/full_screen.dart new file mode 100644 index 00000000..b97222c9 --- /dev/null +++ b/lib/view/page/full_screen.dart @@ -0,0 +1,327 @@ +import 'package:after_layout/after_layout.dart'; +import 'package:circle_chart/circle_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:get_it/get_it.dart'; +import 'package:provider/provider.dart'; +import 'package:toolbox/core/route.dart'; +import 'package:toolbox/data/provider/server.dart'; +import 'package:toolbox/data/res/ui.dart'; +import 'package:toolbox/locator.dart'; + +import '../../core/analysis.dart'; +import '../../core/update.dart'; +import '../../core/utils/ui.dart'; +import '../../data/model/server/server.dart'; +import '../../data/model/server/server_private_info.dart'; +import '../../data/model/server/server_status.dart'; +import '../../data/res/color.dart'; +import 'server/detail.dart'; +import 'server/edit.dart'; +import 'setting.dart'; + +class FullScreenPage extends StatefulWidget { + const FullScreenPage({Key? key}) : super(key: key); + + @override + _FullScreenPageState createState() => _FullScreenPageState(); +} + +class _FullScreenPageState extends State + with AfterLayoutMixin, AutomaticKeepAliveClientMixin { + late S _s; + late MediaQueryData _media; + late ThemeData _theme; + + final _pageController = PageController(initialPage: 0); + final _serverProvider = locator(); + + @override + void initState() { + super.initState(); + hideStatusBar(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _s = S.of(context)!; + _media = MediaQuery.of(context); + _theme = Theme.of(context); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Scaffold( + body: SafeArea( + child: RotatedBox( + quarterTurns: 3, + child: Stack( + children: [ + _buildMain(), + Positioned( + top: 0, + left: 0, + child: _buildSettingBtn(), + ), + ], + ), + ), + ), + ); + } + + Widget _buildSettingBtn() { + return IconButton( + onPressed: () => AppRoute( + const SettingPage(), + 'Setting', + ).go(context), + icon: const Icon(Icons.settings, color: Colors.grey)); + } + + Widget _buildMain() { + return Consumer(builder: (_, pro, __) { + if (pro.serverOrder.isEmpty) { + return Center( + child: TextButton( + onPressed: () => AppRoute( + const ServerEditPage(), + 'Add server info page', + ).go(context), + child: Text( + _s.addAServer, + style: const TextStyle(fontSize: 27), + )), + ); + } + return PageView.builder( + controller: _pageController, + itemCount: pro.servers.length, + itemBuilder: (_, idx) { + final id = pro.serverOrder[idx]; + final s = pro.servers[id]; + if (s == null) { + return placeholder; + } + return _buildRealServerCard(s.status, s.state, s.spi); + }, + ); + }); + } + + Widget _buildRealServerCard( + ServerStatus ss, + ServerState cs, + ServerPrivateInfo spi, + ) { + final rootDisk = ss.disk.firstWhere((element) => element.loc == '/'); + + return InkWell( + onTap: () => AppRoute( + ServerDetailPage(spi.id), + 'server detail page', + ).go(context), + child: Stack( + children: [ + Positioned(top: 0, left: 0, right: 0, child: _buildServerCardTitle(ss, cs, spi)), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: _media.size.width * 0.1), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildPercentCircle(ss.cpu.usedPercent()), + _buildPercentCircle(ss.mem.usedPercent * 100), + _buildIOData( + 'Conn:\n${ss.tcp.maxConn}', 'Fail:\n${ss.tcp.fail}'), + _buildIOData( + 'Total:\n${rootDisk.size}', + 'Used:\n${rootDisk.usedPercent}%', + ) + ], + ), + SizedBox(height: _media.size.width * 0.1), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildExplainText('CPU'), + _buildExplainText('Mem'), + _buildExplainText('Net'), + _buildExplainText('Disk'), + ], + ), + ], + ), + ], + ), + ); + } + + Widget _buildServerCardTitle( + ServerStatus ss, + ServerState cs, + ServerPrivateInfo spi, + ) { + return Padding( + padding: EdgeInsets.symmetric(vertical: _media.size.width * 0.05), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + spi.name, + style: + const TextStyle(fontWeight: FontWeight.bold, fontSize: 19), + textScaleFactor: 1.0, + textAlign: TextAlign.center, + ), + const Icon( + Icons.keyboard_arrow_right, + size: 21, + color: Colors.grey, + ) + ], + ), + height13, + _buildTopRightText(ss, cs), + ], + ), + ); + } + + Widget _buildTopRightText(ServerStatus ss, ServerState cs) { + final topRightStr = _getTopRightStr( + cs, + ss.temps.first, + ss.uptime, + ss.failedInfo, + ); + return Text( + topRightStr, + style: textSize12Grey, + textScaleFactor: 1.0, + ); + } + + Widget _buildExplainText(String text) { + return SizedBox( + width: _media.size.height * 0.2, + child: Text( + text, + style: const TextStyle(fontSize: 13), + textAlign: TextAlign.center, + textScaleFactor: 1.0, + ), + ); + } + + String _getTopRightStr( + ServerState cs, + double? temp, + String upTime, + String? failedInfo, + ) { + switch (cs) { + case ServerState.disconnected: + return _s.disconnected; + case ServerState.connected: + final tempStr = temp == null ? '' : '${temp.toStringAsFixed(1)}°C'; + final items = [tempStr, upTime]; + final str = items.where((element) => element.isNotEmpty).join(' | '); + if (str.isEmpty) return _s.serverTabLoading; + return str; + case ServerState.connecting: + return _s.serverTabConnecting; + case ServerState.failed: + if (failedInfo == null) { + return _s.serverTabFailed; + } + if (failedInfo.contains('encypted')) { + return _s.serverTabPlzSave; + } + return failedInfo; + default: + return _s.serverTabUnkown; + } + } + + Widget _buildIOData(String up, String down) { + final statusTextStyle = TextStyle( + fontSize: 13, color: _theme.textTheme.bodyLarge!.color!.withAlpha(177)); + return SizedBox( + width: _media.size.height * 0.23, + child: Column( + children: [ + const SizedBox(height: 5), + Text( + up, + style: statusTextStyle, + textAlign: TextAlign.center, + textScaleFactor: 1.0, + ), + const SizedBox(height: 3), + Text( + down, + style: statusTextStyle, + textAlign: TextAlign.center, + textScaleFactor: 1.0, + ) + ], + ), + ); + } + + Widget _buildPercentCircle(double percent) { + if (percent <= 0) percent = 0.01; + if (percent >= 100) percent = 99.9; + return SizedBox( + width: _media.size.height * 0.23, + child: Stack( + children: [ + Center( + child: CircleChart( + progressColor: primaryColor, + progressNumber: percent, + animationDuration: const Duration(milliseconds: 377), + maxNumber: 100, + width: _media.size.width * 0.22, + height: _media.size.width * 0.22, + ), + ), + Positioned.fill( + child: Center( + child: Text( + '${percent.toStringAsFixed(1)}%', + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 15), + textScaleFactor: 1.0, + ), + ), + ), + ], + ), + ); + } + + @override + bool get wantKeepAlive => true; + + @override + Future afterFirstLayout(BuildContext context) async { + await GetIt.I.allReady(); + await _serverProvider.loadLocalData(); + await _serverProvider.refreshData(); + await doUpdate(context); + if (!Analysis.enabled) { + await Analysis.init(); + } + } +} diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index 3a11cdb7..99060254 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -112,7 +112,6 @@ class _ServerPageState extends State }), itemBuilder: (_, index) => _buildEachServerCard( pro.servers[filtered[index]], - index, ), itemCount: filtered.length, ); @@ -166,7 +165,7 @@ class _ServerPageState extends State ); } - Widget _buildEachServerCard(Server? si, int index) { + Widget _buildEachServerCard(Server? si) { if (si == null) { return placeholder; } diff --git a/lib/view/page/setting.dart b/lib/view/page/setting.dart index c164452b..1613f8b5 100644 --- a/lib/view/page/setting.dart +++ b/lib/view/page/setting.dart @@ -127,6 +127,7 @@ class _SettingPageState extends State { _buildAppColor(), _buildLaunchPage(), _buildCheckUpdate(), + _buildFullScreen(), ]; if (isIOS) { children.add(_buildPushToken()); @@ -660,4 +661,15 @@ class _SettingPageState extends State { }, ); } + + Widget _buildFullScreen() { + return ListTile( + title: Text(_s.fullScreen), + trailing: buildSwitch( + context, + _setting.fullScreen, + func: (_) => _showRestartSnackbar(), + ), + ); + } }