character_custom_page.dart 19 KB

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