com.qiscus.sdk.presenter.QiscusChatPresenter.java Source code

Java tutorial

Introduction

Here is the source code for com.qiscus.sdk.presenter.QiscusChatPresenter.java

Source

/*
 * Copyright (c) 2016 Qiscus.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.qiscus.sdk.presenter;

import android.net.Uri;
import android.support.v4.util.Pair;
import android.webkit.MimeTypeMap;

import com.qiscus.sdk.Qiscus;
import com.qiscus.sdk.data.local.QiscusCacheManager;
import com.qiscus.sdk.data.model.QiscusAccount;
import com.qiscus.sdk.data.model.QiscusChatRoom;
import com.qiscus.sdk.data.model.QiscusComment;
import com.qiscus.sdk.data.remote.QiscusApi;
import com.qiscus.sdk.data.remote.QiscusPusherApi;
import com.qiscus.sdk.event.QiscusChatRoomEvent;
import com.qiscus.sdk.event.QiscusCommentReceivedEvent;
import com.qiscus.sdk.util.QiscusAndroidUtil;
import com.qiscus.sdk.util.QiscusFileUtil;
import com.qiscus.sdk.util.QiscusImageUtil;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.json.JSONObject;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Func2;
import rx.schedulers.Schedulers;

public class QiscusChatPresenter extends QiscusPresenter<QiscusChatPresenter.View> {

    private QiscusChatRoom room;
    private int currentTopicId;
    private QiscusAccount qiscusAccount;
    private AtomicInteger lastDeliveredCommentId;
    private AtomicInteger lastReadCommentId;
    private Func2<QiscusComment, QiscusComment, Integer> commentComparator = (lhs,
            rhs) -> lhs.getId() != -1 && rhs.getId() != -1 ? QiscusAndroidUtil.compare(rhs.getId(), lhs.getId())
                    : rhs.getTime().compareTo(lhs.getTime());

    public QiscusChatPresenter(View view, QiscusChatRoom room) {
        super(view);
        this.room = room;
        this.currentTopicId = room.getLastTopicId();
        qiscusAccount = Qiscus.getQiscusAccount();
        lastDeliveredCommentId = new AtomicInteger(0);
        lastReadCommentId = new AtomicInteger(0);

        updateReadState();

        listenRoomEvent();
        if (!EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().register(this);
        }
    }

    private void doInIo(Runnable runnable) {
        Observable.just(null).doOnNext(o -> runnable.run()).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread()).compose(bindToLifecycle()).subscribe(o -> {
                }, throwable -> {
                });
    }

    private void updateReadState() {
        doInIo(() -> {
            updateLastReadComment(Qiscus.getDataStore().getLatestReadComment(currentTopicId));
            updateLastDeliveredComment(Qiscus.getDataStore().getLatestDeliveredComment(currentTopicId));
        });
    }

    private void commentSuccess(QiscusComment qiscusComment) {
        qiscusComment.setState(QiscusComment.STATE_ON_QISCUS);
        QiscusComment savedQiscusComment = Qiscus.getDataStore().getComment(qiscusComment.getId(),
                qiscusComment.getUniqueId());
        if (savedQiscusComment != null && savedQiscusComment.getState() > qiscusComment.getState()) {
            qiscusComment.setState(savedQiscusComment.getState());
        }
        Qiscus.getDataStore().addOrUpdate(qiscusComment);
    }

    private void commentFail(QiscusComment qiscusComment) {
        qiscusComment.setState(QiscusComment.STATE_FAILED);
        QiscusComment savedQiscusComment = Qiscus.getDataStore().getComment(qiscusComment.getId(),
                qiscusComment.getUniqueId());
        if (savedQiscusComment != null) {
            if (savedQiscusComment.getState() < qiscusComment.getState()) {
                qiscusComment.setState(QiscusComment.STATE_FAILED);
                Qiscus.getDataStore().addOrUpdate(qiscusComment);
            } else {
                qiscusComment.setState(savedQiscusComment.getState());
            }
        } else {
            qiscusComment.setState(QiscusComment.STATE_FAILED);
            Qiscus.getDataStore().addOrUpdate(qiscusComment);
        }
    }

    public void sendComment(String content) {
        QiscusComment qiscusComment = QiscusComment.generateMessage(content, room.getId(), currentTopicId);
        view.onSendingComment(qiscusComment);
        QiscusApi.getInstance().postComment(qiscusComment)
                .doOnSubscribe(() -> Qiscus.getDataStore().add(qiscusComment)).doOnNext(this::commentSuccess)
                .doOnError(throwable -> commentFail(qiscusComment)).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread()).compose(bindToLifecycle()).subscribe(commentSend -> {
                    if (commentSend.getTopicId() == currentTopicId) {
                        view.onSuccessSendComment(commentSend);
                    }
                }, throwable -> {
                    throwable.printStackTrace();
                    if (qiscusComment.getTopicId() == currentTopicId) {
                        view.onFailedSendComment(qiscusComment);
                    }
                });
    }

    public void sendFile(File file) {
        File compressedFile = file;
        if (file.getName().endsWith(".gif")) {
            compressedFile = QiscusFileUtil.saveFile(compressedFile, currentTopicId);
        } else if (QiscusImageUtil.isImage(file)) {
            try {
                compressedFile = QiscusImageUtil.compressImage(Uri.fromFile(file), currentTopicId);
            } catch (NullPointerException e) {
                view.showError("Can not read file, please make sure that is not corrupted file!");
                return;
            }

        } else {
            compressedFile = QiscusFileUtil.saveFile(compressedFile, currentTopicId);
        }

        QiscusComment qiscusComment = QiscusComment.generateMessage(
                String.format("[file] %s [/file]", compressedFile.getPath()), room.getId(), currentTopicId);
        qiscusComment.setDownloading(true);
        view.onSendingComment(qiscusComment);

        File finalCompressedFile = compressedFile;
        QiscusApi.getInstance()
                .uploadFile(compressedFile, percentage -> qiscusComment.setProgress((int) percentage))
                .doOnSubscribe(() -> Qiscus.getDataStore().add(qiscusComment)).flatMap(uri -> {
                    qiscusComment.setMessage(String.format("[file] %s [/file]", uri.toString()));
                    return QiscusApi.getInstance().postComment(qiscusComment);
                }).doOnNext(commentSend -> {
                    Qiscus.getDataStore().addOrUpdateLocalPath(commentSend.getTopicId(), commentSend.getId(),
                            finalCompressedFile.getAbsolutePath());
                    qiscusComment.setDownloading(false);
                    commentSuccess(commentSend);
                }).doOnError(throwable -> {
                    qiscusComment.setDownloading(false);
                    commentFail(qiscusComment);
                }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).compose(bindToLifecycle())
                .subscribe(commentSend -> {
                    if (commentSend.getTopicId() == currentTopicId) {
                        view.onSuccessSendComment(commentSend);
                    }
                }, throwable -> {
                    throwable.printStackTrace();
                    if (qiscusComment.getTopicId() == currentTopicId) {
                        view.onFailedSendComment(qiscusComment);
                    }
                });
    }

    public void resendComment(QiscusComment qiscusComment) {
        if (qiscusComment.isAttachment()) {
            resendFile(qiscusComment);
        } else {
            qiscusComment.setState(QiscusComment.STATE_SENDING);
            qiscusComment.setTime(new Date());
            view.onNewComment(qiscusComment);
            QiscusApi.getInstance().postComment(qiscusComment)
                    .doOnSubscribe(() -> Qiscus.getDataStore().addOrUpdate(qiscusComment))
                    .doOnNext(this::commentSuccess).doOnError(throwable -> commentFail(qiscusComment))
                    .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                    .compose(bindToLifecycle()).subscribe(commentSend -> {
                        if (commentSend.getTopicId() == currentTopicId) {
                            view.onSuccessSendComment(commentSend);
                        }
                    }, throwable -> {
                        throwable.printStackTrace();
                        if (qiscusComment.getTopicId() == currentTopicId) {
                            view.onFailedSendComment(qiscusComment);
                        }
                    });
        }
    }

    private void resendFile(QiscusComment qiscusComment) {
        File file = new File(qiscusComment.getAttachmentUri().toString());
        qiscusComment.setDownloading(true);
        qiscusComment.setState(QiscusComment.STATE_SENDING);
        qiscusComment.setTime(new Date());
        view.onNewComment(qiscusComment);
        if (!file.exists()) { //Not exist because the uri is not local
            qiscusComment.setProgress(100);
            QiscusApi.getInstance().postComment(qiscusComment)
                    .doOnSubscribe(() -> Qiscus.getDataStore().addOrUpdate(qiscusComment)).doOnNext(commentSend -> {
                        Qiscus.getDataStore().addOrUpdateLocalPath(commentSend.getTopicId(), commentSend.getId(),
                                file.getAbsolutePath());
                        qiscusComment.setDownloading(false);
                        commentSuccess(commentSend);
                    }).doOnError(throwable -> {
                        qiscusComment.setDownloading(false);
                        commentFail(qiscusComment);
                    }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                    .compose(bindToLifecycle()).subscribe(commentSend -> {
                        if (commentSend.getTopicId() == currentTopicId) {
                            view.onSuccessSendComment(commentSend);
                        }
                    }, throwable -> {
                        throwable.printStackTrace();
                        if (qiscusComment.getTopicId() == currentTopicId) {
                            view.onFailedSendComment(qiscusComment);
                        }
                    });
        } else {
            qiscusComment.setProgress(0);
            QiscusApi.getInstance().uploadFile(file, percentage -> qiscusComment.setProgress((int) percentage))
                    .doOnSubscribe(() -> Qiscus.getDataStore().addOrUpdate(qiscusComment)).flatMap(uri -> {
                        qiscusComment.setMessage(String.format("[file] %s [/file]", uri.toString()));
                        return QiscusApi.getInstance().postComment(qiscusComment);
                    }).doOnNext(commentSend -> {
                        Qiscus.getDataStore().addOrUpdateLocalPath(commentSend.getTopicId(), commentSend.getId(),
                                file.getAbsolutePath());
                        qiscusComment.setDownloading(false);
                        commentSuccess(commentSend);
                    }).doOnError(throwable -> {
                        qiscusComment.setDownloading(false);
                        commentFail(qiscusComment);
                    }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                    .compose(bindToLifecycle()).subscribe(commentSend -> {
                        if (commentSend.getTopicId() == currentTopicId) {
                            view.onSuccessSendComment(commentSend);
                        }
                    }, throwable -> {
                        throwable.printStackTrace();
                        if (qiscusComment.getTopicId() == currentTopicId) {
                            view.onFailedSendComment(qiscusComment);
                        }
                    });
        }
    }

    public void deleteComment(QiscusComment qiscusComment) {
        doInIo(() -> Qiscus.getDataStore().delete(qiscusComment));
        view.onCommentDeleted(qiscusComment);
    }

    private Observable<Pair<QiscusChatRoom, List<QiscusComment>>> getInitRoomData() {
        return QiscusApi.getInstance().getChatRoomComments(room.getId()).doOnNext(roomData -> {
            checkForLastRead(roomData.second);
            for (QiscusComment qiscusComment : roomData.second) {
                if (qiscusComment.getId() > lastDeliveredCommentId.get()) {
                    qiscusComment.setState(QiscusComment.STATE_ON_QISCUS);
                } else if (qiscusComment.getId() > lastReadCommentId.get()) {
                    qiscusComment.setState(QiscusComment.STATE_DELIVERED);
                } else {
                    qiscusComment.setState(QiscusComment.STATE_READ);
                }
                Qiscus.getDataStore().addOrUpdate(qiscusComment);
            }
            Collections.sort(roomData.second,
                    (lhs, rhs) -> lhs.getId() != -1 && rhs.getId() != -1
                            ? QiscusAndroidUtil.compare(rhs.getId(), lhs.getId())
                            : rhs.getTime().compareTo(lhs.getTime()));

            if (!roomData.first.isGroup()) {
                roomData.first.setName(room.getName());
            }
            roomData.first.setSubtitle(room.getSubtitle());
            Qiscus.getDataStore().addOrUpdate(roomData.first);
        }).subscribeOn(Schedulers.io()).onErrorReturn(throwable -> null);
    }

    private void checkForLastRead(List<QiscusComment> qiscusComments) {
        for (QiscusComment qiscusComment : qiscusComments) {
            if (!qiscusComment.getSenderEmail().equals(qiscusAccount.getEmail())
                    && qiscusComment.getId() > lastReadCommentId.get()) {
                lastReadCommentId.set(qiscusComment.getId());
                lastDeliveredCommentId.set(lastReadCommentId.get());
            }
        }
    }

    private Observable<List<QiscusComment>> getCommentsFromNetwork(int lastCommentId) {
        return QiscusApi.getInstance().getComments(room.getId(), currentTopicId, lastCommentId)
                .doOnNext(qiscusComment -> {
                    qiscusComment.setRoomId(room.getId());
                    if (qiscusComment.getId() > lastDeliveredCommentId.get()) {
                        qiscusComment.setState(QiscusComment.STATE_ON_QISCUS);
                    } else if (qiscusComment.getId() > lastReadCommentId.get()) {
                        qiscusComment.setState(QiscusComment.STATE_DELIVERED);
                    } else {
                        qiscusComment.setState(QiscusComment.STATE_READ);
                    }
                    Qiscus.getDataStore().addOrUpdate(qiscusComment);
                }).toSortedList(commentComparator).doOnNext(this::checkForLastRead).subscribeOn(Schedulers.io());
    }

    private Observable<List<QiscusComment>> getLocalComments(int count) {
        return Qiscus.getDataStore().getObservableComments(currentTopicId, 2 * count).flatMap(Observable::from)
                .toSortedList(commentComparator).map(comments -> {
                    if (comments.size() >= count) {
                        return comments.subList(0, count);
                    }
                    return comments;
                }).doOnNext(comments -> {
                    checkForLastRead(comments);
                    for (QiscusComment qiscusComment : comments) {
                        if (qiscusComment.getState() == QiscusComment.STATE_SENDING) {
                            qiscusComment.setState(QiscusComment.STATE_FAILED);
                            Qiscus.getDataStore().addOrUpdate(qiscusComment);
                        } else if (qiscusComment.getState() != QiscusComment.STATE_FAILED
                                && qiscusComment.getState() != QiscusComment.STATE_READ) {
                            if (qiscusComment.getId() > lastDeliveredCommentId.get()) {
                                qiscusComment.setState(QiscusComment.STATE_ON_QISCUS);
                            } else if (qiscusComment.getId() > lastReadCommentId.get()) {
                                qiscusComment.setState(QiscusComment.STATE_DELIVERED);
                            } else {
                                qiscusComment.setState(QiscusComment.STATE_READ);
                            }
                            Qiscus.getDataStore().addOrUpdate(qiscusComment);
                        }
                    }
                }).subscribeOn(Schedulers.io());
    }

    public void loadComments(int count) {
        Observable.merge(getInitRoomData(), getLocalComments(count).map(comments -> Pair.create(room, comments)))
                .filter(qiscusChatRoomListPair -> qiscusChatRoomListPair != null)
                .subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
                .compose(bindToLifecycle()).subscribe(roomData -> {
                    if (view != null) {
                        room = roomData.first;
                        view.initRoomData(roomData.first, roomData.second);
                        view.dismissLoading();
                    }
                }, throwable -> {
                    throwable.printStackTrace();
                    if (view != null) {
                        view.showError("Failed to load comments!");
                        view.dismissLoading();
                    }
                });
    }

    private List<QiscusComment> cleanFailedComments(List<QiscusComment> qiscusComments) {
        List<QiscusComment> comments = new ArrayList<>();
        for (QiscusComment qiscusComment : qiscusComments) {
            if (qiscusComment.getId() != -1) {
                comments.add(qiscusComment);
            }
        }
        return comments;
    }

    private boolean isValidOlderComments(List<QiscusComment> qiscusComments, QiscusComment lastQiscusComment) {
        if (qiscusComments.isEmpty())
            return false;

        qiscusComments = cleanFailedComments(qiscusComments);
        boolean containsLastValidComment = qiscusComments.size() <= 0 || lastQiscusComment.getId() == -1;
        int size = qiscusComments.size();

        if (size == 1) {
            return qiscusComments.get(0).getCommentBeforeId() == 0;
        }

        for (int i = 0; i < size - 1; i++) {
            if (!containsLastValidComment
                    && qiscusComments.get(i).getId() == lastQiscusComment.getCommentBeforeId()) {
                containsLastValidComment = true;
            }

            if (qiscusComments.get(i).getCommentBeforeId() != qiscusComments.get(i + 1).getId()) {
                return false;
            }
        }
        return containsLastValidComment;
    }

    public void loadOlderCommentThan(QiscusComment qiscusComment) {
        view.showLoadMoreLoading();
        Qiscus.getDataStore().getObservableOlderCommentsThan(qiscusComment, currentTopicId, 40)
                .flatMap(Observable::from)
                .filter(qiscusComment1 -> qiscusComment.getId() == -1
                        || qiscusComment1.getId() < qiscusComment.getId())
                .toSortedList(commentComparator).map(comments -> {
                    if (comments.size() >= 20) {
                        return comments.subList(0, 20);
                    }
                    return comments;
                }).doOnNext(comments -> {
                    checkForLastRead(comments);
                    for (QiscusComment comment : comments) {
                        if (qiscusComment.getState() == QiscusComment.STATE_SENDING) {
                            qiscusComment.setState(QiscusComment.STATE_FAILED);
                            Qiscus.getDataStore().addOrUpdate(qiscusComment);
                        } else if (qiscusComment.getState() != QiscusComment.STATE_FAILED
                                && qiscusComment.getState() != QiscusComment.STATE_READ) {
                            if (comment.getId() > lastDeliveredCommentId.get()) {
                                comment.setState(QiscusComment.STATE_ON_QISCUS);
                            } else if (comment.getId() > lastReadCommentId.get()) {
                                comment.setState(QiscusComment.STATE_DELIVERED);
                            } else {
                                comment.setState(QiscusComment.STATE_READ);
                            }
                            Qiscus.getDataStore().addOrUpdate(comment);
                        }
                    }
                })
                .flatMap(comments -> isValidOlderComments(comments, qiscusComment)
                        ? Observable.from(comments).toSortedList(commentComparator)
                        : getCommentsFromNetwork(qiscusComment.getId()).map(comments1 -> {
                            for (QiscusComment localComment : comments) {
                                if (localComment.getState() <= QiscusComment.STATE_SENDING) {
                                    comments1.add(localComment);
                                }
                            }
                            return comments1;
                        }))
                .subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
                .compose(bindToLifecycle()).subscribe(comments -> {
                    if (view != null) {
                        view.onLoadMore(comments);
                        view.dismissLoading();
                    }
                }, throwable -> {
                    throwable.printStackTrace();
                    if (view != null) {
                        view.showError("Failed to load comments!");
                        view.dismissLoading();
                    }
                });
    }

    private void listenRoomEvent() {
        QiscusPusherApi.getInstance().listenRoom(room);
    }

    @Subscribe
    public void onRoomEvent(QiscusChatRoomEvent event) {
        if (event.getTopicId() == currentTopicId) {
            switch (event.getEvent()) {
            case TYPING:
                view.onUserTyping(event.getUser(), event.isTyping());
                break;
            case DELIVERED:
                QiscusComment deliveredComment = Qiscus.getDataStore().getComment(event.getCommentId(),
                        event.getCommentUniqueId());
                if (deliveredComment != null) {
                    deliveredComment.setId(event.getCommentId());
                    updateLastDeliveredComment(deliveredComment);
                    doInIo(() -> {
                        if (QiscusComment.STATE_DELIVERED > deliveredComment.getState()) {
                            deliveredComment.setState(QiscusComment.STATE_DELIVERED);
                            Qiscus.getDataStore().update(deliveredComment);
                        }
                    });
                } else {
                    lastDeliveredCommentId.set(event.getCommentId());
                    view.updateLastDeliveredComment(lastDeliveredCommentId.get());
                }
                break;
            case READ:
                QiscusComment readComment = Qiscus.getDataStore().getComment(event.getCommentId(),
                        String.valueOf(event.getCommentId()));
                if (readComment != null) {
                    readComment.setId(event.getCommentId());
                    updateLastReadComment(readComment);
                    doInIo(() -> {
                        if (QiscusComment.STATE_READ > readComment.getState()) {
                            readComment.setState(QiscusComment.STATE_READ);
                            Qiscus.getDataStore().update(readComment);
                        }
                    });
                } else {
                    lastReadCommentId.set(event.getCommentId());
                    lastDeliveredCommentId.set(lastReadCommentId.get());
                    view.updateLastReadComment(lastReadCommentId.get());
                }
                break;
            }
        }
    }

    private void updateLastReadComment(QiscusComment qiscusComment) {
        if (qiscusComment != null && qiscusComment.getId() > lastReadCommentId.get()) {
            lastReadCommentId.set(qiscusComment.getId());
            lastDeliveredCommentId.set(lastReadCommentId.get());
            QiscusAndroidUtil.runOnUIThread(() -> {
                if (view != null) {
                    view.updateLastReadComment(lastReadCommentId.get());
                }
            });
        }
    }

    private void updateLastDeliveredComment(QiscusComment qiscusComment) {
        if (qiscusComment != null && qiscusComment.getId() > lastDeliveredCommentId.get()) {
            lastDeliveredCommentId.set(qiscusComment.getId());
            QiscusAndroidUtil.runOnUIThread(() -> {
                if (view != null) {
                    view.updateLastDeliveredComment(lastDeliveredCommentId.get());
                }
            });
        }
    }

    @Subscribe
    public void onCommentReceivedEvent(QiscusCommentReceivedEvent event) {
        if (event.getQiscusComment().getTopicId() == currentTopicId) {
            onGotNewComment(event.getQiscusComment());
        }
    }

    private void onGotNewComment(QiscusComment qiscusComment) {
        if (qiscusComment.getSenderEmail().equalsIgnoreCase(qiscusAccount.getEmail())) {
            doInIo(() -> commentSuccess(qiscusComment));
        } else {
            updateLastReadComment(qiscusComment);
            qiscusComment.setState(QiscusComment.STATE_READ);
            doInIo(() -> Qiscus.getDataStore().addOrUpdate(qiscusComment));
        }

        if (qiscusComment.isAttachment()) {
            doInIo(() -> {
                String path = QiscusFileUtil.generateFilePath(qiscusComment.getAttachmentName(),
                        qiscusComment.getTopicId());
                boolean exist = QiscusFileUtil.isContains(path);
                if (!exist) {
                    String message = qiscusComment.getMessage();
                    int fileNameEndIndex = message.lastIndexOf(" [/file]");
                    int fileNameBeginIndex = message.lastIndexOf('/', fileNameEndIndex) + 1;
                    String fileName = message.substring(fileNameBeginIndex, fileNameEndIndex);
                    path = QiscusFileUtil.generateFilePath(fileName, qiscusComment.getTopicId());
                    exist = QiscusFileUtil.isContains(path);
                }
                if (exist) {
                    Qiscus.getDataStore().addOrUpdateLocalPath(qiscusComment.getTopicId(), qiscusComment.getId(),
                            path);
                }
            });
        }

        if (qiscusComment.getTopicId() == currentTopicId) {
            doInIo(() -> {
                if (!qiscusComment.getSenderEmail().equalsIgnoreCase(qiscusAccount.getEmail())
                        && QiscusCacheManager.getInstance().getLastChatActivity().first) {
                    QiscusPusherApi.getInstance().setUserRead(room.getId(), currentTopicId, qiscusComment.getId(),
                            qiscusComment.getUniqueId());
                }
            });
            view.onNewComment(qiscusComment);
        }
    }

    public void downloadFile(final QiscusComment qiscusComment) {
        if (qiscusComment.isDownloading()) {
            return;
        }

        File file = Qiscus.getDataStore().getLocalPath(qiscusComment.getId());
        if (file == null) {
            qiscusComment.setDownloading(true);
            QiscusApi.getInstance()
                    .downloadFile(qiscusComment.getTopicId(), qiscusComment.getAttachmentUri().toString(),
                            qiscusComment.getAttachmentName(),
                            percentage -> qiscusComment.setProgress((int) percentage))
                    .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                    .compose(bindToLifecycle()).doOnNext(file1 -> {
                        if (QiscusImageUtil.isImage(file1)) {
                            QiscusImageUtil.addImageToGallery(file1);
                        }
                        qiscusComment.setDownloading(false);
                        Qiscus.getDataStore().addOrUpdateLocalPath(qiscusComment.getTopicId(),
                                qiscusComment.getId(), file1.getAbsolutePath());
                    }).subscribe(file1 -> {
                        view.refreshComment(qiscusComment);
                        if (qiscusComment.getType() == QiscusComment.Type.AUDIO) {
                            qiscusComment.playAudio();
                        } else if (qiscusComment.getType() == QiscusComment.Type.FILE) {
                            view.onFileDownloaded(file1, MimeTypeMap.getSingleton()
                                    .getMimeTypeFromExtension(qiscusComment.getExtension()));
                        }
                    }, throwable -> {
                        throwable.printStackTrace();
                        qiscusComment.setDownloading(false);
                        view.showError("Failed to download file!");
                    });
        } else {
            if (qiscusComment.getType() == QiscusComment.Type.AUDIO) {
                qiscusComment.playAudio();
            } else if (qiscusComment.getType() == QiscusComment.Type.IMAGE) {
                view.startPhotoViewer(qiscusComment);
            } else {
                view.onFileDownloaded(file,
                        MimeTypeMap.getSingleton().getMimeTypeFromExtension(qiscusComment.getExtension()));
            }
        }
    }

    public void clickChatButton(JSONObject jsonButton) {
        if ("postback".equals(jsonButton.opt("type"))) {
            sendCommentPostBack(jsonButton.optString("label", "Button"),
                    jsonButton.optJSONObject("payload").toString());
        }
    }

    public void sendCommentPostBack(String content, String payload) {
        QiscusComment qiscusComment = QiscusComment.generateMessage(content, room.getId(), currentTopicId);
        view.onSendingComment(qiscusComment);
        QiscusApi.getInstance().postCommentPostBack(qiscusComment, payload)
                .doOnSubscribe(() -> Qiscus.getDataStore().add(qiscusComment)).doOnNext(this::commentSuccess)
                .doOnError(throwable -> commentFail(qiscusComment)).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread()).compose(bindToLifecycle()).subscribe(commentSend -> {
                    if (commentSend.getTopicId() == currentTopicId) {
                        view.onSuccessSendComment(commentSend);
                    }
                }, throwable -> {
                    throwable.printStackTrace();
                    if (qiscusComment.getTopicId() == currentTopicId) {
                        view.onFailedSendComment(qiscusComment);
                    }
                });
    }

    @Override
    public void detachView() {
        super.detachView();
        QiscusPusherApi.getInstance().unListenRoom(room);
        room = null;
        EventBus.getDefault().unregister(this);
    }

    public interface View extends QiscusPresenter.View {

        void showLoadMoreLoading();

        void initRoomData(QiscusChatRoom qiscusChatRoom, List<QiscusComment> comments);

        void showComments(List<QiscusComment> qiscusComments);

        void onLoadMore(List<QiscusComment> qiscusComments);

        void onSendingComment(QiscusComment qiscusComment);

        void onSuccessSendComment(QiscusComment qiscusComment);

        void onFailedSendComment(QiscusComment qiscusComment);

        void onNewComment(QiscusComment qiscusComment);

        void onCommentDeleted(QiscusComment qiscusComment);

        void refreshComment(QiscusComment qiscusComment);

        void updateLastDeliveredComment(int lastDeliveredCommentId);

        void updateLastReadComment(int lastReadCommentId);

        void onFileDownloaded(File file, String mimeType);

        void startPhotoViewer(QiscusComment qiscusComment);

        void onUserTyping(String user, boolean typing);
    }
}