character_custom_page.dart 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. import 'package:dotted_border/dotted_border.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_screenutil/flutter_screenutil.dart';
  4. import 'package:get/get.dart';
  5. import 'package:keyboard/base/base_page.dart';
  6. import 'package:keyboard/module/character_custom/character_custom_controller.dart';
  7. import 'package:keyboard/resource/string.gen.dart';
  8. import 'package:keyboard/utils/toast_util.dart';
  9. import '../../resource/assets.gen.dart';
  10. import '../../utils/styles.dart';
  11. class CharacterCustomPage extends BasePage<CharacterCustomController> {
  12. const CharacterCustomPage({super.key});
  13. static start() {
  14. return Get.to(() => CharacterCustomPage());
  15. }
  16. @override
  17. bool immersive() {
  18. return true;
  19. }
  20. @override
  21. bool statusBarDarkFont() {
  22. return false;
  23. }
  24. @override
  25. Widget buildBody(BuildContext context) {
  26. return Obx(() {
  27. if (controller.currentStep.value == StepType.home) {
  28. return _buildCustomHomePage();
  29. } else {
  30. return _buildStepsPage();
  31. }
  32. });
  33. }
  34. // 定制首页
  35. Widget _buildCustomHomePage() {
  36. return Stack(
  37. children: [
  38. Assets.images.bgCharacterCustomHuman.image(
  39. width: double.infinity,
  40. fit: BoxFit.fill,
  41. ),
  42. SafeArea(
  43. child: Row(
  44. crossAxisAlignment: CrossAxisAlignment.center,
  45. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  46. children: [
  47. Padding(
  48. padding: EdgeInsets.only(left: 16.w),
  49. child: GestureDetector(
  50. onTap: () {
  51. controller.clickBack();
  52. },
  53. child: Assets.images.iconCharacterCustomClose.image(
  54. width: 24.w,
  55. height: 24.w,
  56. ),
  57. ),
  58. ),
  59. Opacity(
  60. opacity: 0.80,
  61. child: Container(
  62. width: 76.r,
  63. height: 32.h,
  64. decoration: ShapeDecoration(
  65. color: const Color(0xFF400164),
  66. shape: RoundedRectangleBorder(
  67. borderRadius: BorderRadius.only(
  68. topLeft: Radius.circular(16.r),
  69. bottomLeft: Radius.circular(16.r),
  70. ),
  71. ),
  72. ),
  73. child: Center(
  74. child: Text(
  75. StringName.characterCustomHistory,
  76. style: TextStyle(
  77. color: Colors.white,
  78. fontSize: 14.sp,
  79. fontWeight: FontWeight.w400,
  80. ),
  81. ),
  82. ),
  83. ),
  84. ),
  85. ],
  86. ),
  87. ),
  88. Positioned(
  89. bottom: 50.h,
  90. left: 0,
  91. right: 0,
  92. child: GestureDetector(
  93. onTap: () {
  94. controller.clickNextButton(StepType.hobbies);
  95. },
  96. child: Center(
  97. child: Assets.images.iconCharacterCustomButton.image(
  98. width: 234.w,
  99. fit: BoxFit.contain,
  100. ),
  101. ),
  102. ),
  103. ),
  104. ],
  105. );
  106. }
  107. Widget _buildStepsPage() {
  108. return Stack(
  109. children: [
  110. Assets.images.bgCharacterCustomSteps.image(
  111. width: double.infinity,
  112. fit: BoxFit.fill,
  113. ),
  114. SafeArea(
  115. child: Column(
  116. crossAxisAlignment: CrossAxisAlignment.start,
  117. children: [
  118. Padding(
  119. padding: EdgeInsets.only(left: 16.w, bottom: 48.h),
  120. child: GestureDetector(
  121. onTap: () {
  122. controller.clickBack();
  123. },
  124. child: Assets.images.iconCharacterCustomClose.image(
  125. width: 24.w,
  126. height: 24.w,
  127. ),
  128. ),
  129. ),
  130. Expanded(
  131. child: SingleChildScrollView(
  132. child: Column(
  133. mainAxisAlignment: MainAxisAlignment.center,
  134. crossAxisAlignment: CrossAxisAlignment.center,
  135. children: [
  136. controller.currentStep.value == StepType.hobbies
  137. ? Assets.images.iconCharacterCustomStepOneTitle.image(
  138. fit: BoxFit.cover,
  139. width: 209.w,
  140. )
  141. : controller.currentStep.value == StepType.characters
  142. ? Assets.images.iconCharacterCustomStepTwoTitle.image(
  143. fit: BoxFit.cover,
  144. width: 168.w,
  145. )
  146. : Assets.images.iconCharacterCustomStepThreeTitle
  147. .image(fit: BoxFit.cover, width: 207.w),
  148. Container(
  149. margin: EdgeInsets.only(
  150. left: 16.w,
  151. right: 16.w,
  152. top: 24.h,
  153. ),
  154. width: double.infinity,
  155. decoration: ShapeDecoration(
  156. color: Colors.white,
  157. shape: RoundedRectangleBorder(
  158. borderRadius: BorderRadius.circular(20.r),
  159. ),
  160. shadows: [
  161. BoxShadow(
  162. color: Color(0x66D788FF),
  163. blurRadius: 10.r,
  164. offset: Offset(0, 0),
  165. spreadRadius: 0,
  166. ),
  167. ],
  168. ),
  169. child: Column(
  170. children: [
  171. Container(
  172. padding: EdgeInsets.symmetric(horizontal: 26.w),
  173. decoration: BoxDecoration(
  174. image: DecorationImage(
  175. image:
  176. Assets.images.bgCharacterCustomStepsDesc
  177. .provider(),
  178. fit: BoxFit.fill,
  179. ),
  180. ),
  181. child: Text(
  182. StringName.characterCustomStepsDesc,
  183. style: TextStyle(
  184. color: const Color(0xFFAD88EB),
  185. fontSize: 12.sp,
  186. fontWeight: FontWeight.w400,
  187. ),
  188. ),
  189. ),
  190. SizedBox(height: 24.h),
  191. Row(
  192. mainAxisAlignment: MainAxisAlignment.center,
  193. crossAxisAlignment: CrossAxisAlignment.center,
  194. children: [
  195. Text(
  196. '第${controller.currentStep.value.value}步',
  197. style: TextStyle(
  198. color: const Color(0xFF755AAB),
  199. fontSize: 12.sp,
  200. fontWeight: FontWeight.w500,
  201. ),
  202. ),
  203. Text(
  204. " | ",
  205. style: TextStyle(
  206. color: const Color(0xFF755AAB),
  207. fontSize: 12.sp,
  208. fontWeight: FontWeight.w500,
  209. ),
  210. ),
  211. Opacity(
  212. opacity: 0.60,
  213. child: Text(
  214. '共3步',
  215. style: TextStyle(
  216. color: const Color(0xFF755BAB),
  217. fontSize: 12.sp,
  218. fontWeight: FontWeight.w500,
  219. ),
  220. ),
  221. ),
  222. ],
  223. ),
  224. if (controller.currentStep.value ==
  225. StepType.hobbies)
  226. _buildHobbiesPage(),
  227. if (controller.currentStep.value ==
  228. StepType.characters)
  229. _buildCharacterPage(),
  230. if (controller.currentStep.value ==
  231. StepType.inputName)
  232. _buildInputNamePage(),
  233. ],
  234. ),
  235. ),
  236. ],
  237. ),
  238. ),
  239. ),
  240. ],
  241. ),
  242. ),
  243. ],
  244. );
  245. }
  246. Widget _buildSelectionPage({
  247. required String title,
  248. required String subtitle,
  249. required RxList<dynamic> items,
  250. required RxList<String> selectedLabels,
  251. required Function(String) onSelected,
  252. required bool isCustomEnabled,
  253. required VoidCallback onCustomClick,
  254. required VoidCallback nextClick,
  255. required bool isShowEmoji,
  256. }) {
  257. return Obx(() {
  258. return Column(
  259. children: [
  260. Padding(
  261. padding: EdgeInsets.only(top: 15.h),
  262. child: Text(
  263. title,
  264. style: TextStyle(
  265. color: const Color(0xFF755BAB),
  266. fontSize: 18.sp,
  267. fontWeight: FontWeight.w500,
  268. ),
  269. ),
  270. ),
  271. Text(
  272. subtitle,
  273. style: TextStyle(
  274. color: Color(0xFF755BAB).withValues(alpha: 0.6),
  275. fontSize: 12.sp,
  276. fontWeight: FontWeight.w500,
  277. ),
  278. ),
  279. Container(
  280. margin: EdgeInsets.only(top: 32.h, left: 21.w, right: 21.w),
  281. alignment: Alignment.center,
  282. child: Wrap(
  283. alignment: WrapAlignment.center,
  284. spacing: 8.0.r,
  285. runSpacing: 10.r,
  286. children: [
  287. ...items.map((item) {
  288. final emoji = item.emoji ?? "";
  289. final name = item.name ?? "";
  290. return Obx(() {
  291. bool isSelected = selectedLabels.contains(name);
  292. return Stack(
  293. children: [
  294. ChoiceChip(
  295. label: Row(
  296. mainAxisSize: MainAxisSize.min,
  297. children: [
  298. Text(
  299. isShowEmoji ? "$emoji$name" : name,
  300. style: TextStyle(
  301. color:
  302. isSelected
  303. ? Color(0xFFF5F4F9)
  304. : Color(0xFF755BAB),
  305. fontSize: 14.sp,
  306. fontWeight: FontWeight.w400,
  307. ),
  308. ),
  309. ],
  310. ),
  311. showCheckmark: false,
  312. selected: isSelected,
  313. selectedColor: Color(0xFFB782FF),
  314. backgroundColor: Colors.white,
  315. shape: RoundedRectangleBorder(
  316. side: BorderSide(
  317. width: 1.w,
  318. color: const Color(0x4C755BAB),
  319. ),
  320. borderRadius: BorderRadius.circular(31.r),
  321. ),
  322. onSelected: (selected) {
  323. onSelected(name);
  324. },
  325. ),
  326. ],
  327. );
  328. });
  329. }),
  330. Visibility(
  331. visible: isCustomEnabled,
  332. child: GestureDetector(
  333. onTap: onCustomClick,
  334. child: Container(
  335. margin: EdgeInsets.only(top: 3.h),
  336. child: DottedBorder(
  337. color: const Color(0xFFC9C2DB),
  338. strokeWidth: 1.0.w,
  339. borderType: BorderType.RRect,
  340. radius: Radius.circular(20.r),
  341. child: Container(
  342. width: 86.w,
  343. height: 33.h,
  344. alignment: Alignment.center,
  345. child: Row(
  346. mainAxisAlignment: MainAxisAlignment.center,
  347. children: [
  348. Assets.images.iconCharacterCustomPlus.image(
  349. width: 18.w,
  350. height: 18.w,
  351. ),
  352. Text(
  353. StringName.characterCustomCustomizable,
  354. style: TextStyle(
  355. color: const Color(0xFFC9C2DB),
  356. fontSize: 14.sp,
  357. fontWeight: FontWeight.w500,
  358. ),
  359. ),
  360. ],
  361. ),
  362. ),
  363. ),
  364. ),
  365. ),
  366. ),
  367. ],
  368. ),
  369. ),
  370. Container(
  371. margin: EdgeInsets.only(top: 107.h, bottom: 32.h),
  372. child: _buildNextButton(
  373. isEnable: selectedLabels.isNotEmpty,
  374. onTap: () {
  375. nextClick();
  376. },
  377. ),
  378. ),
  379. ],
  380. );
  381. });
  382. }
  383. Widget _buildNextButton({required VoidCallback onTap, required isEnable}) {
  384. return GestureDetector(
  385. onTap: () {
  386. onTap();
  387. },
  388. child: Container(
  389. width: 220.w,
  390. height: 48.h,
  391. decoration:
  392. isEnable
  393. ? Styles.getActivateButtonDecoration(31.r)
  394. : Styles.getInactiveButtonDecoration(31.r),
  395. child: Center(
  396. child: Text(
  397. '下一步',
  398. style: TextStyle(
  399. color: Colors.white,
  400. fontSize: 16.sp,
  401. fontWeight: FontWeight.w500,
  402. ),
  403. ),
  404. ),
  405. ),
  406. );
  407. }
  408. // 选择爱好页面
  409. Widget _buildHobbiesPage() {
  410. return _buildSelectionPage(
  411. title: StringName.characterCustomHobbiesTitle,
  412. subtitle:
  413. "(最多选择${controller.currentCharacterCustomConfig.value?.maxHobbyNum ?? 3}个)",
  414. items: controller.hobbiesLabelsList,
  415. selectedLabels: controller.hobbiesSelectLabels,
  416. isShowEmoji: true,
  417. onSelected: (name) {
  418. controller.selectHobby(name);
  419. },
  420. isCustomEnabled:
  421. controller.currentCharacterCustomConfig.value?.customHobby == true,
  422. onCustomClick: () {
  423. controller.clickHobbiesCustom();
  424. },
  425. nextClick: () {
  426. controller.clickHobbiesNext();
  427. },
  428. );
  429. }
  430. // 选择性格页面
  431. Widget _buildCharacterPage() {
  432. return _buildSelectionPage(
  433. title: StringName.characterCustomcharacterTitle,
  434. subtitle:
  435. "(最多选择${controller.currentCharacterCustomConfig.value?.maxCharacterNum ?? 3}个)",
  436. items: controller.characterLabelsList,
  437. selectedLabels: controller.characterSelectLabels,
  438. isShowEmoji: false,
  439. onSelected: (name) {
  440. controller.selectCharacter(name);
  441. },
  442. isCustomEnabled:
  443. controller.currentCharacterCustomConfig.value?.customCharacter ==
  444. true,
  445. onCustomClick: () {
  446. controller.clickCharacterCustom();
  447. },
  448. nextClick: () {
  449. controller.clickCharacterNext();
  450. },
  451. );
  452. }
  453. // 输入名字页面
  454. Widget _buildInputNamePage() {
  455. return Obx(() {
  456. return Column(
  457. children: [
  458. Padding(
  459. padding: EdgeInsets.only(top: 15.h),
  460. child: Text(
  461. StringName.characterCustomNameTitle,
  462. style: TextStyle(
  463. color: const Color(0xFF755BAB),
  464. fontSize: 18.sp,
  465. fontWeight: FontWeight.w500,
  466. ),
  467. ),
  468. ),
  469. Text(
  470. "人设名称(最多5个字)",
  471. style: TextStyle(
  472. color: Color(0xFF755BAB).withValues(alpha: 0.6),
  473. fontSize: 12.sp,
  474. fontWeight: FontWeight.w500,
  475. ),
  476. ),
  477. Container(
  478. margin: EdgeInsets.only(top: 32.h, left: 21.w, right: 21.w),
  479. alignment: Alignment.center,
  480. child: Container(
  481. height: 48.h,
  482. alignment: Alignment.center,
  483. decoration: ShapeDecoration(
  484. color: const Color(0xFFF5F4F9),
  485. shape: RoundedRectangleBorder(
  486. borderRadius: BorderRadius.circular(31.r),
  487. ),
  488. ),
  489. child: TextField(
  490. // maxLength: maxLength,
  491. maxLines: null,
  492. expands: true,
  493. textAlign: TextAlign.center,
  494. textAlignVertical: TextAlignVertical.center,
  495. onChanged: (value) {
  496. controller.currentNameValue.value = value;
  497. },
  498. decoration: InputDecoration(
  499. counterText: "",
  500. hintText: StringName.characterCustomNameHint,
  501. hintStyle: TextStyle(color: Colors.black.withAlpha(66)),
  502. border: OutlineInputBorder(
  503. borderRadius: BorderRadius.circular(31.r),
  504. borderSide: BorderSide.none, // 移除边框线
  505. ),
  506. filled: true,
  507. fillColor: const Color(0xFFF5F4F9),
  508. ),
  509. ),
  510. ),
  511. ),
  512. Container(
  513. margin: EdgeInsets.only(top: 44.h, bottom: 32.h),
  514. child: _buildNextButton(
  515. isEnable: controller.currentNameValue.trim().isNotEmpty,
  516. onTap: () {
  517. controller.clickInputNameNext();
  518. },
  519. ),
  520. ),
  521. ],
  522. );
  523. });
  524. }
  525. }