/*
* Copyright (c) 2015 Ha Duy Trung
*
* 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 io.github.hidroh.materialistic.widget;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.os.Build;
import android.support.annotation.CallSuper;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import io.github.hidroh.materialistic.AlertDialogBuilder;
import io.github.hidroh.materialistic.AppUtils;
import io.github.hidroh.materialistic.ComposeActivity;
import io.github.hidroh.materialistic.Injectable;
import io.github.hidroh.materialistic.Navigable;
import io.github.hidroh.materialistic.Preferences;
import io.github.hidroh.materialistic.R;
import io.github.hidroh.materialistic.accounts.UserServices;
import io.github.hidroh.materialistic.annotation.Synthetic;
import io.github.hidroh.materialistic.data.Item;
import io.github.hidroh.materialistic.data.ItemManager;
import io.github.hidroh.materialistic.data.ResponseListener;
public abstract class ItemRecyclerViewAdapter<VH extends ItemRecyclerViewAdapter.ItemViewHolder>
extends RecyclerViewAdapter<VH> {
private static final String PROPERTY_MAX_LINES = "maxLines";
private static final int DURATION_PER_LINE_MILLIS = 20;
LayoutInflater mLayoutInflater;
private ItemManager mItemManager;
@Inject UserServices mUserServices;
@Inject PopupMenu mPopupMenu;
@Inject AlertDialogBuilder mAlertDialogBuilder;
private int mTertiaryTextColorResId;
private int mSecondaryTextColorResId;
private int mCardBackgroundColorResId;
private int mCardHighlightColorResId;
private int mContentMaxLines = Integer.MAX_VALUE;
private String mUsername;
private final Map<String, Integer> mLineCounted = new HashMap<>();
private int mCacheMode = ItemManager.MODE_DEFAULT;
private float mLineHeight = 1.0f;
public interface PositionCallback {
void onPosition(int position);
}
ItemRecyclerViewAdapter(ItemManager itemManager) {
mItemManager = itemManager;
}
@Override
public void attach(Context context, RecyclerView recyclerView) {
super.attach(context, recyclerView);
if (mContext instanceof Injectable) {
((Injectable) mContext).inject(this);
}
mLayoutInflater = AppUtils.createLayoutInflater(mContext);
TypedArray ta = mContext.obtainStyledAttributes(new int[]{
android.R.attr.textColorTertiary,
android.R.attr.textColorSecondary,
R.attr.colorCardBackground,
R.attr.colorCardHighlight
});
mTertiaryTextColorResId = ta.getInt(0, 0);
mSecondaryTextColorResId = ta.getInt(1, 0);
mCardBackgroundColorResId = ta.getInt(2, 0);
mCardHighlightColorResId = ta.getInt(3, 0);
ta.recycle();
}
@Override
public void onBindViewHolder(final VH holder, int position) {
final Item item = getItem(position);
if (item == null) {
return;
}
clear(holder);
if (item.getLocalRevision() < 0) {
load(holder.getAdapterPosition(), item);
} else if (item.getLocalRevision() > 0) {
bind(holder, item);
}
}
@Override
public long getItemId(int position) {
Item item = getItem(position);
return item != null ? item.getLongId() : RecyclerView.NO_ID;
}
public void setCacheMode(int cacheMode) {
mCacheMode = cacheMode;
}
public void initDisplayOptions(Context context) {
mContentMaxLines = Preferences.getCommentMaxLines(context);
mUsername = Preferences.getUsername(context);
mLineHeight = Preferences.getLineHeight(context);
}
public void getNextPosition(int position, int direction, PositionCallback callback) {
switch (direction) {
case Navigable.DIRECTION_UP:
callback.onPosition(position - 1);
break;
case Navigable.DIRECTION_DOWN:
callback.onPosition(position + 1);
break;
}
}
public void lockBinding(int[] lock) { }
@Nullable
protected abstract Item getItem(int position);
@CallSuper
protected void bind(final VH holder, final Item item) {
if (item == null) {
return;
}
highlightUserItem(holder, item);
decorateDead(holder, item);
holder.mContentTextView.setLineSpacing(0f, mLineHeight);
AppUtils.setTextWithLinks(holder.mContentTextView, item.getDisplayedText());
Integer lineCount = mLineCounted.get(item.getId());
if (lineCount != null && lineCount > 0) {
toggleCollapsibleContent(holder, item, lineCount);
} else {
holder.mContentTextView.post(() -> {
if (mContext == null) {
return;
}
int count = holder.mContentTextView.getLineCount();
mLineCounted.put(item.getId(), count);
toggleCollapsibleContent(holder, item, count);
});
}
bindActions(holder, item);
}
protected void clear(VH holder) {
holder.mCommentButton.setVisibility(View.GONE);
holder.mPostedTextView.setOnClickListener(null);
holder.mPostedTextView.setText(R.string.loading_text);
holder.mContentTextView.setText(R.string.loading_text);
holder.mReadMoreTextView.setVisibility(View.GONE);
}
@Synthetic
boolean isAttached() {
return mContext != null;
}
private void load(int adapterPosition, Item item) {
item.setLocalRevision(0);
mItemManager.getItem(item.getId(), mCacheMode,
new ItemResponseListener(this, adapterPosition, item));
}
protected void onItemLoaded(int position, Item item) {
if (position < getItemCount()) {
notifyItemChanged(position);
}
}
private void highlightUserItem(VH holder, Item item) {
boolean highlight = !TextUtils.isEmpty(mUsername) &&
TextUtils.equals(mUsername, item.getBy());
holder.mContentView.setBackgroundColor(highlight ?
mCardHighlightColorResId : mCardBackgroundColorResId);
}
private void decorateDead(VH holder, Item item) {
holder.mContentTextView.setTextColor(item.isDead() ?
mSecondaryTextColorResId : mTertiaryTextColorResId);
}
private void toggleCollapsibleContent(final VH holder, final Item item, int lineCount) {
if (item.isContentExpanded() || lineCount <= mContentMaxLines) {
holder.mContentTextView.setMaxLines(Integer.MAX_VALUE);
holder.mReadMoreTextView.setVisibility(View.GONE);
return;
}
holder.mContentTextView.setMaxLines(mContentMaxLines);
holder.mReadMoreTextView.setVisibility(View.VISIBLE);
holder.mReadMoreTextView.setText(mContext.getString(R.string.read_more, lineCount));
holder.mReadMoreTextView.setOnClickListener(v -> {
item.setContentExpanded(true);
v.setVisibility(View.GONE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
ObjectAnimator.ofInt(holder.mContentTextView, PROPERTY_MAX_LINES, lineCount)
.setDuration((lineCount - mContentMaxLines) * DURATION_PER_LINE_MILLIS)
.start();
} else {
holder.mContentTextView.setMaxLines(Integer.MAX_VALUE);
}
});
}
private void bindActions(final VH holder, final Item item) {
if (item.isDead() || item.isDeleted()) {
holder.mMoreButton.setVisibility(View.INVISIBLE);
return;
}
holder.mMoreButton.setVisibility(View.VISIBLE);
holder.mMoreButton.setOnClickListener(v ->
mPopupMenu.create(mContext, holder.mMoreButton, Gravity.NO_GRAVITY)
.inflate(R.menu.menu_contextual_comment)
.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_contextual_vote) {
vote(item);
return true;
}
if (menuItem.getItemId() == R.id.menu_contextual_comment) {
mContext.startActivity(new Intent(mContext, ComposeActivity.class)
.putExtra(ComposeActivity.EXTRA_PARENT_ID, item.getId())
.putExtra(ComposeActivity.EXTRA_PARENT_TEXT, item.getText()));
return true;
}
if (menuItem.getItemId() == R.id.menu_contextual_share) {
AppUtils.share(mContext,
item.isStoryType() ? item.getDisplayedTitle() : null,
item.isStoryType() ? item.getUrl() :
item.getDisplayedText() == null ?
null : item.getDisplayedText().toString());
return true;
}
return false;
})
.show());
}
private void vote(final Item item) {
mUserServices.voteUp(mContext, item.getId(), new VoteCallback(this));
}
@Synthetic
void onVoted(Boolean successful) {
if (successful == null) {
Toast.makeText(mContext, R.string.vote_failed, Toast.LENGTH_SHORT).show();
} else if (successful) {
Toast.makeText(mContext, R.string.voted, Toast.LENGTH_SHORT).show();
} else {
AppUtils.showLogin(mContext, mAlertDialogBuilder);
}
}
static class ItemViewHolder extends RecyclerView.ViewHolder {
boolean mIsFooter;
TextView mPostedTextView;
TextView mContentTextView;
TextView mReadMoreTextView;
TextView mCommentButton;
View mMoreButton;
View mContentView;
ItemViewHolder(View itemView) {
super(itemView);
mPostedTextView = (TextView) itemView.findViewById(R.id.posted);
mPostedTextView.setMovementMethod(LinkMovementMethod.getInstance());
mContentTextView = (TextView) itemView.findViewById(R.id.text);
mReadMoreTextView = (TextView) itemView.findViewById(R.id.more);
mCommentButton = (TextView) itemView.findViewById(R.id.comment);
mCommentButton.setVisibility(View.GONE);
mMoreButton = itemView.findViewById(R.id.button_more);
mContentView = itemView.findViewById(R.id.content);
}
ItemViewHolder(View itemView, @SuppressWarnings("UnusedParameters") Object payload) {
super(itemView);
mIsFooter = true;
}
boolean isFooter() {
return mIsFooter;
}
}
private static class ItemResponseListener implements ResponseListener<Item> {
private final WeakReference<ItemRecyclerViewAdapter> mAdapter;
private final int mPosition;
private final Item mPartialItem;
@Synthetic
ItemResponseListener(ItemRecyclerViewAdapter adapter, int position,
Item partialItem) {
mAdapter = new WeakReference<>(adapter);
mPosition = position;
mPartialItem = partialItem;
}
@Override
public void onResponse(@Nullable Item response) {
if (mAdapter.get() != null && mAdapter.get().isAttached() && response != null) {
mPartialItem.populate(response);
mAdapter.get().onItemLoaded(mPosition, mPartialItem);
}
}
@Override
public void onError(String errorMessage) {
// do nothing
}
}
static class VoteCallback extends UserServices.Callback {
private final WeakReference<ItemRecyclerViewAdapter> mAdapter;
@Synthetic
VoteCallback(ItemRecyclerViewAdapter adapter) {
mAdapter = new WeakReference<>(adapter);
}
@Override
public void onDone(boolean successful) {
if (mAdapter.get() != null && mAdapter.get().isAttached()) {
mAdapter.get().onVoted(successful);
}
}
@Override
public void onError(Throwable throwable) {
if (mAdapter.get() != null && mAdapter.get().isAttached()) {
mAdapter.get().onVoted(null);
}
}
}
}