import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../servisler/ltx/ltx_baglanti_servisi.dart'; import '../../servisler/ltx/ltx_client.dart'; class LtxDurumRozeti extends ConsumerStatefulWidget { const LtxDurumRozeti({super.key}); @override ConsumerState createState() => _LtxDurumRozetiState(); } class _LtxDurumRozetiState extends ConsumerState { Timer? _zamanlayici; LtxStatus? _ilerleme; Map _modelDurum = const {}; bool _hata = false; String _sonUrl = ''; String? _sonToken; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _zamanlayiciBaslat(); _durumYukle(); }); } @override void dispose() { _zamanlayici?.cancel(); _zamanlayici = null; super.dispose(); } void _zamanlayiciBaslat() { _zamanlayici?.cancel(); _zamanlayici = Timer.periodic(const Duration(seconds: 3), (_) { _durumYukle(); }); } Future _durumYukle() async { if (!mounted) return; final baglanti = ref.read(ltxBaglantiProvider); if (!baglanti.baglandi || baglanti.url.isEmpty) { if (mounted) { setState(() { _ilerleme = null; _modelDurum = const {}; _hata = false; }); } return; } final client = LtxClient(url: baglanti.url, token: baglanti.token); try { final sonuclar = await Future.wait([ client.ilerleme(), client.modellerDurum(), ]); if (!mounted) return; setState(() { _ilerleme = sonuclar[0] as LtxStatus; _modelDurum = sonuclar[1] as Map; _hata = false; }); } catch (_) { if (!mounted) return; setState(() { _hata = true; }); } } String _aktifModel() { final m = _modelDurum; final aday = m['model_loaded'] ?? m['active_model'] ?? m['model'] ?? m['current_model']; if (aday is String && aday.isNotEmpty) return aday; return '—'; } ({double? kullanilan, double? toplam}) _vram() { final m = _modelDurum; double? oku(dynamic v) { if (v is num) return v.toDouble(); if (v is String) return double.tryParse(v); return null; } final kullanilan = oku(m['gpu_memory_used']) ?? oku(m['vram_used']) ?? oku(m['gpu_used']); final toplam = oku(m['gpu_memory_total']) ?? oku(m['vram_total']) ?? oku(m['gpu_total']); return (kullanilan: kullanilan, toplam: toplam); } String _vramYazi() { final v = _vram(); if (v.kullanilan == null) return ''; String fmt(double d) { if (d > 1024 * 1024) return (d / (1024 * 1024 * 1024)).toStringAsFixed(1); if (d > 1024) return (d / 1024).toStringAsFixed(1); return d.toStringAsFixed(1); } if (v.toplam != null && v.toplam! > 0) { return '${fmt(v.kullanilan!)}/${fmt(v.toplam!)} GB'; } return '${fmt(v.kullanilan!)} GB'; } Color _noktaRengi(ColorScheme cs, LtxBaglanti b) { if (!b.baglandi || _hata) return Colors.redAccent; final il = _ilerleme; if (il == null) return Colors.green; switch (il.durum) { case 'running': return Colors.amber; case 'error': case 'cancelled': return Colors.redAccent; case 'idle': case 'complete': default: return Colors.green; } } /// LTX bazen 0-1, bazen 0-100 ölçeğinde döndürüyor — otomatik normalize et. double _ilerleme01(double ham) { if (ham <= 1.0) return ham.clamp(0.0, 1.0); return (ham / 100).clamp(0.0, 1.0); } String _durumYazi(LtxBaglanti b) { if (!b.baglandi) return 'bağlı değil'; if (_hata) return 'hata'; final il = _ilerleme; if (il == null) return 'boşta'; switch (il.durum) { case 'running': final yuzde = (_ilerleme01(il.ilerleme) * 100).round(); final faz = il.faz.isNotEmpty ? ' / ${il.faz}' : ''; return 'üretiyor %$yuzde$faz'; case 'error': return 'hata'; case 'cancelled': return 'iptal'; case 'complete': return 'tamam'; case 'idle': default: return 'boşta'; } } @override Widget build(BuildContext context) { final baglanti = ref.watch(ltxBaglantiProvider); if (baglanti.url != _sonUrl || baglanti.token != _sonToken) { _sonUrl = baglanti.url; _sonToken = baglanti.token; WidgetsBinding.instance.addPostFrameCallback((_) => _durumYukle()); } final theme = Theme.of(context); final cs = theme.colorScheme; final nokta = _noktaRengi(cs, baglanti); final model = _aktifModel(); final vram = _vramYazi(); final il = _ilerleme; final uretiyor = baglanti.baglandi && il?.durum == 'running'; final ipucu = baglanti.hata ?? (baglanti.baglandi ? 'LTX Ayarları' : 'LTX bağlı değil'); final parcalar = ['LTX']; if (baglanti.baglandi) { parcalar.add(model); if (vram.isNotEmpty) parcalar.add(vram); } final ozet = parcalar.join(' · '); final durum = _durumYazi(baglanti); return Tooltip( message: ipucu, child: Material( color: cs.surfaceContainerHighest, borderRadius: BorderRadius.circular(16), child: InkWell( borderRadius: BorderRadius.circular(16), onTap: () => context.go('/ayarlar'), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 280, minHeight: 32), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), child: Row( mainAxisSize: MainAxisSize.min, children: [ AnimatedContainer( duration: const Duration(milliseconds: 250), width: 8, height: 8, decoration: BoxDecoration( color: nokta, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: nokta.withValues(alpha: 0.55), blurRadius: 6, spreadRadius: 0.5, ), ], ), ), const SizedBox(width: 8), Flexible( child: Text( ozet, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.labelMedium?.copyWith( fontWeight: FontWeight.w600, color: cs.onSurface, ), ), ), if (uretiyor) ...[ const SizedBox(width: 8), SizedBox( width: 12, height: 12, child: CircularProgressIndicator( strokeWidth: 2, value: _ilerleme01(il!.ilerleme), color: cs.primary, backgroundColor: cs.outlineVariant, ), ), ], const SizedBox(width: 8), Flexible( child: Text( durum, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.labelSmall?.copyWith( color: cs.onSurfaceVariant, ), ), ), ], ), ), ), ), ), ); } }