feat: SSH page background (#775)

This commit is contained in:
lollipopkit🏳️‍⚧️
2025-06-05 08:53:00 +08:00
committed by GitHub
parent 176cb7da03
commit 4701757857
10 changed files with 289 additions and 166 deletions

View File

@@ -230,6 +230,9 @@ class SettingStore extends HiveStore {
/// ssh page /// ssh page
late final sshWakeLock = propertyDefault('sshWakeLock', true); late final sshWakeLock = propertyDefault('sshWakeLock', true);
late final sshBgImage = propertyDefault('sshBgImage', '');
late final sshBgOpacity = propertyDefault('sshBgOpacity', 0.3);
late final sshBlurRadius = propertyDefault('sshBlurRadius', 0.0);
/// fmt: https://example.com/{DIST}-{BRIGHT}.png /// fmt: https://example.com/{DIST}-{BRIGHT}.png
late final serverLogoUrl = propertyDefault('serverLogoUrl', ''); late final serverLogoUrl = propertyDefault('serverLogoUrl', '');

View File

@@ -78,10 +78,7 @@ extension _App on _AppSettingsPageState {
}, },
trailing: ValBuilder( trailing: ValBuilder(
listenable: _setting.serverStatusUpdateInterval.listenable(), listenable: _setting.serverStatusUpdateInterval.listenable(),
builder: (val) => Text( builder: (val) => Text('$val ${l10n.second}', style: UIs.text15),
'$val ${l10n.second}',
style: UIs.text15,
),
), ),
); );
} }
@@ -90,17 +87,16 @@ extension _App on _AppSettingsPageState {
return ListTile( return ListTile(
leading: const Icon(Icons.colorize), leading: const Icon(Icons.colorize),
title: Text(libL10n.primaryColorSeed), title: Text(libL10n.primaryColorSeed),
trailing: _setting.colorSeed.listenable().listenVal( trailing: _setting.colorSeed.listenable().listenVal((val) {
(val) {
final c = Color(val); final c = Color(val);
return ClipOval(child: Container(color: c, height: 27, width: 27)); return ClipOval(child: Container(color: c, height: 27, width: 27));
}, }),
),
onTap: () async { onTap: () async {
final ctrl = TextEditingController(text: UIs.primaryColor.toHex); final ctrl = TextEditingController(text: UIs.primaryColor.toHex);
await context.showRoundDialog( await context.showRoundDialog(
title: libL10n.primaryColorSeed, title: libL10n.primaryColorSeed,
child: StatefulBuilder(builder: (context, setState) { child: StatefulBuilder(
builder: (context, setState) {
final children = <Widget>[ final children = <Widget>[
/// Plugin [dynamic_color] is not supported on iOS /// Plugin [dynamic_color] is not supported on iOS
if (!isIOS) if (!isIOS)
@@ -110,21 +106,22 @@ extension _App on _AppSettingsPageState {
prop: _setting.useSystemPrimaryColor, prop: _setting.useSystemPrimaryColor,
callback: (_) => setState(() {}), callback: (_) => setState(() {}),
), ),
) ),
]; ];
if (!_setting.useSystemPrimaryColor.fetch()) { if (!_setting.useSystemPrimaryColor.fetch()) {
children.add(ColorPicker( children.add(
ColorPicker(
color: Color(_setting.colorSeed.fetch()), color: Color(_setting.colorSeed.fetch()),
onColorChanged: (c) => ctrl.text = c.toHex, onColorChanged: (c) => ctrl.text = c.toHex,
)); ),
}
return Column(
mainAxisSize: MainAxisSize.min,
children: children,
); );
}), }
return Column(mainAxisSize: MainAxisSize.min, children: children);
},
),
actions: Btn.ok(onTap: () => _onSaveColor(ctrl.text)).toList, actions: Btn.ok(onTap: () => _onSaveColor(ctrl.text)).toList,
); );
ctrl.dispose();
}, },
); );
} }
@@ -157,10 +154,7 @@ extension _App on _AppSettingsPageState {
_setting.maxRetryCount.put(selected); _setting.maxRetryCount.put(selected);
} }
}, },
trailing: Text( trailing: Text('$val ${l10n.times}', style: UIs.text15),
'$val ${l10n.times}',
style: UIs.text15,
),
), ),
); );
} }
@@ -185,10 +179,7 @@ extension _App on _AppSettingsPageState {
}, },
trailing: ValBuilder( trailing: ValBuilder(
listenable: _setting.themeMode.listenable(), listenable: _setting.themeMode.listenable(),
builder: (val) => Text( builder: (val) => Text(_buildThemeModeStr(val), style: UIs.text15),
_buildThemeModeStr(val),
style: UIs.text15,
),
), ),
); );
} }
@@ -216,10 +207,7 @@ extension _App on _AppSettingsPageState {
title: TipText(l10n.fontSize, l10n.termFontSizeTip), title: TipText(l10n.fontSize, l10n.termFontSizeTip),
trailing: ValBuilder( trailing: ValBuilder(
listenable: _setting.termFontSize.listenable(), listenable: _setting.termFontSize.listenable(),
builder: (val) => Text( builder: (val) => Text(val.toString(), style: UIs.text15),
val.toString(),
style: UIs.text15,
),
), ),
onTap: () => _showFontSizeDialog(_setting.termFontSize), onTap: () => _showFontSizeDialog(_setting.termFontSize),
); );
@@ -244,10 +232,7 @@ extension _App on _AppSettingsPageState {
}, },
trailing: ListenBuilder( trailing: ListenBuilder(
listenable: _setting.locale.listenable(), listenable: _setting.locale.listenable(),
builder: () => Text( builder: () => Text(context.localeNativeName, style: UIs.text15),
context.localeNativeName,
style: UIs.text15,
),
), ),
); );
} }

