new: fullscreen mode

This commit is contained in:
lollipopkit
2024-04-10 21:23:00 +08:00
parent 11956aee00
commit 5836275a3f
15 changed files with 198 additions and 58 deletions

View File

@@ -75,7 +75,13 @@ class MyApp extends StatelessWidget {
themeMode: themeMode,
theme: light,
darkTheme: tMode < 3 ? dark : _getAmoledTheme(dark),
home: const HomePage(),
home: Stores.setting.fullScreen.fetch()
? OrientationBuilder(
builder: (_, ori) {
return HomePage(landscape: ori == Orientation.landscape);
},
)
: const HomePage(),
);
}
}

View File

@@ -111,15 +111,15 @@ class SettingStore extends PersistentStore {
Defaults.editorDarkTheme,
);
// late final fullScreen = property(
// 'fullScreen',
// false,
// );
late final fullScreen = property(
'fullScreen',
false,
);
// late final fullScreenJitter = property(
// 'fullScreenJitter',
// true,
// );
late final fullScreenJitter = property(
'fullScreenJitter',
true,
);
// late final fullScreenRotateQuarter = property(
// 'fullScreenRotateQuarter',

View File

@@ -109,6 +109,7 @@
"fullScreen": "Vollbildmodus",
"fullScreenJitter": "Jitter im Vollbildmodus",
"fullScreenJitterHelp": "Einbrennen des Bildschirms verhindern",
"fullScreenTip": "Soll der Vollbildmodus aktiviert werden, wenn das Gerät in den Quermodus gedreht wird? Diese Option gilt nur für die Server-Registerkarte.",
"getPushTokenFailed": "Push-Token kann nicht abgerufen werden",
"gettingToken": "Getting token...",
"goBackQ": "Zurückkommen?",

View File

@@ -109,6 +109,7 @@
"fullScreen": "Full screen mode",
"fullScreenJitter": "Full screen jitter",
"fullScreenJitterHelp": "To avoid screen burn-in",
"fullScreenTip": "Should full-screen mode be enabled when the device is rotated to landscape mode? This option only applies to the server tab.",
"getPushTokenFailed": "Can't fetch push token",
"gettingToken": "Getting token...",
"goBackQ": "Go back?",

View File

@@ -109,6 +109,7 @@
"fullScreen": "Modo pantalla completa",
"fullScreenJitter": "Temblores en modo pantalla completa",
"fullScreenJitterHelp": "Prevención de quemaduras de pantalla",
"fullScreenTip": "¿Debe habilitarse el modo de pantalla completa cuando el dispositivo se rote al modo horizontal? Esta opción solo se aplica a la pestaña del servidor.",
"getPushTokenFailed": "No se pudo obtener el token de notificación",
"gettingToken": "Obteniendo Token...",
"goBackQ": "¿Regresar?",

View File

@@ -109,6 +109,7 @@
"fullScreen": "Mode plein écran",
"fullScreenJitter": "Tremblement plein écran",
"fullScreenJitterHelp": "Pour éviter la rémanence d'écran",
"fullScreenTip": "Le mode plein écran doit-il être activé lorsque l'appareil est basculé en mode paysage ? Cette option s'applique uniquement à l'onglet du serveur.",
"getPushTokenFailed": "Impossible de récupérer le jeton d'identification",
"gettingToken": "Récupération du jeton...",
"goBackQ": "Revenir en arrière ?",

View File

@@ -109,6 +109,7 @@
"fullScreen": "Mode Layar Penuh",
"fullScreenJitter": "Jitter layar penuh",
"fullScreenJitterHelp": "Untuk menghindari pembakaran layar",
"fullScreenTip": "Apakah mode layar penuh diaktifkan ketika perangkat diputar ke modus lanskap? Opsi ini hanya berlaku untuk tab server.",
"getPushTokenFailed": "Tidak bisa mengambil token dorong",
"gettingToken": "Mendapatkan token ...",
"goBackQ": "Datang kembali?",

