/*
* Copyright (c) 2016 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.douya.profile.ui;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.graphics.Outline;
import android.os.Build;
import android.support.v4.widget.TextViewCompat;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import butterknife.BindColor;
import butterknife.BindDimen;
import butterknife.BindView;
import butterknife.ButterKnife;
import de.hdodenhof.circleimageview.CircleImageView;
import me.zhanghai.android.douya.R;
import me.zhanghai.android.douya.followship.content.FollowUserManager;
import me.zhanghai.android.douya.network.api.info.apiv2.SimpleUser;
import me.zhanghai.android.douya.network.api.info.apiv2.User;
import me.zhanghai.android.douya.profile.util.ProfileUtils;
import me.zhanghai.android.douya.ui.FlexibleSpaceHeaderView;
import me.zhanghai.android.douya.ui.GalleryActivity;
import me.zhanghai.android.douya.ui.JoinedAtLocationAutoGoneTextView;
import me.zhanghai.android.douya.ui.WhiteIndeterminateProgressIconDrawable;
import me.zhanghai.android.douya.util.AppUtils;
import me.zhanghai.android.douya.util.ImageUtils;
import me.zhanghai.android.douya.util.MathUtils;
import me.zhanghai.android.douya.util.StatusBarColorUtils;
import me.zhanghai.android.douya.util.ViewUtils;
/**
* Set the initial layout_height to match_parent or wrap_content instead a specific value so that
* the view measures itself correctly for the first time.
*/
public class ProfileHeaderLayout extends FrameLayout implements FlexibleSpaceHeaderView {
@BindColor(R.color.system_window_scrim)
int mStatusBarColorScrim;
private int mStatusBarColorFullscreen;
@BindDimen(R.dimen.profile_large_avatar_size)
int mLargeAvatarSize;
@BindDimen(R.dimen.profile_small_avatar_size)
int mSmallAvatarSize;
@BindDimen(R.dimen.screen_edge_horizontal_margin)
int mScreenEdgeHorizontalMargin;
@BindDimen(R.dimen.toolbar_height)
int mToolbarHeight;
@BindView(R.id.dismiss)
View mDismissView;
@BindView(R.id.appBar)
LinearLayout mAppBarLayout;
@BindView(R.id.toolbar)
Toolbar mToolbar;
@BindView(R.id.toolbar_username)
TextView mToolbarUsernameText;
@BindView(R.id.username)
TextView mUsernameText;
@BindView(R.id.signature)
TextView mSignatureText;
@BindView(R.id.joined_at_location)
JoinedAtLocationAutoGoneTextView mJoinedAtLocationText;
@BindView(R.id.follow)
Button mFollowButton;
@BindView(R.id.avatar_container)
FrameLayout mAvatarContainerLayout;
@BindView(R.id.avatar)
CircleImageView mAvatarImage;
private boolean mUseWideLayout;
private int mInsetTop;
private int mMaxHeight;
private int mScroll;
private Listener mListener;
public ProfileHeaderLayout(Context context) {
super(context);
init();
}
public ProfileHeaderLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ProfileHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ProfileHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void init() {
// HACK: We need to delegate the outline so that elevation can work.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setOutlineProvider(new ViewOutlineProvider() {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void getOutline(View view, Outline outline) {
// We cannot use mAppBarLayout.getOutlineProvider.getOutline() because the
// bounds of it is not kept in sync when this method is called.
// HACK: Workaround the fact that we must provided an outline before we are
// measured.
int height = getHeight();
int top = height > 0 ? height - computeVisibleAppBarHeight() : 0;
outline.setRect(0, top, getWidth(), height);
}
});
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
ButterKnife.bind(this);
Context context = getContext();
mStatusBarColorFullscreen = ViewUtils.getColorFromAttrRes(R.attr.colorPrimaryDark, 0,
context);
mUseWideLayout = ProfileUtils.shouldUseWideLayout(context);
StatusBarColorUtils.set(mStatusBarColorScrim, (Activity) context);
}
public void setListener(Listener listener) {
mListener = listener;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.EXACTLY);
}
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int dismissViewHeight = height - computeVisibleAppBarHeight();
mDismissView.getLayoutParams().height = dismissViewHeight;
MarginLayoutParams appBarLayoutLayoutParams =
(MarginLayoutParams) mAppBarLayout.getLayoutParams();
appBarLayoutLayoutParams.topMargin = dismissViewHeight;
// So that the layout remains stable.
appBarLayoutLayoutParams.height = getAppBarMaxHeight();
int appBarWidth = ProfileUtils.getAppBarWidth(width, getContext());
if (mUseWideLayout) {
mAppBarLayout.setPadding(mAppBarLayout.getPaddingLeft(), mAppBarLayout.getPaddingTop(),
width - appBarWidth, mAppBarLayout.getPaddingBottom());
}
int largeAvatarSizeHalf = mLargeAvatarSize / 2;
int avatarMarginTop = dismissViewHeight - mInsetTop - largeAvatarSizeHalf;
int smallAvatarMarginTop = (mToolbarHeight - mSmallAvatarSize) / 2;
float avatarHorizontalFraction = avatarMarginTop < smallAvatarMarginTop ?
MathUtils.unlerp(smallAvatarMarginTop, -largeAvatarSizeHalf, avatarMarginTop) : 0;
avatarMarginTop = Math.max(smallAvatarMarginTop, avatarMarginTop) + mInsetTop;
int avatarMarginLeft = MathUtils.lerp(appBarWidth / 2 - largeAvatarSizeHalf,
mScreenEdgeHorizontalMargin, avatarHorizontalFraction);
MarginLayoutParams avatarContainerLayoutParams =
(MarginLayoutParams) mAvatarContainerLayout.getLayoutParams();
avatarContainerLayoutParams.leftMargin = avatarMarginLeft;
avatarContainerLayoutParams.topMargin = avatarMarginTop;
float avatarScale = MathUtils.lerp(1, (float) mSmallAvatarSize / mLargeAvatarSize,
avatarHorizontalFraction);
mAvatarContainerLayout.setPivotX(0);
mAvatarContainerLayout.setPivotY(0);
mAvatarContainerLayout.setScaleX(avatarScale);
mAvatarContainerLayout.setScaleY(avatarScale);
for (int i = 0, count = mAppBarLayout.getChildCount(); i < count; ++i) {
View child = mAppBarLayout.getChildAt(i);
if (child != mToolbar) {
child.setAlpha(Math.max(0, 1 - getFraction() * 2));
}
}
mToolbarUsernameText.setAlpha(Math.max(0, getFraction() * 2 - 1));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public void setInsetTop(int insetTop) {
if (mInsetTop == insetTop) {
return;
}
mInsetTop = insetTop;
requestLayout();
}
private int getAppBarMinHeight() {
if (mUseWideLayout) {
return getAppBarMaxHeight();
} else {
// So that we don't need to wait until measure.
return mToolbar.getLayoutParams().height;
}
}
private int getAppBarMaxHeight() {
return mUseWideLayout ? mMaxHeight * 3 / 5 : mMaxHeight / 2;
}
private int computeVisibleAppBarHeight() {
return MathUtils.lerp(getAppBarMaxHeight(), getAppBarMinHeight(), getFraction());
}
private float getFraction() {
int scrollExtent = getScrollExtent();
return scrollExtent > 0 ? (float) mScroll / scrollExtent : 0;
}
@Override
public int getScroll() {
return mScroll;
}
@Override
public int getScrollExtent() {
return mMaxHeight - getMinHeight();
}
@Override
public void scrollTo(int scroll) {
int scrollExtent = getScrollExtent();
scroll = MathUtils.clamp(scroll, 0, scrollExtent);
if (mScroll == scroll) {
return;
}
ViewUtils.setHeight(this, mMaxHeight - scroll);
int oldScroll = mScroll;
mScroll = scroll;
if (oldScroll < scrollExtent && mScroll == scrollExtent) {
StatusBarColorUtils.animateTo(mStatusBarColorFullscreen,
AppUtils.getActivityFromContext(getContext()));
} else if (oldScroll == scrollExtent && mScroll < oldScroll) {
StatusBarColorUtils.animateTo(mStatusBarColorScrim,
AppUtils.getActivityFromContext(getContext()));
}
}
private int getMinHeight() {
if (mUseWideLayout) {
return mMaxHeight;
} else {
// So that we don't need to wait until measure.
return mToolbar.getLayoutParams().height + mInsetTop;
}
}
// Should be called by ProfileLayout.onMeasure() before its super call.
public void setMaxHeight(int maxHeight) {
if (mMaxHeight == maxHeight) {
return;
}
mMaxHeight = maxHeight;
ViewUtils.setHeight(mAppBarLayout, mMaxHeight);
}
public void bindSimpleUser(SimpleUser simpleUser) {
final String largeAvatar = simpleUser.getLargeAvatarOrAvatar();
ImageUtils.loadProfileAvatarAndFadeIn(mAvatarImage, largeAvatar);
final Context context = getContext();
mAvatarImage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
context.startActivity(GalleryActivity.makeIntent(largeAvatar, context));
}
});
mToolbarUsernameText.setText(simpleUser.name);
mUsernameText.setText(simpleUser.name);
mSignatureText.setText(null);
mJoinedAtLocationText.setText(null);
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mFollowButton, 0, 0, 0, 0);
mFollowButton.setVisibility(GONE);
}
public void bindUser(final User user) {
final Context context = getContext();
if (!ViewUtils.isVisible(mAvatarImage)) {
// HACK: Don't load avatar again if already loaded by bindSimpleUser().
final String largeAvatar = user.getLargeAvatarOrAvatar();
ImageUtils.loadProfileAvatarAndFadeIn(mAvatarImage, largeAvatar);
mAvatarImage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
context.startActivity(GalleryActivity.makeIntent(largeAvatar, context));
}
});
}
mToolbarUsernameText.setText(user.name);
mUsernameText.setText(user.name);
mSignatureText.setText(user.signature);
mJoinedAtLocationText.setJoinedAtAndLocation(user.createdAt, user.locationName);
if (user.isOneself()) {
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mFollowButton,
R.drawable.edit_icon_white_24dp, 0, 0, 0);
mFollowButton.setText(R.string.profile_edit);
mFollowButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (mListener != null) {
mListener.onEditProfile(user);
}
}
});
} else {
FollowUserManager followUserManager = FollowUserManager.getInstance();
String userIdOrUid = user.getIdOrUid();
if (followUserManager.isWriting(userIdOrUid)) {
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mFollowButton,
new WhiteIndeterminateProgressIconDrawable(context), null, null, null);
mFollowButton.setText(followUserManager.isWritingFollow(userIdOrUid) ?
R.string.user_following : R.string.user_unfollowing);
} else {
int followDrawableId;
int followStringId;
if (user.isFollowed) {
if (user.isFollower) {
followDrawableId = R.drawable.mutual_icon_white_24dp;
followStringId = R.string.profile_following_mutual;
} else {
followDrawableId = R.drawable.ok_icon_white_24dp;
followStringId = R.string.profile_following;
}
} else {
followDrawableId = R.drawable.add_icon_white_24dp;
followStringId = R.string.profile_follow;
}
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mFollowButton,
followDrawableId, 0, 0, 0);
mFollowButton.setText(followStringId);
}
mFollowButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (mListener != null) {
mListener.onFollowUser(user, !user.isFollowed);
}
}
});
}
mFollowButton.setVisibility(VISIBLE);
}
public interface Listener {
void onEditProfile(User user);
void onFollowUser(User user, boolean follow);
}
}