| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- // features/challenge/add/add_challenge_screen.dart
- import 'package:flutter/material.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:intl/intl.dart';
- import 'package:japp_flutter/core/constants/options.dart';
- import 'package:japp_flutter/core/models/challenge_model.dart';
- import 'package:japp_flutter/features/challenge/view_models/challenge_add_vm.dart';
- import 'package:japp_flutter/features/widgets/app_dropdown.dart';
- import 'package:japp_flutter/features/widgets/date_picker.dart';
- class AddChallengeScreen extends ConsumerWidget {
- const AddChallengeScreen({super.key});
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final challengeState = ref.watch(addChallengeProvider);
- final theme = Theme.of(context);
- return Scaffold(
- appBar: AppBar(
- title: const Text('创建新挑战'),
- actions: [
- IconButton(
- icon: const Icon(Icons.check),
- onPressed: () async {
- await ref.read(addChallengeProvider.notifier).submitChallenge();
- if (context.mounted) Navigator.pop(context);
- },
- tooltip: '提交',
- ),
- ],
- ),
- body: challengeState.when(
- loading: () => const Center(child: CircularProgressIndicator()),
- error: (error, _) => Center(child: Text('错误: $error')),
- data: (challenge) => _BuildForm(challenge: challenge),
- ),
- );
- }
- }
- class _BuildForm extends ConsumerWidget {
- final ChallengeModel challenge;
- const _BuildForm({required this.challenge});
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- return SingleChildScrollView(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- TextFormField(
- initialValue: challenge.title,
- decoration: const InputDecoration(
- labelText: '目标',
- hintText: '请输入目标名称',
- border: OutlineInputBorder(),
- ),
- onChanged: (value) =>
- ref.read(addChallengeProvider.notifier).updateTitle(value),
- ),
- const SizedBox(height: 16),
- TextFormField(
- initialValue: challenge.description,
- decoration: const InputDecoration(
- labelText: '简介',
- hintText: '请输入目标的简介',
- border: OutlineInputBorder(),
- alignLabelWithHint: true,
- ),
- maxLines: 3,
- onChanged: (value) => ref
- .read(addChallengeProvider.notifier)
- .updateDescription(value),
- ),
- const SizedBox(height: 16),
- AppDropdown(
- label: '目标类型',
- value: challenge.actionType,
- items: challengeActionTypeOpts,
- onChanged: (value) => ref
- .read(addChallengeProvider.notifier)
- .updateActionType(value ?? 0),
- ),
- const SizedBox(height: 16),
- AppDropdown(
- label: '挑战状态',
- value: challenge.status,
- items: challengeStatusOpts,
- onChanged: (value) => ref
- .read(addChallengeProvider.notifier)
- .updateStatus(value ?? 0),
- ),
- const SizedBox(height: 16),
- AppDropdown(
- label: '挑战难度',
- value: challenge.difficulty,
- items: challengeDifficultyOpts,
- onChanged: (value) => ref
- .read(addChallengeProvider.notifier)
- .updateDifficulty(value ?? 0),
- ),
- const SizedBox(height: 16),
- _DateRangeField(
- startDate: challenge.startDate!,
- endDate: challenge.endDate!,
- ),
- const SizedBox(height: 16),
- TextFormField(
- keyboardType: TextInputType.number,
- initialValue: challenge.sort.toString(),
- decoration: const InputDecoration(
- labelText: '排序',
- border: OutlineInputBorder(),
- alignLabelWithHint: true,
- ),
- onChanged: (value) => ref
- .read(addChallengeProvider.notifier)
- .updateSort(int.parse(value)),
- ),
- const SizedBox(height: 16),
- DatePickerField(
- labelText: '计划完成日期',
- onDateSelected: (date) => ref
- .read(addChallengeProvider.notifier)
- .updatePlanFinishDate(date!),
- ),
- const SizedBox(height: 16),
- DatePickerField(
- labelText: '完成日期',
- onDateSelected: (date) => ref
- .read(addChallengeProvider.notifier)
- .updateFinishDate(date!),
- ),
- const SizedBox(height: 16),
- TextFormField(
- initialValue: challenge.remark,
- decoration: const InputDecoration(
- labelText: '备注',
- border: OutlineInputBorder(),
- alignLabelWithHint: true,
- ),
- maxLines: 3,
- onChanged: (value) =>
- ref.read(addChallengeProvider.notifier).updateRemark(value),
- ),
- const SizedBox(height: 16),
- ],
- ),
- );
- }
- }
- class _DateRangeField extends ConsumerWidget {
- final DateTime startDate;
- final DateTime endDate;
- const _DateRangeField({required this.startDate, required this.endDate});
- Future<void> _selectDate(
- BuildContext context,
- bool isStartDate,
- WidgetRef ref,
- ) async {
- final initialDate = isStartDate ? startDate : endDate;
- final picked = await showDatePicker(
- context: context,
- initialDate: initialDate,
- firstDate: DateTime.now(),
- lastDate: DateTime(2100),
- );
- if (picked != null) {
- final newStart = isStartDate ? picked : startDate;
- final newEnd = isStartDate ? endDate : picked;
- ref.read(addChallengeProvider.notifier).updateDateRange(newStart, newEnd);
- }
- }
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const Text('挑战周期*', style: TextStyle(fontSize: 16)),
- const SizedBox(height: 8),
- Row(
- children: [
- Expanded(
- child: OutlinedButton(
- onPressed: () => _selectDate(context, true, ref),
- child: Text(DateFormat('yyyy/MM/dd').format(startDate)),
- ),
- ),
- const Padding(
- padding: EdgeInsets.symmetric(horizontal: 8),
- child: Text('至'),
- ),
- Expanded(
- child: OutlinedButton(
- onPressed: () => _selectDate(context, false, ref),
- child: Text(DateFormat('yyyy/MM/dd').format(endDate)),
- ),
- ),
- ],
- ),
- const SizedBox(height: 4),
- Text(
- '总天数: ${endDate.difference(startDate).inDays}天',
- style: const TextStyle(color: Colors.grey),
- ),
- ],
- );
- }
- }
|