Files
MaliangAINovalWriter/AINoval/lib/screens/ai_config/ai_config_management_screen.dart
2025-09-10 00:07:52 +08:00

218 lines
9.9 KiB
Dart

import 'package:ainoval/blocs/ai_config/ai_config_bloc.dart';
import 'package:ainoval/config/app_config.dart'; // <<< Import AppConfig
import 'package:ainoval/models/user_ai_model_config_model.dart';
import 'package:ainoval/services/api_service/base/api_client.dart';
import 'package:ainoval/services/api_service/repositories/impl/user_ai_model_config_repository_impl.dart';
import 'package:ainoval/services/api_service/repositories/user_ai_model_config_repository.dart';
import 'package:ainoval/widgets/common/top_toast.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ainoval/l10n/app_localizations.dart';
import 'package:ainoval/widgets/common/theme_toggle_button.dart';
import 'widgets/add_edit_ai_config_dialog.dart';
import 'widgets/ai_config_list_item.dart';
class AiConfigManagementScreen extends StatelessWidget {
const AiConfigManagementScreen({super.key});
// TODO: Replace with proper dependency injection for repository
static final _tempApiClient =
ApiClient(); // Temporary - use injected instance
static final UserAIModelConfigRepository _repository =
UserAIModelConfigRepositoryImpl(apiClient: _tempApiClient); // Temporary
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
// <<< Get userId from AppConfig >>>
// Ensure userId is available before navigating here, or handle null case
final String? currentUserId = AppConfig.userId; // Allow null initially
// Show an error/loading state if userId is null and required
if (currentUserId == null) {
// <<< Check for null
return Scaffold(
// appBar: AppBar(title: Text(l10n.errorTitle)), // TODO: Add l10n.errorTitle='错误'
appBar: AppBar(title: const Text('错误')), // Placeholder
// body: Center(child: Text(l10n.errorUserNotLoggedIn)) // TODO: Add l10n.errorUserNotLoggedIn = '无法加载配置:用户未登录。'
body: const Center(child: Text('无法加载配置:用户未登录。')) // Placeholder
); // <<< 修正: 移除了多余的括号并添加了分号
}
return BlocProvider(
// Use ! because we checked for null above
create: (context) => AiConfigBloc(repository: _repository)
..add(LoadAiConfigs(userId: currentUserId)),
child: Scaffold(
appBar: AppBar(
// TODO: Add l10n.aiModelConfigTitle string
// title: Text(l10n.aiModelConfigTitle), // Placeholder 'AI 模型配置'
title: const Text('AI 模型配置'), // Placeholder
actions: [
const ThemeToggleButton(),
const SizedBox(width: 16),
],
),
body: BlocConsumer<AiConfigBloc, AiConfigState>(
listener: (context, state) {
if (state.actionStatus == AiConfigActionStatus.error &&
state.actionErrorMessage != null) {
TopToast.error(context, '操作失败: ${state.actionErrorMessage!}');
}
// Optional: Show success message
else if (state.actionStatus == AiConfigActionStatus.success) {
// Consider showing temporary success confirmations if needed
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(content: Text(l10n.operationSuccessful), backgroundColor: Colors.green), // TODO: Add l10n.operationSuccessful = '操作成功'
// );
// Reset action status after showing message? Maybe handle in BLoC directly.
}
},
builder: (context, state) {
if (state.status == AiConfigStatus.loading &&
state.configs.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
if (state.status == AiConfigStatus.error && state.configs.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Text(l10n.errorLoadingConfig, style: TextStyle(color: Colors.red)), // TODO: Add l10n.errorLoadingConfig = '加载配置时出错'
const Text('加载配置时出错',
style: TextStyle(color: Colors.red)), // Placeholder
if (state.errorMessage != null)
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(state.errorMessage!),
),
ElevatedButton(
// Use ! because userId is checked non-null here
onPressed: () => context
.read<AiConfigBloc>()
.add(LoadAiConfigs(userId: currentUserId)),
// child: Text(l10n.retry), // TODO: Add l10n.retry = '重试'
child: const Text('重试'), // Placeholder
)
],
));
}
final configs = state.configs;
final bool isActionLoading =
state.actionStatus == AiConfigActionStatus.loading;
return Stack(
children: [
if (configs.isEmpty && state.status != AiConfigStatus.loading)
// Center(child: Text(l10n.noConfigsFound)), // TODO: Add l10n.noConfigsFound = '未找到任何配置'
const Center(child: Text('未找到任何配置')), // Placeholder
ListView.builder(
padding: const EdgeInsets.only(
bottom: 80), // Add padding to avoid FAB overlap
itemCount: configs.length,
itemBuilder: (context, index) {
final config = configs[index];
// Pass specific loading state for the item if we track it by ID, otherwise use global action loading state
// bool itemIsLoading = isActionLoading && state.loadingConfigId == config.id; // Need state.loadingConfigId
return AiConfigListItem(
config: config,
// If not tracking individual item loading, disable buttons globally during action
isLoading: isActionLoading,
// Use ! for userId
onEdit: () => _showAddEditDialog(context, currentUserId,
config: config), // Pass userId
onDelete: () => _showDeleteConfirmation(
context, currentUserId, config), // Pass userId
onValidate: () => context.read<AiConfigBloc>().add(
ValidateAiConfig(
userId: currentUserId,
configId: config.id)), // Use userId
onSetDefault: () => context.read<AiConfigBloc>().add(
SetDefaultAiConfig(
userId: currentUserId,
configId: config.id)), // Use userId
);
},
),
// Optional: Global loading indicator overlay
// if (isActionLoading)
// Positioned.fill(
// child: Container(
// color: Colors.black.withOpacity(0.1),
// child: const Center(child: CircularProgressIndicator()),
// ),
// ),
],
);
},
),
floatingActionButton: FloatingActionButton(
// Use ! for userId
onPressed: () =>
_showAddEditDialog(context, currentUserId), // Pass userId
// tooltip: l10n.addConfigTooltip, // TODO: Add l10n.addConfigTooltip = '添加配置'
tooltip: '添加配置', // Placeholder
child: const Icon(Icons.add),
),
),
);
}
// <<< Add userId parameter >>>
void _showAddEditDialog(BuildContext context, String userId,
{UserAIModelConfigModel? config}) {
final aiConfigBloc =
context.read<AiConfigBloc>(); // Get BLoC from current context
showDialog(
context: context,
barrierDismissible:
false, // Prevent closing while dialog action is in progress
builder: (_) => BlocProvider.value(
// Provide the *existing* BLoC instance to the dialog
value: aiConfigBloc,
child: AddEditAiConfigDialog(
userId: userId, // Pass userId from parameter
configToEdit: config,
),
),
);
}
// <<< Add userId parameter >>>
void _showDeleteConfirmation(
BuildContext context, String userId, UserAIModelConfigModel config) {
final l10n = AppLocalizations.of(context)!;
showDialog(
context: context,
builder: (ctx) => AlertDialog(
// title: Text(l10n.deleteConfigTitle), // TODO: Add l10n.deleteConfigTitle = '删除配置'
title: const Text('删除配置'), // Placeholder
// content: Text(l10n.deleteConfigConfirmation(config.alias)), // TODO: Add l10n.deleteConfigConfirmation
content: Text('确定要删除配置 ${config.alias} 吗?此操作无法撤销。'), // Placeholder
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
// child: Text(l10n.cancel), // TODO: Add l10n.cancel = '取消'
child: const Text('取消'), // Placeholder
),
TextButton(
style: TextButton.styleFrom(foregroundColor: Colors.red),
onPressed: () {
// <<< Use userId from parameter >>>
context
.read<AiConfigBloc>()
.add(DeleteAiConfig(userId: userId, configId: config.id));
Navigator.pop(ctx); // Close confirmation dialog
},
// child: Text(l10n.delete), // TODO: Add l10n.delete = '删除'
child: const Text('删除'), // Placeholder
),
],
),
);
}
}