feat(ground-suite): add local vehicle catalog

This commit is contained in:
Onur Özkan committed 2026-08-13 20:02:42 +03:00
1 parent 2fe54fb053
commit cc9e9919db
18 files changed
+778 -6

No files matched your search

@@ -0,0 +1,139 @@
import 'package:flutter/material.dart';
import '../../../../shared/widgets/surfaces.dart';
import '../../domain/vehicle_definition.dart';
/// A compact, keyboard-selectable workspace definition tile.
///
/// Only a reviewed [VehicleDefinition] reaches this widget. The radio control
/// changes local pending workspace state; it does not connect, configure, or
/// identify a vehicle.
class VehicleCatalogCard extends StatefulWidget {
const VehicleCatalogCard({
super.key,
required this.definition,
required this.selected,
required this.onSelected,
});
final VehicleDefinition definition;
final bool selected;
final VoidCallback onSelected;
@override
State<VehicleCatalogCard> createState() => _VehicleCatalogCardState();
}
class _VehicleCatalogCardState extends State<VehicleCatalogCard> {
bool _showFocus = false;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final accent = widget.selected || _showFocus ? scheme.primary : null;
return Semantics(
button: true,
checked: widget.selected,
inMutuallyExclusiveGroup: true,
label:
'${widget.definition.name}, ${widget.definition.vehicleClass}, '
'${widget.definition.supportedProfile}, local unit type, '
'local vehicle image, not telemetry',
onTap: widget.onSelected,
child: DecoratedBox(
decoration: BoxDecoration(
border: _showFocus
? Border.all(color: scheme.primary, width: 2)
: null,
borderRadius: BorderRadius.circular(14),
),
child: Material(
color: Colors.transparent,
child: InkWell(
excludeFromSemantics: true,
onFocusChange: (value) => setState(() => _showFocus = value),
onTap: widget.onSelected,
borderRadius: BorderRadius.circular(12),
child: InstrumentPanel(
accent: accent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_VehicleImage(media: widget.definition.media),
Padding(
padding: const EdgeInsets.fromLTRB(14, 12, 14, 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ConsoleLabel('Local unit type'),
const SizedBox(height: 6),
Text(
widget.definition.name,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 2),
Text(
'${widget.definition.vehicleClass} · ${widget.definition.supportedProfile}',
style: Theme.of(context).textTheme.bodySmall,
),
const SizedBox(height: 10),
Row(
children: [
Icon(
widget.selected
? Icons.radio_button_checked
: Icons.radio_button_off,
size: 18,
color: widget.selected
? scheme.primary
: scheme.onSurfaceVariant,
),
const SizedBox(width: 7),
Expanded(
child: Text(
widget.selected
? 'Selected for workspace'
: 'Select for workspace',
style: Theme.of(context).textTheme.labelLarge,
),
),
],
),
],
),
),
],
),
),
),
),
),
);
}
}
class _VehicleImage extends StatelessWidget {
const _VehicleImage({required this.media});
final VehicleCatalogMedia media;
@override
Widget build(BuildContext context) => ClipRRect(
borderRadius: const BorderRadius.vertical(top: Radius.circular(11)),
child: AspectRatio(
aspectRatio: 16 / 10,
child: Image.asset(
media.imageAssetPath,
key: Key('vehicle-image-${media.imageAssetPath}'),
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
excludeFromSemantics: true,
errorBuilder: (context, error, stackTrace) => ColoredBox(
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: const Center(child: Icon(Icons.image_not_supported_outlined)),
),
),
),
);
}