master
jackning 4 years ago
parent 80d892fcd3
commit 5e7708776c

@ -2,7 +2,23 @@
萝卜丝(bytedesk) flutter 客服SDK 萝卜丝(bytedesk) flutter 客服SDK
## Getting Started ## 功能
- 技能组客服
- 一对一客服
- 支持发送电商商品信息
- 支持发送附言消息
- 获取当前客服在线状态
- 获取历史会话
- 消息提示设置
- 机器人对话
- 消息送达/已读
- 消息撤回
- 输入状态(对方正在输入)
<!-- - 提交工单 -->
<!-- - 意见反馈 -->
## 集成步骤
### 第一步 ### 第一步
@ -22,4 +38,9 @@
### 集成完毕 ### 集成完毕
<img src="./home.jpeg" width="25%" height="25%"/> <img src="./home.jpeg" width="25%" height="25%"/>
<img src="./chat.jpeg" width="25%" height="25%"/> <img src="./chat.jpeg" width="25%" height="25%"/>
<img src="./chat_type.jpeg" width="25%" height="25%"/>
### 其他
- 技术支持QQ 3群: 825257535

@ -1,5 +1,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bytedesk.bytedesk_demo"> package="com.bytedesk.bytedesk_demo">
<!-- <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<!-- <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> -->
<!-- 拍照/扫一扫 -->
<!-- <uses-permission android:name="android.permission.CAMERA" /> -->
<!-- 读取联系人 -->
<!-- <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" /> -->
<!-- io.flutter.app.FlutterApplication is an android.app.Application that <!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method. calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide In most cases you can leave this as-is, but you if you want to provide

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 74 KiB

@ -24,6 +24,35 @@
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSAppleMusicUsageDescription</key>
<string>$(PRODUCT_NAME) 需要读取音乐</string>
<key>NSCalendarsUsageDescription</key>
<string>$(PRODUCT_NAME) 需要读取日历</string>
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) 拍照并发送图片</string>
<key>NSContactsUsageDescription</key>
<string>$(PRODUCT_NAME) 需要读取联系人推荐好友</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>地图功能需要您的定位服务,否则无法使用,如果您需要使用后台定位功能请选择“始终允许”。</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>地图功能需要您的定位服务,否则无法使用。</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>地图功能需要您的定位服务,否则无法使用。</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) 发送语音</string>
<key>NSMotionUsageDescription</key>
<string>$(PRODUCT_NAME) 需要读取运动健身</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>$(PRODUCT_NAME) 发送图片</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>$(PRODUCT_NAME) 发送图片</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>$(PRODUCT_NAME) 需要读取语音识别</string>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>

