master
jack ning 3 years ago
parent 4adda0eceb
commit 8c7df6205d

BIN
.DS_Store vendored

Binary file not shown.

@ -44,6 +44,10 @@ class MyApp extends StatefulWidget {
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
// // appkey->->Flutter->->appkey
// String _appKey = '81f427ea-4467-4c7c-b0cd-5c0e4b51456f';
// // subDomain->->->
// String _subDomain = "vip";
//
String _title = '萝卜丝客服Demo(连接中...)';
AudioCache audioCache = AudioCache();
@ -74,7 +78,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
//
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return ChatTypePage();
return const ChatTypePage();
}));
},
),
@ -85,7 +89,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
// anonymousLogin
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return UserInfoPage();
return const UserInfoPage();
}));
},
),
@ -95,7 +99,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return OnlineStatusPage();
return const OnlineStatusPage();
}));
},
),
@ -105,7 +109,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return HistoryThreadPage();
return const HistoryThreadPage();
}));
},
),
@ -131,10 +135,27 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return SettingPage();
return const SettingPage();
}));
},
),
// ListTile(
// title: Text('退出登录'),
// onTap: () {
// BytedeskKefu.logout();
// },
// ),
// ListTile(
// title: Text('重新初始化'),
// onTap: () {
// BytedeskKefu.initWithUsernameAndNicknameAndAvatar(
// 'myflutterusername2',
// '我是帅哥',
// 'https://bytedesk.oss-cn-shenzhen.aliyuncs.com/avatars/boy.png',
// _appKey,
// _subDomain);
// },
// ),
const ListTile(
title: Text('技术支持: QQ-3群: 825257535'),
)

