keyboard_guide_page.dart 14 KB

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