feat: SSH page background (#775)
This commit is contained in:
@@ -78,10 +78,7 @@ extension _App on _AppSettingsPageState {
|
||||
},
|
||||
trailing: ValBuilder(
|
||||
listenable: _setting.serverStatusUpdateInterval.listenable(),
|
||||
builder: (val) => Text(
|
||||
'$val ${l10n.second}',
|
||||
style: UIs.text15,
|
||||
),
|
||||
builder: (val) => Text('$val ${l10n.second}', style: UIs.text15),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -90,41 +87,41 @@ extension _App on _AppSettingsPageState {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.colorize),
|
||||
title: Text(libL10n.primaryColorSeed),
|
||||
trailing: _setting.colorSeed.listenable().listenVal(
|
||||
(val) {
|
||||
final c = Color(val);
|
||||
return ClipOval(child: Container(color: c, height: 27, width: 27));
|
||||
},
|
||||
),
|
||||
trailing: _setting.colorSeed.listenable().listenVal((val) {
|
||||
final c = Color(val);
|
||||
return ClipOval(child: Container(color: c, height: 27, width: 27));
|
||||
}),
|
||||
onTap: () async {
|
||||
final ctrl = TextEditingController(text: UIs.primaryColor.toHex);
|
||||
await context.showRoundDialog(
|
||||
title: libL10n.primaryColorSeed,
|
||||
child: StatefulBuilder(builder: (context, setState) {
|
||||
final children = <Widget>[
|
||||
/// Plugin [dynamic_color] is not supported on iOS
|
||||
if (!isIOS)
|
||||
ListTile(
|
||||
title: Text(l10n.followSystem),
|
||||
trailing: StoreSwitch(
|
||||
prop: _setting.useSystemPrimaryColor,
|
||||
callback: (_) => setState(() {}),
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
final children = <Widget>[
|
||||
/// Plugin [dynamic_color] is not supported on iOS
|
||||
if (!isIOS)
|
||||
ListTile(
|
||||
title: Text(l10n.followSystem),
|
||||
trailing: StoreSwitch(
|
||||
prop: _setting.useSystemPrimaryColor,
|
||||
callback: (_) => setState(() {}),
|
||||
),
|
||||
),
|
||||
)
|
||||
];
|
||||
if (!_setting.useSystemPrimaryColor.fetch()) {
|
||||
children.add(ColorPicker(
|
||||
color: Color(_setting.colorSeed.fetch()),
|
||||
onColorChanged: (c) => ctrl.text = c.toHex,
|
||||
));
|
||||
}
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
}),
|
||||
];
|
||||
if (!_setting.useSystemPrimaryColor.fetch()) {
|
||||
children.add(
|
||||
ColorPicker(
|
||||
color: Color(_setting.colorSeed.fetch()),
|
||||
onColorChanged: (c) => ctrl.text = c.toHex,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Column(mainAxisSize: MainAxisSize.min, children: children);
|
||||
},
|
||||
),
|
||||
actions: Btn.ok(onTap: () => _onSaveColor(ctrl.text)).toList,
|
||||
);
|
||||
ctrl.dispose();
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -157,10 +154,7 @@ extension _App on _AppSettingsPageState {
|
||||
_setting.maxRetryCount.put(selected);
|
||||
}
|
||||
},
|
||||
trailing: Text(
|
||||
'$val ${l10n.times}',
|
||||
style: UIs.text15,
|
||||
),
|
||||
trailing: Text('$val ${l10n.times}', style: UIs.text15),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -185,10 +179,7 @@ extension _App on _AppSettingsPageState {
|
||||
},
|
||||
trailing: ValBuilder(
|
||||
listenable: _setting.themeMode.listenable(),
|
||||
builder: (val) => Text(
|
||||
_buildThemeModeStr(val),
|
||||
style: UIs.text15,
|
||||
),
|
||||
builder: (val) => Text(_buildThemeModeStr(val), style: UIs.text15),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -216,10 +207,7 @@ extension _App on _AppSettingsPageState {
|
||||
title: TipText(l10n.fontSize, l10n.termFontSizeTip),
|
||||
trailing: ValBuilder(
|
||||
listenable: _setting.termFontSize.listenable(),
|
||||
builder: (val) => Text(
|
||||
val.toString(),
|
||||
style: UIs.text15,
|
||||
),
|
||||
builder: (val) => Text(val.toString(), style: UIs.text15),
|
||||
),
|
||||
onTap: () => _showFontSizeDialog(_setting.termFontSize),
|
||||
);
|
||||
@@ -244,10 +232,7 @@ extension _App on _AppSettingsPageState {
|
||||
},
|
||||
trailing: ListenBuilder(
|
||||
listenable: _setting.locale.listenable(),
|
||||
builder: () => Text(
|
||||
context.localeNativeName,
|
||||
style: UIs.text15,
|
||||
),
|
||||
builder: () => Text(context.localeNativeName, style: UIs.text15),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -100,14 +100,13 @@ extension _Editor on _AppSettingsPageState {
|
||||
}
|
||||
|
||||
void _showFontSizeDialog(HiveProp<double> property) {
|
||||
final ctrller = TextEditingController(text: property.get().toString());
|
||||
void onSave() {
|
||||
context.pop();
|
||||
final fontSize = double.tryParse(ctrller.text);
|
||||
final fontSize = double.tryParse(_editorTextSizeCtrl.text);
|
||||
if (fontSize == null) {
|
||||
context.showRoundDialog(
|
||||
title: libL10n.fail,
|
||||
child: Text('Parsed failed: ${ctrller.text}'),
|
||||
child: Text('Parsed failed: ${_editorTextSizeCtrl.text}'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -117,7 +116,7 @@ extension _Editor on _AppSettingsPageState {
|
||||
context.showRoundDialog(
|
||||
title: l10n.fontSize,
|
||||
child: Input(
|
||||
controller: ctrller,
|
||||
controller: _editorTextSizeCtrl,
|
||||
autoFocus: true,
|
||||
type: TextInputType.number,
|
||||
icon: Icons.font_download,
|
||||
|
||||
@@ -72,7 +72,6 @@ extension _Server on _AppSettingsPageState {
|
||||
}
|
||||
|
||||
Widget _buildTextScaler() {
|
||||
final ctrl = TextEditingController(text: _setting.textFactor.toString());
|
||||
return ListTile(
|
||||
// title: Text(l10n.textScaler),
|
||||
// subtitle: Text(l10n.textScalerTip, style: UIs.textGrey),
|
||||
@@ -88,11 +87,11 @@ extension _Server on _AppSettingsPageState {
|
||||
type: TextInputType.number,
|
||||
hint: '1.0',
|
||||
icon: Icons.format_size,
|
||||
controller: ctrl,
|
||||
controller: _textScalerCtrl,
|
||||
onSubmitted: _onSaveTextScaler,
|
||||
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'),
|
||||
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||
onTap: () {
|
||||
final ctrl = TextEditingController(text: _setting.serverLogoUrl.fetch());
|
||||
context.showRoundDialog(
|
||||
title: 'Logo URL',
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Input(
|
||||
controller: ctrl,
|
||||
controller: _serverLogoCtrl,
|
||||
autoFocus: true,
|
||||
hint: 'https://example.com/logo.png',
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -39,39 +39,35 @@ extension _SFTP on _AppSettingsPageState {
|
||||
}
|
||||
|
||||
Widget _buildSftpEditor() {
|
||||
return _setting.sftpEditor.listenable().listenVal(
|
||||
(val) {
|
||||
return ListTile(
|
||||
leading: const Icon(MingCute.edit_fill),
|
||||
title: TipText(l10n.editor, l10n.sftpEditorTip),
|
||||
trailing: Text(
|
||||
val.isEmpty ? l10n.inner : val,
|
||||
style: UIs.text15,
|
||||
),
|
||||
onTap: () async {
|
||||
final ctrl = TextEditingController(text: val);
|
||||
void onSave() {
|
||||
final s = ctrl.text.trim();
|
||||
_setting.sftpEditor.put(s);
|
||||
context.pop();
|
||||
}
|
||||
return _setting.sftpEditor.listenable().listenVal((val) {
|
||||
return ListTile(
|
||||
leading: const Icon(MingCute.edit_fill),
|
||||
title: TipText(l10n.editor, l10n.sftpEditorTip),
|
||||
trailing: Text(val.isEmpty ? l10n.inner : val, style: UIs.text15),
|
||||
onTap: () async {
|
||||
final ctrl = TextEditingController(text: val);
|
||||
void onSave() {
|
||||
final s = ctrl.text.trim();
|
||||
_setting.sftpEditor.put(s);
|
||||
context.pop();
|
||||
}
|
||||
|
||||
await context.showRoundDialog<bool>(
|
||||
title: libL10n.select,
|
||||
child: Input(
|
||||
controller: ctrl,
|
||||
autoFocus: true,
|
||||
label: l10n.editor,
|
||||
hint: '\$EDITOR / vim / nano ...',
|
||||
icon: Icons.edit,
|
||||
suggestion: false,
|
||||
onSubmitted: (_) => onSave(),
|
||||
),
|
||||
actions: Btn.ok(onTap: onSave).toList,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
await context.showRoundDialog<bool>(
|
||||
title: libL10n.select,
|
||||
child: Input(
|
||||
controller: ctrl,
|
||||
autoFocus: true,
|
||||
label: l10n.editor,
|
||||
hint: '\$EDITOR / vim / nano ...',
|
||||
icon: Icons.edit,
|
||||
suggestion: false,
|
||||
onSubmitted: (_) => onSave(),
|
||||
),
|
||||
actions: Btn.ok(onTap: onSave).toList,
|
||||
);
|
||||
ctrl.dispose();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ extension _SSH on _AppSettingsPageState {
|
||||
_buildTermTheme(),
|
||||
_buildFont(),
|
||||
_buildTermFontSize(),
|
||||
_buildSshBg(),
|
||||
if (isDesktop) _buildDesktopTerminal(),
|
||||
_buildSSHVirtualKeyAutoOff(),
|
||||
if (isMobile) _buildSSHVirtKeys(),
|
||||
@@ -46,10 +47,7 @@ extension _SSH on _AppSettingsPageState {
|
||||
context.showRoundDialog(
|
||||
title: l10n.font,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async => await _pickFontFile(),
|
||||
child: Text(libL10n.file),
|
||||
),
|
||||
TextButton(onPressed: () async => await _pickFontFile(), child: Text(libL10n.file)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_setting.fontPath.delete();
|
||||
@@ -81,17 +79,31 @@ extension _SSH on _AppSettingsPageState {
|
||||
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() {
|
||||
return _setting.desktopTerminal.listenable().listenVal((val) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.terminal),
|
||||
title: TipText(l10n.terminal, l10n.desktopTerminalTip),
|
||||
trailing: Text(
|
||||
val,
|
||||
style: UIs.text15,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: Text(val, style: UIs.text15, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
onTap: () async {
|
||||
final ctrl = TextEditingController(text: val);
|
||||
void onSave() {
|
||||
@@ -99,7 +111,7 @@ extension _SSH on _AppSettingsPageState {
|
||||
context.pop();
|
||||
}
|
||||
|
||||
context.showRoundDialog<bool>(
|
||||
await context.showRoundDialog<bool>(
|
||||
title: libL10n.select,
|
||||
child: Input(
|
||||
controller: ctrl,
|
||||
@@ -112,6 +124,7 @@ extension _SSH on _AppSettingsPageState {
|
||||
),
|
||||
actions: Btn.ok(onTap: onSave).toList,
|
||||
);
|
||||
ctrl.dispose();
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -168,11 +181,114 @@ extension _SSH on _AppSettingsPageState {
|
||||
// '${l10n.letterCacheTip}\n${l10n.needRestart}',
|
||||
// style: UIs.textGrey,
|
||||
// ),
|
||||
title: TipText(
|
||||
l10n.letterCache,
|
||||
'${l10n.letterCacheTip}\n${l10n.needRestart}',
|
||||
),
|
||||
title: TipText(l10n.letterCache, '${l10n.letterCacheTip}\n${l10n.needRestart}'),
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user