@ -1,6 +1,11 @@
import 'package:bytedesk_kefu/bytedesk_kefu.dart'; import 'package:bytedesk_kefu/bytedesk_kefu.dart';
import 'package:bytedesk_kefu/util/bytedesk_constants.dart'; import 'package:bytedesk_kefu/util/bytedesk_constants.dart';
import 'package:bytedesk_kefu/util/bytedesk_events.dart'; import 'package:bytedesk_kefu/util/bytedesk_events.dart';
import 'package:bytedesk_demo/page/chat_type_page.dart';
import 'package:bytedesk_demo/page/history_thread_page.dart';
import 'package:bytedesk_demo/page/online_status_page.dart';
import 'package:bytedesk_demo/page/setting_page.dart';
import 'package:bytedesk_demo/page/user_info_page.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
void main() { void main() {
@ -10,8 +15,8 @@ void main() {
home: MyApp(), home: MyApp(),
)); ));
// https://github.com/Bytedesk/bytedesk-flutter
// https://www.bytedesk.com/antv/user/login // https://www.bytedesk.com/antv/user/login
// https://github.com/pengjinning/bytedesk-android
// appkeysubDomain // appkeysubDomain
// appkey->->->->appkey // appkey->->->->appkey
String _androidKey = "66390193-b2c1-4edb-aa5f-50b1541059e8"; String _androidKey = "66390193-b2c1-4edb-aa5f-50b1541059e8";
@ -27,14 +32,15 @@ class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState(); _MyAppState createState() => _MyAppState();
} }
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
// //
String _title = '萝卜丝客服Demo'; String _title = '萝卜丝客服Demo';
// ->- IDwId, wid // ->- IDwId, wid
String _workGroupWid = "201807171659201"; // String _workGroupWid = "201807171659201";
// //
@override @override
void initState() { void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState(); super.initState();
_listener(); _listener();
} }
@ -47,18 +53,78 @@ class _MyAppState extends State<MyApp> {
elevation: 0, elevation: 0,
), ),
body: ListView( body: ListView(
children: <Widget>[ children: ListTile.divideTiles(
context: context,
tiles: [
ListTile( ListTile(
title: Text('联系客服'), title: Text('联系客服'),
trailing: Icon(Icons.keyboard_arrow_right), trailing: Icon(Icons.keyboard_arrow_right),
onTap: () { onTap: () {
print('chat'); //
// Navigator.of(context)
BytedeskKefu.startWorkGroupChat(context, _workGroupWid, "技能组客服"); .push(new MaterialPageRoute(builder: (context) {
return new ChatTypePage();
}));
},
),
ListTile(
title: Text('用户信息'), //
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context)
.push(new MaterialPageRoute(builder: (context) {
return new UserInfoPage();
}));
},
),
ListTile(
title: Text('在线状态'), // 线
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context)
.push(new MaterialPageRoute(builder: (context) {
return new OnlineStatusPage();
}));
},
),
ListTile(
title: Text('历史会话'), //
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context)
.push(new MaterialPageRoute(builder: (context) {
return new HistoryThreadPage();
}));
},
),
// ListTile(
// title: Text('TODO:提交工单'),
// trailing: Icon(Icons.keyboard_arrow_right),
// onTap: () {
// print('ticket');
// // TODO:
// },
// ),
// ListTile(
// title: Text('TODO:意见反馈'),
// trailing: Icon(Icons.keyboard_arrow_right),
// onTap: () {
// print('feedback');
// // TODO:
// },
// ),
ListTile(
title: Text('消息设置'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.of(context)
.push(new MaterialPageRoute(builder: (context) {
return new SettingPage();
}));
}, },
) )
], ],
), ).toList()),
); );
} }
@ -93,4 +159,28 @@ class _MyAppState extends State<MyApp> {
} }
}); });
} }
// @override
// void didChangeAppLifecycleState(AppLifecycleState state) {
// print("main didChangeAppLifecycleState:" + state.toString());
// switch (state) {
// case AppLifecycleState.inactive: //
// break;
// case AppLifecycleState.paused: //
// break;
// case AppLifecycleState.resumed: //
// // APP
// // BytedeskUtils.mqttReConnect();
// break;
// case AppLifecycleState.detached: //
// break;
// }
// }
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
} }

@ -0,0 +1,120 @@
import 'dart:convert';
import 'package:bytedesk_kefu/bytedesk_kefu.dart';
import 'package:bytedesk_kefu/util/bytedesk_constants.dart';
import 'package:flutter/material.dart';
//
class ChatTypePage extends StatefulWidget {
ChatTypePage({Key key}) : super(key: key);
@override
_ChatTypePageState createState() => _ChatTypePageState();
}
class _ChatTypePageState extends State<ChatTypePage> {
// 访
String _workGroupWid = "201807171659201";
//
String _agentUid = "201808221551193";
//
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('对话类型'),
elevation: 0,
),
body: ListView(
children: ListTile.divideTiles(
context: context,
tiles: [
ListTile(
title: Text('技能组客服'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startWorkGroupChat(context, _workGroupWid, "技能组客服");
},
),
ListTile(
title: Text('技能组客服-电商'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
// type/title/content/price/url/imageUrl/id/categoryCode
var custom = json.encode({
"type": BytedeskConstants.MESSAGE_TYPE_COMMODITY,
"title": "商品标题",
"content": "商品详情",
"price": "9.99",
"url": "https://item.m.jd.com/product/12172344.html",
"imageUrl": "https://bytedesk.oss-cn-shenzhen.aliyuncs.com/images/123.webp",
"id": 123,
"categoryCode": "100010003",
"client": "flutter"
});
BytedeskKefu.startWorkGroupChatShop(context, _workGroupWid, "技能组客服", custom);
},
),
ListTile(
title: Text('技能组客服-附言'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startWorkGroupChatPostscript(context, _workGroupWid, "技能组客服", "随便说点什么吧,我会自动发送给客服");
},
),
Container(
height: 20,
),
ListTile(
title: Text('指定一对一客服'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startAppointedChat(context, _agentUid, "指定客服");
},
),
ListTile(
title: Text('指定一对一客服-电商'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
// type/title/content/price/url/imageUrl/id/categoryCode
var custom = json.encode({
"type": BytedeskConstants.MESSAGE_TYPE_COMMODITY,
"title": "商品标题",
"content": "商品详情",
"price": "9.99",
"url": "https://item.m.jd.com/product/12172344.html",
"imageUrl": "https://bytedesk.oss-cn-shenzhen.aliyuncs.com/images/123.webp",
"id": 123,
"categoryCode": "100010003",
"client": "flutter"
});
BytedeskKefu.startAppointedChatShop(context, _agentUid, "指定客服", custom);
},
),
ListTile(
title: Text('指定一对一客服-附言'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
BytedeskKefu.startAppointedChatPostscript(context, _agentUid, "指定客服", "随便说点什么吧,我会自动发送给客服");
},
),
Container(
height: 20,
),
ListTile(
title: Text('H5网页会话'),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
print('h5 chat');
// : ->->()-> URL
String url = "http://www.bytedesk.com/chat?sub=vip&uid=201808221551193&wid=201807171659201&type=workGroup&aid=&hidenav=1&ph=ph";
String title = 'H5在线客服演示';
BytedeskKefu.startH5Chat(context, url, title);
},
),
]
).toList(),
),
);
}
}

