com.freshdigitable.udonroad.TweetInputFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.freshdigitable.udonroad.TweetInputFragment.java

Source

/*
 * Copyright (c) 2016. Matsuda, Akihit (akihito104)
 *
 * 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.freshdigitable.udonroad;

import android.content.Context;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

import com.freshdigitable.udonroad.databinding.FragmentTweetInputBinding;
import com.freshdigitable.udonroad.datastore.AppSettingStore;
import com.freshdigitable.udonroad.datastore.TypedCache;
import com.freshdigitable.udonroad.module.InjectionUtil;
import com.freshdigitable.udonroad.module.twitter.TwitterApi;
import com.freshdigitable.udonroad.subscriber.ConfigRequestWorker;
import com.squareup.picasso.Picasso;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;

import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.functions.Action1;
import twitter4j.Status;
import twitter4j.StatusUpdate;
import twitter4j.User;
import twitter4j.UserMentionEntity;

/**
 * TweetInputFragment provides TweetInputView and logic to send tweet.
 *
 * Created by akihit on 2016/02/06.
 */
public class TweetInputFragment extends Fragment {
    private static final String TAG = TweetInputFragment.class.getSimpleName();
    private FragmentTweetInputBinding binding;
    @Inject
    TwitterApi twitterApi;
    @Inject
    TypedCache<Status> statusCache;
    @Inject
    ConfigRequestWorker configRequestWorker;
    @Inject
    AppSettingStore appSettings;

    public static TweetInputFragment create() {
        return create(TYPE_NONE);
    }

    public static TweetInputFragment create(@TweetType int type) {
        return create(type, -1);
    }

