Files
PeachDeployments/lib/Peach/see/server/editor/ServerEditorState.dart
alvocool 38881d1fe1 Add app source and tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 10:15:30 +03:00

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.
}
}