|
|
import 'dart:async';
|
|
|
// import 'dart:io';
|
|
|
|
|
|
import 'package:bytedesk_kefu/http/bytedesk_device_api.dart';
|
|
|
import 'package:bytedesk_kefu/http/bytedesk_thread_api.dart';
|
|
|
import 'package:bytedesk_kefu/model/app.dart';
|
|
|
import 'package:bytedesk_kefu/model/jsonResult.dart';
|
|
|
import 'package:bytedesk_kefu/model/thread.dart';
|
|
|
import 'package:bytedesk_kefu/model/userJsonResult.dart';
|
|
|
import 'package:bytedesk_kefu/model/wechatResult.dart';
|
|
|
import 'package:bytedesk_kefu/ui/channel/provider/channel_provider.dart';
|
|
|
import 'package:bytedesk_kefu/ui/chat/page/chat_webview_page.dart';
|
|
|
import 'package:bytedesk_kefu/ui/chat/provider/chat_im_provider.dart';
|
|
|
import 'package:bytedesk_kefu/ui/chat/provider/chat_thread_provider.dart';
|
|
|
import 'package:bytedesk_kefu/ui/faq/provider/help_provider.dart';
|
|
|
import 'package:bytedesk_kefu/ui/feedback/provider/feedback_provider.dart';
|
|
|
import 'package:bytedesk_kefu/ui/leavemsg/provider/leavemsg_provider.dart';
|
|
|
import 'package:bytedesk_kefu/ui/ticket/provider/ticket_provider.dart';
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
|
|
import 'package:bytedesk_kefu/http/bytedesk_user_api.dart';
|
|
|
import 'package:bytedesk_kefu/ui/chat/provider/chat_kf_provider.dart';
|
|
|
import 'package:bytedesk_kefu/util/bytedesk_utils.dart';
|
|
|
import 'package:bytedesk_kefu/util/bytedesk_constants.dart';
|
|
|
import 'package:sp_util/sp_util.dart';
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'model/user.dart';
|
|
|
|
|
|
class BytedeskKefu {
|
|
|
//
|
|
|
static const MethodChannel _channel = const MethodChannel('bytedesk_kefu');
|
|
|
static Future<String> get platformVersion async {
|
|
|
final String version = await _channel.invokeMethod('getPlatformVersion');
|
|
|
return version;
|
|
|
}
|
|
|
|
|
|
// 下面为 自定义接口
|
|
|
static void init(String appKey, String subDomain) async {
|
|
|
anonymousLogin(appKey, subDomain);
|
|
|
}
|
|
|
|
|
|
// 支持自定义用户名,方便跟APP业务系统对接
|
|
|
static void initWithUsername(
|
|
|
String username, String appKey, String subDomain) async {
|
|
|
initWithUsernameAndNickname(username, "", appKey, subDomain);
|
|
|
}
|
|
|
|
|
|
static void initWithUsernameAndNickname(
|
|
|
String username, String nickname, String appKey, String subDomain) async {
|
|
|
initWithUsernameAndNicknameAndAvatar(
|
|
|
username, nickname, "", appKey, subDomain);
|
|
|
}
|
|
|
|
|
|
static void initWithUsernameAndNicknameAndAvatar(String username,
|
|
|
String nickname, String avatar, String appKey, String subDomain) async {
|
|
|
/// sp初始化
|
|
|
await SpUtil.getInstance();
|
|
|
// 首先检测是否是第一次,如果是第一此启动
|
|
|
String? spusername = SpUtil.getString(BytedeskConstants.username);
|
|
|
String? sppassword = SpUtil.getString(BytedeskConstants.password);
|
|
|
if (spusername!.isNotEmpty) {
|
|
|
// 登录
|
|
|
userLogin(spusername, sppassword!, appKey, subDomain);
|
|
|
} else {
|
|
|
// 调用注册接口
|
|
|
// 默认密码同用户名
|
|
|
String password = username;
|
|
|
await BytedeskUserHttpApi()
|
|
|
.registerUser(username, nickname, password, avatar, subDomain);
|
|
|
// 注册成功之后,调用登录接口
|
|
|
String usernameCompose = username + "@" + subDomain;
|
|
|
userLogin(usernameCompose, password, appKey, subDomain);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 访客匿名登录接口
|
|
|
static void anonymousLogin(String appKey, String subDomain) async {
|
|
|
/// sp初始化
|
|
|
await SpUtil.getInstance();
|
|
|
// 首先检测是否是第一次,如果是第一此启动
|
|
|
String? username = SpUtil.getString(BytedeskConstants.username);
|
|
|
String? unionid = SpUtil.getString(BytedeskConstants.unionid);
|
|
|
if (username!.isEmpty && unionid!.isEmpty) {
|
|
|
// 第一此启动, 则调用注册接口,否则调用登录接口
|
|
|
User user = await BytedeskUserHttpApi().registerAnonymous(subDomain);
|
|
|
visitorLogin(user.username!, appKey, subDomain);
|
|
|
} else if (username.isNotEmpty) {
|
|
|
// 用户名登录,非微信登录
|
|
|
visitorLogin(username, appKey, subDomain);
|
|
|
} else if (unionid!.isNotEmpty) {
|
|
|
// 微信登录
|
|
|
unionidOAuth(unionid);
|
|
|
} else {
|
|
|
// 验证码登录其他账号,非当前匿名登录账户
|
|
|
otherOAuth();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 匿名访客登录
|
|
|
static void visitorLogin(String username, String appkey, String subDomain) {
|
|
|
String password = username;
|
|
|
login(
|
|
|
username, password, appkey, subDomain, BytedeskConstants.ROLE_VISITOR);
|
|
|
}
|
|
|
|
|
|
// 开发者自定义用户名登录接口
|
|
|
static void userLogin(
|
|
|
String username, String password, String appkey, String subDomain) {
|
|
|
login(
|
|
|
username, password, appkey, subDomain, BytedeskConstants.ROLE_VISITOR);
|
|
|
}
|
|
|
|
|
|
// 用户名登录
|
|
|
static void login(String username, String password, String appkey,
|
|
|
String subDomain, String role) async {
|
|
|
//
|
|
|
SpUtil.putString(BytedeskConstants.role, role);
|
|
|
if (role == BytedeskConstants.ROLE_ADMIN) {
|
|
|
if (!username.contains("@")) {
|
|
|
username = username + "@" + subDomain;
|
|
|
}
|
|
|
}
|
|
|
await BytedeskUserHttpApi().oauth(username, password);
|
|
|
// 登录成功之后,建立长连接
|
|
|
connect();
|
|
|
// if (role == BytedeskConstants.ROLE_ADMIN) {
|
|
|
// // TODO: 如果是客服账号,加载个人信息
|
|
|
// }
|
|
|
// 上传设备信息
|
|
|
await BytedeskDeviceHttpApi().setDeviceInfo();
|
|
|
}
|
|
|
|
|
|
// 微信unionid登录
|
|
|
static void unionidOAuth(String unionid) async {
|
|
|
//
|
|
|
await BytedeskUserHttpApi().unionIdOAuth(unionid);
|
|
|
// 登录成功之后,建立长连接
|
|
|
connect();
|
|
|
// 上传设备信息
|
|
|
await BytedeskDeviceHttpApi().setDeviceInfo();
|
|
|
}
|
|
|
|
|
|
// 直接建立长连接
|
|
|
static void otherOAuth() async {
|
|
|
// 登录成功之后,建立长连接
|
|
|
connect();
|
|
|
// 上传设备信息
|
|
|
await BytedeskDeviceHttpApi().setDeviceInfo();
|
|
|
}
|
|
|
|
|
|
// 建立长连接
|
|
|
static bool connect() {
|
|
|
return BytedeskUtils.mqttConnect();
|
|
|
}
|
|
|
|
|
|
// 重连
|
|
|
static bool reconnect() {
|
|
|
return BytedeskUtils.mqttReConnect();
|
|
|
}
|
|
|
|
|
|
// 断开
|
|
|
static void disconnect() {
|
|
|
BytedeskUtils.mqttDisconnect();
|
|
|
}
|
|
|
|
|
|
// 判断长连接状态
|
|
|
static bool isConnected() {
|
|
|
return BytedeskUtils.isMqttConnected();
|
|
|
}
|
|
|
|
|
|
// 技能组客服会话
|
|
|
static void startWorkGroupChat(
|
|
|
BuildContext context, String wid, String title) {
|
|
|
startChatDefault(
|
|
|
context, wid, BytedeskConstants.CHAT_TYPE_WORKGROUP, title);
|
|
|
}
|
|
|
|
|
|
// 发送附言消息
|
|
|
static void startWorkGroupChatPostscript(
|
|
|
BuildContext context, String wid, String title, String postScript) {
|
|
|
startChat(context, wid, BytedeskConstants.CHAT_TYPE_WORKGROUP, title, '',
|
|
|
postScript, null);
|
|
|
}
|
|
|
|
|
|
// 电商接口,携带商品参数
|
|
|
// type/title/content/price/url/imageUrl/id/categoryCode
|
|
|
static void startWorkGroupChatShop(
|
|
|
BuildContext context, String wid, String title, String commodity) {
|
|
|
startChatShop(context, wid, BytedeskConstants.CHAT_TYPE_WORKGROUP, title,
|
|
|
commodity, null);
|
|
|
}
|
|
|
|
|
|
static void startWorkGroupChatShopCallback(BuildContext context, String wid,
|
|
|
String title, String commodity, ValueSetter<String> customCallback) {
|
|
|
startChatShop(context, wid, BytedeskConstants.CHAT_TYPE_WORKGROUP, title,
|
|
|
commodity, customCallback);
|
|
|
}
|
|
|
|
|
|
// 指定客服会话
|
|
|
static void startAppointedChat(
|
|
|
BuildContext context, String uid, String title) {
|
|
|
startChatDefault(
|
|
|
context, uid, BytedeskConstants.CHAT_TYPE_APPOINTED, title);
|
|
|
}
|
|
|
|
|
|
// 发送附言消息
|
|
|
static void startAppointedChatPostscript(
|
|
|
BuildContext context, String uid, String title, String postScript) {
|
|
|
startChat(context, uid, BytedeskConstants.CHAT_TYPE_APPOINTED, title, '',
|
|
|
postScript, null);
|
|
|
}
|
|
|
|
|
|
// 电商接口,携带商品参数
|
|
|
static void startAppointedChatShop(
|
|
|
BuildContext context, String uid, String title, String commodity) {
|
|
|
startChatShop(context, uid, BytedeskConstants.CHAT_TYPE_APPOINTED, title,
|
|
|
commodity, null);
|
|
|
}
|
|
|
|
|
|
static void startAppointedChatShopCallback(BuildContext context, String uid,
|
|
|
String title, String commodity, ValueSetter<String> customCallback) {
|
|
|
startChatShop(context, uid, BytedeskConstants.CHAT_TYPE_APPOINTED, title,
|
|
|
commodity, customCallback);
|
|
|
}
|
|
|
|
|
|
// 默认设置商品信息和附言为空
|
|
|
static void startChatDefault(
|
|
|
BuildContext context, String uuid, String type, String title) {
|
|
|
startChat(context, uuid, type, title, '', '', null);
|
|
|
}
|
|
|
|
|
|
// 电商对话-自定义类型(技能组、指定客服)
|
|
|
static void startChatShop(BuildContext context, String uuid, String type,
|
|
|
String title, String commodity, ValueSetter<String>? customCallback) {
|
|
|
startChat(context, uuid, type, title, commodity, '', customCallback);
|
|
|
}
|
|
|
|
|
|
// 发送附言消息-自定义类型(技能组、指定客服)
|
|
|
static void startChatPostscript(BuildContext context, String uuid,
|
|
|
String type, String title, String postScript) {
|
|
|
startChat(context, uuid, type, title, '', postScript, null);
|
|
|
}
|
|
|
|
|
|
// 客服会话-自定义类型(技能组、指定客服)
|
|
|
static void startChat(
|
|
|
BuildContext context,
|
|
|
String uuid,
|
|
|
String type,
|
|
|
String title,
|
|
|
String commodity,
|
|
|
String postScript,
|
|
|
ValueSetter<String>? customCallback) {
|
|
|
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
return new ChatKFProvider(
|
|
|
wid: uuid,
|
|
|
aid: uuid,
|
|
|
type: type,
|
|
|
title: title,
|
|
|
custom: commodity,
|
|
|
postscript: postScript,
|
|
|
customCallback: customCallback,
|
|
|
);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// 从历史会话或者点击通知栏进入
|
|
|
static void startChatThread(BuildContext context, Thread thread,
|
|
|
{String title = ''}) {
|
|
|
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
return new ChatThreadProvider(
|
|
|
thread: thread,
|
|
|
title: title,
|
|
|
);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// 应用内打开H5客服页面
|
|
|
static void startH5Chat(BuildContext context, String url, String title) {
|
|
|
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
return new ChatWebViewPage(
|
|
|
url: url,
|
|
|
title: title,
|
|
|
);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// 应用内打开网页
|
|
|
static void openWebView(BuildContext context, String url, String title) {
|
|
|
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
return new ChatWebViewPage(
|
|
|
url: url,
|
|
|
title: title,
|
|
|
);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// TODO: 好友一对一聊天
|
|
|
static void startContactChat(BuildContext context, String cid, String title) {
|
|
|
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
return new ChatIMProvider(
|
|
|
// cid: cid,
|
|
|
title: title,
|
|
|
);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// TODO: 好友一对一,携带商品参数
|
|
|
// static void startContactChatShop(
|
|
|
// BuildContext context, String cid, String title, String commodity) {
|
|
|
// Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
// return new ChatIMProvider(
|
|
|
// // cid: cid,
|
|
|
// title: title,
|
|
|
// custom: commodity,
|
|
|
// );
|
|
|
// }));
|
|
|
// }
|
|
|
|
|
|
// TODO: 客服端-进入接待访客对话页面
|
|
|
static void startChatThreadIM(BuildContext context, Thread thread) {
|
|
|
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
return new ChatIMProvider(
|
|
|
thread: thread,
|
|
|
isThread: true,
|
|
|
);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// 常见问题列表
|
|
|
static void showFaq(BuildContext context, String uid) {
|
|
|
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
return new HelpProvider(uid: uid);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// 意见反馈
|
|
|
static void showFeedback(BuildContext context, String uid) {
|
|
|
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
return new FeedbackProvider(uid: uid);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// TODO: 提交工单
|
|
|
static void showTicket(BuildContext context, String uid) {
|
|
|
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
return new TicketProvider(uid: uid);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// TODO: 留言
|
|
|
static void showLeaveMessage(
|
|
|
BuildContext context, String uuid, String type, String tip) {
|
|
|
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
return new LeaveMsgProvider(
|
|
|
wid: uuid,
|
|
|
aid: uuid,
|
|
|
type: type,
|
|
|
tip: tip,
|
|
|
);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// 频道消息
|
|
|
static void showChannel(BuildContext context, Thread thread) {
|
|
|
Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
|
|
|
return new ChannelProvider(
|
|
|
thread: thread,
|
|
|
);
|
|
|
}));
|
|
|
}
|
|
|
|
|
|
// 获取个人资料
|
|
|
static Future<User> getProfile() async {
|
|
|
return BytedeskUserHttpApi().getProfile();
|
|
|
}
|
|
|
|
|
|
// 设置用户昵称
|
|
|
static Future<User> updateNickname(String nickname) async {
|
|
|
return BytedeskUserHttpApi().updateNickname(nickname);
|
|
|
}
|
|
|
|
|
|
// 设置用户头像
|
|
|
static Future<User> updateAvatar(String avatar) async {
|
|
|
return BytedeskUserHttpApi().updateAvatar(avatar);
|
|
|
}
|
|
|
|
|
|
// 设置用户备注
|
|
|
static Future<User> updateDescription(String avatar) async {
|
|
|
return BytedeskUserHttpApi().updateDescription(avatar);
|
|
|
}
|
|
|
|
|
|
// 查询技能组在线状态
|
|
|
static Future<String> getWorkGroupStatus(String workGroupWid) async {
|
|
|
return BytedeskUserHttpApi().getWorkGroupStatus(workGroupWid);
|
|
|
}
|
|
|
|
|
|
// 查询客服在线状态
|
|
|
static Future<String> getAgentStatus(String agentUid) async {
|
|
|
return BytedeskUserHttpApi().getAgentStatus(agentUid);
|
|
|
}
|
|
|
|
|
|
// 查询当前用户-某技能组wid或指定客服未读消息数目
|
|
|
static Future<String> getUnreadCount(String wid) async {
|
|
|
return BytedeskUserHttpApi().getUnreadCount(wid);
|
|
|
}
|
|
|
|
|
|
// 访客端-查询访客所有未读消息数目
|
|
|
static Future<String> getUnreadCountVisitor() async {
|
|
|
return BytedeskUserHttpApi().getUnreadCountVisitor();
|
|
|
}
|
|
|
|
|
|
// 客服端-查询客服所有未读消息数目
|
|
|
static Future<String> getUnreadCountAgent() async {
|
|
|
return BytedeskUserHttpApi().getUnreadCountAgent();
|
|
|
}
|
|
|
|
|
|
// 访客历史会话
|
|
|
static Future<List<Thread>> getVisitorThreads(int page, int size) async {
|
|
|
return BytedeskThreadHttpApi().getVisitorThreads(page, size);
|
|
|
}
|
|
|
|
|
|
// 消息提示设置
|
|
|
static bool? getPlayAudioOnSendMessage() {
|
|
|
return SpUtil.getBool(BytedeskConstants.PLAY_AUDIO_ON_SEND_MESSAGE,
|
|
|
defValue: true);
|
|
|
}
|
|
|
|
|
|
static void setPlayAudioOnSendMessage(bool flag) {
|
|
|
SpUtil.putBool(BytedeskConstants.PLAY_AUDIO_ON_SEND_MESSAGE, flag);
|
|
|
}
|
|
|
|
|
|
static bool? getPlayAudioOnReceiveMessage() {
|
|
|
return SpUtil.getBool(BytedeskConstants.PLAY_AUDIO_ON_RECEIVE_MESSAGE,
|
|
|
defValue: true);
|
|
|
}
|
|
|
|
|
|
static void setPlayAudioOnReceiveMessage(bool flag) {
|
|
|
SpUtil.putBool(BytedeskConstants.PLAY_AUDIO_ON_RECEIVE_MESSAGE, flag);
|
|
|
}
|
|
|
|
|
|
static bool? getVibrateOnReceiveMessage() {
|
|
|
return SpUtil.getBool(BytedeskConstants.VIBRATE_ON_RECEIVE_MESSAGE,
|
|
|
defValue: true);
|
|
|
}
|
|
|
|
|
|
static void setVibrateOnReceiveMessage(bool flag) {
|
|
|
SpUtil.putBool(BytedeskConstants.VIBRATE_ON_RECEIVE_MESSAGE, flag);
|
|
|
}
|
|
|
|
|
|
// 上传ios devicetoken
|
|
|
static Future<void> uploadIOSDeviceToken(
|
|
|
String appkey, String deviceToken) async {
|
|
|
BytedeskDeviceHttpApi()
|
|
|
.updateIOSDeviceToken(appkey, BytedeskConstants.build, deviceToken);
|
|
|
}
|
|
|
|
|
|
// build的值只有两种情况,测试设置为:debug 或 线上设置为:release
|
|
|
// static Future<void> uploadIOSDeviceToken(
|
|
|
// String appkey, String build, String deviceToken) async {
|
|
|
// BytedeskDeviceHttpApi().updateIOSDeviceToken(appkey, build, deviceToken);
|
|
|
// }
|
|
|
|
|
|
// build的值只有两种情况,测试设置为:debug 或 线上设置为:release
|
|
|
static Future<void> deleteIOSDeviceToken(String build) async {
|
|
|
BytedeskDeviceHttpApi().deleteIOSDeviceToken(build);
|
|
|
}
|
|
|
|
|
|
// The package version. `CFBundleShortVersionString` on iOS, `versionName` on Android.
|
|
|
static Future<String> getAppVersion() {
|
|
|
return BytedeskDeviceHttpApi().getAppVersion();
|
|
|
}
|
|
|
|
|
|
// The build number. `CFBundleVersion` on iOS, `versionCode` on Android.
|
|
|
static Future<String> getAppBuildNumber() {
|
|
|
return BytedeskDeviceHttpApi().getAppBuildNumber();
|
|
|
}
|
|
|
|
|
|
// 从服务器检测当前APP是否有新版
|
|
|
static Future<App> checkAppVersion(String androidKey, String iosKey) {
|
|
|
if (BytedeskUtils.isWeb) {
|
|
|
// FIXME: 仅用于占位,待修改
|
|
|
return BytedeskUserHttpApi().checkAppVersion(iosKey);
|
|
|
} else if (BytedeskUtils.isAndroid) {
|
|
|
return BytedeskUserHttpApi().checkAppVersion(androidKey);
|
|
|
}
|
|
|
return BytedeskUserHttpApi().checkAppVersion(iosKey);
|
|
|
}
|
|
|
|
|
|
// 退出登录
|
|
|
static Future<void> logout() {
|
|
|
// 断开长链接
|
|
|
disconnect();
|
|
|
// 通知服务器,并清空本地数据
|
|
|
return BytedeskUserHttpApi().logout();
|
|
|
}
|
|
|
|
|
|
// 以下为专用接口,普通用户请忽略
|
|
|
|
|
|
// 通过token获取手机号-良师app-专用
|
|
|
static Future<String> getAliyunOneKeyLoginMobile(String token) async {
|
|
|
return BytedeskUserHttpApi().getAliyunOneKeyLoginMobile(token);
|
|
|
}
|
|
|
|
|
|
// 匿名登录之后,绑定手机号-良师app-专用
|
|
|
static Future<JsonResult> bindMobile(String mobile) async {
|
|
|
return BytedeskUserHttpApi().bindMobile(mobile);
|
|
|
}
|
|
|
|
|
|
// 微信登录之后,获取微信用户信息-良师app-专用
|
|
|
static Future<WeChatResult> getWechatUserinfo(String code) async {
|
|
|
return BytedeskUserHttpApi().getWechatUserinfo(code);
|
|
|
}
|
|
|
|
|
|
// 手机端注册微信登录用户-并绑定手机号-良师app-专用
|
|
|
static Future<UserJsonResult> registerWechatMobile(String mobile,
|
|
|
String nickname, String avatar, String unionid, String openid) async {
|
|
|
return BytedeskUserHttpApi()
|
|
|
.registerWechatMobile(mobile, nickname, avatar, unionid, openid);
|
|
|
}
|
|
|
|
|
|
// 将unionid绑定到已经存在的手机账号-良师app-专用
|
|
|
static Future<UserJsonResult> bindWeChatMobile(
|
|
|
String mobile, String unionid) async {
|
|
|
return BytedeskUserHttpApi().bindWeChatMobile(mobile, unionid);
|
|
|
}
|
|
|
}
|