character_custom_detail_page.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. import 'package:cached_network_image/cached_network_image.dart';
  2. import 'package:flutter/cupertino.dart';
  3. import 'package:flutter_screenutil/flutter_screenutil.dart';
  4. import 'package:keyboard/base/base_page.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:keyboard/data/bean/character_info.dart';
  7. import '../../../data/bean/custom_config_info.dart';
  8. import '../../../resource/assets.gen.dart';
  9. import '../../../resource/string.gen.dart';
  10. import '../../../router/app_pages.dart';
  11. import '../../../utils/age_zodiac_sign_util.dart';
  12. import '../../../utils/styles.dart';
  13. import '../../../widget/avatar/avatar_image_widget.dart';
  14. import 'character_custom_detail_controller.dart';
  15. import 'package:get/get.dart';
  16. class CharacterCustomDetailPage
  17. extends BasePage<CharacterCustomDetailController> {
  18. const CharacterCustomDetailPage({super.key});
  19. static Future start({
  20. List<Hobbies>? hobbiesSelectLabels,
  21. List<CharactersList>? characterSelectLabels,
  22. String? characterCustomName,
  23. CharacterInfo? currentCharacterInfo,
  24. }) async {
  25. return Get.toNamed(
  26. RoutePath.characterCustomDetail,
  27. arguments: {
  28. 'hobbiesSelectLabels': hobbiesSelectLabels,
  29. 'characterSelectLabels': characterSelectLabels,
  30. 'characterCustomName': characterCustomName,
  31. 'currentCharacterInfo': currentCharacterInfo,
  32. },
  33. );
  34. }
  35. @override
  36. bool immersive() {
  37. return true;
  38. }
  39. @override
  40. Widget buildBody(BuildContext context) {
  41. return Stack(
  42. children: [
  43. Container(
  44. child: Assets.images.bgCharacterCustomDetail.image(
  45. width: double.infinity,
  46. fit: BoxFit.fill,
  47. ),
  48. ),
  49. SafeArea(
  50. child: Container(
  51. color: Colors.transparent,
  52. alignment: Alignment.topCenter,
  53. child: Stack(
  54. children: [
  55. Column(
  56. children: [
  57. _buildTitle(),
  58. SizedBox(height: 42.h),
  59. Expanded(
  60. child: Container(
  61. decoration: ShapeDecoration(
  62. color: Color(0xFFF6F5FA),
  63. shape: RoundedRectangleBorder(
  64. borderRadius: BorderRadius.only(
  65. topLeft: Radius.circular(20.r),
  66. topRight: Radius.circular(20.r),
  67. ),
  68. ),
  69. ),
  70. child: SingleChildScrollView(
  71. child: Column(
  72. children: [
  73. _buildNameCard(),
  74. SizedBox(height: 34.h),
  75. _buildGenderCard(),
  76. SizedBox(height: 10.h),
  77. _buildBirthdayCard(),
  78. SizedBox(height: 10.h),
  79. _hobbiesCard(),
  80. SizedBox(height: 18.h),
  81. _characterCard(),
  82. ],
  83. ),
  84. ),
  85. ),
  86. ),
  87. Container(
  88. color: Color(0xFFF6F5FA),
  89. child: _buildUnlockButton(),
  90. ),
  91. ],
  92. ),
  93. Positioned(left: 16.w, top: 60.h, child: _buildAvatar()),
  94. Positioned(left: 68.w, top: 112.h, child: _buildAvatarSwitch()),
  95. ],
  96. ),
  97. ),
  98. ),
  99. ],
  100. );
  101. }
  102. _buildTitle() {
  103. return Container(
  104. alignment: Alignment.centerLeft,
  105. padding: EdgeInsets.only(top: 12.h, left: 16.w),
  106. child: GestureDetector(
  107. onTap: controller.clickBack,
  108. child: Assets.images.iconMineBackArrow.image(width: 24.w, height: 24.w),
  109. ),
  110. );
  111. }
  112. _buildNameCard() {
  113. return GestureDetector(
  114. onTap: () {
  115. controller.clickNickname();
  116. },
  117. child: Container(
  118. padding: EdgeInsets.only(left: 104.w, top: 14.h),
  119. child: Row(
  120. crossAxisAlignment: CrossAxisAlignment.end,
  121. children: [
  122. Obx(() {
  123. return Text(
  124. controller.currentNickname ?? "请输入昵称",
  125. textAlign: TextAlign.center,
  126. style: TextStyle(
  127. color: Colors.black.withAlpha(204),
  128. fontSize: 18.sp,
  129. fontWeight: FontWeight.w500,
  130. ),
  131. );
  132. }),
  133. Container(
  134. child: Assets.images.iconCharacterCustomDetailEdit.image(
  135. width: 20.r,
  136. height: 20.r,
  137. ),
  138. ),
  139. ],
  140. ),
  141. ),
  142. );
  143. }
  144. _buildAvatar() {
  145. return GestureDetector(
  146. onTap: controller.nextAvatar,
  147. child: Obx(() {
  148. return Container(
  149. width: 72.r,
  150. height: 72.r,
  151. child:
  152. controller.avatarUrl.isNotEmpty
  153. ? CircleAvatarWidget(
  154. image: Assets.images.iconKeyboardDefaultAvatar.provider(),
  155. imageUrl: controller.avatarUrl,
  156. size: 72.w,
  157. borderColor: Colors.white,
  158. borderWidth: 2.r,
  159. placeholder: (_, __) {
  160. return const CupertinoActivityIndicator();
  161. },
  162. )
  163. : SizedBox(),
  164. );
  165. }),
  166. );
  167. }
  168. _buildAvatarSwitch() {
  169. return GestureDetector(
  170. onTap: controller.nextAvatar,
  171. child: SizedBox(
  172. width: 22.r,
  173. height: 22.r,
  174. child: Assets.images.iconCharacterCustomDetailSwitch.image(
  175. width: 22.r,
  176. height: 22.r,
  177. ),
  178. ),
  179. );
  180. }
  181. // 性别
  182. Widget _buildGenderCard() {
  183. return _buildListItem(
  184. onBottomTap: () {
  185. controller.clickGender();
  186. },
  187. firstWidget: Text('性别', style: Styles.getTextStyleBlack204W400(14.sp)),
  188. bottomWidget: Obx(() {
  189. return Row(
  190. children: [
  191. controller.genderImage != null
  192. ? controller.genderImage!.image(width: 24.w, height: 24.w)
  193. : const SizedBox(),
  194. SizedBox(width: 6.w),
  195. Text(
  196. controller.genderText,
  197. style: Styles.getTextStyleBlack204W400(14.sp),
  198. ),
  199. Spacer(),
  200. Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
  201. ],
  202. );
  203. }),
  204. );
  205. }
  206. Widget _buildBirthdayCard() {
  207. return _buildListItem(
  208. onBottomTap: () {
  209. controller.clickBirthday();
  210. },
  211. firstWidget: Text('出生日期', style: Styles.getTextStyleBlack204W400(14.sp)),
  212. bottomWidget: Obx(() {
  213. return Row(
  214. children: [
  215. Text(
  216. controller.currentBirthday ?? "请选择",
  217. style: Styles.getTextStyleBlack204W400(14.sp),
  218. ),
  219. SizedBox(width: 12.w),
  220. Text(
  221. controller.currentBirthday != null
  222. ? '${AgeZodiacSignUtil.calculateAgeFromString(controller.currentBirthday!).toString()}岁'
  223. : "",
  224. style: Styles.getTextStyleBlack204W400(14.sp),
  225. ),
  226. Spacer(),
  227. Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
  228. ],
  229. );
  230. }),
  231. );
  232. }
  233. Widget _hobbiesCard() {
  234. return Obx(() {
  235. return _buildTagCard(
  236. title: StringName.hobbies,
  237. labels: controller.hobbiesSelectLabels,
  238. isShowEmoji: true,
  239. onTap: () {
  240. controller.clickHobbies();
  241. },
  242. );
  243. });
  244. }
  245. Widget _characterCard() {
  246. return Obx(() {
  247. return _buildTagCard(
  248. title: StringName.characterKeywords,
  249. labels: controller.characterSelectLabels,
  250. isShowEmoji: true,
  251. onTap: () {
  252. controller.clickCharacter();
  253. },
  254. );
  255. });
  256. }
  257. Widget _buildUnlockButton() {
  258. return GestureDetector(
  259. onTap: () {
  260. controller.clickUnlockButton();
  261. },
  262. child: Container(
  263. width: double.infinity,
  264. margin: EdgeInsets.only(left: 16.w, right: 16.w, bottom: 16.h),
  265. height: 48.h,
  266. alignment: Alignment.center,
  267. decoration: ShapeDecoration(
  268. color: const Color(0xFF7D46FC),
  269. shape: RoundedRectangleBorder(
  270. borderRadius: BorderRadius.circular(50.r),
  271. ),
  272. ),
  273. child:
  274. controller.isEditMode
  275. ? Text(
  276. StringName.save,
  277. textAlign: TextAlign.center,
  278. style: Styles.getTextStyleWhiteW500(16.sp),
  279. )
  280. : Row(
  281. mainAxisAlignment: MainAxisAlignment.center,
  282. children: [
  283. Assets.images.iconCharacterCustomDetailLock.image(
  284. width: 22.w,
  285. height: 22.w,
  286. ),
  287. Text(
  288. StringName.unlockExclusiveCharacter,
  289. textAlign: TextAlign.center,
  290. style: Styles.getTextStyleWhiteW500(16.sp),
  291. ),
  292. ],
  293. ),
  294. ),
  295. );
  296. }
  297. // 列表项
  298. Widget _buildListItem({
  299. required Widget firstWidget,
  300. required Widget bottomWidget,
  301. VoidCallback? onBottomTap,
  302. VoidCallback? onFirstTap,
  303. }) {
  304. return Container(
  305. padding: EdgeInsets.only(
  306. left: 12.w,
  307. right: 12.w,
  308. top: 14.h,
  309. bottom: 14.h,
  310. ),
  311. margin: EdgeInsets.only(left: 16.w, right: 16.w),
  312. width: double.infinity,
  313. decoration: ShapeDecoration(
  314. color: Colors.white,
  315. shape: RoundedRectangleBorder(
  316. borderRadius: BorderRadius.circular(12.r),
  317. ),
  318. ),
  319. child: Column(
  320. crossAxisAlignment: CrossAxisAlignment.start,
  321. mainAxisAlignment: MainAxisAlignment.center,
  322. children: [
  323. GestureDetector(
  324. behavior: HitTestBehavior.opaque,
  325. onTap: onFirstTap,
  326. child: firstWidget,
  327. ),
  328. _buildDivider(),
  329. GestureDetector(
  330. behavior: HitTestBehavior.opaque,
  331. onTap: onBottomTap,
  332. child: bottomWidget,
  333. ),
  334. ],
  335. ),
  336. );
  337. }
  338. // 下划线
  339. Widget _buildDivider() {
  340. return Container(
  341. margin: EdgeInsets.only(top: 8.h, bottom: 8.h),
  342. width: 304.w,
  343. decoration: ShapeDecoration(
  344. shape: RoundedRectangleBorder(
  345. side: BorderSide(
  346. width: 0.5.r,
  347. strokeAlign: BorderSide.strokeAlignCenter,
  348. color: const Color(0xFFF5F4F9),
  349. ),
  350. ),
  351. ),
  352. );
  353. }
  354. Widget _buildTagCard({
  355. required String title,
  356. required List<dynamic> labels,
  357. required bool isShowEmoji,
  358. VoidCallback? onTap,
  359. }) {
  360. return _buildListItem(
  361. onBottomTap: onTap,
  362. firstWidget: Text(title, style: Styles.getTextStyleBlack204W400(14.sp)),
  363. bottomWidget: Row(
  364. children: [
  365. Expanded(
  366. child: Wrap(
  367. spacing: 8.w,
  368. runSpacing: 8.h,
  369. children:
  370. labels.asMap().entries.map((entry) {
  371. int index = entry.key;
  372. var label = entry.value;
  373. // 根据索引设置不同的颜色
  374. Color color;
  375. switch (index) {
  376. case 0:
  377. color = Color(0xFFFEECE0);
  378. break;
  379. case 1:
  380. color = Color(0xFFE8E4FC);
  381. break;
  382. case 2:
  383. color = Color(0xFFE8F8E0);
  384. break;
  385. default:
  386. color = Color(0xFFFEECE0);
  387. }
  388. return _buildColorTag(
  389. color: color,
  390. name: label.name,
  391. emoji: label.emoji,
  392. isShowEmoji: isShowEmoji,
  393. );
  394. }).toList(),
  395. ),
  396. ),
  397. SizedBox(width: 8.w),
  398. Assets.images.iconArrowRight.image(width: 24.w, height: 24.w),
  399. ],
  400. ),
  401. );
  402. }
  403. Widget _buildColorTag({
  404. required Color color,
  405. String? name,
  406. String? emoji,
  407. bool isShowEmoji = true,
  408. }) {
  409. return Container(
  410. padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 3.h),
  411. decoration: ShapeDecoration(
  412. color: color,
  413. shape: RoundedRectangleBorder(
  414. borderRadius: BorderRadius.circular(23.r),
  415. ),
  416. ),
  417. child: Row(
  418. mainAxisSize: MainAxisSize.min,
  419. mainAxisAlignment: MainAxisAlignment.center,
  420. crossAxisAlignment: CrossAxisAlignment.center,
  421. children: [
  422. if (emoji != null && isShowEmoji)
  423. Text(emoji, style: Styles.getTextStyleBlack204W400(14.sp)),
  424. if (emoji != null && name != null) SizedBox(width: 4.w),
  425. if (name != null)
  426. Text(name, style: Styles.getTextStyleBlack204W400(14.sp)),
  427. ],
  428. ),
  429. );
  430. }
  431. }