# 高级功能

# 创建消息和发送消息

在我们 SDK 中消息的载体是通过 SDK 中的 IMessage 类去实现的,我们发送消息给客服需要两个步骤,一是创建 IMessage 对象,二是发送 IMessage,在 V4.3.0 版本中我们开放了创建消息和发送消息的接口,App 可以通过 UnicornMessageBuilder 类中的方法去创建不同类型的 IMessage,然后通过 MessageService 中的方法去发送 IMessage。

# 创建消息

SDK 中创建消息的方法都在 UnicornMessageBuilder 类中,该类提供到了创建不同类型消息的方法,具体如下:

/**
     * 创建一条图片消息
     *
     * @param sessionId   聊天对象ID
     * @param file        图片文件
     * @param displayName 图片文件的显示名,可不同于文件名
     * @return IMMessage 生成的消息对象
     */
    public static final IMMessage buildImageMessage(String sessionId, File file, String displayName){
        return MessageBuilder.createImageMessage(sessionId, SessionTypeEnum.Ysf, file, displayName);
    }

    /**
     * 创建一条普通文本消息
     *
     * @param sessionId   聊天对象ID
     * @param text        文本消息内容
     * @return IMMessage 生成的消息对象
     */
    public static final IMMessage buildTextMessage(String sessionId, String text){
        return MessageBuilder.createTextMessage(sessionId, SessionTypeEnum.Ysf, text);
    }

    /**
     * 创建一条视频消息
     * @param file  视频文件
     * @param duration 视频文件时长   可通过 MediaPlayer 获取
     * @param width  视频文件宽       可通过 MediaPlayer 获取
     * @param height  视频文件高      可通过 MediaPlayer 获取
     * @param displayName  视频文件名字,可通过 file.getName 获取
     * @return
     */
    public static final IMMessage buildVideoMessage(File file, long duration, int width, int height, String displayName) {
        return MessageBuilder.createVideoMessage(SessionHelper.getDefaultSessionId(), SessionTypeEnum.Ysf,
                file, duration, width, height, displayName);
    }

    /**
     * 创建一条视频消息
     * @param sessionId 聊天对象的 id
     * @param file  视频文件
     * @param duration 视频文件时长   可通过 MediaPlayer 获取
     * @param width  视频文件宽       可通过 MediaPlayer 获取
     * @param height  视频文件高      可通过 MediaPlayer 获取
     * @param displayName  视频文件名字,可通过 file.getName 获取
     * @return
     */
    public static final IMMessage buildVideoMessage(String sessionId, File file, long duration, int width, int height, String displayName) {
        return MessageBuilder.createVideoMessage(sessionId, SessionTypeEnum.Ysf,
                file, duration, width, height, displayName);
    }

    /**
     * 创建一条APP自定义类型消息
     * @param attachment  消息附件对象
     * @return 自定义消息
     */
    public static IMMessage buildCustomMessage(MsgAttachment attachment) {
        return buildCustomMessage(SessionHelper.getDefaultSessionId(),  null, attachment, null);
    }

    /**
     * 创建一条APP自定义类型消息
     *
     * @param sessionId   聊天对象ID  可以通过 message.getSessionId() 获取
     * @param attachment  消息附件对象
     * @return 自定义消息
     */
    public static IMMessage buildCustomMessage(String sessionId,  MsgAttachment attachment) {
        return buildCustomMessage(sessionId,  null, attachment, null);
    }

    /**
     * 创建一条APP自定义类型消息, 同时提供描述字段,可用于推送以及状态栏消息提醒的展示。
     *
     * @param sessionId   聊天对象ID
     * @param content     消息简要描述,可通过IMMessage#getContent()获取,主要用于用户推送展示。
     * @param attachment  消息附件对象
     * @param config      自定义消息配置
     * @return 自定义消息
     */
    public static IMMessage buildCustomMessage(String sessionId, String content, MsgAttachment attachment, CustomMessageConfig config) {
       ···
        return message;
    }

    /**
     * 创建一条APP 私有的消息类型的消息
     * @param attachment  消息附件对象
     * @return 自定义消息
     */
    public static IMMessage buildAppCustomMessage(MsgAttachment attachment) {
        ···
        return message;
    }

    /**
     * 构造一条文件消息
     * @param sessionId 如果是平台版则为 shopId,非平台版则可以通过
     * @see #getSessionId  方法获取
     * @param filePath
     * @return
     */
    public static IMMessage buildFileMessage(String sessionId, String filePath) {
    	  ···
        return message;
    }

