Files
Libretranslate-app/lib/screens/home_screen.dart
T
2026-07-14 17:37:17 +03:00

369 lines
11 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../models/language.dart';
import '../models/history_item.dart';
import '../services/settings_service.dart';
import '../services/translate_service.dart';
import '../services/history_service.dart';
import 'settings_screen.dart';
import 'history_screen.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _settingsService = SettingsService();
late final TranslateService _translateService = TranslateService(_settingsService);
final _historyService = HistoryService();
final _sourceController = TextEditingController();
Timer? _debounce;
List<Language> _languages = [];
Language _sourceLang = Language.auto;
Language _targetLang = const Language(code: 'en', name: 'İngilizce');
String _resultText = '';
String? _detectedLangLabel;
bool _loadingLanguages = true;
bool _translating = false;
String? _connectionError;
@override
void initState() {
super.initState();
_bootstrap();
_sourceController.addListener(_onTextChanged);
}
@override
void dispose() {
_debounce?.cancel();
_sourceController.dispose();
super.dispose();
}
/// Uygulama açılışında sunucuya otomatik bağlanmayı dener.
Future<void> _bootstrap() async {
final hasSettings = await _settingsService.hasSettings();
if (!hasSettings) {
final saved = await Navigator.of(context).push<bool>(
MaterialPageRoute(builder: (_) => const SettingsScreen(isFirstRun: true)),
);
if (saved != true) {
// Kullanıcı ayarları kaydetmeden geri döndüyse yine de tekrar dene.
}
}
await _loadLanguages();
}
Future<void> _loadLanguages() async {
setState(() {
_loadingLanguages = true;
_connectionError = null;
});
try {
final langs = await _translateService.fetchLanguages();
setState(() {
_languages = langs;
// Hedef dil listede yoksa ilk uygun dile düş.
if (!_languages.any((l) => l.code == _targetLang.code) && _languages.isNotEmpty) {
_targetLang = _languages.firstWhere((l) => l.code != 'auto', orElse: () => _languages.first);
}
});
} catch (e) {
setState(() => _connectionError = e.toString());
} finally {
setState(() => _loadingLanguages = false);
}
}
void _onTextChanged() {
_debounce?.cancel();
_debounce = Timer(const Duration(milliseconds: 600), _runTranslate);
}
Future<void> _runTranslate() async {
final text = _sourceController.text;
if (text.trim().isEmpty) {
setState(() {
_resultText = '';
_detectedLangLabel = null;
});
return;
}
setState(() => _translating = true);
try {
String effectiveSource = _sourceLang.code;
if (_sourceLang.code == 'auto') {
final detected = await _translateService.detect(text);
if (detected != null) {
effectiveSource = detected.language;
final match = _languages.firstWhere(
(l) => l.code == detected.language,
orElse: () => Language(code: detected.language, name: detected.language),
);
setState(() => _detectedLangLabel = 'Algılanan dil: ${match.name}');
}
} else {
_detectedLangLabel = null;
}
final translated = await _translateService.translate(
text: text,
sourceLang: effectiveSource,
targetLang: _targetLang.code,
);
if (!mounted) return;
setState(() => _resultText = translated);
await _historyService.add(HistoryItem(
sourceLang: effectiveSource,
targetLang: _targetLang.code,
sourceText: text,
translatedText: translated,
timestamp: DateTime.now(),
));
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Çeviri hatası: $e')),
);
} finally {
if (mounted) setState(() => _translating = false);
}
}
void _swapLanguages() {
if (_sourceLang.code == 'auto') return; // otomatik iken takas anlamsız
setState(() {
final tmp = _sourceLang;
_sourceLang = _targetLang;
_targetLang = tmp;
final tmpText = _sourceController.text;
_sourceController.text = _resultText;
_resultText = tmpText;
});
}
List<Language> get _sourceOptions => [Language.auto, ..._languages];
List<Language> get _targetOptions => _languages;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Çeviri'),
actions: [
IconButton(
tooltip: 'Geçmiş',
icon: const Icon(Icons.history),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const HistoryScreen()),
),
),
IconButton(
tooltip: 'Ayarlar',
icon: const Icon(Icons.settings_outlined),
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const SettingsScreen()),
);
_loadLanguages();
},
),
],
),
body: _loadingLanguages
? const Center(child: CircularProgressIndicator())
: _connectionError != null
? _buildConnectionError()
: _buildTranslator(),
);
}
Widget _buildConnectionError() {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.cloud_off, size: 48, color: Colors.grey),
const SizedBox(height: 16),
Text('Sunucuya bağlanılamadı', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Text(_connectionError!, textAlign: TextAlign.center),
const SizedBox(height: 20),
Wrap(
spacing: 12,
children: [
OutlinedButton(onPressed: _loadLanguages, child: const Text('Tekrar Dene')),
FilledButton(
onPressed: () async {
await Navigator.of(context).push(
MaterialPageRoute(builder: (_) => const SettingsScreen()),
);
_loadLanguages();
},
child: const Text('Ayarları Aç'),
),
],
),
],
),
),
);
}
Widget _buildTranslator() {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_buildLanguageBar(),
const SizedBox(height: 12),
Expanded(
child: Column(
children: [
Expanded(child: _buildInputCard()),
const SizedBox(height: 12),
Expanded(child: _buildResultCard()),
],
),
),
],
),
);
}
Widget _buildLanguageBar() {
return Row(
children: [
Expanded(child: _buildLangDropdown(isSource: true)),
IconButton(
icon: const Icon(Icons.swap_horiz),
tooltip: 'Dilleri değiştir',
onPressed: _sourceLang.code == 'auto' ? null : _swapLanguages,
),
Expanded(child: _buildLangDropdown(isSource: false)),
],
);
}
Widget _buildLangDropdown({required bool isSource}) {
final options = isSource ? _sourceOptions : _targetOptions;
final value = isSource ? _sourceLang : _targetLang;
return DropdownButtonFormField<Language>(
isExpanded: true,
value: options.contains(value) ? value : null,
decoration: const InputDecoration(isDense: true),
items: options
.map((l) => DropdownMenuItem(value: l, child: Text(l.name, overflow: TextOverflow.ellipsis)))
.toList(),
onChanged: (l) {
if (l == null) return;
setState(() {
if (isSource) {
_sourceLang = l;
} else {
_targetLang = l;
}
});
_runTranslate();
},
);
}
Widget _buildInputCard() {
return Card(
margin: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: TextField(
controller: _sourceController,
maxLines: null,
expands: true,
textAlignVertical: TextAlignVertical.top,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: 'Çevrilecek metni yazın...',
),
),
),
if (_detectedLangLabel != null)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(_detectedLangLabel!, style: Theme.of(context).textTheme.bodySmall),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: const Icon(Icons.clear, size: 20),
tooltip: 'Temizle',
onPressed: () => _sourceController.clear(),
),
],
),
],
),
),
);
}
Widget _buildResultCard() {
return Card(
margin: EdgeInsets.zero,
color: Theme.of(context).colorScheme.surfaceContainerHighest,
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
child: _translating
? const Center(child: CircularProgressIndicator())
: SingleChildScrollView(
child: SelectableText(
_resultText,
style: Theme.of(context).textTheme.bodyLarge,
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: const Icon(Icons.copy, size: 20),
tooltip: 'Kopyala',
onPressed: _resultText.isEmpty
? null
: () {
Clipboard.setData(ClipboardData(text: _resultText));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Kopyalandı'), duration: Duration(seconds: 1)),
);
},
),
],
),
],
),
),
);
}
}