activity_detail_page.dart 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. import 'dart:io';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_riverpod/flutter_riverpod.dart';
  4. import 'package:flutter_screenutil/flutter_screenutil.dart';
  5. import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
  6. import 'package:news_app/constant/color_res.dart';
  7. import 'package:news_app/constant/config.dart';
  8. import 'package:news_app/util/time_util.dart';
  9. import '../../constant/size_res.dart';
  10. import '../../model/activity_model.dart';
  11. import '../../provider/activity_detail_provider.dart';
  12. import '../../util/device_util.dart';
  13. import '../../util/share_util.dart';
  14. import '../../widget/load_image.dart';
  15. import '../../widget/my_txt.dart';
  16. import '../../widget/right_action_widget.dart';
  17. class ActivityDetailPage extends ConsumerStatefulWidget {
  18. final String activityId;
  19. const ActivityDetailPage(this.activityId, {super.key});
  20. @override
  21. ConsumerState<ActivityDetailPage> createState() => _ActivityDetailPageState();
  22. }
  23. final activityDetailProvider =
  24. NotifierProvider<ActivityDetailProvider, ActivityModelRecord>(
  25. () => ActivityDetailProvider(),
  26. );
  27. String getRegisterStatus(String registerStatus) {
  28. switch (registerStatus) {
  29. case "0":
  30. return "未报名";
  31. case "1":
  32. return "已报名";
  33. case "2":
  34. return "审核通过";
  35. case "3":
  36. return "审核不通过";
  37. default:
  38. return "未报名";
  39. }
  40. }
  41. class _ActivityDetailPageState extends ConsumerState<ActivityDetailPage> {
  42. bool _isWeChatInstalled = false;
  43. @override
  44. void initState() {
  45. super.initState();
  46. ref
  47. .read(activityDetailProvider.notifier)
  48. .fetchActivityDetail(activityId: widget.activityId);
  49. _checkWeChatInstallation();
  50. }
  51. Future<void> _checkWeChatInstallation() async {
  52. if (Platform.isAndroid) {
  53. final installed = await isWeChatInstalledOnlyAndroid();
  54. setState(() => _isWeChatInstalled = installed);
  55. } else if (Platform.isIOS) {
  56. final installed = await fluwx.isWeChatInstalled;
  57. setState(() => _isWeChatInstalled = installed);
  58. }
  59. }
  60. @override
  61. void dispose() {
  62. _nameController.dispose();
  63. _phoneController.dispose();
  64. super.dispose();
  65. }
  66. final TextEditingController _nameController = TextEditingController();
  67. final TextEditingController _phoneController = TextEditingController();
  68. Future<void> shareAction(ActivityModelRecord data) async {
  69. showModalBottomSheet(
  70. context: context,
  71. builder:
  72. (context) => SafeArea(
  73. child: Column(
  74. mainAxisSize: MainAxisSize.min,
  75. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  76. children: [
  77. SizedBox(height: 10.h),
  78. Container(
  79. width: double.infinity,
  80. height: 80.h,
  81. decoration: BoxDecoration(
  82. borderRadius: BorderRadius.only(
  83. topLeft: Radius.circular(10.r),
  84. topRight: Radius.circular(10.r),
  85. ),
  86. color: Colors.white,
  87. ),
  88. child: Row(
  89. crossAxisAlignment: CrossAxisAlignment.center,
  90. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  91. children: [
  92. if (_isWeChatInstalled)
  93. GestureDetector(
  94. onTap: () {
  95. Navigator.pop(context);
  96. shareWeiXinUrl(
  97. title: data.shareDesc ?? "",
  98. url: data.shareUrl ?? "",
  99. );
  100. },
  101. child: Container(
  102. width: 100.w,
  103. height: 80.h,
  104. child: Column(
  105. crossAxisAlignment: CrossAxisAlignment.center,
  106. mainAxisAlignment: MainAxisAlignment.center,
  107. children: [
  108. LoadAssetImage(
  109. 'share_wxhy',
  110. width: 40.w,
  111. height: 40.h,
  112. ),
  113. SizedBox(height: 10.h),
  114. myTxt(
  115. text: "微信好友",
  116. color: Colors.black,
  117. fontSize: 12.sp,
  118. fontWeight: FontWeight.bold,
  119. ),
  120. ],
  121. ),
  122. ),
  123. ),
  124. if (_isWeChatInstalled)
  125. GestureDetector(
  126. onTap: () {
  127. Navigator.pop(context);
  128. shareWeiXinPYUrl(
  129. title: data.shareDesc ?? "",
  130. url: data.shareUrl ?? "",
  131. );
  132. },
  133. child: Container(
  134. width: 100.w,
  135. height: 80.h,
  136. child: Column(
  137. mainAxisAlignment: MainAxisAlignment.center,
  138. children: [
  139. LoadAssetImage(
  140. 'share_pyq',
  141. width: 40.w,
  142. height: 40.h,
  143. ),
  144. SizedBox(height: 10.h),
  145. myTxt(
  146. text: "朋友圈",
  147. color: Colors.black,
  148. fontSize: 12.sp,
  149. fontWeight: FontWeight.bold,
  150. ),
  151. ],
  152. ),
  153. ),
  154. ),
  155. GestureDetector(
  156. onTap: () {
  157. Navigator.pop(context);
  158. shareUrl(
  159. title: data.shareDesc ?? "",
  160. url: data.shareUrl ?? "",
  161. );
  162. },
  163. child: Container(
  164. width: 100.w,
  165. height: 80.h,
  166. child: Column(
  167. mainAxisAlignment: MainAxisAlignment.center,
  168. children: [
  169. LoadAssetImage(
  170. 'share_xtfx',
  171. width: 40.w,
  172. height: 40.h,
  173. ),
  174. SizedBox(height: 10.h),
  175. myTxt(
  176. text: "系统分享",
  177. color: Colors.black,
  178. fontSize: 12.sp,
  179. fontWeight: FontWeight.bold,
  180. ),
  181. ],
  182. ),
  183. ),
  184. ),
  185. ],
  186. ),
  187. ),
  188. ],
  189. ),
  190. ),
  191. );
  192. }
  193. void _showRegisterDialog(WidgetRef ref) {
  194. showModalBottomSheet(
  195. backgroundColor: Colors.transparent,
  196. isScrollControlled: true, // 🔥 关键:允许内容超出默认高度
  197. context: context,
  198. builder: (BuildContext context) {
  199. return DraggableScrollableSheet(
  200. initialChildSize: 0.6,
  201. maxChildSize: 0.9,
  202. minChildSize: 0.4,
  203. expand: false,
  204. builder: (context, scrollController) {
  205. return ClipRRect(
  206. borderRadius: BorderRadius.vertical(top: Radius.circular(20.r)),
  207. child: Container(
  208. padding: EdgeInsets.only(
  209. left: horizontalPadding,
  210. right: horizontalPadding,
  211. bottom: bottomBarHeight, // 🔥 关键:适配键盘高度
  212. ),
  213. color: Colors.white,
  214. child: SingleChildScrollView(
  215. controller: scrollController,
  216. child: Column(
  217. crossAxisAlignment: CrossAxisAlignment.start,
  218. children: [
  219. SizedBox(height: 30.h),
  220. Center(
  221. child: myTxt(
  222. text: "活动报名",
  223. fontWeight: FontWeight.bold,
  224. fontSize: 18.sp,
  225. color: color333333,
  226. ),
  227. ),
  228. SizedBox(height: 30.h),
  229. Text.rich(
  230. TextSpan(
  231. text: "*",
  232. style: TextStyle(color: Colors.red),
  233. children: [
  234. TextSpan(
  235. text: "姓名",
  236. style: TextStyle(
  237. color: color333333,
  238. fontSize: 16.sp,
  239. ),
  240. ),
  241. ],
  242. ),
  243. ),
  244. SizedBox(height: 15.h),
  245. SizedBox(
  246. height: 50.h,
  247. child: TextField(
  248. controller: _nameController,
  249. textInputAction: TextInputAction.next,
  250. decoration: InputDecoration(
  251. contentPadding: EdgeInsets.only(
  252. bottom: 4.h,
  253. left: 10.w,
  254. ),
  255. hintText: "请输入姓名",
  256. hintStyle: TextStyle(
  257. color: color666666,
  258. fontSize: 16.sp,
  259. ),
  260. //无边框
  261. border: OutlineInputBorder(
  262. borderRadius: BorderRadius.circular(8.r),
  263. borderSide: BorderSide.none,
  264. ),
  265. fillColor: colorF5F7FD,
  266. filled: true,
  267. ),
  268. ),
  269. ),
  270. SizedBox(height: 30.h),
  271. Text.rich(
  272. TextSpan(
  273. text: "*",
  274. style: TextStyle(color: Colors.red),
  275. children: [
  276. TextSpan(
  277. text: "手机号",
  278. style: TextStyle(
  279. color: color333333,
  280. fontSize: 16.sp,
  281. ),
  282. ),
  283. ],
  284. ),
  285. ),
  286. SizedBox(height: 15.h),
  287. SizedBox(
  288. height: 50.h,
  289. child: TextField(
  290. controller: _phoneController,
  291. keyboardType: TextInputType.phone,
  292. textInputAction: TextInputAction.done,
  293. decoration: InputDecoration(
  294. contentPadding: EdgeInsets.only(
  295. bottom: 4.h,
  296. left: 10.w,
  297. ),
  298. hintText: "请输入手机号",
  299. hintStyle: TextStyle(
  300. color: color666666,
  301. fontSize: 16.sp,
  302. ),
  303. //无边框
  304. border: OutlineInputBorder(
  305. borderRadius: BorderRadius.circular(8.r),
  306. borderSide: BorderSide.none,
  307. ),
  308. fillColor: colorF5F7FD,
  309. filled: true,
  310. ),
  311. ),
  312. ),
  313. SizedBox(height: 30.h),
  314. GestureDetector(
  315. onTap: () {
  316. ref
  317. .watch(activityDetailProvider.notifier)
  318. .fetchRegisterActivity(
  319. activityId: widget.activityId,
  320. name: _nameController.text,
  321. phone: _phoneController.text,
  322. );
  323. },
  324. child: Container(
  325. margin: EdgeInsets.symmetric(vertical: 20.h),
  326. decoration: BoxDecoration(
  327. color: color5F59F7,
  328. borderRadius: BorderRadius.all(
  329. Radius.circular(20.r),
  330. ),
  331. ),
  332. height: 45.h,
  333. alignment: Alignment.center,
  334. child: myTxt(
  335. text: "提交",
  336. color: Colors.white,
  337. fontSize: 16.sp,
  338. ),
  339. ),
  340. ),
  341. ],
  342. ),
  343. ),
  344. ),
  345. );
  346. },
  347. );
  348. },
  349. );
  350. }
  351. @override
  352. Widget build(BuildContext context) {
  353. final detail = ref.watch(activityDetailProvider);
  354. return Scaffold(
  355. appBar: AppBar(
  356. // 移除阴影
  357. scrolledUnderElevation: 0,
  358. centerTitle: true,
  359. // 禁用滚动时的阴影变化
  360. title: myTxt(text: "活动详情", color: Colors.black, fontSize: 18.sp),
  361. leading: IconButton(
  362. icon: Icon(Icons.arrow_back_ios, color: Colors.black),
  363. onPressed: () {
  364. Navigator.pop(context);
  365. },
  366. ),
  367. actions: [
  368. Padding(
  369. padding: EdgeInsets.only(right: horizontalPadding),
  370. child: RightActionWidget(
  371. isLike: detail.isLiked ?? false,
  372. isFavorite: detail.isFavorite ?? false,
  373. tap: (value) {
  374. if (value == 0) {
  375. ref
  376. .read(activityDetailProvider.notifier)
  377. .fetchActivityLike(
  378. contentId: widget.activityId,
  379. current: detail.isLiked ?? false,
  380. );
  381. } else if (value == 1) {
  382. ref
  383. .read(activityDetailProvider.notifier)
  384. .fetchActivityFavorite(
  385. contentId: widget.activityId,
  386. current: detail.isFavorite ?? false,
  387. );
  388. } else if (value == 2) {
  389. // shareUrl(
  390. // title: detail.shareDesc ?? "",
  391. // url: detail.shareUrl ?? "",
  392. // );
  393. shareAction(detail);
  394. if (uuid.isNotEmpty) {
  395. ref
  396. .read(activityDetailProvider.notifier)
  397. .fetchActivityShare(activityId: widget.activityId);
  398. }
  399. }
  400. },
  401. ),
  402. ),
  403. ],
  404. ),
  405. body: Stack(
  406. children: [
  407. LoadImage(
  408. detail.image ?? 'bignone',
  409. width: double.infinity,
  410. height: 190.h,
  411. fit: BoxFit.fill,
  412. holderImg: 'bignone',
  413. ),
  414. SingleChildScrollView(
  415. child: Column(
  416. children: [
  417. SizedBox(height: 169.h),
  418. Container(
  419. padding: EdgeInsets.only(
  420. left: 14.w,
  421. right: 14.w,
  422. bottom: 26.h,
  423. ),
  424. decoration: BoxDecoration(
  425. color: Color(0xFFFFFFFF),
  426. borderRadius: BorderRadius.only(
  427. topLeft: Radius.circular(16),
  428. topRight: Radius.circular(16),
  429. ),
  430. ),
  431. child: Column(
  432. children: [
  433. SizedBox(height: 10.w),
  434. myTxt(
  435. text: detail.title ?? "金融赋能镇江高质量发展大会",
  436. color: Colors.black,
  437. fontSize: 16,
  438. fontWeight: FontWeight.bold,
  439. ),
  440. SizedBox(height: 10.w),
  441. Text(
  442. detail.summary ??
  443. "继今年全市“新春第一会”再次吹响“产业强市”号角后,在3月5日召开的全市金融系统工作会议上,“产业强市”再次成为高频词。",
  444. style: TextStyle(
  445. color: Color(0xFF333333),
  446. fontSize: 13,
  447. ),
  448. maxLines: 1000,
  449. ),
  450. SizedBox(height: 10.w),
  451. Row(
  452. crossAxisAlignment: CrossAxisAlignment.center,
  453. children: [
  454. Container(
  455. width: 10.w,
  456. height: 10.h,
  457. alignment: Alignment.center,
  458. decoration: BoxDecoration(
  459. color: Color(0xFF4578FF),
  460. borderRadius: BorderRadius.all(
  461. Radius.circular(5),
  462. ),
  463. ),
  464. child: Container(
  465. width: 6.w,
  466. height: 6.h,
  467. alignment: Alignment.center,
  468. decoration: BoxDecoration(
  469. color: Color(0xFFFFFFFF),
  470. borderRadius: BorderRadius.all(
  471. Radius.circular(3),
  472. ),
  473. ),
  474. ),
  475. ),
  476. SizedBox(width: 4),
  477. myTxt(
  478. text: "活动报名人数限额:${detail.registerLimit}",
  479. color: Color(0xFF333333),
  480. fontSize: 12,
  481. ),
  482. ],
  483. ),
  484. SizedBox(height: 10.w),
  485. Row(
  486. crossAxisAlignment: CrossAxisAlignment.center,
  487. children: [
  488. Container(
  489. width: 10.w,
  490. height: 10.h,
  491. alignment: Alignment.center,
  492. decoration: BoxDecoration(
  493. color: Color(0xFF4578FF),
  494. borderRadius: BorderRadius.all(
  495. Radius.circular(5),
  496. ),
  497. ),
  498. child: Container(
  499. width: 6.w,
  500. height: 6.h,
  501. alignment: Alignment.center,
  502. decoration: BoxDecoration(
  503. color: Color(0xFFFFFFFF),
  504. borderRadius: BorderRadius.all(
  505. Radius.circular(3),
  506. ),
  507. ),
  508. ),
  509. ),
  510. SizedBox(width: 4),
  511. myTxt(
  512. text:
  513. "活动报名时间:${TimeUtil.formatTime(detail.registerStartTime)} / ${TimeUtil.formatTime(detail.registerEndTime)}",
  514. color: Color(0xFF333333),
  515. fontSize: 12,
  516. ),
  517. ],
  518. ),
  519. SizedBox(height: 10.w),
  520. Row(
  521. crossAxisAlignment: CrossAxisAlignment.center,
  522. children: [
  523. Icon(
  524. Icons.alarm_outlined,
  525. color: Color(0xFF333333),
  526. size: 13,
  527. ),
  528. SizedBox(width: 4),
  529. myTxt(
  530. text:
  531. "时间:${TimeUtil.formatTime(detail.activityStartTime)} / ${TimeUtil.formatTime(detail.activityEndTime)}",
  532. color: Color(0xFF333333),
  533. fontSize: 12,
  534. ),
  535. ],
  536. ),
  537. SizedBox(height: 10.w),
  538. Row(
  539. crossAxisAlignment: CrossAxisAlignment.center,
  540. children: [
  541. Icon(
  542. Icons.location_on_outlined,
  543. color: Color(0xFF333333),
  544. size: 13,
  545. ),
  546. SizedBox(width: 4),
  547. myTxt(
  548. text: "地址:${detail.activityLocation}",
  549. color: Color(0xFF333333),
  550. fontSize: 12,
  551. ),
  552. ],
  553. ),
  554. SizedBox(height: 10.w),
  555. Row(
  556. crossAxisAlignment: CrossAxisAlignment.center,
  557. children: [
  558. Icon(
  559. Icons.people_alt_outlined,
  560. color: Color(0xFF333333),
  561. size: 13,
  562. ),
  563. SizedBox(width: 4),
  564. myTxt(
  565. text: "举办方:${detail.activityOrg}",
  566. color: Color(0xFF333333),
  567. fontSize: 12,
  568. ),
  569. ],
  570. ),
  571. SizedBox(height: 12.w),
  572. Container(
  573. color: Color(0xFFDCDFE2),
  574. height: 1,
  575. width: double.infinity,
  576. ),
  577. SizedBox(height: 12.h),
  578. HtmlWidget(detail.content ?? ""),
  579. ],
  580. ),
  581. ),
  582. ],
  583. ),
  584. ),
  585. if (detail.canRegister == true)
  586. Positioned(
  587. bottom: bottomBarHeight,
  588. left: horizontalPadding,
  589. right: horizontalPadding,
  590. child: ColoredBox(
  591. color: Colors.white,
  592. child: GestureDetector(
  593. onTap: () {
  594. if (detail.registerStatus == "0") {
  595. _showRegisterDialog(ref);
  596. }
  597. },
  598. child: Container(
  599. margin: EdgeInsets.symmetric(vertical: 20.h),
  600. decoration: BoxDecoration(
  601. color: color5F59F7,
  602. borderRadius: BorderRadius.all(Radius.circular(20.r)),
  603. ),
  604. height: 45.h,
  605. alignment: Alignment.center,
  606. child: myTxt(
  607. text: getRegisterStatus(detail.registerStatus ?? ""),
  608. color: Colors.white,
  609. fontSize: 16.sp,
  610. ),
  611. ),
  612. ),
  613. ),
  614. ),
  615. ],
  616. ),
  617. );
  618. }
  619. }