# 发送消息

SDK 中发送消息的方法的实现都在 MessageService 中,具体如下:

/**
     * 发送消息的接口
     *
     * @param message 要发送的消息
     */
    public static void sendMessage(IMMessage message) {
        ···
    }

    /**
     * 把消息存储本地的方法,调用该方法之后,本条消息只能访客看到
     * 客服看不到
     *
     * @param message  即将存储的消息
     * @param isNotify 是否通知
     * @param isSaveDB 是否将消息存储到数据库
     */
    public static void saveMessageToLocal(IMMessage message, boolean isNotify, boolean isSaveDB) {
        ···
    }

    /**
     * 非平台企业发送商品信息
     * @param productDetail productDetail
     */
    public static void sendProductMessage(ProductDetail productDetail) {
        ···
    }

    /**
     * 发送商品信息到 shopId 指定的商户
     * @param shopId 商户 id 非平台企业为 -1
     * @param productDetail productDetail
     */
    public static void sendProductMessage(String shopId, ProductDetail productDetail) {
            	···
    }

想要查看内部实现,请查看 SDK 代码

# 自定义消息处理

在我们平时使用 ListView 和 RecyclerView 的时候可以根据数据源的不同使用不同的 item 布局,那我们 SDK 也是采取了这种方式去展示消息的,当 SDK 收到消息时,会根据 IMessage(消息的载体) 中不同的 Attachment(也就是我们所说的数据源) 展示不同的布局(每种布局都是一个 ViewHolder)。这只是 SDK 中展示消息的相关的处理逻辑,在 V4.3.0 版本中我们加入了用户自定义处理消息的接口,App 可以通过该接口去实现消息的处理,这里所说的处理和展示是完全分离的,SDK 在收到消息的时候,会通知 App 我收到消息,App 可以根据消息中的 attachment 类型去做不同的处理,但是这个处理不会影响我们 SDK 的展示工作,相关内容的示例代码如下:

//告诉 SDK 语音消息不需要展示 UI 了
MsgCustomizationRegistry.hideViewForMsgType(AudioAttachment.class);
//注册 handler 的工厂
MsgCustomizationRegistry.registerMessageHandlerFactory(new MessageHandlerFactory() {
    @Override
    public UnicornMessageHandler handlerOf(UnicornMessage message) {
        //下面是根据不同的消息进行不同的处理
        if(message.getAttachment() instanceof AudioAttachment){
            return new AudioDemoHandler();  //自定义处理音频消息的 Handler
        }else if(message.getAttachment() instanceof ImageAttachment){
            return new ImageDemoHandler();  //自定义的图片消息
        }else if(message.getAttachment() instanceof ProductAttachment){
            return new ProductDemoHandler(); //自定义商品消息
        }else {
            return null;  //不做处理
        }

    }
});

在这里 hideViewForMsgType 方法有必要说一下,当我们处理的消息不希望展示在客服界面的时候,可以使用该方法去隐藏。上面代码已经很清晰,在 SDK 初始化的时候,你可以注册一个 Factory,当 SDK 收到消息的时候回调用 handlerOf 方法,在 handlerOf 方法中可以根据不同的 attachment 返回不同的 handler,返回之后会调用 Handler 的 onMessage 方法,上面的三个 handler 都是需要 App 通过继承 UnicornMessageHandler 类去实现,下面的代码展示了如何自定义一个 handler:

public class DemoMsgHandler implements UnicornMessageHandler {
    /**
     *
     * @param context: 消息流所在的上下文
     * @param message:你将要处理的 message ,通过 message.getAttachment 可以拿到你注册的 attachment
     * @param handledOnce:之前是否已经成功处理过该消息,有些活动只需要处理一次,例如一条订单消息只需要弹一次订单列表,所以可以根据这个参数去判断是否需要处理
     * @return 是否处理了该消息,true 为处理了该消息,false 为没处理该消息
     * 在 > V5.7.7 版本中 onMessage 回调中的 UnicornMessage 变为 IMMessage 类型
     */
    @Override
    public boolean onMessage(Context context, UnicornMessage message, boolean handledOnce) {
        //此方里面可以进行一些处理跳转界面,弹框等操作
        Intent intent = new Intent(context, OrderListActivity.class);
        context.startActivity(intent);
        return true;
    }
}

