keyboard_guide_page.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_screenutil/flutter_screenutil.dart';
  3. import 'package:get/get.dart';
  4. import 'package:keyboard/module/keyboard_guide/keyboard_guide_controller.dart';
  5. import 'package:keyboard/router/app_pages.dart';
  6. import 'package:keyboard/utils/toast_util.dart';
  7. import 'package:keyboard/widget/platform_util.dart';
  8. import 'package:lottie/lottie.dart';
  9. import '../../base/base_page.dart';
  10. import '../../data/bean/keyboard_guide_msg.dart';
  11. import '../../data/consts/event_report.dart';
  12. import '../../handler/event_handler.dart';
  13. import '../../resource/assets.gen.dart';
  14. import '../../resource/colors.gen.dart';
  15. import '../../resource/string.gen.dart';
  16. import '../../utils/clipboard_util.dart';
  17. import '../../utils/url_launcher_util.dart';
  18. import '../../widget/ai/ai_generate_tip_widget.dart';
  19. import '../../widget/app_lifecycle_widget.dart';
  20. import '../intimacy_scale/intimacy_scale_page.dart';
  21. import 'enums/keyboard_guide_msg_type.dart';
  22. /// 键盘引导页面
  23. class KeyboardGuidePage extends BasePage<KeyboardGuidePageController> {
  24. const KeyboardGuidePage({super.key});
  25. /// 跳转到键盘引导页
  26. static void start() {
  27. Get.toNamed(RoutePath.keyboardGuide);
  28. }
  29. /// 跳转并关闭当前页
  30. static void startAndOffMe() {
  31. Get.offNamed(RoutePath.keyboardGuide);
  32. }
  33. @override
  34. immersive() {
  35. return false;
  36. }
  37. @override
  38. Widget buildBody(BuildContext context) {
  39. return Scaffold(
  40. backgroundColor: backgroundColor(),
  41. body: AppLifecycleWidget(
  42. onAppLifecycleCallback: (isForeground) {
  43. // 完成教程
  44. controller.setNotFirstShowKeyboardTutorial();
  45. if (isForeground) {
  46. // 切换到前台时,重新检查设置,更新按钮状态
  47. controller.checkSetting();
  48. // 如果选择为默认键盘了,则尝试显示引导弹窗
  49. if (controller.isDefaultKeyboard.value) {
  50. controller.showGuideOverlayDialog();
  51. }
  52. }
  53. },
  54. child: Column(
  55. children: [
  56. // 标题栏
  57. _buildTitleBar(),
  58. // 消息列表
  59. Expanded(flex: 1, child: _buildContent()),
  60. // 底部输入栏
  61. _buildBottomInput(),
  62. ],
  63. ),
  64. ),
  65. );
  66. }
  67. // 标题栏
  68. Widget _buildTitleBar() {
  69. return Container(
  70. color: backgroundColor(),
  71. height: kToolbarHeight,
  72. padding: EdgeInsets.symmetric(horizontal: 16.0),
  73. child: Row(
  74. children: [
  75. // 返回按钮
  76. GestureDetector(
  77. onTap: controller.clickBack,
  78. child: Assets.images.iconMineBackArrow.image(
  79. width: 24.w,
  80. height: 24.h,
  81. ),
  82. ),
  83. // 标题
  84. Expanded(
  85. child: Container(
  86. alignment: Alignment.center,
  87. child: Text("", style: const TextStyle(fontSize: 18)),
  88. ),
  89. ),
  90. // 右侧按钮
  91. GestureDetector(
  92. onTap: () async {
  93. bool result = await UrlLauncherUtil.openWeChat();
  94. EventHandler.report(EventId.event_03007);
  95. if (!result) {
  96. ToastUtil.show(StringName.keyboardGuideWechatNotInstall);
  97. }
  98. },
  99. child: Container(
  100. padding: EdgeInsets.only(
  101. left: 12.w,
  102. right: 14.w,
  103. top: 10.w,
  104. bottom: 10.w,
  105. ),
  106. decoration: BoxDecoration(
  107. image: DecorationImage(
  108. image: Assets.images.bgGoApp.provider(),
  109. fit: BoxFit.fill,
  110. ),
  111. ),
  112. child: Row(
  113. children: [
  114. Assets.images.iconWechat.image(height: 22.w, width: 22.w),
  115. SizedBox(width: 1.0),
  116. Text(
  117. StringName.keyboardGuideGoWechat,
  118. style: TextStyle(
  119. color: ColorName.black80,
  120. fontSize: 12,
  121. fontWeight: FontWeight.w400,
  122. ),
  123. ),
  124. ],
  125. ),
  126. ),
  127. ),
  128. ],
  129. ),
  130. );
  131. }
  132. /// 内容
  133. Widget _buildContent() {
  134. return Obx(() {
  135. // 选择了默认键盘,显示聊天列表
  136. if (controller.isDefaultKeyboard.value) {
  137. return _buildChatList();
  138. } else {
  139. // 未选择,显示引导动画
  140. return _buildGuideAnimation();
  141. }
  142. });
  143. }
  144. /// 引导动画
  145. Widget _buildGuideAnimation() {
  146. Widget animationWidget;
  147. if (PlatformUtil.isIOS) {
  148. animationWidget = Lottie.asset(
  149. Assets.anim.animKeyboardFloatingWindowChooseKeyboardIos,
  150. repeat: true,
  151. );
  152. } else if (PlatformUtil.isAndroid) {
  153. animationWidget = Lottie.asset(
  154. Assets.anim.animKeyboardFloatingWindowChooseKeyboardAndroid,
  155. repeat: true,
  156. );
  157. } else {
  158. animationWidget = SizedBox.shrink();
  159. }
  160. return Container(child: animationWidget);
  161. }
  162. /// 聊天列表
  163. Widget _buildChatList() {
  164. return Obx(() {
  165. return ListView.builder(
  166. controller: controller.scrollController,
  167. itemCount: controller.msgList.length + 1,
  168. itemBuilder: (BuildContext context, int index) {
  169. if (index == controller.msgList.length) {
  170. return (controller.msgList.length > 5)
  171. ? Container(
  172. child: _buildAiGenerateTip(),
  173. )
  174. : SizedBox();
  175. }
  176. KeyboardGuideMsg msg = controller.msgList[index];
  177. return _buildMsgItem(msg, index);
  178. },
  179. );
  180. });
  181. }
  182. Widget _buildAiGenerateTip() {
  183. return Row(
  184. mainAxisAlignment: MainAxisAlignment.center,
  185. children: [
  186. AiGenerateTipWidget.normalTip(textColor: Colors.black.withAlpha(92)),
  187. ],
  188. );
  189. }
  190. /// 构建底部输入框
  191. Widget _buildBottomInput() {
  192. return Center(
  193. child: Column(
  194. children: [
  195. Container(
  196. color: ColorName.msgInputBar,
  197. padding: const EdgeInsets.symmetric(
  198. vertical: 11.0,
  199. horizontal: 12.0,
  200. ),
  201. child: Row(
  202. mainAxisAlignment: MainAxisAlignment.start,
  203. children: [
  204. Expanded(
  205. flex: 1,
  206. // 输入框的圆角边框
  207. child: Container(
  208. decoration: BoxDecoration(
  209. color: ColorName.white,
  210. borderRadius: BorderRadius.circular(10.0),
  211. ),
  212. child: Obx(() {
  213. return TextField(
  214. // 是否可用,选择了默认键盘时,才可用
  215. enabled: controller.isDefaultKeyboard.value,
  216. // 是否自动获取焦点
  217. autofocus: false,
  218. style: TextStyle(
  219. color: ColorName.black80,
  220. fontSize: 14.0,
  221. fontWeight: FontWeight.w500,
  222. ),
  223. // 设置光标颜色
  224. cursorColor: ColorName.inputCursor,
  225. // 光标宽度
  226. cursorWidth: 2.0,
  227. // 光标圆角
  228. cursorRadius: Radius.circular(2),
  229. // 设置按钮显示为发送
  230. textInputAction: TextInputAction.send,
  231. // 用户点击软键盘的发送按钮时,触发回调
  232. onSubmitted: (value) {
  233. var msg = controller.editingController.text;
  234. controller.sendMsg(msg);
  235. // 保持输入框焦点获取,不降下键盘
  236. controller.requestInputFocus();
  237. },
  238. // 输入框焦点
  239. focusNode: controller.inputFocusNode,
  240. // 点击外部区域,关闭软键盘
  241. // onTapUpOutside: (event) {
  242. // controller.clearInputFocus();
  243. // },
  244. // 输入框控制器
  245. controller: controller.editingController,
  246. decoration: InputDecoration(
  247. // 提示文字
  248. hintText: StringName.keyboardGuideInputHint,
  249. hintStyle: TextStyle(
  250. fontSize: 14.0,
  251. fontWeight: FontWeight.w400,
  252. color: ColorName.black40,
  253. ),
  254. // 去掉默认的边框
  255. border: InputBorder.none,
  256. // 设置输入框的内边距
  257. contentPadding: EdgeInsets.symmetric(
  258. horizontal: 9.0,
  259. vertical: 13.0,
  260. ),
  261. ),
  262. );
  263. }),
  264. ),
  265. ),
  266. ],
  267. ),
  268. ),
  269. ],
  270. ),
  271. );
  272. }
  273. /// 构建聊天气泡
  274. Widget _buildMsgBubble(KeyboardGuideMsg msg) {
  275. // 设置气泡的外边距,让气泡不易过长
  276. double marginValue = 35.0;
  277. EdgeInsets marginEdgeInsets;
  278. if (msg.isMe) {
  279. marginEdgeInsets = EdgeInsets.only(left: marginValue);
  280. } else {
  281. marginEdgeInsets = EdgeInsets.only(right: marginValue);
  282. }
  283. // 圆角大小
  284. double radiusSize = 14.0;
  285. // 背景圆角
  286. BorderRadius bgBorderRadius;
  287. if (msg.isMe) {
  288. bgBorderRadius = BorderRadius.only(
  289. topLeft: Radius.circular(radiusSize),
  290. topRight: Radius.circular(0),
  291. bottomLeft: Radius.circular(radiusSize),
  292. bottomRight: Radius.circular(radiusSize),
  293. );
  294. } else {
  295. bgBorderRadius = BorderRadius.only(
  296. topLeft: Radius.circular(0),
  297. topRight: Radius.circular(radiusSize),
  298. bottomLeft: Radius.circular(radiusSize),
  299. bottomRight: Radius.circular(0),
  300. );
  301. }
  302. // Flexible,文本超过一行时,自动换行,并且不超过最大宽度,不超过一行时,则自动包裹内容
  303. return Flexible(
  304. child: Container(
  305. padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 10.0),
  306. margin: marginEdgeInsets,
  307. decoration: BoxDecoration(
  308. color: msg.isMe ? ColorName.msgBubbleMe : ColorName.msgBubbleTa,
  309. borderRadius: bgBorderRadius,
  310. ),
  311. child: GestureDetector(
  312. onTap: () {
  313. // 复制内容到剪切板
  314. if (msg.type == KeyboardGuideMsgType.copy.type) {
  315. ClipboardUtil.copyToClipboard(msg.content);
  316. ToastUtil.show(StringName.copySuccess);
  317. } else if (msg.type == KeyboardGuideMsgType.intimacySetting.type) {
  318. // 跳转到亲密度设置页
  319. IntimacyScalePage.start();
  320. }
  321. },
  322. child: Row(
  323. // 宽高包裹内容
  324. mainAxisSize: MainAxisSize.min,
  325. // 图标和文本,垂直居中
  326. crossAxisAlignment: CrossAxisAlignment.center,
  327. children: [
  328. Flexible(
  329. // 消息文本
  330. child: Text(
  331. msg.content,
  332. style: TextStyle(
  333. fontSize: 14.0,
  334. color: ColorName.black80,
  335. fontWeight: FontWeight.w500,
  336. height: 1.5,
  337. ),
  338. softWrap: true,
  339. ),
  340. ),
  341. // 只有对方发送的,才有操作按钮
  342. if (!msg.isMe)
  343. Visibility(
  344. visible: msg.type != KeyboardGuideMsgType.normal.type,
  345. child: Padding(
  346. padding: EdgeInsets.only(left: 8.0),
  347. child: _buildMsgActionBtn(msg),
  348. ),
  349. ),
  350. ],
  351. ),
  352. ),
  353. ),
  354. );
  355. }
  356. /// 消息操作按钮
  357. Widget _buildMsgActionBtn(KeyboardGuideMsg msg) {
  358. if (msg.type == KeyboardGuideMsgType.copy.type) {
  359. return Assets.images.iconCopy.image(width: 18.w, height: 18.w);
  360. } else if (msg.type == KeyboardGuideMsgType.intimacySetting.type) {
  361. return Assets.images.iconSetting.image(width: 18.w, height: 18.w);
  362. } else {
  363. return SizedBox.shrink();
  364. }
  365. }
  366. /// 构建聊天消息列表项
  367. Widget _buildMsgItem(KeyboardGuideMsg msg, int index) {
  368. return Obx(() {
  369. Widget content;
  370. // 自己发的
  371. if (msg.isMe) {
  372. content = Row(
  373. // 如果是自己发的,则在右边
  374. mainAxisAlignment: MainAxisAlignment.end,
  375. // 顶部对齐
  376. crossAxisAlignment: CrossAxisAlignment.start,
  377. children: [
  378. // 聊天气泡
  379. _buildMsgBubble(msg),
  380. SizedBox(width: 9.w),
  381. // 头像
  382. _buildAvatar(msg),
  383. ],
  384. );
  385. } else {
  386. // 对方发的
  387. content = Row(
  388. // 如果是自己发的,则在右边
  389. mainAxisAlignment: MainAxisAlignment.start,
  390. // 顶部对齐
  391. crossAxisAlignment: CrossAxisAlignment.start,
  392. children: [
  393. // 头像
  394. _buildAvatar(msg),
  395. SizedBox(width: 9.w),
  396. // 聊天气泡
  397. _buildMsgBubble(msg),
  398. ],
  399. );
  400. }
  401. bool isTargetGuildMsg = controller.guideMsgIndex.value == index;
  402. return Container(
  403. margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.0.h),
  404. child:
  405. isTargetGuildMsg
  406. ? Container(key: controller.guideMsgGlobalKey, child: content)
  407. : content,
  408. );
  409. });
  410. }
  411. /// 构建头像
  412. Widget _buildAvatar(KeyboardGuideMsg msg) {
  413. double avatarSize = 36.0;
  414. return CircleAvatar(
  415. radius: 20,
  416. child:
  417. msg.isMe
  418. ? Assets.images.iconDefaultAvatar.image(
  419. height: avatarSize,
  420. width: avatarSize,
  421. )
  422. : Assets.images.iconTaAvatar.image(
  423. height: avatarSize,
  424. width: avatarSize,
  425. ),
  426. );
  427. }
  428. }