369 lines
11 KiB
Dart
369 lines
11 KiB
Dart
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)),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|