232 lines
7.0 KiB
Dart
232 lines
7.0 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:equatable/equatable.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
import 'package:uto_manager/domain/entities/position_slot.dart' as domain;
|
|
import 'package:uto_manager/domain/entities/unit_node.dart';
|
|
import 'package:uto_manager/domain/repositories/unit_repository.dart';
|
|
import 'package:uto_manager/domain/services/doctrinal_templates.dart';
|
|
import 'package:uto_manager/domain/services/template_instantiator.dart';
|
|
import 'package:uto_manager/infrastructure/codecs/unit_tree_codec.dart';
|
|
import 'package:uto_manager/infrastructure/database/app_database.dart';
|
|
import 'package:uto_manager/infrastructure/database/daos/template_dao.dart';
|
|
import 'package:uto_manager/infrastructure/repositories/drift_position_slot_repository.dart';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// State
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// State for the template cubit.
|
|
sealed class TemplateState extends Equatable {
|
|
const TemplateState();
|
|
}
|
|
|
|
class TemplateInitial extends TemplateState {
|
|
const TemplateInitial();
|
|
|
|
@override
|
|
List<Object?> get props => [];
|
|
}
|
|
|
|
class TemplateLoading extends TemplateState {
|
|
const TemplateLoading();
|
|
|
|
@override
|
|
List<Object?> get props => [];
|
|
}
|
|
|
|
class TemplateLoaded extends TemplateState {
|
|
const TemplateLoaded(this.templates);
|
|
|
|
final List<Template> templates;
|
|
|
|
@override
|
|
List<Object?> get props => [templates];
|
|
}
|
|
|
|
class TemplateError extends TemplateState {
|
|
const TemplateError(this.message);
|
|
|
|
final String message;
|
|
|
|
@override
|
|
List<Object?> get props => [message];
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cubit
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Manages saved unit tree templates and doctrinal template instantiation.
|
|
class TemplateCubit extends Cubit<TemplateState> {
|
|
TemplateCubit({
|
|
required this.dao,
|
|
required this.unitRepository,
|
|
required this.slotRepository,
|
|
}) : super(const TemplateInitial());
|
|
|
|
final TemplateDao dao;
|
|
final UnitRepository unitRepository;
|
|
final DriftPositionSlotRepository slotRepository;
|
|
|
|
final _codec = const UnitTreeCodec();
|
|
final _instantiator = TemplateInstantiator(idGenerator: const Uuid().v4);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Loading
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Loads all saved templates from the database.
|
|
Future<void> loadTemplates() async {
|
|
emit(const TemplateLoading());
|
|
try {
|
|
final templates = await dao.getAllTemplates();
|
|
emit(TemplateLoaded(templates));
|
|
} catch (e) {
|
|
emit(TemplateError(e.toString()));
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Doctrinal template instantiation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Instantiates a doctrinal template by [key] (e.g., 'mbct', 'sbct') and
|
|
/// persists all generated units and slots to the database in a single batch.
|
|
///
|
|
/// Returns true on success, false on failure.
|
|
Future<bool> instantiateDoctrinalTemplate(String key) async {
|
|
final definition = DoctrinalTemplates.all[key];
|
|
if (definition == null) {
|
|
emit(TemplateError('Unknown template key: "$key"'));
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
final result = _instantiator.instantiate(definition);
|
|
await unitRepository.insertAll(result.units);
|
|
await slotRepository.insertAll(result.slots);
|
|
return true;
|
|
} catch (e) {
|
|
emit(TemplateError('Failed to instantiate template "$key": $e'));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Save / Delete
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Saves a template with [name], [bctType], and [jsonData].
|
|
Future<void> saveTemplate({
|
|
required String templateId,
|
|
required String name,
|
|
required String bctType,
|
|
required Map<String, dynamic> jsonData,
|
|
}) async {
|
|
try {
|
|
final now = DateTime.now();
|
|
await dao.upsertTemplate(
|
|
TemplatesCompanion.insert(
|
|
templateId: templateId,
|
|
name: name,
|
|
bctType: bctType,
|
|
jsonData: jsonEncode(jsonData),
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
),
|
|
);
|
|
await loadTemplates();
|
|
} catch (e) {
|
|
emit(TemplateError(e.toString()));
|
|
}
|
|
}
|
|
|
|
/// Saves the current live unit tree (from [units] and [slots]) as a named
|
|
/// template snapshot in the database.
|
|
Future<void> saveSnapshot({
|
|
required String name,
|
|
required String bctType,
|
|
required List<UnitNode> units,
|
|
required List<domain.PositionSlot> slots,
|
|
}) async {
|
|
try {
|
|
final jsonMap = _codec.encodeToMap(
|
|
units: units,
|
|
slots: slots,
|
|
personnel: const [],
|
|
);
|
|
await saveTemplate(
|
|
templateId: const Uuid().v4(),
|
|
name: name,
|
|
bctType: bctType,
|
|
jsonData: jsonMap,
|
|
);
|
|
} catch (e) {
|
|
emit(TemplateError(e.toString()));
|
|
}
|
|
}
|
|
|
|
/// Deletes the template with [templateId].
|
|
Future<void> deleteTemplate(String templateId) async {
|
|
try {
|
|
await dao.deleteTemplate(templateId);
|
|
await loadTemplates();
|
|
} catch (e) {
|
|
emit(TemplateError(e.toString()));
|
|
}
|
|
}
|
|
|
|
/// Returns the JSON data for [templateId], or null if not found.
|
|
Future<Map<String, dynamic>?> getTemplateJson(String templateId) async {
|
|
try {
|
|
final template = await dao.getTemplateById(templateId);
|
|
if (template == null) return null;
|
|
return jsonDecode(template.jsonData) as Map<String, dynamic>;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Export / Import
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Encodes [units] and [slots] to a JSON string for export.
|
|
String exportToJson({
|
|
required List<UnitNode> units,
|
|
required List<domain.PositionSlot> slots,
|
|
}) {
|
|
return _codec.encode(units: units, slots: slots, personnel: const []);
|
|
}
|
|
|
|
/// Decodes a JSON export string and persists the units and slots to the
|
|
/// database. Clears existing data first if [replaceExisting] is true.
|
|
///
|
|
/// Returns true on success, false on failure.
|
|
Future<bool> importFromJson(
|
|
String json, {
|
|
bool replaceExisting = false,
|
|
}) async {
|
|
try {
|
|
final snapshot = _codec.decode(json);
|
|
|
|
if (replaceExisting) {
|
|
await slotRepository.deleteAll();
|
|
await unitRepository.deleteAll();
|
|
}
|
|
|
|
await unitRepository.insertAll(snapshot.units);
|
|
await slotRepository.insertAll(snapshot.slots);
|
|
return true;
|
|
} on FormatException catch (e) {
|
|
emit(TemplateError('Import failed — invalid format: ${e.message}'));
|
|
return false;
|
|
} catch (e) {
|
|
emit(TemplateError('Import failed: $e'));
|
|
return false;
|
|
}
|
|
}
|
|
}
|