feat(recording): add safety limits and adaptive headers
This commit is contained in:
@@ -26,11 +26,111 @@ class PluriScreenHeader extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.pluriTokens;
|
||||
final theme = Theme.of(context);
|
||||
final width = MediaQuery.sizeOf(context).width;
|
||||
final scale = MediaQuery.textScalerOf(context).scale(1);
|
||||
final compact = width < 380 || scale >= 1.25;
|
||||
final iconSize = compact ? 50.0 : 56.0;
|
||||
|
||||
Widget glyphBadge() => Container(
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
const Color(0xFF20E6FF).withValues(alpha: 0.95),
|
||||
t.electricMagenta,
|
||||
t.warmCoral,
|
||||
],
|
||||
),
|
||||
boxShadow: [BoxShadow(color: t.glowColor, blurRadius: 28, spreadRadius: 2)],
|
||||
),
|
||||
child: Center(
|
||||
child: PluriIcon(
|
||||
glyph: glyph,
|
||||
variant: PluriIconVariant.filled,
|
||||
size: compact ? 25 : 28,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget textBlock() => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
maxLines: compact ? 2 : 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: -0.7,
|
||||
height: 1.05,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
subtitle,
|
||||
maxLines: compact ? 4 : 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.78),
|
||||
height: 1.28,
|
||||
),
|
||||
),
|
||||
if (primaryActionLabel != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: onPrimaryAction,
|
||||
icon: const Icon(Icons.auto_awesome_rounded, size: 18),
|
||||
label: Text(primaryActionLabel!),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
Widget foreground() {
|
||||
if (compact) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
glyphBadge(),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(child: textBlock()),
|
||||
],
|
||||
),
|
||||
if (trailing != null) ...[
|
||||
const SizedBox(height: 14),
|
||||
Align(alignment: Alignment.centerLeft, child: trailing!),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
glyphBadge(),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(child: textBlock()),
|
||||
if (trailing != null) ...[
|
||||
const SizedBox(width: 12),
|
||||
ConstrainedBox(constraints: const BoxConstraints(maxWidth: 220), child: trailing!),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(t.spacingMd, t.spacingSm, t.spacingMd, t.spacingSm),
|
||||
child: PluriGlassSurface(
|
||||
borderRadius: BorderRadius.circular(t.radiusLg + 8),
|
||||
padding: const EdgeInsets.all(18),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: compact ? 16 : 20,
|
||||
vertical: compact ? 18 : 20,
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
@@ -84,63 +184,9 @@ class PluriScreenHeader extends StatelessWidget {
|
||||
bottom: -54,
|
||||
child: _Orb(color: const Color(0xFF20E6FF).withValues(alpha: 0.22), size: 116),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
const Color(0xFF20E6FF).withValues(alpha: 0.95),
|
||||
t.electricMagenta,
|
||||
t.warmCoral,
|
||||
],
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(color: t.glowColor, blurRadius: 28, spreadRadius: 2),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: PluriIcon(glyph: glyph, variant: PluriIconVariant.filled, size: 28),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: theme.textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: -0.7,
|
||||
height: 1.02,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
subtitle,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.76),
|
||||
height: 1.25,
|
||||
),
|
||||
),
|
||||
if (primaryActionLabel != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: onPrimaryAction,
|
||||
icon: const Icon(Icons.auto_awesome_rounded, size: 18),
|
||||
label: Text(primaryActionLabel!),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
Padding(
|
||||
padding: EdgeInsets.all(compact ? 2 : 4),
|
||||
child: foreground(),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -149,6 +195,7 @@ class PluriScreenHeader extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PluriStatusPill extends StatelessWidget {
|
||||
const PluriStatusPill({
|
||||
super.key,
|
||||
@@ -165,20 +212,31 @@ class PluriStatusPill extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.pluriTokens;
|
||||
final color = accent ?? t.electricMagenta;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
color: color.withValues(alpha: 0.13),
|
||||
border: Border.all(color: color.withValues(alpha: 0.38)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color),
|
||||
const SizedBox(width: 7),
|
||||
Text(label, style: Theme.of(context).textTheme.labelMedium?.copyWith(fontWeight: FontWeight.w800)),
|
||||
],
|
||||
return Tooltip(
|
||||
message: label,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 220),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
color: color.withValues(alpha: 0.13),
|
||||
border: Border.all(color: color.withValues(alpha: 0.38)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 16, color: color),
|
||||
const SizedBox(width: 7),
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.labelMedium?.copyWith(fontWeight: FontWeight.w800),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user