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