feat(apps): 重构 UI 架构为 presentation 层并新增 l10n 国际化支持
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../../app/di/injection.dart';
|
||||
import '../../../../app/router/app_routes.dart';
|
||||
import '../../../../core/l10n/l10n.dart';
|
||||
import '../../../../core/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/app_button.dart';
|
||||
import '../../../../shared/widgets/app_loading_indicator.dart';
|
||||
import '../../../../shared/widgets/app_pressable.dart';
|
||||
import '../../../../shared/widgets/app_toggle_switch.dart';
|
||||
import '../../../../shared/widgets/toast/toast.dart';
|
||||
import '../../../../shared/widgets/toast/toast_type.dart';
|
||||
import '../../data/models/automation_job_model.dart';
|
||||
import '../../data/services/automation_jobs_api.dart';
|
||||
import '../../presentation/cubits/automation_jobs_cubit.dart';
|
||||
import '../widgets/settings_page_scaffold.dart';
|
||||
|
||||
class FeaturesScreen extends StatefulWidget {
|
||||
const FeaturesScreen({super.key});
|
||||
|
||||
@override
|
||||
State<FeaturesScreen> createState() => _FeaturesScreenState();
|
||||
}
|
||||
|
||||
class _FeaturesScreenState extends State<FeaturesScreen> {
|
||||
late final AutomationJobsCubit _cubit;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_cubit = AutomationJobsCubit(sl<AutomationJobsApi>());
|
||||
_cubit.loadJobs();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_cubit.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cubit,
|
||||
child: SettingsPageScaffold(
|
||||
title: context.l10n.settingsFeaturesTitle,
|
||||
onBack: () => context.pop(),
|
||||
body: BlocBuilder<AutomationJobsCubit, AutomationJobsState>(
|
||||
builder: (context, state) {
|
||||
if (state.isLoading) {
|
||||
return const Center(child: AppLoadingIndicator());
|
||||
}
|
||||
if (state.error != null) {
|
||||
return Center(child: Text(state.error!));
|
||||
}
|
||||
return _buildJobList(state);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildJobList(AutomationJobsState state) {
|
||||
final dailyJobs = state.dailyJobs;
|
||||
final weeklyJobs = state.weeklyJobs;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildSectionTitle(context.l10n.settingsSectionDaily),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
if (dailyJobs.isEmpty)
|
||||
_buildEmptyHint(context.l10n.settingsNoDailyPlans)
|
||||
else
|
||||
...dailyJobs.map(_buildJobCard),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
_buildSectionTitle(context.l10n.settingsSectionWeekly),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
if (weeklyJobs.isEmpty)
|
||||
_buildEmptyHint(context.l10n.settingsNoWeeklyPlans)
|
||||
else
|
||||
...weeklyJobs.map(_buildJobCard),
|
||||
if (state.canCreateMore) ...[
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
_buildCreateButton(),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyHint(String text) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.only(bottom: AppSpacing.sm),
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: Border.all(color: AppColors.borderSecondary),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
color: AppColors.slate500,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionTitle(String title) {
|
||||
return Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.slate500,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildJobCard(AutomationJobModel job) {
|
||||
return AppPressable(
|
||||
onTap: () async {
|
||||
await context.push(AppRoutes.settingsJobDetail(job.id));
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
_cubit.loadJobs();
|
||||
},
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: AppSpacing.sm),
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
border: Border.all(color: AppColors.borderSecondary),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: AppSpacing.xxl + AppSpacing.lg,
|
||||
height: AppSpacing.xxl + AppSpacing.lg,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surfaceTertiary,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.auto_awesome,
|
||||
size: AppSpacing.lg,
|
||||
color: AppColors.blue500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.md),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
job.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
_buildSubtitle(job),
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.slate500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
AppToggleSwitch(
|
||||
value: job.isActive,
|
||||
onChanged: (next) {
|
||||
if (job.isSystem) {
|
||||
Toast.show(
|
||||
context,
|
||||
context.l10n.settingsSystemJobReadonly,
|
||||
type: ToastType.info,
|
||||
);
|
||||
return;
|
||||
}
|
||||
_cubit.updateJobStatus(id: job.id, enabled: next);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _buildSubtitle(AutomationJobModel job) {
|
||||
final statusText = job.isActive
|
||||
? context.l10n.settingsJobStatusEnabled
|
||||
: context.l10n.settingsJobStatusDisabled;
|
||||
final sourceText = job.isSystem
|
||||
? context.l10n.settingsJobSourceSystem
|
||||
: context.l10n.settingsJobSourceCustom;
|
||||
return '$sourceText • $statusText';
|
||||
}
|
||||
|
||||
Widget _buildCreateButton() {
|
||||
return AppButton(
|
||||
text: context.l10n.settingsCreateJob,
|
||||
onPressed: () async {
|
||||
await context.push(AppRoutes.settingsJobNew);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
_cubit.loadJobs();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user