import 'dart:io'; import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../cekirdek/kimlik.dart'; /// Karakter — kullanıcının tanımladığı yüz + tanım + meta. /// Bir kere oluşturulur, sonra istediği kıyafet/saç/mekan ile farklı /// görsellerde kullanılabilir. class Karakter { final String id; String isim; String? yuzRefYolu; // KALICI yol (karakter klasöründe) String tanim; // "young woman, brown eyes, athletic build" String cinsiyet; // 'kadın' | 'erkek' | 'kişi' String? notlar; // Ses alanları — RVC voice clone için String? sesRefYolu; // 10-30sn örnek vokal (mp3/wav/flac/ogg/m4a) String? sesModelYolu; // RVC trained .pth (varsa) String? sesIndexYolu; // RVC index (.index, opsiyonel) // Sihirbaz/üretim sabitleri — karakter seçilince form'a yüklenir Map? sihirbazSnapshot; // prompt sihirbaz form değerleri int? sabitSeed; // sabit seed (üretimde tekrar tutarlı sonuç için) DateTime olusturma; Karakter({ required this.id, required this.isim, required this.tanim, this.yuzRefYolu, this.cinsiyet = 'kişi', this.notlar, this.sesRefYolu, this.sesModelYolu, this.sesIndexYolu, this.sihirbazSnapshot, this.sabitSeed, required this.olusturma, }); Map toJson() => { 'id': id, 'isim': isim, 'yuzRefYolu': yuzRefYolu, 'tanim': tanim, 'cinsiyet': cinsiyet, 'notlar': notlar, 'sesRefYolu': sesRefYolu, 'sesModelYolu': sesModelYolu, 'sesIndexYolu': sesIndexYolu, 'sihirbazSnapshot': sihirbazSnapshot, 'sabitSeed': sabitSeed, 'olusturma': olusturma.toIso8601String(), }; static Karakter fromJson(Map j) => Karakter( id: j['id'] as String, isim: j['isim'] as String, yuzRefYolu: j['yuzRefYolu'] as String?, tanim: j['tanim'] as String? ?? '', cinsiyet: j['cinsiyet'] as String? ?? 'kişi', notlar: j['notlar'] as String?, sesRefYolu: j['sesRefYolu'] as String?, sesModelYolu: j['sesModelYolu'] as String?, sesIndexYolu: j['sesIndexYolu'] as String?, sihirbazSnapshot: (j['sihirbazSnapshot'] as Map?) ?.cast(), sabitSeed: (j['sabitSeed'] as num?)?.toInt(), olusturma: DateTime.parse(j['olusturma'] as String? ?? DateTime.now().toIso8601String()), ); } class KarakterDurumu extends StateNotifier> { KarakterDurumu() : super([]) { _yukle(); } Box? _kutu; Future _box() async => _kutu ??= await Hive.openBox('gorsel_karakterler'); Future _yukle() async { final b = await _box(); final list = []; for (final v in b.values) { try { list.add(Karakter.fromJson(Map.from(v as Map))); } catch (_) {} } list.sort((a, b) => b.olusturma.compareTo(a.olusturma)); state = list; } /// Karakter klasörüne yüz fotoğrafını kalıcı kopyalar. /// Timestamp ile unique path — Flutter image cache'inden kaçınır. Future _yuzuKopyala(String kaynakYolu, String karakterId) async { final docs = await getApplicationDocumentsDirectory(); final hedefKlasor = Directory(p.join(docs.path, 'FikoYT-VideoStudyo', 'karakterler')); if (!await hedefKlasor.exists()) { await hedefKlasor.create(recursive: true); } final ext = p.extension(kaynakYolu); final ts = DateTime.now().millisecondsSinceEpoch; final hedef = p.join(hedefKlasor.path, '${karakterId}_yuz_$ts$ext'); await File(kaynakYolu).copy(hedef); return hedef; } /// Karakter klasörüne ses dosyasını (ref/model/index) kalıcı kopyalar. /// suffix: 'sesRef' | 'sesModel' | 'sesIndex' Future _sesiKopyala( String kaynakYolu, String karakterId, String suffix) async { final docs = await getApplicationDocumentsDirectory(); final hedefKlasor = Directory(p.join(docs.path, 'FikoYT-VideoStudyo', 'karakterler')); if (!await hedefKlasor.exists()) { await hedefKlasor.create(recursive: true); } final ext = p.extension(kaynakYolu); final ts = DateTime.now().millisecondsSinceEpoch; final hedef = p.join(hedefKlasor.path, '${karakterId}_${suffix}_$ts$ext'); await File(kaynakYolu).copy(hedef); return hedef; } Future ekle({ required String isim, required String tanim, String? yuzKaynagi, String cinsiyet = 'kişi', String? notlar, }) async { final id = FikoYTId.yeni(); String? yuzKalici; if (yuzKaynagi != null && File(yuzKaynagi).existsSync()) { yuzKalici = await _yuzuKopyala(yuzKaynagi, id); } final k = Karakter( id: id, isim: isim, tanim: tanim, yuzRefYolu: yuzKalici, cinsiyet: cinsiyet, notlar: notlar, olusturma: DateTime.now(), ); final b = await _box(); await b.put(id, k.toJson()); await _yukle(); return k; } Future guncelle( Karakter k, { String? yeniYuzKaynagi, String? yeniSesRefKaynagi, String? yeniSesModelKaynagi, String? yeniSesIndexKaynagi, bool sesRefSil = false, bool sesModelIndexSil = false, }) async { if (yeniYuzKaynagi != null && File(yeniYuzKaynagi).existsSync()) { // Eski yüzün image cache'ini ve dosyasını temizle final eskiYol = k.yuzRefYolu; if (eskiYol != null) { try { PaintingBinding.instance.imageCache.evict(FileImage(File(eskiYol))); } catch (_) {} if (File(eskiYol).existsSync()) { try { await File(eskiYol).delete(); } catch (_) {} } } k.yuzRefYolu = await _yuzuKopyala(yeniYuzKaynagi, k.id); } // Ses referansı if (sesRefSil) { final eski = k.sesRefYolu; if (eski != null && File(eski).existsSync()) { try { await File(eski).delete(); } catch (_) {} } k.sesRefYolu = null; } else if (yeniSesRefKaynagi != null && File(yeniSesRefKaynagi).existsSync()) { final eski = k.sesRefYolu; if (eski != null && File(eski).existsSync()) { try { await File(eski).delete(); } catch (_) {} } k.sesRefYolu = await _sesiKopyala(yeniSesRefKaynagi, k.id, 'sesRef'); } // RVC model + index ikilisi if (sesModelIndexSil) { for (final eski in [k.sesModelYolu, k.sesIndexYolu]) { if (eski != null && File(eski).existsSync()) { try { await File(eski).delete(); } catch (_) {} } } k.sesModelYolu = null; k.sesIndexYolu = null; } else { if (yeniSesModelKaynagi != null && File(yeniSesModelKaynagi).existsSync()) { final eski = k.sesModelYolu; if (eski != null && File(eski).existsSync()) { try { await File(eski).delete(); } catch (_) {} } k.sesModelYolu = await _sesiKopyala(yeniSesModelKaynagi, k.id, 'sesModel'); } if (yeniSesIndexKaynagi != null && File(yeniSesIndexKaynagi).existsSync()) { final eski = k.sesIndexYolu; if (eski != null && File(eski).existsSync()) { try { await File(eski).delete(); } catch (_) {} } k.sesIndexYolu = await _sesiKopyala(yeniSesIndexKaynagi, k.id, 'sesIndex'); } } final b = await _box(); await b.put(k.id, k.toJson()); await _yukle(); } /// Karakter'e sihirbaz form snapshot'ını + sabit seed'i kaydet. /// "Bu karakteri seçtiğimde sihirbaz hep bu değerlerle açılsın" için. Future sihirbazKaydet( String karakterId, { Map? snapshot, int? sabitSeed, }) async { final idx = state.indexWhere((e) => e.id == karakterId); if (idx == -1) return; final k = state[idx]; if (snapshot != null) k.sihirbazSnapshot = Map.from(snapshot); k.sabitSeed = sabitSeed; final b = await _box(); await b.put(k.id, k.toJson()); await _yukle(); } /// Karakter'in sihirbaz snapshot'ını temizle (formdaki "Sıfırla" butonu için) Future sihirbazSil(String karakterId) async { final idx = state.indexWhere((e) => e.id == karakterId); if (idx == -1) return; final k = state[idx]; k.sihirbazSnapshot = null; k.sabitSeed = null; final b = await _box(); await b.put(k.id, k.toJson()); await _yukle(); } Future sil(String id) async { final k = state.firstWhere( (e) => e.id == id, orElse: () => Karakter( id: '', isim: '', tanim: '', olusturma: DateTime.now(), ), ); if (k.yuzRefYolu != null && File(k.yuzRefYolu!).existsSync()) { try { await File(k.yuzRefYolu!).delete(); } catch (_) {} } for (final ses in [k.sesRefYolu, k.sesModelYolu, k.sesIndexYolu]) { if (ses != null && File(ses).existsSync()) { try { await File(ses).delete(); } catch (_) {} } } final b = await _box(); await b.delete(id); state = state.where((e) => e.id != id).toList(); } } final karakterlerProvider = StateNotifierProvider>( (ref) => KarakterDurumu(), ); /// Aktif karakter — form bunun yüzünü+tanımını otomatik kullanır final aktifKarakterIdProvider = StateProvider((ref) => null); /// Aktif karakter objesi final aktifKarakterProvider = Provider((ref) { final id = ref.watch(aktifKarakterIdProvider); if (id == null) return null; final liste = ref.watch(karakterlerProvider); try { return liste.firstWhere((k) => k.id == id); } catch (_) { return null; } });