Files
chanora/apps/chanora_flutter/lib/widgets/connect_widgets.dart
T

213 lines
6.3 KiB
Dart

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),
LayoutBuilder(
builder: (context, constraints) {
const stackedActionsMaxWidth = 400.0;
final connectButton = FilledButton.icon(
icon: const Icon(Icons.login),
label: Text(l10n.connectAction),
onPressed: widget.onConnect,
);
final bookmarkButton = OutlinedButton.icon(
icon: const Icon(Icons.bookmark_add_outlined),
label: Text(l10n.bookmarkAddAction),
onPressed: widget.onAddBookmark,
);
if (constraints.maxWidth <= stackedActionsMaxWidth) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
connectButton,
const SizedBox(height: 8),
bookmarkButton,
],
);
}
return Row(
children: [
Expanded(child: connectButton),
const SizedBox(width: 8),
Flexible(child: bookmarkButton),
],
);
},
),
],
);
}
}
/// 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),
),
],
),
),
),
],
);
}
}