View File

@@ -100,14 +100,13 @@ extension _Editor on _AppSettingsPageState {
} }
void _showFontSizeDialog(HiveProp<double> property) { void _showFontSizeDialog(HiveProp<double> property) {
final ctrller = TextEditingController(text: property.get().toString());
void onSave() { void onSave() {
context.pop(); context.pop();
final fontSize = double.tryParse(ctrller.text); final fontSize = double.tryParse(_editorTextSizeCtrl.text);
if (fontSize == null) { if (fontSize == null) {
context.showRoundDialog( context.showRoundDialog(
title: libL10n.fail, title: libL10n.fail,
child: Text('Parsed failed: ${ctrller.text}'), child: Text('Parsed failed: ${_editorTextSizeCtrl.text}'),
); );
return; return;
} }
@@ -117,7 +116,7 @@ extension _Editor on _AppSettingsPageState {
context.showRoundDialog( context.showRoundDialog(
title: l10n.fontSize, title: l10n.fontSize,
child: Input( child: Input(
controller: ctrller, controller: _editorTextSizeCtrl,
autoFocus: true, autoFocus: true,
type: TextInputType.number, type: TextInputType.number,
icon: Icons.font_download, icon: Icons.font_download,

View File

@@ -72,7 +72,6 @@ extension _Server on _AppSettingsPageState {
} }
Widget _buildTextScaler() { Widget _buildTextScaler() {
final ctrl = TextEditingController(text: _setting.textFactor.toString());
return ListTile( return ListTile(
// title: Text(l10n.textScaler), // title: Text(l10n.textScaler),
// subtitle: Text(l10n.textScalerTip, style: UIs.textGrey), // subtitle: Text(l10n.textScalerTip, style: UIs.textGrey),
@@ -88,11 +87,11 @@ extension _Server on _AppSettingsPageState {
type: TextInputType.number, type: TextInputType.number,
hint: '1.0', hint: '1.0',
icon: Icons.format_size, icon: Icons.format_size,
controller: ctrl, controller: _textScalerCtrl,
onSubmitted: _onSaveTextScaler, onSubmitted: _onSaveTextScaler,
suggestion: false, suggestion: false,
), ),
actions: Btn.ok(onTap: () => _onSaveTextScaler(ctrl.text)).toList, actions: Btn.ok(onTap: () => _onSaveTextScaler(_textScalerCtrl.text)).toList,
), ),
); );
} }
@@ -273,14 +272,13 @@ extension _Server on _AppSettingsPageState {
title: const Text('Logo URL'), title: const Text('Logo URL'),
trailing: const Icon(Icons.keyboard_arrow_right), trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () { onTap: () {
final ctrl = TextEditingController(text: _setting.serverLogoUrl.fetch());
context.showRoundDialog( context.showRoundDialog(
title: 'Logo URL', title: 'Logo URL',
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Input( Input(
controller: ctrl, controller: _serverLogoCtrl,
autoFocus: true, autoFocus: true,
hint: 'https://example.com/logo.png', hint: 'https://example.com/logo.png',
icon: Icons.link, icon: Icons.link,
@@ -295,7 +293,7 @@ extension _Server on _AppSettingsPageState {
), ),
], ],
), ),
actions: Btn.ok(onTap: () => onSave(ctrl.text)).toList, actions: Btn.ok(onTap: () => onSave(_serverLogoCtrl.text)).toList,
); );
}, },
); );

View File

@@ -39,15 +39,11 @@ extension _SFTP on _AppSettingsPageState {
} }
Widget _buildSftpEditor() { Widget _buildSftpEditor() {
return _setting.sftpEditor.listenable().listenVal( return _setting.sftpEditor.listenable().listenVal((val) {
(val) {
return ListTile( return ListTile(
leading: const Icon(MingCute.edit_fill), leading: const Icon(MingCute.edit_fill),
title: TipText(l10n.editor, l10n.sftpEditorTip), title: TipText(l10n.editor, l10n.sftpEditorTip),
trailing: Text( trailing: Text(val.isEmpty ? l10n.inner : val, style: UIs.text15),
val.isEmpty ? l10n.inner : val,
style: UIs.text15,
),
onTap: () async { onTap: () async {
final ctrl = TextEditingController(text: val); final ctrl = TextEditingController(text: val);
void onSave() { void onSave() {
@@ -69,9 +65,9 @@ extension _SFTP on _AppSettingsPageState {
), ),
actions: Btn.ok(onTap: onSave).toList, actions: Btn.ok(onTap: onSave).toList,
); );
ctrl.dispose();
}, },
); );
}, });
);
} }
} }

