/*
* Copyright 2015 Google Inc.
*
* 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.plaidapp.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.SharedElementCallback;
import android.app.assist.AssistContent;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.customtabs.CustomTabsIntent;
import android.support.v4.content.ContextCompat;
import android.support.v7.graphics.Palette;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.text.NumberFormat;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import io.plaidapp.R;
import io.plaidapp.data.api.AuthInterceptor;
import io.plaidapp.data.api.dribbble.DribbbleService;
import io.plaidapp.data.api.dribbble.model.Comment;
import io.plaidapp.data.api.dribbble.model.Like;
import io.plaidapp.data.api.dribbble.model.Shot;
import io.plaidapp.data.prefs.DribbblePrefs;
import io.plaidapp.ui.transitions.FabDialogMorphSetup;
import io.plaidapp.ui.widget.AuthorTextView;
import io.plaidapp.ui.widget.CheckableImageButton;
import io.plaidapp.ui.widget.ElasticDragDismissFrameLayout;
import io.plaidapp.ui.widget.FABToggle;
import io.plaidapp.ui.widget.FabOverlapTextView;
import io.plaidapp.ui.widget.ForegroundImageView;
import io.plaidapp.ui.widget.ParallaxScrimageView;
import io.plaidapp.util.AnimUtils;
import io.plaidapp.util.ColorUtils;
import io.plaidapp.util.HtmlUtils;
import io.plaidapp.util.ViewUtils;
import io.plaidapp.util.customtabs.CustomTabActivityHelper;
import io.plaidapp.util.glide.CircleTransform;
import io.plaidapp.util.glide.GlideUtils;
import retrofit.RestAdapter;
import retrofit.RetrofitError;
import retrofit.client.Response;
import retrofit.converter.GsonConverter;
public class DribbbleShot extends Activity {
protected final static String EXTRA_SHOT = "shot";
private static final int RC_LOGIN_LIKE = 0;
private static final int RC_LOGIN_COMMENT = 1;
private static final float SCRIM_ADJUSTMENT = 0.075f;
@Bind(R.id.draggable_frame) ElasticDragDismissFrameLayout draggableFrame;
@Bind(R.id.back) ImageButton back;
@Bind(R.id.shot) ParallaxScrimageView imageView;
@Bind(R.id.fab_heart) FABToggle fab;
private View shotSpacer;
private View title;
private TextView description;
private LinearLayout shotActions;
private Button likeCount;
private Button viewCount;
private Button share;
private TextView playerName;
private ImageView playerAvatar;
private TextView shotTimeAgo;
private ListView commentsList;
private DribbbleCommentsAdapter commentsAdapter;
private ImageView userAvatar;
private EditText enterComment;
private ImageButton postComment;
private Shot shot;
private int fabOffset;
private DribbblePrefs dribbblePrefs;
private DribbbleService dribbbleApi;
private boolean performingLike;
private CircleTransform circleTransform;
private ElasticDragDismissFrameLayout.SystemChromeFader chromeFader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dribbble_shot);
shot = getIntent().getParcelableExtra(EXTRA_SHOT);
setupDribbble();
setExitSharedElementCallback(fabLoginSharedElementCallback);
getWindow().getSharedElementReturnTransition().addListener(shotReturnHomeListener);
Resources res = getResources();
ButterKnife.bind(this);
View shotDescription = getLayoutInflater().inflate(R.layout.dribbble_shot_description,
commentsList, false);
shotSpacer = shotDescription.findViewById(R.id.shot_spacer);
title = shotDescription.findViewById(R.id.shot_title);
description = (TextView) shotDescription.findViewById(R.id.shot_description);
shotActions = (LinearLayout) shotDescription.findViewById(R.id.shot_actions);
likeCount = (Button) shotDescription.findViewById(R.id.shot_like_count);
viewCount = (Button) shotDescription.findViewById(R.id.shot_view_count);
share = (Button) shotDescription.findViewById(R.id.shot_share_action);
playerName = (TextView) shotDescription.findViewById(R.id.player_name);
playerAvatar = (ImageView) shotDescription.findViewById(R.id.player_avatar);
shotTimeAgo = (TextView) shotDescription.findViewById(R.id.shot_time_ago);
commentsList = (ListView) findViewById(R.id.dribbble_comments);
commentsList.addHeaderView(shotDescription);
View enterCommentView = getLayoutInflater().inflate(R.layout.dribbble_enter_comment,
commentsList, false);
userAvatar = (ForegroundImageView) enterCommentView.findViewById(R.id.avatar);
enterComment = (EditText) enterCommentView.findViewById(R.id.comment);
postComment = (ImageButton) enterCommentView.findViewById(R.id.post_comment);
enterComment.setOnFocusChangeListener(enterCommentFocus);
commentsList.addFooterView(enterCommentView);
commentsList.setOnScrollListener(scrollListener);
back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
expandImageAndFinish();
}
});
fab.setOnClickListener(fabClick);
chromeFader = new ElasticDragDismissFrameLayout.SystemChromeFader(getWindow()) {
@Override
public void onDragDismissed() {
expandImageAndFinish();
}
};
circleTransform = new CircleTransform(this);
// load the main image
Glide.with(this)
.load(shot.images.best())
.listener(shotLoadListener)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.priority(Priority.IMMEDIATE)
.into(imageView);
imageView.setOnClickListener(shotClick);
shotSpacer.setOnClickListener(shotClick);
postponeEnterTransition();
imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver
.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
imageView.getViewTreeObserver().removeOnPreDrawListener(this);
calculateFabPosition();
enterAnimation();
startPostponedEnterTransition();
return true;
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
((FabOverlapTextView) title).setText(shot.title);
} else {
((TextView) title).setText(shot.title);
}
if (!TextUtils.isEmpty(shot.description)) {
HtmlUtils.setTextWithNiceLinks(description, shot.getParsedDescription(description));
} else {
description.setVisibility(View.GONE);
}
NumberFormat nf = NumberFormat.getInstance();
likeCount.setText(
res.getQuantityString(R.plurals.likes,
(int) shot.likes_count,
nf.format(shot.likes_count)));
// TODO onClick show likes
viewCount.setText(
res.getQuantityString(R.plurals.views,
(int) shot.views_count,
nf.format(shot.views_count)));
share.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new ShareDribbbleImageTask(DribbbleShot.this, shot).execute();
}
});
if (shot.user != null) {
playerName.setText("–" + shot.user.name);
Glide.with(this)
.load(shot.user.avatar_url)
.transform(circleTransform)
.placeholder(R.drawable.avatar_placeholder)
.into(playerAvatar);
playerAvatar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DribbbleShot.this.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(shot
.user.html_url)));
}
});
if (shot.created_at != null) {
shotTimeAgo.setText(DateUtils.getRelativeTimeSpanString(shot.created_at.getTime(),
System.currentTimeMillis(),
DateUtils.SECOND_IN_MILLIS));
}
} else {
playerName.setVisibility(View.GONE);
playerAvatar.setVisibility(View.GONE);
shotTimeAgo.setVisibility(View.GONE);
}
if (shot.comments_count > 0) {
loadComments();
} else {
commentsList.setAdapter(getNoCommentsAdapter());
}
if (dribbblePrefs.isLoggedIn() && !TextUtils.isEmpty(dribbblePrefs.getUserAvatar())) {
Glide.with(this)
.load(dribbblePrefs.getUserAvatar())
.transform(circleTransform)
.placeholder(R.drawable.ic_player)
.into(userAvatar);
}
}
@Override
protected void onResume() {
super.onResume();
if (!performingLike) {
checkLiked();
}
draggableFrame.addListener(chromeFader);
}
@Override
protected void onPause() {
draggableFrame.removeListener(chromeFader);
super.onPause();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case RC_LOGIN_LIKE:
if (resultCode == RESULT_OK) {
setupDribbble(); // recreate to capture the new access token
// TODO when we add more authenticated actions will need to keep track of what
// the user was trying to do when forced to login
fab.setChecked(true);
doLike();
}
break;
}
}
@Override
public void onBackPressed() {
expandImageAndFinish();
}
@Override
public boolean onNavigateUp() {
expandImageAndFinish();
return true;
}
@Override @TargetApi(Build.VERSION_CODES.M)
public void onProvideAssistContent(AssistContent outContent) {
outContent.setWebUri(Uri.parse(shot.url));
}
private View.OnClickListener shotClick = new View.OnClickListener() {
@Override
public void onClick(View view) {
openLink(shot.url);
}
};
private void openLink(String url) {
CustomTabActivityHelper.openCustomTab(
DribbbleShot.this,
new CustomTabsIntent.Builder()
.setToolbarColor(ContextCompat.getColor(DribbbleShot.this, R.color.dribbble))
.build(),
Uri.parse(url));
}
private RequestListener shotLoadListener = new RequestListener<String, GlideDrawable>() {
@Override
public boolean onResourceReady(GlideDrawable resource, String model,
Target<GlideDrawable> target, boolean isFromMemoryCache,
boolean isFirstResource) {
final Bitmap bitmap = GlideUtils.getBitmap(resource);
float imageScale = (float) imageView.getHeight() / (float) bitmap.getHeight();
float twentyFourDip = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24,
DribbbleShot.this.getResources().getDisplayMetrics());
Palette.from(bitmap)
.maximumColorCount(3)
.clearFilters()
.setRegion(0, 0, bitmap.getWidth(), (int) (twentyFourDip / imageScale))
.generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
boolean isDark;
@ColorUtils.Lightness int lightness = ColorUtils.isDark(palette);
if (lightness == ColorUtils.LIGHTNESS_UNKNOWN) {
isDark = ColorUtils.isDark(bitmap, bitmap.getWidth() / 2, 0);
} else {
isDark = lightness == ColorUtils.IS_DARK;
}
if (!isDark) { // make back icon dark on light images
back.setColorFilter(ContextCompat.getColor(
DribbbleShot.this, R.color.dark_icon));
}
// color the status bar. Set a complementary dark color on L,
// light or dark color on M (with matching status bar icons)
int statusBarColor = getWindow().getStatusBarColor();
Palette.Swatch topColor = ColorUtils.getMostPopulousSwatch(palette);
if (topColor != null &&
(isDark || Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)) {
statusBarColor = ColorUtils.scrimify(topColor.getRgb(),
isDark, SCRIM_ADJUSTMENT);
// set a light status bar on M+
if (!isDark && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ViewUtils.setLightStatusBar(imageView);
}
}
if (statusBarColor != getWindow().getStatusBarColor()) {
imageView.setScrimColor(statusBarColor);
ValueAnimator statusBarColorAnim = ValueAnimator.ofArgb(getWindow
().getStatusBarColor(), statusBarColor);
statusBarColorAnim.addUpdateListener(new ValueAnimator
.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
getWindow().setStatusBarColor((int) animation
.getAnimatedValue());
}
});
statusBarColorAnim.setDuration(1000);
statusBarColorAnim.setInterpolator(AnimationUtils
.loadInterpolator(DribbbleShot.this, android.R
.interpolator.fast_out_slow_in));
statusBarColorAnim.start();
}
}
});
Palette.from(bitmap)
.clearFilters() // by default palette ignore certain hues (e.g. pure
// black/white) but we don't want this.
.generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
// color the ripple on the image spacer (default is grey)
shotSpacer.setBackground(ViewUtils.createRipple(palette, 0.25f, 0.5f,
ContextCompat.getColor(DribbbleShot.this, R.color.mid_grey),
true));
// slightly more opaque ripple on the pinned image to compensate
// for the scrim
imageView.setForeground(ViewUtils.createRipple(palette, 0.3f, 0.6f,
ContextCompat.getColor(DribbbleShot.this, R.color.mid_grey),
true));
}
});
// TODO should keep the background if the image contains transparency?!
imageView.setBackground(null);
return false;
}
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target,
boolean isFirstResource) {
return false;
}
};
private View.OnFocusChangeListener enterCommentFocus = new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean hasFocus) {
// kick off an anim (via animated state list) on the post button. see
// @drawable/ic_add_comment_state
postComment.setActivated(hasFocus);
}
};
private AbsListView.OnScrollListener scrollListener = new AbsListView.OnScrollListener() {
@Override
public void onScroll(AbsListView view, int firstVisibleItemPosition, int
visibleItemCount, int totalItemCount) {
if (commentsList.getMaxScrollAmount() > 0
&& firstVisibleItemPosition == 0
&& commentsList.getChildAt(0) != null) {
int listScroll = commentsList.getChildAt(0).getTop();
imageView.setOffset(listScroll);
fab.setOffset(fabOffset + listScroll);
}
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
// as we animate the main image's elevation change when it 'pins' at it's min height
// a fling can cause the title to go over the image before the animation has a chance to
// run. In this case we short circuit the animation and just jump to state.
imageView.setImmediatePin(scrollState == AbsListView.OnScrollListener
.SCROLL_STATE_FLING);
}
};
private View.OnClickListener fabClick = new View.OnClickListener() {
@Override
public void onClick(View view) {
if (dribbblePrefs.isLoggedIn()) {
fab.toggle();
doLike();
} else {
Intent login = new Intent(DribbbleShot.this, DribbbleLogin.class);
login.putExtra(FabDialogMorphSetup.EXTRA_SHARED_ELEMENT_START_COLOR,
ContextCompat.getColor(DribbbleShot.this, R.color.dribbble));
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation
(DribbbleShot.this, fab, getString(R.string.transition_dribbble_login));
startActivityForResult(login, RC_LOGIN_LIKE, options.toBundle());
}
}
};
private SharedElementCallback fabLoginSharedElementCallback = new SharedElementCallback() {
@Override
public Parcelable onCaptureSharedElementSnapshot(View sharedElement,
Matrix viewToGlobalMatrix,
RectF screenBounds) {
// store a snapshot of the fab to fade out when morphing to the login dialog
int bitmapWidth = Math.round(screenBounds.width());
int bitmapHeight = Math.round(screenBounds.height());
Bitmap bitmap = null;
if (bitmapWidth > 0 && bitmapHeight > 0) {
bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
sharedElement.draw(new Canvas(bitmap));
}
return bitmap;
}
};
private Transition.TransitionListener shotReturnHomeListener = new AnimUtils
.TransitionListenerAdapter() {
@Override
public void onTransitionStart(Transition transition) {
super.onTransitionStart(transition);
// hide the fab as for some reason it jumps position?? TODO work out why
fab.setVisibility(View.INVISIBLE);
// fade out the "toolbar" & list as we don't want them to be visible during return
// animation
back.animate()
.alpha(0f)
.setDuration(100)
.setInterpolator(AnimationUtils.loadInterpolator(DribbbleShot.this, android.R
.interpolator.linear_out_slow_in));
imageView.setElevation(1f);
back.setElevation(0f);
commentsList.animate()
.alpha(0f)
.setDuration(50)
.setInterpolator(AnimationUtils.loadInterpolator(DribbbleShot.this, android.R
.interpolator.linear_out_slow_in));
}
};
private void loadComments() {
commentsList.setAdapter(getLoadingCommentsAdapter());
// then load comments
dribbbleApi.getComments(shot.id, null, DribbbleService.PER_PAGE_MAX, new retrofit
.Callback<List<Comment>>() {
@Override
public void success(List<Comment> comments, Response response) {
if (comments != null && !comments.isEmpty()) {
commentsAdapter = new DribbbleCommentsAdapter(DribbbleShot.this, R.layout
.dribbble_comment, comments);
commentsList.setAdapter(commentsAdapter);
commentsList.setDivider(getDrawable(R.drawable.list_divider));
commentsList.setDividerHeight(getResources().getDimensionPixelSize(R.dimen
.divider_height));
}
}
@Override
public void failure(RetrofitError error) {
}
});
}
private void expandImageAndFinish() {
if (imageView.getOffset() != 0f) {
Animator expandImage = ObjectAnimator.ofFloat(imageView, ParallaxScrimageView.OFFSET,
0f);
expandImage.setDuration(80);
expandImage.setInterpolator(AnimationUtils.loadInterpolator(this, android.R
.interpolator.fast_out_slow_in));
expandImage.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finishAfterTransition();
}
});
expandImage.start();
} else {
finishAfterTransition();
}
}
private void setupDribbble() {
// setup the api object which captures the current access token
dribbblePrefs = DribbblePrefs.get(this);
Gson gson = new GsonBuilder()
.setDateFormat(DribbbleService.DATE_FORMAT)
.create();
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(DribbbleService.ENDPOINT)
.setConverter(new GsonConverter(gson))
.setRequestInterceptor(new AuthInterceptor(dribbblePrefs.getAccessToken()))
.build();
dribbbleApi = restAdapter.create(DribbbleService.class);
}
private void calculateFabPosition() {
// calculate 'natural' position i.e. with full height image. Store it for use when scrolling
fabOffset = imageView.getHeight() + title.getHeight() - (fab.getHeight() / 2);
fab.setOffset(fabOffset);
// calculate min position i.e. pinned to the collapsed image when scrolled
fab.setMinOffset(imageView.getMinimumHeight() - (fab.getHeight() / 2));
}
/**
* Animate in the title, description and author – can't do this in a content transition as they
* are within the ListView so do it manually. Also handle the FAB tanslation here so that it
* plays nicely with #calculateFabPosition
*/
private void enterAnimation() {
Interpolator interp = AnimationUtils.loadInterpolator(this, android.R.interpolator
.fast_out_slow_in);
int offset = title.getHeight();
viewEnterAnimation(title, offset, interp);
if (description.getVisibility() == View.VISIBLE) {
offset *= 1.5f;
viewEnterAnimation(description, offset, interp);
}
// animate the fab without touching the alpha as this is handled in the content transition
offset *= 1.5f;
float fabTransY = fab.getTranslationY();
fab.setTranslationY(fabTransY + offset);
fab.animate()
.translationY(fabTransY)
.setDuration(600)
.setInterpolator(interp)
.start();
offset *= 1.5f;
viewEnterAnimation(shotActions, offset, interp);
offset *= 1.5f;
viewEnterAnimation(playerName, offset, interp);
viewEnterAnimation(playerAvatar, offset, interp);
viewEnterAnimation(shotTimeAgo, offset, interp);
back.animate()
.alpha(1f)
.setDuration(600)
.setInterpolator(interp)
.start();
}
private void viewEnterAnimation(View view, float offset, Interpolator interp) {
view.setTranslationY(offset);
view.setAlpha(0.8f);
view.animate()
.translationY(0f)
.alpha(1f)
.setDuration(600)
.setInterpolator(interp)
.setListener(null)
.start();
}
private void doLike() {
performingLike = true;
if (fab.isChecked()) {
dribbbleApi.like(shot.id, "", new retrofit.Callback<Like>() {
@Override
public void success(Like like, Response response) {
performingLike = false;
}
@Override
public void failure(RetrofitError error) {
performingLike = false;
}
});
} else {
dribbbleApi.unlike(shot.id, new retrofit.Callback<Void>() {
@Override
public void success(Void aVoid, Response response) {
performingLike = false;
}
@Override
public void failure(RetrofitError error) {
performingLike = false;
}
});
}
}
private void checkLiked() {
if (dribbblePrefs.isLoggedIn()) {
dribbbleApi.liked(shot.id, new retrofit.Callback<Like>() {
@Override
public void success(Like like, Response response) {
// note that like.user will be null here
fab.setChecked(like != null);
fab.jumpDrawablesToCurrentState();
}
@Override
public void failure(RetrofitError error) {
// 404 is expected if shot is not liked
fab.setChecked(false);
fab.jumpDrawablesToCurrentState();
}
});
}
}
public void postComment(View view) {
if (dribbblePrefs.isLoggedIn()) {
if (TextUtils.isEmpty(enterComment.getText())) return;
enterComment.setEnabled(false);
dribbbleApi.postComment(shot.id, enterComment.getText().toString().trim(), new retrofit
.Callback<Comment>() {
@Override
public void success(Comment comment, Response response) {
loadComments();
enterComment.getText().clear();
enterComment.setEnabled(true);
}
@Override
public void failure(RetrofitError error) {
enterComment.setEnabled(true);
}
});
} else {
Intent login = new Intent(DribbbleShot.this, DribbbleLogin.class);
login.putExtra(FabDialogMorphSetup.EXTRA_SHARED_ELEMENT_START_COLOR, ContextCompat.getColor
(this, R.color.background_light));
ActivityOptions options =
ActivityOptions.makeSceneTransitionAnimation(DribbbleShot.this, postComment,
getString(R.string.transition_dribbble_login));
startActivityForResult(login, RC_LOGIN_COMMENT, options.toBundle());
}
}
private boolean isOP(long playerId) {
return shot.user != null && shot.user.id == playerId;
}
private ListAdapter getNoCommentsAdapter() {
String[] noComments = {getString(R.string.no_comments)};
return new ArrayAdapter<>(this, R.layout.dribbble_no_comments, noComments);
}
private ListAdapter getLoadingCommentsAdapter() {
return new BaseAdapter() {
@Override
public int getCount() {
return 1;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return DribbbleShot.this.getLayoutInflater().inflate(R.layout.loading, parent,
false);
}
};
}
protected class DribbbleCommentsAdapter extends ArrayAdapter<Comment> {
private final LayoutInflater inflater;
private int expandedCommentPosition = ListView.INVALID_POSITION;
public DribbbleCommentsAdapter(Context context, int resource, List<Comment> comments) {
super(context, resource, comments);
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public View getView(int position, View view, ViewGroup container) {
if (view == null) {
view = newNewCommentView(position, container);
}
bindComment(getItem(position), position, view);
return view;
}
private View newNewCommentView(int position, ViewGroup parent) {
View view = inflater.inflate(R.layout.dribbble_comment, parent, false);
view.setTag(R.id.player_avatar, view.findViewById(R.id.player_avatar));
view.setTag(R.id.comment_author, view.findViewById(R.id.comment_author));
view.setTag(R.id.comment_time_ago, view.findViewById(R.id.comment_time_ago));
view.setTag(R.id.comment_text, view.findViewById(R.id.comment_text));
view.setTag(R.id.comment_reply, view.findViewById(R.id.comment_reply));
view.setTag(R.id.comment_like, view.findViewById(R.id.comment_like));
view.setTag(R.id.comment_likes_count, view.findViewById(R.id.comment_likes_count));
return view;
}
private void bindComment(final Comment comment, final int position, final View view) {
final ImageView avatar = (ImageView) view.getTag(R.id.player_avatar);
final AuthorTextView author = (AuthorTextView) view.getTag(R.id.comment_author);
final TextView timeAgo = (TextView) view.getTag(R.id.comment_time_ago);
final TextView commentBody = (TextView) view.getTag(R.id.comment_text);
final ImageButton reply = (ImageButton) view.getTag(R.id.comment_reply);
final CheckableImageButton likeHeart = (CheckableImageButton) view.getTag(R.id
.comment_like);
final TextView likesCount = (TextView) view.getTag(R.id.comment_likes_count);
Glide.with(DribbbleShot.this)
.load(comment.user.avatar_url)
.transform(circleTransform)
.placeholder(R.drawable.avatar_placeholder)
.into(avatar);
avatar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DribbbleShot.this.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse
(comment.user.html_url)));
}
});
author.setText(comment.user.name);
author.setOriginalPoster(isOP(comment.user.id));
timeAgo.setText(comment.created_at == null ? "" :
DateUtils.getRelativeTimeSpanString(comment.created_at.getTime(),
System.currentTimeMillis(),
DateUtils.SECOND_IN_MILLIS));
HtmlUtils.setTextWithNiceLinks(commentBody, comment.getParsedBody(commentBody));
view.setActivated(position == expandedCommentPosition);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean isExpanded = reply.getVisibility() == View.VISIBLE;
TransitionManager.beginDelayedTransition((ViewGroup) view);
view.setActivated(!isExpanded);
if (!isExpanded) { // do expand
expandedCommentPosition = position;
reply.setVisibility(View.VISIBLE);
likeHeart.setVisibility(View.VISIBLE);
likesCount.setVisibility(View.VISIBLE);
if (comment.liked == null) {
dribbbleApi.likedComment(shot.id, comment.id, new retrofit
.Callback<Like>() {
@Override
public void success(Like like, Response response) {
comment.liked = true;
likeHeart.setChecked(true);
likeHeart.jumpDrawablesToCurrentState();
}
@Override
public void failure(RetrofitError error) {
comment.liked = false;
likeHeart.setChecked(false);
likeHeart.jumpDrawablesToCurrentState();
}
});
}
} else { // do collapse
expandedCommentPosition = ListView.INVALID_POSITION;
reply.setVisibility(View.GONE);
likeHeart.setVisibility(View.GONE);
likesCount.setVisibility(View.GONE);
}
notifyDataSetChanged();
}
});
reply.setVisibility(position == expandedCommentPosition ? View.VISIBLE : View.GONE);
reply.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
enterComment.setText("@" + comment.user.username + " ");
enterComment.setSelection(enterComment.getText().length());
// collapse the comment and scroll the reply box (in the footer) into view
expandedCommentPosition = ListView.INVALID_POSITION;
notifyDataSetChanged();
enterComment.requestFocus();
commentsList.smoothScrollToPositionFromTop(commentsList.getCount(), 0, 300);
}
});
likeHeart.setChecked(comment.liked != null && comment.liked.booleanValue());
likeHeart.setVisibility(position == expandedCommentPosition ? View.VISIBLE : View.GONE);
if (comment.user.id != dribbblePrefs.getUserId()) {
likeHeart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (dribbblePrefs.isLoggedIn()) {
if (comment.liked == null || !comment.liked) {
comment.liked = true;
comment.likes_count++;
likesCount.setText(String.valueOf(comment.likes_count));
notifyDataSetChanged();
dribbbleApi.likeComment(shot.id, comment.id, "", new retrofit
.Callback<Like>() {
@Override
public void success(Like like, Response response) {
}
@Override
public void failure(RetrofitError error) {
}
});
} else {
comment.liked = false;
comment.likes_count--;
likesCount.setText(String.valueOf(comment.likes_count));
notifyDataSetChanged();
dribbbleApi.unlikeComment(shot.id, comment.id, new retrofit
.Callback<Void>() {
@Override
public void success(Void voyd, Response response) {
}
@Override
public void failure(RetrofitError error) {
}
});
}
} else {
likeHeart.setChecked(false);
startActivityForResult(new Intent(DribbbleShot.this, DribbbleLogin
.class), RC_LOGIN_LIKE);
}
}
});
}
likesCount.setVisibility(position == expandedCommentPosition ? View.VISIBLE : View
.GONE);
likesCount.setText(String.valueOf(comment.likes_count));
likesCount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dribbbleApi.getCommentLikes(shot.id, comment.id, new retrofit
.Callback<List<Like>>() {
@Override
public void success(List<Like> likes, Response response) {
// TODO something better than this.
StringBuilder sb = new StringBuilder("Liked by:\n\n");
for (Like like : likes) {
if (like.user != null) {
sb.append("@");
sb.append(like.user.username);
sb.append("\n");
}
}
Toast.makeText(getApplicationContext(), sb.toString(), Toast
.LENGTH_SHORT).show();
}
@Override
public void failure(RetrofitError error) {
Log.e("GET COMMENT LIKES", error.getMessage(), error);
}
});
}
});
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public long getItemId(int position) {
return getItem(position).id;
}
}
}