View File

@@ -109,6 +109,7 @@
"fullScreen": "フルスクリーンモード",
"fullScreenJitter": "フルスクリーンモードのジッター",
"fullScreenJitterHelp": "焼き付き防止",
"fullScreenTip": "デバイスが横向きに回転したときにフルスクリーンモードを有効にしますか?このオプションはサーバータブにのみ適用されます。",
"getPushTokenFailed": "プッシュトークンを取得できませんでした",
"gettingToken": "トークンを取得しています...",
"goBackQ": "戻りますか?",

View File

@@ -109,6 +109,7 @@
"fullScreen": "Modo tela cheia",
"fullScreenJitter": "Tremulação em tela cheia",
"fullScreenJitterHelp": "Prevenir burn-in de tela",
"fullScreenTip": "Deve ser ativado o modo de tela cheia quando o dispositivo é girado para o modo paisagem? Esta opção aplica-se apenas à aba do servidor.",
"getPushTokenFailed": "Não foi possível obter token de notificação",
"gettingToken": "Obtendo Token...",
"goBackQ": "Voltar?",

View File

@@ -109,6 +109,7 @@
"fullScreen": "полноэкранный режим",
"fullScreenJitter": "дрожание в полноэкранном режиме",
"fullScreenJitterHelp": "предотвращение выгорания экрана",
"fullScreenTip": "Следует ли включить полноэкранный режим, когда устройство поворачивается в альбомный режим? Эта опция применяется только к вкладке сервера.",
"getPushTokenFailed": "Не удалось получить токен уведомлений",
"gettingToken": "Получение токена...",
"goBackQ": "Вернуться?",

View File

@@ -109,6 +109,7 @@
"fullScreen": "全屏模式",
"fullScreenJitter": "全屏模式抖动",
"fullScreenJitterHelp": "防止烧屏",
"fullScreenTip": "当设备旋转为横屏时是否开启全屏模式。此选项仅作用于服务器Tab页。",
"getPushTokenFailed": "未能获取到推送token",
"gettingToken": "正在获取Token...",
"goBackQ": "返回?",

View File

@@ -109,6 +109,7 @@
"fullScreen": "全屏模式",
"fullScreenJitter": "全屏模式抖動",
"fullScreenJitterHelp": "防止燒屏",
"fullScreenTip": "當設備旋轉為橫屏時,是否開啟全屏模式?此選項僅適用於伺服器選項卡。",
"getPushTokenFailed": "未能獲取到推送token",
"gettingToken": "正在獲取Token...",
"goBackQ": "返回?",

View File

