com.chatwingsdk.fragments.CommunicationMessagesFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.chatwingsdk.fragments.CommunicationMessagesFragment.java

Source

/*
 * Copyright (C) 2014 ChatWing
 *
 * 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.chatwingsdk.fragments;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.ToggleButton;

import com.chatwingsdk.R;
import com.chatwingsdk.events.internal.AccessTokenExpiredEvent;
import com.chatwingsdk.events.internal.AppendEmoticonEvent;
import com.chatwingsdk.events.internal.CreateMessageEvent;
import com.chatwingsdk.events.internal.InvalidIdentityEvent;
import com.chatwingsdk.events.internal.MessageEvent;
import com.chatwingsdk.events.internal.UserUnauthenticatedEvent;
import com.chatwingsdk.interfaces.ChatWingJSInterface;
import com.chatwingsdk.managers.ApiManager;
import com.chatwingsdk.managers.UserManager;
import com.chatwingsdk.parsers.BBCodePair;
import com.chatwingsdk.parsers.BBCodeParser;
import com.chatwingsdk.pojos.ChatBox;
import com.chatwingsdk.pojos.Message;
import com.chatwingsdk.pojos.User;
import com.chatwingsdk.pojos.errors.CreateMessageParamsError;
import com.chatwingsdk.services.CreateMessageIntentService;
import com.chatwingsdk.utils.LogUtils;
import com.chatwingsdk.validators.PermissionsValidator;
import com.chatwingsdk.views.BBCodeEditText;
import com.chatwingsdk.views.ErrorMessageView;
import com.github.kevinsawicki.http.HttpRequest;
import com.squareup.otto.Bus;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.inject.Inject;
import javax.inject.Provider;

/**
 * Created by cuongthai on 18/07/2014.
 */
public abstract class CommunicationMessagesFragment extends Fragment {
    @Inject
    Bus mBus;
    @Inject
    InputMethodManager mInputMethodManager;
    @Inject
    Provider<Typeface> mIconicTypefaceProvider;
    @Inject
    ErrorMessageView mErrorMessageView;
    @Inject
    UserManager mUserManager;
    @Inject
    ChatWingJSInterface mFayeJsInterface;

    protected WebView mWebview;
    private View mBBCodeControlsContainer;
    private Map<BBCodeParser.BBCode, Button> mBBCodeControls;
    protected View newContentBtn;
    private Delegate mDelegate;
    private boolean mSyncingBBCodeControls;
    private BBCodeEditText mCommunicationBoxEditText;
    private View sendBtn;
    private View mComposeContainer;

    public boolean isShowingBBControls() {
        return mBBCodeControlsContainer.getVisibility() == View.VISIBLE;
    }

    public void handleComposeView(ChatBox chatBox) {
        if (!chatBox.isReadOnly() || chatBox.isAdmin()
                || mUserManager.userHasPermission(chatBox, PermissionsValidator.Permission.SEND_MESSAGE)) {
            mComposeContainer.setVisibility(View.VISIBLE);
        } else {
            mComposeContainer.setVisibility(View.GONE);
        }
    }

    public static interface Delegate extends InjectableFragmentDelegate {
        void showColorPickerDialogFragment(BBCodeParser.BBCode code);

        void showNewContentFragment();

        void inject(BBCodeEditText mCommunicationBoxEditText);
    }

    public CommunicationMessagesFragment() {
    }

    protected abstract Message constructMessage(User user, String content, long createdDate, String randomKey,
            Message.Status status);

    protected abstract boolean canHandle(MessageEvent event);

    protected abstract boolean isInCurrentCommunicationBox(MessageEvent event);

    protected abstract boolean hasCurrentCommunication();

