import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../app/tema.dart'; import '../cekirdek/ffmpeg_servisi.dart'; import '../karakter/karakter_servisi.dart'; import '../veri/isar_servisi.dart'; import '../veri/modeller/bolum.dart'; import '../veri/modeller/film_proje.dart'; import '../veri/modeller/karakter.dart'; import '../veri/modeller/sahne.dart'; import 'ai_senaryo.dart'; import 'film_montaj_servisi.dart'; import 'film_servisi.dart'; import 'sahne_duzenle.dart'; import 'sahne_uretim_servisi.dart'; /// Film/Dizi Stüdyosu — PROJE → BÖLÜM → SAHNE orkestra katmanı. class FilmEkrani extends ConsumerWidget { const FilmEkrani({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { return const Padding( padding: EdgeInsets.fromLTRB(12, 10, 12, 8), child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox(width: 240, child: _ProjePaneli()), SizedBox(width: 12), Expanded(child: _IcerikPaneli()), ], ), ); } } // ============================ SOL: PROJELER ============================ class _ProjePaneli extends ConsumerWidget { const _ProjePaneli(); @override Widget build(BuildContext context, WidgetRef ref) { final projeler = ref.watch(projeListesiProvider).valueOrNull ?? []; final aktif = ref.watch(aktifProjeProvider); return Card( child: Padding( padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row(children: [ const Icon(Icons.movie_filter_rounded, size: 18, color: FikoRenkler.neonMor), const SizedBox(width: 7), const Expanded( child: Text('Projeler', style: TextStyle(fontSize: 13.5, fontWeight: FontWeight.w800)), ), ]), const SizedBox(height: 8), _GradyanButon( etiket: 'Yeni Proje', ikon: Icons.add, renkler: const [FikoRenkler.neonMor, FikoRenkler.neonCyan], onTap: () => _projeDuzenle(context, ref, FilmProje()), ), const SizedBox(height: 8), Expanded( child: projeler.isEmpty ? const _Bos( ikon: Icons.movie_outlined, mesaj: 'Henüz proje yok', altMesaj: '"Yeni Proje" ile başla') : ListView.separated( itemCount: projeler.length, separatorBuilder: (_, _) => const SizedBox(height: 6), itemBuilder: (_, i) => _ProjeKart( proje: projeler[i], secili: projeler[i].id == aktif, ), ), ), ], ), ), ); } } class _ProjeKart extends ConsumerWidget { const _ProjeKart({required this.proje, required this.secili}); final FilmProje proje; final bool secili; @override Widget build(BuildContext context, WidgetRef ref) { final t = Theme.of(context); final dizi = proje.tip == ProjeTipi.dizi; return GestureDetector( onTap: () { ref.read(aktifProjeProvider.notifier).state = proje.id; ref.read(aktifBolumProvider.notifier).state = null; }, child: AnimatedContainer( duration: const Duration(milliseconds: 150), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 9), decoration: BoxDecoration( color: secili ? FikoRenkler.neonMor.withValues(alpha: 0.13) : t.colorScheme.surface, borderRadius: BorderRadius.circular(10), border: Border.all( color: secili ? FikoRenkler.neonMor.withValues(alpha: 0.5) : t.dividerColor), boxShadow: secili ? [ BoxShadow( color: FikoRenkler.neonMor.withValues(alpha: 0.16), blurRadius: 12, spreadRadius: 1) ] : null, ), child: Row(children: [ Icon(dizi ? Icons.live_tv_rounded : Icons.theaters_rounded, size: 17, color: secili ? FikoRenkler.neonMor : t.colorScheme.onSurface.withValues(alpha: 0.6)), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(proje.ad.isEmpty ? 'İsimsiz' : proje.ad, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 12.5, fontWeight: secili ? FontWeight.w800 : FontWeight.w600)), Text( [ dizi ? 'Dizi' : 'Film', if (proje.tur.isNotEmpty) proje.tur, ].join(' • '), maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 9.5, color: t.colorScheme.onSurface.withValues(alpha: 0.5))), ], ), ), InkWell( onTap: () => _projeDuzenle(context, ref, proje), child: const Padding( padding: EdgeInsets.all(2), child: Icon(Icons.edit, size: 14)), ), ]), ), ); } } // ====================== ORTA: BÖLÜM / SAHNE PANOSU ====================== class _IcerikPaneli extends ConsumerWidget { const _IcerikPaneli(); @override Widget build(BuildContext context, WidgetRef ref) { final projeId = ref.watch(aktifProjeProvider); if (projeId == null) { return const Card( child: _Bos( ikon: Icons.theaters_rounded, mesaj: 'Bir proje seç', altMesaj: 'Soldan bir proje seç veya yeni oluştur'), ); } final proje = (ref.watch(projeListesiProvider).valueOrNull ?? []).where((p) => p.id == projeId); if (proje.isEmpty) return const SizedBox.shrink(); final p = proje.first; final bolumId = ref.watch(aktifBolumProvider); return Card( child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ _BolumSeridi(proje: p), const Divider(height: 18), Expanded( child: bolumId == null ? const _Bos( ikon: Icons.dashboard_customize_rounded, mesaj: 'Bir bölüm seç', altMesaj: 'Storyboard panosu için bir bölüm aç') : _SahnePanosu(bolumId: bolumId), ), ], ), ), ); } } class _BolumSeridi extends ConsumerWidget { const _BolumSeridi({required this.proje}); final FilmProje proje; @override Widget build(BuildContext context, WidgetRef ref) { final bolumler = ref.watch(bolumListesiProvider).valueOrNull ?? []; final aktif = ref.watch(aktifBolumProvider); final dizi = proje.tip == ProjeTipi.dizi; return Row(children: [ Icon(dizi ? Icons.live_tv_rounded : Icons.theaters_rounded, size: 18, color: FikoRenkler.neonMor), const SizedBox(width: 8), Text(proje.ad, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w800)), const SizedBox(width: 14), Expanded( child: bolumler.isEmpty ? Text('Henüz bölüm yok', style: TextStyle( fontSize: 11, color: Theme.of(context) .colorScheme .onSurface .withValues(alpha: 0.45))) : SizedBox( height: 30, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: bolumler.length, separatorBuilder: (_, _) => const SizedBox(width: 6), itemBuilder: (_, i) { final b = bolumler[i]; final secili = b.id == aktif; final etiket = dizi ? 'S${b.sezonNo}B${b.bolumNo}' : (b.baslik.isEmpty ? 'Bölüm' : b.baslik); return GestureDetector( onTap: () => ref.read(aktifBolumProvider.notifier).state = b.id, onLongPress: () => _bolumDuzenle(context, ref, proje, b), child: Container( padding: const EdgeInsets.symmetric( horizontal: 11, vertical: 5), decoration: BoxDecoration( color: secili ? FikoRenkler.neonMor.withValues(alpha: 0.2) : Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(8), border: Border.all( color: secili ? FikoRenkler.neonMor : Theme.of(context).dividerColor), ), child: Tooltip( message: b.baslik.isEmpty ? etiket : b.baslik, child: Text(etiket, style: TextStyle( fontSize: 11.5, fontWeight: secili ? FontWeight.w800 : FontWeight.w600, color: secili ? FikoRenkler.neonMor : Theme.of(context).colorScheme.onSurface)), ), ), ); }, ), ), ), const SizedBox(width: 8), OutlinedButton.icon( style: OutlinedButton.styleFrom(visualDensity: VisualDensity.compact), icon: const Icon(Icons.add, size: 15), label: const Text('Bölüm', style: TextStyle(fontSize: 11.5)), onPressed: () => _bolumDuzenle(context, ref, proje, Bolum()), ), ]); } } // ----------------------- STORYBOARD PANOSU ----------------------- class _SahnePanosu extends ConsumerWidget { const _SahnePanosu({required this.bolumId}); final int bolumId; @override Widget build(BuildContext context, WidgetRef ref) { // Üretim tamamlanma takibini (orkestratör dinleyicisi) aktif tut. ref.watch(sahneUretimServisiProvider); final sahneler = ref.watch(sahneListesiProvider).valueOrNull ?? []; final karakterler = ref.watch(karakterlerProvider).valueOrNull ?? []; final karakterAd = {for (final k in karakterler) k.id: k}; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row(children: [ const Icon(Icons.dashboard_rounded, size: 16, color: FikoRenkler.neonCyan), const SizedBox(width: 6), const Text('Storyboard Panosu', style: TextStyle(fontSize: 12.5, fontWeight: FontWeight.w800)), const SizedBox(width: 8), Text('${sahneler.length} sahne', style: TextStyle( fontSize: 10.5, color: Theme.of(context) .colorScheme .onSurface .withValues(alpha: 0.5))), const Spacer(), Builder(builder: (context) { final hazir = sahneler .where((s) => s.durum == SahneDurumu.hazir && s.uretilenVideoYolu.isNotEmpty) .length; return OutlinedButton.icon( style: OutlinedButton.styleFrom( visualDensity: VisualDensity.compact, foregroundColor: FikoRenkler.neonYesil, side: const BorderSide(color: FikoRenkler.neonYesil), padding: const EdgeInsets.symmetric(horizontal: 10), ), icon: const Icon(Icons.movie_filter_rounded, size: 15), label: Text('🎬 Bölümü Birleştir${hazir > 0 ? ' ($hazir)' : ''}', style: const TextStyle(fontSize: 11.5)), onPressed: hazir == 0 ? null : () => _bolumuBirlestir(context, ref, bolumId), ); }), const SizedBox(width: 8), OutlinedButton.icon( style: OutlinedButton.styleFrom( visualDensity: VisualDensity.compact, foregroundColor: FikoRenkler.neonAmber, side: const BorderSide(color: FikoRenkler.neonAmber), padding: const EdgeInsets.symmetric(horizontal: 10), ), icon: const Icon(Icons.auto_fix_high_rounded, size: 15), label: const Text('🤖 AI Senaryo', style: TextStyle(fontSize: 11.5)), onPressed: () => aiSenaryoAc(context, ref, bolumId), ), const SizedBox(width: 8), _GradyanButon( etiket: 'Yeni Sahne', ikon: Icons.add_photo_alternate_rounded, renkler: const [FikoRenkler.neonCyan, FikoRenkler.neonMor], dar: true, onTap: () async { final s = Sahne() ..bolumId = bolumId ..sira = await FilmServisi.sonrakiSira(bolumId); await FilmServisi.sahneKaydet(s); }, ), ]), const SizedBox(height: 10), Expanded( child: sahneler.isEmpty ? const _Bos( ikon: Icons.add_photo_alternate_outlined, mesaj: 'Bu bölümde sahne yok', altMesaj: '"Yeni Sahne" ile panoya kart ekle') : ReorderableListView.builder( buildDefaultDragHandles: false, itemCount: sahneler.length, onReorder: (eski, yeni) async { final liste = [...sahneler]; if (yeni > eski) yeni -= 1; final tasinan = liste.removeAt(eski); liste.insert(yeni, tasinan); await FilmServisi.sahneSiralariniGuncelle(liste); }, itemBuilder: (_, i) => Padding( key: ValueKey(sahneler[i].id), padding: const EdgeInsets.only(bottom: 10), child: _SahneKart( sahne: sahneler[i], indeks: i, karakterAd: karakterAd), ), ), ), ], ); } Future _bolumuBirlestir( BuildContext context, WidgetRef ref, int bolumId) async { final klasor = ref.read(ayarlarProvider).ciktiKlasoru; final ilerleme = ValueNotifier(0); showDialog( context: context, barrierDismissible: false, builder: (_) => AlertDialog( content: Row(children: [ ValueListenableBuilder( valueListenable: ilerleme, builder: (_, v, child) => SizedBox( width: 34, height: 34, child: CircularProgressIndicator( value: v > 0 ? v : null, strokeWidth: 3), ), ), const SizedBox(width: 16), const Expanded(child: Text('Bölüm birleştiriliyor…')), ]), ), ); String? sonuc; try { sonuc = await FilmMontajServisi.bolumuBirlestir( bolumId: bolumId, ciktiKlasoru: klasor, ilerle: (o) => ilerleme.value = o, ); } catch (_) {} if (context.mounted) { Navigator.of(context, rootNavigator: true).pop(); } if (!context.mounted) return; final msj = ScaffoldMessenger.of(context); if (sonuc != null) { msj.showSnackBar(SnackBar( backgroundColor: FikoRenkler.neonYesil.withValues(alpha: 0.92), content: Text('🎬 Bölüm birleştirildi:\n$sonuc', style: const TextStyle(color: Colors.black)), )); } else { final h = FfmpegServisi.sonHata; msj.showSnackBar(SnackBar( backgroundColor: Colors.red.shade700, content: Text(h != null ? 'Birleştirme başarısız:\n$h' : 'Birleştirilecek hazır sahne videosu bulunamadı.'), )); } } } class _SahneKart extends ConsumerWidget { const _SahneKart( {required this.sahne, required this.indeks, required this.karakterAd}); final Sahne sahne; final int indeks; final Map karakterAd; @override Widget build(BuildContext context, WidgetRef ref) { final t = Theme.of(context); final (durumRenk, durumEtiket) = switch (sahne.durum) { SahneDurumu.taslak => ( t.colorScheme.onSurface.withValues(alpha: 0.5), 'Taslak' ), SahneDurumu.uretiliyor => (FikoRenkler.neonAmber, 'Üretiliyor'), SahneDurumu.hazir => (FikoRenkler.neonYesil, 'Hazır'), }; final keyframeVar = sahne.keyframeYolu.isNotEmpty && File(sahne.keyframeYolu).existsSync(); final uretiliyor = sahne.durum == SahneDurumu.uretiliyor; return Container( decoration: BoxDecoration( color: t.colorScheme.surface, borderRadius: BorderRadius.circular(11), border: Border.all(color: t.dividerColor), ), clipBehavior: Clip.antiAlias, child: IntrinsicHeight( child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Sürükle tutamacı + sıra no ReorderableDragStartListener( index: indeks, child: Container( width: 34, color: FikoRenkler.neonMor.withValues(alpha: 0.08), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('${indeks + 1}', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w800, color: FikoRenkler.neonMor)), const SizedBox(height: 4), Icon(Icons.drag_indicator, size: 16, color: t.colorScheme.onSurface.withValues(alpha: 0.4)), ], ), ), ), // Keyframe önizleme Container( width: 116, color: Colors.black26, child: keyframeVar ? Image.file(File(sahne.keyframeYolu), fit: BoxFit.cover, cacheWidth: 240) : Center( child: Icon( sahne.durum == SahneDurumu.uretiliyor ? Icons.hourglass_top_rounded : Icons.image_outlined, size: 28, color: t.colorScheme.onSurface.withValues(alpha: 0.3))), ), // İçerik Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(11, 9, 8, 9), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Expanded( child: Text( sahne.aciklama.isEmpty ? 'Sahne ${indeks + 1}' : sahne.aciklama, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 12.5, fontWeight: FontWeight.w700)), ), Container( padding: const EdgeInsets.symmetric( horizontal: 7, vertical: 2), decoration: BoxDecoration( color: durumRenk.withValues(alpha: 0.16), borderRadius: BorderRadius.circular(6), ), child: Text(durumEtiket, style: TextStyle( fontSize: 9.5, fontWeight: FontWeight.w800, color: durumRenk)), ), ]), const SizedBox(height: 3), Wrap(spacing: 8, runSpacing: 2, children: [ if (sahne.mekan.isNotEmpty) _meta(t, Icons.place_outlined, sahne.mekan), if (sahne.zaman.isNotEmpty) _meta(t, Icons.schedule, sahne.zaman), if (sahne.ruhHali.isNotEmpty) _meta(t, Icons.mood, sahne.ruhHali), _meta(t, Icons.timer_outlined, '${sahne.sureSn.toStringAsFixed(0)} sn'), ]), if (sahne.karakterIdler.isNotEmpty) ...[ const SizedBox(height: 5), Wrap(spacing: 5, runSpacing: 4, children: [ for (final id in sahne.karakterIdler) _KarakterCip(karakter: karakterAd[id]), ]), ], const Spacer(), Row(children: [ TextButton.icon( style: TextButton.styleFrom( visualDensity: VisualDensity.compact, padding: const EdgeInsets.symmetric(horizontal: 8)), icon: const Icon(Icons.edit, size: 14), label: const Text('Düzenle', style: TextStyle(fontSize: 11)), onPressed: () => sahneDuzenleAc(context, ref, sahne), ), const SizedBox(width: 4), uretiliyor ? const Padding( padding: EdgeInsets.symmetric(horizontal: 14), child: SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, color: FikoRenkler.neonAmber)), ) : FilledButton.icon( style: FilledButton.styleFrom( visualDensity: VisualDensity.compact, padding: const EdgeInsets.symmetric(horizontal: 10), backgroundColor: FikoRenkler.neonMor.withValues(alpha: 0.85), ), icon: const Icon(Icons.auto_awesome, size: 14), label: const Text('Üret', style: TextStyle(fontSize: 11)), onPressed: () => _uret(context, ref), ), const Spacer(), InkWell( onTap: () => FilmServisi.sahneSil(sahne.id), child: Padding( padding: const EdgeInsets.all(4), child: Icon(Icons.delete_outline, size: 16, color: t.colorScheme.onSurface .withValues(alpha: 0.5)), ), ), ]), ], ), ), ), ], ), ), ); } Widget _meta(ThemeData t, IconData ikon, String metin) => Row( mainAxisSize: MainAxisSize.min, children: [ Icon(ikon, size: 12, color: t.colorScheme.onSurface.withValues(alpha: 0.55)), const SizedBox(width: 3), Text(metin, style: TextStyle( fontSize: 10, color: t.colorScheme.onSurface.withValues(alpha: 0.65))), ], ); Future _uret(BuildContext context, WidgetRef ref) async { final messenger = ScaffoldMessenger.of(context); try { await ref.read(sahneUretimServisiProvider).uret(sahne); messenger.showSnackBar(const SnackBar( content: Text('Sahne MSR motoruna gönderildi. Üretiliyor…'), duration: Duration(seconds: 2), )); } on SahneUretimHatasi catch (e) { messenger.showSnackBar(SnackBar( content: Text(e.mesaj), backgroundColor: Colors.redAccent, duration: const Duration(seconds: 4), )); } } } class _KarakterCip extends StatelessWidget { const _KarakterCip({required this.karakter}); final Karakter? karakter; @override Widget build(BuildContext context) { final ad = karakter?.ad.isNotEmpty == true ? karakter!.ad : 'silinmiş'; return Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( color: FikoRenkler.neonYesil.withValues(alpha: 0.13), borderRadius: BorderRadius.circular(20), border: Border.all(color: FikoRenkler.neonYesil.withValues(alpha: 0.4)), ), child: Row(mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.face, size: 11, color: FikoRenkler.neonYesil), const SizedBox(width: 4), Text(ad, style: const TextStyle( fontSize: 10, fontWeight: FontWeight.w600, color: FikoRenkler.neonYesil)), ]), ); } } // ============================ ORTAK PARÇALAR ============================ class _GradyanButon extends StatelessWidget { const _GradyanButon( {required this.etiket, required this.ikon, required this.renkler, required this.onTap, this.dar = false}); final String etiket; final IconData ikon; final List renkler; final VoidCallback onTap; final bool dar; @override Widget build(BuildContext context) { return DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), gradient: LinearGradient(colors: renkler), ), child: FilledButton.icon( style: FilledButton.styleFrom( backgroundColor: Colors.transparent, shadowColor: Colors.transparent, visualDensity: dar ? VisualDensity.compact : null, padding: dar ? const EdgeInsets.symmetric(horizontal: 12) : null), icon: Icon(ikon, size: dar ? 16 : 18), label: Text(etiket, style: TextStyle( fontWeight: FontWeight.w800, fontSize: dar ? 12 : 13)), onPressed: onTap, ), ); } } class _Bos extends StatelessWidget { const _Bos( {required this.ikon, required this.mesaj, required this.altMesaj}); final IconData ikon; final String mesaj, altMesaj; @override Widget build(BuildContext context) { final soluk = Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.4); return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(ikon, size: 52, color: soluk), const SizedBox(height: 12), Text(mesaj, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: soluk)), const SizedBox(height: 5), Text(altMesaj, textAlign: TextAlign.center, style: TextStyle(fontSize: 11.5, color: soluk)), ], ), ); } } // ============================ DİYALOGLAR ============================ const _projeTurleri = [ 'dram', 'aksiyon', 'komedi', 'gerilim', 'korku', 'bilim kurgu', 'fantastik', 'romantik', 'belgesel', 'animasyon', ]; const _projeStiller = [ 'cinematic', 'photorealistic', 'anime', 'noir', '3d render', 'oil painting', 'fantasy art', 'cyberpunk', ]; const _oranlar = ['16:9', '9:16', '1:1', '2.39:1', '4:3', '21:9']; void _projeDuzenle(BuildContext context, WidgetRef ref, FilmProje p) { final ad = TextEditingController(text: p.ad); var tip = p.tip; var tur = p.tur, stil = p.stil, oran = p.enBoyOrani; final yeni = p.id == _isarAuto; showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setSt) => AlertDialog( title: Text(yeni ? 'Yeni Proje' : 'Proje Düzenle'), content: SizedBox( width: 420, child: SingleChildScrollView( child: Column(mainAxisSize: MainAxisSize.min, children: [ TextField( controller: ad, autofocus: true, decoration: const InputDecoration(labelText: 'Proje adı *')), const SizedBox(height: 10), SegmentedButton( segments: const [ ButtonSegment( value: ProjeTipi.film, label: Text('Film'), icon: Icon(Icons.theaters_rounded, size: 16)), ButtonSegment( value: ProjeTipi.dizi, label: Text('Dizi'), icon: Icon(Icons.live_tv_rounded, size: 16)), ], selected: {tip}, onSelectionChanged: (s) => setSt(() => tip = s.first), ), const SizedBox(height: 10), Row(children: [ Expanded( child: _drop('Tür', _projeTurleri, tur, (v) => setSt(() => tur = v))), const SizedBox(width: 8), Expanded( child: _drop('Stil', _projeStiller, stil, (v) => setSt(() => stil = v))), ]), const SizedBox(height: 8), _drop('En/boy oranı', _oranlar, oran, (v) => setSt(() => oran = v)), ]), ), ), actions: [ if (!yeni) TextButton( onPressed: () { FilmServisi.projeSil(p.id); if (ref.read(aktifProjeProvider) == p.id) { ref.read(aktifProjeProvider.notifier).state = null; ref.read(aktifBolumProvider.notifier).state = null; } Navigator.pop(ctx); }, child: const Text('Sil', style: TextStyle(color: Colors.redAccent)), ), TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Vazgeç')), FilledButton( onPressed: () async { if (ad.text.trim().isEmpty) return; p ..ad = ad.text.trim() ..tip = tip ..tur = tur ..stil = stil ..enBoyOrani = oran; final id = await FilmServisi.projeKaydet(p); ref.read(aktifProjeProvider.notifier).state = id; if (ctx.mounted) Navigator.pop(ctx); }, child: const Text('Kaydet'), ), ], ), ), ); } void _bolumDuzenle( BuildContext context, WidgetRef ref, FilmProje proje, Bolum b) async { final dizi = proje.tip == ProjeTipi.dizi; final yeni = b.id == _isarAuto; if (yeni) { b ..projeId = proje.id ..bolumNo = await FilmServisi.sonrakiBolumNo(proje.id, b.sezonNo); } final baslik = TextEditingController(text: b.baslik); final ozet = TextEditingController(text: b.ozet); final senaryo = TextEditingController(text: b.senaryo); final sezon = TextEditingController(text: '${b.sezonNo}'); final bolumNo = TextEditingController(text: '${b.bolumNo}'); if (!context.mounted) return; showDialog( context: context, builder: (ctx) => AlertDialog( title: Text(yeni ? 'Yeni Bölüm' : 'Bölüm Düzenle'), content: SizedBox( width: 460, child: SingleChildScrollView( child: Column(mainAxisSize: MainAxisSize.min, children: [ if (dizi) Row(children: [ Expanded( child: TextField( controller: sezon, keyboardType: TextInputType.number, decoration: const InputDecoration(labelText: 'Sezon no'))), const SizedBox(width: 8), Expanded( child: TextField( controller: bolumNo, keyboardType: TextInputType.number, decoration: const InputDecoration(labelText: 'Bölüm no'))), ]), if (dizi) const SizedBox(height: 8), TextField( controller: baslik, autofocus: true, decoration: const InputDecoration(labelText: 'Başlık')), const SizedBox(height: 8), TextField( controller: ozet, minLines: 2, maxLines: 3, decoration: const InputDecoration(labelText: 'Özet')), const SizedBox(height: 8), TextField( controller: senaryo, minLines: 3, maxLines: 8, decoration: const InputDecoration(labelText: 'Senaryo metni')), ]), ), ), actions: [ if (!yeni) TextButton( onPressed: () { FilmServisi.bolumSil(b.id); if (ref.read(aktifBolumProvider) == b.id) { ref.read(aktifBolumProvider.notifier).state = null; } Navigator.pop(ctx); }, child: const Text('Sil', style: TextStyle(color: Colors.redAccent)), ), TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Vazgeç')), FilledButton( onPressed: () async { b ..baslik = baslik.text.trim() ..ozet = ozet.text.trim() ..senaryo = senaryo.text.trim() ..sezonNo = int.tryParse(sezon.text.trim()) ?? b.sezonNo ..bolumNo = int.tryParse(bolumNo.text.trim()) ?? b.bolumNo; final id = await FilmServisi.bolumKaydet(b); ref.read(aktifBolumProvider.notifier).state = id; if (ctx.mounted) Navigator.pop(ctx); }, child: const Text('Kaydet'), ), ], ), ); } Widget _drop(String etiket, List opts, String secili, void Function(String) onChange) { final deger = opts.contains(secili) ? secili : null; return DropdownButtonFormField( initialValue: deger, isExpanded: true, decoration: InputDecoration(labelText: etiket, isDense: true), items: [ for (final o in opts) DropdownMenuItem( value: o, child: Text(o, style: const TextStyle(fontSize: 12))) ], onChanged: (v) { if (v != null) onChange(v); }, ); } // Isar.autoIncrement sabiti (yeni kayıt tespiti) // ignore: constant_identifier_names const _isarAuto = -9223372036854775808;