187 lines
6.3 KiB
Dart
187 lines
6.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:stacked/stacked.dart';
|
|
|
|
import '../../../about/internal/application/ServerProfile.dart';
|
|
import '../../../about/internal/application/TextType.dart';
|
|
import '../../../designs/input/InputFields.dart';
|
|
import '../../../designs/text/Text.dart';
|
|
import '../../../utils/Colors.dart';
|
|
import '../../../utils/Validators.dart';
|
|
import 'ConnectServerEditor.dart';
|
|
import 'ServerEditor.dart';
|
|
import 'ViewServerEditor.dart';
|
|
|
|
class ServerEditorState extends State<ServerEditor>
|
|
implements ConnectServerEditor {
|
|
ViewServerEditor? _model;
|
|
|
|
final _formKey = GlobalKey<FormState>();
|
|
late final ServerProfile _server;
|
|
bool _showPassword = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_server = widget.server?.copy() ?? ServerProfile();
|
|
}
|
|
|
|
bool get _isNew => widget.server == null;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ViewModelBuilder<ViewServerEditor>.reactive(
|
|
viewModelBuilder: () => ViewServerEditor(context, this),
|
|
onViewModelReady: _onReady,
|
|
builder: (context, viewModel, child) => Scaffold(
|
|
appBar: AppBar(
|
|
title: text(
|
|
_isNew ? 'Register server' : 'Edit server',
|
|
20,
|
|
TextType.Bold,
|
|
color: context.jp.text,
|
|
),
|
|
actions: [
|
|
TextButton.icon(
|
|
onPressed: _onTestConnection,
|
|
icon: const Icon(Icons.wifi_tethering, size: 18),
|
|
label: text('Test', 13, TextType.Bold, color: colorPrimary),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 12, left: 8),
|
|
child: FilledButton.icon(
|
|
onPressed: _onSave,
|
|
icon: const Icon(Icons.check, size: 18),
|
|
label: text('Save', 14, TextType.Bold, color: colorWhite),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
body: Form(
|
|
key: _formKey,
|
|
child: ListView(
|
|
padding: const EdgeInsets.fromLTRB(20, 20, 20, 40),
|
|
children: [
|
|
inputSection(context, 'Identity'),
|
|
inputField(
|
|
label: 'Server name',
|
|
hint: 'prod-1',
|
|
initial: _server.name,
|
|
onSaved: _onSaveName,
|
|
validator: requiredField,
|
|
),
|
|
inputSection(context, 'Connection'),
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
flex: 3,
|
|
child: inputField(
|
|
label: 'Host / IP',
|
|
hint: '10.0.0.5 or example.com',
|
|
initial: _server.host,
|
|
onSaved: _onSaveHost,
|
|
validator: validHost,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: inputField(
|
|
label: 'Port',
|
|
initial: _server.port.toString(),
|
|
keyboardType: TextInputType.number,
|
|
onSaved: _onSavePort,
|
|
validator: validPort,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
inputField(
|
|
label: 'Username',
|
|
hint: 'root / ubuntu',
|
|
initial: _server.username,
|
|
onSaved: _onSaveUsername,
|
|
validator: requiredField,
|
|
),
|
|
inputField(
|
|
label: 'Password',
|
|
initial: _server.password,
|
|
obscure: !_showPassword,
|
|
onSaved: _onSavePassword,
|
|
suffix: IconButton(
|
|
icon: Icon(
|
|
_showPassword ? Icons.visibility_off : Icons.visibility,
|
|
),
|
|
onPressed: _onTogglePassword,
|
|
),
|
|
),
|
|
inputHint(
|
|
context,
|
|
Icons.lock,
|
|
'Stored encrypted in your OS keystore '
|
|
'(Keychain / Keystore / DPAPI / libsecret).',
|
|
),
|
|
inputSwitch(
|
|
context: context,
|
|
title: 'Use sudo for privileged commands',
|
|
subtitle:
|
|
'Sends the password to sudo -S for systemctl / mkdir / rm.',
|
|
value: _server.useSudo,
|
|
onChanged: _onToggleSudo,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _onReady(ViewServerEditor model) {
|
|
_model = model;
|
|
// onViewModelReady fires mid-build; a synchronous setState from the
|
|
// first load would dirty this State while it is still being built.
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (mounted) _initiate();
|
|
});
|
|
}
|
|
|
|
Future<void> _initiate() async {}
|
|
|
|
// ── named handlers ─────────────────────────────────────────────────────────
|
|
|
|
void _onSaveName(String v) => _server.name = v;
|
|
|
|
void _onSaveHost(String v) => _server.host = v;
|
|
|
|
void _onSavePort(String v) => _server.port = int.tryParse(v) ?? 22;
|
|
|
|
void _onSaveUsername(String v) => _server.username = v;
|
|
|
|
void _onSavePassword(String v) => _server.password = v;
|
|
|
|
void _onTogglePassword() => setState(() => _showPassword = !_showPassword);
|
|
|
|
void _onToggleSudo(bool v) => setState(() => _server.useSudo = v);
|
|
|
|
/// Commits the form and hands the profile back to whoever opened the editor.
|
|
void _onSave() {
|
|
if (!_formKey.currentState!.validate()) return;
|
|
_formKey.currentState!.save();
|
|
Navigator.of(context).pop(_server);
|
|
}
|
|
|
|
/// Verifies the details as typed, without leaving the editor.
|
|
void _onTestConnection() {
|
|
if (!_formKey.currentState!.validate()) return;
|
|
_formKey.currentState!.save();
|
|
_model?.testConnection(_server);
|
|
}
|
|
|
|
// ── ConnectServerEditor ────────────────────────────────────────────────────
|
|
|
|
@override
|
|
void onConnectionVerified(ServerProfile server) {
|
|
// The success sheet raised by the ViewModel is the whole outcome; the form
|
|
// keeps its values so the user can save straight away.
|
|
}
|
|
}
|