204 lines
6.4 KiB
Dart
204 lines
6.4 KiB
Dart
/// Metadata for a motorcycle photograph or reference image
|
|
class MotorcycleImageReference {
|
|
final String? assetPath;
|
|
final String? networkUrl;
|
|
final String? localFilePath;
|
|
final String caption;
|
|
final String attribution;
|
|
final bool isPhoto;
|
|
|
|
const MotorcycleImageReference({
|
|
this.assetPath,
|
|
this.networkUrl,
|
|
this.localFilePath,
|
|
required this.caption,
|
|
this.attribution = 'Technical Reference',
|
|
this.isPhoto = false,
|
|
});
|
|
|
|
bool get hasImage => assetPath != null || networkUrl != null || localFilePath != null;
|
|
}
|
|
|
|
/// Metadata descriptor for registered motorcycle reference images
|
|
class MotorcycleImageDescriptor {
|
|
final String make;
|
|
final String model;
|
|
final int? exactYear;
|
|
final int? minYear;
|
|
final int? maxYear;
|
|
final List<String> modelAliases;
|
|
final String assetPath;
|
|
final String caption;
|
|
final String attribution;
|
|
final bool isPhoto;
|
|
|
|
const MotorcycleImageDescriptor({
|
|
required this.make,
|
|
required this.model,
|
|
this.exactYear,
|
|
this.minYear,
|
|
this.maxYear,
|
|
this.modelAliases = const [],
|
|
required this.assetPath,
|
|
required this.caption,
|
|
this.attribution = 'Factory Technical Reference',
|
|
this.isPhoto = true,
|
|
});
|
|
|
|
/// Normalizes vehicle names for invariant comparison
|
|
static String normalize(String input) {
|
|
return input.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]'), '');
|
|
}
|
|
|
|
/// Evaluates whether this descriptor matches the specified make, model, and year
|
|
bool matches(String targetMake, String targetModel, int? targetYear) {
|
|
final normTargetMake = normalize(targetMake);
|
|
final normMake = normalize(make);
|
|
if (normMake != normTargetMake) return false;
|
|
|
|
final normTargetModel = normalize(targetModel);
|
|
final normModel = normalize(model);
|
|
final isModelMatch = normModel == normTargetModel ||
|
|
modelAliases.any((alias) => normalize(alias) == normTargetModel);
|
|
|
|
if (!isModelMatch) return false;
|
|
|
|
// Exact year check
|
|
if (exactYear != null) {
|
|
return targetYear == exactYear;
|
|
}
|
|
|
|
// Generation / Year range check
|
|
if (minYear != null || maxYear != null) {
|
|
if (targetYear == null) return false;
|
|
if (minYear != null && targetYear < minYear!) return false;
|
|
if (maxYear != null && targetYear > maxYear!) return false;
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
MotorcycleImageReference toReference() {
|
|
return MotorcycleImageReference(
|
|
assetPath: assetPath,
|
|
caption: caption,
|
|
attribution: attribution,
|
|
isPhoto: isPhoto,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Central extensible repository providing authentic motorcycle reference images
|
|
class MotorcycleImageService {
|
|
static const MotorcycleImageService _instance = MotorcycleImageService._internal();
|
|
factory MotorcycleImageService() => _instance;
|
|
const MotorcycleImageService._internal();
|
|
|
|
/// List of verified physical photographic assets bundled in the APK
|
|
static const List<String> bundledPhotographicAssets = [
|
|
'assets/images/motorcycles/yamaha_yzf_r6_2001.png',
|
|
];
|
|
|
|
/// Default registry of curated motorcycle reference descriptors
|
|
static final List<MotorcycleImageDescriptor> _defaultDescriptors = [
|
|
// Tier 1 & 2: 1st Gen Yamaha YZF-R6 (1999-2002 5EB) including 2001 field test vehicle
|
|
const MotorcycleImageDescriptor(
|
|
make: 'Yamaha',
|
|
model: 'YZF-R6',
|
|
minYear: 1999,
|
|
maxYear: 2002,
|
|
modelAliases: ['R6', 'YZFR6', 'YZF R6'],
|
|
assetPath: 'assets/images/motorcycles/yamaha_yzf_r6_2001.png',
|
|
caption: '2001 Yamaha YZF-R6 (5EB)',
|
|
attribution: 'Factory Supersport Reference',
|
|
isPhoto: true,
|
|
),
|
|
];
|
|
|
|
static final List<MotorcycleImageDescriptor> _customDescriptors = [];
|
|
|
|
/// Registers a custom image descriptor (for extensions/plugins/tests)
|
|
static void registerDescriptor(MotorcycleImageDescriptor descriptor) {
|
|
_customDescriptors.add(descriptor);
|
|
}
|
|
|
|
/// Clears custom descriptors (resets to default registry)
|
|
static void resetRegistry() {
|
|
_customDescriptors.clear();
|
|
}
|
|
|
|
/// Returns all active descriptors in evaluation order (custom first, then defaults)
|
|
List<MotorcycleImageDescriptor> get allDescriptors => [
|
|
..._customDescriptors,
|
|
..._defaultDescriptors,
|
|
];
|
|
|
|
/// Resolves the best available photographic image or reference for a motorcycle
|
|
///
|
|
/// Matching Hierarchy:
|
|
/// 1. Exact Year Match (Make + Model + Exact Year)
|
|
/// 2. Generation / Year-Range Match (Make + Model + Generation Range)
|
|
/// 3. Make + Model Match (Ageless / General Model Reference)
|
|
/// 4. Dynamic Technical Schematic Fallback (Accurate make/model/year vector card)
|
|
MotorcycleImageReference getReferenceImage({
|
|
required String? make,
|
|
required String? model,
|
|
int? year,
|
|
}) {
|
|
if (make == null || model == null || make.trim().isEmpty || model.trim().isEmpty) {
|
|
return const MotorcycleImageReference(
|
|
caption: 'Motorcycle Technical Reference',
|
|
attribution: 'Schematic Vector',
|
|
isPhoto: false,
|
|
);
|
|
}
|
|
|
|
final descriptors = allDescriptors;
|
|
|
|
// Tier 1: Exact Year Match
|
|
if (year != null) {
|
|
for (final desc in descriptors) {
|
|
if (desc.exactYear != null && desc.matches(make, model, year)) {
|
|
return desc.toReference();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tier 2: Generation / Year-Range Match (e.g. 1999-2002 1st Gen R6)
|
|
if (year != null) {
|
|
for (final desc in descriptors) {
|
|
if ((desc.minYear != null || desc.maxYear != null) && desc.matches(make, model, year)) {
|
|
return desc.toReference();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tier 3: Make + Model Match (Ageless / General Model Reference)
|
|
for (final desc in descriptors) {
|
|
if (desc.exactYear == null && desc.minYear == null && desc.maxYear == null && desc.matches(make, model, year)) {
|
|
return desc.toReference();
|
|
}
|
|
}
|
|
|
|
// Tier 4: Graceful Technical Schematic Fallback
|
|
final yearPrefix = year != null && year > 1900 ? '$year ' : '';
|
|
final formattedCaption = '$yearPrefix${make.trim()} ${model.trim()}'.trim();
|
|
return MotorcycleImageReference(
|
|
caption: formattedCaption,
|
|
attribution: 'Technical Schematic',
|
|
isPhoto: false,
|
|
);
|
|
}
|
|
|
|
/// Checks whether a genuine photographic asset is registered and available for a given vehicle
|
|
bool hasPhotographicAsset({
|
|
required String? make,
|
|
required String? model,
|
|
int? year,
|
|
}) {
|
|
final ref = getReferenceImage(make: make, model: model, year: year);
|
|
return ref.isPhoto && ref.assetPath != null;
|
|
}
|
|
}
|