详情里的对话框切换动画ok

This commit is contained in:
zhutao
2025-09-04 23:24:48 +08:00
parent 0231dcfe1a
commit 70aa3e6ab6
8 changed files with 368 additions and 41 deletions

View File

@@ -8,9 +8,8 @@ import 'package:remixicon/remixicon.dart';
import '../widgets/edit_desc_dialog.dart';
import 'widgets/avatar_card.dart';
import 'widgets/plan_item.dart';
import 'widgets/coach_message.dart';
import 'widgets/scroll_box.dart';
import 'widgets/suggested.dart';
class PlanDetailPage extends StatefulWidget {
final String? id;
@@ -55,7 +54,11 @@ class _PlanDetailPageState extends State<PlanDetailPage> {
Widget build(BuildContext context) {
return ChangeNotifierProvider<PlanDetailStore>(
create: (_) {
return PlanDetailStore();
return PlanDetailStore(
planId: widget.id.toString(),
planContent: widget.planName ?? "",
showRoleTalk: widget.planName == null,
);
},
child: CupertinoPageScaffold(
backgroundColor: Colors.white,
@@ -108,31 +111,32 @@ class _PlanDetailPageState extends State<PlanDetailPage> {
decoration: shadowDecoration,
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: SizedBox(height: 20),
),
SliverList.builder(
itemBuilder: (BuildContext context, int index) {
return PlanItem(
showEdit: _isEdit,
title: "测试 ${index + 1}",
desc: "测测 ${index + 1}",
onDelete: (id) {},
);
},
itemCount: 10,
),
SliverToBoxAdapter(
child: SuggestedTitle(),
),
SliverList.builder(
itemBuilder: (BuildContext context, int index) {
return SuggestedItem(
title: "测试",
);
},
itemCount: 5,
),
CoachMessage(),
// SliverToBoxAdapter(
// child: SizedBox(height: 20),
// ),
// SliverList.builder(
// itemBuilder: (BuildContext context, int index) {
// return PlanItem(
// showEdit: _isEdit,
// title: "测试 ${index + 1}",
// desc: "测测 ${index + 1}",
// onDelete: (id) {},
// );
// },
// itemCount: 10,
// ),
// SliverToBoxAdapter(
// child: SuggestedTitle(),
// ),
// SliverList.builder(
// itemBuilder: (BuildContext context, int index) {
// return SuggestedItem(
// title: "测试",
// );
// },
// itemCount: 5,
// ),
],
),
),

View File

@@ -1,6 +1,15 @@
import 'package:flutter/cupertino.dart';
import 'package:plan/api/dto/plan_detail_dto.dart';
import 'package:plan/api/endpoints/plan_api.dart';
class PlanDetailStore extends ChangeNotifier {
///构造函数
PlanDetailStore({
this.planContent = "",
this.planId = "",
bool showRoleTalk = true,
}) : _showRoleTalk = showRoleTalk;
///角色话语是否显示
bool _showRoleTalk = true;
@@ -10,4 +19,19 @@ class PlanDetailStore extends ChangeNotifier {
_showRoleTalk = value;
notifyListeners();
}
///计划的内容,只有新增时才会有
String planContent = "";
///计划id
String planId = "";
///计划详情
PlanDetailDto planDetail = PlanDetailDto();
///创建计划
void createPlan() async {
var id = await initPlanApi(planContent, 1);
planId = id.toString();
}
}

View File

@@ -1,6 +1,9 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:remixicon/remixicon.dart';
import '../viewmodel/plan_detail_store.dart';
class AvatarCard extends StatefulWidget {
const AvatarCard({super.key});
@@ -9,23 +12,35 @@ class AvatarCard extends StatefulWidget {
}
class _AvatarCardState extends State<AvatarCard> with SingleTickerProviderStateMixin {
bool _isShow = false;
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 400),
duration: Duration(milliseconds: 300),
);
_animation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
void _toggleShow() {
var store = context.read<PlanDetailStore>();
setState(() {
_isShow = !_isShow;
if (_isShow) {
store.showRoleTalk = !store.showRoleTalk;
if (store.showRoleTalk) {
_controller.forward();
} else {
_controller.reverse();
@@ -41,11 +56,10 @@ class _AvatarCardState extends State<AvatarCard> with SingleTickerProviderStateM
offset: Offset(0, 50),
child: Column(
children: [
Transform(
alignment: Alignment.center,
transform: Matrix4.identity()..translate(40.0, 40.0)..scale(0.2),
BothSizeTransition(
animation: _animation,
offset: Offset(50, 50),
child: Container(
color: Colors.red,
padding: EdgeInsets.only(bottom: 10),
child: InkWell(
onTap: _toggleShow,
@@ -84,10 +98,14 @@ class _AvatarCardState extends State<AvatarCard> with SingleTickerProviderStateM
Positioned(
top: 20,
child: Transform.translate(
offset: Offset(50, -10),
offset: Offset(40, -10),
child: GestureDetector(
onTap: _toggleShow,
child: Icon(RemixIcons.message_2_line, size: 26),
child: BothSizeTransition(
animation: ReverseAnimation(_animation),
offset: Offset(-50, -50),
child: Icon(RemixIcons.message_2_line),
),
),
),
),
@@ -101,6 +119,7 @@ class _AvatarCardState extends State<AvatarCard> with SingleTickerProviderStateM
}
}
///聊天气泡三角
class BubblePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
@@ -135,3 +154,47 @@ class BubblePainter extends CustomPainter {
return true;
}
}
///缩放动画
class BothSizeTransition extends StatelessWidget {
final Animation<double> animation;
final Widget child;
final Offset offset;
const BothSizeTransition({
super.key,
required this.animation,
this.offset = Offset.zero,
required this.child,
});
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (_, child) {
return Align(
alignment: Alignment(0.7, 1),
child: Opacity(
opacity: animation.value,
child: Transform(
alignment: Alignment(0.5, 1),
transform: Matrix4.identity()
..translate(
offset.dx * (1 - animation.value),
offset.dy * (1 - animation.value),
)
..scale(animation.value, 1.0),
child: SizeTransition(
sizeFactor: animation,
axis: Axis.vertical,
child: child,
),
),
),
);
},
child: child,
);
}
}

View File

@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:plan/page/plan/detail/viewmodel/plan_detail_store.dart';
import 'package:provider/provider.dart';
class CoachMessage extends StatefulWidget {
const CoachMessage({super.key});
@override
State<CoachMessage> createState() => _CoachMessageState();
}
class _CoachMessageState extends State<CoachMessage> {
@override
Widget build(BuildContext context) {
var store = context.read<PlanDetailStore>();
if (store.planContent.isEmpty) {
return SliverToBoxAdapter();
}
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,
),
],
),
),
);
}
}