View File

@@ -9,6 +9,7 @@ extension _SSH on _AppSettingsPageState {
_buildTermTheme(), _buildTermTheme(),
_buildFont(), _buildFont(),
_buildTermFontSize(), _buildTermFontSize(),
_buildSshBg(),
if (isDesktop) _buildDesktopTerminal(), if (isDesktop) _buildDesktopTerminal(),
_buildSSHVirtualKeyAutoOff(), _buildSSHVirtualKeyAutoOff(),
if (isMobile) _buildSSHVirtKeys(), if (isMobile) _buildSSHVirtKeys(),
@@ -46,10 +47,7 @@ extension _SSH on _AppSettingsPageState {
context.showRoundDialog( context.showRoundDialog(
title: l10n.font, title: l10n.font,
actions: [ actions: [
TextButton( TextButton(onPressed: () async => await _pickFontFile(), child: Text(libL10n.file)),
onPressed: () async => await _pickFontFile(),
child: Text(libL10n.file),
),
TextButton( TextButton(
onPressed: () { onPressed: () {
_setting.fontPath.delete(); _setting.fontPath.delete();
@@ -81,17 +79,31 @@ extension _SSH on _AppSettingsPageState {
RNodes.app.notify(); RNodes.app.notify();
} }
Future<void> _pickBgImage() async {
final path = await Pfs.pickFilePath();
if (path == null) return;
final file = File(path);
final extIndex = path.lastIndexOf('.');
final ext = extIndex != -1 ? path.substring(extIndex) : '';
final newPath = Paths.img.joinPath('ssh_bg$ext');
final destFile = File(newPath);
if (await destFile.exists()) {
await destFile.delete();
}
await file.copy(newPath);
_setting.sshBgImage.put(newPath);
context.pop();
RNodes.app.notify();
}
Widget _buildDesktopTerminal() { Widget _buildDesktopTerminal() {
return _setting.desktopTerminal.listenable().listenVal((val) { return _setting.desktopTerminal.listenable().listenVal((val) {
return ListTile( return ListTile(
leading: const Icon(Icons.terminal), leading: const Icon(Icons.terminal),
title: TipText(l10n.terminal, l10n.desktopTerminalTip), title: TipText(l10n.terminal, l10n.desktopTerminalTip),
trailing: Text( trailing: Text(val, style: UIs.text15, maxLines: 1, overflow: TextOverflow.ellipsis),
val,
style: UIs.text15,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
onTap: () async { onTap: () async {
final ctrl = TextEditingController(text: val); final ctrl = TextEditingController(text: val);
void onSave() { void onSave() {
@@ -99,7 +111,7 @@ extension _SSH on _AppSettingsPageState {
context.pop(); context.pop();
} }
context.showRoundDialog<bool>( await context.showRoundDialog<bool>(
title: libL10n.select, title: libL10n.select,
child: Input( child: Input(
controller: ctrl, controller: ctrl,
@@ -112,6 +124,7 @@ extension _SSH on _AppSettingsPageState {
), ),
actions: Btn.ok(onTap: onSave).toList, actions: Btn.ok(onTap: onSave).toList,
); );
ctrl.dispose();
}, },
); );
}); });
@@ -168,11 +181,114 @@ extension _SSH on _AppSettingsPageState {
// '${l10n.letterCacheTip}\n${l10n.needRestart}', // '${l10n.letterCacheTip}\n${l10n.needRestart}',
// style: UIs.textGrey, // style: UIs.textGrey,
// ), // ),
title: TipText( title: TipText(l10n.letterCache, '${l10n.letterCacheTip}\n${l10n.needRestart}'),
l10n.letterCache,
'${l10n.letterCacheTip}\n${l10n.needRestart}',
),
trailing: StoreSwitch(prop: _setting.letterCache), trailing: StoreSwitch(prop: _setting.letterCache),
); );
} }
Widget _buildSshBg() {
return ExpandTile(
leading: const Icon(MingCute.background_fill),
title: Text(libL10n.background),
children: [_buildSshBgImage(), _buildSshBgOpacity(), _buildSshBlurRadius()],
);
}
Widget _buildSshBgImage() {
return ListTile(
leading: const Icon(Icons.image),
title: Text(libL10n.image),
trailing: _setting.sshBgImage.listenable().listenVal((val) {
final name = val.getFileName();
return Text(name ?? libL10n.empty, style: UIs.text15);
}),
onTap: () {
context.showRoundDialog(
title: libL10n.image,
actions: [
TextButton(onPressed: () async => await _pickBgImage(), child: Text(libL10n.file)),
TextButton(
onPressed: () {
_setting.sshBgImage.delete();
context.pop();
RNodes.app.notify();
},
child: Text(libL10n.clear),
),
],
);
},
);
}
Widget _buildSshBgOpacity() {
void onSave(String s) {
final val = double.tryParse(s);
if (val == null) {
context.showSnackBar(libL10n.fail);
return;
}
_setting.sshBgOpacity.put(val.clamp(0.0, 1.0));
context.pop();
}
return ListTile(
leading: const Icon(Icons.opacity),
title: Text(libL10n.opacity),
trailing: ValBuilder(
listenable: _setting.sshBgOpacity.listenable(),
builder: (val) => Text(val.toString(), style: UIs.text15),
),
onTap: () => context.showRoundDialog(
title: libL10n.opacity,
child: Input(
controller: _sshOpacityCtrl,
autoFocus: true,
type: TextInputType.number,
hint: '0.3',
icon: Icons.opacity,
suggestion: false,
onSubmitted: onSave,
),
actions: Btn.ok(onTap: () => onSave(_sshOpacityCtrl.text)).toList,
),
);
}
Widget _buildSshBlurRadius() {
void onSave(String s) {
final val = double.tryParse(s);
if (val == null) {
context.showSnackBar(libL10n.fail);
return;
}
const minRadius = 0.0;
const maxBlur = 50.0;
final clampedVal = val.clamp(minRadius, maxBlur);
_setting.sshBlurRadius.put(clampedVal);
context.pop();
}
return ListTile(
leading: const Icon(Icons.blur_on),
title: Text(libL10n.blurRadius),
trailing: ValBuilder(
listenable: _setting.sshBlurRadius.listenable(),
builder: (val) => Text(val.toString(), style: UIs.text15),
),
onTap: () => context.showRoundDialog(
title: libL10n.blurRadius,
child: Input(
controller: _sshBlurCtrl,
autoFocus: true,
type: TextInputType.number,
hint: '0',
icon: Icons.blur_on,
suggestion: false,
onSubmitted: onSave,
),
actions: Btn.ok(onTap: () => onSave(_sshBlurCtrl.text)).toList,
),
);
}
} }

View File

@@ -37,10 +37,7 @@ const _kIconSize = 23.0;
class SettingsPage extends StatefulWidget { class SettingsPage extends StatefulWidget {
const SettingsPage({super.key}); const SettingsPage({super.key});
static const route = AppRouteNoArg( static const route = AppRouteNoArg(page: SettingsPage.new, path: '/settings');
page: SettingsPage.new,
path: '/settings',
);
@override @override
State<SettingsPage> createState() => _SettingsPageState(); State<SettingsPage> createState() => _SettingsPageState();
@@ -70,19 +67,15 @@ class _SettingsPageState extends State<SettingsPage> with SingleTickerProviderSt
actions: [ actions: [
Btn.text( Btn.text(
text: 'Logs', text: 'Logs',
onTap: () => DebugPage.route.go( onTap: () =>
context, DebugPage.route.go(context, args: const DebugPageArgs(title: 'Logs(${BuildData.build})')),
args: const DebugPageArgs(title: 'Logs(${BuildData.build})'),
),
), ),
Btn.icon( Btn.icon(
icon: const Icon(Icons.delete), icon: const Icon(Icons.delete),
onTap: () => context.showRoundDialog( onTap: () => context.showRoundDialog(
title: libL10n.attention, title: libL10n.attention,
child: SimpleMarkdown( child: SimpleMarkdown(
data: libL10n.askContinue( data: libL10n.askContinue('${libL10n.delete} **${libL10n.all}** ${libL10n.setting}'),
'${libL10n.delete} **${libL10n.all}** ${libL10n.setting}',
),
), ),
actions: [ actions: [
CountDownBtn( CountDownBtn(
@@ -93,7 +86,7 @@ class _SettingsPageState extends State<SettingsPage> with SingleTickerProviderSt
context.showSnackBar(libL10n.success); context.showSnackBar(libL10n.success);
}, },
afterColor: Colors.red, afterColor: Colors.red,
) ),
], ],
), ),
), ),
@@ -114,6 +107,13 @@ final class AppSettingsPage extends StatefulWidget {
final class _AppSettingsPageState extends State<AppSettingsPage> { final class _AppSettingsPageState extends State<AppSettingsPage> {
final _setting = Stores.setting; final _setting = Stores.setting;
late final _sshOpacityCtrl = TextEditingController(text: _setting.sshBgOpacity.fetch().toString());
late final _sshBlurCtrl = TextEditingController(text: _setting.sshBlurRadius.fetch().toString());
late final _textScalerCtrl = TextEditingController(text: _setting.textFactor.toString());
late final _editorTextSizeCtrl = TextEditingController(text: _setting.editorFontSize.get().toString());
late final _serverLogoCtrl = TextEditingController(text: _setting.serverLogoUrl.fetch());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiList( return MultiList(
@@ -121,12 +121,7 @@ final class _AppSettingsPageState extends State<AppSettingsPage> {
[const CenterGreyTitle('App'), _buildApp()], [const CenterGreyTitle('App'), _buildApp()],
[CenterGreyTitle(l10n.server), _buildServer()], [CenterGreyTitle(l10n.server), _buildServer()],
[const CenterGreyTitle('SSH'), _buildSSH(), const CenterGreyTitle('SFTP'), _buildSFTP()], [const CenterGreyTitle('SSH'), _buildSSH(), const CenterGreyTitle('SFTP'), _buildSFTP()],
[ [CenterGreyTitle(l10n.container), _buildContainer(), CenterGreyTitle(l10n.editor), _buildEditor()],
CenterGreyTitle(l10n.container),
_buildContainer(),
CenterGreyTitle(l10n.editor),
_buildEditor(),
],
/// Fullscreen Mode is designed for old mobile phone which can be /// Fullscreen Mode is designed for old mobile phone which can be
/// used as a status screen. /// used as a status screen.
@@ -140,8 +135,7 @@ enum SettingsTabs {
app, app,
privateKey, privateKey,
backup, backup,
about, about;
;
String get i18n => switch (this) { String get i18n => switch (this) {
SettingsTabs.app => libL10n.app, SettingsTabs.app => libL10n.app,

View File

@@ -1,5 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'dart:ui';
import 'package:dartssh2/dartssh2.dart'; import 'package:dartssh2/dartssh2.dart';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
@@ -144,6 +146,8 @@ class SSHPageState extends State<SSHPage> with AutomaticKeepAliveClientMixin, Af
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
final bgImage = Stores.setting.sshBgImage.fetch();
final hasBg = bgImage.isNotEmpty;
Widget child = PopScope( Widget child = PopScope(
canPop: false, canPop: false,
onPopInvokedWithResult: (didPop, _) { onPopInvokedWithResult: (didPop, _) {
@@ -156,7 +160,7 @@ class SSHPageState extends State<SSHPage> with AutomaticKeepAliveClientMixin, Af
actions: [_buildCopyBtn, _buildKillBtn], actions: [_buildCopyBtn, _buildKillBtn],
centerTitle: false, centerTitle: false,
), ),
backgroundColor: _terminalTheme.background, backgroundColor: hasBg ? Colors.transparent : _terminalTheme.background,
body: _buildBody(), body: _buildBody(),
bottomNavigationBar: isDesktop ? null : _buildBottom(), bottomNavigationBar: isDesktop ? null : _buildBottom(),
), ),
@@ -173,14 +177,38 @@ class SSHPageState extends State<SSHPage> with AutomaticKeepAliveClientMixin, Af
Widget _buildBody() { Widget _buildBody() {
final letterCache = Stores.setting.letterCache.fetch(); final letterCache = Stores.setting.letterCache.fetch();
return SizedBox( final bgImage = Stores.setting.sshBgImage.fetch();
height: _media.size.height - _virtKeysHeight - _media.padding.bottom - _media.padding.top, final opacity = Stores.setting.sshBgOpacity.fetch();
child: Padding( final blur = Stores.setting.sshBlurRadius.fetch();
padding: EdgeInsets.only( final file = File(bgImage);
// top: widget.args.notFromTab ? CustomAppBar.sysStatusBarHeight : 0, final hasBg = bgImage.isNotEmpty && file.existsSync();
left: _horizonPadding, final theme = hasBg ? _terminalTheme.copyWith(background: Colors.transparent) : _terminalTheme;
right: _horizonPadding, final children = <Widget>[];
if (hasBg) {
children.add(
Positioned.fill(
child: Image.file(file, fit: BoxFit.cover, errorBuilder: (_, __, ___) => const SizedBox()),
), ),
);
if (blur > 0) {
children.add(
Positioned.fill(
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur),
child: const SizedBox(),
),
),
);
}
children.add(
Positioned.fill(
child: ColoredBox(color: _terminalTheme.background.withValues(alpha: opacity)),
),
);
}
children.add(
Padding(
padding: EdgeInsets.only(left: _horizonPadding, right: _horizonPadding),
child: TerminalView( child: TerminalView(
_terminal, _terminal,
key: _termKey, key: _termKey,
@@ -188,7 +216,8 @@ class SSHPageState extends State<SSHPage> with AutomaticKeepAliveClientMixin, Af
keyboardType: TextInputType.text, keyboardType: TextInputType.text,
enableSuggestions: letterCache, enableSuggestions: letterCache,
textStyle: _terminalStyle, textStyle: _terminalStyle,
theme: _terminalTheme, backgroundOpacity: 0,
theme: theme,
deleteDetection: isMobile, deleteDetection: isMobile,
autofocus: false, autofocus: false,
keyboardAppearance: _isDark ? Brightness.dark : Brightness.light, keyboardAppearance: _isDark ? Brightness.dark : Brightness.light,
@@ -199,17 +228,21 @@ class SSHPageState extends State<SSHPage> with AutomaticKeepAliveClientMixin, Af
), ),
), ),
); );
return SizedBox(
height: _media.size.height - _virtKeysHeight - _media.padding.bottom - _media.padding.top,
child: Stack(children: children),
);
} }
Widget _buildBottom() { Widget _buildBottom() {
return SafeArea( return AnimatedPadding(
child: AnimatedPadding(
padding: _media.viewInsets, padding: _media.viewInsets,
duration: const Duration(milliseconds: 23), duration: const Duration(milliseconds: 23),
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
child: Container( child: Container(
color: _terminalTheme.background, color: _terminalTheme.background,
height: _virtKeysHeight, height: _virtKeysHeight + _media.padding.bottom,
child: ChangeNotifierProvider( child: ChangeNotifierProvider(
create: (_) => _keyboard, create: (_) => _keyboard,
builder: (_, __) => Consumer<VirtKeyProvider>( builder: (_, __) => Consumer<VirtKeyProvider>(
@@ -219,7 +252,6 @@ class SSHPageState extends State<SSHPage> with AutomaticKeepAliveClientMixin, Af
), ),
), ),
), ),
),
); );
} }

View File

@@ -497,8 +497,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "v1.0.315" ref: "v1.0.316"
resolved-ref: "8593608e8f78ea53f2b1fdad5d5395392870c0d3" resolved-ref: "93da3ff5e042f9f0d29ace7b1ffeff5716fe2c81"
url: "https://github.com/lppcg/fl_lib" url: "https://github.com/lppcg/fl_lib"
source: git source: git
version: "0.0.1" version: "0.0.1"
@@ -1774,8 +1774,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "v1.0.586" ref: "v1.0.587"
resolved-ref: b8c73bec722055f24c5724cd2f2297859e95b6af resolved-ref: "0f5027286380e3829ad2696d69d5a1ecf68eb875"
url: "https://github.com/lollipopkit/xterm.dart" url: "https://github.com/lollipopkit/xterm.dart"
source: git source: git
version: "4.0.0" version: "4.0.0"

View File

@@ -46,7 +46,7 @@ dependencies:
xterm: xterm:
git: git:
url: https://github.com/lollipopkit/xterm.dart url: https://github.com/lollipopkit/xterm.dart
ref: v1.0.586 ref: v1.0.587
computer: computer:
git: git:
url: https://github.com/lollipopkit/dart_computer url: https://github.com/lollipopkit/dart_computer
@@ -62,7 +62,7 @@ dependencies:
fl_lib: fl_lib:
git: git:
url: https://github.com/lppcg/fl_lib url: https://github.com/lppcg/fl_lib
ref: v1.0.315 ref: v1.0.316
dependency_overrides: dependency_overrides:
# webdav_client_plus: # webdav_client_plus: