refactor(useragent): moved shared CreamPanel and StatePanel into generic widgets

This commit is contained in:
hdbg
2026-03-28 17:57:50 +01:00
parent a3203936d2
commit 0c2d4986a2
12 changed files with 151 additions and 310 deletions

View File

@@ -1,4 +1,5 @@
import 'package:arbiter/theme/palette.dart';
import 'package:arbiter/widgets/cream_frame.dart';
import 'package:flutter/material.dart';
class ClientDetailsStatePanel extends StatelessWidget {
@@ -17,27 +18,18 @@ class ClientDetailsStatePanel extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Center(
child: Padding(
child: CreamFrame(
margin: const EdgeInsets.all(24),
padding: const EdgeInsets.all(24),
child: DecoratedBox(
decoration: BoxDecoration(
color: Palette.cream,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: Palette.line),
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: Palette.coral),
const SizedBox(height: 12),
Text(title, style: theme.textTheme.titleLarge),
const SizedBox(height: 8),
Text(body, textAlign: TextAlign.center),
],
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: Palette.coral),
const SizedBox(height: 12),
Text(title, style: theme.textTheme.titleLarge),
const SizedBox(height: 8),
Text(body, textAlign: TextAlign.center),
],
),
),
);

View File

@@ -1,5 +1,5 @@
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:arbiter/theme/palette.dart';
import 'package:arbiter/widgets/cream_frame.dart';
import 'package:flutter/material.dart';
class ClientSummaryCard extends StatelessWidget {
@@ -9,15 +9,9 @@ class ClientSummaryCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: Palette.cream,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: Palette.line),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
return CreamFrame(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
@@ -42,7 +36,6 @@ class ClientSummaryCard extends StatelessWidget {
),
],
),
),
);
}
}

View File

@@ -1,5 +1,6 @@
import 'package:arbiter/providers/sdk_clients/wallet_access.dart';
import 'package:arbiter/theme/palette.dart';
import 'package:arbiter/widgets/cream_frame.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/experimental/mutation.dart';
@@ -24,15 +25,9 @@ class WalletAccessSaveBar extends StatelessWidget {
MutationError(:final error) => error.toString(),
_ => null,
};
return DecoratedBox(
decoration: BoxDecoration(
color: Palette.cream,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: Palette.line),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
return CreamFrame(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (errorText != null) ...[
@@ -54,7 +49,6 @@ class WalletAccessSaveBar extends StatelessWidget {
),
],
),
),
);
}
}

View File

@@ -2,7 +2,7 @@ import 'package:arbiter/providers/sdk_clients/wallet_access.dart';
import 'package:arbiter/screens/dashboard/clients/details/widgets/client_details_state_panel.dart';
import 'package:arbiter/screens/dashboard/clients/details/widgets/wallet_access_list.dart';
import 'package:arbiter/screens/dashboard/clients/details/widgets/wallet_access_search_field.dart';
import 'package:arbiter/theme/palette.dart';
import 'package:arbiter/widgets/cream_frame.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -27,15 +27,9 @@ class WalletAccessSection extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final optionsAsync = ref.watch(clientWalletOptionsProvider);
return DecoratedBox(
decoration: BoxDecoration(
color: Palette.cream,
borderRadius: BorderRadius.circular(24),
border: Border.all(color: Palette.line),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
return CreamFrame(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
@@ -56,7 +50,6 @@ class WalletAccessSection extends ConsumerWidget {
),
],
),
),
);
}
}

View File

@@ -10,6 +10,8 @@ import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:arbiter/theme/palette.dart';
import 'package:arbiter/widgets/cream_frame.dart';
import 'package:arbiter/widgets/state_panel.dart';
import 'package:sizer/sizer.dart';
// ─── Column width getters ─────────────────────────────────────────────────────
@@ -59,79 +61,6 @@ String _formatError(Object error) {
return message;
}
// ─── State panel ─────────────────────────────────────────────────────────────
class _StatePanel extends StatelessWidget {
const _StatePanel({
required this.icon,
required this.title,
required this.body,
this.actionLabel,
this.onAction,
this.busy = false,
});
final IconData icon;
final String title;
final String body;
final String? actionLabel;
final Future<void> Function()? onAction;
final bool busy;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Palette.cream.withValues(alpha: 0.92),
border: Border.all(color: Palette.line),
),
child: Padding(
padding: EdgeInsets.all(2.8.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (busy)
SizedBox(
width: 2.8.h,
height: 2.8.h,
child: const CircularProgressIndicator(strokeWidth: 2.5),
)
else
Icon(icon, size: 34, color: Palette.coral),
SizedBox(height: 1.8.h),
Text(
title,
style: theme.textTheme.headlineSmall?.copyWith(
color: Palette.ink,
fontWeight: FontWeight.w800,
),
),
SizedBox(height: 1.h),
Text(
body,
style: theme.textTheme.bodyLarge?.copyWith(
color: Palette.ink.withValues(alpha: 0.72),
height: 1.5,
),
),
if (actionLabel != null && onAction != null) ...[
SizedBox(height: 2.h),
OutlinedButton.icon(
onPressed: () => onAction!(),
icon: const Icon(Icons.refresh),
label: Text(actionLabel!),
),
],
],
),
),
);
}
}
// ─── Header ───────────────────────────────────────────────────────────────────
class _Header extends StatelessWidget {
@@ -443,17 +372,11 @@ class _ClientTable extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(24),
color: Palette.cream.withValues(alpha: 0.92),
border: Border.all(color: Palette.line),
),
child: Padding(
padding: EdgeInsets.all(2.h),
child: LayoutBuilder(
builder: (context, constraints) {
final tableWidth = math.max(_tableMinWidth, constraints.maxWidth);
return CreamFrame(
padding: EdgeInsets.all(2.h),
child: LayoutBuilder(
builder: (context, constraints) {
final tableWidth = math.max(_tableMinWidth, constraints.maxWidth);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -497,7 +420,6 @@ class _ClientTable extends StatelessWidget {
);
},
),
),
);
}
}
@@ -533,27 +455,27 @@ class ClientsScreen extends HookConsumerWidget {
final clients = clientsAsync.asData?.value;
final content = switch (clientsAsync) {
AsyncLoading() when clients == null => const _StatePanel(
AsyncLoading() when clients == null => const StatePanel(
icon: Icons.hourglass_top,
title: 'Loading clients',
body: 'Pulling client registry from Arbiter.',
busy: true,
),
AsyncError(:final error) => _StatePanel(
AsyncError(:final error) => StatePanel(
icon: Icons.sync_problem,
title: 'Client registry unavailable',
body: _formatError(error),
actionLabel: 'Retry',
onAction: refresh,
),
_ when !isConnected => _StatePanel(
_ when !isConnected => StatePanel(
icon: Icons.portable_wifi_off,
title: 'No active server connection',
body: 'Reconnect to Arbiter to list SDK clients.',
actionLabel: 'Refresh',
onAction: refresh,
),
_ when clients != null && clients.isEmpty => _StatePanel(
_ when clients != null && clients.isEmpty => StatePanel(
icon: Icons.devices_other_outlined,
title: 'No clients yet',
body: 'SDK clients appear here once they register with Arbiter.',