import 'package:flutter/material.dart'; import '../app/tasarim.dart'; import '../app/tema.dart'; /// Negatif prompt sihirbazı — kategorize çip seti, NSFW-farkında. Kontrollü /// widget: [deger] mevcut negatif string'i, [onDegisti] her değişimde çağrılır. /// Resim/Video/Karakter negatif alanlarında ortak kullanılır (her yerde sihirbaz). class _NegKategori { const _NegKategori(this.baslik, this.ikon, this.renk, this.terimler); final String baslik; final IconData ikon; final Color renk; final List<(String, String)> terimler; // (etiketTr, promptEn) } List<_NegKategori> _kategoriler(bool nsfw) => [ const _NegKategori('Kalite Kusurları', Icons.broken_image_outlined, FikoRenkler.neonAmber, [ ('Bulanık', 'blurry'), ('Düşük kalite', 'low quality, worst quality'), ('Düşük çözünürlük', 'low resolution, lowres'), ('JPEG bozulması', 'jpeg artifacts'), ('Pikselli', 'pixelated'), ('Gren/Parazit', 'grainy, noise'), ('Aşırı doygun', 'oversaturated'), ('Soluk', 'washed out, desaturated'), ('Aşırı pozlanmış', 'overexposed'), ('Az pozlanmış', 'underexposed'), ('Kötü ışık', 'bad lighting'), ]), const _NegKategori('Anatomi / Deformasyon', Icons.accessibility_new, FikoRenkler.neonPembe, [ ('Kötü anatomi', 'bad anatomy'), ('Deforme', 'deformed, disfigured'), ('Mutasyon', 'mutated, mutation'), ('Fazla uzuv', 'extra limbs'), ('Eksik uzuv', 'missing limbs'), ('Kötü oran', 'bad proportions'), ('Uzun boyun', 'long neck'), ('Çift gövde', 'duplicate body'), ]), const _NegKategori('Yüz / Eller', Icons.front_hand_outlined, FikoRenkler.neonCyan, [ ('Kötü eller', 'bad hands, malformed hands'), ('Fazla parmak', 'extra fingers'), ('Birleşik parmak', 'fused fingers'), ('Kötü gözler', 'bad eyes, deformed eyes'), ('Şaşı', 'cross-eyed'), ('Çarpık yüz', 'asymmetric face, deformed face'), ('Plastik cilt', 'plastic skin'), ]), const _NegKategori('Stil Artefaktı', Icons.brush_outlined, FikoRenkler.neonMor, [ ('Çizgi film', 'cartoon, anime'), ('3D render', '3d render, cgi'), ('Resim/Çizim', 'painting, drawing, sketch'), ('Filigran', 'watermark'), ('İmza', 'signature'), ('Metin/Yazı', 'text, letters'), ('Logo', 'logo'), ('Çerçeve', 'frame, border'), ]), const _NegKategori('Kompozisyon', Icons.crop_outlined, FikoRenkler.neonYesil, [ ('Kırpılmış', 'cropped, cut off'), ('Kadraj dışı', 'out of frame'), ('Tekrarlı', 'duplicate, cloned'), ('Döşeme/Tiling', 'tiling'), ]), if (nsfw) const _NegKategori('NSFW — Sansür Reddi', Icons.no_encryption_outlined, FikoRenkler.neonPembe, [ ('Sansür', 'censored'), ('Mozaik', 'mosaic censoring'), ('Bar sansür', 'bar censor'), ('Pixel sansür', 'pixelated censor'), ('Kıyafet (istemiyorsan)', 'clothing, clothed'), ]), ]; class NegatifSihirbazi extends StatelessWidget { const NegatifSihirbazi({ super.key, required this.deger, required this.onDegisti, this.nsfw = false, }); final String deger; final ValueChanged onDegisti; final bool nsfw; List get _parcalar => deger .split(',') .map((e) => e.trim()) .where((e) => e.isNotEmpty) .toList(); bool _seciliMi(String terim) { final t = terim.toLowerCase(); final hepsi = deger.toLowerCase(); // Terim çoklu olabilir (ilk kelimeyle kontrol yeterli). return hepsi.contains(t.split(',').first.trim()); } void _degistir(String terim) { final parcalar = _parcalar; final ilk = terim.split(',').first.trim().toLowerCase(); if (parcalar.any((p) => p.toLowerCase().contains(ilk))) { // Kaldır (terimin tüm alt parçalarını) final kaldir = terim.split(',').map((e) => e.trim().toLowerCase()).toSet(); final yeni = parcalar .where((p) => !kaldir.contains(p.toLowerCase())) .toList(); onDegisti(yeni.join(', ')); } else { onDegisti([...parcalar, terim].join(', ')); } } @override Widget build(BuildContext context) { final kategoriler = _kategoriler(nsfw); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row(children: [ const Icon(Icons.block_rounded, size: 16, color: FikoRenkler.neonPembe), const SizedBox(width: 6), Text('Negatif Sihirbazı', style: FikoTipo.baslikS .copyWith(color: FikoRenkler.neonPembe)), const Spacer(), TextButton.icon( style: TextButton.styleFrom(visualDensity: VisualDensity.compact), icon: const Icon(Icons.clear_all_rounded, size: 15), label: const Text('Temizle', style: TextStyle(fontSize: 11.5)), onPressed: () => onDegisti(''), ), ]), const SizedBox(height: FikoBosluk.sm), for (final k in kategoriler) ...[ Padding( padding: const EdgeInsets.only(bottom: 5, top: 4), child: Row(children: [ Icon(k.ikon, size: 13, color: k.renk), const SizedBox(width: 5), Text(k.baslik, style: FikoTipo.etiket.copyWith(color: k.renk)), ]), ), Wrap( spacing: 5, runSpacing: 5, children: [ for (final (tr, en) in k.terimler) _NegCip( etiket: tr, secili: _seciliMi(en), renk: k.renk, onTap: () => _degistir(en), ), ], ), const SizedBox(height: FikoBosluk.sm), ], const SizedBox(height: 4), TextField( controller: TextEditingController(text: deger) ..selection = TextSelection.collapsed(offset: deger.length), maxLines: 2, style: const TextStyle(fontSize: 11.5), decoration: const InputDecoration( isDense: true, labelText: 'Negatif (elle düzenle)', hintText: 'virgülle ayrılmış...', ), onSubmitted: onDegisti, ), ], ); } } class _NegCip extends StatelessWidget { const _NegCip( {required this.etiket, required this.secili, required this.renk, required this.onTap}); final String etiket; final bool secili; final Color renk; final VoidCallback onTap; @override Widget build(BuildContext context) { return InkWell( borderRadius: FikoYaricap.rSm, onTap: onTap, child: AnimatedContainer( duration: FikoHareket.aninda, padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 5), decoration: BoxDecoration( color: secili ? renk.withValues(alpha: 0.2) : Colors.transparent, borderRadius: FikoYaricap.rSm, border: Border.all( color: secili ? renk : Theme.of(context).dividerColor, width: secili ? 1.3 : 1), ), child: Row(mainAxisSize: MainAxisSize.min, children: [ if (secili) ...[ Icon(Icons.check_rounded, size: 12, color: renk), const SizedBox(width: 3), ], Text(etiket, style: TextStyle( fontSize: 11, color: secili ? renk : null, fontWeight: secili ? FontWeight.w700 : FontWeight.w500)), ]), ), ); } }