# SDK 中事件的拦截处理

我们知道 SDK 中有很多事件,例如请求客服事件,请求访客评价事件等事件,在 V4.4.0 版本中我们开放了请求客服事件的拦截,App 可以通过实现 API 去监听 SDK 请求客服的事件并得到请求客服事件的一些数据,App 可以通过修改数据去干预请求客服的事件以达到请求特定客服的目的。下面的代码展示了如何去监听一个请求客服事件:

//一、首先在初始化的时候去赋值 YsfOption 中的 SDKEvents。然后去实现一个 EventProcessFactory 接口,每当有事件发生都会调用 eventOf 方法,可以根据事件的类型按需处理事件
YSFOptions options = new YSFOptions();
options.sdkEvents = new SDKEvents();
options.sdkEvents.eventProcessFactory = new EventProcessFactory() {
    @Override
    public UnicornEventBase eventOf(int eventType) {
        if(eventType == 0){
            //0 代表请求客服事件
            return new DemoRequestStaffEvent();
        } else if(eventType == 1){
            //请求客服结果事件
            return new DemoConnectionResultEvent();
        } else if(eventType == 2) {
            // 2 代表需要注册自定消息样式解析器的事件(切记不要使用一个参数的方法)
NIMClient.getService(YsfService.class).registerCustomAttachmentParser(MsgTypeEnum.appCustom,DemoMsgParser.getInstance());
        } else if(eventType == 4) {
            //点击聊天界面头像事件,具体实现参考 DemoAvatarClickEvent()
            return new DemoAvatarClickEvent();
        } else if(eventType == 5) {
            //SDK 申请权限事件,具体可参考 DemoRequestPermissionEvent 中的配置
            return new DemoRequestPermissionEvent();
        }
        return null;
    }
};
//二、去自定义一个请求客服事件拦截器 DemoRequestStaffEvent,当有请求客服事件的时候,就会调用 onEvent 方法,然后在自定义一个 DemoConnectionResultEvent ,当客服连接有结果的时候同样会回调它的 onEvent 方法
/**
 * 请求客服事件的拦截器
 */
public class DemoRequestStaffEvent implements UnicornEventBase<RequestStaffEntry>{
    /**
     * 当事件发生的时候,会回调这个方法,在这个方法中,事件是处于等待的状态的只有调用了
     * callback 中的方法,事件才会继续
     * @param entry 请求客服事件的一些参数
     * @param context 当前界面的 context 对象,使用之前请判断是否为 null
     * @param callback SDK 的回调对象 具体请查看 EventCallback
     */
    @Override
    public void onEvent(final RequestStaffEntry entry, Context context, final EventCallback<RequestStaffEntry> callback) {
        if(context == null){
            ToastUtil.showLongToast("请求客服的信息为:" + entry.toString());
        }else {
            //当处理成功之后可以调用 onProcessEventSuccess 通知 sdk 可以请求客服了
            AlertDialog dialog = new AlertDialog.Builder(context).setMessage("请求客服的信息为" + entry.toString()+"是否拦截")
                    .setPositiveButton("是", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            callback.onInterceptEvent();
                        }
                    })
                    .setNegativeButton("否", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            callback.onProcessEventSuccess(entry);
                        }
                    }).create();
            dialog.show();
        }

    }
}


/**
 * Describe:客户连接结果事件通知的类
 */
public class DemoConnectionResultEvent implements UnicornEventBase<ConnectionStaffResultEntry> {

    /**
     * 当客服连接请求
     * @param connectionStaffResultEntry  请求客服结果事件的 entry
     * @param context 当前界面的 context 对象,使用之前请判断是否为 null
     * @param callback SDK 的回调对象  注意:如果该事件 SDK 不需要回调的时候,这个对象会为 null,所以当使用的时候需要做一下非null判断
     */
    @Override
    public void onEvent(ConnectionStaffResultEntry connectionStaffResultEntry, Context context, EventCallback<ConnectionStaffResultEntry> callback) {
        if(connectionStaffResultEntry.getCode() == 200){
            ToastUtil.showToast("请求客服成功,请求到的客服类型为:" + connectionStaffResultEntry.getStaffType());
        } else {
            ToastUtil.showToast("请求客服失败,失败的类型" + connectionStaffResultEntry.getCode());
        }
    }
}