@@ -29,13 +29,14 @@ import 'package:toolbox/data/res/ui.dart';
import 'package:toolbox/data/res/url.dart';
import 'package:toolbox/view/widget/appbar.dart';
import 'package:toolbox/view/widget/cardx.dart';
import 'package:toolbox/view/widget/count_down_btn.dart';
import 'package:toolbox/view/widget/markdown.dart';
part 'appbar.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
final bool landscape;
const HomePage({super.key, this.landscape = false});
@override
State<HomePage> createState() => _HomePageState();
@@ -119,18 +120,20 @@ class _HomePageState extends State<HomePage>
return Scaffold(
drawer: _buildDrawer(),
appBar: _AppBar(
selectIndex: _selectIndex,
centerTitle: false,
title: const Text(BuildData.name),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.developer_mode, size: 23),
tooltip: l10n.debug,
onPressed: () => AppRoute.debug().go(context),
),
],
),
appBar: widget.landscape
? null
: _AppBar(
selectIndex: _selectIndex,
centerTitle: false,
title: const Text(BuildData.name),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.developer_mode, size: 23),
tooltip: l10n.debug,
onPressed: () => AppRoute.debug().go(context),
),
],
),
body: PageView.builder(
controller: _pageController,
itemCount: AppTab.values.length,
@@ -141,10 +144,12 @@ class _HomePageState extends State<HomePage>
}
},
),
bottomNavigationBar: ListenableBuilder(
listenable: _selectIndex,
builder: (_, __) => _buildBottomBar(),
),
bottomNavigationBar: widget.landscape
? null
: ListenableBuilder(
listenable: _selectIndex,
builder: (_, __) => _buildBottomBar(),
),
);
}
@@ -321,7 +326,6 @@ ${GithubIds.participants.map((e) => '[$e](${e.url})').join(' ')}
BioAuth.go();
_reqNotiPerm();
context.showRoundDialog(child: CountDownBtn(onTap: context.pop));
if (Stores.setting.autoCheckAppUpdate.fetch()) {
doUpdate(context);

View File

@@ -1,3 +1,6 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:after_layout/after_layout.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
@@ -5,6 +8,7 @@ import 'package:provider/provider.dart';
import 'package:toolbox/core/extension/context/common.dart';
import 'package:toolbox/core/extension/context/dialog.dart';
import 'package:toolbox/core/extension/context/locale.dart';
import 'package:toolbox/core/extension/listx.dart';
import 'package:toolbox/core/extension/media_queryx.dart';
import 'package:toolbox/core/extension/numx.dart';
import 'package:toolbox/core/extension/ssh_client.dart';
@@ -38,17 +42,36 @@ class _ServerPageState extends State<ServerPage>
late MediaQueryData _media;
late double _textFactorDouble;
double _offset = 1;
late TextScaler _textFactor;
final _cardsStatus = <String, _CardNotifier>{};
Timer? _timer;
String? _tag;
bool _useDoubleColumn = false;
@override
void initState() {
super.initState();
if (!Stores.setting.fullScreenJitter.fetch()) return;
_timer = Timer.periodic(const Duration(seconds: 30), (_) {
if (mounted) {
_updateOffset();
setState(() {});
} else {
_timer?.cancel();
}
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_media = MediaQuery.of(context);
_updateOffset();
_updateTextScaler();
_useDoubleColumn = _media.useDoubleColumn &&
Stores.setting.doubleColumnServersPage.fetch();
}
@@ -56,13 +79,22 @@ class _ServerPageState extends State<ServerPage>
@override
Widget build(BuildContext context) {
super.build(context);
return OrientationBuilder(builder: (_, orientation) {
if (orientation == Orientation.landscape) {
final useFullScreen = Stores.setting.fullScreen.fetch();
if (useFullScreen) return _buildLandscape();
}
return _buildPortrait();
});
}
Widget _buildPortrait() {
return Scaffold(
appBar: _buildTagsSwitcher(Pros.server),
body: ListenableBuilder(
listenable: Stores.setting.textFactor.listenable(),
builder: (_, __) {
_textFactorDouble = Stores.setting.textFactor.fetch();
_textFactor = TextScaler.linear(_textFactorDouble);
_updateTextScaler();
return _buildBody();
},
),
@@ -74,6 +106,75 @@ class _ServerPageState extends State<ServerPage>
);
}
Widget _buildLandscape() {
final offset = Offset(_offset, _offset);
return Padding(
// Avoid display cutout
padding: EdgeInsets.all(_offset.abs()),
child: Transform.translate(
offset: offset,
child: Stack(
children: [
_buildLandscapeBody(),
Positioned(
top: 0,
left: 0,
child: IconButton(
onPressed: () => AppRoute.settings().go(context),
icon: const Icon(Icons.settings, color: Colors.grey),
),
),
],
),
),
);
}
Widget _buildLandscapeBody() {
return Consumer<ServerProvider>(builder: (_, pro, __) {
if (pro.serverOrder.isEmpty) {
return Center(
child: Text(
l10n.serverTabEmpty,
textAlign: TextAlign.center,
),
);
}
return PageView.builder(
itemCount: pro.serverOrder.length,
itemBuilder: (_, idx) {
final id = pro.serverOrder[idx];
final srv = pro.pick(id: id);
if (srv == null) return UIs.placeholder;
final title = _buildServerCardTitle(srv);
final List<Widget> children = [
title,
..._buildNormalCard(srv.status, srv.spi).joinWith(SizedBox(
height: _media.size.height / 10,
))
];
return Padding(
padding: _media.padding,
child: ListenableBuilder(
listenable: _getCardNoti(id),
builder: (_, __) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
);
},
),
);
},
);
});
}
Widget _buildBody() {
final child = Consumer<ServerProvider>(
builder: (_, pro, __) {
@@ -560,6 +661,19 @@ class _ServerPageState extends State<ServerPage>
id,
() => _CardNotifier(const _CardStatus()),
);
void _updateOffset() {
if (!Stores.setting.fullScreenJitter.fetch()) return;
final x = _media.size.height * 0.03;
final r = math.Random().nextDouble();
final n = math.Random().nextBool() ? 1 : -1;
_offset = x * r * n;
}
void _updateTextScaler() {
_textFactorDouble = Stores.setting.textFactor.fetch();
_textFactor = TextScaler.linear(_textFactorDouble);
}
}
typedef _CardNotifier = ValueNotifier<_CardStatus>;

View File

@@ -91,9 +91,9 @@ class _SettingPageState extends State<SettingPage> {
_buildEditor(),
/// Fullscreen Mode is designed for old mobile phone which can be
/// used as a status screen, so it's only available on mobile phone.
// if (!isDesktop) _buildTitle(l10n.fullScreen),
// if (!isDesktop) _buildFullScreen(),
/// used as a status screen.
if (isMobile) _buildTitle(l10n.fullScreen),
if (isMobile) _buildFullScreen(),
const SizedBox(height: 37),
],
),
@@ -131,15 +131,15 @@ class _SettingPageState extends State<SettingPage> {
);
}
// Widget _buildFullScreen() {
// return Column(
// children: [
// _buildFullScreenSwitch(),
// _buildFullScreenJitter(),
// _buildFulScreenRotateQuarter(),
// ].map((e) => CardX(child: e)).toList(),
// );
// }
Widget _buildFullScreen() {
return Column(
children: [
_buildFullScreenSwitch(),
_buildFullScreenJitter(),
// _buildFulScreenRotateQuarter(),
].map((e) => CardX(child: e)).toList(),
);
}
Widget _buildServer() {
return Column(
@@ -599,23 +599,29 @@ class _SettingPageState extends State<SettingPage> {
);
}
// Widget _buildFullScreenSwitch() {
// return ListTile(
// title: Text(l10n.fullScreen),
// trailing: StoreSwitch(
// prop: _setting.fullScreen,
// callback: (_) => RebuildNodes.app.rebuild(),
// ),
// );
// }
Widget _buildFullScreenSwitch() {
return ListTile(
title: Text(l10n.fullScreen),
subtitle: Text(l10n.fullScreenTip, style: UIs.textGrey),
trailing: StoreSwitch(
prop: _setting.fullScreen,
callback: (_) => RebuildNodes.app.rebuild(),
),
);
}
// Widget _buildFullScreenJitter() {
// return ListTile(
// title: Text(l10n.fullScreenJitter),
// subtitle: Text(l10n.fullScreenJitterHelp, style: UIs.textGrey),
// trailing: StoreSwitch(prop: _setting.fullScreenJitter),
// );
// }
Widget _buildFullScreenJitter() {
return ListTile(
title: Text(l10n.fullScreenJitter),
subtitle: Text(l10n.fullScreenJitterHelp, style: UIs.textGrey),
trailing: StoreSwitch(
prop: _setting.fullScreenJitter,
callback: (_) {
context.showSnackBar(l10n.needRestart);
},
),
);
}
// Widget _buildFulScreenRotateQuarter() {
// final degrees = List.generate(4, (idx) => '${idx * 90}°').toList();