keyboard_view.dart 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. import 'package:auto_size_text/auto_size_text.dart';
  2. import 'package:cached_network_image/cached_network_image.dart';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter_screenutil/flutter_screenutil.dart';
  6. import 'package:get/get.dart';
  7. import 'package:keyboard/base/base_view.dart';
  8. import 'package:keyboard/data/bean/character_info.dart';
  9. import 'package:keyboard/resource/string.gen.dart';
  10. import '../../data/consts/constants.dart';
  11. import '../../resource/assets.gen.dart';
  12. import '../../utils/styles.dart';
  13. import '../../widget/app_lifecycle_widget.dart';
  14. import '../../widget/avatar/avatar_image_widget.dart';
  15. import '../../widget/heart_fill_view.dart';
  16. import '../../widget/pargress_bar.dart';
  17. import 'keyboard_controller.dart';
  18. class KeyBoardView extends BaseView<KeyBoardController> {
  19. const KeyBoardView({super.key});
  20. @override
  21. Widget buildBody(BuildContext context) {
  22. return Stack(
  23. children: [
  24. IgnorePointer(
  25. child: Assets.images.bgKeyboard.image(width: 360.w, fit: BoxFit.fill),
  26. ),
  27. SafeArea(
  28. child: Stack(
  29. children: [
  30. Column(
  31. children: [
  32. Expanded(
  33. child: SingleChildScrollView(
  34. child: Column(
  35. children: [
  36. _buildTitle(),
  37. _buildAvatarCard(),
  38. Container(height: 10.h, color: Colors.transparent),
  39. _buildLoveIndexCard(),
  40. SizedBox(height: 10.h),
  41. Container(
  42. padding: EdgeInsets.only(top: 16.h),
  43. decoration: BoxDecoration(
  44. color: Colors.white,
  45. borderRadius: BorderRadius.only(
  46. topLeft: Radius.circular(16.r),
  47. topRight: Radius.circular(16.r),
  48. ),
  49. ),
  50. child: Column(
  51. children: [
  52. _buildHitCard(),
  53. SizedBox(height: 10.h),
  54. _buildKeyboardSettings(),
  55. SizedBox(height: 90.h),
  56. ],
  57. ),
  58. ),
  59. ],
  60. ),
  61. ),
  62. ),
  63. ],
  64. ),
  65. Positioned(
  66. bottom: 0,
  67. left: 16.w,
  68. right: 16.w,
  69. child: _buildBanner(),
  70. ),
  71. ],
  72. ),
  73. ),
  74. ],
  75. );
  76. }
  77. // 顶部标题栏
  78. Widget _buildTitle() {
  79. return Container(
  80. padding: EdgeInsets.only(
  81. top: 12.h,
  82. left: 16.w,
  83. right: 16.w,
  84. bottom: 25.h,
  85. ),
  86. color: Colors.transparent,
  87. child: Row(
  88. children: [
  89. Assets.images.iconKeyboardTitle.image(
  90. width: 110.w,
  91. height: 26.h,
  92. fit: BoxFit.cover,
  93. ),
  94. const Spacer(),
  95. GestureDetector(
  96. onTap: controller.clickVip,
  97. child: Container(
  98. padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 6.w),
  99. decoration: ShapeDecoration(
  100. color: Colors.white.withAlpha(204),
  101. shape: RoundedRectangleBorder(
  102. side: BorderSide(width: 1, color: Colors.white),
  103. borderRadius: BorderRadius.circular(13.r),
  104. ),
  105. ),
  106. child: Row(
  107. children: [
  108. Assets.images.iconKeyboardVipLogo.image(
  109. width: 12.w,
  110. height: 12.w,
  111. ),
  112. const SizedBox(width: 2),
  113. Text(
  114. StringName.keyboardMemberOpen,
  115. style: TextStyle(
  116. color: Color(0xFFA85600),
  117. fontSize: 12,
  118. fontWeight: FontWeight.w400,
  119. ),
  120. ),
  121. Icon(
  122. Icons.chevron_right,
  123. color: Color(0xFFA85600),
  124. size: 11.r,
  125. ),
  126. ],
  127. ),
  128. ),
  129. ),
  130. ],
  131. ),
  132. );
  133. }
  134. // 用户头像卡片
  135. Widget _buildAvatarCard() {
  136. return Obx(() {
  137. return Container(
  138. padding: EdgeInsets.symmetric(horizontal: 22.w),
  139. decoration: BoxDecoration(color: Colors.transparent),
  140. child: Column(
  141. crossAxisAlignment: CrossAxisAlignment.start,
  142. children: [
  143. Row(
  144. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  145. children: [
  146. _buildAvatar(true),
  147. _buildLovePercentage(),
  148. _buildAvatar(false),
  149. ],
  150. ),
  151. ],
  152. ),
  153. );
  154. });
  155. }
  156. // 爱情指数卡片
  157. Widget _buildLoveIndexCard() {
  158. return GestureDetector(
  159. onTap: () {
  160. controller.clickZodiacLoveIntimacy();
  161. },
  162. child: Stack(
  163. clipBehavior: Clip.none,
  164. children: [
  165. Positioned(
  166. left: 0,
  167. right: 0,
  168. top: -12.h,
  169. child: Assets.images.iconKeyboardTriangle.image(
  170. color: Colors.white,
  171. width: 20.w,
  172. height: 16.h,
  173. ),
  174. ),
  175. Container(
  176. margin: EdgeInsets.symmetric(horizontal: 22.w),
  177. padding: EdgeInsets.symmetric(vertical: 5.h, horizontal: 5.w),
  178. decoration: BoxDecoration(
  179. color: Colors.white,
  180. borderRadius: BorderRadius.circular(12.r),
  181. ),
  182. child: Row(
  183. children: [
  184. Assets.images.iconKeyboardLoveIndex.image(
  185. width: 72.w,
  186. height: 23.h,
  187. ),
  188. SizedBox(width: 10.w),
  189. Expanded(
  190. child: Obx(() {
  191. return Container(
  192. padding: EdgeInsets.symmetric(
  193. horizontal: 10.w,
  194. vertical: 8.h,
  195. ),
  196. decoration: BoxDecoration(
  197. color: Color(0xFFFAFAFC),
  198. borderRadius: BorderRadius.circular(12.r),
  199. ),
  200. child: Row(
  201. children: [
  202. Expanded(
  203. child: Column(
  204. children: [
  205. ProgressBar(
  206. title: StringName.keyboardPassion,
  207. value: controller.loveIndex.value?.passion,
  208. color: Color(0XFFFF637D),
  209. ),
  210. SizedBox(height: 6.h),
  211. ProgressBar(
  212. title: StringName.keyboardRapport,
  213. value: controller.loveIndex.value?.rapport,
  214. color: Color(0XFFCE63FF),
  215. ),
  216. ],
  217. ),
  218. ),
  219. SizedBox(width: 21.w),
  220. Expanded(
  221. child: Column(
  222. children: [
  223. Obx(() {
  224. return ProgressBar(
  225. title: StringName.keyboardFetter,
  226. value: controller.loveIndex.value?.fetter,
  227. color: Color(0xFFFFC954),
  228. );
  229. }),
  230. SizedBox(height: 6.h),
  231. ProgressBar(
  232. title: StringName.keyboardPromise,
  233. value: controller.loveIndex.value?.promise,
  234. color: Color(0XFF6382FF),
  235. ),
  236. ],
  237. ),
  238. ),
  239. ],
  240. ),
  241. );
  242. }),
  243. ),
  244. ],
  245. ),
  246. ),
  247. ],
  248. ),
  249. );
  250. }
  251. // 用户头像
  252. Widget _buildAvatar(bool isUser) {
  253. return GestureDetector(
  254. onTap: () {
  255. controller.clickAvatar(isUser);
  256. },
  257. child: Column(
  258. children: [
  259. Stack(
  260. alignment: Alignment.bottomCenter,
  261. children: [
  262. Column(
  263. children: [
  264. Container(
  265. width: 98.r,
  266. height: 98.r,
  267. decoration: ShapeDecoration(
  268. shape: RoundedRectangleBorder(
  269. borderRadius: BorderRadius.circular(50.r),
  270. ),
  271. ),
  272. child: CircleAvatarWidget(
  273. imageUrl:
  274. isUser
  275. ? controller.homeInfo?.imageUrl
  276. : controller.homeInfo?.targetImageUrl,
  277. placeholderImage:
  278. Assets.images.iconKeyboardDefaultAvatar.provider(),
  279. borderColor: Colors.white,
  280. borderWidth: 2.r,
  281. placeholder: (_, __) {
  282. return const SizedBox();
  283. },
  284. ),
  285. ),
  286. SizedBox(height: 10.h),
  287. ],
  288. ),
  289. Container(
  290. padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.w),
  291. constraints: BoxConstraints(maxWidth: 76.w),
  292. decoration: BoxDecoration(
  293. color: Colors.white,
  294. borderRadius: BorderRadius.circular(22.r),
  295. ),
  296. child:
  297. isUser
  298. ? AutoSizeText(
  299. controller.homeInfo?.name ??
  300. StringName.keyboardNoLogin,
  301. style: Styles.getTextStyleBlack204W400(14.sp),
  302. maxLines: 1,
  303. minFontSize: 10,
  304. overflow: TextOverflow.clip,
  305. )
  306. : AutoSizeText(
  307. controller.homeInfo?.targetName ??
  308. StringName.keyboardAdd,
  309. style:
  310. controller.homeInfo?.targetName != null
  311. ? Styles.getTextStyleBlack204W400(14.sp)
  312. : TextStyle(
  313. color: const Color(0xFF8651FF),
  314. fontSize: 14.sp,
  315. fontWeight: FontWeight.w400,
  316. ),
  317. maxLines: 1,
  318. minFontSize: 10,
  319. overflow: TextOverflow.ellipsis,
  320. ),
  321. ),
  322. ],
  323. ),
  324. const SizedBox(height: 4),
  325. // Text(name, style: const TextStyle(fontSize: 12)),
  326. ],
  327. ),
  328. );
  329. }
  330. // 爱情百分比
  331. Widget _buildLovePercentage() {
  332. return Container(
  333. decoration: BoxDecoration(shape: BoxShape.circle),
  334. child: Center(
  335. child: Column(
  336. mainAxisSize: MainAxisSize.min,
  337. children: [
  338. Container(
  339. padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
  340. decoration: ShapeDecoration(
  341. color: Colors.white.withValues(alpha: 153),
  342. shape: RoundedRectangleBorder(
  343. borderRadius: BorderRadius.circular(17.r),
  344. ),
  345. ),
  346. child: Row(
  347. children: [
  348. Assets.images.iconKeyboardLoveLogo.image(
  349. width: 18.w,
  350. height: 18.w,
  351. ),
  352. Text(
  353. controller.homeInfo?.intimacyName ?? "",
  354. textAlign: TextAlign.center,
  355. style: Styles.getTextStyleBlack153W400(14.sp),
  356. ),
  357. ],
  358. ),
  359. ),
  360. SizedBox(height: 7.h),
  361. GestureDetector(
  362. onTap: () {
  363. controller.clickLovePercentage();
  364. },
  365. child: SizedBox(
  366. width: 88.w,
  367. height: 72.w,
  368. child: Stack(
  369. children: [
  370. // Assets.images.bgKeyboardLove.image(width: 88.w, height: 72.h),
  371. AppLifecycleWidget(
  372. onAppLifecycleCallback: (isForeground) {
  373. if (isForeground) {
  374. print("qqq App is in foreground");
  375. controller.heartController.start();
  376. } else {
  377. print("qqq App is in background");
  378. controller.heartController.stop();
  379. }
  380. },
  381. child: HeartFillAnimation(
  382. fillProgress:
  383. controller.homeInfo?.intimacy != null
  384. ? controller.homeInfo!.intimacy! / 100
  385. : 0,
  386. width: 88.w,
  387. onControllerCreated: (ctrl) => controller.heartController = ctrl,
  388. ),
  389. ),
  390. Positioned.fill(
  391. child: Center(
  392. child: Obx(
  393. () => Text.rich(
  394. TextSpan(
  395. children: [
  396. TextSpan(
  397. text:
  398. controller.homeInfo?.intimacy != null
  399. ? controller.homeInfo?.intimacy
  400. .toString()
  401. : "?",
  402. style: TextStyle(
  403. color: Colors.white,
  404. fontSize: 33.sp,
  405. fontWeight: FontWeight.w700,
  406. shadows: [
  407. Shadow(
  408. offset: Offset(0, 1),
  409. blurRadius: 4.r,
  410. color: const Color(
  411. 0xFFFF6BD3,
  412. ).withValues(alpha: 0.63),
  413. ),
  414. ],
  415. ),
  416. ),
  417. TextSpan(
  418. text: '%',
  419. style: TextStyle(
  420. color: Colors.white,
  421. fontSize: 14.sp,
  422. fontWeight: FontWeight.w700,
  423. shadows: [
  424. Shadow(
  425. offset: Offset(0, 1),
  426. blurRadius: 4.r,
  427. color: const Color(
  428. 0xFFFF6BD3,
  429. ).withValues(alpha: 0.63),
  430. ),
  431. ],
  432. ),
  433. ),
  434. ],
  435. ),
  436. ),
  437. ),
  438. ),
  439. ),
  440. ],
  441. ),
  442. ),
  443. ),
  444. ],
  445. ),
  446. ),
  447. );
  448. }
  449. // 爆款玩法区域
  450. Widget _buildHitCard() {
  451. return Column(
  452. crossAxisAlignment: CrossAxisAlignment.start,
  453. children: [
  454. Padding(
  455. padding: EdgeInsets.symmetric(horizontal: 16.r),
  456. child: Assets.images.iconKeyboardHitPlay.image(
  457. width: 83.w,
  458. height: 22.h,
  459. fit: BoxFit.cover,
  460. ),
  461. ),
  462. const SizedBox(height: 5),
  463. Padding(
  464. padding: EdgeInsets.only(left: 12.w, right: 12.w),
  465. child: Row(
  466. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  467. children: [
  468. Obx(() {
  469. ImageProvider imageProvider;
  470. // 已选择为默认键盘,显示启用键盘
  471. if (!controller.isDefaultKeyboard.value) {
  472. imageProvider = Assets.images.bgKeyboardStartUsing.provider();
  473. } else {
  474. // 未选择为默认键盘,显示体验键盘
  475. imageProvider =
  476. Assets.images.bgKeyboardTryExperience.provider();
  477. }
  478. return GestureDetector(
  479. onTap: controller.clickEasyReply,
  480. child: SizedBox(
  481. width: 170.w,
  482. height: 155.85.w,
  483. child: Image(image: imageProvider, fit: BoxFit.contain),
  484. ),
  485. );
  486. }),
  487. Column(
  488. children: [
  489. _buildFeatureCard(
  490. bg: Assets.images.bgKeyboardIntimacyAnalyze.image(
  491. width: 162.w,
  492. height: 77.5.w,
  493. fit: BoxFit.contain,
  494. ),
  495. onTap: controller.clickIntimacyAnalyze,
  496. ),
  497. _buildFeatureCard(
  498. bg: Assets.images.bgKeyboardScreenshotReply.image(
  499. width: 162.w,
  500. height: 77.5.w,
  501. fit: BoxFit.contain,
  502. ),
  503. onTap: controller.clickScreenshotReply,
  504. ),
  505. ],
  506. ),
  507. ],
  508. ),
  509. ),
  510. ],
  511. );
  512. }
  513. // 功能卡片
  514. Widget _buildFeatureCard({required Widget bg, required VoidCallback onTap}) {
  515. return GestureDetector(onTap: onTap, child: Container(child: bg));
  516. }
  517. // 当前键盘人设信息
  518. Widget _buildKeyboardSettings() {
  519. return GestureDetector(
  520. onTap: controller.clickGoKeyboardManage,
  521. child: Container(
  522. margin: EdgeInsets.symmetric(horizontal: 16.w),
  523. padding: EdgeInsets.only(
  524. left: 11.w,
  525. right: 11.w,
  526. top: 15.h,
  527. bottom: 15.h,
  528. ),
  529. decoration: ShapeDecoration(
  530. color: Colors.white,
  531. shape: RoundedRectangleBorder(
  532. side: BorderSide(width: 2, color: const Color(0xFFF5F4F9)),
  533. borderRadius: BorderRadius.only(
  534. topLeft: Radius.circular(16.r),
  535. topRight: Radius.circular(16.r),
  536. bottomLeft: Radius.circular(16.r),
  537. bottomRight: Radius.circular(16.r),
  538. ),
  539. ),
  540. ),
  541. child: Column(
  542. crossAxisAlignment: CrossAxisAlignment.start,
  543. children: [
  544. Row(
  545. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  546. children: [
  547. Assets.images.iconKeyboardCurrentCharacterTitle.image(
  548. width: 90.w,
  549. height: 20.h,
  550. fit: BoxFit.cover,
  551. ),
  552. GestureDetector(
  553. // onTap: controller.clickGoKeyboardManage,
  554. child: Row(
  555. children: [
  556. Text(
  557. StringName.keyboardGoToManage,
  558. style: TextStyle(
  559. color: Colors.black.withAlpha(102),
  560. fontSize: 12.sp,
  561. fontWeight: FontWeight.w500,
  562. ),
  563. ),
  564. Assets.images.iconKeyboardCurrentGo.image(
  565. width: 7.w,
  566. height: 7.w,
  567. ),
  568. ],
  569. ),
  570. ),
  571. ],
  572. ),
  573. const SizedBox(height: 16),
  574. Obx(() {
  575. final list = controller.homeInfo?.characterInfos;
  576. if (list == null) {
  577. return const Center(child: CircularProgressIndicator());
  578. }
  579. final showList = list.take(9).toList();
  580. return SizedBox(
  581. height: 32.h * 3 + 8.h * 2, // 三行高度 + 两个间距
  582. child: GridView.builder(
  583. padding: EdgeInsets.zero,
  584. physics: const NeverScrollableScrollPhysics(),
  585. // 禁止滑动
  586. itemCount: showList.length,
  587. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  588. crossAxisCount: 3, // 每行3个
  589. mainAxisSpacing: 8.h,
  590. crossAxisSpacing: 8.w,
  591. childAspectRatio: 96.w / 32.h, // 控制宽高比
  592. ),
  593. itemBuilder: (context, index) {
  594. return _buildCharacterItem(showList[index]);
  595. },
  596. ),
  597. );
  598. }),
  599. ],
  600. ),
  601. ),
  602. );
  603. }
  604. // 人设标签
  605. Widget _buildCharacterItem(CharacterInfo character) {
  606. return Container(
  607. alignment: Alignment.center,
  608. decoration: ShapeDecoration(
  609. color: const Color(0xFFF5F4F9),
  610. shape: RoundedRectangleBorder(
  611. borderRadius: BorderRadius.circular(31.r),
  612. ),
  613. ),
  614. child: Text(
  615. '${character.emoji}${character.name}',
  616. style: Styles.getTextStyleBlack204W400(12.sp),
  617. maxLines: 1,
  618. overflow: TextOverflow.fade,
  619. ),
  620. );
  621. }
  622. // 活动banner
  623. Widget _buildBanner() {
  624. return Obx(() {
  625. if (!controller.isShowBanner.value) {
  626. return SizedBox(width: 328.w, height: 84.h);
  627. }
  628. return GestureDetector(
  629. onTap: () {
  630. controller.clickBanner();
  631. },
  632. child: SizedBox(
  633. width: 328.w,
  634. height: 84.h,
  635. child: Stack(
  636. clipBehavior: Clip.none,
  637. children: [
  638. Positioned(
  639. top: 20.h,
  640. child: Visibility(
  641. visible: isNotHWChannel(),
  642. replacement: Assets.images.iconKeyboardBannerNoCountdown
  643. .image(width: 328.w, height: 64.h),
  644. child: Assets.images.iconKeyboardBanner.image(
  645. width: 328.w,
  646. height: 64.h,
  647. ),
  648. ),
  649. ),
  650. Visibility(
  651. visible: isNotHWChannel(),
  652. child: Positioned(
  653. right: 53.w,
  654. bottom: 18.h,
  655. child: Obx(
  656. () => Text(
  657. controller.formattedTime,
  658. style: TextStyle(
  659. color: Colors.white,
  660. fontSize: 12.sp,
  661. fontWeight: FontWeight.w500,
  662. ),
  663. ),
  664. ),
  665. ),
  666. ),
  667. Positioned(
  668. right: 6.w,
  669. top: 0.h,
  670. child: GestureDetector(
  671. onTap: controller.clickCloseBanner,
  672. child: Assets.images.iconKeyboardBannerClose.image(
  673. width: 14.w,
  674. height: 22.h,
  675. ),
  676. ),
  677. ),
  678. ],
  679. ),
  680. ),
  681. );
  682. });
  683. }
  684. @override
  685. Color backgroundColor() {
  686. return const Color(0xFFF5F5F5);
  687. }
  688. }