# 供外部调用的事件

因为 APP 在使用客服功能的过程中,有可能想自己控制回话过程,所以 SDK 提供了相关方法供 APP 调用,具体方法都在 EventService.class 中,用户可自行查看

  1. 结束会话事件,在 V5.2.0 版本中,SDK 开放了结束会话的事件,用户可以通过该事件结束当前会话 具体操作请参考 SDK 中 EventService.closeSession 方法

  2. 调起评价界面,在 V5.2.0 版本中,SDK 开放了调起评价界面的接口,用户可以通过该接口打开评价的相关界面,具体请查看 SDK 中的 EventService.openEvaluation 方法

  3. 请求客服事件,当 APP 想主动请求客服的时候,可以通过 EventService.requestStaff 方法去请求客服,具体参数已经在方法中说明

  4. 退出排队事件,在 V5.4.0 版本中,当 APP 想自己弹窗实现退出排队的时候,可以通过 EventService.quitQueue 方法去退出排队

  5. 清空未读消息,当我们希望清空未读消息的时候可以通过 POPManage 中的 clearUnreadCount 方法清空未读消息。非平台企业 shopId 参数为 -1,平台企业参数为相关 shopId 即可,具体使用规则也可以查看 Demo 中的 CallMethodDemoActivity 中的代码

/**
 * 请求客服的方法通过此方法可以请求客服
 *
 * @param humanOnly 是否只请求人工客服,true 则只请求人工客服 false 则为人工客服和机器人都可以
 *                  return 请求是否成功,有可能你当前的状态不需要请求客服,也有可能你已经在人工的状态了,那么也会返回 true
 * @param callback  返回回调
 */
EventService.requestStaff(boolean humanOnly, RequestStaffCallback callback)

/**
 * 请求客服的方法,可以通过该方法去请求客服
 *
 * @param exchange           可以通过 UnicornMessageBuilder.getSessionId 获取该值
 * @param humanOnly          是否只请求人工客服,true 则只请求人工客服 false 则为人工客服和机器人都可以
 * @param staffId            客服Id
 * @param groupId            客服组Id
 * @param requestStaffScenes 请求客服的当前场景,因为现在请求客服事件可以进行拦截,这个值是与 RequestStaffEntry 中 scenes 中相对应的
 * @param callback           返回回调
 */
EventService.requestStaff(String exchange, boolean humanOnly, long staffId, long groupId, int requestStaffScenes, RequestStaffCallback callback);

/**
 * 转接客服的接口,在必要的时候可以通过此方法进行客服的转接
 * 方法内部实现是,现结束当前客服的会话,然后在重新连接一下客服
 *
 * @param exchange                    可以通过 UnicornMessageBuilder.getSessionId 获取该值
 * @param staffId                     想要转接的客服 id
 * @param groupId                     想要转接的分组 id    如果同时设置 staffId 和 groupId 那么以 staffId 为主
 * @param closeSessionMsg             关闭客服的提示语
 * @param isHuman                     转接客服是否只请求人工
 * @param transferCloseResultCallback 转接客服中的关闭客服事件的回调
 * @param transferRequestCallback     转接客服中的请求客服事件的回调
 */
EventService.transferStaff(String exchange, long staffId, long groupId, String closeSessionMsg, boolean isHuman, TransferCloseResultCallback transferCloseResultCallback, TransferRequestCallback transferRequestCallback);

/**
 * 退出排队的方法
 *
 * @param exchange 可以通过 UnicornMessageBuilder.getSessionId 获取该值
 * @return 退出排队是否成功
 */
EventService.quitQueue(final String exchange);

/**
 * 打开评价界面,如果自定义了评价界面会跳转自定义的评价界面,如果没有自定义,则进行七鱼评价界面的流程
 *
 * @param activity      Activity对象
 * @param exchange      可以通过 UnicornMessageBuilder.getSessionId 获取该值
 * @param callbackWrapper   评价界面的回调
 */
EventService.openEvaluation(final Activity activity, final String exchange, final RequestCallbackWrapper callbackWrapper);

/**
 * 关闭会话的方法,可通过该方法结束 id 为 sessionId 的会话
 *
 * @param exchange        可以通过 UnicornMessageBuilder.getSessionId 获取该值
 * @param closeSessionMsg 关闭会话在消息流中的提示文案
 */
