Refine prototype agent list and creation form styling

Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
Tom Brow
2026-10-19 17:57:05 -07:00
parent 59bd1376f9
commit b72a438d01
5 changed files with 78 additions and 54 deletions

View File

@@ -482,14 +482,6 @@ class ChannelsPage extends HookConsumerWidget {
onPressed: openCommunitySwitcher,
),
nativeActions: [
if (onSearch != null)
IosNavigationAction(
label: 'Search',
symbol: 'magnifyingglass',
sharesBackground: true,
icon: BuzzIcons.search300,
onPressed: onSearch,
),
if (onAgents != null)
IosNavigationAction(
label: 'Agents',
@@ -498,6 +490,14 @@ class ChannelsPage extends HookConsumerWidget {
icon: BuzzIcons.bot,
onPressed: onAgents,
),
if (onSearch != null)
IosNavigationAction(
label: 'Search',
symbol: 'magnifyingglass',
sharesBackground: true,
icon: BuzzIcons.search300,
onPressed: onSearch,
),
if (onInbox != null)
IosNavigationAction(
label: 'Inbox',

View File

@@ -24,18 +24,18 @@ class _ChannelHeaderActions extends StatelessWidget {
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (onSearch != null)
IconButton(
tooltip: 'Search',
icon: const Icon(BuzzIcons.search300),
onPressed: onSearch,
),
if (onAgents != null)
IconButton(
tooltip: 'Agents',
icon: const Icon(BuzzIcons.bot),
onPressed: onAgents,
),
if (onSearch != null)
IconButton(
tooltip: 'Search',
icon: const Icon(BuzzIcons.search300),
onPressed: onSearch,
),
if (onInbox != null)
Semantics(
label: hasUnreadInbox ? 'Inbox, unread' : 'Inbox',

View File

@@ -5,7 +5,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../features/profile/user_profile_sheet.dart';
import '../features/profile/presence_cache_provider.dart';
import '../shared/community/community_provider.dart';
import '../shared/crypto/nip_oa.dart';
import '../shared/identity_names/identity_names_provider.dart';
@@ -88,7 +87,6 @@ class PrototypeAgentsSheet extends HookConsumerWidget {
);
keys.sort((a, b) => names.labelFor(a).compareTo(names.labelFor(b)));
final profiles = ref.watch(userCacheProvider);
final presence = ref.watch(presentedPresenceProvider);
final failed = directory.hasError || registered.hasError;
final loading =
controller.busy ||
@@ -109,7 +107,8 @@ class PrototypeAgentsSheet extends HookConsumerWidget {
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
trailing: TextButton(
trailing: IconButton(
tooltip: 'Add agent',
onPressed: community == null || controller.busy
? null
: () => showBuzzModalBottomSheet<bool>(
@@ -122,7 +121,7 @@ class PrototypeAgentsSheet extends HookConsumerWidget {
onCreated: () => ref.invalidate(agentDirectoryProvider),
),
),
child: const Text('Add agent'),
icon: const Icon(Icons.add),
),
child: RefreshIndicator(
onRefresh: refresh,
@@ -161,6 +160,7 @@ class PrototypeAgentsSheet extends HookConsumerWidget {
key: ValueKey(key),
contentPadding: const EdgeInsets.symmetric(
horizontal: Grid.xxs,
vertical: Grid.xxs,
),
leading: AvatarImage(
imageUrl: profiles[key]?.avatarUrl,
@@ -172,19 +172,6 @@ class PrototypeAgentsSheet extends HookConsumerWidget {
),
),
title: Text(names.labelFor(key)),
subtitle: presence[key] == 'online'
? Row(
children: [
Icon(
Icons.circle,
size: Grid.xxs,
color: context.appColors.success,
),
const SizedBox(width: Grid.half),
const Text('Online'),
],
)
: null,
onTap: () => Navigator.pop(context, key),
),
],

View File

@@ -36,6 +36,38 @@ class CreatePrototypeAgentSheet extends HookConsumerWidget {
final active = ref.watch(activeCommunityProvider).asData?.value;
final sameCommunity = active?.id == community.id;
final pending = controller.pending != null;
final canCreate =
!controller.busy && sameCommunity && name.text.trim().isNotEmpty;
final actionStyle = IconButton.styleFrom(
minimumSize: const Size.square(44),
backgroundColor: context.colors.surfaceContainerHighest,
side: BorderSide(color: context.colors.outlineVariant),
shape: const CircleBorder(),
);
InputDecoration fieldDecoration(String label, {String? hint}) =>
InputDecoration(
labelText: label,
floatingLabelBehavior: FloatingLabelBehavior.always,
hintText: hint,
filled: true,
fillColor: context.colors.surfaceContainerHighest,
contentPadding: const EdgeInsets.symmetric(
horizontal: Grid.xs,
vertical: Grid.xs,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(Radii.container),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(Radii.container),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(Radii.container),
borderSide: BorderSide.none,
),
);
Future<void> create() async {
error.value = null;
@@ -91,16 +123,28 @@ class CreatePrototypeAgentSheet extends HookConsumerWidget {
),
child: BuzzTitledSheetLayout(
title: 'Create agent',
leading: TextButton(
leading: IconButton(
tooltip: 'Cancel',
style: actionStyle,
onPressed: controller.busy ? null : () => Navigator.pop(context),
child: const Text('Cancel'),
icon: const Icon(Icons.close),
),
trailing: TextButton(
onPressed:
controller.busy || !sameCommunity || name.text.trim().isEmpty
? null
: create,
child: Text(pending ? 'Resume' : 'Create'),
trailing: IconButton(
tooltip: pending ? 'Resume' : 'Create',
style: actionStyle.copyWith(
backgroundColor: WidgetStatePropertyAll(
canCreate
? context.colors.primary
: context.colors.surfaceContainerHighest,
),
foregroundColor: WidgetStatePropertyAll(
canCreate
? context.colors.onPrimary
: context.colors.onSurfaceVariant,
),
),
onPressed: canCreate ? create : null,
icon: const Icon(Icons.check),
),
child: SingleChildScrollView(
padding: const EdgeInsets.all(Grid.gutter),
@@ -112,28 +156,21 @@ class CreatePrototypeAgentSheet extends HookConsumerWidget {
autofocus: true,
enabled: !controller.busy && !pending,
textCapitalization: TextCapitalization.words,
decoration: const InputDecoration(
labelText: 'Display name',
border: OutlineInputBorder(),
),
decoration: fieldDecoration('Name'),
),
const SizedBox(height: Grid.sm),
TextField(
controller: instructions,
enabled: !controller.busy && !pending,
minLines: 4,
maxLines: 7,
decoration: const InputDecoration(
labelText: 'Instructions (optional)',
hintText: 'Describe what this agent should do.',
border: OutlineInputBorder(),
maxLines: 6,
textCapitalization: TextCapitalization.sentences,
textInputAction: TextInputAction.newline,
decoration: fieldDecoration(
'Instructions (optional)',
hint: 'Describe what this agent should do.',
),
),
const SizedBox(height: Grid.xs),
Text(
'Runs remotely and responds to you in ${community.name}.',
style: context.textTheme.bodySmall,
),
if (controller.busy) ...[
const SizedBox(height: Grid.xs),
const LinearProgressIndicator(),

View File

@@ -164,7 +164,7 @@ void main() {
'Reply concisely.',
);
await tester.pump();
await tester.tap(find.text('Create'));
await tester.tap(find.byTooltip('Create'));
await tester.pumpAndSettle();
expect(controller.submittedName, 'Scout');
expect(controller.submittedInstructions, 'Reply concisely.');