zodiac_love_intimacy_page.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. import 'package:collection/collection.dart';
  2. import 'package:flutter/Material.dart';
  3. import 'package:flutter_screenutil/flutter_screenutil.dart';
  4. import 'package:get/get.dart';
  5. import 'package:keyboard/base/base_page.dart';
  6. import 'package:keyboard/data/repository/account_repository.dart';
  7. import 'package:keyboard/dialog/login/login_dialog.dart';
  8. import 'package:keyboard/module/login/login_page.dart';
  9. import 'package:keyboard/module/profile/profile_page.dart';
  10. import 'package:keyboard/module/zodiac_love_intimacy/tody/zodiac_love_today_view.dart';
  11. import 'package:keyboard/module/zodiac_love_intimacy/zodiac_love_intimacy_controller.dart';
  12. import 'package:keyboard/resource/colors.gen.dart';
  13. import 'package:keyboard/router/app_page_arguments.dart';
  14. import 'package:keyboard/utils/toast_util.dart';
  15. import 'package:keyboard/widget/platform_util.dart';
  16. import 'package:lottie/lottie.dart';
  17. import 'package:nested_scroll_views/material.dart';
  18. import '../../data/repository/keyboard_repository.dart';
  19. import '../../di/get_it.dart';
  20. import '../../resource/assets.gen.dart';
  21. import '../../resource/string.gen.dart';
  22. import '../../router/app_pages.dart';
  23. import '../../utils/age_zodiac_sign_util.dart';
  24. import '../../widget/avatar/avatar_image_widget.dart';
  25. import '../../widget/status_bar_placeholder_widget.dart';
  26. import '../../widget/top_bar.dart';
  27. import '../user_profile/user_profile_page.dart';
  28. import 'enums/zodiac_love_intimacy_tab.dart';
  29. import 'future_week/zodiac_love_future_week_view.dart';
  30. /// 星座恋爱分析Tab页
  31. class ZodiacLoveIntimacyPage extends BasePage<ZodiacLoveIntimacyController> {
  32. const ZodiacLoveIntimacyPage({super.key});
  33. static start({ZodiacLoveIntimacyTab tab = ZodiacLoveIntimacyTab.today}) {
  34. var accountRepository = getIt.get<AccountRepository>();
  35. var keyboardRepository = getIt.get<KeyboardRepository>();
  36. // 未登录,要求先登录
  37. if (!accountRepository.isLogin.value) {
  38. ToastUtil.show(StringName.accountNoLogin);
  39. if (PlatformUtil.isIOS) {
  40. LoginPage.start();
  41. } else {
  42. LoginDialog.show();
  43. }
  44. return;
  45. }
  46. // 如果用户未设置生日,则要求先设置生日,才能跳转
  47. if (accountRepository.userInfo.value?.birthday == null) {
  48. ToastUtil.show(StringName.userNotSetBirthdayTip);
  49. UserProfilePage.start();
  50. return;
  51. }
  52. // 未选择档案,要求先创建或选择档案
  53. if (keyboardRepository.chooseKeyboardInfo.value == null) {
  54. ToastUtil.show(StringName.userNotCreateProfile);
  55. ProfilePage.start();
  56. return;
  57. }
  58. var args = {AppPageArguments.index: tab.tabIndex};
  59. Get.toNamed(RoutePath.zodiacLoveIntimacy, arguments: args);
  60. }
  61. @override
  62. bool immersive() {
  63. return true;
  64. }
  65. @override
  66. bool statusBarDarkFont() {
  67. return false;
  68. }
  69. @override
  70. backgroundColor() {
  71. return Colors.transparent;
  72. }
  73. @override
  74. Widget buildBody(BuildContext context) {
  75. return Scaffold(
  76. backgroundColor: backgroundColor(),
  77. body: Stack(
  78. children: [
  79. // 背景图
  80. Positioned.fill(child: _buildBackgroundImage()),
  81. // 内容,填充剩余部分
  82. Positioned.fill(
  83. top: 0,
  84. left: 0,
  85. right: 0,
  86. bottom: 0,
  87. child: _buildContent(context),
  88. ),
  89. // 状态栏和标题栏
  90. Positioned(
  91. left: 0,
  92. top: 0,
  93. right: 0,
  94. child: Column(
  95. mainAxisSize: MainAxisSize.min,
  96. children: [StatusBarPlaceholderWidget(), _buildTopBar()],
  97. ),
  98. ),
  99. ],
  100. ),
  101. );
  102. }
  103. /// 背景图
  104. Widget _buildBackgroundImage() {
  105. return Container(
  106. child: Assets.images.bgZodiacLoveIntimacy.image(
  107. fit: BoxFit.cover,
  108. width: double.infinity,
  109. height: double.infinity,
  110. ),
  111. );
  112. }
  113. /// 顶部栏
  114. Widget _buildTopBar() {
  115. return TopBar(
  116. leftWidget: GestureDetector(
  117. onTap: controller.clickBack,
  118. child: Assets.images.iconWhiteBackArrow.image(
  119. width: 24.w,
  120. height: 24.h,
  121. ),
  122. ),
  123. centerWidget: Text(
  124. StringName.zodiacLoveIntimacy,
  125. style: TextStyle(
  126. color: ColorName.white,
  127. fontSize: 17.sp,
  128. fontWeight: FontWeight.w500,
  129. ),
  130. ),
  131. );
  132. }
  133. /// 指示器
  134. Widget _buildTabBar() {
  135. return Obx(() {
  136. return PreferredSize(
  137. preferredSize: Size.fromHeight(45.h),
  138. child: Stack(
  139. children: [
  140. Positioned(
  141. left: 0,
  142. right: 0,
  143. top: 6.h,
  144. child: Container(
  145. height: 41.h,
  146. // 顶部2边圆角
  147. decoration: BoxDecoration(
  148. color: Color(0xFFEBE7FD),
  149. borderRadius: BorderRadius.only(
  150. topLeft: Radius.circular(45.r),
  151. topRight: Radius.circular(45.r),
  152. ),
  153. ),
  154. ),
  155. ),
  156. // Tab
  157. TabBar(
  158. // 只有2个Tab,均分宽度,所以不可以滚动
  159. isScrollable: false,
  160. // 去除底部的黑线
  161. dividerHeight: 0,
  162. // 去掉自带的指示器
  163. indicator: BoxDecoration(),
  164. // 对齐方式,填充式对齐
  165. tabAlignment: TabAlignment.fill,
  166. // 指示器的高度设置为0,才能完全隐藏,否则还是会有一条线的高度
  167. indicatorWeight: 0,
  168. // 移除左右边距
  169. padding: EdgeInsets.zero,
  170. // 移除指示器与标签的间距
  171. indicatorPadding: EdgeInsets.zero,
  172. // 移除标签内部边距
  173. labelPadding: EdgeInsets.zero,
  174. // 配置Tab数据
  175. tabs:
  176. controller.tabBarList.mapIndexed((
  177. int index,
  178. ZodiacLoveIntimacyTab tab,
  179. ) {
  180. bool isSelected = controller.currentTabIndex.value == index;
  181. return _buildTab(tab, isSelected);
  182. }).toList(),
  183. controller: controller.tabController,
  184. onTap: (index) {
  185. controller.handleTabChange(index);
  186. },
  187. ),
  188. ],
  189. ),
  190. );
  191. });
  192. }
  193. /// 每个Tab
  194. Tab _buildTab(ZodiacLoveIntimacyTab tab, bool isSelected) {
  195. String tabName = tab.getTabName();
  196. TextStyle tabTextStyle;
  197. if (isSelected) {
  198. // 选中时的颜色
  199. tabTextStyle = TextStyle(
  200. fontSize: 14.sp,
  201. fontWeight: FontWeight.w700,
  202. color: ColorName.black80,
  203. );
  204. } else {
  205. // 未选中时的颜色
  206. tabTextStyle = TextStyle(
  207. fontSize: 14.sp,
  208. fontWeight: FontWeight.w600,
  209. color: ColorName.black60,
  210. );
  211. }
  212. return Tab(
  213. child: Container(
  214. width: double.infinity,
  215. height: double.infinity,
  216. // 未选中时,距离顶部有一定距离,选中时则撑满TabBar
  217. margin: EdgeInsets.only(top: isSelected ? 0 : 6.h),
  218. decoration: BoxDecoration(
  219. image:
  220. // 选中时,才有背景图
  221. isSelected
  222. ? DecorationImage(
  223. image: tab.getTabSelectedBg(),
  224. fit: BoxFit.fill,
  225. )
  226. : null,
  227. ),
  228. child: Center(
  229. child: Container(
  230. margin: EdgeInsets.only(top: 5.h),
  231. child: Text(tabName, style: tabTextStyle),
  232. ),
  233. ),
  234. ),
  235. );
  236. }
  237. /// 内容
  238. Widget _buildContent(BuildContext context) {
  239. return Column(
  240. children: [
  241. // 顶部基础信息
  242. _buildTopBasicInfoHeader(),
  243. // TabBar
  244. _buildTabBar(),
  245. // PageView
  246. Expanded(child: _buildPage()),
  247. ],
  248. );
  249. }
  250. /// 顶部基础信息视图
  251. Widget _buildTopBasicInfoHeader() {
  252. return Column(
  253. mainAxisAlignment: MainAxisAlignment.center,
  254. crossAxisAlignment: CrossAxisAlignment.center,
  255. children: [
  256. SizedBox(height: 70.w),
  257. // 头像布局
  258. _buildAvatarLayout(),
  259. SizedBox(height: 14.w),
  260. // 星座梗语与解读
  261. _buildZodiacDesc(),
  262. SizedBox(height: 16.w),
  263. ],
  264. );
  265. }
  266. /// 头像布局
  267. Widget _buildAvatarLayout() {
  268. return Obx(() {
  269. String? myAvatar =
  270. controller.zodiacLoveIntimacyLoveInfoResponse.value?.imageUrl ?? "";
  271. if (myAvatar.isEmpty) {
  272. myAvatar = controller.userInfo.value?.imageUrl ?? "";
  273. }
  274. return Row(
  275. mainAxisAlignment: MainAxisAlignment.center,
  276. children: [
  277. // 我的头像
  278. _buildAvatarAndZodiac(
  279. imageUrl: myAvatar,
  280. zodiac: controller.myZodiacInfo,
  281. ),
  282. // 爱心动画
  283. Lottie.asset(
  284. Assets.anim.animNewUserData,
  285. repeat: true,
  286. width: 131.w,
  287. fit: BoxFit.contain,
  288. ),
  289. // Ta的头像
  290. _buildAvatarAndZodiac(
  291. imageUrl:
  292. controller
  293. .zodiacLoveIntimacyLoveInfoResponse
  294. .value
  295. ?.targetImageUrl,
  296. zodiac: controller.taZodiacInfo,
  297. ),
  298. ],
  299. );
  300. });
  301. }
  302. /// 头像和星座
  303. Widget _buildAvatarAndZodiac({
  304. required String? imageUrl,
  305. required Zodiac? zodiac,
  306. }) {
  307. return Column(
  308. children: [
  309. SizedBox(height: 20.w),
  310. // 头像
  311. CircleAvatarWidget(
  312. image: Assets.images.iconKeyboardDefaultAvatar.provider(),
  313. imageUrl: imageUrl,
  314. size: 88.w,
  315. borderColor: Colors.white,
  316. borderWidth: 2.r,
  317. placeholder: (_, __) => const SizedBox.shrink(),
  318. ),
  319. SizedBox(height: 14.w),
  320. // 星座
  321. Builder(
  322. builder: (context) {
  323. if (zodiac != null) {
  324. return Row(
  325. children: [
  326. zodiac.image.image(width: 14.w, height: 14.w),
  327. SizedBox(width: 4.w),
  328. Text(
  329. zodiac.name,
  330. style: TextStyle(
  331. color: Colors.white,
  332. fontSize: 15.sp,
  333. fontWeight: FontWeight.w700,
  334. ),
  335. ),
  336. ],
  337. );
  338. } else {
  339. return const SizedBox.shrink();
  340. }
  341. },
  342. ),
  343. ],
  344. );
  345. }
  346. /// 星座梗语与解读
  347. Widget _buildZodiacDesc() {
  348. return Obx(() {
  349. var info = controller.zodiacLoveIntimacyLoveInfoResponse.value;
  350. var meme = info?.meme ?? "";
  351. var explain = info?.explain ?? "";
  352. // 无数据,不显示
  353. if (meme.isEmpty && explain.isEmpty) {
  354. return SizedBox(height: 67.h);
  355. }
  356. return Stack(
  357. alignment: Alignment.center,
  358. children: [
  359. Positioned(
  360. // IntrinsicWidth,包裹内容,宽度不撑大到屏幕宽度
  361. child: IntrinsicWidth(
  362. child: Container(
  363. alignment: Alignment.center,
  364. padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 42.w),
  365. // 圆角背景
  366. decoration: ShapeDecoration(
  367. color: const Color(0x29B80081),
  368. shape: RoundedRectangleBorder(
  369. borderRadius: BorderRadius.circular(16.r),
  370. ),
  371. ),
  372. child: Column(
  373. mainAxisSize: MainAxisSize.min,
  374. children: [
  375. // 梗语标题
  376. Visibility(
  377. visible: meme.isNotEmpty,
  378. child: Container(
  379. margin: EdgeInsets.only(bottom: 2.h),
  380. child: Text(
  381. meme,
  382. textAlign: TextAlign.center,
  383. style: TextStyle(
  384. color: ColorName.white,
  385. fontSize: 12.sp,
  386. fontWeight: FontWeight.w500,
  387. ),
  388. ),
  389. ),
  390. ),
  391. // 星座梗语
  392. Visibility(
  393. visible: explain.isNotEmpty,
  394. child: Text(
  395. explain,
  396. textAlign: TextAlign.center,
  397. style: TextStyle(
  398. color: ColorName.white,
  399. fontSize: 12.sp,
  400. fontWeight: FontWeight.w500,
  401. ),
  402. ),
  403. ),
  404. ],
  405. ),
  406. ),
  407. ),
  408. ),
  409. // 左上角的逗号
  410. Positioned(
  411. top: 5.w,
  412. left: 8.w,
  413. child: Assets.images.iconNewUserZodiacLeft.image(
  414. width: 13.w,
  415. height: 13.w,
  416. fit: BoxFit.contain,
  417. ),
  418. ),
  419. // 右下角的逗号
  420. Positioned(
  421. right: 8.w,
  422. bottom: 5.w,
  423. child: Assets.images.iconNewUserZodiacRight.image(
  424. width: 13.w,
  425. height: 13.w,
  426. fit: BoxFit.contain,
  427. ),
  428. ),
  429. ],
  430. );
  431. });
  432. }
  433. /// PageView
  434. Widget _buildPage() {
  435. return Obx(() {
  436. return NestedPageView(
  437. controller: controller.pageController,
  438. // 保持页面缓存
  439. wantKeepAlive: true,
  440. // 是否禁止滑动切换
  441. physics:
  442. controller.isPageViewSwipeEnabled.value
  443. ? ScrollPhysics()
  444. : NeverScrollableScrollPhysics(),
  445. onPageChanged: (index) {
  446. controller.handlePageChange(index);
  447. },
  448. children: [
  449. // 今日Tab
  450. ZodiacLoveTodayView(),
  451. // 未来一周Tab
  452. ZodiacLoveFutureWeekView(),
  453. ],
  454. );
  455. });
  456. }
  457. }