Twidere-App-Android-Twitter.../twidere/src/main/java/org/mariotaku/twidere/fragment/card/CardPollFragment.java

374 lines
15 KiB
Java

/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.fragment.card;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RadioButton;
import android.widget.TableLayout;
import android.widget.TextView;
import org.apache.commons.lang3.math.NumberUtils;
import org.mariotaku.abstask.library.AbstractTask;
import org.mariotaku.abstask.library.TaskStarter;
import org.mariotaku.microblog.library.MicroBlogException;
import org.mariotaku.microblog.library.twitter.TwitterCaps;
import org.mariotaku.microblog.library.twitter.model.CardDataMap;
import org.mariotaku.microblog.library.twitter.model.CardEntity;
import org.mariotaku.twidere.R;
import org.mariotaku.twidere.fragment.BaseSupportFragment;
import org.mariotaku.twidere.model.ParcelableCardEntity;
import org.mariotaku.twidere.model.ParcelableStatus;
import org.mariotaku.twidere.model.UserKey;
import org.mariotaku.twidere.model.util.ParcelableCardEntityUtils;
import org.mariotaku.twidere.util.MicroBlogAPIFactory;
import org.mariotaku.twidere.util.support.ViewSupport;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by mariotaku on 15/12/20.
*/
public class CardPollFragment extends BaseSupportFragment implements
LoaderManager.LoaderCallbacks<ParcelableCardEntity>, View.OnClickListener {
public static final Pattern PATTERN_POLL_TEXT_ONLY = Pattern.compile("poll([\\d]+)choice_text_only");
private TableLayout mPollContainer;
private TextView mPollSummary;
private ParcelableCardEntity mCard;
public static CardPollFragment show(ParcelableStatus status) {
final CardPollFragment fragment = new CardPollFragment();
final Bundle args = new Bundle();
args.putParcelable(EXTRA_STATUS, status);
args.putParcelable(EXTRA_CARD, status.card);
fragment.setArguments(args);
return fragment;
}
public static boolean isPoll(@NonNull ParcelableCardEntity card) {
return PATTERN_POLL_TEXT_ONLY.matcher(card.name).matches() && !TextUtils.isEmpty(card.url);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initChoiceView(savedInstanceState);
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mPollContainer = (TableLayout) view.findViewById(R.id.poll_container);
mPollSummary = (TextView) view.findViewById(R.id.poll_summary);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_card_poll, container, false);
}
@Override
protected void fitSystemWindows(Rect insets) {
// No-op
}
private void initChoiceView(@Nullable Bundle savedInstanceState) {
final ParcelableCardEntity card = getCard();
final ParcelableStatus status = getStatus();
final int choicesCount = getChoicesCount(card);
final LayoutInflater inflater = getLayoutInflater(savedInstanceState);
for (int i = 0; i < choicesCount; i++) {
inflater.inflate(R.layout.layout_poll_item, mPollContainer, true);
}
displayPoll(card, status);
}
private void displayPoll(final ParcelableCardEntity card, final ParcelableStatus status) {
final Context context = getContext();
if (card == null || status == null || context == null) return;
mCard = card;
final int choicesCount = getChoicesCount(card);
int votesSum = 0;
final boolean countsAreFinal = ParcelableCardEntityUtils.getAsBoolean(card, "counts_are_final", false);
final int selectedChoice = ParcelableCardEntityUtils.getAsInteger(card, "selected_choice", -1);
final Date endDatetimeUtc = ParcelableCardEntityUtils.getAsDate(card, "end_datetime_utc", new Date());
final boolean hasChoice = selectedChoice != -1;
final boolean isMyPoll = status.account_key.equals(status.user_key);
final boolean showResult = countsAreFinal || isMyPoll || hasChoice;
for (int i = 0; i < choicesCount; i++) {
final int choiceIndex = i + 1;
votesSum += ParcelableCardEntityUtils.getAsInteger(card, "choice" + choiceIndex + "_count", 0);
}
final View.OnClickListener clickListener = new View.OnClickListener() {
private boolean clickedChoice;
@Override
public void onClick(View v) {
if (hasChoice || clickedChoice) return;
for (int i = 0, j = mPollContainer.getChildCount(); i < j; i++) {
final View pollItem = mPollContainer.getChildAt(i);
pollItem.setClickable(false);
clickedChoice = true;
final RadioButton choiceRadioButton = (RadioButton) pollItem.findViewById(R.id.choice_button);
final boolean checked = v == pollItem;
choiceRadioButton.setChecked(checked);
if (checked) {
final CardDataMap cardData = new CardDataMap();
cardData.putLong("original_tweet_id", NumberUtils.toLong(status.id));
cardData.putString("card_uri", card.url);
cardData.putString("cards_platform", MicroBlogAPIFactory.CARDS_PLATFORM_ANDROID_12);
cardData.putString("response_card_name", card.name);
cardData.putString("selected_choice", String.valueOf(i + 1));
AbstractTask<CardDataMap, ParcelableCardEntity, CardPollFragment> task
= new AbstractTask<CardDataMap, ParcelableCardEntity, CardPollFragment>() {
@Override
public void afterExecute(CardPollFragment handler, ParcelableCardEntity result) {
handler.displayAndReloadPoll(result, status);
}
@Override
public ParcelableCardEntity doLongOperation(CardDataMap cardDataMap) {
final Context context = getContext();
if (context == null) return null;
final TwitterCaps caps = MicroBlogAPIFactory.getInstance(context,
card.account_key, true, true, TwitterCaps.class);
if (caps == null) return null;
try {
final CardEntity cardEntity = caps.sendPassThrough(cardDataMap).getCard();
return ParcelableCardEntityUtils.fromCardEntity(cardEntity,
card.account_key);
} catch (MicroBlogException e) {
Log.w(LOGTAG, e);
}
return null;
}
};
task.setCallback(CardPollFragment.this);
task.setParams(cardData);
TaskStarter.execute(task);
}
}
}
};
final int color = ContextCompat.getColor(context, R.color.material_light_blue_a200);
final float radius = getResources().getDimension(R.dimen.element_spacing_small);
for (int i = 0; i < choicesCount; i++) {
final View pollItem = mPollContainer.getChildAt(i);
final TextView choicePercentView = (TextView) pollItem.findViewById(R.id.choice_percent);
final TextView choiceLabelView = (TextView) pollItem.findViewById(R.id.choice_label);
final RadioButton choiceRadioButton = (RadioButton) pollItem.findViewById(R.id.choice_button);
final int choiceIndex = i + 1;
final String label = ParcelableCardEntityUtils.getAsString(card, "choice" + choiceIndex + "_label", null);
final int value = ParcelableCardEntityUtils.getAsInteger(card, "choice" + choiceIndex + "_count", 0);
if (label == null) throw new NullPointerException();
final float choicePercent = votesSum == 0 ? 0 : value / (float) votesSum;
choiceLabelView.setText(label);
choicePercentView.setText(String.format(Locale.US, "%d%%", Math.round(choicePercent * 100)));
pollItem.setOnClickListener(clickListener);
final boolean isSelected = selectedChoice == choiceIndex;
if (showResult) {
choicePercentView.setVisibility(View.VISIBLE);
choiceRadioButton.setVisibility(hasChoice && isSelected ? View.VISIBLE : View.INVISIBLE);
ViewSupport.setBackground(choiceLabelView, new PercentDrawable(choicePercent, radius, color));
} else {
choicePercentView.setVisibility(View.GONE);
choiceRadioButton.setVisibility(View.VISIBLE);
ViewSupport.setBackground(choiceLabelView, null);
}
choiceRadioButton.setChecked(isSelected);
pollItem.setClickable(selectedChoice == -1);
}
final String nVotes = getResources().getQuantityString(R.plurals.N_votes, votesSum, votesSum);
final CharSequence timeLeft = DateUtils.getRelativeTimeSpanString(context,
endDatetimeUtc.getTime(), true);
mPollSummary.setText(getString(R.string.poll_summary_format, nVotes, timeLeft));
}
private void displayAndReloadPoll(ParcelableCardEntity result, ParcelableStatus status) {
if (getHost() == null) return;
displayPoll(result, status);
getLoaderManager().restartLoader(0, null, this);
}
private int getChoicesCount(ParcelableCardEntity card) {
final Matcher matcher = PATTERN_POLL_TEXT_ONLY.matcher(card.name);
if (!matcher.matches()) throw new IllegalStateException();
return NumberUtils.toInt(matcher.group(1));
}
@NonNull
private ParcelableCardEntity getCard() {
if (mCard != null) return mCard;
final ParcelableCardEntity card = getArguments().getParcelable(EXTRA_CARD);
assert card != null && card.name != null;
return card;
}
private ParcelableStatus getStatus() {
return getArguments().getParcelable(EXTRA_STATUS);
}
@Override
public void onClick(View v) {
}
@Override
public Loader<ParcelableCardEntity> onCreateLoader(int id, Bundle args) {
final ParcelableCardEntity card = getCard();
return new ParcelableCardEntityLoader(getContext(), card.account_key, card.url, card.name);
}
@Override
public void onLoadFinished(Loader<ParcelableCardEntity> loader, ParcelableCardEntity data) {
if (data == null) return;
displayPoll(data, getStatus());
}
@Override
public void onLoaderReset(Loader<ParcelableCardEntity> loader) {
}
private static class PercentDrawable extends Drawable {
private final Paint mPaint;
private final RectF mBounds;
private final float mPercent;
private final float mRadius;
PercentDrawable(float percent, float radius, int color) {
mPercent = percent;
mRadius = radius;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(color);
mBounds = new RectF();
}
@Override
public void draw(Canvas canvas) {
canvas.drawRoundRect(mBounds, mRadius, mRadius, mPaint);
}
@Override
protected void onBoundsChange(Rect bounds) {
mBounds.set(bounds);
mBounds.right = mBounds.left + mBounds.width() * mPercent;
super.onBoundsChange(bounds);
}
@Override
public void setAlpha(int alpha) {
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
public static class ParcelableCardEntityLoader extends AsyncTaskLoader<ParcelableCardEntity> {
private final UserKey mAccountKey;
private final String mCardUri;
private final String mCardName;
public ParcelableCardEntityLoader(Context context, UserKey accountKey,
String cardUri, String cardName) {
super(context);
mAccountKey = accountKey;
mCardUri = cardUri;
mCardName = cardName;
}
@Override
public ParcelableCardEntity loadInBackground() {
final TwitterCaps caps = MicroBlogAPIFactory.getInstance(getContext(), mAccountKey,
true, true, TwitterCaps.class);
if (caps == null) return null;
try {
final CardDataMap params = new CardDataMap();
params.putString("card_uri", mCardUri);
params.putString("cards_platform", MicroBlogAPIFactory.CARDS_PLATFORM_ANDROID_12);
params.putString("response_card_name", mCardName);
final CardEntity card = caps.getPassThrough(params).getCard();
if (card == null || card.getName() == null) {
return null;
}
return ParcelableCardEntityUtils.fromCardEntity(card, mAccountKey);
} catch (MicroBlogException e) {
return null;
}
}
@Override
protected void onStartLoading() {
forceLoad();
}
}
}