EventService.closeSession(final String exchange, String closeSessionMsg);

/**
 * 当开启再次评价,并设置自定义评价界面,调用放弃评价方法
 *
 * @param exchange        可以通过 UnicornMessageBuilder.getSessionId 获取该值
 */
EventService.cancelEvaluation(String exchange)

# 自定义本地消息

在 V4.7.0 版本中 SDK 开放了自定义本地消息的功能,开发者可以通过 SDK 开放的 API 实现任何样式的本地消息。具体实现涉及两块内容,一块是数据源(也就是 SDK 中的 MsgAttachment),一块是我们展示消息的 View (也就是 SDK 中的 UnicornMessageViewHolder),开发者可以通过实现 MsgAttachment 和 UnicornMessageViewHolder 来实现改功能,具体实现方案如下:

  1. 实现自己的 MsgAttachment ,数据源里面的数据开发者可以随意定义
  2. 实现自己的 UnicornMessageViewHolder,该类中主要涉及三个方法,getViewHolderResid (返回 item 的 layout布局),inflateFindView(进行 item 中的 findViewById 操作),bindContentView(数据绑定操作)。
  3. 通过监听 SDK 的事件,当 eventType = 2 的时候,去注册我们的数据源解析器,因为我们发送出去的消息都是一个 String 类型的消息,所以开发者需要在解析器中把这个 String 类型的消息解析成自己的 MsgAttachment。
  4. 通过 MsgCustomizationRegistry.registerMessageViewHolder 方法,将数据源和 viewHolder 联系起来。
  5. 通过 UnicornMessageBuilder.buildCustomMessage(开发者实现的 MsgAttachment) 方法去构建一条消息,然后通过 MessageService.sendMessage 去发送消息

具体实现基本就这 6 步,我们看一下每步实现的源码就会很清晰:

/**
 * 第一步实现自己的 MsgAttachment
 */
public class DemoAttachment implements MsgAttachment{

    public static final String TAG_STRING = "tag_string";
    public static final String TAG_URL = "tag_url";

    private String msgContent;

    private String imgUrl;

    public String getImgUrl() {
        return imgUrl;
    }

    public void setImgUrl(String imgUrl) {
        this.imgUrl = imgUrl;
    }

    public String getMsgContent() {
        return msgContent;
    }

    public void setMsgContent(String msgContent) {
        this.msgContent = msgContent;
    }

    //这个方法必须重写,因为在存入数据库的时候需要用到
    @Override
    public String toJson(boolean send) {
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put(TAG_STRING,getMsgContent());
            jsonObject.put(TAG_URL,getImgUrl());
        } catch (JSONException e) {
            //log
        }
        return jsonObject.toString();
    }

    @Override
    public boolean countToUnread() {
        return false;
    }

    @Override
    public String getContent(Context context) {
        return msgContent;
    }
}

/**
 * 第二步实现自己的 UnicornMessageViewHolder
 */
public class DemoCustomViewHolder extends UnicornMessageViewHolder{

    private TextView tvViewHolderDemoText;
    private TextView tvViewHolderDemoUrl;

    @Override
    public int getViewHolderResid() {
        return R.layout.viewholder_demo_custom;
    }

    @Override
    public void inflateFindView() {
        tvViewHolderDemoText = findViewById(R.id.tv_view_holder_demo_text);
        tvViewHolderDemoUrl = findViewById(R.id.tv_view_holder_demo_url);

    }

    @Override
    public void bindContentView(IMMessage message, Context context) {
        DemoAttachment attachment = (DemoAttachment) message.getAttachment();
        tvViewHolderDemoText.setText(attachment.getMsgContent());
        tvViewHolderDemoUrl.setText(attachment.getImgUrl());
        progressBar.setVisibility(View.GONE);
    }

    @Override
    protected boolean isMiddleItem() {
        return true;
    }

    @Override
    protected boolean showAvatar() {
        return false;
    }
}

/**
 * 第三步请见上文所述的拦截 SDK 事件一节
 */

/**
 * 第四步注册 Attachment 和 ViewHolder 的关系
 * 该步可以在任何地方调用,但切记在发送自定义消息之前
 */
MsgCustomizationRegistry.registerMessageViewHolder(DemoAttachment.class, DemoCustomViewHolder.class);

/**
 * 第五步创建消息并保存消息到本地
 */
