keyboard_view.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  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/pargress_bar.dart';
  12. import 'keyboard_controller.dart';
  13. class KeyBoardView extends BaseView<KeyBoardController> {
  14. const KeyBoardView({super.key});
  15. @override
  16. Widget buildBody(BuildContext context) {
  17. return Stack(
  18. children: [
  19. IgnorePointer(child: Assets.images.bgKeyboard.image(width: 360.w)),
  20. SafeArea(
  21. child: Column(
  22. children: [
  23. _buildTitle(),
  24. Expanded(
  25. child: SingleChildScrollView(
  26. physics: NeverScrollableScrollPhysics(),
  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. _buildUserAvatar(true),
  126. const SizedBox(width: 16),
  127. _buildLovePercentage(),
  128. const SizedBox(width: 16),
  129. _buildUserAvatar(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: Container(
  168. padding: EdgeInsets.symmetric(
  169. horizontal: 10.w,
  170. vertical: 8.h,
  171. ),
  172. decoration: BoxDecoration(
  173. color: Color(0xFFFAFAFC),
  174. borderRadius: BorderRadius.circular(12.r),
  175. ),
  176. child: Row(
  177. children: [
  178. Expanded(
  179. child: Column(
  180. children: [
  181. ProgressBar(
  182. title: "激情",
  183. value: controller.appearancePercentage,
  184. color: Color(0xFFFF637D),
  185. ),
  186. SizedBox(height: 6.h),
  187. ProgressBar(
  188. title: "默契",
  189. value: controller.appearancePercentage,
  190. color: Color(0xFFF5E8FC),
  191. ),
  192. ],
  193. ),
  194. ),
  195. SizedBox(width: 21.w),
  196. Expanded(
  197. child: Column(
  198. children: [
  199. ProgressBar(
  200. title: "羁绊",
  201. value: controller.appearancePercentage,
  202. color: Color(0xFFFFC954),
  203. ),
  204. SizedBox(height: 6.h),
  205. ProgressBar(
  206. title: "承诺",
  207. value: controller.appearancePercentage,
  208. color: Color(0xFF6382FF),
  209. ),
  210. ],
  211. ),
  212. ),
  213. ],
  214. ),
  215. ),
  216. ),
  217. ],
  218. ),
  219. ),
  220. ],
  221. );
  222. }
  223. // 用户头像
  224. Widget _buildUserAvatar(bool isUser) {
  225. return Column(
  226. children: [
  227. Stack(
  228. alignment: Alignment.bottomCenter,
  229. children: [
  230. Column(
  231. children: [
  232. Container(
  233. width: 98.r,
  234. height: 98.r,
  235. decoration: ShapeDecoration(
  236. shape: RoundedRectangleBorder(
  237. borderRadius: BorderRadius.circular(50.r),
  238. ),
  239. ),
  240. child: CachedNetworkImage(
  241. imageUrl:
  242. isUser
  243. ? controller.homeInfo?.imageUrl ?? ""
  244. : controller.homeInfo?.targetImageUrl ?? "",
  245. placeholder: (_, __) => const CupertinoActivityIndicator(),
  246. errorWidget:
  247. (context, url, error) => CircleAvatar(
  248. backgroundImage:
  249. Assets.images.iconKeyboardDefaultAvatar
  250. .provider(),
  251. ),
  252. imageBuilder:
  253. (context, imageProvider) => Container(
  254. decoration: ShapeDecoration(
  255. shape: RoundedRectangleBorder(
  256. side: BorderSide(width: 2.r, color: Colors.white),
  257. borderRadius: BorderRadius.circular(50.r),
  258. ),
  259. ),
  260. child: CircleAvatar(backgroundImage: imageProvider),
  261. ),
  262. ),
  263. ),
  264. SizedBox(height: 10.h),
  265. ],
  266. ),
  267. Container(
  268. padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 4.w),
  269. decoration: BoxDecoration(
  270. color: Colors.white,
  271. borderRadius: BorderRadius.circular(22.r),
  272. ),
  273. child:
  274. isUser
  275. ? Text(
  276. controller.homeInfo?.name ?? StringName.keyboardNoLogin,
  277. style: Styles.getTextStyleBlack204W400(14.sp),
  278. )
  279. : Text(
  280. controller.homeInfo?.targetName ??
  281. StringName.keyboardAdd,
  282. style:
  283. controller.homeInfo?.targetName != null
  284. ? Styles.getTextStyleBlack204W400(14.sp)
  285. : TextStyle(
  286. color: const Color(0xFF8651FF),
  287. fontSize: 14.sp,
  288. fontWeight: FontWeight.w500,
  289. ),
  290. ),
  291. ),
  292. ],
  293. ),
  294. const SizedBox(height: 4),
  295. // Text(name, style: const TextStyle(fontSize: 12)),
  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. onTap: controller.clickGoKeyboardManage,
  538. child: Container(
  539. margin: EdgeInsets.symmetric(horizontal: 16.w),
  540. padding: EdgeInsets.only(
  541. left: 11.w,
  542. right: 11.w,
  543. top: 15.h,
  544. bottom: 15.h,
  545. ),
  546. decoration: ShapeDecoration(
  547. color: Colors.white,
  548. shape: RoundedRectangleBorder(
  549. side: BorderSide(width: 2, color: const Color(0xFFF5F4F9)),
  550. borderRadius: BorderRadius.only(
  551. topLeft: Radius.circular(16.r),
  552. topRight: Radius.circular(16.r),
  553. bottomLeft: Radius.circular(16.r),
  554. bottomRight: Radius.circular(16.r),
  555. ),
  556. ),
  557. ),
  558. child: Column(
  559. crossAxisAlignment: CrossAxisAlignment.start,
  560. children: [
  561. Row(
  562. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  563. children: [
  564. Assets.images.iconKeyboardCurrentCharacterTitle.image(
  565. width: 90.w,
  566. height: 20.h,
  567. fit: BoxFit.cover,
  568. ),
  569. Row(
  570. children: [
  571. Text(
  572. StringName.keyboardGoToManage,
  573. style: TextStyle(
  574. color: Colors.black.withAlpha(102),
  575. fontSize: 12.sp,
  576. fontWeight: FontWeight.w500,
  577. ),
  578. ),
  579. Assets.images.iconKeyboardCurrentGo.image(
  580. width: 7.w,
  581. height: 7.w,
  582. ),
  583. ],
  584. ),
  585. ],
  586. ),
  587. const SizedBox(height: 16),
  588. Obx(() {
  589. final list = controller.homeInfo?.characterInfos;
  590. if (list == null) {
  591. return const Center(child: CircularProgressIndicator());
  592. }
  593. final showList = list.take(9).toList();
  594. return SizedBox(
  595. height: 32.h * 3 + 8.h * 2, // 三行高度 + 两个间距
  596. child: GridView.builder(
  597. padding: EdgeInsets.zero,
  598. physics: const NeverScrollableScrollPhysics(),
  599. // 禁止滑动
  600. itemCount: showList.length,
  601. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  602. crossAxisCount: 3, // 每行3个
  603. mainAxisSpacing: 8.h,
  604. crossAxisSpacing: 8.w,
  605. childAspectRatio: 96.w / 32.h, // 控制宽高比
  606. ),
  607. itemBuilder: (context, index) {
  608. return _buildCharacterItem(showList[index]);
  609. },
  610. ),
  611. );
  612. }),
  613. ],
  614. ),
  615. ),
  616. );
  617. }
  618. // 人设标签
  619. Widget _buildCharacterItem(CharacterInfo character) {
  620. return Container(
  621. alignment: Alignment.center,
  622. decoration: ShapeDecoration(
  623. color: const Color(0xFFF5F4F9),
  624. shape: RoundedRectangleBorder(
  625. borderRadius: BorderRadius.circular(31.r),
  626. ),
  627. ),
  628. child: Text(
  629. '${character.emoji}${character.name}',
  630. style: Styles.getTextStyleBlack204W400(12.sp),
  631. maxLines: 1,
  632. overflow: TextOverflow.fade,
  633. ),
  634. );
  635. }
  636. // 活动banner
  637. Widget _buildBanner() {
  638. return Container(
  639. width: 328.w,
  640. height: 64.h,
  641. child: Stack(
  642. children: [
  643. Assets.images.iconKeyboardBanner.image(width: 328.w, height: 64.h),
  644. Positioned(
  645. right: 53.w,
  646. bottom: 18.h,
  647. child: Obx(
  648. () => Text(
  649. controller.formattedTime,
  650. style: TextStyle(
  651. color: Colors.white,
  652. fontSize: 12.sp,
  653. fontWeight: FontWeight.w500,
  654. ),
  655. ),
  656. ),
  657. ),
  658. ],
  659. ),
  660. );
  661. }
  662. @override
  663. Color backgroundColor() {
  664. return const Color(0xFFF5F5F5);
  665. }
  666. }