@ -0,0 +1,58 @@
import 'package:bytedesk_kefu/bytedesk_kefu.dart';
import 'package:bytedesk_kefu/model/thread.dart';
import 'package:flutter/material.dart';
//
class HistoryThreadPage extends StatefulWidget {
HistoryThreadPage({Key key}) : super(key: key);
@override
_HistoryThreadPageState createState() => _HistoryThreadPageState();
}
class _HistoryThreadPageState extends State<HistoryThreadPage> {
int _page = 0;
int _size = 20;
List<Thread> _historyThreadList = [];
//
@override
void initState() {
_getVisitorThreads();
super.initState();
}
//
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('历史会话'),
elevation: 0,
),
body: RefreshIndicator(
child: ListView.builder(
padding: EdgeInsets.all(8.0),
itemBuilder: (_, int index) => ListTile(
leading: Image.network(_historyThreadList[index].avatar),
title: Text('${_historyThreadList[index].nickname}, ${_historyThreadList[index].timestamp}'),
subtitle: Text('${_historyThreadList[index].content}'),
),
itemCount: _historyThreadList.length,
),
onRefresh: _onRefresh,
));
}
void _getVisitorThreads() {
BytedeskKefu.getVisitorThreads(_page, _size).then((value) => {
setState(() {
_historyThreadList = value;
})
});
}
Future<void> _onRefresh() async {
_page++;
_getVisitorThreads();
}
}

@ -0,0 +1,76 @@
import 'package:bytedesk_kefu/bytedesk_kefu.dart';
import 'package:flutter/material.dart';
// 线
class OnlineStatusPage extends StatefulWidget {
OnlineStatusPage({Key key}) : super(key: key);
@override
_OnlineStatusPageState createState() => _OnlineStatusPageState();
}
class _OnlineStatusPageState extends State<OnlineStatusPage> {
// ->- IDwId
String _workGroupWid = "201807171659201";
// ->- IDuId
String _agentUid = "201808221551193";
//
String _workGroupStatus = '';
String _agentStatus = '';
//
@override
void initState() {
_getWorkGroupStatus();
_getAgentStatus();
super.initState();
}
//
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('在线状态'),
elevation: 0,
),
body: ListView(
children: ListTile.divideTiles(
context: context,
tiles: [
ListTile(
title: Text('技能组在线状态'),
subtitle: Text(_workGroupStatus),
onTap: () {
_getWorkGroupStatus();
},
),
ListTile(
title: Text('客服在线状态'),
subtitle: Text(_agentStatus),
onTap: () {
_getAgentStatus();
},
),
],
).toList()),
);
}
void _getWorkGroupStatus() {
BytedeskKefu.getWorkGroupStatus(_workGroupWid).then((status) => {
print(status),
setState(() {
_workGroupStatus = status;
})
});
}
void _getAgentStatus() {
BytedeskKefu.getAgentStatus(_agentUid).then((status) => {
print(status),
setState(() {
_agentStatus = status;
})
});
}
}

