Java tutorial
/* * Moments - To the best Instagram client * Copyright (C) 2015 XiNGRZ <xxx@oxo.ooo> * * 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 2 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 ooo.oxo.moments.user; import android.animation.Animator; import android.annotation.TargetApi; import android.content.Intent; import android.os.Build; import android.os.Bundle; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CollapsingToolbarLayout; import android.support.v4.view.ViewCompat; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.transition.Transition; import android.util.Log; import android.util.Pair; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewAnimationUtils; import android.widget.ImageView; import android.widget.TextView; import com.bumptech.glide.Glide; 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 java.util.List; import butterknife.Bind; import butterknife.BindColor; import butterknife.BindDimen; import butterknife.ButterKnife; import jp.wasabeef.glide.transformations.CropCircleTransformation; import ooo.oxo.moments.InstaApplication; import ooo.oxo.moments.InstaSharedState; import ooo.oxo.moments.R; import ooo.oxo.moments.feed.FeedAdapter; import ooo.oxo.moments.model.Media; import ooo.oxo.moments.model.User; import ooo.oxo.moments.net.Envelope; import ooo.oxo.moments.net.UserApi; import ooo.oxo.moments.util.StatusBarTintDelegate; import ooo.oxo.moments.util.StatusBarUtils; import ooo.oxo.moments.util.ViewGroupUtils; import pocketknife.BindExtra; import pocketknife.NotRequired; import pocketknife.PocketKnife; import rx.Observable; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Func2; public class UserActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener, UserGridAdapter.GridListener, RequestListener<String, GlideDrawable> { private static final String TAG = "UserActivity"; @Bind(R.id.appbar) AppBarLayout appbar; @Bind(R.id.collapsing_toolbar) CollapsingToolbarLayout collapsingToolbar; @Bind(R.id.toolbar) Toolbar toolbar; @Bind(R.id.refresher) SwipeRefreshLayout refresher; @Bind(R.id.content) RecyclerView content; @Bind(R.id.avatar) ImageView avatar; @Bind(R.id.full_name) TextView fullName; @Bind(R.id.user_name) TextView userName; @Bind(R.id.bio) TextView bio; @Bind(R.id.counts) View counts; @Bind(R.id.posts) TextView posts; @Bind(R.id.followers) TextView followers; @Bind(R.id.following) TextView following; @BindColor(R.color.primary) int colorPrimary; @BindDimen(R.dimen.srl_start) int refresherStart; @BindDimen(R.dimen.srl_length) int refresherLength; @BindDimen(R.dimen.item_margin_grid) int marginGrid; @BindDimen(R.dimen.item_margin_stream) int marginStream; int statusBarHeight = 0; @BindExtra("id") @NotRequired String id; @BindExtra("user") @NotRequired User user; @BindExtra("from_post") @NotRequired String fromPostId; private MenuItem viewAsGrid; private MenuItem viewAsStream; private String accessToken; private UserApi userApi; private FeedAdapter streamAdapter; private UserGridAdapter gridAdapter; private Animator avatarReveal; private boolean isEntered = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.user_activity); ButterKnife.bind(this); PocketKnife.bindExtras(this); setTitle(null); setSupportActionBar(toolbar); statusBarHeight = StatusBarUtils.getStatusBarHeight(this); collapsingToolbar.setPadding(0, statusBarHeight, 0, 0); appbar.addOnOffsetChangedListener((AppBarLayout appbar, int i) -> refresher.setEnabled(i == 0)); toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp); toolbar.setNavigationOnClickListener(v -> supportFinishAfterTransition()); toolbar.post(() -> appbar .addOnOffsetChangedListener(new StatusBarTintDelegate(this, toolbar.getHeight(), statusBarHeight))); refresher.setProgressViewOffset(true, refresherStart, refresherStart + refresherLength); refresher.setColorSchemeColors(colorPrimary); refresher.setOnRefreshListener(this); accessToken = InstaSharedState.getInstance().getAccessToken(); userApi = InstaApplication.from(this).createApi(UserApi.class); if (user != null) { populateProfile(user); } if (id == null && user != null) { id = user.id; } if (id == null) { throw new IllegalStateException("Must specify which user to load"); } if (Build.VERSION.SDK_INT >= 21 && fromPostId != null) { isEntered = false; supportPostponeEnterTransition(); ViewCompat.setTransitionName(avatar, id + "_" + fromPostId + "_avatar"); getWindow().getSharedElementEnterTransition().addListener(new Transition.TransitionListener() { @Override public void onTransitionStart(Transition transition) { } @Override public void onTransitionEnd(Transition transition) { //revealAvatarView(); } @Override public void onTransitionCancel(Transition transition) { } @Override public void onTransitionPause(Transition transition) { } @Override public void onTransitionResume(Transition transition) { } }); } streamAdapter = new FeedAdapter(this, this); gridAdapter = new UserGridAdapter(this, this); viewAsStream(); load(); } @Override public void onRefresh() { load(); } private void load() { refresher.post(() -> refresher.setRefreshing(true)); Observable.combineLatest(userApi.profile(id, accessToken), userApi.timeline(id, 200, accessToken), (Func2<Envelope<User>, Envelope<List<Media>>, Pair<Envelope<User>, Envelope<List<Media>>>>) Pair::new) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> populate(result.first, result.second), throwable -> Log.d(TAG, "failed to load user", throwable)); } @TargetApi(21) private void setupAvatarReveal() { // FIXME: it flashes, unexpectedly int[] bounds = ViewGroupUtils.calculateBounds(avatar, appbar); avatarReveal = ViewAnimationUtils.createCircularReveal(appbar, (int) (bounds[0] + (float) avatar.getWidth() / 2f), (int) (bounds[1] + (float) avatar.getHeight() / 2f), (float) avatar.getWidth() / 2f, (float) bounds[2]); } @TargetApi(21) private void revealAvatarView() { if (avatarReveal != null) { avatarReveal.start(); avatarReveal = null; } } private void populate(Envelope<User> profile, Envelope<List<Media>> timeline) { refresher.setRefreshing(false); if (profile == null || timeline == null) { return; } streamAdapter.setFeed(timeline.data); gridAdapter.setFeed(timeline.data); populateProfile(profile.data); } private void populateProfile(User profile) { Glide.with(this).load(profile.profilePicture).diskCacheStrategy(DiskCacheStrategy.ALL) .bitmapTransform(new CropCircleTransformation(this)).listener(this).into(avatar); userName.setText(profile.username); if (TextUtils.isEmpty(profile.fullName)) { fullName.setVisibility(View.GONE); } else { fullName.setText(profile.fullName); fullName.setVisibility(View.VISIBLE); } if (TextUtils.isEmpty(profile.bio)) { bio.setVisibility(View.GONE); } else { bio.setText(profile.bio); bio.setVisibility(View.VISIBLE); } if (profile.counts != null) { posts.setText(String.valueOf(profile.counts.media)); followers.setText(String.valueOf(profile.counts.followedBy)); following.setText(String.valueOf(profile.counts.follows)); counts.setVisibility(View.VISIBLE); } } @Override public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) { supportStartPostponedEnterTransition(); isEntered = true; return false; } @Override public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) { if (!isEntered) { //setupAvatarReveal(); supportStartPostponedEnterTransition(); isEntered = true; } return false; } @Override public void onUserClick(FeedAdapter.ViewHolder holder) { } @Override public void onUserClick(String id) { if (this.id.equals(id)) { return; } Intent intent = new Intent(this, UserActivity.class); intent.putExtra("id", id); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } @Override public void onImageClick(FeedAdapter.ViewHolder holder) { } @Override public void onImageClick(UserGridAdapter.ViewHolder holder) { } @Override public void onLikesClick(FeedAdapter.ViewHolder holder) { } @Override public void onLike(FeedAdapter.ViewHolder holder) { } @Override public void onComment(FeedAdapter.ViewHolder holder) { } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.user, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { viewAsGrid = menu.findItem(R.id.view_as_grid); viewAsStream = menu.findItem(R.id.view_as_stream); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.view_as_grid: viewAsGrid.setVisible(false); viewAsStream.setVisible(true); viewAsGrid(); return true; case R.id.view_as_stream: viewAsStream.setVisible(false); viewAsGrid.setVisible(true); viewAsStream(); return true; default: return super.onOptionsItemSelected(item); } } private void viewAsStream() { content.setPadding(marginStream, marginStream, marginStream, marginStream); content.setLayoutManager(new LinearLayoutManager(this)); content.setAdapter(streamAdapter); } private void viewAsGrid() { content.setPadding(marginGrid, marginGrid, marginGrid, marginGrid); content.setLayoutManager(new GridLayoutManager(this, 3)); content.setAdapter(gridAdapter); } }