IMMessage message = UnicornMessageBuilder.buildAppCustomMessage(attachment);
//具体参数请查看 MessageService 类的说明
MessageService.saveMessageToLocal(message, true, true);

# 自定义评价界面

在 V5.0.0 版本中我们开放了自定义评价界面的接口,整体的思路就是当用户点击评价或者客服邀请评价的时候,我们把评价事件交给 App 方处理,当 App 方处理之后调用 SDK 的接口回传评价结果就可以完成评价了,主要涉及的 SDK 接口有如下几个:

/**
 * Describe: 评价相关 Api class
 */
public class EvaluationApi {


    private OnEvaluationEventListener onEvaluationEventListener;

    /**
     * 评价事件监听类,用户可以通过调用 setOnEvaluationEventListener
     * 方法去设置自己实现的 OnEvaluationEventListener ,当用户设置了 OnEvaluationEventListener
     * 那么当用户触发了评价事件就会交给 App 方处理
     */
    public static abstract class OnEvaluationEventListener {
        /**
         * 评价状态改变
         *
         * @param state 0:不可评价,1:可评价,2:评价完成
         */
        public void onEvaluationStateChange(int state) {
        }

        /**
         * 邀评消息被点击,App 方可以在此方法启动自己的评价界面
         * @param entry 评价的相关数据
         */
        public void onEvaluationMessageClick(EvaluationOpenEntry entry, Context context) {
        }
    }

    private static class SingletonHolder {
        private static final EvaluationApi sInstance = new EvaluationApi();
    }

    public static EvaluationApi getInstance() {
        return SingletonHolder.sInstance;
    }


    public OnEvaluationEventListener getOnEvaluationEventListener() {
        return onEvaluationEventListener;
    }

    public void setOnEvaluationEventListener(OnEvaluationEventListener onEvaluationEventListener) {
        this.onEvaluationEventListener = onEvaluationEventListener;
    }

    /**
     * 评价
     *
     * @param shopCode 商家ID, 在 EvaluationOpenEntry 里面有这个值,只需要回传就可以了
     * @param sessionId 会话 ID ,在 EvaluationOpenEntry 里面有这个值,只需要回传就可以了
     * @param score    评分
     * @param remark   评价内容
     * @param tagList  标签
     * @param name     评价结果的文案,例如非常满意、满意、不满意等
     * @param callback 回调
     */
    public void evaluate(String shopCode, long sessionId, int score, String remark, List<String> tagList, String name, RequestCallbackWrapper<String> callback) {
        SessionManager.getInstance().getEvaluationManager().doEvaluate(shopCode, sessionId, score, remark, tagList, name, callback);
    }

}

当需要自定义评价界面的时候,App 首先需要实现一个自己的 OnEvaluationEventListener ,然后把实现的 OnEvaluationEventListener 赋值给 EvaluationApi 中的 onEvaluationEventListener 变量,例如如下代码:

EvaluationApi.getInstance().setOnEvaluationEventListener(new EvaluationApi.OnEvaluationEventListener() {
                @Override
                public void onEvaluationStateChange(int state) {
                    //当评价状态改变的时候,会调用该方法,所以当有需求的时候可以在改方法中做一些操作
                }

                @Override
                public void onEvaluationMessageClick(EvaluationOpenEntry entry, Context context) {
                    if (context == null)
                        return;
                    Intent intent = new Intent(context, DemoEvaluationActivity.class);
                    intent.putExtra(ENTRYTAG, entry);
                    context.startActivity(intent);
                }
            });

从上面的代码中看到,当调用 onEvaluationMessageClick 方法的时候我们跳转到了 DemoEvaluationActivity(也就是我们自定义的评价界面),然后把评价里面的数据给到了DemoEvaluationActivity,这样就可以在 DemoEvaluationActivity 展示评价的数据了。

如果想要查看评价里面的数据具体如何使用,请参考 Demo 工程中的 DemoEvaluationActivity 类

# 自定义商品类型消息

在 SDK V5.5.0 版本中新增了自定义商品类型的消息,主要的功能为当客服发送 iframe 商品消息的时候,SDK 可以自定义该类型消息的样式。

# 实现方式

一、在客服端需要发送如下格式的数据:

