view.dart 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import 'package:flutter/material.dart';
  2. import 'package:get/get.dart';
  3. import 'package:lottie/lottie.dart';
  4. import '../../../../common/api/index.dart';
  5. import '../../../../common/models/enums/index.dart';
  6. import '../../../../common/models/im/index.dart';
  7. import '../../../../common/routers/index.dart';
  8. import '../../../../common/utils/loading.dart';
  9. import 'index.dart';
  10. class SpeechAssistantChatPage extends GetView<SpeechAssistantChatController> {
  11. const SpeechAssistantChatPage({Key? key}) : super(key: key);
  12. static Future<void> open() async {
  13. if (ProgramCenterService.to.speechScript().isEmpty) {
  14. Loading.showError('没有配置语音脚本,请联系管理员');
  15. return ;
  16. }
  17. await Get.toNamed(O2OARoutes.homeImChatSpeechAssistant);
  18. }
  19. Widget _contentView(BuildContext context) {
  20. return Obx(() => controller.state.chatList.length > 0
  21. ? _msgListView()
  22. : _initRandomCommandListView(context));
  23. }
  24. /// 初始化随机命令列表
  25. Widget _initRandomCommandListView(BuildContext context) {
  26. return Column(
  27. children: [
  28. const SizedBox(height: 24),
  29. Padding(
  30. padding: const EdgeInsets.only(top: 12, bottom: 12),
  31. child: Text('im_chat_speech_assistant_tips'.tr,
  32. style: Theme.of(context).textTheme.bodySmall,
  33. textAlign: TextAlign.center)),
  34. const SizedBox(height: 12),
  35. Expanded(
  36. flex: 1,
  37. child: Obx(() => ListView.builder(
  38. itemCount: controller.state.initCommandList.length,
  39. itemBuilder: ((context, index) {
  40. final item = controller.state.initCommandList[index];
  41. return Padding(
  42. padding: const EdgeInsets.only(top: 12, bottom: 12),
  43. child: Text(
  44. '“$item”',
  45. style: Theme.of(context).textTheme.bodyLarge,
  46. textAlign: TextAlign.center,
  47. ));
  48. }))))
  49. ],
  50. );
  51. }
  52. /// 对话列表
  53. Widget _msgListView() {
  54. return Padding(
  55. padding: const EdgeInsets.all(12),
  56. child: Obx(() => ListView.separated(
  57. padding: const EdgeInsets.only(bottom: 10),
  58. // reverse: true, // 反转
  59. shrinkWrap: true,
  60. separatorBuilder: (context, index) {
  61. return const SizedBox(height: 1);
  62. },
  63. itemCount: controller.state.chatList.length,
  64. itemBuilder: (context, index) {
  65. final item = controller.state.chatList[index];
  66. var msg = item.msg ?? '';
  67. if (msg.isEmpty) {
  68. msg = item.err ?? '';
  69. }
  70. if (item.side == ImSpeechAssistantResponse.rightSide) {
  71. return _msgViewRight(msg);
  72. } else {
  73. return _msgViewLeft(msg);
  74. }
  75. })));
  76. }
  77. Widget _msgViewLeft(String msg) {
  78. return Padding(
  79. padding: const EdgeInsets.only(right: 50.0, top: 5, bottom: 5),
  80. child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [
  81. const SizedBox(height: 30),
  82. Flexible(
  83. child: Container(
  84. padding: const EdgeInsets.all(14),
  85. decoration: BoxDecoration(
  86. color: Colors.grey[300],
  87. borderRadius: const BorderRadius.only(
  88. topRight: Radius.circular(18),
  89. bottomLeft: Radius.circular(18),
  90. bottomRight: Radius.circular(18),
  91. ),
  92. ),
  93. child: _textView(msg, false),
  94. ),
  95. )
  96. ]));
  97. }
  98. Widget _msgViewRight(String msg) {
  99. return Padding(
  100. padding: const EdgeInsets.only(left: 50, top: 5, bottom: 5),
  101. child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
  102. const SizedBox(height: 30),
  103. Flexible(
  104. child: Container(
  105. padding: const EdgeInsets.all(14),
  106. decoration: const BoxDecoration(
  107. color: Color.fromARGB(255, 0, 127, 255),
  108. borderRadius: BorderRadius.only(
  109. topLeft: Radius.circular(18),
  110. bottomLeft: Radius.circular(18),
  111. bottomRight: Radius.circular(18),
  112. ),
  113. ),
  114. child: _textView(msg, true),
  115. ),
  116. )
  117. ]));
  118. }
  119. Widget _textView(String text, bool isRight) {
  120. return Text(
  121. text,
  122. style:
  123. TextStyle(color: isRight ? Colors.white : Colors.black, fontSize: 14),
  124. );
  125. }
  126. // 底部操作栏
  127. Widget _bottomBarView(BuildContext context) {
  128. return Column(
  129. children: [
  130. SizedBox(height: 5),
  131. Padding(
  132. padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20),
  133. child: GestureDetector(
  134. onTapDown: (x) => controller.startRecorder(),
  135. onTapUp: (x) => controller.stopRecorder(),
  136. onTapCancel: () => controller.stopRecorder(),
  137. child: Container(
  138. width: double.infinity,
  139. height: 44,
  140. decoration: BoxDecoration(
  141. color: Theme.of(context).colorScheme.primary,
  142. borderRadius: const BorderRadius.all(Radius.circular(6)),
  143. // boxShadow: const [
  144. // BoxShadow(
  145. // color: Colors.grey,
  146. // offset: Offset(6.0, 6.0), //阴影x轴偏移量
  147. // blurRadius: 10, //阴影模糊程度
  148. // spreadRadius: 0 //阴影扩散程度
  149. // )
  150. // ]
  151. ),
  152. child: _speechButton(context),
  153. ),
  154. )),
  155. const SizedBox(height: 5)
  156. ],
  157. );
  158. }
  159. Widget _speechButton(BuildContext context) {
  160. return Obx(() {
  161. switch (controller.state.status.value) {
  162. case SpeechStatus.idle:
  163. return Center(
  164. child: Row(
  165. mainAxisSize: MainAxisSize.min,
  166. children: [
  167. const Icon(Icons.mic, color: Colors.white, size: 24,),
  168. const SizedBox(width: 5),
  169. Text(controller.state.btnTitle,
  170. style: Theme.of(context)
  171. .textTheme
  172. .bodyMedium
  173. ?.copyWith(color: Colors.white))
  174. ],)
  175. );
  176. case SpeechStatus.speaking:
  177. return Padding(
  178. padding: const EdgeInsets.only(left: 32, right: 32),
  179. child: Lottie.asset(
  180. 'assets/json/lottie-animation-voice-line.json',
  181. height: 44,
  182. fit: BoxFit.fill));
  183. case SpeechStatus.thinking:
  184. return Center(
  185. child: Row(
  186. mainAxisAlignment: MainAxisAlignment.center,
  187. children: [
  188. Lottie.asset('assets/json/lottie-animation-loading-white.json',
  189. width: 36, height: 36, fit: BoxFit.fill),
  190. const SizedBox(width: 10),
  191. Text('loading'.tr,
  192. style: Theme.of(context)
  193. .textTheme
  194. .bodyMedium
  195. ?.copyWith(color: Colors.white))
  196. ],
  197. ));
  198. }
  199. });
  200. }
  201. @override
  202. Widget build(BuildContext context) {
  203. return GetBuilder<SpeechAssistantChatController>(
  204. builder: (_) {
  205. return Scaffold(
  206. appBar: AppBar(title: Text("im_chat_speech_assistant_title".tr)),
  207. body: SafeArea(
  208. child: Container(
  209. color: Theme.of(context).scaffoldBackgroundColor,
  210. child: Column(
  211. children: [
  212. Expanded(flex: 1, child: _contentView(context)),
  213. _bottomBarView(context)
  214. ],
  215. ),
  216. ),
  217. ),
  218. );
  219. },
  220. );
  221. }
  222. }