    public void onContextMenuClosed(Menu menu) {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_messageview, container, false);
    }

    private void setupWebView() {
        mWebview.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                try {
                    view.getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
                    return true;
                } catch (Exception e) {
                    return false;
                }
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                //TODO should be when page start rendering chatbox
                LogUtils.v("Loading page DONE");
            }

            @Override
            public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
                super.onReceivedError(view, errorCode, description, failingUrl);
                LogUtils.v("Loading page Oh no! " + description);
            }
        });

        mWebview.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                super.onProgressChanged(view, newProgress);
                LogUtils.v("Loading page " + newProgress + ":" + mWebview);
            }
        });
        WebSettings webSettings = mWebview.getSettings();
        webSettings.setAppCacheEnabled(false);
        webSettings.setJavaScriptEnabled(true);
        mWebview.addJavascriptInterface(mFayeJsInterface, ChatWingJSInterface.CHATWING_JS_NAME);
    }

    protected boolean handleException(Exception exception) {
        if (exception instanceof HttpRequest.HttpRequestException) {
            mErrorMessageView.show(R.string.error_network_connection);
            return true;
        }
        return false;
    }

    protected void onCreateMessageEvent(CreateMessageEvent event) {
        Exception exception = event.getException();
        if (exception != null && exception instanceof ApiManager.CreateMessageException) {
            ApiManager.CreateMessageException createMessageException = (ApiManager.CreateMessageException) exception;
            handle(createMessageException.getCreateMessageParamsError(),
                    createMessageException.getCommunicationMessage());
            return;
        }
        //Handle general errors
        if (onMessageEvent(event, getString(R.string.error_while_sending_message))) {
            return;
        }
    }

    /**
     * Checks and pre-processes a {@link com.chatwingsdk.events.internal.MessageEvent} for common cases.
     *
     * @param event the event to be checked and processed.
     * @return {@code true} if the event is already checked, processed and can
     * be ignored. {@code false} otherwise and the event processing can proceed.
     */
    protected boolean onMessageEvent(MessageEvent event, String detailMessasge) {
        if (!canHandle(event)) {
            return true;
        }

        Exception exception = event.getException();
        //Handle known exception
        if (exception instanceof ApiManager.UserUnauthenticatedException) {
            mBus.post(new UserUnauthenticatedEvent());
            return true;
        }

        if (exception instanceof ApiManager.InvalidIdentityException) {
            mBus.post(new InvalidIdentityEvent((ApiManager.InvalidIdentityException) event.getException()));
            return true;
        }

        if (exception instanceof ApiManager.InvalidAccessTokenException) {
            mBus.post(new AccessTokenExpiredEvent());
            return true;
        }

        //Handle unknown exception
        if (exception != null) {
            mErrorMessageView.show(exception, detailMessasge);
            return true;
        }

        // Ignore this event (return true) if it is not related to current communication box.
        return !isInCurrentCommunicationBox(event);
    }

    /**
     * Handles a {@link com.chatwingsdk.pojos.errors.CreateMessageParamsError} which is returned from server.
     *
     * @param error           required.
     * @param originalMessage optional.
     */
    protected void handle(CreateMessageParamsError error, Message originalMessage) {
        String type = error.getType();
        String toastMessage;
        boolean removeOriginalMessage = true;
        if (type.equals(CreateMessageParamsError.TYPE_EMPTY_MESSAGE)) {
            // This shouldn't happen because the message was checked in sendMessage
            // before the request to server is made.
            toastMessage = getString(R.string.error_empty_message);
        } else if (type.equals(CreateMessageParamsError.TYPE_LONG_MESSAGE)) {
            // TODO: may retry with the first 512 chars or split the message into 2.
            toastMessage = getString(R.string.error_too_long_message);
            if (originalMessage != null) {
                String content = originalMessage.getContent();
                mCommunicationBoxEditText.setText(content);
                mCommunicationBoxEditText.setSelection(content.length());
            }
        } else if (type.equals(CreateMessageParamsError.TYPE_ILLEGAL_INPUT)) {
            toastMessage = getString(R.string.error_illegal_input);
        } else if (type.equals(CreateMessageParamsError.TYPE_BLOCK)) {
            toastMessage = error.getErrorMessage();
            // even if the user is unable to send the message,
            // the message is still appended to the message list (but not stored in the database)
            removeOriginalMessage = true;
        } else {
            toastMessage = getString(R.string.error_unknown);
            // Unknown error, just leave the message in the list with a
            // "Failed" label.
            removeOriginalMessage = false;
            LogUtils.e("Unknown error: " + type);
        }

        mErrorMessageView.show(toastMessage);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mDelegate.inject(this);

        mWebview = (WebView) view.findViewById(R.id.webView);
        setupWebView();

        newContentBtn = view.findViewById(R.id.btn_new_content);
        newContentBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDelegate.showNewContentFragment();
            }
        });

        mComposeContainer = view.findViewById(R.id.chat_box_container);
        mCommunicationBoxEditText = (BBCodeEditText) view.findViewById(R.id.chat_box);
        mDelegate.inject(mCommunicationBoxEditText);

        // BBCode controls
        mBBCodeControlsContainer = view.findViewById(R.id.bbcode_controls_container);
        hideBBCodeControls();
        mBBCodeControlsContainer.findViewById(R.id.cancel_bbcode_controls)
                .setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        hideBBCodeControls();
                    }
                });
        mBBCodeControls = new TreeMap<BBCodeParser.BBCode, Button>();
        mBBCodeControls.put(BBCodeParser.BBCode.BOLD,
                (Button) mBBCodeControlsContainer.findViewById(R.id.btn_bbcode_bold));
        mBBCodeControls.put(BBCodeParser.BBCode.ITALIC,
                (Button) mBBCodeControlsContainer.findViewById(R.id.btn_bbcode_italic));
        mBBCodeControls.put(BBCodeParser.BBCode.UNDERLINE,
                (Button) mBBCodeControlsContainer.findViewById(R.id.btn_bbcode_underline));
        mBBCodeControls.put(BBCodeParser.BBCode.STRIKE_THROUGH,
                (Button) mBBCodeControlsContainer.findViewById(R.id.btn_bbcode_strike_through));
        mBBCodeControls.put(BBCodeParser.BBCode.COLOR,
                (Button) mBBCodeControlsContainer.findViewById(R.id.btn_bbcode_color));
        mBBCodeControls.put(BBCodeParser.BBCode.BACKGROUND_COLOR,
                (Button) mBBCodeControlsContainer.findViewById(R.id.btn_bbcode_background_color));
        mBBCodeControls.put(BBCodeParser.BBCode.IMAGE,
                (Button) mBBCodeControlsContainer.findViewById(R.id.btn_bbcode_image));
        mBBCodeControls.put(BBCodeParser.BBCode.VIDEO,
                (Button) mBBCodeControlsContainer.findViewById(R.id.btn_bbcode_video));
        mBBCodeControls.put(BBCodeParser.BBCode.URL,
                (Button) mBBCodeControlsContainer.findViewById(R.id.btn_bbcode_url));
        mBBCodeControls.put(BBCodeParser.BBCode.EMAIL,
                (Button) mBBCodeControlsContainer.findViewById(R.id.btn_bbcode_email));
        configBBCodeControls();

        //Setup typeface for format buttons
        Typeface iconicTypeface = mIconicTypefaceProvider.get();
        for (final BBCodeParser.BBCode code : mBBCodeControls.keySet()) {
            Button button = mBBCodeControls.get(code);
            if (button.getTypeface() == null || !button.getTypeface().equals(iconicTypeface)) {
                button.setTypeface(iconicTypeface);
            }
        }

        // Compose view
        mCommunicationBoxEditText = (BBCodeEditText) view.findViewById(R.id.chat_box);
        sendBtn = view.findViewById(R.id.btn_send);
        sendBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                sendMessage();
            }
        });
    }

    @Override
    public void onStart() {
        super.onStart();

        // Set style to chat box edit text and sync BBCode controls
        mCommunicationBoxEditText.clearAllBBCodes();
        Set<BBCodePair> pairs = mUserManager.getStyle();
        if (pairs != null && !pairs.isEmpty()) {
            for (BBCodePair pair : pairs) {
                mCommunicationBoxEditText.append(pair);
            }
        }
        syncBBCodeControlsState();
    }

    @Override
    public void onResume() {
        super.onResume();
        mBus.register(this);
    }

    public void onAppendEmoticonEvent(AppendEmoticonEvent event) {
        mCommunicationBoxEditText.append(event.getEmoticon());
    }

    @Override
    public void onPause() {
        super.onPause();
        mBus.unregister(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mWebview != null) {
            mWebview.destroy();
            mWebview = null;
        }
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        mDelegate = (Delegate) activity;

    }

    private void hideBBCodeControls() {
        mBBCodeControlsContainer.setVisibility(View.GONE);
    }

    public void showBBCodeControls() {
        mBBCodeControlsContainer.setVisibility(View.VISIBLE);
    }

    private void configBBCodeControls() {
        for (final BBCodeParser.BBCode code : mBBCodeControls.keySet()) {
            Button button = mBBCodeControls.get(code);
            // Config listener accordingly
            if (button instanceof ToggleButton) {
                // If it is a toggle button, set checked change listener.
                // When unchecked, the code is removed.
                ToggleButton toggle = (ToggleButton) button;
                if (code == BBCodeParser.BBCode.COLOR || code == BBCodeParser.BBCode.BACKGROUND_COLOR) {
                    // When checked, show color picker.
                    toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                        @Override
                        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                            // Check mSyncingBBCodeControls to avoid recursion.
                            if (!mSyncingBBCodeControls) {
                                if (isChecked) {
                                    mDelegate.showColorPickerDialogFragment(code);
                                    // Invalidate the checked property now,
                                    // because user can cancel the action later.
                                    // The toggle will be updated later when user
                                    // confirmed the action.
                                    buttonView.setChecked(false);
                                } else {
                                    removeBBCode(code);
                                }
                            }
                        }
                    });
                } else {
                    // When checked, append the code with an empty value.
                    toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                        @Override
                        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                            // Check mSyncingBBCodeControls to avoid recursion.
                            if (!mSyncingBBCodeControls) {
                                if (isChecked) {
                                    appendBBCode(new BBCodePair(code));
                                } else {
                                    removeBBCode(code);
                                }
                            }
                        }
                    });
                }
            } else {
                // It is a normal button, set on click listener. When clicked,
                // append the code with empty value.
                button.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        appendBBCode(new BBCodePair(code));
                    }
                });
            }
        }
    }

    public void appendBBCode(BBCodePair pair) {
        if (mCommunicationBoxEditText.append(pair)) {
            showKeyboard();
            syncBBCodeControlState(pair.getCode());
            saveBBCodesIfNeeded();
        }
    }

    private void removeBBCode(BBCodeParser.BBCode code) {
        if (mCommunicationBoxEditText.remove(code)) {
            syncBBCodeControlState(code);
            saveBBCodesIfNeeded();
        }
    }

    private void showKeyboard() {
        mInputMethodManager.showSoftInput(mCommunicationBoxEditText, InputMethodManager.SHOW_IMPLICIT);
    }

    /**
     * Syncs state of the {@link BBCodeParser.BBCode} in {@link #mCommunicationBoxEditText}
     * with BBCode controls. Only {@link ToggleButton}s are supported by now.
     * <p/>
     * If the code is used in {@link #mCommunicationBoxEditText}, the corresponding
     * {@link ToggleButton} will be checked. Otherwise, it will be unchecked.
     *
     * @param code the code to be synced.
     */
    private void syncBBCodeControlState(BBCodeParser.BBCode code) {
        View control = mBBCodeControls.get(code);
        if (!(control instanceof ToggleButton)) {
            return;
        }

        mSyncingBBCodeControls = true;
        ((ToggleButton) control).setChecked(mCommunicationBoxEditText.contains(code));
        mSyncingBBCodeControls = false;
    }

    private void saveBBCodesIfNeeded() {
        if (mUserManager.shouldRememberPreviousStyle()) {
            Set<BBCodePair> pairs = new TreeSet<BBCodePair>(mCommunicationBoxEditText.getAllBBCodes());
            mUserManager.saveStyle(pairs);
        }
    }

    //////////////////////////////////////////////////////////////
    // Instance methods
    //////////////////////////////////////////////////////////////
    private void sendMessage() {
        if (!mUserManager.userCanSendMessage()) {
            mErrorMessageView.show(R.string.error_required_login);
            return;
        }
        User user = mUserManager.getCurrentUser();

        String content = mCommunicationBoxEditText.getFullText();
        if (TextUtils.isEmpty(content)) {
            // Nothing to send
            return;
        }

        if (!hasCurrentCommunication()) {
            // No current communication box to send new message to
            return;
        }

        long createdDate = System.currentTimeMillis();
        Message newMessage = constructMessage(user, content, createdDate, null, Message.Status.SENDING);
        newMessage.setSendingDate(System.currentTimeMillis());

        mCommunicationBoxEditText.setText("");
        if (!user.getProfile().shouldRememberPreviousStyle()) {
            mCommunicationBoxEditText.clearAllBBCodes();
            syncBBCodeControlsState();
        }

        // Send task to CreateMessageIntentService to make a async request.
        Intent i = new Intent(getActivity(), CreateMessageIntentService.class);
        i.putExtra(CreateMessageIntentService.EXTRA_MESSAGE, newMessage);
        i.putExtra(CreateMessageIntentService.EXTRA_SENDING_DATE, newMessage.getSendingDate());
        getActivity().startService(i);
    }

    private void syncBBCodeControlsState() {
        for (BBCodeParser.BBCode code : mBBCodeControls.keySet()) {
            syncBBCodeControlState(code);
        }
    }

}