@ -15,10 +15,10 @@ class ChatTypePage extends StatefulWidget {
class _ChatTypePageState extends State<ChatTypePage> {
// ->- IDwId, wid
// 访
String _workGroupWid = "201807171659201"; //
String _workGroupWidRobot = "201809061716221"; // ,
final String _workGroupWid = "201807171659201"; //
final String _workGroupWidRobot = "201809061716221"; // ,
//
String _agentUid = "201808221551193";
final String _agentUid = "201808221551193";
//
String _unreadMessageCount = "0";
//
@ -53,6 +53,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: const Text('技能组客服'),
subtitle: const Text('默认人工'),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startWorkGroupChat(
@ -61,14 +62,25 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: const Text('技能组客服-机器人'),
subtitle: const Text('默认热门问题'),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startWorkGroupChat(
context, _workGroupWidRobot, "技能组客服-默认机器人");
context, _workGroupWidRobot, "技能组客服-默认机器人问题");
},
),
ListTile(
title: const Text('技能组客服-机器人2'),
subtitle: const Text('默认问题分类'),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startWorkGroupChatV2Robot(
context, _workGroupWidRobot, "技能组客服-默认机器人分类");
},
),
ListTile(
title: const Text('技能组客服-电商'),
subtitle: const Text('携带商品参数'),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
// type/title/content/price/url/imageUrl/id/categoryCode
@ -92,6 +104,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: const Text('技能组客服-电商-回调'),
subtitle: const Text('携带商品参数,点击商品支持回调'),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
// type/title/content/price/url/imageUrl/id/categoryCode
@ -122,6 +135,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: const Text('技能组客服-附言'),
subtitle: const Text('自动发送一条消息'),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startWorkGroupChatPostscript(
@ -133,6 +147,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: const Text('指定一对一客服'),
subtitle: const Text('默认人工'),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startAppointedChat(context, _agentUid, "指定一对一客服");
@ -140,6 +155,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: const Text('指定一对一客服-电商'),
subtitle: const Text('携带商品参数'),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
// type/title/content/price/url/imageUrl/id/categoryCode
@ -162,6 +178,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: const Text('指定一对一客服-电商-回调'),
subtitle: const Text('携带商品参数,点击商品支持回调'),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
// type/title/content/price/url/imageUrl/id/categoryCode
@ -191,6 +208,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: const Text('指定一对一客服-附言'),
subtitle: const Text('自动发送一条消息'),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startAppointedChatPostscript(
@ -204,7 +222,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
title: const Text('H5网页会话'),
trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () {
// print('h5 chat');
print('h5 chat');
// : ->->()-> URL
String url =
"https://h2.kefux.cn/chat/h5/index.html?sub=vip&uid=201808221551193&wid=201807171659201&type=workGroup&aid=&hidenav=1&ph=ph";

@ -46,7 +46,7 @@ dependencies:
# 请在ios/Podfile中添加use_frameworks!
vibration: ^1.7.3
# 在线客服 https://pub.dev/packages/bytedesk_kefu
bytedesk_kefu: ^1.2.5
bytedesk_kefu: ^1.2.8
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.

@ -1,5 +1,13 @@
# Upgrade Log
## 1.2.8
* optimize user experience
## 1.2.7
* optimize user experience
## 1.2.6
* optimize user experience

@ -65,7 +65,7 @@ BytedeskKefu.startWorkGroupChat(context, workGroupWid, "title");
```dart
bytedesk_kefu:
path: ./vendors/bytedesk
path: ./vendors/bytedesk_kefu
```
### Support

@ -43,10 +43,10 @@ class MyApp extends StatefulWidget {
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
// appkey->->Flutter->->appkey
String _appKey = '81f427ea-4467-4c7c-b0cd-5c0e4b51456f';
// subDomain->->->
String _subDomain = "vip";
// // appkey->->Flutter->->appkey
// String _appKey = '81f427ea-4467-4c7c-b0cd-5c0e4b51456f';
// // subDomain->->->
// String _subDomain = "vip";
//
String _title = '萝卜丝客服Demo(连接中...)';
// AudioCache audioCache = AudioCache();

@ -53,6 +53,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: Text('技能组客服'),
subtitle: Text('默认人工'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startWorkGroupChat(
@ -61,14 +62,25 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: Text('技能组客服-机器人'),
subtitle: Text('默认热门问题'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startWorkGroupChat(
context, _workGroupWidRobot, "技能组客服-默认机器人");
context, _workGroupWidRobot, "技能组客服-默认机器人问题");
},
),
ListTile(
title: Text('技能组客服-机器人2'),
subtitle: Text('默认问题分类'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startWorkGroupChatV2Robot(
context, _workGroupWidRobot, "技能组客服-默认机器人分类");
},
),
ListTile(
title: Text('技能组客服-电商'),
subtitle: Text('携带商品参数'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
// type/title/content/price/url/imageUrl/id/categoryCode
@ -92,6 +104,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: Text('技能组客服-电商-回调'),
subtitle: Text('携带商品参数,点击商品支持回调'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
// type/title/content/price/url/imageUrl/id/categoryCode
@ -122,6 +135,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: Text('技能组客服-附言'),
subtitle: Text('自动发送一条消息'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startWorkGroupChatPostscript(
@ -133,6 +147,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: Text('指定一对一客服'),
subtitle: Text('默认人工'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startAppointedChat(context, _agentUid, "指定一对一客服");
@ -140,6 +155,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: Text('指定一对一客服-电商'),
subtitle: Text('携带商品参数'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
// type/title/content/price/url/imageUrl/id/categoryCode
@ -162,6 +178,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: Text('指定一对一客服-电商-回调'),
subtitle: Text('携带商品参数,点击商品支持回调'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
// type/title/content/price/url/imageUrl/id/categoryCode
@ -191,6 +208,7 @@ class _ChatTypePageState extends State<ChatTypePage> {
),
ListTile(
title: Text('指定一对一客服-附言'),
subtitle: Text('自动发送一条消息'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startAppointedChatPostscript(

@ -3,10 +3,7 @@ import 'package:bloc/bloc.dart';
import './bloc.dart';
class GroupBloc extends Bloc<GroupEvent, GroupState> {
GroupBloc() : super(InitialGroupState()) {
}
GroupBloc() : super(InitialGroupState());
// @override
// Stream<GroupState> mapEventToState(

@ -1,4 +1,4 @@
import 'dart:async';
// import 'dart:async';
import 'package:bytedesk_kefu/blocs/help_bloc/bloc.dart';
import 'package:bytedesk_kefu/model/helpArticle.dart';
import 'package:bytedesk_kefu/model/helpCategory.dart';

@ -1,4 +1,4 @@
import 'dart:async';
// import 'dart:async';
import 'package:bytedesk_kefu/blocs/leavemsg_bloc/bloc.dart';
import 'package:bloc/bloc.dart';
import 'package:bytedesk_kefu/repositories/leavemsg_repository.dart';

@ -2,6 +2,7 @@
import 'package:bytedesk_kefu/model/jsonResult.dart';
import 'package:bytedesk_kefu/model/message.dart';
import 'package:bytedesk_kefu/model/requestAnswer.dart';
import 'package:bytedesk_kefu/model/requestCategory.dart';
import 'package:bytedesk_kefu/model/uploadJsonResult.dart';
import 'package:bytedesk_kefu/repositories/message_repository.dart';
import 'package:bloc/bloc.dart';
@ -21,6 +22,7 @@ class MessageBloc extends Bloc<MessageEvent, MessageState> {
on<LoadChannelMessageEvent>(_mapLoadChannelMessageToState);
on<QueryAnswerEvent>(_mapQueryAnswerToState);
on<QueryCategoryEvent>(_mapQueryCategoryToState);
on<MessageAnswerEvent>(_mapMessageAnswerToState);
on<RateAnswerEvent>(_mapRateAnswerToState);
}
@ -134,7 +136,7 @@ class MessageBloc extends Bloc<MessageEvent, MessageState> {
emit(MessageLoading());
try {
final RequestAnswerResult requestAnswerResult =
await messageRepository.queryAnswer(event.tid, event.aid);
await messageRepository.queryAnswer(event.tid, event.aid, event.mid);
emit(QueryAnswerSuccess(
query: requestAnswerResult.query, answer: requestAnswerResult.anwser));
} catch (error) {
@ -143,11 +145,26 @@ class MessageBloc extends Bloc<MessageEvent, MessageState> {
}
}
void _mapQueryCategoryToState(
QueryCategoryEvent event, Emitter<MessageState> emit) async {
emit(MessageLoading());
try {
final RequestCategoryResult requestAnswerResult =
await messageRepository.queryCategory(event.tid, event.cid);
emit(QueryCategorySuccess(
query: requestAnswerResult.query,
answer: requestAnswerResult.anwser));
} catch (error) {
print(error);
emit(UpLoadImageError());
}
}
void _mapMessageAnswerToState(MessageAnswerEvent event, Emitter<MessageState> emit) async {
emit(MessageLoading());
try {
final RequestAnswerResult requestAnswerResult = await messageRepository
.messageAnswer(event.type, event.wid, event.aid, event.content);
.messageAnswer(event.wid, event.content);
emit(MessageAnswerSuccess(
query: requestAnswerResult.query, answer: requestAnswerResult.anwser));
} catch (error) {

@ -67,20 +67,30 @@ class LoadChannelMessageEvent extends MessageEvent {
class QueryAnswerEvent extends MessageEvent {
final String? tid;
final String? aid;
final String? mid;
QueryAnswerEvent({@required this.tid, @required this.aid, @required this.mid}) : super();
}
class QueryCategoryEvent extends MessageEvent {
final String? tid;
final String? cid;
QueryAnswerEvent({@required this.tid, @required this.aid}) : super();
QueryCategoryEvent({@required this.tid, @required this.cid})
: super();
}
class MessageAnswerEvent extends MessageEvent {
final String? type;
// final String? type;
final String? wid;
final String? aid;
// final String? aid;
final String? content;
MessageAnswerEvent(
{@required this.type,
{
// @required this.type,
@required this.wid,
@required this.aid,
// @required this.aid,
@required this.content})
: super();
}

@ -126,6 +126,13 @@ class QueryAnswerSuccess extends MessageState {
QueryAnswerSuccess({@required this.query, @required this.answer}) : super();
}
class QueryCategorySuccess extends MessageState {
final Message? query;
final Message? answer;
QueryCategorySuccess({@required this.query, @required this.answer}) : super();
}
class MessageAnswerSuccess extends MessageState {
final Message? query;
final Message? answer;

@ -1,4 +1,4 @@
import 'dart:async';
// import 'dart:async';
import 'package:bytedesk_kefu/model/jsonResult.dart';
import 'package:bytedesk_kefu/model/user.dart';
import 'package:bytedesk_kefu/repositories/user_repository.dart';

@ -17,8 +17,8 @@ class ThreadBloc extends Bloc<ThreadEvent, ThreadState> {
on<RefreshHistoryThreadEvent>(_mapRefreshHistoryThreadToState);
on<RefreshVisitorThreadEvent>(_mapRefreshVisitorThreadToState);
on<RefreshVisitorThreadAllEvent>(_mapRefreshVisitorThreadAllToState);
on<RequestThreadEvent>(_mapRequestThreadToState);
on<RequestThreadEvent>(_mapRequestThreadToState);
on<RequestAgentEvent>(_mapRequestAgentToState);
on<RequestContactThreadEvent>(_mapRequestContactThreadToState);
on<RequestGroupThreadEvent>(_mapRequestGroupThreadToState);
@ -85,11 +85,10 @@ class ThreadBloc extends Bloc<ThreadEvent, ThreadState> {
void _mapRequestThreadToState(
RequestThreadEvent event, Emitter<ThreadState> emit) async {
print('RequestThreadEvent');
emit(RequestThreading());
try {
final RequestThreadResult thread = await threadRepository.requestThread(
event.wid, event.type, event.aid);
event.wid, event.type, event.aid, event.isV2Robot);
emit(RequestThreadSuccess(thread));
} catch (error) {
print(error);

@ -55,9 +55,10 @@ class RequestThreadEvent extends ThreadEvent {
final String? wid;
final String? type;
final String? aid;
final bool? isV2Robot;
RequestThreadEvent(
{@required this.wid, @required this.type, @required this.aid})
{@required this.wid, @required this.type, @required this.aid, @required this.isV2Robot})
: super();
}

@ -173,14 +173,20 @@ class BytedeskKefu {
static void startWorkGroupChat(
BuildContext context, String wid, String title) {
startChatDefault(
context, wid, BytedeskConstants.CHAT_TYPE_WORKGROUP, title);
context, wid, BytedeskConstants.CHAT_TYPE_WORKGROUP, title, false);
}
static void startWorkGroupChatV2Robot(
BuildContext context, String wid, String title) {
startChatDefault(
context, wid, BytedeskConstants.CHAT_TYPE_WORKGROUP, title, true);
}
//
static void startWorkGroupChatPostscript(
BuildContext context, String wid, String title, String postScript) {
startChat(context, wid, BytedeskConstants.CHAT_TYPE_WORKGROUP, title, '',
postScript, null);
postScript, false, null);
}
//
@ -201,14 +207,14 @@ class BytedeskKefu {
static void startAppointedChat(
BuildContext context, String uid, String title) {
startChatDefault(
context, uid, BytedeskConstants.CHAT_TYPE_APPOINTED, title);
context, uid, BytedeskConstants.CHAT_TYPE_APPOINTED, title, false);
}
//
static void startAppointedChatPostscript(
BuildContext context, String uid, String title, String postScript) {
startChat(context, uid, BytedeskConstants.CHAT_TYPE_APPOINTED, title, '',
postScript, null);
postScript, false, null);
}
//
@ -226,20 +232,20 @@ class BytedeskKefu {
//
static void startChatDefault(
BuildContext context, String uuid, String type, String title) {
startChat(context, uuid, type, title, '', '', null);
BuildContext context, String uuid, String type, String title, bool isV2Robot) {
startChat(context, uuid, type, title, '', '', isV2Robot, null);
}
// -()
static void startChatShop(BuildContext context, String uuid, String type,
String title, String commodity, ValueSetter<String>? customCallback) {
startChat(context, uuid, type, title, commodity, '', customCallback);
startChat(context, uuid, type, title, commodity, '', false, customCallback);
}
// -()
static void startChatPostscript(BuildContext context, String uuid,
String type, String title, String postScript) {
startChat(context, uuid, type, title, '', postScript, null);
startChat(context, uuid, type, title, '', postScript, false, null);
}
// -()
@ -250,6 +256,7 @@ class BytedeskKefu {
String title,
String commodity,
String postScript,
bool isV2Robot,
ValueSetter<String>? customCallback) {
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
return new ChatKFProvider(
@ -259,6 +266,7 @@ class BytedeskKefu {
title: title,
custom: commodity,
postscript: postScript,
isV2Robot: isV2Robot,
customCallback: customCallback,
);
}));

@ -5,6 +5,7 @@ import 'package:bytedesk_kefu/http/bytedesk_base_api.dart';
import 'package:bytedesk_kefu/model/jsonResult.dart';
import 'package:bytedesk_kefu/model/message.dart';
import 'package:bytedesk_kefu/model/requestAnswer.dart';
import 'package:bytedesk_kefu/model/requestCategory.dart';
import 'package:bytedesk_kefu/model/uploadJsonResult.dart';
import 'package:bytedesk_kefu/util/bytedesk_constants.dart';
import 'package:bytedesk_kefu/util/bytedesk_events.dart';
@ -143,8 +144,6 @@ class BytedeskMessageHttpApi extends BytedeskBaseHttpApi {
//
Future<RequestAnswerResult> queryAnswer(String? tid, String? aid) async {
//
// final queryAnswerUrl =
// '$baseUrl/api/answer/query?tid=$tid&aid=$aid&client=$client';
final queryAnswerUrl = Uri.http(BytedeskConstants.host, '/api/answer/query',
{'tid': tid, 'aid': aid, 'client': client});
print("query Url $queryAnswerUrl");
@ -165,16 +164,64 @@ class BytedeskMessageHttpApi extends BytedeskBaseHttpApi {
}
//
Future<RequestAnswerResult> messageAnswer(
String? type, String? wid, String? aid, String? content) async {
Future<RequestAnswerResult> queryAnswer2(
String? tid, String? aid, String? mid) async {
//
final queryAnswerUrl = Uri.http(
BytedeskConstants.host,
'/api/v2/answer/query',
{'tid': tid, 'aid': aid, 'mid': mid, 'client': client});
print("query Url $queryAnswerUrl");
final initResponse =
await this.httpClient.get(queryAnswerUrl, headers: getHeaders());
//
//json
Utf8Decoder utf8decoder = Utf8Decoder(); // fix
//string json
final responseJson =
json.decode(utf8decoder.convert(initResponse.bodyBytes));
// token
if (responseJson.toString().contains('invalid_token')) {
bytedeskEventBus.fire(InvalidTokenEventBus());
}
//
return RequestAnswerResult.fromJson(responseJson);
}
//
Future<RequestCategoryResult> queryCategory(
String? tid, String? cid) async {
//
final queryCategoryUrl = Uri.http(
BytedeskConstants.host,
'/api/v2/answer/category',
{'tid': tid, 'cid': cid, 'client': client});
print("query Url $queryCategoryUrl");
final initResponse =
await this.httpClient.get(queryCategoryUrl, headers: getHeaders());
//
//json
Utf8Decoder utf8decoder = Utf8Decoder(); // fix
//string json
final responseJson =
json.decode(utf8decoder.convert(initResponse.bodyBytes));
// token
if (responseJson.toString().contains('invalid_token')) {
bytedeskEventBus.fire(InvalidTokenEventBus());
}
//
// final messageAnswerUrl =
// '$baseUrl/api/v2/answer/message?type=$type&wid=$wid&aid=$aid&content=$content&client=$client';
final messageAnswerUrl = Uri.http(
BytedeskConstants.host, '/api/v2/answer/message', {
'type': type,
return RequestCategoryResult.fromJson(responseJson);
}
//
Future<RequestAnswerResult> messageAnswer(
String? wid, String? content) async {
// String? aid, String? type,
final messageAnswerUrl =
Uri.http(BytedeskConstants.host, '/api/v2/answer/message', {
// 'type': type,
'wid': wid,
'aid': aid,
// 'aid': aid,
'content': content,
'client': client
});
@ -187,7 +234,8 @@ class BytedeskMessageHttpApi extends BytedeskBaseHttpApi {
//string json
final responseJson =
json.decode(utf8decoder.convert(initResponse.bodyBytes));
print("messageAnswer responseJson $responseJson");
print("messageAnswer:");
print(responseJson);
// token
if (responseJson.toString().contains('invalid_token')) {
bytedeskEventBus.fire(InvalidTokenEventBus());

@ -149,7 +149,7 @@ class BytedeskThreadHttpApi extends BytedeskBaseHttpApi {
//
final threadUrl = Uri.http(BytedeskConstants.host, '/api/thread/request',
{'wId': wid, 'type': type, 'aId': aid, 'client': client});
// print("request thread Url $threadUrl");
print(threadUrl);
final initResponse =
await this.httpClient.get(threadUrl, headers: getHeaders());
@ -158,7 +158,8 @@ class BytedeskThreadHttpApi extends BytedeskBaseHttpApi {
//string json
final responseJson =
json.decode(utf8decoder.convert(initResponse.bodyBytes));
// print("responseJson $responseJson");
// print("requestThread:");
// print(responseJson);
// token
if (responseJson.toString().contains('invalid_token')) {
bytedeskEventBus.fire(InvalidTokenEventBus());
@ -167,6 +168,30 @@ class BytedeskThreadHttpApi extends BytedeskBaseHttpApi {
return RequestThreadResult.fromJson(responseJson);
}
//
Future<RequestThreadResult> requestWorkGroupThreadV2(String? wid) async {
//
final threadUrl = Uri.http(BytedeskConstants.host,
'/api/v2/thread/workGroup', {'wId': wid, 'client': client});
print(threadUrl);
final initResponse =
await this.httpClient.get(threadUrl, headers: getHeaders());
//json
Utf8Decoder utf8decoder = Utf8Decoder(); // fix
//string json
final responseJson =
json.decode(utf8decoder.convert(initResponse.bodyBytes));
print("requestWorkGroupThreadV2:");
print(responseJson);
// token
if (responseJson.toString().contains('invalid_token')) {
bytedeskEventBus.fire(InvalidTokenEventBus());
}
return RequestThreadResult.fromJsonV2(responseJson);
}
// 线
Future<RequestThreadResult> requestAgent(
String? wid, String? type, String? aid) async {

@ -33,11 +33,11 @@ class BytedeskUserHttpApi extends BytedeskBaseHttpApi {
// print('oauth result: $oauthResponse');
// check the status code for the result
int statusCode = oauthResponse.statusCode;
print("statusCode $statusCode");
// print("statusCode $statusCode");
// 200:
final oauthJson = jsonDecode(oauthResponse.body);
print('oauth:');
print(oauthJson);
// print('oauth:');
// print(oauthJson);
SpUtil.putBool(BytedeskConstants.isLogin, true);
SpUtil.putString(BytedeskConstants.accessToken, oauthJson['access_token']);
//

@ -0,0 +1,18 @@
import 'package:equatable/equatable.dart';
class Category extends Equatable {
//
final String? cid;
final String? name;
//
Category({this.cid, this.name}) : super();
//
static Category fromJson(dynamic json) {
return Category(
cid: json['cid'], name: json['name']);
}
//
@override
List<Object> get props => [cid!];
}

@ -5,6 +5,8 @@ import 'package:bytedesk_kefu/model/thread.dart';
import 'package:bytedesk_kefu/model/user.dart';
import 'package:bytedesk_kefu/util/bytedesk_constants.dart';
import 'package:sp_util/sp_util.dart';
import 'category.dart';
// import 'package:equatable/equatable.dart';
class Message {
@ -30,6 +32,9 @@ class Message {
//
List<Answer>? answers;
String? answersJson;
//
List<Category>? categories;
String? categoriesJson;
Message(
{this.mid,
@ -50,7 +55,9 @@ class Message {
this.currentUid,
this.client,
this.answers,
this.answersJson})
this.answersJson,
this.categories,
this.categoriesJson})
: super();
//
@ -91,6 +98,77 @@ class Message {
answersJson: json['answers'].toString());
}
//
static Message fromJsonThreadWorkGroupV2(dynamic json) {
//
List<Category> categoriesList = [];
if (json['type'] == BytedeskConstants.MESSAGE_TYPE_ROBOT_V2) {
categoriesList = json['categories'] == null
? []
: (json['categories'] as List<dynamic>)
.map((item) => Category.fromJson(item))
.toList();
}
//
return Message(
mid: json['mid'],
content: json['content'],
imageUrl: json['imageUrl'],
voiceUrl: json['voiceUrl'],
fileUrl: json['fileUrl'],
videoUrl: json['videoOrShortUrl'],
nickname: json['user']['nickname'],
avatar: json['user']['avatar'],
type: json['type'],
timestamp: json['createdAt'],
status: 'stored',
isSend: 0,
currentUid: SpUtil.getString(BytedeskConstants.uid),
client: json['client'],
thread: Thread.fromVisitorJson(json['thread']),
user: User.fromJson(json['user']),
categories: categoriesList,
categoriesJson: json['categories'].toString());
}
//
static Message fromJsonRobotQuery(dynamic json) {
//
// String? content = json['content'];
List<Answer> robotQaList = [];
if (json['type'] == BytedeskConstants.MESSAGE_TYPE_ROBOT) {
robotQaList = json['answers'] == null
? []
: (json['answers'] as List<dynamic>)
.map((item) => Answer.fromJson(item))
.toList();
// for (var i = 0; i < robotQaList.length; i++) {
// Answer answer = robotQaList[i];
// content += '\n\n' + answer.aid + ':' + answer.question;
// }
}
//
return Message(
mid: json['mid'],
content: json['content'],
imageUrl: json['imageUrl'],
voiceUrl: json['voiceUrl'],
fileUrl: json['fileUrl'],
videoUrl: json['videoOrShortUrl'],
nickname: json['user']['nickname'],
avatar: json['user']['avatar'],
type: json['type'],
timestamp: json['createdAt'],
status: 'stored',
isSend: 0,
currentUid: SpUtil.getString(BytedeskConstants.uid),
client: json['client'],
// thread: Thread.fromVisitorJson(json['thread']),
user: User.fromJson(json['user']),
answers: robotQaList,
answersJson: json['answers'].toString());
}
static Message fromJson(dynamic json) {
List<Answer> robotQaList = [];
if (json['type'] == BytedeskConstants.MESSAGE_TYPE_ROBOT) {
@ -139,7 +217,8 @@ class Message {
'isSend': isSend,
'currentUid': currentUid,
'client': client,
'answers': answersJson
'answers': answersJson,
'categories': categoriesJson
};
}

@ -1,6 +1,8 @@
import 'dart:async';
import 'dart:convert';
import 'package:bytedesk_kefu/model/answer.dart';
import 'package:bytedesk_kefu/model/category.dart';
import 'package:bytedesk_kefu/util/bytedesk_constants.dart';
import 'package:bytedesk_kefu/util/bytedesk_utils.dart';
import 'package:path/path.dart';
@ -43,6 +45,7 @@ class MessageProvider {
final String? columnCurrentUid = 'currentUid';
final String? columnClient = 'client';
final String? columnAnswers = 'answers';
final String? columnCategories = 'categories';
//
Database? database;
@ -52,19 +55,19 @@ class MessageProvider {
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'bytedesk-message-v9.db'),
join(await getDatabasesPath(), 'bytedesk-message-v10.db'),
// When the database is first created, create a table to store dogs.
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database. autoincrement
return db.execute(
"CREATE TABLE $tableMessage($columnId INTEGER PRIMARY KEY AUTOINCREMENT, " +
"$columnMid TEXT, $columnType TEXT, $columnTopic TEXT, $columnContent TEXT, $columnImageUrl TEXT, $columnVoiceUrl TEXT,$columnVideoUrl TEXT, $columnFileUrl TEXT, $columnNickname TEXT, $columnAvatar TEXT, $columnStatus TEXT, " +
"$columnIsSend INTEGER, $columnTimestamp TEXT, $columnCurrentUid TEXT, $columnClient TEXT, $columnAnswers TEXT)",
"$columnIsSend INTEGER, $columnTimestamp TEXT, $columnCurrentUid TEXT, $columnClient TEXT, $columnAnswers TEXT, $columnCategories TEXT)",
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 9,
version: 10,
);
// database path:/Users/ningjinpeng/Library/Developer/CoreSimulator/Devices/715CBA02-A602-4DE1-8C57-75A64B53BF03/data/Containers/Data/Application/8F46273D-9492-4C42-A618-4DF3815562BA/Documents/bytedesk-message-v9.db
// print('database path:' + database!.path);
@ -104,7 +107,9 @@ class MessageProvider {
columnNickname!,
columnAvatar!,
columnIsSend!,
columnClient!
columnClient!,
columnAnswers!,
columnCategories!
],
where: '$columnTopic = ? and $columnCurrentUid = ?',
whereArgs: [topic, currentUid],
@ -118,13 +123,23 @@ class MessageProvider {
return List.generate(maps.length, (i) {
//
List<Answer> robotQaList = [];
if (maps[i]['type'] == BytedeskConstants.MESSAGE_TYPE_ROBOT) {
robotQaList = maps[i]['answers'] == null
? []
: (maps[i]['answers'] as List<dynamic>)
.map((item) => Answer.fromJson(item))
.toList();
}
// FIXME:
// if (maps[i]['type'] == BytedeskConstants.MESSAGE_TYPE_ROBOT) {
// robotQaList = maps[i][columnAnswers] == null
// ? []
// : (json.decode(maps[i][columnAnswers]) as List<dynamic>)
// .map((item) => Answer.fromJson(item))
// .toList();
// }
List<Category> categoriesList = [];
// FIXME:
// if (maps[i]['type'] == BytedeskConstants.MESSAGE_TYPE_ROBOT_V2) {
// categoriesList = maps[i][columnCategories] == null
// ? []
// : (json.decode(maps[i][columnCategories]) as List<dynamic>)
// .map((item) => Category.fromJson(item))
// .toList();
// }
// print('4');
return Message(
mid: maps[i]['mid'],
@ -141,7 +156,8 @@ class MessageProvider {
avatar: maps[i]['avatar'],
isSend: maps[i]['isSend'],
client: maps[i]['client'],
answers: robotQaList);
answers: robotQaList,
categories: categoriesList);
});
}

@ -15,15 +15,15 @@ class RequestAnswerResult extends Equatable {
return RequestAnswerResult(
message: json["message"],
statusCode: json["status_code"],
query: Message.fromJsonThread(json["data"]["query"]),
anwser: Message.fromJsonThread(json["data"]["reply"]));
query: Message.fromJsonRobotQuery(json["data"]["query"]),
anwser: Message.fromJsonRobotQuery(json["data"]["reply"]));
}
static RequestAnswerResult fromRateJson(dynamic json) {
return RequestAnswerResult(
message: json["message"],
statusCode: json["status_code"],
anwser: Message.fromJsonThread(json["data"]));
anwser: Message.fromJsonRobotQuery(json["data"]));
}
@override

@ -0,0 +1,24 @@
import 'package:equatable/equatable.dart';
import 'message.dart';
class RequestCategoryResult extends Equatable {
final String? message;
final int? statusCode;
//
final Message? query;
final Message? anwser;
RequestCategoryResult({this.message, this.statusCode, this.query, this.anwser})
: super();
static RequestCategoryResult fromJson(dynamic json) {
return RequestCategoryResult(
message: json["message"],
statusCode: json["status_code"],
query: Message.fromJsonRobotQuery(json["data"]["query"]),
anwser: Message.fromJsonRobotQuery(json["data"]["reply"]));
}
@override
List<Object> get props => [this.statusCode!];
}

@ -16,6 +16,13 @@ class RequestThreadResult extends Equatable {
msg: Message.fromJsonThread(json["data"]));
}
static RequestThreadResult fromJsonV2(dynamic json) {
return RequestThreadResult(
message: json["message"],
statusCode: json["status_code"],
msg: Message.fromJsonThreadWorkGroupV2(json["data"]));
}
@override
List<Object> get props => [this.msg!.mid!];
}

@ -47,7 +47,8 @@ class Thread extends Equatable {
this.nodisturbVisitor,
this.unread,
this.unreadVisitor,
this.client});
this.client,
this.currentUid});
static Thread fromWorkGroupJson(dynamic json) {
return Thread(
@ -61,6 +62,7 @@ class Thread extends Equatable {
unreadCount: json['unreadCount'],
type: json['type'],
current: json['current'],
client: json['client'],
top: json['top'],
topVisitor: json['topVisitor'],
nodisturb: json['nodisturb'],
@ -82,6 +84,7 @@ class Thread extends Equatable {
unreadCount: json['unreadCount'],
type: json['type'],
current: json['current'],
client: json['client'],
top: json['top'],
topVisitor: json['topVisitor'],
nodisturb: json['nodisturb'],
@ -100,6 +103,7 @@ class Thread extends Equatable {
unreadCount: json['unreadCount'],
type: json['type'],
current: json['current'],
client: json['client'],
top: json['top'],
topVisitor: json['topVisitor'],
nodisturb: json['nodisturb'],
@ -118,6 +122,7 @@ class Thread extends Equatable {
unreadCount: json['unreadCount'],
type: json['type'],
current: json['current'],
client: json['client'],
top: json['top'],
topVisitor: json['topVisitor'],
nodisturb: json['nodisturb'],
@ -137,6 +142,7 @@ class Thread extends Equatable {
unreadCount: json['unreadCount'],
type: json['type'],
current: json['current'],
client: json['client'],
top: json['top'],
topVisitor: json['topVisitor'],
nodisturb: json['nodisturb'],
@ -157,6 +163,7 @@ class Thread extends Equatable {
unreadCount: json['unreadCount'],
type: json['type'],
current: json['current'],
client: json['client'],
top: json['top'],
topVisitor: json['topVisitor'],
nodisturb: json['nodisturb'],
@ -177,6 +184,7 @@ class Thread extends Equatable {
unreadCount: json['unreadCount'],
type: json['type'],
current: json['current'],
client: json['client'],
top: json['top'],
topVisitor: json['topVisitor'],
nodisturb: json['nodisturb'],
@ -196,6 +204,7 @@ class Thread extends Equatable {
unreadCount: json['unreadCount'],
type: json['type'],
current: json['current'],
client: json['client'],
top: json['top'],
topVisitor: json['topVisitor'],
nodisturb: json['nodisturb'],
@ -215,6 +224,7 @@ class Thread extends Equatable {
unreadCount: json['unreadCount'],
type: json['type'],
current: json['current'],
client: json['client'],
top: json['top'],
topVisitor: json['topVisitor'],
nodisturb: json['nodisturb'],
@ -264,6 +274,7 @@ class Thread extends Equatable {
unreadCount = map['unreadCount'];
type = map['type'];
current = map['current'];
client = map['client'];
top = map['top'];
topVisitor = map['topVisitor'];
nodisturb = map['nodisturb'];

@ -45,7 +45,7 @@ class BytedeskMqtt {
factory BytedeskMqtt() {
return _singleton;
}
BytedeskMqtt._internal() {}
BytedeskMqtt._internal();
void connect() async {
// eventbus广...
@ -670,7 +670,8 @@ class BytedeskMqtt {
currentThread, extraParam);
}
void publish(String content, String type, Thread currentThread, ExtraParam? extraParam) {
void publish(String content, String type, Thread currentThread,
ExtraParam? extraParam) {
// if (currentThread == null) {
// print('连接客服失败,请退出页面重新进入。注意: 请在App启动的时候调用init接口');
// Fluttertoast.showToast(msg: '连接客服失败,请退出页面重新进入');
@ -689,6 +690,7 @@ class BytedeskMqtt {
thread.topic = currentThread.topic!;
thread.nickname = currentThread.nickname!;
thread.avatar = currentThread.avatar!;
thread.client = currentThread.client!;
thread.timestamp = BytedeskUtils.formatedDateNow(); //
thread.unreadCount = 0;
var extra = {'top': false, 'undisturb': false};

@ -2,6 +2,7 @@ import 'package:bytedesk_kefu/http/bytedesk_message_api.dart';
import 'package:bytedesk_kefu/model/jsonResult.dart';
import 'package:bytedesk_kefu/model/message.dart';
import 'package:bytedesk_kefu/model/requestAnswer.dart';
import 'package:bytedesk_kefu/model/requestCategory.dart';
import 'package:bytedesk_kefu/model/uploadJsonResult.dart';
class MessageRepository {
@ -28,13 +29,17 @@ class MessageRepository {
return await bytedeskHttpApi.loadChannelMessages(cid, page, size);
}
Future<RequestAnswerResult> queryAnswer(String? tid, String? aid) async {
return await bytedeskHttpApi.queryAnswer(tid, aid);
Future<RequestAnswerResult> queryAnswer(String? tid, String? aid, String? mid) async {
return await bytedeskHttpApi.queryAnswer2(tid, aid, mid);
}
Future<RequestAnswerResult> messageAnswer(
String? type, String? wid, String? aid, String? content) async {
return await bytedeskHttpApi.messageAnswer(type, wid, aid, content);
Future<RequestCategoryResult> queryCategory(
String? tid, String? cid) async {
return await bytedeskHttpApi.queryCategory(tid, cid);
}
Future<RequestAnswerResult> messageAnswer(String? wid, String? content) async {
return await bytedeskHttpApi.messageAnswer(wid, content);
}
Future<RequestAnswerResult> rateAnswer(

@ -28,7 +28,10 @@ class ThreadRepository {
}
Future<RequestThreadResult> requestThread(
String? wid, String? type, String? aid) async {
String? wid, String? type, String? aid, bool? isV2Robot) async {
if (isV2Robot!) {
return await bytedeskHttpApi.requestWorkGroupThreadV2(wid);
}
return await bytedeskHttpApi.requestThread(wid, type, aid);
}

@ -500,9 +500,9 @@ class _ChatIMPageState extends State<ChatIMPage>
//
BlocProvider.of<MessageBloc>(context)
..add(MessageAnswerEvent(
type: widget.type,
// type: widget.type,
wid: widget.wid,
aid: widget.aid,
// aid: widget.aid,
content: text));
} else if (_bdMqtt.isConnected()) {
if (_currentThread == null) {

@ -40,6 +40,7 @@ class ChatKFPage extends StatefulWidget {
final String? title;
final String? custom;
final String? postscript;
final bool? isV2Robot;
//
final bool? isThread;
final Thread? thread;
@ -53,6 +54,7 @@ class ChatKFPage extends StatefulWidget {
this.title,
this.custom,
this.postscript,
this.isV2Robot,
this.isThread,
this.thread,
this.customCallback})
@ -89,6 +91,7 @@ class _ChatKFPageState extends State<ChatKFPage>
String? _currentAvatar = SpUtil.getString(BytedeskConstants.avatar);
//
Thread? _currentThread;
User? _robotUser;
//
bool _isRobot = false;
//
@ -189,11 +192,8 @@ class _ChatKFPageState extends State<ChatKFPage>
_currentThread = state.threadResult.msg!.thread;
_isRequestingThread = false;
});
//
// _messageProvider.insert(state.threadResult.msg!);
// TODO:
_getMessages(_page, _size);
// _appendMessage(state.threadResult.msg!);
//
if (state.threadResult.statusCode == 200 ||
state.threadResult.statusCode == 201) {
@ -286,6 +286,7 @@ class _ChatKFPageState extends State<ChatKFPage>
// TODO:
setState(() {
_isRobot = true;
_robotUser = state.threadResult.msg!.user;
_currentThread = state.threadResult.msg!.thread;
});
//
@ -347,20 +348,25 @@ class _ChatKFPageState extends State<ChatKFPage>
} else if (state is UploadVideoSuccess) {
_bdMqtt.sendVideoMessage(
state.uploadJsonResult.url!, _currentThread!);
// TODO: http rest sendImageRest
} else if (state is QueryAnswerSuccess) {
Message queryMessage = state.query!;
queryMessage.isSend = 1;
_messageProvider.insert(queryMessage);
_appendMessage(queryMessage);
//
//
// Message queryMessage = state.query!;
// queryMessage.isSend = 1;
// _messageProvider.insert(queryMessage);
// _appendMessage(queryMessage);
// //
// _messageProvider.insert(state.answer!);
// _appendMessage(state.answer!);
} else if (state is QueryCategorySuccess) {
_messageProvider.insert(state.answer!);
_appendMessage(state.answer!);
} else if (state is MessageAnswerSuccess) {
Message queryMessage = state.query!;
queryMessage.isSend = 1;
_messageProvider.insert(queryMessage);
_appendMessage(queryMessage);
// Message queryMessage = state.query!;
// queryMessage.isSend = 1;
// _messageProvider.insert(queryMessage);
// _appendMessage(queryMessage);
//
if (state.query!.content!.contains('人工')) {
BlocProvider.of<ThreadBloc>(context)
@ -375,7 +381,7 @@ class _ChatKFPageState extends State<ChatKFPage>
} else if (state is RateAnswerSuccess) {
// TODO:
} else if (state is LoadHistoryMessageSuccess) {
print('LoadHistoryMessageSuccess');
// print('LoadHistoryMessageSuccess');
//
for (var i = 0; i < state.messageList!.length; i++) {
Message message = state.messageList![i];
@ -490,7 +496,7 @@ class _ChatKFPageState extends State<ChatKFPage>
if (_debounce?.isActive ?? false) _debounce!.cancel();
// 500
_debounce = Timer(const Duration(milliseconds: 500), () {
print('send preview $value');
// print('send preview $value');
//
if (value.trim().length > 0) {
_bdMqtt.sendPreviewMessage(value, _currentThread!);
@ -530,12 +536,14 @@ class _ChatKFPageState extends State<ChatKFPage>
}
//
if (_isRobot) {
//
appendQueryMessage(text);
//
BlocProvider.of<MessageBloc>(context)
..add(MessageAnswerEvent(
type: widget.type,
// type: widget.type,
wid: widget.wid,
aid: widget.aid,
// aid: widget.aid,
content: text));
} else if (_bdMqtt.isConnected()) {
if (_currentThread == null) {
@ -545,7 +553,7 @@ class _ChatKFPageState extends State<ChatKFPage>
//
_bdMqtt.sendTextMessage(text, _currentThread!);
} else {
print('长连接断开的情况下调用rest接口');
// print('长连接断开的情况下调用rest接口');
sendTextMessageRest(text);
}
}
@ -553,7 +561,7 @@ class _ChatKFPageState extends State<ChatKFPage>
// http rest
void sendTextMessageRest(String text) {
//
String? mid = BytedeskUuid.generateV4();
String? mid = BytedeskUuid.uuid();
String? timestamp = BytedeskUtils.formatedDateNow();
String? client = BytedeskUtils.getClient();
String? type = BytedeskConstants.MESSAGE_TYPE_TEXT;
@ -564,6 +572,7 @@ class _ChatKFPageState extends State<ChatKFPage>
"client": client,
"version": "1",
"type": type,
"status": "sending",
"user": {
"uid": this._currentUid,
"nickname": this._currentNickname,
@ -606,12 +615,14 @@ class _ChatKFPageState extends State<ChatKFPage>
message.content = text;
//
_messageProvider.insert(message);
//
pushToMessageArray(message, true);
}
// http rest
void sendImageMessageRest(String imageUrl) {
//
String? mid = BytedeskUuid.generateV4();
String? mid = BytedeskUuid.uuid();
String? timestamp = BytedeskUtils.formatedDateNow();
String? client = BytedeskUtils.getClient();
String? type = BytedeskConstants.MESSAGE_TYPE_IMAGE;
@ -622,6 +633,7 @@ class _ChatKFPageState extends State<ChatKFPage>
"client": client,
"version": "1",
"type": type,
"status": "sending",
"user": {
"uid": this._currentUid,
"nickname": this._currentNickname,
@ -664,6 +676,73 @@ class _ChatKFPageState extends State<ChatKFPage>
message.imageUrl = imageUrl;
//
_messageProvider.insert(message);
//
pushToMessageArray(message, true);
}
void appendQueryMessage(String content) {
//
String? mid = BytedeskUuid.uuid();
String? timestamp = BytedeskUtils.formatedDateNow();
String? client = BytedeskUtils.getClient();
String? type = BytedeskConstants.MESSAGE_TYPE_ROBOT;
//
//
Message message = new Message();
message.mid = mid;
message.type = type;
message.timestamp = timestamp;
message.client = client;
message.nickname = _currentNickname;
message.avatar = _currentAvatar;
message.topic = this._currentThread!.topic;
message.status = BytedeskConstants.MESSAGE_STATUS_STORED;
message.isSend = 1;
message.currentUid = this._currentUid;
message.answersJson = '';
message.thread = this._currentThread;
message.user = User(
uid: this._currentUid,
avatar: this._currentAvatar,
nickname: this._currentNickname);
//
message.content = content;
//
_messageProvider.insert(message);
//
pushToMessageArray(message, true);
}
void appendReplyMessage(String aid, String mid, String content) {
//
String? timestamp = BytedeskUtils.formatedDateNow();
String? client = BytedeskUtils.getClient();
String? type = BytedeskConstants.MESSAGE_TYPE_ROBOT_RESULT;
//
// R
Message message = new Message();
message.mid = mid;
message.type = type;
message.timestamp = timestamp;
message.client = client;
message.nickname = _robotUser!.nickname;
message.avatar = _robotUser!.avatar;
message.topic = this._currentThread!.topic;
message.status = BytedeskConstants.MESSAGE_STATUS_STORED;
message.isSend = 0;
message.currentUid = this._currentUid;
message.answersJson = '';
message.thread = this._currentThread;
message.user = User(
uid: this._robotUser!.uid,
avatar: this._robotUser!.avatar,
nickname: this._robotUser!.nickname);
//
message.content = content;
//
_messageProvider.insert(message);
//
pushToMessageArray(message, true);
}
//
@ -731,18 +810,19 @@ class _ChatKFPageState extends State<ChatKFPage>
event.message.mid!, event.message.thread!);
}
//
if (this.mounted) {
//
MessageWidget messageWidget = new MessageWidget(
message: event.message,
customCallback: widget.customCallback,
animationController: new AnimationController(
vsync: this, duration: Duration(milliseconds: 500)));
setState(() {
_messages.insert(0, messageWidget);
// _messages.add(messageWidget);
});
}
pushToMessageArray(event.message, true);
// if (this.mounted) {
// //
// MessageWidget messageWidget = new MessageWidget(
// message: event.message,
// customCallback: widget.customCallback,
// animationController: new AnimationController(
// vsync: this, duration: Duration(milliseconds: 500)));
// setState(() {
// _messages.insert(0, messageWidget);
// // _messages.add(messageWidget);
// });
// }
});
//
bytedeskEventBus.on<DeleteMessageEventBus>().listen((event) {
@ -761,11 +841,26 @@ class _ChatKFPageState extends State<ChatKFPage>
if (this.mounted) {
// print('aid ${event.aid}, question ${event.question}, answer ${event.answer}');
//
appendQueryMessage(event.question);
//
String? mid = BytedeskUuid.uuid();
appendReplyMessage(event.aid, mid, event.answer);
//
BlocProvider.of<MessageBloc>(context)
..add(QueryAnswerEvent(
tid: _currentThread!.tid,
aid: event.aid,
));
tid: _currentThread!.tid, aid: event.aid, mid: mid));
}
});
//
bytedeskEventBus.on<QueryCategoryEventBus>().listen((event) {
if (this.mounted) {
print('cid ${event.cid}, name ${event.name}');
//
appendQueryMessage(event.name);
//
BlocProvider.of<MessageBloc>(context)
..add(QueryCategoryEvent(
tid: _currentThread!.tid, cid: event.cid));
}
});
//
@ -971,30 +1066,45 @@ class _ChatKFPageState extends State<ChatKFPage>
BytedeskConstants.MESSAGE_TYPE_NOTIFICATION_THREAD_REENTRY) {
continue;
} else {
pushToMessageArray(message);
pushToMessageArray(message, false);
}
}
} else {
pushToMessageArray(message);
pushToMessageArray(message, false);
}
}
//
_page += 1;
}
void pushToMessageArray(Message message) {
void pushToMessageArray(Message message, bool append) {
if (this.mounted) {
MessageWidget messageWidget = new MessageWidget(
message: message,
customCallback: widget.customCallback,
animationController: new AnimationController(
vsync: this, duration: Duration(milliseconds: 500)));
setState(() {
_messages.add(messageWidget);
_messages.sort((a, b) {
return b.message!.timestamp!.compareTo(a.message!.timestamp!);
bool contains = false;
for (var i = 0; i < _messages.length; i++) {
Message? element = _messages[i].message;
if (element!.mid == message.mid) {
contains = true;
//
_messageProvider.update(element.mid, message.status);
}
}
if (!contains) {
MessageWidget messageWidget = new MessageWidget(
message: message,
customCallback: widget.customCallback,
animationController: new AnimationController(
vsync: this, duration: Duration(milliseconds: 500)));
setState(() {
if (append) {
_messages.insert(0, messageWidget);
} else {
_messages.add(messageWidget);
_messages.sort((a, b) {
return b.message!.timestamp!.compareTo(a.message!.timestamp!);
});
}
});
});
}
}
if (message.status != BytedeskConstants.MESSAGE_STATUS_READ) {
//
@ -1005,29 +1115,8 @@ class _ChatKFPageState extends State<ChatKFPage>
}
Future<Null> _appendMessage(Message message) async {
// print('append:' + message.mid! + 'content:' + message.content!);
bool contains = false;
for (var i = 0; i < _messages.length; i++) {
Message? element = _messages[i].message;
if (element!.mid == message.mid) {
contains = true;
//
_messageProvider.update(element.mid, message.status);
}
}
if (!contains) {
// _messageProvider.insert(message); //
MessageWidget messageWidget = new MessageWidget(
message: message,
customCallback: widget.customCallback,
animationController: new AnimationController(
vsync: this, duration: Duration(milliseconds: 500)));
if (this.mounted) {
setState(() {
_messages.insert(0, messageWidget);
});
}
}
print('append:' + message.mid! + 'content:' + message.content!);
pushToMessageArray(message, true);
}
void scrollToBottom() {

@ -469,9 +469,9 @@ class _ChatLSPageState extends State<ChatLSPage>
if (_isRobot) {
BlocProvider.of<MessageBloc>(context)
..add(MessageAnswerEvent(
type: widget.type,
// type: widget.type,
wid: widget.wid,
aid: widget.aid,
// aid: widget.aid,
content: text));
} else {
if (_currentThread == null) {

@ -12,6 +12,7 @@ class ChatKFProvider extends StatelessWidget {
final String? title;
final String? custom;
final String? postscript;
final bool? isV2Robot;
final ValueSetter<String>? customCallback;
//
const ChatKFProvider(
@ -22,6 +23,7 @@ class ChatKFProvider extends StatelessWidget {
this.title,
this.custom,
this.postscript,
this.isV2Robot,
this.customCallback})
: super(key: key);
//
@ -32,7 +34,7 @@ class ChatKFProvider extends StatelessWidget {
providers: [
BlocProvider<ThreadBloc>(
create: (BuildContext context) => ThreadBloc()
..add(RequestThreadEvent(wid: wid, aid: aid, type: type)),
..add(RequestThreadEvent(wid: wid, aid: aid, type: type, isV2Robot: isV2Robot)),
),
BlocProvider<MessageBloc>(
create: (BuildContext context) => MessageBloc(),
@ -45,6 +47,7 @@ class ChatKFProvider extends StatelessWidget {
title: title,
custom: custom,
postscript: postscript,
isV2Robot: isV2Robot,
isThread: false,
customCallback: customCallback),
);

@ -41,12 +41,16 @@ class MessageWidget extends StatelessWidget {
double tWidth = MediaQuery.of(context).size.width - 160;
// FIXME:
String status = '';
if (message!.status == BytedeskConstants.MESSAGE_STATUS_STORED) {
// status = '发送成功';
if (message!.status == BytedeskConstants.MESSAGE_STATUS_SENDING) {
status = '发送中';
} else if (message!.status == BytedeskConstants.MESSAGE_STATUS_STORED) {
status = ''; //
} else if (message!.status == BytedeskConstants.MESSAGE_STATUS_RECEIVED) {
status = '送达';
} else if (message!.status == BytedeskConstants.MESSAGE_STATUS_READ) {
status = '已读';
} else if (message!.status == BytedeskConstants.MESSAGE_STATUS_ERROR) {
status = '失败';
}
return Container(
margin: EdgeInsets.only(top: 8.0, left: 8.0),
@ -450,43 +454,46 @@ class MessageWidget extends StatelessWidget {
// softWrap: true,
// style: TextStyle(color: Colors.black, fontSize: 16.0),
// ),
Html(
data: message.content ?? '',
onLinkTap: (url, _, __, ___) {
// url
BytedeskKefu.openWebView(context, url!, '网页');
},
onImageTap: (src, _, __, ___) {
//
// print("open image $src");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhotoViewWrapper(
imageUrl: message.imageUrl!,
imageProvider: NetworkImage(
src!,
),
loadingBuilder: (context, event) {
if (event == null) {
return const Center(
child: Text("Loading"),
Visibility(
visible: message.content != null && message.content!.length > 0,
child: Html(
data: message.content ?? '',
onLinkTap: (url, _, __, ___) {
// url
BytedeskKefu.openWebView(context, url!, '网页');
},
onImageTap: (src, _, __, ___) {
//
// print("open image $src");
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhotoViewWrapper(
imageUrl: message.imageUrl!,
imageProvider: NetworkImage(
src!,
),
loadingBuilder: (context, event) {
if (event == null) {
return const Center(
child: Text("Loading"),
);
}
final value = event.cumulativeBytesLoaded /
event.expectedTotalBytes!;
final percentage = (100 * value).floor();
return Center(
child: Text("$percentage%"),
);
}
final value = event.cumulativeBytesLoaded /
event.expectedTotalBytes!;
final percentage = (100 * value).floor();
return Center(
child: Text("$percentage%"),
);
},
},
),
),
),
);
},
onImageError: (exception, stackTrace) {
print(exception);
},
);
},
onImageError: (exception, stackTrace) {
print(exception);
},
),
),
Visibility(
visible: message.answers != null && message.answers!.length > 0,
@ -549,6 +556,88 @@ class MessageWidget extends StatelessWidget {
)
],
);
} else if (message.type == BytedeskConstants.MESSAGE_TYPE_ROBOT_V2) {
return Column(
children: <Widget>[
Text(
message.content ?? '',
textAlign: TextAlign.left,
softWrap: true,
style: TextStyle(
color: Colors.black,
fontSize: 16.0,
),
),
Visibility(
visible: message.categories != null && message.categories!.length > 0,
// visible: false,
child: Container(
// color: Colors.black,
child: ListView.builder(
// shrinkWraptrue
shrinkWrap: true,
// ListView使ScrollView
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.all(0),
itemCount:
message.categories == null ? 0 : message.categories!.length,
itemBuilder: (_, index) {
//
var category = message.categories![index];
// return Text(answer.question!);
return DecoratedBox(
decoration: BoxDecoration(
border: Border(
bottom: Divider.createBorderSide(context, width: 0.8),
)),
child: Container(
margin: EdgeInsets.only(top: 6, left: 8, bottom: 8),
// color: Colors.pink,
child: InkWell(
child: Text(
category.name!,
style: TextStyle(color: Colors.blue),
),
onTap: () => {
// print(category.name),
bytedeskEventBus.fire(QueryCategoryEventBus(
category.cid!,
category.name!))
}),
));
},
),
)),
Container(
margin: EdgeInsets.only(left: 10, top: 10),
child: Row(
children: [
Text('没有找到答案?'),
GestureDetector(
child: Text(
'人工客服',
style: TextStyle(color: Theme.of(context).primaryColor),
),
onTap: () {
print('请求人工客服');
bytedeskEventBus.fire(RequestAgentThreadEventBus());
},
)
],
),
)
],
);
} else if (message.type == BytedeskConstants.MESSAGE_TYPE_ROBOT_RESULT) {
return Text(
message.content ?? '',
textAlign: TextAlign.left,
softWrap: true,
style: TextStyle(
color: Colors.black,
fontSize: 16.0,
),
);
} else if (message.type == BytedeskConstants.MESSAGE_TYPE_COMMODITY) {
// , TODO: add send button
final commodityJson = json.decode(message.content!);

@ -6,7 +6,7 @@ import 'package:bytedesk_kefu/ui/widget/loading_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:flutter_easyrefresh/phoenix_header.dart';
// import 'package:flutter_easyrefresh/phoenix_header.dart';
class FeedbackPage extends StatefulWidget {
final String? uid;

@ -45,7 +45,7 @@ class BytedeskConstants {
// static const String httpBaseUrliOS = 'http://' + mqttHost + ':8000';
// static const String httpUploadUrl = 'http://' + mqttHost + ':8000';
// static const String host = mqttHost + ':8000';
// static const String mqttHost = '192.168.0.102';
// static const String mqttHost = '192.168.0.104';
// 线
static const bool isDebug = false; // false;
@ -149,6 +149,10 @@ class BytedeskConstants {
static const String MESSAGE_TYPE_EVENT = 'event';
//
static const String MESSAGE_TYPE_ROBOT = 'robot';
//
static const String MESSAGE_TYPE_ROBOT_V2 = 'robotv2';
//
static const String MESSAGE_TYPE_ROBOT_RESULT = 'robot_result';
//
static const String MESSAGE_TYPE_QUESTIONNAIRE = 'questionnaire';
// 便

@ -46,6 +46,12 @@ class QueryAnswerEventBus {
QueryAnswerEventBus(this.aid, this.question, this.answer);
}
class QueryCategoryEventBus {
String cid;
String name;
QueryCategoryEventBus(this.cid, this.name);
}
class RequestAgentThreadEventBus {
RequestAgentThreadEventBus();
}

@ -1,3 +1,5 @@
// ignore_for_file: unnecessary_null_comparison
import 'dart:io';
import 'dart:math';
@ -219,7 +221,7 @@ class BytedeskUtils {
}
static String getDateStr(DateTime date) {
if (date == null || date.toString() == null) {
if (date.toString() == null) {
return "";
} else if (date.toString().length < 10) {
return date.toString();

@ -1,6 +1,6 @@
name: bytedesk_kefu
description: the best app chat sdk in china, you can chat with the agent freely at anytime. the agent can chat with the visitor by web/pc/mac/ios/android client.
version: 1.2.6
version: 1.2.8
homepage: https://www.weikefu.net
environment:

Loading…
Cancel
Save