TermRemoteCtl/apps/mobile_app/lib/app/ui_shell.dart

220 lines
5.7 KiB
Dart

import 'package:flutter/material.dart';
import 'app_theme.dart';
enum AppPanelTone { primary, subdued, emphasis }
class AppPanel extends StatelessWidget {
const AppPanel({
super.key,
required this.child,
this.padding = AppTheme.panelPadding,
this.borderRadius = AppTheme.panelRadius,
this.tone = AppPanelTone.primary,
});
final Widget child;
final EdgeInsetsGeometry padding;
final BorderRadiusGeometry borderRadius;
final AppPanelTone tone;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final palette = switch (tone) {
AppPanelTone.primary => (
base: colorScheme.surfaceContainerHighest,
edge: colorScheme.outlineVariant,
glow: const Color(0x22000000),
),
AppPanelTone.subdued => (
base: colorScheme.surfaceContainerHigh,
edge: colorScheme.outlineVariant.withValues(alpha: 0.8),
glow: const Color(0x18000000),
),
AppPanelTone.emphasis => (
base: const Color(0xFF161B21),
edge: const Color(0xFF4B4338),
glow: const Color(0x14000000),
),
};
return DecoratedBox(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color.alphaBlend(
Colors.white.withValues(alpha: 0.035),
palette.base,
),
palette.base,
],
),
borderRadius: borderRadius,
border: Border.all(color: palette.edge),
boxShadow: [
BoxShadow(
color: palette.glow,
blurRadius: 22,
offset: const Offset(0, 10),
),
],
),
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: borderRadius,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.white.withValues(alpha: 0.02), Colors.transparent],
),
),
child: Padding(padding: padding, child: child),
),
);
}
}
class AppSectionHeader extends StatelessWidget {
const AppSectionHeader({
super.key,
required this.eyebrow,
required this.title,
required this.subtitle,
});
final String eyebrow;
final String title;
final String subtitle;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(width: 28, height: 1, color: const Color(0xFF766855)),
const SizedBox(width: 8),
Text(
eyebrow.toUpperCase(),
style: Theme.of(
context,
).textTheme.labelLarge?.copyWith(color: const Color(0xFFD0C0A3)),
),
],
),
const SizedBox(height: 10),
Text(title, style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 6),
Text(subtitle, style: Theme.of(context).textTheme.bodySmall),
],
);
}
}
class AppEmptyState extends StatelessWidget {
const AppEmptyState({
super.key,
required this.icon,
required this.title,
required this.message,
});
final IconData icon;
final String title;
final String message;
@override
Widget build(BuildContext context) {
return AppPanel(
tone: AppPanelTone.subdued,
child: Column(
children: [
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: Theme.of(
context,
).colorScheme.primary.withValues(alpha: 0.09),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: Theme.of(
context,
).colorScheme.primary.withValues(alpha: 0.18),
),
),
child: Icon(
icon,
size: 28,
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 16),
Text(
title,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(
message,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
);
}
}
class StatusPill extends StatelessWidget {
const StatusPill({
super.key,
required this.label,
required this.icon,
required this.color,
});
final String label;
final IconData icon;
final Color color;
@override
Widget build(BuildContext context) {
final backgroundColor = Color.alphaBlend(
color.withValues(alpha: 0.18),
Theme.of(context).colorScheme.surfaceContainerHigh,
);
return DecoratedBox(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(999),
border: Border.all(color: color.withValues(alpha: 0.28)),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 14, color: color),
const SizedBox(width: 6),
Text(
label,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: color,
fontWeight: FontWeight.w700,
),
),
],
),
),
);
}
}