    public static TweetInputFragment create(@TweetType int type, long statusId) {
        final Bundle args = new Bundle();
        args.putInt("tweet_type", type);
        args.putLong("status_id", statusId);
        final TweetInputFragment tweetInputFragment = new TweetInputFragment();
        tweetInputFragment.setArguments(args);
        return tweetInputFragment;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        InjectionUtil.getComponent(this).inject(this);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

    private MenuItem sendStatusMenuItem;
    private MenuItem cancelMenuItem;

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        Log.d(TAG, "onCreateOptionsMenu: ");
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.tweet_input, menu);
        sendStatusMenuItem = menu.findItem(R.id.action_write);
        cancelMenuItem = menu.findItem(R.id.action_cancel);
        setupMenuVisibility();
    }

    private void setupMenuVisibility() {
        if (sendStatusMenuItem != null) {
            sendStatusMenuItem.setVisible(!binding.mainTweetInputView.isVisible());
        }
        if (cancelMenuItem != null) {
            cancelMenuItem.setVisible(binding.mainTweetInputView.isVisible());
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        Log.d(TAG, "onOptionsItemSelected: ");
        final int itemId = item.getItemId();
        if (itemId == R.id.action_write) {
            stretchTweetInputView();
        } else if (itemId == R.id.action_cancel) {
            collapseStatusInputView();
        }
        return false;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView: ");
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_tweet_input, container, false);
        return binding.getRoot();
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onActivityCreated: ");
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onStart() {
        super.onStart();
        statusCache.open();
        configRequestWorker.open();
        appSettings.open();
        final Bundle arguments = getArguments();
        final @TweetType int tweetType = arguments.getInt("tweet_type");
        final long statusId = arguments.getLong("status_id", -1);
        stretchTweetInputView(tweetType, statusId);
    }

    @Override
    public void onStop() {
        super.onStop();
        appSettings.close();
        configRequestWorker.close();
        statusCache.close();
    }

    private FloatingActionButton tweetSendFab;

    public void setTweetSendFab(FloatingActionButton fab) {
        this.tweetSendFab = fab;
    }

    private TextWatcher textWatcher = new TextWatcher() {
        @Override
        public void afterTextChanged(Editable editable) {
            tweetSendFab.setEnabled(editable.length() >= 1);
        }

        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        }
    };

    private List<Long> quoteStatusIds = new ArrayList<>(4);

    public void stretchTweetInputView(@TweetType int type, long statusId) {
        if (type == TYPE_NONE) {
            return;
        }
        if (type == TYPE_DEFAULT) {
            stretchTweetInputView();
        } else if (type == TYPE_REPLY) {
            stretchTweetInputViewWithInReplyTo(statusId);
        } else if (type == TYPE_QUOTE) {
            stretchTweetInputViewWithQuoteStatus(statusId);
        }
    }

    private void stretchTweetInputView() {
        setUpTweetInputView();
        setUpTweetSendFab();
        binding.mainTweetInputView.appearing();
        setupMenuVisibility();
    }

    private ReplyEntity replyEntity;

    private void stretchTweetInputViewWithInReplyTo(long inReplyToStatusId) {
        final Status inReplyTo = statusCache.find(inReplyToStatusId);
        final TweetInputView inputText = binding.mainTweetInputView;
        if (inReplyTo == null) {
            stretchTweetInputView();
            return;
        }
        replyEntity = ReplyEntity.create(inReplyTo);
        inputText.addText(replyEntity.createReplyString());
        inputText.setInReplyTo();
        stretchTweetInputView();
    }

    private void stretchTweetInputViewWithQuoteStatus(long quotedStatus) {
        quoteStatusIds.add(quotedStatus);
        binding.mainTweetInputView.setQuote();
        stretchTweetInputView();
    }

    private void setUpTweetInputView() {
        final TweetInputView inputText = binding.mainTweetInputView;
        configRequestWorker.getAuthenticatedUser().subscribe(new Action1<User>() {
            @Override
            public void call(User authenticatedUser) {
                inputText.setUserInfo(authenticatedUser);
                Picasso.with(inputText.getContext()).load(authenticatedUser.getMiniProfileImageURLHttps())
                        .resizeDimen(R.dimen.small_user_icon, R.dimen.small_user_icon).into(inputText.getIcon());
            }
        });
        inputText.addTextWatcher(textWatcher);
        inputText.setShortUrlLength(appSettings.getTwitterAPIConfig().getShortURLLengthHttps());
    }

    public void tearDownTweetInputView() {
        binding.mainTweetInputView.removeTextWatcher(textWatcher);
        binding.mainTweetInputView.reset();
    }

    private void setUpTweetSendFab() {
        final TweetInputView inputText = binding.mainTweetInputView;
        if (inputText.getText().length() < 1) {
            tweetSendFab.setEnabled(false);
        }
        tweetSendFab.setOnClickListener(createSendClickListener());
    }

    private View.OnClickListener createSendClickListener() {
        final FragmentActivity activity = getActivity();
        if (activity instanceof TweetSendable) {
            return new View.OnClickListener() {
                @Override
                public void onClick(final View v) {
                    v.setClickable(false);
                    ((TweetSendable) activity)
                            .observeUpdateStatus(observeUpdateStatus().doOnCompleted(new Action0() {
                                @Override
                                public void call() {
                                    v.setClickable(true);
                                }
                            }));
                }
            };
        } else {
            return new View.OnClickListener() {
                @Override
                public void onClick(final View view) {
                    view.setClickable(false);
                    observeUpdateStatus().doOnCompleted(new Action0() {
                        @Override
                        public void call() {
                            view.setClickable(true);
                        }
                    }).subscribe();
                }
            };
        }
    }

    private void tearDownSendTweetFab() {
        tweetSendFab.setOnClickListener(null);
    }

    private Observable<Status> createSendObservable() {
        final String sendingText = binding.mainTweetInputView.getText().toString();
        if (!isStatusUpdateNeeded()) {
            return twitterApi.updateStatus(sendingText);
        }
        String s = sendingText;
        for (long q : quoteStatusIds) {
            final Status status = statusCache.find(q);
            if (status == null) {
                continue;
            }
            s += " https://twitter.com/" + status.getUser().getScreenName() + "/status/" + q;
        }
        final StatusUpdate statusUpdate = new StatusUpdate(s);
        if (replyEntity != null) {
            statusUpdate.setInReplyToStatusId(replyEntity.inReplyToStatusId);
        }
        return twitterApi.updateStatus(statusUpdate);
    }

    private Observable<Status> observeUpdateStatus() {
        final TweetInputView inputText = binding.mainTweetInputView;
        return createSendObservable().observeOn(AndroidSchedulers.mainThread()).doOnNext(new Action1<Status>() {
            @Override
            public void call(Status status) {
                inputText.getText().clear();
                inputText.clearFocus();
                inputText.disappearing();
                setupMenuVisibility();
            }
        });
    }

    private boolean isStatusUpdateNeeded() {
        return replyEntity != null || quoteStatusIds.size() > 0;
    }

    public void addQuoteStatus(long quoteStatusId) {
        quoteStatusIds.add(quoteStatusId);
    }

    public void collapseStatusInputView() {
        tearDownTweetInputView();
        tearDownSendTweetFab();
        replyEntity = null;
        quoteStatusIds.clear();
        binding.mainTweetInputView.disappearing();
        setupMenuVisibility();
    }

    public boolean isStatusInputViewVisible() {
        return binding.mainTweetInputView.isVisible();
    }

    interface TweetSendable {
        void setupInput(@TweetType int type, long statusId);

        void observeUpdateStatus(Observable<Status> updateStatusObservable);
    }

    public static final int TYPE_DEFAULT = 0;
    public static final int TYPE_REPLY = 1;
    public static final int TYPE_QUOTE = 2;
    public static final int TYPE_NONE = -1;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = { TYPE_DEFAULT, TYPE_REPLY, TYPE_QUOTE, TYPE_NONE })
    public @interface TweetType {
    }

    private static class ReplyEntity {
        long inReplyToStatusId;
        Set<String> screenNames;

        static ReplyEntity create(@NonNull Status status) {
            final ReplyEntity res = new ReplyEntity();
            res.inReplyToStatusId = status.getId();
            res.screenNames = new LinkedHashSet<>();
            final UserMentionEntity[] userMentionEntities = status.getUserMentionEntities();
            for (UserMentionEntity u : userMentionEntities) {
                res.addScreenName(u.getScreenName());
            }

            if (status.isRetweet()) {
                final Status retweetedStatus = status.getRetweetedStatus();
                res.addScreenName(retweetedStatus.getUser().getScreenName());
            }

            final User user = status.getUser();
            res.addScreenName(user.getScreenName());
            return res;
        }

        private void addScreenName(String screenName) {
            screenNames.add(screenName);
        }

        String createReplyString() {
            String s = "";
            for (String sn : screenNames) {
                s += "@" + sn + " ";
            }
            return s;
        }
    }
}