diff --git a/lib/beans/MessageBean.dart b/lib/beans/MessageBean.dart deleted file mode 100644 index 28b53ef..0000000 --- a/lib/beans/MessageBean.dart +++ /dev/null @@ -1,19 +0,0 @@ - -import 'package:json_annotation/json_annotation.dart'; - -part 'MessageBean.g.dart'; - -@JsonSerializable(explicitToJson: true) -class MessageBean{ - - String? role; - String? content; - - - MessageBean(this.role,this.content,); - - factory MessageBean.fromJson(Map json) => _$MessageBeanFromJson(json); - - Map toJson() => _$MessageBeanToJson(this); - -} \ No newline at end of file diff --git a/lib/beans/category_info_list_bean.dart b/lib/beans/category_info_list_bean.dart new file mode 100644 index 0000000..1155b7d --- /dev/null +++ b/lib/beans/category_info_list_bean.dart @@ -0,0 +1,23 @@ +import 'package:json_annotation/json_annotation.dart'; + +import 'label_bean.dart'; + +part 'category_info_list_bean.g.dart'; + +///人物信息 +@JsonSerializable(explicitToJson: true) +class CategoryInfoListBean { + int? id; // + String? name; // + String? biography; + int? lookCount; + String? bgImage; + String? iconImage; + List? label; + + CategoryInfoListBean(this.id, this.name, this.biography, this.lookCount, this.bgImage, this.iconImage, this.label); + + factory CategoryInfoListBean.fromJson(Map json) => _$CategoryInfoListBeanFromJson(json); + + Map toJson() => _$CategoryInfoListBeanToJson(this); +} diff --git a/lib/beans/category_info_list_bean.g.dart b/lib/beans/category_info_list_bean.g.dart new file mode 100644 index 0000000..d7d97e8 --- /dev/null +++ b/lib/beans/category_info_list_bean.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'category_info_list_bean.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CategoryInfoListBean _$CategoryInfoListBeanFromJson( + Map json) => + CategoryInfoListBean( + (json['id'] as num?)?.toInt(), + json['name'] as String?, + json['biography'] as String?, + (json['lookCount'] as num?)?.toInt(), + json['bgImage'] as String?, + json['iconImage'] as String?, + (json['label'] as List?) + ?.map((e) => LabelBean.fromJson(e as Map)) + .toList(), + ); + +Map _$CategoryInfoListBeanToJson( + CategoryInfoListBean instance) => + { + 'id': instance.id, + 'name': instance.name, + 'biography': instance.biography, + 'lookCount': instance.lookCount, + 'bgImage': instance.bgImage, + 'iconImage': instance.iconImage, + 'label': instance.label?.map((e) => e.toJson()).toList(), + }; diff --git a/lib/beans/character_info_bean.dart b/lib/beans/character_info_bean.dart index b6fe4cf..1107138 100644 --- a/lib/beans/character_info_bean.dart +++ b/lib/beans/character_info_bean.dart @@ -4,7 +4,7 @@ import 'label_bean.dart'; part 'character_info_bean.g.dart'; -///任务信息 +///人物信息 @JsonSerializable(explicitToJson: true) class CharacterInfoBean { String? icon; diff --git a/lib/beans/chat_info_bean.dart b/lib/beans/chat_info_bean.dart new file mode 100644 index 0000000..8b8e78b --- /dev/null +++ b/lib/beans/chat_info_bean.dart @@ -0,0 +1,21 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'chat_info_bean.g.dart'; + +///聊天历史记录 +@JsonSerializable(explicitToJson: true) +class ChatInfoBean { + int? id; + String? role; + String? content; + String? timestamp; + String? claudeType; + int? messageType; + String? userIcon; + + ChatInfoBean(this.id, this.role, this.content, this.timestamp, this.claudeType, this.messageType, this.userIcon); + + factory ChatInfoBean.fromJson(Map json) => _$ChatInfoBeanFromJson(json); + + Map toJson() => _$ChatInfoBeanToJson(this); +} diff --git a/lib/beans/chat_info_bean.g.dart b/lib/beans/chat_info_bean.g.dart new file mode 100644 index 0000000..71a3378 --- /dev/null +++ b/lib/beans/chat_info_bean.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'chat_info_bean.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ChatInfoBean _$ChatInfoBeanFromJson(Map json) => ChatInfoBean( + (json['id'] as num?)?.toInt(), + json['role'] as String?, + json['content'] as String?, + json['timestamp'] as String?, + json['claudeType'] as String?, + (json['messageType'] as num?)?.toInt(), + json['userIcon'] as String?, + ); + +Map _$ChatInfoBeanToJson(ChatInfoBean instance) => + { + 'id': instance.id, + 'role': instance.role, + 'content': instance.content, + 'timestamp': instance.timestamp, + 'claudeType': instance.claudeType, + 'messageType': instance.messageType, + 'userIcon': instance.userIcon, + }; diff --git a/lib/beans/find_banner_bean.dart b/lib/beans/find_banner_bean.dart new file mode 100644 index 0000000..2182d78 --- /dev/null +++ b/lib/beans/find_banner_bean.dart @@ -0,0 +1,17 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'find_banner_bean.g.dart'; + +///标签 +@JsonSerializable(explicitToJson: true) +class FindBannerBean { + String? imageUrl; + String? actionType; + String? actionId; + + FindBannerBean(this.imageUrl, this.actionType, this.actionId); + + factory FindBannerBean.fromJson(Map json) => _$FindBannerBeanFromJson(json); + + Map toJson() => _$FindBannerBeanToJson(this); +} diff --git a/lib/beans/find_banner_bean.g.dart b/lib/beans/find_banner_bean.g.dart new file mode 100644 index 0000000..c8bc7d0 --- /dev/null +++ b/lib/beans/find_banner_bean.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'find_banner_bean.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FindBannerBean _$FindBannerBeanFromJson(Map json) => + FindBannerBean( + json['imageUrl'] as String?, + json['actionType'] as String?, + json['actionId'] as String?, + ); + +Map _$FindBannerBeanToJson(FindBannerBean instance) => + { + 'imageUrl': instance.imageUrl, + 'actionType': instance.actionType, + 'actionId': instance.actionId, + }; diff --git a/lib/beans/find_type_bean.dart b/lib/beans/find_type_bean.dart new file mode 100644 index 0000000..d5b03b2 --- /dev/null +++ b/lib/beans/find_type_bean.dart @@ -0,0 +1,16 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'find_type_bean.g.dart'; + +///标签 +@JsonSerializable(explicitToJson: true) +class FindTypeBean { + int? id; + String? name; + + FindTypeBean(this.id, this.name); + + factory FindTypeBean.fromJson(Map json) => _$FindTypeBeanFromJson(json); + + Map toJson() => _$FindTypeBeanToJson(this); +} diff --git a/lib/beans/find_type_bean.g.dart b/lib/beans/find_type_bean.g.dart new file mode 100644 index 0000000..fb9c914 --- /dev/null +++ b/lib/beans/find_type_bean.g.dart @@ -0,0 +1,18 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'find_type_bean.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FindTypeBean _$FindTypeBeanFromJson(Map json) => FindTypeBean( + (json['id'] as num?)?.toInt(), + json['name'] as String?, + ); + +Map _$FindTypeBeanToJson(FindTypeBean instance) => + { + 'id': instance.id, + 'name': instance.name, + }; diff --git a/lib/beans/message_bean.dart b/lib/beans/message_bean.dart new file mode 100644 index 0000000..8ee332c --- /dev/null +++ b/lib/beans/message_bean.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'message_bean.g.dart'; + +///标签 +@JsonSerializable(explicitToJson: true) +class MessageBean { + int? id; + String? name; + String? biography; + String? iconImage; + String? lastContactTime; + + MessageBean(this.id, this.name, this.biography, this.iconImage, this.lastContactTime); + + factory MessageBean.fromJson(Map json) => _$MessageBeanFromJson(json); + + Map toJson() => _$MessageBeanToJson(this); +} diff --git a/lib/beans/MessageBean.g.dart b/lib/beans/message_bean.g.dart similarity index 51% rename from lib/beans/MessageBean.g.dart rename to lib/beans/message_bean.g.dart index c2631e7..8cd17c8 100644 --- a/lib/beans/MessageBean.g.dart +++ b/lib/beans/message_bean.g.dart @@ -1,18 +1,24 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'MessageBean.dart'; +part of 'message_bean.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** MessageBean _$MessageBeanFromJson(Map json) => MessageBean( - json['role'] as String?, - json['content'] as String?, + (json['id'] as num?)?.toInt(), + json['name'] as String?, + json['biography'] as String?, + json['iconImage'] as String?, + json['lastContactTime'] as String?, ); Map _$MessageBeanToJson(MessageBean instance) => { - 'role': instance.role, - 'content': instance.content, + 'id': instance.id, + 'name': instance.name, + 'biography': instance.biography, + 'iconImage': instance.iconImage, + 'lastContactTime': instance.lastContactTime, }; diff --git a/lib/beans/recommend_bean.dart b/lib/beans/recommend_bean.dart new file mode 100644 index 0000000..46f68c6 --- /dev/null +++ b/lib/beans/recommend_bean.dart @@ -0,0 +1,25 @@ +import 'package:json_annotation/json_annotation.dart'; + +import 'label_bean.dart'; + +part 'recommend_bean.g.dart'; + +///标签 +@JsonSerializable(explicitToJson: true) +class RecommendBean { + int? id; + String? bgImage; + String? name; + String? biography; + int? gender; + List? label; + String? imageUrl; + String? actionType; + String? actionId; + + RecommendBean(this.id, this.bgImage, this.name, this.biography, this.gender, this.label, this.imageUrl, this.actionType, this.actionId); + + factory RecommendBean.fromJson(Map json) => _$RecommendBeanFromJson(json); + + Map toJson() => _$RecommendBeanToJson(this); +} diff --git a/lib/beans/recommend_bean.g.dart b/lib/beans/recommend_bean.g.dart new file mode 100644 index 0000000..5825a25 --- /dev/null +++ b/lib/beans/recommend_bean.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'recommend_bean.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RecommendBean _$RecommendBeanFromJson(Map json) => + RecommendBean( + (json['id'] as num?)?.toInt(), + json['bgImage'] as String?, + json['name'] as String?, + json['biography'] as String?, + (json['gender'] as num?)?.toInt(), + (json['label'] as List?) + ?.map((e) => LabelBean.fromJson(e as Map)) + .toList(), + json['imageUrl'] as String?, + json['actionType'] as String?, + json['actionId'] as String?, + ); + +Map _$RecommendBeanToJson(RecommendBean instance) => + { + 'id': instance.id, + 'bgImage': instance.bgImage, + 'name': instance.name, + 'biography': instance.biography, + 'gender': instance.gender, + 'label': instance.label?.map((e) => e.toJson()).toList(), + 'imageUrl': instance.imageUrl, + 'actionType': instance.actionType, + 'actionId': instance.actionId, + }; diff --git a/lib/beans/send_message_bean.dart b/lib/beans/send_message_bean.dart new file mode 100644 index 0000000..42f2e1c --- /dev/null +++ b/lib/beans/send_message_bean.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +import 'chat_info_bean.dart'; + +part 'send_message_bean.g.dart'; + +///发送消息对话 +@JsonSerializable(explicitToJson: true) +class SendMessageBean { + List? chatList; + int? remainingChatCount; + int? lastMessageId; + + SendMessageBean(this.chatList, this.remainingChatCount, this.lastMessageId); + + factory SendMessageBean.fromJson(Map json) => _$SendMessageBeanFromJson(json); + + Map toJson() => _$SendMessageBeanToJson(this); +} diff --git a/lib/beans/send_message_bean.g.dart b/lib/beans/send_message_bean.g.dart new file mode 100644 index 0000000..a079686 --- /dev/null +++ b/lib/beans/send_message_bean.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'send_message_bean.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SendMessageBean _$SendMessageBeanFromJson(Map json) => + SendMessageBean( + (json['chatList'] as List?) + ?.map((e) => ChatInfoBean.fromJson(e as Map)) + .toList(), + (json['remainingChatCount'] as num?)?.toInt(), + (json['lastMessageId'] as num?)?.toInt(), + ); + +Map _$SendMessageBeanToJson(SendMessageBean instance) => + { + 'chatList': instance.chatList?.map((e) => e.toJson()).toList(), + 'remainingChatCount': instance.remainingChatCount, + 'lastMessageId': instance.lastMessageId, + }; diff --git a/lib/custom/easy_load.dart b/lib/custom/easy_load.dart new file mode 100644 index 0000000..3d1035c --- /dev/null +++ b/lib/custom/easy_load.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + +class EasyLoad { + // static show() { + // EasyLoading.show( + // indicator: SpinKitPumpingHeart( + // color: Colors.red, + // size: 30.0, + // ), loadingStyle = EasyLoadingStyle.dark + // // text: 'Loading...', + // // maskColor: 'transparent', + // ); + // } +} diff --git a/lib/network/NetworkConfig.dart b/lib/network/NetworkConfig.dart index eaa03df..00bec35 100644 --- a/lib/network/NetworkConfig.dart +++ b/lib/network/NetworkConfig.dart @@ -35,6 +35,20 @@ class NetworkConfig { static const String getChatInfo = "api/Chat/GetChatInfo"; //获取聊天内容 + static const String sendMessage = "api/Chat/SendMessage"; //发送消息 + + static const String delChatByIds = "api/Chat/DelChatByIds"; //删除单条或多条信息 + + static const String delChat = "api/Chat/DelChat"; //清空聊天信息(重启聊天) + + static const String getChatHistoryList = "api/Chat/GetChatHistoryList"; //消息列表 + + static const String getCategoryList = "api/Category/GetCategoryList"; //发现-获取分类列表 + + static const String getCategoryInfoList = "api/Category/GetCategoryInfoList"; //发现-复用页面角色信息 + + static const String getCategoryFindList = "api/Category/GetCategoryFindList"; //发现-第一页固定内容 + static const String chat = "/v1/chat/completions"; //聊天 static const String uploadImg = "/upload/image"; //审核模式上传图片 diff --git a/lib/network/RequestCenter.dart b/lib/network/RequestCenter.dart index 2d48b73..2d78646 100644 --- a/lib/network/RequestCenter.dart +++ b/lib/network/RequestCenter.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:typed_data'; import 'package:crypto/crypto.dart'; import 'package:dio/dio.dart'; @@ -95,7 +94,12 @@ class RequestCenter { {RequestMethod? method}) async { try { //FormData formData = FormData.fromMap(parmeters); - Response response = await _dio!.post(path, data: parmeters); + Response response = await _dio!.post(path, data: parmeters, + options: Options( + headers: { + 'Authorization': "Bearer ${NetworkConfig.token}" + }, + )); BaseEntity entity = BaseEntity.PlayfromJson(response.data); success(entity); return entity; @@ -113,7 +117,12 @@ class RequestCenter { {RequestMethod? method}) async { try { //FormData formData = FormData.fromMap(parmeters); - Response response = await _dio!.get(path, queryParameters: parmeters); + Response response = await _dio!.get(path, queryParameters: parmeters, + options: Options( + headers: { + 'Authorization': "Bearer ${NetworkConfig.token}" + }, + )); BaseEntity entity = BaseEntity.PlayfromJson(response.data); success(entity); return entity; diff --git a/lib/tools/chat/chat_model.dart b/lib/tools/chat/chat_model.dart index 3d5f912..9f12cf8 100644 --- a/lib/tools/chat/chat_model.dart +++ b/lib/tools/chat/chat_model.dart @@ -4,7 +4,8 @@ import 'package:talk/network/NetworkConfig.dart'; import 'package:talk/network/RequestCenter.dart'; import '../../beans/character_info_bean.dart'; -import '../../beans/home_character_bean.dart'; +import '../../beans/chat_info_bean.dart'; +import '../../beans/send_message_bean.dart'; import '../../network/BaseEntity.dart'; class ChatModel { @@ -19,7 +20,6 @@ class ChatModel { ///获取人物信息 Future getCharacterInfo(characterId) async { RequestCenter.instance.requestGet(NetworkConfig.getCharacterInfo, {"characterId": characterId}, (BaseEntity dataEntity) { - print("dataEntity==$dataEntity"); if (dataEntity.code == 0) { CharacterInfoBean characterInfoBean = CharacterInfoBean.fromJson(dataEntity.data); @@ -45,11 +45,84 @@ class ChatModel { print("dataEntity==$dataEntity"); if (dataEntity.code == 0) { - CharacterInfoBean characterInfoBean = CharacterInfoBean.fromJson(dataEntity.data); + List data = (dataEntity.data as List).map((e) => ChatInfoBean.fromJson(e as Map)).toList(); streamController.sink.add({ 'code': "getChatInfo", //有数据 - 'data': characterInfoBean + 'data': data + }); + } else { + streamController.sink.add({ + 'code': "-1", //有数据 + 'data': dataEntity.message + }); + } + }, (ErrorEntity errorEntity) { + print("errorEntity==${errorEntity.message}"); + }); + } + + ///发送消息聊天 + Future sendMessage(characterId, message) async { + RequestCenter.instance.request(NetworkConfig.sendMessage, { + "characterId": characterId, + "message": message, + }, (BaseEntity dataEntity) { + print("dataEntity==${dataEntity.data}"); + + if (dataEntity.code == 0) { + SendMessageBean sendMessageBean = SendMessageBean.fromJson(dataEntity.data); + + streamController.sink.add({ + 'code': "sendMessage", //有数据 + 'data': sendMessageBean + }); + } else { + streamController.sink.add({ + 'code': "-1", //有数据 + 'data': dataEntity.message + }); + } + }, (ErrorEntity errorEntity) { + print("errorEntity==${errorEntity.message}"); + }); + } + + ///删除单条或多条信息 + Future delChatByIds(id, characterId) async { + RequestCenter.instance.request(NetworkConfig.delChatByIds, { + "id": id, + "characterId": characterId, + }, (BaseEntity dataEntity) { + print("dataEntity==$dataEntity"); + + if (dataEntity.code == 0) { + streamController.sink.add({ + 'code': "delChatByIds", //有数据 + 'data': dataEntity.data + }); + } else { + streamController.sink.add({ + 'code': "-1", //有数据 + 'data': dataEntity.message + }); + } + }, (ErrorEntity errorEntity) { + print("errorEntity==${errorEntity.message}"); + }); + } + + ///清空聊天信息(重启聊天) + Future delChat(characterId) async { + RequestCenter.instance.request(NetworkConfig.delChat, { + "characterId": characterId, + }, (BaseEntity dataEntity) { + print("dataEntity==$dataEntity"); + + if (dataEntity.code == 0) { + streamController.sink.add({ + 'code': "delChat", //有数据 + 'data': dataEntity.data }); } else { streamController.sink.add({ diff --git a/lib/tools/chat/chat_page.dart b/lib/tools/chat/chat_page.dart index cdd7f8e..2c9bd17 100644 --- a/lib/tools/chat/chat_page.dart +++ b/lib/tools/chat/chat_page.dart @@ -1,54 +1,165 @@ +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 '../../beans/MessageBean.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 id; + String characterId; - ChatPage({super.key, required this.id}); + ChatPage({super.key, required this.characterId}); @override State createState() => _ChatPageState(); } class _ChatPageState extends State { - final TextEditingController _chatController = TextEditingController(); final ScrollController _scrollController = ScrollController(); - List chatList = []; + final TextEditingController _chatController = TextEditingController(); + + late StreamSubscription subscription; + final ChatModel _viewmodel = ChatModel(); + + ///人物信息 + CharacterInfoBean? characterInfoBean; + + ///发送消息聊天 + late SendMessageBean sendMessageBean; + + ///聊天列表 + List chatList = []; + + ///聊天列表是否在底部 + bool isBottom = true; - String text = ""; - bool isHalf = false; bool isMore = false; + bool isHalf = true; + + ///输入框内容 + String text = ""; + void _textFieldChanged(String str) { text = str; setState(() {}); } + int delIndex = 0; + + void _scrollToBottom() { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 300), + ); + } + @override void initState() { // TODO: implement initState super.initState(); - chatList = [MessageBean("user", "你好"), MessageBean("assistant", "你好"), MessageBean("time", "8:00AM"), MessageBean("user", "你好")]; + + 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(() {}); + Future.delayed(Duration(milliseconds: 100), () { + _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; + } + }); + + 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 - _chatController.dispose(); _scrollController.dispose(); + _chatController.dispose(); + subscription.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Color(0xFFE8EEFC), + backgroundColor: Color(0xFF121213), body: Stack( children: [ - Image(image: AssetImage('assets/images/bj.png')), + characterInfoBean != null && characterInfoBean?.bgUrl != null + ? CachedNetworkImage( + fit: BoxFit.fitHeight, + imageUrl: "${characterInfoBean?.bgUrl}", + errorWidget: (context, url, error) => const Icon(Icons.error), + ) + : Container(), Positioned( bottom: 0, child: Container( @@ -65,6 +176,7 @@ class _ChatPageState extends State { Container( child: Column( children: [ + ///返回 Container( width: MediaQuery.of(context).size.width, alignment: Alignment.centerLeft, @@ -131,7 +243,7 @@ class _ChatPageState extends State { SizedBox( width: 35, child: Text( - '柳如烟', + "${characterInfoBean?.characterName}", overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 10, color: Colors.white), ), @@ -139,7 +251,7 @@ class _ChatPageState extends State { SizedBox( width: 35, child: Text( - '1.2K 聊过', + '${characterInfoBean?.lookCount} 聊过', style: TextStyle(fontSize: 7, color: Colors.white), ), ), @@ -152,13 +264,13 @@ class _ChatPageState extends State { margin: EdgeInsets.only(left: 6, right: 6), child: GestureDetector( onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ChatPage( - id: '123', - )), - ); + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => ChatPage( + // id: '123', + // )), + // ); }, child: Stack( alignment: Alignment.center, @@ -168,10 +280,12 @@ class _ChatPageState extends State { height: 21, image: AssetImage('assets/images/ic_beckoning.png'), ), - Text( - '10', - style: TextStyle(fontSize: 8, color: Colors.white), - ) + characterInfoBean != null + ? Text( + characterInfoBean!.intimacy.toString(), + style: TextStyle(fontSize: 8, color: Colors.white), + ) + : Container(), ], ), ), @@ -264,6 +378,8 @@ class _ChatPageState extends State { ), ), ), + + ///发送 Container( margin: const EdgeInsets.only(right: 7), child: text == "" @@ -273,7 +389,9 @@ class _ChatPageState extends State { ) : GestureDetector( onTap: () { - chatList.add(MessageBean("user", text)); + 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), () { @@ -346,18 +464,24 @@ class _ChatPageState extends State { ), Container( margin: EdgeInsets.only(left: 23), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Image(width: 26, image: AssetImage('assets/images/ic_more.png')), - Container( - margin: EdgeInsets.only(top: 9), - child: Text( - '重启对话', - style: TextStyle(fontSize: 10, color: Color(0xFFA2A2A2)), + child: GestureDetector( + onTap: () { + 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)), + ), ), - ), - ], + ], + ), ), ), ], @@ -384,12 +508,12 @@ class _ChatPageState extends State { ///聊天条目 _item(index) { - if (index == 0) { - return Container(); - } + // if (index == 0) { + // return Container(); + // } ///简介 - if (chatList[index].role == 'introduce') { + if (chatList[index].role == 'profile') { return Center( child: Container( margin: EdgeInsets.only(left: 16, right: 16), @@ -418,6 +542,29 @@ class _ChatPageState extends State { // ); // } + ///提示 + 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), @@ -435,22 +582,23 @@ class _ChatPageState extends State { maxWidth: MediaQuery.of(context).size.width - 50, // 确保不超过屏幕宽度 ), child: Container( - margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), + margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), padding: const EdgeInsets.all(11.0), decoration: const BoxDecoration( - color: Color(0xFFFF9000), + 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"); + EasyLoading.showToast("status${chatList[index].id}"); + delChat(chatList[index].id!, index); }, - child: SelectableText( + child: Text( chatList[index].content!, style: const TextStyle( - fontSize: 16, - color: Colors.black, + fontSize: 14, + color: Color(0xFFE8E8E8), ), ), ), @@ -498,7 +646,7 @@ class _ChatPageState extends State { maxWidth: MediaQuery.of(context).size.width - 50, // 确保不超过屏幕宽度 ), child: Container( - margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), + margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), padding: const EdgeInsets.all(11.0), decoration: const BoxDecoration( color: Color(0x99252525), @@ -509,7 +657,7 @@ class _ChatPageState extends State { chatList[index].content!, style: const TextStyle( fontSize: 16, - color: Colors.white, + color: Color(0xFFE8E8E8), ), ), ), diff --git a/lib/tools/find/find_model.dart b/lib/tools/find/find_model.dart new file mode 100644 index 0000000..cfd1ced --- /dev/null +++ b/lib/tools/find/find_model.dart @@ -0,0 +1,82 @@ +import 'dart:async'; + +import 'package:talk/network/NetworkConfig.dart'; +import 'package:talk/network/RequestCenter.dart'; + +import '../../beans/category_info_list_bean.dart'; +import '../../beans/find_type_bean.dart'; +import '../../network/BaseEntity.dart'; + +class FindModel { + StreamController streamController = StreamController.broadcast(); + + FindModel() { + setUp(); + } + + void setUp() {} + + ///发现-获取分类列表 + Future getCategoryList() async { + RequestCenter.instance.requestGet(NetworkConfig.getCategoryList, {}, (BaseEntity dataEntity) { + if (dataEntity.code == 0) { + List data = (dataEntity.data as List).map((e) => FindTypeBean.fromJson(e as Map)).toList(); + + streamController.sink.add({ + 'code': "getChatInfo", //有数据 + 'data': data + }); + } else { + streamController.sink.add({ + 'code': "-1", //有数据 + 'data': dataEntity.message + }); + } + }, (ErrorEntity errorEntity) { + print("errorEntity==${errorEntity.message}"); + }); + } + + ///发现-复用页面角色信息 + Future getCategoryInfoList(categoryId) async { + RequestCenter.instance.requestGet(NetworkConfig.getCategoryInfoList, { + "categoryId": categoryId, + }, (BaseEntity dataEntity) { + if (dataEntity.code == 0) { + List data = + (dataEntity.data as List).map((e) => CategoryInfoListBean.fromJson(e as Map)).toList(); + + streamController.sink.add({ + 'code': "getCategoryInfoList", //有数据 + 'data': data + }); + } else { + streamController.sink.add({ + 'code': "-1", //有数据 + 'data': dataEntity.message + }); + } + }, (ErrorEntity errorEntity) { + print("errorEntity==${errorEntity.message}"); + }); + } + + ///发现-第一页固定内容 + Future getCategoryFindList() async { + RequestCenter.instance.requestGet(NetworkConfig.getCategoryFindList, {}, (BaseEntity dataEntity) { + if (dataEntity.code == 0) { + streamController.sink.add({ + 'code': "getCategoryFindList", //有数据 + 'data': dataEntity.data + }); + } else { + streamController.sink.add({ + 'code': "-1", //有数据 + 'data': dataEntity.message + }); + } + }, (ErrorEntity errorEntity) { + print("errorEntity==${errorEntity.message}"); + }); + } +} diff --git a/lib/tools/find/find_page.dart b/lib/tools/find/find_page.dart index 710711b..c10b79e 100644 --- a/lib/tools/find/find_page.dart +++ b/lib/tools/find/find_page.dart @@ -1,6 +1,11 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:talk/tools/find/find_model.dart'; import 'package:talk/tools/find/recommend_page.dart'; +import '../../beans/find_type_bean.dart'; import 'multiplex_page.dart'; ///发现 @@ -12,19 +17,45 @@ class FindPage extends StatefulWidget { } class _FindPageState extends State { + late StreamSubscription subscription; final PageController _pageController = PageController(); int currentIndex = 0; + final FindModel _viewmodel = FindModel(); + + List findList = []; + @override void initState() { // TODO: implement initState super.initState(); + + subscription = _viewmodel.streamController.stream.listen((newData) { + String code = newData['code']; + if (code.isNotEmpty) { + switch (code) { + case "getChatInfo": + findList = newData['data']; + break; + + default: + EasyLoading.showToast(newData['data']); + break; + } + setState(() {}); + EasyLoading.dismiss(); + } + }); + + EasyLoading.show(status: "'loading...'"); + _viewmodel.getCategoryList(); } @override void dispose() { // TODO: implement dispose _pageController.dispose(); + subscription.cancel(); super.dispose(); } @@ -39,11 +70,13 @@ class _FindPageState extends State { itemCount: 10, // 假设有5个页面,其中第一个是固定的 itemBuilder: (context, index) { if (index == 0) { - // 第一个页面是固定的 + // 第一个页面固定 return RecommendPage(); } else { - // 其他页面是动态生成的 - return MultiplexPage(); + // 其他页面动态生成 + return MultiplexPage( + id: findList[index].id.toString(), + ); } }, physics: NeverScrollableScrollPhysics(), @@ -53,7 +86,7 @@ class _FindPageState extends State { Container( padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top + 9, left: 16, right: 16), // width: MediaQuery.of(context).size.width, - color: Color(0xFF18181A), + color: Color(0x66121213), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -66,12 +99,12 @@ class _FindPageState extends State { ///tab Expanded( - child: Container( + child: SizedBox( height: 30, child: ListView.builder( - itemCount: 10, + itemCount: findList.length, itemBuilder: (BuildContext context, int index) { - return _item(index); + return _item(index, findList[index]); }, scrollDirection: Axis.horizontal, ), @@ -111,7 +144,7 @@ class _FindPageState extends State { ); } - _item(index) { + _item(index, FindTypeBean data) { return Container( margin: EdgeInsets.only(left: 5, right: 12), child: Column( @@ -127,7 +160,7 @@ class _FindPageState extends State { setState(() {}); }, child: Text( - "推荐", + "${data.name}", style: TextStyle(color: Color(currentIndex == index ? 0xFFFF9000 : 0xFF8D8D8D)), ), ), diff --git a/lib/tools/find/multiplex_page.dart b/lib/tools/find/multiplex_page.dart index cb32b3b..b328cae 100644 --- a/lib/tools/find/multiplex_page.dart +++ b/lib/tools/find/multiplex_page.dart @@ -1,26 +1,69 @@ +import 'dart:async'; + +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; + +import '../../beans/category_info_list_bean.dart'; +import 'find_model.dart'; class MultiplexPage extends StatefulWidget { - const MultiplexPage({super.key}); + String id; + + MultiplexPage({super.key, required this.id}); @override State createState() => _MultiplexPageState(); } class _MultiplexPageState extends State { + late StreamSubscription subscription; + + final FindModel _viewmodel = FindModel(); + + List categoryList = []; + + @override + void initState() { + // TODO: implement initState + super.initState(); + subscription = _viewmodel.streamController.stream.listen((newData) { + String code = newData['code']; + if (code.isNotEmpty) { + switch (code) { + case "getCategoryInfoList": + categoryList = newData['data']; + break; + } + setState(() {}); + EasyLoading.dismiss(); + } + }); + + EasyLoading.show(status: "'loading...'"); + _viewmodel.getCategoryInfoList(widget.id); + } + + @override + void dispose() { + // TODO: implement dispose + subscription.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top + 25, bottom: 60, left: 16, right: 16), child: ListView.builder( - itemCount: 10, + itemCount: categoryList.length, itemBuilder: (BuildContext context, int index) { - return _item2(index); + return _item(index, categoryList[index]); }), ); } - _item2(index) { + _item(index, CategoryInfoListBean data) { return Container( height: 130, width: 50, @@ -34,17 +77,19 @@ class _MultiplexPageState extends State { alignment: Alignment.center, children: [ Positioned( - left: 7, - child: Image( - width: 83, - height: 115, - image: AssetImage('assets/images/a3.png'), - )), + left: 7, + child: CachedNetworkImage( + width: 83, + height: 115, + imageUrl: data.iconImage!, + errorWidget: (context, url, error) => const Icon(Icons.error), + ), + ), Positioned( left: 101, top: 19, child: Text( - '重生之千金大小姐倒追我', + '${data.name}', style: TextStyle(color: Colors.white, fontSize: 14), )), Positioned( @@ -54,7 +99,7 @@ class _MultiplexPageState extends State { width: 200, height: 16, child: ListView.builder( - itemCount: 3, + itemCount: data.label?.length, scrollDirection: Axis.horizontal, itemBuilder: (BuildContext context, int index) { return Container( @@ -63,7 +108,7 @@ class _MultiplexPageState extends State { margin: EdgeInsets.only(right: 5), decoration: BoxDecoration(border: Border.all(color: Color(0xFFFF9000)), borderRadius: BorderRadius.all(Radius.circular(7))), child: Text( - '123', + '${data.label?[index].name}', style: TextStyle(fontSize: 10, color: Color(0xFFFF9000)), ), ); @@ -76,7 +121,7 @@ class _MultiplexPageState extends State { width: 200, child: Text( maxLines: 3, - '这里是属于斗气的世界,没有花俏艳丽的魔法有的,仅仅是,斗王,斗皇,斗宗,斗尊,斗圣,斗帝...', + '${data.biography}', overflow: TextOverflow.ellipsis, style: TextStyle(color: Colors.white, fontSize: 10), ), diff --git a/lib/tools/find/recommend_page.dart b/lib/tools/find/recommend_page.dart index 6dcc2c5..581fdaf 100644 --- a/lib/tools/find/recommend_page.dart +++ b/lib/tools/find/recommend_page.dart @@ -1,7 +1,14 @@ +import 'dart:async'; + +import 'package:cached_network_image/cached_network_image.dart'; import 'package:card_swiper/card_swiper.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import '../../beans/find_banner_bean.dart'; +import '../../beans/recommend_bean.dart'; import '../../custom/custom_swiper_pagination.dart'; +import 'find_model.dart'; ///推荐 class RecommendPage extends StatefulWidget { @@ -12,6 +19,61 @@ class RecommendPage extends StatefulWidget { } class _RecommendPageState extends State { + late StreamSubscription subscription; + + final FindModel _viewmodel = FindModel(); + + List bannerList = []; + List recommendList = []; //推荐 + List popularList = []; //热门 + + @override + void initState() { + // TODO: implement initState + super.initState(); + + subscription = _viewmodel.streamController.stream.listen((newData) { + String code = newData['code']; + if (code.isNotEmpty) { + switch (code) { + case "getCategoryFindList": + List data = newData['data']; + for (var value in data) { + switch (value['type']) { + case "banner": //轮播图 + bannerList = (value['data'] as List).map((e) => FindBannerBean.fromJson(e as Map)).toList(); + break; + case "tuijian": //推荐 + recommendList = (value['data'] as List).map((e) => RecommendBean.fromJson(e as Map)).toList(); + break; + case "xiaoshuo": //推荐 + popularList = (value['data'] as List).map((e) => RecommendBean.fromJson(e as Map)).toList(); + break; + } + } + setState(() {}); + break; + + default: + EasyLoading.showToast(newData['data']); + break; + } + // setState(() {}); + EasyLoading.dismiss(); + } + }); + + EasyLoading.show(status: "'loading...'"); + _viewmodel.getCategoryFindList(); + } + + @override + void dispose() { + // TODO: implement dispose + subscription.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Stack( @@ -28,14 +90,16 @@ class _RecommendPageState extends State { child: Swiper( itemBuilder: (BuildContext context, int index) { return Container( + key: ValueKey(index), padding: EdgeInsets.only(bottom: 30), - child: Image( - image: AssetImage('assets/images/banner1.png'), + child: CachedNetworkImage( fit: BoxFit.fill, + imageUrl: bannerList[index].imageUrl!, + errorWidget: (context, url, error) => const Icon(Icons.error), ), ); }, - itemCount: 3, + itemCount: bannerList.length, pagination: SwiperPagination( // 此处使用自己编写的样式 builder: @@ -55,10 +119,10 @@ class _RecommendPageState extends State { margin: EdgeInsets.only(top: 21), height: 360, child: ListView.builder( - itemCount: 10, + itemCount: recommendList.length, scrollDirection: Axis.horizontal, itemBuilder: (BuildContext context, int index) { - return _item(index); + return _item(index, recommendList[index]); }), ), Container( @@ -70,13 +134,13 @@ class _RecommendPageState extends State { ), ), Container( - height: 153 * 10, + height: 153.0 * (popularList.length), margin: EdgeInsets.only(left: 16, right: 16, bottom: 60), child: ListView.builder( - itemCount: 10, + itemCount: popularList.length, physics: NeverScrollableScrollPhysics(), itemBuilder: (BuildContext context, int index) { - return _item2(index); + return _item2(index, popularList[index]); }), ) ], @@ -88,19 +152,28 @@ class _RecommendPageState extends State { } } -_item(int index) { +_item(int index, RecommendBean data) { return Container( margin: EdgeInsets.only(right: 9, left: index == 0 ? 16 : 0), child: Column( children: [ Stack( children: [ - Image(width: 113, height: 159, image: AssetImage('assets/images/a1.png')), + ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(15)), + child: CachedNetworkImage( + width: 113, + height: 159, + fit: BoxFit.fill, + imageUrl: data.imageUrl!, + errorWidget: (context, url, error) => const Icon(Icons.error), + ), + ), Positioned( left: 7, bottom: 21, child: Text( - '细弱', + '${data.name}', style: TextStyle(color: Colors.white, fontSize: 12), )), Positioned( @@ -109,7 +182,7 @@ _item(int index) { child: Container( width: 105, child: Text( - '这里是属于斗气有圣斗帝...', + '${data.biography}', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(color: Color(0xFFC2C2C2), fontSize: 9), @@ -121,16 +194,21 @@ _item(int index) { margin: EdgeInsets.only(top: 12), child: Stack( children: [ - Image( - width: 113, - height: 159, - image: AssetImage('assets/images/a2.png'), + ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(15)), + child: CachedNetworkImage( + width: 113, + height: 159, + fit: BoxFit.fill, + imageUrl: data.imageUrl!, + errorWidget: (context, url, error) => const Icon(Icons.error), + ), ), Positioned( left: 7, bottom: 21, child: Text( - '细弱', + '${data.name}', style: TextStyle(color: Colors.white, fontSize: 12), )), Positioned( @@ -139,7 +217,7 @@ _item(int index) { child: Container( width: 105, child: Text( - '这里是属于斗气有圣斗帝...', + '${data.biography}', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(color: Color(0xFFC2C2C2), fontSize: 9), @@ -153,7 +231,7 @@ _item(int index) { ); } -_item2(index) { +_item2(index, RecommendBean data) { return Container( height: 130, width: 50, @@ -167,17 +245,23 @@ _item2(index) { alignment: Alignment.center, children: [ Positioned( - left: 7, - child: Image( + left: 7, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(15)), + child: CachedNetworkImage( width: 83, height: 115, - image: AssetImage('assets/images/a3.png'), - )), + fit: BoxFit.fill, + imageUrl: data.imageUrl!, + errorWidget: (context, url, error) => const Icon(Icons.error), + ), + ), + ), Positioned( left: 101, top: 19, child: Text( - '重生之千金大小姐倒追我', + '${data.name}', style: TextStyle(color: Colors.white, fontSize: 14), )), Positioned( @@ -187,7 +271,7 @@ _item2(index) { width: 200, height: 16, child: ListView.builder( - itemCount: 3, + itemCount: data.label?.length, scrollDirection: Axis.horizontal, itemBuilder: (BuildContext context, int index) { return Container( @@ -196,7 +280,7 @@ _item2(index) { margin: EdgeInsets.only(right: 5), decoration: BoxDecoration(border: Border.all(color: Color(0xFFFF9000)), borderRadius: BorderRadius.all(Radius.circular(7))), child: Text( - '123', + '${data.label?[index].name}', style: TextStyle(fontSize: 10, color: Color(0xFFFF9000)), ), ); @@ -209,7 +293,7 @@ _item2(index) { width: 200, child: Text( maxLines: 3, - '这里是属于斗气的世界,没有花俏艳丽的魔法有的,仅仅是,斗王,斗皇,斗宗,斗尊,斗圣,斗帝...', + '${data.biography}', overflow: TextOverflow.ellipsis, style: TextStyle(color: Colors.white, fontSize: 10), ), diff --git a/lib/tools/home/home_chat_page.dart b/lib/tools/home/home_chat_page.dart index 99f1426..6c2264b 100644 --- a/lib/tools/home/home_chat_page.dart +++ b/lib/tools/home/home_chat_page.dart @@ -5,11 +5,11 @@ import 'package:expandable_text/expandable_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; -import '../../beans/MessageBean.dart'; import '../../beans/character_info_bean.dart'; +import '../../beans/chat_info_bean.dart'; +import '../../beans/send_message_bean.dart'; import '../chat/chat_info_page.dart'; import '../chat/chat_model.dart'; -import '../chat/chat_page.dart'; class HomeChatPage extends StatefulWidget { String characterId; @@ -30,8 +30,14 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie ///人物信息 late CharacterInfoBean characterInfoBean; + ///发送消息聊天 + late SendMessageBean sendMessageBean; + ///聊天列表 - List chatList = []; + List chatList = []; + + ///聊天列表是否在底部 + bool isBottom = true; bool isMore = false; @@ -45,6 +51,8 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie setState(() {}); } + int delIndex = 0; + @override void initState() { // TODO: implement initState @@ -56,20 +64,79 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie switch (code) { case "getCharacterInfo": characterInfoBean = newData['data']; + _viewmodel.getChatInfo(widget.characterId); + break; + case "getChatInfo": + chatList.addAll(newData['data']); + setState(() {}); + Future.delayed(Duration(milliseconds: 100), () { + _scrollToBottom(); + }); + EasyLoading.dismiss(); + 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(() {}); } }); - _viewmodel.getCharacterInfo(widget.characterId); + _scrollController.addListener(() { + if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent) { + //滚动到底部 + isBottom = true; + } + }); - chatList = [ - MessageBean("user", "你好"), - MessageBean("introduce", "王语嫣 年龄22 三围 28 36 48 分别多喜欢打台球旅行,唱歌,当绿茶喜欢打台球旅行,唱歌,当绿茶当绿茶喜欢打台球旅行,唱歌,当绿茶当绿茶喜欢打台球旅行,唱歌,当绿茶"), - MessageBean("assistant", "(仔细观察你的样子,露出满意的笑容我想...)"), - ]; + 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); + } + + void _scrollToBottom() { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 300), + ); } @override @@ -83,6 +150,7 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie @override Widget build(BuildContext context) { + super.build(context); return Stack( children: [ CachedNetworkImage( @@ -115,6 +183,7 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie alignment: Alignment.center, children: [ Positioned( + top: 0, left: 105, child: Text( "+1", @@ -179,13 +248,13 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie margin: EdgeInsets.only(left: 6, right: 6), child: GestureDetector( onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ChatPage( - id: characterInfoBean.characterId.toString(), - )), - ); + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => ChatPage( + // characterId: characterInfoBean.characterId.toString(), + // )), + // ); }, child: Stack( alignment: Alignment.center, @@ -291,6 +360,8 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie ), ), ), + + ///发送 Container( margin: const EdgeInsets.only(right: 7), child: text == "" @@ -300,7 +371,9 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie ) : GestureDetector( onTap: () { - chatList.add(MessageBean("user", text)); + 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), () { @@ -373,18 +446,24 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie ), Container( margin: EdgeInsets.only(left: 23), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Image(width: 26, image: AssetImage('assets/images/ic_more.png')), - Container( - margin: EdgeInsets.only(top: 9), - child: Text( - '重启对话', - style: TextStyle(fontSize: 10, color: Color(0xFFA2A2A2)), + child: GestureDetector( + onTap: () { + 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)), + ), ), - ), - ], + ], + ), ), ), ], @@ -410,12 +489,12 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie ///聊天条目 _item(index) { - if (index == 0) { - return Container(); - } + // if (index == 0) { + // return Container(); + // } ///简介 - if (chatList[index].role == 'introduce') { + if (chatList[index].role == 'profile') { return Center( child: Container( margin: EdgeInsets.only(left: 16, right: 16), @@ -444,6 +523,29 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie // ); // } + ///提示 + 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), @@ -464,19 +566,20 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), padding: const EdgeInsets.all(11.0), decoration: const BoxDecoration( - color: Color(0xFFFF9000), + 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"); + EasyLoading.showToast("status${chatList[index].id}"); + delChat(chatList[index].id!, index); }, - child: SelectableText( + child: Text( chatList[index].content!, style: const TextStyle( - fontSize: 16, - color: Colors.black, + fontSize: 14, + color: Color(0xFFE8E8E8), ), ), ), @@ -535,7 +638,7 @@ class _HomeChatPageState extends State with AutomaticKeepAliveClie chatList[index].content!, style: const TextStyle( fontSize: 16, - color: Colors.white, + color: Color(0xFFE8E8E8), ), ), ), diff --git a/lib/tools/login/login_model.dart b/lib/tools/login/login_model.dart index eb5dcbc..d7e59e2 100644 --- a/lib/tools/login/login_model.dart +++ b/lib/tools/login/login_model.dart @@ -53,6 +53,7 @@ class LoginModel { LoginBean loginBean = LoginBean.fromJson(dataEntity.data); NetworkConfig.userId = loginBean.userId!.toString(); NetworkConfig.userName = loginBean.nickName!; + NetworkConfig.token = loginBean.token!; // final SharedPreferences prefs = await SharedPreferences.getInstance(); diff --git a/lib/tools/message/message_model.dart b/lib/tools/message/message_model.dart new file mode 100644 index 0000000..5a8b864 --- /dev/null +++ b/lib/tools/message/message_model.dart @@ -0,0 +1,42 @@ +import 'dart:async'; + +import 'package:talk/network/NetworkConfig.dart'; +import 'package:talk/network/RequestCenter.dart'; + +import '../../beans/message_bean.dart'; +import '../../network/BaseEntity.dart'; + +class MessageModel { + StreamController streamController = StreamController.broadcast(); + + MessageModel() { + setup(); + } + + void setup() { + //初始化 + } + + ///消息列表 + Future getChatHistoryList() async { + RequestCenter.instance.requestGet(NetworkConfig.getChatHistoryList, {}, (BaseEntity dataEntity) { + print("dataEntity==${dataEntity.data}"); + + if (dataEntity.code == 0) { + List data = (dataEntity.data as List).map((e) => MessageBean.fromJson(e as Map)).toList(); + + streamController.sink.add({ + 'code': "getChatHistoryList", //有数据 + 'data': data, + }); + } else { + streamController.sink.add({ + 'code': "error", // + 'data': dataEntity.message, + }); + } + }, (ErrorEntity errorEntity) { + print("errorEntity==${errorEntity.message}"); + }); + } +} diff --git a/lib/tools/message/message_page.dart b/lib/tools/message/message_page.dart index 3a7cf3e..73f5375 100644 --- a/lib/tools/message/message_page.dart +++ b/lib/tools/message/message_page.dart @@ -1,7 +1,15 @@ +import 'dart:async'; + +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:talk/beans/message_bean.dart'; +import 'package:talk/tools/message/message_model.dart'; +import '../chat/chat_page.dart'; + +///消息列表 class MessagePage extends StatefulWidget { const MessagePage({super.key}); @@ -10,12 +18,47 @@ class MessagePage extends StatefulWidget { } class _MessagePageState extends State { - List messageList = [1, 2, 3, 4, 5, 6]; + List messageList = []; + + late StreamSubscription subscription; + + final MessageModel _viewmodel = MessageModel(); @override void initState() { // TODO: implement initState super.initState(); + + subscription = _viewmodel.streamController.stream.listen((newData) { + String code = newData['code']; + if (code.isNotEmpty) { + switch (code) { + case "getChatHistoryList": + messageList = newData['data']; + break; + default: + EasyLoading.dismiss(); + EasyLoading.showToast(newData['data']); + break; + } + EasyLoading.dismiss(); + setState(() {}); + } + }); + + loadData(); + } + + loadData() { + EasyLoading.show(status: 'loading...'); + _viewmodel.getChatHistoryList(); + } + + @override + void dispose() { + // TODO: implement dispose + subscription.cancel(); + super.dispose(); } @override @@ -37,11 +80,13 @@ class _MessagePageState extends State { Expanded( child: Container( padding: EdgeInsets.only(bottom: 60), - child: ListView.builder( - itemCount: 10, - itemBuilder: (BuildContext context, int index) { - return _item(index); - }), + child: SlidableAutoCloseBehavior( + child: ListView.builder( + itemCount: messageList.length, + itemBuilder: (BuildContext context, int index) { + return _item(messageList[index], context); + }), + ), )) ], ) @@ -50,7 +95,7 @@ class _MessagePageState extends State { ); } - _item(index) { + _item(MessageBean data, BuildContext context) { return Slidable( //左滑划出的菜单 endActionPane: ActionPane( @@ -61,10 +106,11 @@ class _MessagePageState extends State { // 滑动动效 // DrawerMotion() StretchMotion() // motion: ScrollMotion(), + // A pane can dismiss the Slidable. motion: BehindMotion(), - children: const [ + children: [ SlidableAction( - onPressed: doNothing, + onPressed: doNothing(context, data.id), backgroundColor: Color(0xFFFF9000), foregroundColor: Colors.black, label: '删除', @@ -72,54 +118,68 @@ class _MessagePageState extends State { ], ), - child: Container( - height: 50, - margin: EdgeInsets.only(bottom: 10), - child: Stack( - alignment: Alignment.center, - children: [ - Positioned( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChatPage( + characterId: data.id.toString(), + )), + ); + }, + child: Container( + height: 50, + margin: EdgeInsets.only(bottom: 10), + child: Stack( + alignment: Alignment.center, + children: [ + Positioned( left: 16, - child: Image( + child: CachedNetworkImage( width: 40, height: 40, - image: AssetImage('assets/images/img_head2.png'), - )), - Positioned( - left: 68, - top: 6, - child: Text( - '王语嫣', - style: TextStyle(fontSize: 13, color: Color(0xFFE1E1E1)), - )), - Positioned( - left: 68, - top: 25, - child: Container( - width: 160, + imageUrl: data.iconImage!, + errorWidget: (context, url, error) => const Icon(Icons.error), + ), + ), + Positioned( + left: 68, + top: 6, child: Text( - '你好', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 12, color: Color(0xFF777777)), - ), - )), - Positioned( - right: 16, - top: 6, - child: Container( - child: Text( - '12:39', - style: TextStyle(fontSize: 10, color: Color(0xFF8D8D8D)), - ), - )), - ], + data.name!, + style: TextStyle(fontSize: 13, color: Color(0xFFE1E1E1)), + )), + Positioned( + left: 68, + top: 25, + child: Container( + width: 160, + child: Text( + '你好', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 12, color: Color(0xFF777777)), + ), + )), + Positioned( + right: 16, + top: 6, + child: Container( + child: Text( + '12:39', + style: TextStyle(fontSize: 10, color: Color(0xFF8D8D8D)), + ), + )), + ], + ), ), ), ); } } -void doNothing(BuildContext context) { - EasyLoading.showToast("删除"); +doNothing(BuildContext context, id) { + // EasyLoading.showToast("删除"); } diff --git a/pubspec.yaml b/pubspec.yaml index 91e6b3e..56e5791 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,7 @@ dependencies: dio: ^5.4.3+1 cupertino_icons: ^1.0.6 flutter_easyloading: ^3.0.5 + flutter_spinkit: ^5.2.1 crypto: ^3.0.3 shared_preferences: ^2.2.3 json_annotation: ^4.9.0