298 lines
7.5 KiB
Dart
298 lines
7.5 KiB
Dart
import 'package:hive/hive.dart';
|
|
import 'schema/display_data.dart';
|
|
import 'schema/hidden_specs.dart';
|
|
import 'schema/user_data.dart';
|
|
import 'schema/gamification.dart';
|
|
import 'schema/metadata.dart';
|
|
import 'schema/item_type.dart';
|
|
|
|
export 'schema/display_data.dart';
|
|
export 'schema/hidden_specs.dart';
|
|
export 'schema/user_data.dart';
|
|
export 'schema/gamification.dart';
|
|
export 'schema/metadata.dart';
|
|
export 'schema/item_type.dart';
|
|
|
|
part 'sake_item.g.dart';
|
|
|
|
@HiveType(typeId: 0)
|
|
class SakeItem extends HiveObject {
|
|
// --- Root Identity ---
|
|
@HiveField(0)
|
|
final String id;
|
|
|
|
// --- New Hierarchical Data (Fields 20+) ---
|
|
@HiveField(20)
|
|
DisplayData? _displayData;
|
|
|
|
@HiveField(21)
|
|
HiddenSpecs? _hiddenSpecs;
|
|
|
|
@HiveField(22)
|
|
UserData? _userData;
|
|
|
|
@HiveField(23)
|
|
Gamification? _gamification;
|
|
|
|
@HiveField(24)
|
|
Metadata? _metadata;
|
|
|
|
@HiveField(25)
|
|
ItemType? _itemType;
|
|
|
|
// --- Legacy Fields (Deprecated: Kept for Migration) ---
|
|
@HiveField(1)
|
|
final String? legacyName;
|
|
|
|
@HiveField(2)
|
|
final String? legacyBrand;
|
|
|
|
@HiveField(3)
|
|
final String? legacyPrefecture;
|
|
|
|
@HiveField(4)
|
|
final String? legacyDescription;
|
|
|
|
@HiveField(5)
|
|
final String? legacyCatchCopy;
|
|
|
|
@HiveField(6)
|
|
final List<String>? legacyImagePaths;
|
|
|
|
@HiveField(7)
|
|
final double? legacySweetnessScore;
|
|
|
|
@HiveField(8)
|
|
final double? legacyBodyScore;
|
|
|
|
@HiveField(9)
|
|
final DateTime? legacyCreatedAt;
|
|
|
|
@HiveField(10)
|
|
final int? legacyConfidenceScore;
|
|
|
|
@HiveField(11)
|
|
final List<String>? legacyFlavorTags;
|
|
|
|
@HiveField(12)
|
|
final bool? legacyIsFavorite;
|
|
|
|
@HiveField(13)
|
|
final Map<String, int>? legacyTasteStats;
|
|
|
|
@HiveField(14)
|
|
final bool? legacyIsUserEdited;
|
|
|
|
@HiveField(15)
|
|
final int? legacyCostPrice;
|
|
|
|
@HiveField(16)
|
|
final int? legacyManualPrice;
|
|
|
|
@HiveField(17)
|
|
final double? legacyMarkup;
|
|
|
|
@HiveField(18)
|
|
final Map<String, int>? legacyPriceVariants;
|
|
|
|
SakeItem({
|
|
required this.id,
|
|
DisplayData? displayData,
|
|
HiddenSpecs? hiddenSpecs,
|
|
UserData? userData,
|
|
Gamification? gamification,
|
|
Metadata? metadata,
|
|
ItemType? itemType,
|
|
// Legacy params for migration compatibility (optional)
|
|
this.legacyName,
|
|
this.legacyBrand,
|
|
this.legacyPrefecture,
|
|
this.legacyDescription,
|
|
this.legacyCatchCopy,
|
|
this.legacyImagePaths,
|
|
this.legacySweetnessScore,
|
|
this.legacyBodyScore,
|
|
this.legacyCreatedAt,
|
|
this.legacyConfidenceScore,
|
|
this.legacyFlavorTags,
|
|
this.legacyIsFavorite,
|
|
this.legacyTasteStats,
|
|
this.legacyIsUserEdited,
|
|
this.legacyCostPrice,
|
|
this.legacyManualPrice,
|
|
this.legacyMarkup,
|
|
this.legacyPriceVariants,
|
|
}) {
|
|
_displayData = displayData;
|
|
_hiddenSpecs = hiddenSpecs;
|
|
_userData = userData;
|
|
_gamification = gamification;
|
|
_metadata = metadata;
|
|
_itemType = itemType;
|
|
}
|
|
|
|
// --- Smart Getters (Auto-Migration / Fallback) ---
|
|
DisplayData get displayData {
|
|
if (_displayData != null) return _displayData!;
|
|
// Fallback: Construct from Legacy
|
|
return DisplayData(
|
|
name: legacyName ?? 'Unknown',
|
|
brewery: legacyBrand ?? 'Unknown',
|
|
prefecture: legacyPrefecture ?? 'Unknown',
|
|
catchCopy: legacyCatchCopy,
|
|
imagePaths: legacyImagePaths ?? [],
|
|
rating: null, // Legacy didn't have rating explicit in display
|
|
);
|
|
}
|
|
|
|
// Allow setting for UI updates
|
|
set displayData(DisplayData val) {
|
|
_displayData = val;
|
|
save(); // Auto-save on set? Or just update memory. HiveObject usually requires save().
|
|
// Better to just update memory here.
|
|
}
|
|
|
|
HiddenSpecs get hiddenSpecs {
|
|
if (_hiddenSpecs != null) return _hiddenSpecs!;
|
|
return HiddenSpecs(
|
|
description: legacyDescription,
|
|
tasteStats: legacyTasteStats ?? {},
|
|
flavorTags: legacyFlavorTags ?? [],
|
|
sweetnessScore: legacySweetnessScore,
|
|
bodyScore: legacyBodyScore,
|
|
);
|
|
}
|
|
|
|
UserData get userData {
|
|
if (_userData != null) return _userData!;
|
|
return UserData(
|
|
isFavorite: legacyIsFavorite ?? false,
|
|
isUserEdited: legacyIsUserEdited ?? false,
|
|
price: legacyManualPrice,
|
|
costPrice: legacyCostPrice,
|
|
markup: legacyMarkup ?? 3.0,
|
|
priceVariants: legacyPriceVariants,
|
|
);
|
|
}
|
|
|
|
Gamification get gamification {
|
|
if (_gamification != null) return _gamification!;
|
|
return Gamification(
|
|
ponPoints: 0, // Legacy didn't track
|
|
);
|
|
}
|
|
|
|
Metadata get metadata {
|
|
if (_metadata != null) return _metadata!;
|
|
return Metadata(
|
|
createdAt: legacyCreatedAt ?? DateTime.now(),
|
|
aiConfidence: legacyConfidenceScore,
|
|
);
|
|
}
|
|
|
|
ItemType get itemType {
|
|
// Default to 'sake' for existing data
|
|
return _itemType ?? ItemType.sake;
|
|
}
|
|
|
|
set itemType(ItemType val) {
|
|
_itemType = val;
|
|
}
|
|
|
|
// --- Migration Method ---
|
|
// Returns true if migration was performed (i.e., was legacy)
|
|
bool ensureMigrated() {
|
|
if (_displayData != null) return false; // Already new structure
|
|
|
|
// Create New Objects from Legacy Fields
|
|
_displayData = displayData; // Uses getter logic
|
|
_hiddenSpecs = hiddenSpecs;
|
|
_userData = userData;
|
|
_gamification = gamification;
|
|
_metadata = metadata;
|
|
|
|
return true;
|
|
}
|
|
|
|
// --- CopyWith for Immutable Updates (Backend Compatible) ---
|
|
SakeItem copyWith({
|
|
String? name,
|
|
String? brand, // Maps to Brewery
|
|
String? prefecture,
|
|
String? description,
|
|
String? catchCopy,
|
|
List<String>? imagePaths,
|
|
double? sweetnessScore,
|
|
double? bodyScore,
|
|
int? confidenceScore, // Maps to aiConfidence
|
|
List<String>? flavorTags,
|
|
bool? isFavorite,
|
|
Map<String, int>? tasteStats,
|
|
bool? isUserEdited,
|
|
String? memo,
|
|
int? costPrice,
|
|
int? manualPrice, // Maps to price
|
|
double? markup,
|
|
Map<String, int>? priceVariants,
|
|
ItemType? itemType,
|
|
// New Specs
|
|
String? specificDesignation, // Maps to hiddenSpecs.type
|
|
double? alcoholContent,
|
|
int? polishingRatio,
|
|
double? sakeMeterValue,
|
|
String? riceVariety,
|
|
String? yeast,
|
|
String? manufacturingYearMonth,
|
|
}) {
|
|
// Ensure we have current data structures
|
|
final currentDisplay = displayData;
|
|
final currentHidden = hiddenSpecs;
|
|
final currentUser = userData;
|
|
final currentMeta = metadata;
|
|
final currentGame = gamification;
|
|
|
|
return SakeItem(
|
|
id: id,
|
|
displayData: currentDisplay.copyWith(
|
|
name: name,
|
|
brewery: brand,
|
|
prefecture: prefecture,
|
|
catchCopy: catchCopy,
|
|
imagePaths: imagePaths,
|
|
),
|
|
hiddenSpecs: currentHidden.copyWith(
|
|
description: description,
|
|
tasteStats: tasteStats,
|
|
flavorTags: flavorTags,
|
|
sweetnessScore: sweetnessScore,
|
|
bodyScore: bodyScore,
|
|
type: specificDesignation,
|
|
alcoholContent: alcoholContent,
|
|
polishingRatio: polishingRatio,
|
|
sakeMeterValue: sakeMeterValue,
|
|
riceVariety: riceVariety,
|
|
yeast: yeast,
|
|
manufacturingYearMonth: manufacturingYearMonth,
|
|
),
|
|
userData: currentUser.copyWith(
|
|
isFavorite: isFavorite,
|
|
isUserEdited: isUserEdited,
|
|
memo: memo,
|
|
price: manualPrice,
|
|
costPrice: costPrice,
|
|
markup: markup,
|
|
priceVariants: priceVariants,
|
|
),
|
|
gamification: currentGame,
|
|
metadata: currentMeta.copyWith(
|
|
aiConfidence: confidenceScore,
|
|
),
|
|
itemType: itemType ?? this.itemType,
|
|
);
|
|
}
|
|
// Compact JSON for QR ecosystem
|
|
String toQrJson() {
|
|
return '{"id":"$id","n":"${displayData.name}","b":"${displayData.brewery}","p":"${displayData.prefecture}","s":${hiddenSpecs.sweetnessScore ?? 0},"y":${hiddenSpecs.bodyScore ?? 0},"a":${hiddenSpecs.alcoholContent ?? 0}}';
|
|
}
|
|
}
|