import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../durum/proje_durumu.dart'; import '../../modeller/altyazi_satiri.dart'; import '../../modeller/cikis_ayarlari.dart'; import '../../servisler/srt_yardimcisi.dart'; class CikisAyarlariSayfasi extends ConsumerWidget { const CikisAyarlariSayfasi({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final proje = ref.watch(projeProvider); if (proje == null) { return const Center(child: Text('Önce bir proje aç')); } final c = proje.cikis; final theme = Theme.of(context); return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Çıkış Ayarları', style: theme.textTheme.headlineSmall), const SizedBox(height: 16), // Renk düzeltme Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Renk Düzeltme', style: theme.textTheme.titleMedium), const SizedBox(height: 12), _RenkDuzeltmePaneli( rd: c.renkDuzeltme ?? const RenkDuzeltme(), onDegisti: (yeni) => ref.read(projeProvider.notifier).guncelle( (p) => p.copyWith( cikis: p.cikis.copyWith(renkDuzeltme: yeni), ), ), ), ], ), ), ), const SizedBox(height: 12), // Watermark Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Watermark / Logo', style: theme.textTheme.titleMedium), const SizedBox(height: 12), _WatermarkPaneli( wm: c.watermark, onDegisti: (yeni) => ref.read(projeProvider.notifier).guncelle( (p) => p.copyWith( cikis: p.cikis.copyWith(watermark: yeni), ), ), ), ], ), ), ), const SizedBox(height: 12), // Altyazı Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Altyazı', style: theme.textTheme.titleMedium), const SizedBox(height: 12), _AltyaziPaneli( a: c.altyazi ?? const Altyazi(), onDegisti: (yeni) => ref.read(projeProvider.notifier).guncelle( (p) => p.copyWith( cikis: p.cikis.copyWith(altyazi: yeni), ), ), ), ], ), ), ), const SizedBox(height: 12), // Loudnorm + Ses karışımı Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Ses Karışımı', style: theme.textTheme.titleMedium), const SizedBox(height: 8), SwitchListTile( contentPadding: EdgeInsets.zero, title: const Text('Loudness Normalize (-14 LUFS)'), subtitle: const Text( 'YouTube/Spotify standartına eşitle', style: TextStyle(fontSize: 11), ), value: c.loudnessNormalize, onChanged: (v) => ref .read(projeProvider.notifier) .guncelle((p) => p.copyWith( cikis: p.cikis.copyWith(loudnessNormalize: v), )), ), const SizedBox(height: 8), Text('Ses karışım modu', style: theme.textTheme.labelMedium), Wrap( spacing: 8, children: SesKarisimiModu.values .map((m) => ChoiceChip( label: Text(_modIsmi(m)), selected: c.sesModu == m, onSelected: (_) => ref .read(projeProvider.notifier) .guncelle((p) => p.copyWith( cikis: p.cikis.copyWith(sesModu: m), )), )) .toList(), ), ], ), ), ), ], ), ); } String _modIsmi(SesKarisimiModu m) => switch (m) { SesKarisimiModu.tamOrijinal => 'Orijinal şarkı', SesKarisimiModu.segmentSesleri => 'Segment sesleri', SesKarisimiModu.mix => 'Mix', SesKarisimiModu.sessiz => 'Sessiz', }; } class _RenkDuzeltmePaneli extends StatelessWidget { final RenkDuzeltme rd; final ValueChanged onDegisti; const _RenkDuzeltmePaneli({required this.rd, required this.onDegisti}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Önizleme Container( height: 80, decoration: BoxDecoration( gradient: const LinearGradient( colors: [ Color(0xFF1E3A8A), Color(0xFF581C87), Color(0xFFEC4899), ], ), borderRadius: BorderRadius.circular(6), ), child: ColorFiltered( colorFilter: ColorFilter.matrix([ rd.kontrast, 0, 0, 0, rd.parlaklik * 255, 0, rd.kontrast, 0, 0, rd.parlaklik * 255, 0, 0, rd.kontrast, 0, rd.parlaklik * 255, 0, 0, 0, 1, 0, ]), child: const SizedBox.expand(), ), ), const SizedBox(height: 8), _slider('Parlaklık', rd.parlaklik, -1, 1, (v) => onDegisti(rd.copyWith(parlaklik: v))), _slider('Kontrast', rd.kontrast, 0.5, 2.0, (v) => onDegisti(rd.copyWith(kontrast: v))), _slider('Doygunluk', rd.doygunluk, 0, 2.0, (v) => onDegisti(rd.copyWith(doygunluk: v))), const SizedBox(height: 8), Row( children: [ const Text('LUT (.cube): ', style: TextStyle(fontSize: 12)), Expanded( child: Text( rd.lutPath ?? 'yok', overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11), ), ), if (rd.lutPath != null) IconButton( icon: const Icon(Icons.clear, size: 16), onPressed: () => onDegisti(rd.copyWith(lutPath: null)), ), TextButton( onPressed: () async { final r = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['cube'], ); if (r != null && r.files.single.path != null) { onDegisti(rd.copyWith(lutPath: r.files.single.path)); } }, child: const Text('Yükle'), ), ], ), if (rd.parlaklik != 0 || rd.kontrast != 1 || rd.doygunluk != 1 || rd.lutPath != null) TextButton.icon( icon: const Icon(Icons.refresh, size: 14), label: const Text('Sıfırla'), onPressed: () => onDegisti(null), ), ], ); } Widget _slider(String etiket, double v, double min, double max, ValueChanged cb) { return Row( children: [ SizedBox(width: 80, child: Text(etiket, style: const TextStyle(fontSize: 12))), Expanded( child: Slider( value: v, min: min, max: max, label: v.toStringAsFixed(2), onChanged: cb, ), ), SizedBox( width: 50, child: Text( v.toStringAsFixed(2), textAlign: TextAlign.right, style: const TextStyle(fontSize: 11, fontFamily: 'monospace'), ), ), ], ); } } class _WatermarkPaneli extends StatelessWidget { final Watermark? wm; final ValueChanged onDegisti; const _WatermarkPaneli({this.wm, required this.onDegisti}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (wm == null) Row( children: [ const Expanded(child: Text('Watermark seçilmemiş')), FilledButton.icon( icon: const Icon(Icons.add_photo_alternate_outlined, size: 16), label: const Text('Logo seç'), onPressed: () async { final r = await FilePicker.platform.pickFiles( type: FileType.image, ); if (r != null && r.files.single.path != null) { onDegisti(Watermark(path: r.files.single.path!)); } }, ), ], ) else ...[ // Önizleme + konum seçici Container( height: 200, decoration: BoxDecoration( color: Colors.grey.shade900, borderRadius: BorderRadius.circular(6), ), child: Stack( children: [ Positioned.fill( child: Container( decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.indigo, Colors.purple, Colors.pink], ), borderRadius: BorderRadius.circular(6), ), ), ), ..._konumlar.map((k) => _konumNoktasi(k, theme)), if (File(wm!.path).existsSync()) _wmPlasmani(), ], ), ), const SizedBox(height: 8), Row( children: [ Expanded( child: Text( wm!.path, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11), ), ), IconButton( icon: const Icon(Icons.clear, size: 16), onPressed: () => onDegisti(null), ), ], ), DropdownButtonFormField( initialValue: wm!.konum, decoration: const InputDecoration(labelText: 'Konum'), items: _konumlar .map((k) => DropdownMenuItem(value: k, child: Text(_konumIsmi(k)))) .toList(), onChanged: (v) { if (v != null) onDegisti(wm!.copyWith(konum: v)); }, ), Row( children: [ const SizedBox(width: 80, child: Text('Opaklık', style: TextStyle(fontSize: 12))), Expanded( child: Slider( value: wm!.opaklik, min: 0, max: 1, label: wm!.opaklik.toStringAsFixed(2), onChanged: (v) => onDegisti(wm!.copyWith(opaklik: v)), ), ), ], ), Row( children: [ const SizedBox(width: 80, child: Text('Boyut', style: TextStyle(fontSize: 12))), Expanded( child: Slider( value: wm!.genislikOran, min: 0.05, max: 0.5, label: '${(wm!.genislikOran * 100).toStringAsFixed(0)}%', onChanged: (v) => onDegisti(wm!.copyWith(genislikOran: v)), ), ), ], ), ], ], ); } static const _konumlar = [ 'top-left', 'top-right', 'bottom-left', 'bottom-right', ]; String _konumIsmi(String k) => switch (k) { 'top-left' => 'Sol Üst', 'top-right' => 'Sağ Üst', 'bottom-left' => 'Sol Alt', 'bottom-right' => 'Sağ Alt', _ => k, }; Widget _konumNoktasi(String k, ThemeData theme) { final secili = wm!.konum == k; AlignmentGeometry alig; switch (k) { case 'top-left': alig = Alignment.topLeft; break; case 'top-right': alig = Alignment.topRight; break; case 'bottom-left': alig = Alignment.bottomLeft; break; default: alig = Alignment.bottomRight; } return Align( alignment: alig, child: Padding( padding: const EdgeInsets.all(8), child: GestureDetector( onTap: () => onDegisti(wm!.copyWith(konum: k)), child: Container( width: 16, height: 16, decoration: BoxDecoration( color: secili ? Colors.amber : Colors.white.withValues(alpha: 0.5), shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 2), ), ), ), ), ); } Widget _wmPlasmani() { AlignmentGeometry alig; switch (wm!.konum) { case 'top-left': alig = Alignment.topLeft; break; case 'top-right': alig = Alignment.topRight; break; case 'bottom-left': alig = Alignment.bottomLeft; break; default: alig = Alignment.bottomRight; } return Align( alignment: alig, child: Padding( padding: const EdgeInsets.all(20), child: Opacity( opacity: wm!.opaklik, child: SizedBox( width: 200 * wm!.genislikOran * 2, child: Image.file(File(wm!.path), fit: BoxFit.contain), ), ), ), ); } } class _AltyaziPaneli extends StatefulWidget { final Altyazi a; final ValueChanged onDegisti; const _AltyaziPaneli({required this.a, required this.onDegisti}); @override State<_AltyaziPaneli> createState() => _AltyaziPaneliState(); } class _AltyaziPaneliState extends State<_AltyaziPaneli> { List? _satirlar; @override void initState() { super.initState(); _satirlariYukle(); } Future _satirlariYukle() async { if (widget.a.srtPath != null && File(widget.a.srtPath!).existsSync()) { final s = await SrtYardimcisi.oku(widget.a.srtPath!); if (mounted) setState(() => _satirlar = s); } } @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SwitchListTile( contentPadding: EdgeInsets.zero, title: const Text('Whisper otomatik altyazı'), subtitle: const Text( 'Whisper.cpp ile şarkıdan transkript çıkar (Whisper kurulu olmalı)', style: TextStyle(fontSize: 11), ), value: widget.a.whisperOtomatik, onChanged: (v) => widget.onDegisti(widget.a.copyWith(whisperOtomatik: v)), ), const Divider(), Row( children: [ const Expanded(child: Text('Manuel SRT dosyası:')), if (widget.a.srtPath != null) IconButton( icon: const Icon(Icons.clear, size: 16), onPressed: () => widget.onDegisti(widget.a.copyWith(srtPath: null)), ), TextButton( onPressed: () async { final r = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['srt'], ); if (r != null && r.files.single.path != null) { widget.onDegisti(widget.a.copyWith(srtPath: r.files.single.path)); await _satirlariYukle(); } }, child: const Text('SRT yükle'), ), ], ), if (widget.a.srtPath != null) Text(widget.a.srtPath!, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11)), const SizedBox(height: 8), if (_satirlar != null && _satirlar!.isNotEmpty) ...[ Text('${_satirlar!.length} satır', style: const TextStyle(fontSize: 12)), Container( height: 200, margin: const EdgeInsets.only(top: 4), decoration: BoxDecoration( border: Border.all(color: Theme.of(context).dividerColor), borderRadius: BorderRadius.circular(4), ), child: ListView.builder( itemCount: _satirlar!.length, itemBuilder: (c, i) { final s = _satirlar![i]; return Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Row( children: [ SizedBox( width: 90, child: Text( '${s.basla.toStringAsFixed(1)}-${s.bit.toStringAsFixed(1)}', style: const TextStyle( fontSize: 10, fontFamily: 'monospace'), ), ), Expanded( child: Text(s.metin, style: const TextStyle(fontSize: 11)), ), ], ), ); }, ), ), ], const Divider(), Row( children: [ const SizedBox(width: 60, child: Text('Font:')), Expanded( child: TextFormField( initialValue: widget.a.font, onChanged: (v) => widget.onDegisti(widget.a.copyWith(font: v)), decoration: const InputDecoration(isDense: true), ), ), const SizedBox(width: 12), const SizedBox(width: 50, child: Text('Boyut:')), SizedBox( width: 60, child: TextFormField( initialValue: widget.a.boyut.toString(), keyboardType: TextInputType.number, onChanged: (v) => widget.onDegisti(widget.a.copyWith(boyut: int.tryParse(v) ?? 28)), decoration: const InputDecoration(isDense: true), ), ), ], ), ], ); } }