feat(ground-suite): add local vehicle catalog
This commit is contained in:
1 parent
2fe54fb053
commit
cc9e9919db
18 files changed
+778
-6
No files matched your search
+139
@@ -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)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user