keyboard_view.dart 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  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(
  21. child: Assets.images.bgKeyboard.image(width: 360.w, fit: BoxFit.fill),
  22. ),
  23. SafeArea(
  24. child: Stack(
  25. children: [
  26. Column(
  27. children: [
  28. Expanded(
  29. child: SingleChildScrollView(
  30. child: Column(
  31. children: [
  32. _buildTitle(),
  33. _buildAvatarCard(),
  34. Container(height: 10.h, color: Colors.transparent),
  35. _buildLoveIndexCard(),
  36. SizedBox(height: 10.h),
  37. Container(
  38. padding: EdgeInsets.only(top: 16.h),
  39. decoration: BoxDecoration(
  40. color: Colors.white,
  41. borderRadius: BorderRadius.only(
  42. topLeft: Radius.circular(16.r),
  43. topRight: Radius.circular(16.r),
  44. ),
  45. ),
  46. child: Column(
  47. children: [
  48. _buildHitCard(),
  49. SizedBox(height: 10.h),
  50. _buildKeyboardSettings(),
  51. SizedBox(height: 90.h),
  52. ],
  53. ),
  54. ),
  55. ],
  56. ),
  57. ),
  58. ),
  59. ],
  60. ),
  61. Positioned(
  62. bottom: 0,
  63. left: 16.w,
  64. right: 16.w,
  65. child: _buildBanner(),
  66. ),
  67. ],
  68. ),
  69. ),
  70. ],
  71. );
  72. }
  73. // 顶部标题栏
  74. Widget _buildTitle() {
  75. return Container(
  76. padding: EdgeInsets.only(
  77. top: 12.h,
  78. left: 16.w,
  79. right: 16.w,
  80. bottom: 25.h,
  81. ),
  82. color: Colors.transparent,
  83. child: Row(
  84. children: [
  85. Assets.images.iconKeyboardTitle.image(
  86. width: 110.w,
  87. height: 26.h,
  88. fit: BoxFit.cover,
  89. ),
  90. const Spacer(),
  91. GestureDetector(
  92. onTap: controller.clickVip,
  93. child: Container(
  94. padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 6.w),
  95. decoration: ShapeDecoration(
  96. color: Colors.white.withAlpha(204),
  97. shape: RoundedRectangleBorder(
  98. side: BorderSide(width: 1, color: Colors.white),
  99. borderRadius: BorderRadius.circular(13.r),
  100. ),
  101. ),
  102. child: Row(
  103. children: [
  104. Assets.images.iconKeyboardVipLogo.image(
  105. width: 12.w,
  106. height: 12.w,
  107. ),
  108. const SizedBox(width: 2),
  109. Text(
  110. StringName.keyboardMemberOpen,
  111. style: TextStyle(
  112. color: Color(0xFFA85600),
  113. fontSize: 12,
  114. fontWeight: FontWeight.w400,
  115. ),
  116. ),
  117. Icon(
  118. Icons.chevron_right,
  119. color: Color(0xFFA85600),
  120. size: 11.r,
  121. ),
  122. ],
  123. ),
  124. ),
  125. ),
  126. ],
  127. ),
  128. );
  129. }
  130. // 用户头像卡片
  131. Widget _buildAvatarCard() {
  132. return Obx(() {
  133. return Container(
  134. padding: EdgeInsets.symmetric(horizontal: 22.w),
  135. decoration: BoxDecoration(color: Colors.transparent),
  136. child: Column(
  137. crossAxisAlignment: CrossAxisAlignment.start,
  138. children: [
  139. Row(
  140. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  141. children: [
  142. _buildAvatar(true),
  143. const SizedBox(width: 16),
  144. _buildLovePercentage(),
  145. const SizedBox(width: 16),
  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 CupertinoActivityIndicator();
  276. },
  277. ),
  278. ),
  279. SizedBox(height: 10.h),
  280. ],
  281. ),
  282. Container(
  283. padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 4.w),
  284. decoration: BoxDecoration(
  285. color: Colors.white,
  286. borderRadius: BorderRadius.circular(22.r),
  287. ),
  288. child:
  289. isUser
  290. ? Text(
  291. controller.homeInfo?.name ??
  292. StringName.keyboardNoLogin,
  293. style: Styles.getTextStyleBlack204W400(14.sp),
  294. )
  295. : Text(
  296. controller.homeInfo?.targetName ??
  297. StringName.keyboardAdd,
  298. style:
  299. controller.homeInfo?.targetName != null
  300. ? Styles.getTextStyleBlack204W400(14.sp)
  301. : TextStyle(
  302. color: const Color(0xFF8651FF),
  303. fontSize: 14.sp,
  304. fontWeight: FontWeight.w400,
  305. ),
  306. ),
  307. ),
  308. ],
  309. ),
  310. const SizedBox(height: 4),
  311. // Text(name, style: const TextStyle(fontSize: 12)),
  312. ],
  313. ),
  314. );
  315. }
  316. // 爱情百分比
  317. Widget _buildLovePercentage() {
  318. return Container(
  319. decoration: BoxDecoration(shape: BoxShape.circle),
  320. child: Center(
  321. child: Column(
  322. mainAxisSize: MainAxisSize.min,
  323. children: [
  324. Container(
  325. padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
  326. decoration: ShapeDecoration(
  327. color: Colors.white.withValues(alpha: 153),
  328. shape: RoundedRectangleBorder(
  329. borderRadius: BorderRadius.circular(17.r),
  330. ),
  331. ),
  332. child: Row(
  333. children: [
  334. Assets.images.iconKeyboardLoveLogo.image(
  335. width: 18.w,
  336. height: 18.w,
  337. ),
  338. Text(
  339. controller.homeInfo?.intimacyName ?? "",
  340. textAlign: TextAlign.center,
  341. style: Styles.getTextStyleBlack153W400(14.sp),
  342. ),
  343. ],
  344. ),
  345. ),
  346. SizedBox(height: 7.h),
  347. SizedBox(
  348. width: 88.w,
  349. height: 72.h,
  350. child: Stack(
  351. children: [
  352. Assets.images.bgKeyboardLove.image(width: 88.w, height: 72.h),
  353. Positioned.fill(
  354. child: Center(
  355. child: Obx(
  356. () => Text.rich(
  357. TextSpan(
  358. children: [
  359. TextSpan(
  360. text:
  361. controller.homeInfo?.intimacy != null
  362. ? controller.homeInfo?.intimacy
  363. .toString()
  364. : "?",
  365. style: TextStyle(
  366. color: Colors.white,
  367. fontSize: 33.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. TextSpan(
  381. text: '%',
  382. style: TextStyle(
  383. color: Colors.white,
  384. fontSize: 14.sp,
  385. fontWeight: FontWeight.w700,
  386. shadows: [
  387. Shadow(
  388. offset: Offset(0, 1),
  389. blurRadius: 4.r,
  390. color: const Color(
  391. 0xFFFF6BD3,
  392. ).withValues(alpha: 0.63),
  393. ),
  394. ],
  395. ),
  396. ),
  397. ],
  398. ),
  399. ),
  400. ),
  401. ),
  402. ),
  403. ],
  404. ),
  405. ),
  406. ],
  407. ),
  408. ),
  409. );
  410. }
  411. // 爆款玩法区域
  412. Widget _buildHitCard() {
  413. return Column(
  414. crossAxisAlignment: CrossAxisAlignment.start,
  415. children: [
  416. Padding(
  417. padding: EdgeInsets.symmetric(horizontal: 16.r),
  418. child: Assets.images.iconKeyboardHitPlay.image(
  419. width: 83.w,
  420. height: 22.h,
  421. fit: BoxFit.cover,
  422. ),
  423. ),
  424. const SizedBox(height: 5),
  425. Padding(
  426. padding: EdgeInsets.only(left: 12.w, right: 12.w),
  427. child: Row(
  428. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  429. children: [
  430. GestureDetector(
  431. onTap: controller.clickEasyReply,
  432. child: Container(
  433. width: 166.w,
  434. height: 155.11.w,
  435. decoration: BoxDecoration(
  436. boxShadow: [
  437. BoxShadow(
  438. color: Colors.black.withValues(alpha: 0.10),
  439. offset: Offset(0, 6),
  440. blurRadius: 20,
  441. spreadRadius: 0,
  442. ),
  443. ],
  444. ),
  445. child: Assets.images.bgKeyboardEasyReply.image(
  446. fit: BoxFit.contain,
  447. ),
  448. ),
  449. ),
  450. SizedBox(width: 11.w),
  451. Column(
  452. children: [
  453. _buildFeatureCard(
  454. bg: Assets.images.bgKeyboardIntimacyAnalyze.image(),
  455. title: Assets.images.iconKeyboardInitmacyTitle.image(
  456. width: 80.w,
  457. height: 19.h,
  458. ),
  459. subtitle: StringName.keyboardIntimacySubtitle,
  460. logo: Assets.images.iconKeyboardIntimacyLogo.image(
  461. width: 69.w,
  462. height: 68.w,
  463. fit: BoxFit.cover,
  464. ),
  465. onTap: controller.clickIntimacyAnalyze,
  466. ),
  467. SizedBox(height: 10.h),
  468. _buildFeatureCard(
  469. bg: Assets.images.bgKeyboardScreenshotReply.image(),
  470. title: Assets.images.iconKeyboardScreenshotTitle.image(
  471. width: 72.w,
  472. height: 22.h,
  473. ),
  474. subtitle: StringName.keyboardScreenshotSubtitle,
  475. logo: Assets.images.iconKeyboardScreenshotLogo.image(
  476. width: 67.w,
  477. height: 59.w,
  478. fit: BoxFit.cover,
  479. ),
  480. onTap: controller.clickScreenshotReply,
  481. ),
  482. ],
  483. ),
  484. ],
  485. ),
  486. ),
  487. ],
  488. );
  489. }
  490. // 功能卡片
  491. Widget _buildFeatureCard({
  492. required Widget bg,
  493. required Widget title,
  494. required String subtitle,
  495. required Widget logo,
  496. required VoidCallback onTap,
  497. }) {
  498. return GestureDetector(
  499. onTap: onTap,
  500. child: SizedBox(
  501. height: 73.h,
  502. width: 159.w,
  503. child: Stack(
  504. clipBehavior: Clip.none,
  505. children: [
  506. Positioned(
  507. child: Container(
  508. decoration: BoxDecoration(
  509. boxShadow: [
  510. BoxShadow(
  511. color: Colors.black.withValues(alpha: 0.10),
  512. offset: Offset(0, 6),
  513. blurRadius: 20,
  514. spreadRadius: 0,
  515. ),
  516. ],
  517. ),
  518. child: bg,
  519. ),
  520. ),
  521. Row(
  522. crossAxisAlignment: CrossAxisAlignment.start,
  523. children: [
  524. Container(
  525. padding: EdgeInsets.only(top: 12.h, left: 8.w),
  526. child: Column(
  527. crossAxisAlignment: CrossAxisAlignment.start,
  528. children: [
  529. title,
  530. Padding(
  531. padding: EdgeInsets.only(left: 2.w),
  532. child: Text(
  533. subtitle,
  534. style: TextStyle(
  535. color: Colors.black.withAlpha(128),
  536. fontSize: 10.sp,
  537. fontWeight: FontWeight.w400,
  538. ),
  539. ),
  540. ),
  541. ],
  542. ),
  543. ),
  544. ],
  545. ),
  546. Positioned(top: -5.h, right: 5.w, child: logo),
  547. ],
  548. ),
  549. ),
  550. );
  551. }
  552. // 当前键盘人设信息
  553. Widget _buildKeyboardSettings() {
  554. return GestureDetector(
  555. child: Container(
  556. margin: EdgeInsets.symmetric(horizontal: 16.w),
  557. padding: EdgeInsets.only(
  558. left: 11.w,
  559. right: 11.w,
  560. top: 15.h,
  561. bottom: 15.h,
  562. ),
  563. decoration: ShapeDecoration(
  564. color: Colors.white,
  565. shape: RoundedRectangleBorder(
  566. side: BorderSide(width: 2, color: const Color(0xFFF5F4F9)),
  567. borderRadius: BorderRadius.only(
  568. topLeft: Radius.circular(16.r),
  569. topRight: Radius.circular(16.r),
  570. bottomLeft: Radius.circular(16.r),
  571. bottomRight: Radius.circular(16.r),
  572. ),
  573. ),
  574. ),
  575. child: Column(
  576. crossAxisAlignment: CrossAxisAlignment.start,
  577. children: [
  578. Row(
  579. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  580. children: [
  581. Assets.images.iconKeyboardCurrentCharacterTitle.image(
  582. width: 90.w,
  583. height: 20.h,
  584. fit: BoxFit.cover,
  585. ),
  586. GestureDetector(
  587. onTap: controller.clickGoKeyboardManage,
  588. child: Row(
  589. children: [
  590. Text(
  591. StringName.keyboardGoToManage,
  592. style: TextStyle(
  593. color: Colors.black.withAlpha(102),
  594. fontSize: 12.sp,
  595. fontWeight: FontWeight.w500,
  596. ),
  597. ),
  598. Assets.images.iconKeyboardCurrentGo.image(
  599. width: 7.w,
  600. height: 7.w,
  601. ),
  602. ],
  603. ),
  604. ),
  605. ],
  606. ),
  607. const SizedBox(height: 16),
  608. Obx(() {
  609. final list = controller.homeInfo?.characterInfos;
  610. if (list == null) {
  611. return const Center(child: CircularProgressIndicator());
  612. }
  613. final showList = list.take(9).toList();
  614. return SizedBox(
  615. height: 32.h * 3 + 8.h * 2, // 三行高度 + 两个间距
  616. child: GridView.builder(
  617. padding: EdgeInsets.zero,
  618. physics: const NeverScrollableScrollPhysics(),
  619. // 禁止滑动
  620. itemCount: showList.length,
  621. gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
  622. crossAxisCount: 3, // 每行3个
  623. mainAxisSpacing: 8.h,
  624. crossAxisSpacing: 8.w,
  625. childAspectRatio: 96.w / 32.h, // 控制宽高比
  626. ),
  627. itemBuilder: (context, index) {
  628. return _buildCharacterItem(showList[index]);
  629. },
  630. ),
  631. );
  632. }),
  633. ],
  634. ),
  635. ),
  636. );
  637. }
  638. // 人设标签
  639. Widget _buildCharacterItem(CharacterInfo character) {
  640. return Container(
  641. alignment: Alignment.center,
  642. decoration: ShapeDecoration(
  643. color: const Color(0xFFF5F4F9),
  644. shape: RoundedRectangleBorder(
  645. borderRadius: BorderRadius.circular(31.r),
  646. ),
  647. ),
  648. child: Text(
  649. '${character.emoji}${character.name}',
  650. style: Styles.getTextStyleBlack204W400(12.sp),
  651. maxLines: 1,
  652. overflow: TextOverflow.fade,
  653. ),
  654. );
  655. }
  656. // 活动banner
  657. Widget _buildBanner() {
  658. return Obx(() {
  659. if (!controller.isShowBanner.value) {
  660. return SizedBox(width: 328.w, height: 84.h);
  661. }
  662. return GestureDetector(
  663. onTap: () {
  664. controller.clickBanner();
  665. },
  666. child: SizedBox(
  667. width: 328.w,
  668. height: 84.h,
  669. child: Stack(
  670. clipBehavior: Clip.none,
  671. children: [
  672. Positioned(
  673. top: 20.h,
  674. child: Assets.images.iconKeyboardBanner.image(
  675. width: 328.w,
  676. height: 64.h,
  677. ),
  678. ),
  679. Positioned(
  680. right: 53.w,
  681. bottom: 18.h,
  682. child: Obx(
  683. () => Text(
  684. controller.formattedTime,
  685. style: TextStyle(
  686. color: Colors.white,
  687. fontSize: 12.sp,
  688. fontWeight: FontWeight.w500,
  689. ),
  690. ),
  691. ),
  692. ),
  693. Positioned(
  694. right: 6.w,
  695. top: 0.h,
  696. child: GestureDetector(
  697. onTap: controller.clickCloseBanner,
  698. child: Assets.images.iconKeyboardBannerClose.image(
  699. width: 14.w,
  700. height: 22.h,
  701. ),
  702. ),
  703. ),
  704. ],
  705. ),
  706. ),
  707. );
  708. });
  709. }
  710. @override
  711. Color backgroundColor() {
  712. return const Color(0xFFF5F5F5);
  713. }
  714. }