keyboard_guide_page.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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: TextField(
  184. style: TextStyle(
  185. color: ColorName.black80,
  186. fontSize: 14.0,
  187. fontWeight: FontWeight.w500,
  188. ),
  189. // 设置光标颜色
  190. cursorColor: ColorName.inputCursor,
  191. // 光标宽度
  192. cursorWidth: 2.0,
  193. // 光标圆角
  194. cursorRadius: Radius.circular(2),
  195. // 设置按钮显示为发送
  196. textInputAction: TextInputAction.send,
  197. // 用户点击软键盘的发送按钮时,触发回调
  198. onSubmitted: (value) {
  199. var msg = controller.editingController.text;
  200. controller.sendMsg(msg);
  201. },
  202. // 输入框焦点
  203. focusNode: controller.inputFocusNode,
  204. // 点击外部区域,关闭软键盘
  205. // onTapUpOutside: (event) {
  206. // controller.inputFocusNode.unfocus();
  207. // },
  208. // 输入框控制器
  209. controller: controller.editingController,
  210. decoration: InputDecoration(
  211. // 提示文字
  212. hintText: StringName.keyboardGuideInputHint,
  213. hintStyle: TextStyle(
  214. fontSize: 14.0,
  215. fontWeight: FontWeight.w400,
  216. color: ColorName.black40,
  217. ),
  218. // 去掉默认的边框
  219. border: InputBorder.none,
  220. // 设置输入框的内边距
  221. contentPadding: EdgeInsets.symmetric(
  222. horizontal: 9.0,
  223. vertical: 13.0,
  224. ),
  225. ),
  226. ),
  227. ),
  228. ),
  229. ],
  230. ),
  231. ),
  232. ],
  233. ),
  234. );
  235. }
  236. /// 构建聊天气泡
  237. Widget _buildMsgBubble(KeyboardGuideMsg msg) {
  238. // 设置气泡的外边距,让气泡不易过长
  239. double marginValue = 35.0;
  240. EdgeInsets marginEdgeInsets;
  241. if (msg.isMe) {
  242. marginEdgeInsets = EdgeInsets.only(left: marginValue);
  243. } else {
  244. marginEdgeInsets = EdgeInsets.only(right: marginValue);
  245. }
  246. // 圆角大小
  247. double radiusSize = 14.0;
  248. // 背景圆角
  249. BorderRadius bgBorderRadius;
  250. if (msg.isMe) {
  251. bgBorderRadius = BorderRadius.only(
  252. topLeft: Radius.circular(radiusSize),
  253. topRight: Radius.circular(0),
  254. bottomLeft: Radius.circular(radiusSize),
  255. bottomRight: Radius.circular(radiusSize),
  256. );
  257. } else {
  258. bgBorderRadius = BorderRadius.only(
  259. topLeft: Radius.circular(0),
  260. topRight: Radius.circular(radiusSize),
  261. bottomLeft: Radius.circular(radiusSize),
  262. bottomRight: Radius.circular(0),
  263. );
  264. }
  265. // Flexible,文本超过一行时,自动换行,并且不超过最大宽度,不超过一行时,则自动包裹内容
  266. return Flexible(
  267. child: Container(
  268. padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 10.0),
  269. margin: marginEdgeInsets,
  270. decoration: BoxDecoration(
  271. color: msg.isMe ? ColorName.msgBubbleMe : ColorName.msgBubbleTa,
  272. borderRadius: bgBorderRadius,
  273. ),
  274. child: GestureDetector(
  275. onTap: () {
  276. // 复制内容到剪切板
  277. if (msg.type == KeyboardGuideMsgType.copy.type) {
  278. ClipboardUtil.copyToClipboard(msg.content);
  279. ToastUtil.show(StringName.copySuccess);
  280. } else if (msg.type == KeyboardGuideMsgType.intimacySetting.type) {
  281. // 跳转到亲密度设置页
  282. IntimacyScalePage.start();
  283. }
  284. },
  285. child: Row(
  286. // 宽高包裹内容
  287. mainAxisSize: MainAxisSize.min,
  288. // 图标和文本,垂直居中
  289. crossAxisAlignment: CrossAxisAlignment.center,
  290. children: [
  291. Flexible(
  292. // 消息文本
  293. child: Text(
  294. msg.content,
  295. style: TextStyle(
  296. fontSize: 14.0,
  297. color: ColorName.black80,
  298. fontWeight: FontWeight.w500,
  299. height: 1.5,
  300. ),
  301. softWrap: true,
  302. ),
  303. ),
  304. // 只有对方发送的,才有操作按钮
  305. if (!msg.isMe)
  306. Visibility(
  307. visible: msg.type != KeyboardGuideMsgType.normal.type,
  308. child: Padding(
  309. padding: EdgeInsets.only(left: 8.0),
  310. child: _buildMsgActionBtn(msg),
  311. ),
  312. ),
  313. ],
  314. ),
  315. ),
  316. ),
  317. );
  318. }
  319. /// 消息操作按钮
  320. Widget _buildMsgActionBtn(KeyboardGuideMsg msg) {
  321. if (msg.type == KeyboardGuideMsgType.copy.type) {
  322. return Assets.images.iconCopy.image(width: 18.w, height: 18.w);
  323. } else if (msg.type == KeyboardGuideMsgType.intimacySetting.type) {
  324. return Assets.images.iconSetting.image(width: 18.w, height: 18.w);
  325. } else {
  326. return SizedBox.shrink();
  327. }
  328. }
  329. /// 构建聊天消息列表项
  330. Widget _buildMsgItem(KeyboardGuideMsg msg, int index) {
  331. return Obx(() {
  332. Widget content;
  333. // 自己发的
  334. if (msg.isMe) {
  335. content = Row(
  336. // 如果是自己发的,则在右边
  337. mainAxisAlignment: MainAxisAlignment.end,
  338. // 顶部对齐
  339. crossAxisAlignment: CrossAxisAlignment.start,
  340. children: [
  341. // 聊天气泡
  342. _buildMsgBubble(msg),
  343. SizedBox(width: 9.w),
  344. // 头像
  345. _buildAvatar(msg),
  346. ],
  347. );
  348. } else {
  349. // 对方发的
  350. content = Row(
  351. // 如果是自己发的,则在右边
  352. mainAxisAlignment: MainAxisAlignment.start,
  353. // 顶部对齐
  354. crossAxisAlignment: CrossAxisAlignment.start,
  355. children: [
  356. // 头像
  357. _buildAvatar(msg),
  358. SizedBox(width: 9.w),
  359. // 聊天气泡
  360. _buildMsgBubble(msg),
  361. ],
  362. );
  363. }
  364. bool isTargetGuildMsg = controller.guideMsgIndex.value == index;
  365. return Container(
  366. margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.0.h),
  367. child:
  368. isTargetGuildMsg
  369. ? Container(key: controller.guideMsgGlobalKey, child: content)
  370. : content,
  371. );
  372. });
  373. }
  374. /// 构建头像
  375. Widget _buildAvatar(KeyboardGuideMsg msg) {
  376. double avatarSize = 36.0;
  377. return CircleAvatar(
  378. radius: 20,
  379. child:
  380. msg.isMe
  381. ? Assets.images.iconDefaultAvatar.image(
  382. height: avatarSize,
  383. width: avatarSize,
  384. )
  385. : Assets.images.iconTaAvatar.image(
  386. height: avatarSize,
  387. width: avatarSize,
  388. ),
  389. );
  390. }
  391. }