{
	"productCustomField":"2342342342342342",
	"isOpenCustomProduct":true,
	"sendByUser":1,
	"actionText":"fasonglianjiessz",
	"actionTextColor":"#FF1493",
	"template":"pictureLink",
	"picture": "https://ysf.nosdn.127.net/32FD7DAACD42CE79DD03B1B82F690B7B?imageView&type=png",
	"title": "[自营]Orange Juice",
	"desc": "Relish the goodness of hand-picked oranges from the finest orchards. Foster a healthy lifestyle with the benefits of oranges. 100 percent orange juice with no added sugar for a healthy you.",
	"price": "¥999",
	"url": "http://10.242.118.244:8000/shop-detail.html",
	"activity":"活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动活动",
	"activityHref": "https://www.baidu.com",
	"showCustomMsg":1
}

之前发送 iframe 商品消息是没有 productCustomField 和 isOpenCustomProduct 这两个字段的,当 isOpenCustomProduct 为 true 的时候,商品消息的样式为自定义的,当为 false 的时候,使用 SDK 中默认的商品样式。所以如果想要使用自定义商品样式,发送数据中 isOpenCustomProduct 必须为 true。productCustomField 字段的意思是自定义商品消息的数据信息,用户可以把想要展示的自定义数据放到该字段中,当 SDK 收到该字段,会将该字段透传到 CustomProductParserparseCustomProduct 方法中

二、定义 Attachment 和 ViewHolder

一条消息由两部分组成,展示的数据和展示的样式,下面分两步讲解自定义商品消息体的实现。

因为每一种类型的消息数据格式都是不同的,自定义商品消息也不例外,所以需要定义自定义商品消息的 Attachment ,Attachment 中的字段由开发者自定义,想要展示什么数据就定义什么字段,例如:

//Attachment 一定要继承 UnicornAttachmentBase
public class DemoCustomProductAttachment extends UnicornAttachmentBase {
	//想要展示的信息
    private String customProductString;

	// attach 字段可以通过 CustomProductParser 的 parseCustomProduct 的参数获取
	 public DemoCustomProductAttachment(String attach) {
        super(attach);
    }


    public String getCustomProductString() {
        return customProductString;
    }

    public void setCustomProductString(String customProductString) {
        this.customProductString = customProductString;
    }
}

上面的代码只是简单的把 attach 直接拿过来使用,现实情况,用户可以把 productCustomField 定义为一个 String 类型的 json,然后把 productCustomField 解析出来更多的字段使用

上面自定义消息想要展示的数据已经定义好了,下面需要定义一下自定义商品消息的消息体,也就是 ViewHolder,用户可以通过继承 UnicornMessageViewHolder 实现,例如:

public class DemoCustomProductHolder extends UnicornMessageViewHolder {
    private TextView tvDemoViewHolder;
	//返回消息样式的layout id
    @Override
    public int getViewHolderResId() {
        return R.layout.viewholder_product;
    }
	//初始化相关 view
    @Override
    public void inflateFindView() {
        tvDemoViewHolder = findViewById(R.id.tv_demo_view_holder);
    }
	//进行数据绑定
    @Override
    public void bindContentView(IMMessage message, Context context) {
        DemoCustomProductAttachment demoCustomProductAttachment = (DemoCustomProductAttachment) message.getAttachment();
        tvDemoViewHolder.setText(demoCustomProductAttachment.getCustomProductString());
    }
}

上面的代码中只是比较简单的,用户可以自行扩展布局,用户也可以查看 UnicornMessageViewHolder 中的相关方法修改消息的样式

三、定义解析器 Parser

因为当客服发送过来的消息是一个 String 类型的数据,所以需要把这个 String 解析成自定义商品消息对应的 Attachment,当用户需要解析自定义商品消息的时候 SDK 会去回调 CustomProductParserparseCustomProduct 方法,用户可以在 YSFOptionscustomProductParser 设置自己的 Parser,例如:

YSFOptions options = new YSFOptions();
options.customProductParser = DemoMsgParser.getInstance();

上面的代码中指定了 DemoMsgParser 为自定义商品消息的解析器,下面看一下里面的具体实现:

public class DemoMsgParser implements CustomProductParser {
	//单例对象
    private static DemoMsgParser instance;

    public static DemoMsgParser getInstance() {
        if (instance == null) {
            instance = new DemoMsgParser();
        }
        return instance;
    }

