keyboard_view.dart 25 KB

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