keyboard_view.dart 23 KB

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