feat: integrate chat voice and diagnostics client
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../l10n/generated/app_localizations.dart';
|
||||
import '../src/rust/api.dart' as rust;
|
||||
|
||||
/// Server connection form.
|
||||
class ConnectForm extends StatefulWidget {
|
||||
/// Construct a connect form.
|
||||
const ConnectForm({
|
||||
super.key,
|
||||
required this.hostCtl,
|
||||
required this.nickCtl,
|
||||
required this.passwordCtl,
|
||||
required this.onConnect,
|
||||
required this.onAddBookmark,
|
||||
});
|
||||
|
||||
/// Server host controller.
|
||||
final TextEditingController hostCtl;
|
||||
|
||||
/// Nickname controller.
|
||||
final TextEditingController nickCtl;
|
||||
|
||||
/// Server password controller.
|
||||
final TextEditingController passwordCtl;
|
||||
|
||||
/// Called when the user submits a connection.
|
||||
final VoidCallback onConnect;
|
||||
|
||||
/// Called when the user saves the current form as a bookmark.
|
||||
final VoidCallback onAddBookmark;
|
||||
|
||||
@override
|
||||
State<ConnectForm> createState() => _ConnectFormState();
|
||||
}
|
||||
|
||||
class _ConnectFormState extends State<ConnectForm> {
|
||||
final FocusNode _hostFocus = FocusNode();
|
||||
final FocusNode _nickFocus = FocusNode();
|
||||
final FocusNode _passwordFocus = FocusNode();
|
||||
|
||||
void _onTapOutside(PointerDownEvent _) {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hostFocus.dispose();
|
||||
_nickFocus.dispose();
|
||||
_passwordFocus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppL10n.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
controller: widget.hostCtl,
|
||||
focusNode: _hostFocus,
|
||||
onTapOutside: _onTapOutside,
|
||||
keyboardType: TextInputType.url,
|
||||
textCapitalization: TextCapitalization.none,
|
||||
textInputAction: TextInputAction.next,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.deny(RegExp(r'\s')),
|
||||
TextInputFormatter.withFunction(
|
||||
(oldValue, newValue) => newValue.copyWith(
|
||||
text: newValue.text.toLowerCase(),
|
||||
selection: newValue.selection,
|
||||
),
|
||||
),
|
||||
],
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.fieldServerHost,
|
||||
hintText: 'host[:port]',
|
||||
prefixIcon: const Icon(Icons.dns_outlined),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: widget.nickCtl,
|
||||
focusNode: _nickFocus,
|
||||
onTapOutside: _onTapOutside,
|
||||
textInputAction: TextInputAction.next,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.fieldNickname,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: widget.passwordCtl,
|
||||
focusNode: _passwordFocus,
|
||||
onTapOutside: _onTapOutside,
|
||||
obscureText: true,
|
||||
textInputAction: TextInputAction.done,
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.fieldServerPassword,
|
||||
helperText: l10n.fieldServerPasswordHelp,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FilledButton.icon(
|
||||
icon: const Icon(Icons.login),
|
||||
label: Text(l10n.connectAction),
|
||||
onPressed: widget.onConnect,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
icon: const Icon(Icons.bookmark_add_outlined),
|
||||
label: Text(l10n.bookmarkAddAction),
|
||||
onPressed: widget.onAddBookmark,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Saved bookmark list.
|
||||
class BookmarkList extends StatelessWidget {
|
||||
/// Construct a bookmark list.
|
||||
const BookmarkList({
|
||||
super.key,
|
||||
required this.bookmarks,
|
||||
required this.onConnect,
|
||||
required this.onDelete,
|
||||
});
|
||||
|
||||
/// Saved bookmarks.
|
||||
final List<rust.BridgeBookmark> bookmarks;
|
||||
|
||||
/// Connect to a bookmark.
|
||||
final ValueChanged<rust.BridgeBookmark> onConnect;
|
||||
|
||||
/// Delete a bookmark.
|
||||
final ValueChanged<rust.BridgeBookmark> onDelete;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppL10n.of(context);
|
||||
final theme = Theme.of(context);
|
||||
if (bookmarks.isEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text(l10n.bookmarksEmpty, style: theme.textTheme.bodySmall),
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(l10n.bookmarksHeading, style: theme.textTheme.titleSmall),
|
||||
const SizedBox(height: 4),
|
||||
for (final b in bookmarks)
|
||||
Card(
|
||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: ListTile(
|
||||
title: Text(b.displayName),
|
||||
subtitle: Text('${b.host} — ${b.nickname}'),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.login),
|
||||
tooltip: l10n.connectAction,
|
||||
onPressed: () => onConnect(b),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
tooltip: l10n.bookmarkDeleteAction,
|
||||
onPressed: () => onDelete(b),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user