Files
plan_flutter/lib/page/plan/detail/widgets/avatar_card.dart
2025-09-24 11:34:55 +08:00

236 lines
6.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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});
@override
State<AvatarCard> createState() => _AvatarCardState();
}
class _AvatarCardState extends State<AvatarCard> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
///对话框值
String _dialog = "";
@override
void initState() {
super.initState();
//初始化动画
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 300),
);
_animation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
_initDialog();
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
///初始化是否显示对话
void _initDialog() {
WidgetsBinding.instance.addPostFrameCallback((_) {
final store = context.read<PlanDetailStore>();
void listener() {
if (store.planDetail.dialog != null && !store.showRoleTalk) {
setState(() {
_dialog = store.planDetail.dialog!;
});
_toggleShow();
store.removeListener(listener);
}
}
store.addListener(listener);
});
}
///切换显示show
void _toggleShow() {
var store = context.read<PlanDetailStore>();
if (store.planDetail.dialog == null) {
return;
}
setState(() {
store.showRoleTalk = !store.showRoleTalk;
if (store.showRoleTalk) {
_controller.forward();
} else {
_controller.reverse();
}
});
}
@override
Widget build(BuildContext context) {
return Selector<PlanDetailStore, bool>(
selector: (_, store) => store.isAllDone(),
builder: (context, isAllDone, _) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: Transform.translate(
offset: Offset(0, 50),
child: Column(
children: [
BothSizeTransition(
animation: _animation,
offset: Offset(50, 50),
child: Container(
padding: EdgeInsets.only(bottom: 10),
child: InkWell(
onTap: _toggleShow,
child: Stack(
children: [
Container(
width: double.infinity,
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
border: Border.all(color: Colors.black, width: 1),
),
child: Text(
isAllDone ? "🎉 Great — youve completed everything! 🎉" : _dialog,
style: TextStyle(fontSize: 12),
textAlign: TextAlign.center,
),
),
Positioned(
bottom: 0,
left: 0,
right: 0,
child: CustomPaint(
painter: BubblePainter(),
),
),
],
),
),
),
),
SizedBox(
width: double.infinity,
child: Stack(
alignment: Alignment.bottomCenter,
children: [
Image.asset("assets/image/kbn.png", height: 100),
Positioned(
top: 20,
child: Transform.translate(
offset: Offset(40, -10),
child: GestureDetector(
onTap: _toggleShow,
child: BothSizeTransition(
animation: ReverseAnimation(_animation),
offset: Offset(-50, -50),
child: Icon(RemixIcons.message_2_line),
),
),
),
),
],
),
),
],
),
),
);
},
);
}
}
///聊天气泡三角
class BubblePainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
var bottomWidth = 10;
//点坐标
var start = Offset((size.width - bottomWidth) / 2, 0);
//底线
final bottomLinePaint = Paint()
..color = Colors.white
..strokeWidth = 2
..style = PaintingStyle.stroke;
final bottomLinePath = Path()
..moveTo(start.dx, 0)
..lineTo(start.dx + bottomWidth, 0);
canvas.drawPath(bottomLinePath, bottomLinePaint);
//边线
final sideLinePaint = Paint()
..color = Colors.black
..strokeWidth = 1
..style = bottomLinePaint.style;
final sideLinePath = Path()
..moveTo(start.dx, 0)
..lineTo(start.dx + bottomWidth / 2, 5)
..lineTo(start.dx + bottomWidth, 0);
canvas.drawPath(sideLinePath, sideLinePaint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
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.3, 1),
child: Opacity(
opacity: animation.value,
child: Transform(
alignment: Alignment(0, 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,
);
}
}