    private DemoMsgParser() {
    }
    //重写了自定义商品消息的解析方法,当有自定义商品消息的时候会回调该方法,用户需要把
    //解析完成 Attachment return 给 SDK
    @Override
    public UnicornAttachmentBase parseCustomProduct(String attach) {
        DemoCustomProductAttachment attachment = new DemoCustomProductAttachment(attach);
        try {
            JSONObject jsonObject = new JSONObject(attach);
            attachment.setCustomProductString(jsonObject.getString("productCustomField"));
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return attachment;
    }
}

上面的代码中实现了 CustomProductParser 接口,并实现了 parseCustomProduct 方法,该方法中我们我们通过解析 attach 字段,然后拿到了 productCustomField 字段(也就是客服发送过来的 productCustomField) 字段。客服端也可以把 productCustomField 里面塞一个 String 类型的 json 。这样 productCustomField 就可以解析出来更多的字段供展示使用。

四、注册 Attachment 和 ViewHolder 的对应关系

因为 SDK 需要把数据类型和 UI 对应起来,所以用户需要手动调用 MsgCustomizationRegistry.registerMessageViewHolder 方法告诉 SDK Attachment 和 ViewHolder 的对应关系(切记在 init 之后,打开客服界面之前调用),下面看代码:

MsgCustomizationRegistry.registerMessageViewHolder(DemoCustomProductAttachment.class, DemoCustomProductHolder.class);

上面展示了完成自定义商品消息的流程,如果有不理解的地方可以查看 Demo 中的源码和源码中的相关注释

# 访客工单相关

# 访客自助提工单

在 V5.7.0 版本中,SDK 增加了自助提工单的功能,用户可以通过在后台配置快捷入口和 "+" 中配置提工单的功能,SDK 中也开放了主动调用工单功能的接口,主要涉及 SDK 中的以下内容:

1、WorkSheetAction

如果用户只是想在 “+” 中使用自助提工单的功能,那么可以在 ActionPanelOptions 的 ActionListProvider 中加入 WorkSheetAction,切记不要忘记给 WorkSheetAction 设置 templateId

# 访客自助查询工单

在 V5.12.0 版本中,SDK 增加了访客自助查询工单的功能,用户可以通过后台配置设置展示访客查询工单的功能,也可以通过 SDK 内部配置访客查询工单列表功能。

# 在 "+" 中配置查询访客工单

SDK 中通过 InquireWorkSheetAction 实现访客自助提供单功能,开发者可以通过在 ActionListProvider.getActionList 方法中返回 InquireWorkSheetAction 的方式,增加访客查询工单的功能,具体 ActionListProvider.getActionList 的使用请查看 自定义样式 -> 输入栏样式 查看,具体代码如下:

private InputPanelOptions configInputOption() {
        InputPanelOptions inputPanelOptions = new InputPanelOptions();
        inputPanelOptions.showActionPanel = true;
        inputPanelOptions.actionPanelOptions = new ActionPanelOptions();
        inputPanelOptions.actionPanelOptions.actionListProvider = new ActionListProvider() {
	    @Override
	    public List<BaseAction> getActionList() {
	        List<BaseAction> list = new ArrayList<>();
	        //加入自助查询工单 Action
	        InquireWorkSheetAction workSheetAction = new InquireWorkSheetAction(R.drawable.ic_work_sheet,R.string.work_sheet_str);
	        List<Long> listTmpId = new ArrayList<>();
	        listTmpId.add(123456);
	        workSheetAction.setTemplateIds(listTmpId);
	        list.add(workSheetAction);
	        return list;
	    };
        return inputPanelOptions;
    }

# 自助启动查询工单界面

开发者如果想在客服界面之外启动查询工单 Activity,可以通过 UnicornWorkSheetHelper 的如下方法启动:

 /**
     * 打开用户工单列表界面
     * @param context Context
     * @param templateIds 工单模板 id
     * @param isOpenUrge 是否打开催单功能
     * @param exchange 如果是平台版本传递 shopId,如果是非平台可以通过 UnicornMessageBuilder.getSessionId 获取
     */
    public static void openUserWorkSheetActivity(Context context, List<Long> templateIds, boolean isOpenUrge, String exchange) {
        UserWorkSheetListActivity.start(context, templateIds, isOpenUrge, exchange);
    }

上述两种方式都涉及到了 templateId, 该字段为工单模板 ID,可通过 开发指南 -> 服务端 -> 获取工单模板 接口获取