@ -0,0 +1,72 @@
import 'package:bytedesk_kefu/bytedesk_kefu.dart';
import 'package:flutter/material.dart';
import 'package:list_tile_switch/list_tile_switch.dart';
//
class SettingPage extends StatefulWidget {
SettingPage({Key key}) : super(key: key);
@override
_SettingPageState createState() => _SettingPageState();
}
class _SettingPageState extends State<SettingPage> {
// bool _playAudioOnSendMessage = false;
// bool _playAudioOnReceiveMessage = false;
bool _vibrateOnReceiveMessage = false;
//
@override
void initState() {
// _playAudioOnSendMessage = BytedeskKefu.getPlayAudioOnSendMessage();
// _playAudioOnReceiveMessage = BytedeskKefu.getPlayAudioOnReceiveMessage();
_vibrateOnReceiveMessage = BytedeskKefu.getVibrateOnReceiveMessage();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('消息设置'),
elevation: 0,
),
body: ListView(
children: ListTile.divideTiles(
context: context,
tiles: [
// ListTileSwitch(
// value: _playAudioOnSendMessage,
// onChanged: (value) {
// setState(() {
// _playAudioOnSendMessage = value;
// });
// BytedeskKefu.setPlayAudioOnSendMessage(value);
// },
// title: Text('TODO: 发送消息时播放声音'),
// ),
// ListTileSwitch(
// value: _playAudioOnReceiveMessage,
// onChanged: (value) {
// setState(() {
// _playAudioOnReceiveMessage = value;
// });
// BytedeskKefu.setPlayAudioOnReceiveMessage(value);
// },
// title: Text('TODO: 收到消息时播放声音'),
// ),
ListTileSwitch(
value: _vibrateOnReceiveMessage,
onChanged: (value) {
setState(() {
_vibrateOnReceiveMessage = value;
});
// AndroidManifest.xml<uses-permission android:name="android.permission.VIBRATE"/>
BytedeskKefu.setVibrateOnReceiveMessage(value);
},
title: Text('收到消息时振动'),
),
],
).toList()),
);
}
}

@ -0,0 +1,87 @@
import 'package:bytedesk_kefu/bytedesk_kefu.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
// -APP
class UserInfoPage extends StatefulWidget {
UserInfoPage({Key key}) : super(key: key);
@override
_UserInfoPageState createState() => _UserInfoPageState();
}
class _UserInfoPageState extends State<UserInfoPage> {
String _nickname = '';
String _avatar =
'https://chainsnow.oss-cn-shenzhen.aliyuncs.com/avatars/chrome_default_avatar.png';
@override
void initState() {
_getProfile();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('用户信息'),
elevation: 0,
),
body: ListView(
children: ListTile.divideTiles(
context: context,
tiles: [
ListTile(
title: Text('设置昵称(见代码)'),
subtitle: Text(_nickname ?? ''),
onTap: () {
//
_setNickname();
},
),
ListTile(
leading: Image.network(
_avatar ?? 'https://chainsnow.oss-cn-shenzhen.aliyuncs.com/avatars/chrome_default_avatar.png',
height: 30,
width: 30,
),
title: Text('设置头像(见代码)'),
onTap: () {
//
_setAvatar();
},
),
],
).toList()),
);
}
void _getProfile() {
BytedeskKefu.getProfile().then((user) => {
setState(() {
_nickname = user.nickname;
_avatar = user.avatar;
})
});
}
void _setNickname() {
String mynickname = 'APP昵称123';
BytedeskKefu.updateNickname(mynickname).then((user) => {
setState(() {
_nickname = mynickname;
}),
Fluttertoast.showToast(msg: "设置昵称成功")
});
}
void _setAvatar() {
String myavatarurl = 'https://chainsnow.oss-cn-shenzhen.aliyuncs.com/avatars/visitor_default_avatar.png'; // url
BytedeskKefu.updateAvatar(myavatarurl).then((user) => {
setState(() {
_avatar = myavatarurl;
}),
Fluttertoast.showToast(msg: "设置头像成功")
});
}
}

@ -18,14 +18,21 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.8.0 <3.0.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# 事件通知总线
# https://pub.dev/packages/event_bus
event_bus: ^1.1.1
# toast https://pub.dev/packages/fluttertoast
fluttertoast: ^7.1.1
# 消息设置switch https://pub.dev/packages/list_tile_switch
list_tile_switch: ^0.0.2
# https://pub.dev/packages/bytedesk_kefu # https://pub.dev/packages/bytedesk_kefu
bytedesk_kefu: ^0.0.8 bytedesk_kefu: ^0.1.0
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.

Loading…
Cancel
Save