389 lines
17 KiB
Dart
389 lines
17 KiB
Dart
import 'dart:ui';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import '../../../shared/widgets/confirm_dialog.dart';
|
|
import '../../../shared/widgets/error_view.dart';
|
|
import 'providers_controller.dart';
|
|
import 'widgets/provider_card.dart';
|
|
|
|
class ProvidersScreen extends ConsumerWidget {
|
|
const ProvidersScreen({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final theme = Theme.of(context);
|
|
final isDark = theme.brightness == Brightness.dark;
|
|
final isRu = Localizations.maybeLocaleOf(context)?.languageCode == 'ru';
|
|
final providers = ref.watch(providersControllerProvider);
|
|
final bottomInset = MediaQuery.paddingOf(context).bottom;
|
|
|
|
return Scaffold(
|
|
extendBodyBehindAppBar: true,
|
|
appBar: PreferredSize(
|
|
preferredSize: const Size.fromHeight(60),
|
|
child: ClipRRect(
|
|
child: BackdropFilter(
|
|
filter: ImageFilter.blur(sigmaX: 18, sigmaY: 18),
|
|
child: AppBar(
|
|
backgroundColor: isDark
|
|
? theme.colorScheme.surface.withValues(alpha: 0.72)
|
|
: Colors.white.withValues(alpha: 0.82),
|
|
surfaceTintColor: Colors.transparent,
|
|
elevation: 0,
|
|
centerTitle: false,
|
|
leading: Navigator.of(context).canPop()
|
|
? IconButton(
|
|
icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20),
|
|
onPressed: () => context.pop(),
|
|
tooltip: isRu ? 'Назад' : 'Back',
|
|
)
|
|
: null,
|
|
title: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
width: 30,
|
|
height: 30,
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(9),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color:
|
|
const Color(0xFF6366F1).withValues(alpha: 0.35),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: const Center(
|
|
child:
|
|
Icon(Icons.hub_rounded, size: 16, color: Colors.white),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Flexible(
|
|
child: Text(
|
|
isRu ? 'Провайдеры' : 'Providers',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: -0.2,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
// Provider Check Glass Button
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 6),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(14),
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(14),
|
|
onTap: () {
|
|
HapticFeedback.lightImpact();
|
|
context.push('/providers/check');
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 10, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: theme.colorScheme.primary
|
|
.withValues(alpha: isDark ? 0.16 : 0.10),
|
|
borderRadius: BorderRadius.circular(14),
|
|
border: Border.all(
|
|
color: theme.colorScheme.primary
|
|
.withValues(alpha: isDark ? 0.35 : 0.22),
|
|
width: 1,
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
Icons.network_check_rounded,
|
|
size: 16,
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
const SizedBox(width: 5),
|
|
Text(
|
|
isRu ? 'Проверка' : 'Check',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w700,
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Add Provider Accent Button
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: 12),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(14),
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(14),
|
|
onTap: () {
|
|
HapticFeedback.lightImpact();
|
|
context.push('/providers/new');
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(14),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: const Color(0xFF6366F1)
|
|
.withValues(alpha: 0.35),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Icon(
|
|
Icons.add_rounded,
|
|
size: 16,
|
|
color: Colors.white,
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
isRu ? 'Создать' : 'Add',
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w700,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
body: providers.when(
|
|
loading: () => const Center(child: CircularProgressIndicator()),
|
|
error: (error, _) => ErrorView(message: error.toString()),
|
|
data: (items) {
|
|
final activeCount = items.where((p) => p.enabled).length;
|
|
|
|
return Align(
|
|
alignment: Alignment.topCenter,
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 860),
|
|
child: ListView(
|
|
padding: EdgeInsets.fromLTRB(
|
|
16,
|
|
MediaQuery.paddingOf(context).top + 68,
|
|
16,
|
|
140 + bottomInset,
|
|
),
|
|
children: [
|
|
// Overview Glass Card
|
|
Container(
|
|
margin: const EdgeInsets.only(bottom: 16),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(22),
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: isDark
|
|
? [
|
|
theme.colorScheme.surfaceContainerHigh
|
|
.withValues(alpha: 0.65),
|
|
theme.colorScheme.surfaceContainerLow
|
|
.withValues(alpha: 0.40),
|
|
]
|
|
: [
|
|
Colors.white.withValues(alpha: 0.90),
|
|
Colors.white.withValues(alpha: 0.75),
|
|
],
|
|
),
|
|
border: Border.all(
|
|
color: isDark
|
|
? Colors.white.withValues(alpha: 0.14)
|
|
: Colors.white.withValues(alpha: 0.85),
|
|
width: 1.1,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: isDark
|
|
? Colors.black.withValues(alpha: 0.25)
|
|
: const Color(0xFF6366F1).withValues(alpha: 0.08),
|
|
blurRadius: 16,
|
|
offset: const Offset(0, 4),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
isRu
|
|
? 'Управление источниками'
|
|
: 'Provider Management',
|
|
style: theme.textTheme.titleSmall?.copyWith(
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: -0.2,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
isRu
|
|
? 'Настройте подключение, API ключи и приоритеты booru-сервисов'
|
|
: 'Configure connections, API keys and search priorities',
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 10, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF10B981)
|
|
.withValues(alpha: isDark ? 0.16 : 0.10),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: const Color(0xFF10B981)
|
|
.withValues(alpha: isDark ? 0.40 : 0.28),
|
|
),
|
|
),
|
|
child: Text(
|
|
'$activeCount / ${items.length} ${isRu ? 'вкл' : 'on'}',
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w700,
|
|
color: Color(0xFF10B981),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Empty State
|
|
if (items.isEmpty)
|
|
Container(
|
|
padding: const EdgeInsets.all(32),
|
|
decoration: BoxDecoration(
|
|
color: isDark
|
|
? theme.colorScheme.surfaceContainerHigh
|
|
.withValues(alpha: 0.40)
|
|
: Colors.white.withValues(alpha: 0.80),
|
|
borderRadius: BorderRadius.circular(24),
|
|
border: Border.all(
|
|
color: isDark
|
|
? Colors.white.withValues(alpha: 0.10)
|
|
: Colors.white,
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
Icons.cloud_off_rounded,
|
|
size: 48,
|
|
color: theme.colorScheme.onSurfaceVariant
|
|
.withValues(alpha: 0.5),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
isRu
|
|
? 'Нет настроенных источников'
|
|
: 'No providers configured',
|
|
style: theme.textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Text(
|
|
isRu
|
|
? 'Нажмите «Создать», чтобы добавить новый booru-провайдер'
|
|
: 'Tap "Add" to set up a new booru provider',
|
|
style: theme.textTheme.bodySmall?.copyWith(
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
)
|
|
else
|
|
// List of Provider Cards
|
|
...items.map((item) {
|
|
return ProviderCard(
|
|
key: ValueKey(item.id),
|
|
config: item,
|
|
onToggle: (enabled) => ref
|
|
.read(providersControllerProvider.notifier)
|
|
.toggle(item.id, enabled),
|
|
onEdit: () => context.push(
|
|
'/providers/new',
|
|
extra: item,
|
|
),
|
|
onSaveConfig: (updated) => ref
|
|
.read(providersControllerProvider.notifier)
|
|
.save(updated),
|
|
onDelete: () async {
|
|
final ok = await showConfirmDialog(
|
|
context,
|
|
title: isRu ? 'Удалить источник' : 'Delete provider',
|
|
message: isRu
|
|
? 'Вы действительно хотите удалить ${item.name}?'
|
|
: 'Remove ${item.name}?',
|
|
);
|
|
if (ok) {
|
|
await ref
|
|
.read(providersControllerProvider.notifier)
|
|
.delete(item.id);
|
|
}
|
|
},
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|