Files
Prisma/lib/features/providers/presentation/provider_form_screen.dart
T

201 lines
6.9 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../backend/backend.dart';
import 'providers_controller.dart';
class ProviderFormScreen extends ConsumerStatefulWidget {
const ProviderFormScreen({this.initialConfig, super.key});
final ContentProviderConfig? initialConfig;
@override
ConsumerState<ProviderFormScreen> createState() => _ProviderFormScreenState();
}
class _ProviderFormScreenState extends ConsumerState<ProviderFormScreen> {
late final TextEditingController _name;
late final TextEditingController _baseUrl;
late final TextEditingController _priority;
late final TextEditingController _timeout;
late final TextEditingController _headers;
late final TextEditingController _apiKey;
late final TextEditingController _userId;
late final TextEditingController _login;
late String _apiType;
late bool _enabled;
@override
void initState() {
super.initState();
final config = widget.initialConfig;
_name = TextEditingController(text: config?.name);
_baseUrl = TextEditingController(text: config?.baseUrl);
_priority =
TextEditingController(text: (config?.priority ?? 10).toString());
_timeout =
TextEditingController(text: (config?.timeoutSeconds ?? 20).toString());
_headers = TextEditingController(
text: jsonEncode(_visibleHeaders(config?.customHeaders ?? const {})),
);
_apiKey =
TextEditingController(text: config?.customHeaders['query.api_key']);
_userId =
TextEditingController(text: config?.customHeaders['query.user_id']);
_login = TextEditingController(text: config?.customHeaders['query.login']);
_apiType = config?.apiType ?? 'gelbooru';
_enabled = config?.enabled ?? true;
}
@override
void dispose() {
_name.dispose();
_baseUrl.dispose();
_priority.dispose();
_timeout.dispose();
_headers.dispose();
_apiKey.dispose();
_userId.dispose();
_login.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.initialConfig == null ? 'New provider' : 'Edit provider')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
TextField(
controller: _name,
decoration: const InputDecoration(labelText: 'Name')),
const SizedBox(height: 12),
TextField(
controller: _baseUrl,
decoration: const InputDecoration(labelText: 'Base URL')),
const SizedBox(height: 12),
DropdownButtonFormField<String>(
initialValue: _apiType,
decoration: const InputDecoration(labelText: 'API type'),
items: const [
DropdownMenuItem(
value: 'gelbooru', child: Text('gelbooru-compatible')),
DropdownMenuItem(
value: 'rule34', child: Text('rule34-compatible')),
DropdownMenuItem(
value: 'realbooru', child: Text('realbooru-compatible')),
DropdownMenuItem(
value: 'danbooru', child: Text('danbooru-compatible')),
DropdownMenuItem(
value: 'moebooru', child: Text('moebooru-compatible')),
DropdownMenuItem(value: 'e621', child: Text('e621/e926')),
DropdownMenuItem(value: 'custom', child: Text('custom')),
],
onChanged: (value) =>
setState(() => _apiType = value ?? 'gelbooru'),
),
const SizedBox(height: 12),
SwitchListTile(
value: _enabled,
title: const Text('Enabled'),
onChanged: (value) => setState(() => _enabled = value),
),
TextField(
controller: _priority,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: 'Priority'),
),
const SizedBox(height: 12),
TextField(
controller: _timeout,
keyboardType: TextInputType.number,
decoration: const InputDecoration(labelText: 'Timeout seconds'),
),
const SizedBox(height: 12),
Text('Provider API credentials',
style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
TextField(
controller: _apiKey,
decoration: const InputDecoration(
labelText: 'API key',
helperText: 'Gelbooru: api_key, Danbooru: api_key',
),
),
const SizedBox(height: 12),
TextField(
controller: _userId,
decoration: const InputDecoration(
labelText: 'User ID',
helperText: 'Gelbooru-compatible APIs may require user_id',
),
),
const SizedBox(height: 12),
TextField(
controller: _login,
decoration: const InputDecoration(
labelText: 'Login',
helperText: 'Danbooru-compatible APIs may require login',
),
),
const SizedBox(height: 12),
TextField(
controller: _headers,
minLines: 3,
maxLines: 6,
decoration: const InputDecoration(labelText: 'Custom headers JSON'),
),
const SizedBox(height: 20),
FilledButton.icon(
onPressed: _save,
icon: const Icon(Icons.save_rounded),
label: const Text('Save provider'),
),
],
),
);
}
Future<void> _save() async {
final now = DateTime.now();
final id = widget.initialConfig?.id ??
_name.text.trim().toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), '_');
final headersJson =
jsonDecode(_headers.text.trim().isEmpty ? '{}' : _headers.text);
final customHeaders = Map<String, String>.from(headersJson as Map);
void putQuery(String key, String value) {
final trimmed = value.trim();
if (trimmed.isNotEmpty) customHeaders['query.$key'] = trimmed;
}
putQuery('api_key', _apiKey.text);
putQuery('user_id', _userId.text);
putQuery('login', _login.text);
final config = ContentProviderConfig(
id: id,
name: _name.text.trim(),
baseUrl: _baseUrl.text.trim(),
apiType: _apiType,
enabled: _enabled,
priority: int.tryParse(_priority.text) ?? 10,
timeoutSeconds: int.tryParse(_timeout.text) ?? 20,
customHeaders: customHeaders,
createdAt: widget.initialConfig?.createdAt ?? now,
updatedAt: now,
);
await ref.read(providersControllerProvider.notifier).save(config);
if (mounted) context.go('/providers');
}
Map<String, String> _visibleHeaders(Map<String, String> headers) {
return Map<String, String>.from(headers)
..removeWhere((key, _) => key.startsWith('query.'));
}
}