This commit is contained in:
zhutao
2025-09-04 17:57:35 +08:00
parent 4d12f8afc2
commit 0231dcfe1a
34 changed files with 1339 additions and 368 deletions

View File

@@ -1,29 +1,148 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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/plan_item.dart';
import 'widgets/scroll_box.dart';
import 'widgets/suggested.dart';
class PlanDetailPage extends StatefulWidget {
const PlanDetailPage({super.key});
final String? id;
final String? planName;
const PlanDetailPage({
super.key,
this.id,
this.planName,
});
@override
State<PlanDetailPage> createState() => _PlanDetailPageState();
}
class _PlanDetailPageState extends State<PlanDetailPage> {
bool _isEdit = false;
///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 CupertinoPageScaffold(
backgroundColor: Colors.white,
navigationBar: CupertinoNavigationBar(
middle: Text('计划详情'),
trailing: Row(
mainAxisSize: MainAxisSize.min, // 关键Row 只占实际内容宽度
children: [
Icon(RemixIcons.more_fill),
],
return ChangeNotifierProvider<PlanDetailStore>(
create: (_) {
return PlanDetailStore();
},
child: CupertinoPageScaffold(
backgroundColor: Colors.white,
navigationBar: CupertinoNavigationBar(
middle: Text('计划详情'),
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),
),
),
],
),
),
child: SafeArea(
child: Column(
children: [
AvatarCard(),
Expanded(
child: Padding(
padding: EdgeInsets.all(15),
child: ScrollBox(
child: Container(
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,
),
],
),
),
),
),
),
],
),
),
),
child: Column(),
);
}
}

View File

@@ -0,0 +1,13 @@
import 'package:flutter/cupertino.dart';
class PlanDetailStore extends ChangeNotifier {
///角色话语是否显示
bool _showRoleTalk = true;
bool get showRoleTalk => _showRoleTalk;
set showRoleTalk(bool value) {
_showRoleTalk = value;
notifyListeners();
}
}

View File

@@ -0,0 +1,137 @@
import 'package:flutter/material.dart';
import 'package:remixicon/remixicon.dart';
class AvatarCard extends StatefulWidget {
const AvatarCard({super.key});
@override
State<AvatarCard> createState() => _AvatarCardState();
}
class _AvatarCardState extends State<AvatarCard> with SingleTickerProviderStateMixin {
bool _isShow = false;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 400),
);
}
void _toggleShow() {
setState(() {
_isShow = !_isShow;
if (_isShow) {
_controller.forward();
} else {
_controller.reverse();
}
});
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 30),
child: Transform.translate(
offset: Offset(0, 50),
child: Column(
children: [
Transform(
alignment: Alignment.center,
transform: Matrix4.identity()..translate(40.0, 40.0)..scale(0.2),
child: Container(
color: Colors.red,
padding: EdgeInsets.only(bottom: 10),
child: InkWell(
onTap: _toggleShow,
child: Stack(
children: [
Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
border: Border.all(color: Colors.black, width: 1),
),
child: Text(
"好的,让我们把学习软件开发这个目标分解成最简单的小步骤,这样你明天就能轻松开始行动",
style: TextStyle(fontSize: 12),
),
),
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/xiaozhi.png", height: 100),
Positioned(
top: 20,
child: Transform.translate(
offset: Offset(50, -10),
child: GestureDetector(
onTap: _toggleShow,
child: Icon(RemixIcons.message_2_line, size: 26),
),
),
),
],
),
),
],
),
),
);
}
}
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;
}
}

View File

@@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:plan/widgets/business/delete_row_item.dart';
import 'package:remixicon/remixicon.dart';
class PlanItem extends StatefulWidget {
final String title;
final String desc;
final bool showEdit;
final Function(int) onDelete;
const PlanItem({
super.key,
required this.title,
required this.desc,
this.showEdit = false,
required this.onDelete,
});
@override
State<PlanItem> createState() => _PlanItemState();
}
class _PlanItemState extends State<PlanItem> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: DeleteRowItem(
showDelete: widget.showEdit,
onDelete: () {},
builder: (_, animate) {
return [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(bottom: 3),
child: Text(
"完成以上步骤后,给自己倒杯水休息一下。",
style: Theme.of(context).textTheme.bodyMedium,
),
),
Text(
"就从这里开始写下基本信息只需要2分钟这是最简单的一部",
style: Theme.of(context).textTheme.labelSmall,
),
],
),
),
SizeTransition(
axis: Axis.horizontal,
sizeFactor: animate,
child: Container(
margin: EdgeInsets.only(left: 10),
child: Opacity(
opacity: 0.4,
child: Icon(RemixIcons.menu_line),
),
),
),
];
},
),
);
}
@override
bool get wantKeepAlive => true;
}

View File

@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
class ScrollBox extends StatelessWidget {
final Widget child;
const ScrollBox({super.key, required this.child});
@override
Widget build(BuildContext context) {
return ScrollbarTheme(
data: ScrollbarThemeData(
thumbColor: WidgetStateProperty.all(Theme.of(context).colorScheme.surfaceContainerHigh),
thickness: WidgetStateProperty.all(3),
crossAxisMargin: 3,
mainAxisMargin: 2,
radius: const Radius.circular(5),
),
child: Scrollbar(
child: child,
),
);
}
}

View File

@@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:remixicon/remixicon.dart';
///模块标题
class SuggestedTitle extends StatelessWidget {
const SuggestedTitle({super.key});
@override
Widget build(BuildContext context) {
return 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,
),
],
),
),
);
}
}
class SuggestedItem extends StatelessWidget {
final String title;
const SuggestedItem({super.key, required this.title});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 10,
children: [
Transform.translate(
offset: const Offset(0, 5),
child: Icon(
RemixIcons.lightbulb_flash_fill,
color: Color(0xfff2a529),
size: 18,
),
),
Expanded(
child: Opacity(
opacity: 0.5,
child: Text(
title,
style: Theme.of(context).textTheme.bodyMedium,
),
),
),
],
),
);
}
}