keyboard_guide_page.dart 13 KB

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