1
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
class PlanStepDto {
|
||||
num? id;
|
||||
int? id;
|
||||
String? stepIcon;
|
||||
String? stepContent;
|
||||
String? stepExplain;
|
||||
num? stepStatus;
|
||||
int? stepStatus;
|
||||
|
||||
PlanStepDto({this.id, this.stepIcon, this.stepContent, this.stepExplain, this.stepStatus});
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:plan/api/dto/plan_detail_dto.dart';
|
||||
import 'package:plan/api/dto/plan_item_dto.dart';
|
||||
import 'package:plan/api/network/request.dart';
|
||||
|
||||
import '../../data/models/plan_acttion_type.dart';
|
||||
|
||||
///初始化计划
|
||||
Future<int> initPlanApi(String need, int agentId) async {
|
||||
var res = await Request().post("/plan/init", {
|
||||
@@ -56,3 +58,28 @@ Future<void> deletePlanApi(int planId) async {
|
||||
"plan_id": planId,
|
||||
});
|
||||
}
|
||||
|
||||
///编辑用户计划步骤
|
||||
Future<void> editPlanStepApi(
|
||||
int planId, {
|
||||
required PlanActionType act,
|
||||
int? stepId,
|
||||
String? content,
|
||||
String? explain,
|
||||
}) async {
|
||||
await Request().post("/plan/edit_plan_steps_info", {
|
||||
"plan_id": planId,
|
||||
"act": act.value,
|
||||
"step_id": stepId,
|
||||
"step_content": content,
|
||||
"step_explain": explain,
|
||||
});
|
||||
}
|
||||
|
||||
///修改步骤顺序
|
||||
Future<void> editPlanStepOrderApi(int planId,List<PlanStepDto> list) async {
|
||||
await Request().post("/plan/change_plan_steps", {
|
||||
"plan_id": planId,
|
||||
"steps": list.map((e) => e.toJson()).toList(),
|
||||
});
|
||||
}
|
||||
|
||||
10
lib/data/models/plan_acttion_type.dart
Normal file
10
lib/data/models/plan_acttion_type.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
enum PlanActionType {
|
||||
delete(1), //删除
|
||||
complete(2), //完成
|
||||
edit(3), //编辑
|
||||
completeAll(99); //全部完成
|
||||
|
||||
final int value;
|
||||
|
||||
const PlanActionType(this.value);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class PlanFormCard extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PlanFormCardState extends State<PlanFormCard> {
|
||||
final TextEditingController _inputController = TextEditingController(text: "");
|
||||
final TextEditingController _inputController = TextEditingController(text: "刷牙");
|
||||
|
||||
void _handSubmit() {
|
||||
if (_inputController.text.isEmpty) {
|
||||
|
||||
80
lib/page/plan/detail/navigation/bar_actions.dart
Normal file
80
lib/page/plan/detail/navigation/bar_actions.dart
Normal file
@@ -0,0 +1,80 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:plan/api/endpoints/plan_api.dart';
|
||||
import 'package:plan/widgets/ui_kit/popup/popup_action.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
import '../../widgets/edit_desc_dialog.dart';
|
||||
import '../viewmodel/plan_detail_store.dart';
|
||||
|
||||
class BarActions extends StatefulWidget {
|
||||
final PlanDetailStore store;
|
||||
|
||||
const BarActions({super.key, required this.store});
|
||||
|
||||
@override
|
||||
State<BarActions> createState() => _BarActionsState();
|
||||
}
|
||||
|
||||
class _BarActionsState extends State<BarActions> {
|
||||
///popup菜单
|
||||
void _onPopupActionSelected(String value) {
|
||||
if (value == 'edit_step') {
|
||||
widget.store.setEdit(true);
|
||||
} else if (value == 'edit_desc') {
|
||||
showEditDescDialog(
|
||||
context,
|
||||
value: widget.store.planDetail.summary ?? "",
|
||||
onConfirm: (value) async {
|
||||
widget.store.updatePlanDetail((dto) {
|
||||
dto.summary = value;
|
||||
});
|
||||
await editPlanSummaryApi(int.parse(widget.store.planId), value);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///取消编辑
|
||||
void _cancelEdit() {
|
||||
widget.store.setEdit(false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min, // 关键:Row 只占实际内容宽度
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
transitionBuilder: (child, animation) {
|
||||
// 仅使用渐变动画
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: widget.store.isEdit
|
||||
? InkWell(
|
||||
onTap: _cancelEdit,
|
||||
child: Icon(RemixIcons.check_fill),
|
||||
)
|
||||
: PopupAction(
|
||||
onSelected: _onPopupActionSelected,
|
||||
items: [
|
||||
PopupMenuItem(
|
||||
value: 'edit_step',
|
||||
child: Text("编辑步骤"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'edit_desc',
|
||||
child: Text("编辑摘要"),
|
||||
),
|
||||
],
|
||||
child: Icon(RemixIcons.more_fill),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:plan/page/plan/detail/navigation/bar_actions.dart';
|
||||
import 'package:plan/page/plan/detail/viewmodel/plan_detail_store.dart';
|
||||
import 'package:plan/theme/decorations/app_shadows.dart';
|
||||
import 'package:plan/widgets/ui_kit/popup/popup_action.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
import '../widgets/edit_desc_dialog.dart';
|
||||
import 'widgets/avatar_card.dart';
|
||||
import 'widgets/coach_message.dart';
|
||||
import 'widgets/plan_list.dart';
|
||||
@@ -28,7 +26,6 @@ class PlanDetailPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PlanDetailPageState extends State<PlanDetailPage> {
|
||||
bool _isEdit = false;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
///store对象
|
||||
@@ -44,28 +41,6 @@ class _PlanDetailPageState extends State<PlanDetailPage> {
|
||||
);
|
||||
}
|
||||
|
||||
///popup菜单
|
||||
void _onPopupActionSelected(String value) {
|
||||
if (value == 'edit_step') {
|
||||
setState(() {
|
||||
_isEdit = true;
|
||||
});
|
||||
} else if (value == 'edit_desc') {
|
||||
showEditDescDialog(
|
||||
context,
|
||||
value: "你好",
|
||||
onConfirm: (value) {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///取消编辑
|
||||
void _cancelEdit() {
|
||||
setState(() {
|
||||
_isEdit = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider<PlanDetailStore>(
|
||||
@@ -83,9 +58,8 @@ class _PlanDetailPageState extends State<PlanDetailPage> {
|
||||
child: ScrollBox(
|
||||
child: Container(
|
||||
decoration: shadowDecoration,
|
||||
child: CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
child: Column(
|
||||
children: [
|
||||
CoachMessage(),
|
||||
PlanList(),
|
||||
SuggestedTitle(),
|
||||
@@ -104,40 +78,7 @@ class _PlanDetailPageState extends State<PlanDetailPage> {
|
||||
backgroundColor: Colors.white,
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: Text(store.planDetail.summary ?? ""),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min, // 关键:Row 只占实际内容宽度
|
||||
children: [
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
transitionBuilder: (child, animation) {
|
||||
// 仅使用渐变动画
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: _isEdit
|
||||
? InkWell(
|
||||
onTap: _cancelEdit,
|
||||
child: Icon(RemixIcons.check_fill),
|
||||
)
|
||||
: PopupAction(
|
||||
onSelected: _onPopupActionSelected,
|
||||
items: [
|
||||
PopupMenuItem(
|
||||
value: 'edit_step',
|
||||
child: Text("编辑步骤"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'edit_desc',
|
||||
child: Text("编辑摘要"),
|
||||
),
|
||||
],
|
||||
child: Icon(RemixIcons.more_fill),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: BarActions(store: store),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@ class PlanDetailStore extends ChangeNotifier {
|
||||
//如果没有id进行初始化
|
||||
if (planId == "0") {
|
||||
createPlan();
|
||||
}else{
|
||||
} else {
|
||||
//获取详情
|
||||
getPlanDetail();
|
||||
}
|
||||
@@ -42,6 +42,15 @@ class PlanDetailStore extends ChangeNotifier {
|
||||
///计划详情
|
||||
PlanDetailDto planDetail = PlanDetailDto(summary: "计划详情");
|
||||
|
||||
///是否正在编辑
|
||||
bool isEdit = false;
|
||||
|
||||
///切换编辑模式
|
||||
void setEdit(bool value) {
|
||||
isEdit = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
///流请求工具
|
||||
StreamUtils streamUtils = StreamUtils();
|
||||
|
||||
@@ -49,7 +58,6 @@ class PlanDetailStore extends ChangeNotifier {
|
||||
void createPlan() async {
|
||||
var id = await initPlanApi(planContent, 1);
|
||||
planId = id.toString();
|
||||
// planId = "3";
|
||||
|
||||
///生成摘要---------------------------
|
||||
String summary = "";
|
||||
@@ -92,9 +100,12 @@ class PlanDetailStore extends ChangeNotifier {
|
||||
.where((e) => e.contains("[NEXT]"))
|
||||
.map((e) => e.replaceAll("[NEXT]", "").trim())
|
||||
.toList();
|
||||
|
||||
// 直接覆盖 stepsList
|
||||
planDetail.stepsList = sentences.map((s) => PlanStepDto(stepContent: s)).toList();
|
||||
planDetail.stepsList = sentences
|
||||
.asMap()
|
||||
.entries
|
||||
.map((e) => PlanStepDto(id: e.key, stepContent: e.value))
|
||||
.toList();
|
||||
notifyListeners();
|
||||
},
|
||||
);
|
||||
@@ -159,4 +170,10 @@ class PlanDetailStore extends ChangeNotifier {
|
||||
planDetail = await getPlanDetailApi(planId);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
///更新计划详情
|
||||
void updatePlanDetail(void Function(PlanDetailDto dto) updater) {
|
||||
updater(planDetail);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,23 +14,21 @@ class _CoachMessageState extends State<CoachMessage> {
|
||||
Widget build(BuildContext context) {
|
||||
var store = context.read<PlanDetailStore>();
|
||||
if (store.planContent.isEmpty) {
|
||||
return SliverToBoxAdapter();
|
||||
return SizedBox();
|
||||
}
|
||||
return SliverToBoxAdapter(
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"你的教练正在拆分",
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
Text(
|
||||
'"${store.planContent}"',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
return Container(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"你的教练正在拆分",
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
Text(
|
||||
'"${store.planContent}"',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import 'package:animated_reorderable_list/animated_reorderable_list.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:plan/api/dto/plan_detail_dto.dart';
|
||||
import 'package:plan/api/endpoints/plan_api.dart';
|
||||
import 'package:plan/data/models/plan_acttion_type.dart';
|
||||
import 'package:plan/page/plan/detail/viewmodel/plan_detail_store.dart';
|
||||
import 'package:plan/utils/common.dart';
|
||||
import 'package:plan/widgets/business/delete_row_item.dart';
|
||||
@@ -14,143 +18,197 @@ class PlanList extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PlanListState extends State<PlanList> {
|
||||
final GlobalKey<SliverAnimatedListState> _listKey = GlobalKey<SliverAnimatedListState>();
|
||||
List<PlanStepDto> _stepsList = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initListen();
|
||||
}
|
||||
|
||||
void _initListen() {
|
||||
final store = context.read<PlanDetailStore>();
|
||||
//先初始化,只在详情时才会有意义
|
||||
_stepsList = store.planDetail.stepsList;
|
||||
store.addListener(() {
|
||||
final newList = store.planDetail.stepsList;
|
||||
///删除步骤
|
||||
void _handDelete(int id) async {
|
||||
var store = context.read<PlanDetailStore>();
|
||||
store.updatePlanDetail((dto) {
|
||||
dto.stepsList = dto.stepsList.where((element) => element.id != id).toList();
|
||||
});
|
||||
await editPlanStepApi(
|
||||
int.parse(store.planId),
|
||||
act: PlanActionType.delete,
|
||||
stepId: id,
|
||||
);
|
||||
}
|
||||
|
||||
// 找出新增的 item
|
||||
if (newList.length > _stepsList.length) {
|
||||
final addedItems = newList.sublist(_stepsList.length);
|
||||
///确认排序
|
||||
void _confirmSort(List<PlanStepDto> list) async {
|
||||
var store = context.read<PlanDetailStore>();
|
||||
store.updatePlanDetail((dto) {
|
||||
dto.stepsList = list;
|
||||
});
|
||||
await editPlanStepOrderApi(int.parse(store.planId), list);
|
||||
}
|
||||
|
||||
for (var item in addedItems) {
|
||||
final index = _stepsList.length;
|
||||
_stepsList.add(item);
|
||||
///确认完成或者取消
|
||||
void _handComplete(int id) async {
|
||||
HapticFeedback.vibrate();
|
||||
var store = context.read<PlanDetailStore>();
|
||||
|
||||
// 插入动画
|
||||
_listKey.currentState?.insertItem(
|
||||
index,
|
||||
duration: Duration(milliseconds: 300),
|
||||
store.updatePlanDetail((dto) {
|
||||
dto.stepsList = dto.stepsList.map((step) {
|
||||
if (step.id == id) {
|
||||
// 只更新匹配的项
|
||||
step.stepStatus = step.stepStatus == 2 ? 0 : 2;
|
||||
editPlanStepApi(
|
||||
int.parse(store.planId),
|
||||
act: PlanActionType.complete,
|
||||
stepId: id,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setState(() {
|
||||
_stepsList = store.planDetail.stepsList;
|
||||
});
|
||||
}
|
||||
return step;
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverAnimatedList(
|
||||
key: _listKey,
|
||||
initialItemCount: _stepsList.length,
|
||||
itemBuilder: (_, index, animation) {
|
||||
//动画
|
||||
final curvedAnimation = CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
return PlanItem(
|
||||
step: _stepsList[index],
|
||||
curvedAnimation: curvedAnimation,
|
||||
onDelete: (id) {},
|
||||
final isEdit = context.select<PlanDetailStore, bool>((s) => s.isEdit);
|
||||
return Selector<PlanDetailStore, List<String>>(
|
||||
selector: (_, store) => store.planDetail.stepsList
|
||||
.map((e) => '${e.id}_${e.stepExplain}_${e.stepStatus}')
|
||||
.toList(),
|
||||
builder: (context, list, _) {
|
||||
final list = context.read<PlanDetailStore>().planDetail.stepsList;
|
||||
return AnimatedReorderableListView(
|
||||
items: list,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
var item = list[index];
|
||||
return PlanItem(
|
||||
key: ValueKey('${item.id ?? index}_${item.hashCode}'),
|
||||
step: item,
|
||||
isEdit: isEdit,
|
||||
onDelete: _handDelete,
|
||||
onComplete: _handComplete,
|
||||
);
|
||||
},
|
||||
enterTransition: [SlideInDown()],
|
||||
exitTransition: [SlideInUp()],
|
||||
insertDuration: const Duration(milliseconds: 300),
|
||||
removeDuration: const Duration(milliseconds: 300),
|
||||
dragStartDelay: const Duration(milliseconds: 300),
|
||||
onReorder: (int oldIndex, int newIndex) {
|
||||
setState(() {
|
||||
final item = list.removeAt(oldIndex);
|
||||
list.insert(newIndex, item);
|
||||
_confirmSort(list);
|
||||
});
|
||||
},
|
||||
nonDraggableItems: isEdit ? <PlanStepDto>[] : list,
|
||||
buildDefaultDragHandles: false,
|
||||
longPressDraggable: false,
|
||||
isSameItem: (a, b) => a.stepContent == b.stepContent,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///计划item
|
||||
class PlanItem extends StatelessWidget {
|
||||
final PlanStepDto step;
|
||||
final bool showEdit;
|
||||
final CurvedAnimation curvedAnimation;
|
||||
final bool isEdit;
|
||||
final Function(int) onDelete;
|
||||
final Function(int) onComplete;
|
||||
|
||||
const PlanItem({
|
||||
super.key,
|
||||
required this.step,
|
||||
required this.curvedAnimation,
|
||||
this.showEdit = false,
|
||||
this.isEdit = false,
|
||||
required this.onDelete,
|
||||
required this.onComplete,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FadeTransition(
|
||||
opacity: curvedAnimation,
|
||||
child: SizeTransition(
|
||||
sizeFactor: curvedAnimation,
|
||||
axis: Axis.vertical,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: DeleteRowItem(
|
||||
showDelete: showEdit,
|
||||
onDelete: () {},
|
||||
builder: (_, animate) {
|
||||
return [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 3),
|
||||
child: Text(
|
||||
step.stepContent ?? "",
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (!isEdit) {
|
||||
onComplete(step.id!);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
color: Colors.white,
|
||||
child: DeleteRowItem(
|
||||
showDelete: isEdit,
|
||||
onDelete: () {
|
||||
onDelete(step.id!);
|
||||
},
|
||||
builder: (_, animate) {
|
||||
return [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 3),
|
||||
child: Text(
|
||||
step.stepContent ?? "",
|
||||
style: stepTextStyle(context, Theme.of(context).textTheme.bodyMedium),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axis: Axis.vertical,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: getNotEmpty(step.stepExplain) == null
|
||||
? SizedBox.shrink(key: ValueKey("empty"))
|
||||
: Text(
|
||||
step.stepExplain!,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
key: ValueKey(step.stepExplain),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizeTransition(
|
||||
axis: Axis.horizontal,
|
||||
sizeFactor: animate,
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(left: 10),
|
||||
child: Opacity(
|
||||
opacity: 0.4,
|
||||
child: Icon(RemixIcons.menu_line),
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: SizeTransition(
|
||||
sizeFactor: animation,
|
||||
axis: Axis.vertical,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: getNotEmpty(step.stepExplain) == null
|
||||
? SizedBox.shrink(key: ValueKey("empty"))
|
||||
: Text(
|
||||
step.stepExplain!,
|
||||
style: stepTextStyle(
|
||||
context,
|
||||
Theme.of(context).textTheme.labelSmall,
|
||||
),
|
||||
key: ValueKey(step.stepExplain),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizeTransition(
|
||||
axis: Axis.horizontal,
|
||||
sizeFactor: animate,
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
margin: EdgeInsets.only(left: 10),
|
||||
child: Opacity(
|
||||
opacity: 0.4,
|
||||
child: Icon(RemixIcons.menu_line),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle? stepTextStyle(BuildContext context, TextStyle? baseStyle) {
|
||||
if (step.stepStatus == 2) {
|
||||
return baseStyle?.copyWith(
|
||||
decoration: TextDecoration.lineThrough,
|
||||
decorationThickness: 3,
|
||||
decorationColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
);
|
||||
}
|
||||
return baseStyle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ class ScrollBox extends StatelessWidget {
|
||||
radius: const Radius.circular(5),
|
||||
),
|
||||
child: Scrollbar(
|
||||
controller: scrollController,
|
||||
child: child,
|
||||
child: SingleChildScrollView(
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,51 +9,48 @@ class SuggestedTitle extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var list = context.select<PlanDetailStore, List<String>>(
|
||||
(store) => store.planDetail.suggestionsList,
|
||||
);
|
||||
if (list.isEmpty) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(),
|
||||
);
|
||||
}
|
||||
return SliverToBoxAdapter(
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0, end: 1), // 从透明到完全显示
|
||||
duration: const Duration(milliseconds: 300),
|
||||
builder: (context, value, child) {
|
||||
return Opacity(
|
||||
opacity: value,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 20, bottom: 5),
|
||||
child: Opacity(
|
||||
opacity: 0.6,
|
||||
child: Row(
|
||||
spacing: 20,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 15,
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
Text(
|
||||
"额外建议",
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
Container(
|
||||
width: 15,
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
],
|
||||
return Consumer<PlanDetailStore>(
|
||||
builder: (context, store, _) {
|
||||
if (store.planDetail.suggestionsList.isEmpty) {
|
||||
return SizedBox();
|
||||
}
|
||||
return TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0, end: 1), // 从透明到完全显示
|
||||
duration: const Duration(milliseconds: 300),
|
||||
builder: (context, value, child) {
|
||||
return Opacity(
|
||||
opacity: value,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 20, bottom: 5),
|
||||
child: Opacity(
|
||||
opacity: 0.6,
|
||||
child: Row(
|
||||
spacing: 20,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 15,
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
Text(
|
||||
"额外建议",
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
Container(
|
||||
width: 15,
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -67,13 +64,14 @@ class SuggestedList extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SuggestedListState extends State<SuggestedList> {
|
||||
final GlobalKey<SliverAnimatedListState> _listKey = GlobalKey<SliverAnimatedListState>();
|
||||
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
|
||||
List<String> _suggestionsList = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initListen();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _initListen());
|
||||
}
|
||||
|
||||
void _initListen() {
|
||||
@@ -82,7 +80,6 @@ class _SuggestedListState extends State<SuggestedList> {
|
||||
_suggestionsList = store.planDetail.suggestionsList;
|
||||
store.addListener(() {
|
||||
final newList = store.planDetail.suggestionsList;
|
||||
|
||||
// 找出新增的 item
|
||||
if (newList.length > _suggestionsList.length) {
|
||||
final addedItems = newList.sublist(_suggestionsList.length);
|
||||
@@ -107,8 +104,10 @@ class _SuggestedListState extends State<SuggestedList> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SliverAnimatedList(
|
||||
return AnimatedList(
|
||||
key: _listKey,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
initialItemCount: _suggestionsList.length,
|
||||
itemBuilder: (_, index, animation) {
|
||||
final curvedAnimation = CurvedAnimation(
|
||||
|
||||
@@ -2,10 +2,8 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:plan/api/dto/plan_item_dto.dart';
|
||||
import 'package:plan/api/endpoints/plan_api.dart';
|
||||
import 'package:remixicon/remixicon.dart';
|
||||
|
||||
import 'widgets/history_item.dart';
|
||||
import '../../../widgets/ui_kit/popup/popup_action.dart';
|
||||
import 'widgets/loading_box.dart';
|
||||
|
||||
class PlanHistoryPage extends StatefulWidget {
|
||||
@@ -38,23 +36,11 @@ class _PlanHistoryPageState extends State<PlanHistoryPage> {
|
||||
});
|
||||
}
|
||||
|
||||
///popup事件
|
||||
void _onPopupActionSelected(String value) {
|
||||
switch (value) {
|
||||
case 'edit':
|
||||
setState(() {
|
||||
_isDelete = !_isDelete;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
///确认删除
|
||||
void _confirmDelete(int id) {
|
||||
print(_record.map((e) => e.toJson()));
|
||||
setState(() {
|
||||
_record.removeWhere((element) => element.id == id);
|
||||
print(_record.map((e) => e.toJson()));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ class _DeleteRowItemState extends State<DeleteRowItem> with TickerProviderStateM
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizeTransition(
|
||||
axis: Axis.horizontal,
|
||||
@@ -98,6 +99,7 @@ class _DeleteRowItemState extends State<DeleteRowItem> with TickerProviderStateM
|
||||
onTap: _handleDelete,
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(right: 10),
|
||||
alignment: Alignment.centerRight,
|
||||
child: Icon(
|
||||
RemixIcons.indeterminate_circle_fill,
|
||||
color: Colors.red,
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
animated_reorderable_list:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: animated_reorderable_list
|
||||
sha256: "5de5cca556a8c9c8f7b65234ae4b683593dc6e167db498744a5e389302f24d13"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -30,6 +30,7 @@ dependencies:
|
||||
flutter_image_compress: ^2.4.0
|
||||
cached_network_image: ^3.4.1
|
||||
intl: ^0.19.0
|
||||
animated_reorderable_list: ^1.3.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user