import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:expandable_text/expandable_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import '../../beans/character_info_bean.dart'; import '../../beans/chat_info_bean.dart'; import '../../beans/send_message_bean.dart'; import 'chat_model.dart'; class ChatPage extends StatefulWidget { String characterId; ChatPage({super.key, required this.characterId}); @override State createState() => _ChatPageState(); } class _ChatPageState extends State { final ScrollController _scrollController = ScrollController(); final TextEditingController _chatController = TextEditingController(); late StreamSubscription subscription; final ChatModel _viewmodel = ChatModel(); final FocusNode _focusNode = FocusNode(); ///人物信息 CharacterInfoBean? characterInfoBean; ///发送消息聊天 late SendMessageBean sendMessageBean; ///聊天列表 List chatList = []; ///聊天列表是否在底部 bool isBottom = true; bool isMore = false; bool isHalf = true; ///输入框内容 String text = ""; void _textFieldChanged(String str) { text = str; setState(() {}); } int delIndex = 0; ///聊天列表滑动到底部 void _scrollToBottom() { Future.delayed(Duration(milliseconds: 300), () { _scrollController.animateTo( _scrollController.position.maxScrollExtent, curve: Curves.easeOut, duration: const Duration(milliseconds: 300), ); }); } @override void initState() { // TODO: implement initState super.initState(); subscription = _viewmodel.streamController.stream.listen((newData) { String code = newData['code']; if (code.isNotEmpty) { switch (code) { case "getCharacterInfo": characterInfoBean = newData['data']; _viewmodel.getChatInfo(widget.characterId); break; case "getChatInfo": chatList.addAll(newData['data']); EasyLoading.dismiss(); setState(() {}); _scrollToBottom(); break; case "sendMessage": sendMessageBean = newData['data']; chatList.addAll(sendMessageBean.chatList!); Future.delayed(Duration(milliseconds: 200), () { _scrollController.jumpTo(_scrollController.position.maxScrollExtent); }); EasyLoading.dismiss(); break; case "delChatByIds": if (newData['data']) { chatList.removeAt(delIndex); setState(() {}); } EasyLoading.dismiss(); break; case "delChat": //重启对话 if (newData['data']) { chatList.clear(); _viewmodel.getCharacterInfo(widget.characterId); } EasyLoading.dismiss(); break; default: EasyLoading.dismiss(); EasyLoading.showToast(newData['data']); break; } setState(() {}); } }); _scrollController.addListener(() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent) { //滚动到底部 isBottom = true; } }); // 创建键盘可见性监测器 final keyboardVisibilityController = KeyboardVisibilityController(); // 订阅键盘可见性变化 keyboardVisibilityController.onChange.listen((bool visible) { // 在这里处理键盘可见性变化的逻辑 if (visible) { // 键盘弹出时的处理逻辑 print('Keyboard is visible'); _scrollToBottom(); } else { // 键盘隐藏时的处理逻辑 print('Keyboard is hidden'); _focusNode.unfocus(); } }); loadData(); } loadData() { EasyLoading.show(status: 'loading...'); _viewmodel.getCharacterInfo(widget.characterId); } ///删除消息 delChat(int id, index) { EasyLoading.show(status: 'loading...'); delIndex = index; List ids = [id]; _viewmodel.delChatByIds(ids, widget.characterId); } @override void dispose() { // TODO: implement dispose print("object=="); _scrollController.dispose(); _chatController.dispose(); subscription.cancel(); _focusNode.unfocus(); _focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xFF121213), body: Stack( children: [ characterInfoBean != null && characterInfoBean?.bgUrl != null ? CachedNetworkImage( fit: BoxFit.cover, width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, imageUrl: "${characterInfoBean?.bgUrl}", errorWidget: (context, url, error) => const Icon(Icons.error), ) : Container(), Positioned( bottom: 0, child: Container( width: MediaQuery.of(context).size.width, height: 311.67, decoration: BoxDecoration( gradient: LinearGradient( colors: [Color(0x00000000), Color(0xFF0C0909)], // 三色渐变数组 begin: Alignment.topCenter, // 渐变开始位置 end: Alignment.bottomCenter, // 渐变结束位置 ), ), )), Container( child: Column( children: [ ///返回 Container( width: MediaQuery.of(context).size.width, alignment: Alignment.centerLeft, margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top + 9, left: 16, right: 16), child: GestureDetector( onTap: () { Navigator.pop(context); }, child: Image( height: 23, image: AssetImage('assets/images/ic_left_arrow.png'), ), ), ), ///title Container( width: double.infinity, height: 30, margin: EdgeInsets.only(top: 10, left: 16, right: 16), child: Stack( alignment: Alignment.center, children: [ // Positioned( // left: 105, // child: Text( // "+1", // style: TextStyle(color: Color(0xFFF14476), fontSize: 10), // )), Positioned( left: 0, child: Container( height: 30, decoration: BoxDecoration(color: Color(0x33000000), borderRadius: BorderRadius.all(Radius.circular(14))), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ ///AI头像 GestureDetector( onTap: () { // Navigator.push( // context, // MaterialPageRoute(builder: (context) => ChatInfoPage()), // ); }, child: Container( margin: EdgeInsets.only(left: 2), child: ClipOval( child: CachedNetworkImage( width: 23, height: 23, imageUrl: '${characterInfoBean?.icon}', errorWidget: (context, url, error) => const Icon(Icons.error), ), ), ), ), ///AI名 Container( margin: EdgeInsets.only(left: 5, right: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 35, child: Text( "${characterInfoBean?.characterName}", overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 10, color: Colors.white), ), ), SizedBox( width: 35, child: Text( '${characterInfoBean?.lookCount} 聊过', style: TextStyle(fontSize: 7, color: Colors.white), ), ), ], ), ), ///心动值 // Container( // margin: EdgeInsets.only(left: 6, right: 6), // child: GestureDetector( // onTap: () { // // Navigator.push( // // context, // // MaterialPageRoute( // // builder: (context) => ChatPage( // // id: '123', // // )), // // ); // }, // child: Stack( // alignment: Alignment.center, // children: [ // Image( // width: 24, // height: 21, // image: AssetImage('assets/images/ic_beckoning.png'), // ), // characterInfoBean != null // ? Text( // characterInfoBean!.intimacy.toString(), // style: TextStyle(fontSize: 8, color: Colors.white), // ) // : Container(), // ], // ), // ), // ), ], ), ), ), ///关注 Positioned( right: 0, child: GestureDetector( onTap: () { isHalf = !isHalf; setState(() {}); }, child: Container( width: 50, height: 24, alignment: Alignment.center, decoration: BoxDecoration(color: Color(0x33000000), borderRadius: BorderRadius.all(Radius.circular(12))), child: Text( '+ 关注', style: TextStyle(fontSize: 12, color: Colors.white), ), ), ), ), ], ), ), isHalf ? Container( height: MediaQuery.of(context).size.height / 3.5, ) : Container(), ///聊天列表 Expanded( child: Align( alignment: Alignment.bottomCenter, child: ShaderMask( shaderCallback: (Rect bounds) { return const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.white, ], stops: [0.0, 0.1], // 调整透明的范围 ).createShader(bounds); }, blendMode: BlendMode.dstIn, child: ListView.builder( shrinkWrap: true, controller: _scrollController, itemCount: chatList.length, itemBuilder: (BuildContext context, int index) { return _item(index); }), ), )), // Container( // alignment: Alignment.centerLeft, // margin: EdgeInsets.only(left: 16, bottom: 3), // child: Image(width: 63, height: 18, image: AssetImage('assets/images/ic_memory.png')), // ), ///输入 功能 Container( width: MediaQuery.of(context).size.width, margin: EdgeInsets.only(left: 16, right: 16, bottom: 20), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ ///输入框 Expanded( child: Container( padding: const EdgeInsets.symmetric(vertical: 5), decoration: BoxDecoration( color: Color(0x33FFFFFF), borderRadius: BorderRadius.all(Radius.circular(20)), ), child: Row( children: [ Expanded( child: Container( padding: const EdgeInsets.only(left: 15), child: TextField( focusNode: _focusNode, controller: _chatController, onChanged: _textFieldChanged, maxLines: null, cursorColor: const Color(0xFFFF9000), decoration: const InputDecoration( border: InputBorder.none, // 移除非聚焦状态下的边框 enabledBorder: InputBorder.none, // 移除获得焦点但未输入内容时的边框 focusedBorder: InputBorder.none, isCollapsed: true, // 移除输入时的边框 hintText: "打个招呼吧...", hintStyle: TextStyle(color: Color(0xFFB6B6B6), fontSize: 13), ), style: const TextStyle(color: Colors.white), ), ), ), ///发送 Container( margin: const EdgeInsets.only(right: 7), child: text == "" ? const Image( width: 27, image: AssetImage('assets/images/ic_send_n.png'), ) : GestureDetector( onTap: () { EasyLoading.show(status: 'loading...'); chatList.add(ChatInfoBean(0, "user", text, "timestamp", "claudeType", 0, "userIcon")); _viewmodel.sendMessage(widget.characterId, text); _chatController.clear(); text = ""; Future.delayed(Duration(milliseconds: 200), () { _scrollController.jumpTo(_scrollController.position.maxScrollExtent); }); setState(() {}); }, child: Image( width: 27, image: AssetImage('assets/images/ic_send.png'), ), ), ) ], ), )), ///智能输入 Container( margin: const EdgeInsets.only(left: 14, right: 14), child: GestureDetector( onTap: () { Navigator.of(context).pushNamed('/LoginPage'); }, child: Image( width: 27, image: AssetImage('assets/images/ic_smart_chat.png'), ), ), ), ///功能 更多 GestureDetector( onTap: () { isMore = !isMore; setState(() {}); }, child: Image( width: 27, image: AssetImage('assets/images/ic_more.png'), ), ), ], ), ), isMore ? Container( height: 50, alignment: Alignment.centerLeft, margin: const EdgeInsets.only(left: 16, right: 16, bottom: 20), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( margin: EdgeInsets.only(left: 23), child: Column( mainAxisSize: MainAxisSize.min, children: [ Image(width: 26, image: AssetImage('assets/images/ic_album.png')), Container( margin: EdgeInsets.only(top: 9), child: Text( '角色相册', style: TextStyle(fontSize: 10, color: Color(0xFFA2A2A2)), ), ), ], ), ), Container( margin: EdgeInsets.only(left: 23), child: GestureDetector( onTap: () { setState(() { isMore = false; }); EasyLoading.show(status: 'loading...'); _viewmodel.delChat(widget.characterId); }, child: Column( mainAxisSize: MainAxisSize.min, children: [ Image(width: 26, height: 26, image: AssetImage('assets/images/ic_restart.png')), Container( margin: EdgeInsets.only(top: 9), child: Text( '重启对话', style: TextStyle(fontSize: 10, color: Color(0xFFA2A2A2)), ), ), ], ), ), ), Container( margin: EdgeInsets.only(left: 23), child: GestureDetector( onTap: () { setState(() { isMore = false; }); // EasyLoading.show(status: 'loading...'); // _viewmodel.delChat(widget.characterId); }, child: Column( mainAxisSize: MainAxisSize.min, children: [ Image(width: 26, height: 26, image: AssetImage('assets/images/ic_memory.png')), Container( margin: EdgeInsets.only(top: 9), child: Text( '记忆提升', style: TextStyle(fontSize: 10, color: Color(0xFFA2A2A2)), ), ), ], ), ), ), ], ), ) : Container(), ], ), ), ], ), ); } ///聊天条目 _item(index) { // if (index == 0) { // return Container(); // } ///简介 if (chatList[index].role == 'profile') { return Center( child: Container( margin: EdgeInsets.only(left: 16, right: 16), padding: EdgeInsets.only(left: 20, right: 20, top: 12, bottom: 12), decoration: BoxDecoration(color: Color(0x99000000), borderRadius: BorderRadius.all(Radius.circular(13))), child: ExpandableText( chatList[index].content!, expandText: '更多', collapseText: '关闭', maxLines: 3, linkColor: Color(0xFFFF9000), style: TextStyle(fontSize: 13, color: Colors.white), ), ), ); } ///时间 // if (chatList[index].role == 'time') { // return Center( // child: Container( // padding: EdgeInsets.only(left: 9, right: 9, top: 6, bottom: 6), // decoration: BoxDecoration(color: Color(0x99FFFFFF), borderRadius: BorderRadius.all(Radius.circular(8))), // child: Text(chatList[index].content!, style: TextStyle(color: Color(0xFFA7AFD9), fontSize: 10)), // ), // ); // } ///提示 if (chatList[index].role == 'tips') { return Center( child: Container( padding: const EdgeInsets.only(left: 9, right: 9, top: 6, bottom: 6), decoration: const BoxDecoration( color: Color(0xCC000000), borderRadius: BorderRadius.all(Radius.circular(15)), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(chatList[index].content!, style: TextStyle(color: Color(0xFFB6B6B6), fontSize: 10)), Container( margin: const EdgeInsets.only(left: 10), child: const Text("立即增加", style: TextStyle(color: Color(0xFFFF9000), fontSize: 10)), ), ], ), ), ); } ///聊天内容 return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: chatList[index].role != 'user' ? Container( ///AI alignment: Alignment.centerLeft, child: Stack( children: [ Row( mainAxisAlignment: MainAxisAlignment.start, children: [ ConstrainedBox( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width - 50, // 确保不超过屏幕宽度 ), child: Container( margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), padding: const EdgeInsets.all(11.0), decoration: const BoxDecoration( color: Color(0x99FF9000), borderRadius: BorderRadius.only( topRight: Radius.circular(16.0), bottomLeft: Radius.circular(16.0), bottomRight: Radius.circular(16.0)), ), child: GestureDetector( onTap: () { EasyLoading.showToast("status${chatList[index].id}"); delChat(chatList[index].id!, index); }, child: Text( chatList[index].content!, style: const TextStyle( fontSize: 14, color: Color(0xFFE8E8E8), ), ), ), ), ), ], ), // Container( // width: 60, // height: 27, // margin: EdgeInsets.only(left: 16), // alignment: Alignment.center, // decoration: BoxDecoration( // color: Color(0xFFF14476), // borderRadius: BorderRadius.only( // topLeft: Radius.circular(4), // topRight: Radius.circular(13), // bottomRight: Radius.circular(13), // bottomLeft: Radius.circular(13), // ), // ), // child: Row( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // Image(width: 8, height: 13, image: AssetImage('assets/images/ic_voice.png')), // Container( // margin: EdgeInsets.only(left: 8), // child: Text( // '60s', // style: TextStyle(fontSize: 13, color: Colors.white), // ), // ), // ], // ), // ), ], ), ) : Row( ///用户 mainAxisAlignment: MainAxisAlignment.end, children: [ ConstrainedBox( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width - 50, // 确保不超过屏幕宽度 ), child: Container( margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), padding: const EdgeInsets.all(11.0), decoration: const BoxDecoration( color: Color(0x99252525), borderRadius: BorderRadius.only(topLeft: Radius.circular(16.0), bottomLeft: Radius.circular(16.0), bottomRight: Radius.circular(16.0)), ), child: Text( chatList[index].content!, style: const TextStyle( fontSize: 16, color: Color(0xFFE8E8E8), ), ), ), ), ], ), ); } }