Merge branch 'media-preview' of https://github.com/charlag/Tusky into charlag-media-preview

This commit is contained in:
Vavassor 2017-07-16 19:59:17 -04:00
commit f7d4d20984
11 changed files with 222 additions and 50 deletions

View file

@ -16,10 +16,13 @@
package com.keylesspalace.tusky; package com.keylesspalace.tusky;
import android.Manifest; import android.Manifest;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.DownloadManager; import android.app.DownloadManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
@ -45,20 +48,25 @@ import com.keylesspalace.tusky.view.ImageViewPager;
import java.io.File; import java.io.File;
public class ViewMediaActivity extends BaseActivity implements ViewMediaFragment.OnDismissListener { public class ViewMediaActivity extends BaseActivity implements ViewMediaFragment.PhotoActionsListener {
private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1; private static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
private ImageViewPager viewPager; private ImageViewPager viewPager;
private View anyView; private View anyView;
private String[] imageUrls; private String[] imageUrls;
private Toolbar toolbar;
private boolean isToolbarVisible = true;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_media); setContentView(R.layout.activity_view_media);
supportPostponeEnterTransition();
// Obtain the views. // Obtain the views.
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar = (Toolbar) findViewById(R.id.toolbar);
viewPager = (ImageViewPager) findViewById(R.id.view_pager); viewPager = (ImageViewPager) findViewById(R.id.view_pager);
anyView = toolbar; anyView = toolbar;
@ -69,13 +77,14 @@ public class ViewMediaActivity extends BaseActivity implements ViewMediaFragment
// Setup the view pager. // Setup the view pager.
final ImagePagerAdapter adapter = new ImagePagerAdapter(getSupportFragmentManager(), final ImagePagerAdapter adapter = new ImagePagerAdapter(getSupportFragmentManager(),
imageUrls); imageUrls, initialPosition);
viewPager.setAdapter(adapter); viewPager.setAdapter(adapter);
viewPager.setCurrentItem(initialPosition); viewPager.setCurrentItem(initialPosition);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override @Override
public void onPageScrolled(int position, float positionOffset, public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {} int positionOffsetPixels) {
}
@Override @Override
public void onPageSelected(int position) { public void onPageSelected(int position) {
@ -84,7 +93,8 @@ public class ViewMediaActivity extends BaseActivity implements ViewMediaFragment
} }
@Override @Override
public void onPageScrollStateChanged(int state) {} public void onPageScrollStateChanged(int state) {
}
}); });
// Setup the toolbar. // Setup the toolbar.
@ -98,7 +108,7 @@ public class ViewMediaActivity extends BaseActivity implements ViewMediaFragment
toolbar.setNavigationOnClickListener(new View.OnClickListener() { toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
finish(); supportFinishAfterTransition();
} }
}); });
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() { toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@ -113,6 +123,13 @@ public class ViewMediaActivity extends BaseActivity implements ViewMediaFragment
return true; return true;
} }
}); });
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_LOW_PROFILE;
decorView.setSystemUiVisibility(uiOptions);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(Color.BLACK);
}
} }
@Override @Override
@ -132,12 +149,28 @@ public class ViewMediaActivity extends BaseActivity implements ViewMediaFragment
@Override @Override
public void onDismiss() { public void onDismiss() {
finish(); supportFinishAfterTransition();
}
@Override
public void onPhotoTap() {
isToolbarVisible = !isToolbarVisible;
final int visibility = isToolbarVisible ? View.VISIBLE : View.INVISIBLE;
int alpha = isToolbarVisible ? 1 : 0;
toolbar.animate().alpha(alpha)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
toolbar.setVisibility(visibility);
animation.removeListener(this);
}
})
.start();
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
@NonNull int[] grantResults) { @NonNull int[] grantResults) {
switch (requestCode) { switch (requestCode) {
case PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE: { case PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE: {
if (grantResults.length > 0 if (grantResults.length > 0
@ -158,7 +191,7 @@ public class ViewMediaActivity extends BaseActivity implements ViewMediaFragment
} }
private void doErrorDialog(@StringRes int descriptionId, @StringRes int actionId, private void doErrorDialog(@StringRes int descriptionId, @StringRes int actionId,
View.OnClickListener listener) { View.OnClickListener listener) {
if (anyView != null) { if (anyView != null) {
Snackbar bar = Snackbar.make(anyView, getString(descriptionId), Snackbar bar = Snackbar.make(anyView, getString(descriptionId),
Snackbar.LENGTH_SHORT); Snackbar.LENGTH_SHORT);
@ -170,9 +203,9 @@ public class ViewMediaActivity extends BaseActivity implements ViewMediaFragment
private void downloadImage() { private void downloadImage() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, ActivityCompat.requestPermissions(this,
new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
} else { } else {
String url = imageUrls[viewPager.getCurrentItem()]; String url = imageUrls[viewPager.getCurrentItem()];

View file

@ -15,17 +15,28 @@
package com.keylesspalace.tusky; package com.keylesspalace.tusky;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.graphics.Color;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.widget.MediaController; import android.widget.MediaController;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.VideoView; import android.widget.VideoView;
public class ViewVideoActivity extends BaseActivity { public class ViewVideoActivity extends BaseActivity {
Handler handler = new Handler(Looper.getMainLooper());
Toolbar toolbar;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -34,7 +45,7 @@ public class ViewVideoActivity extends BaseActivity {
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.video_progress); final ProgressBar progressBar = (ProgressBar) findViewById(R.id.video_progress);
VideoView videoView = (VideoView) findViewById(R.id.video_player); VideoView videoView = (VideoView) findViewById(R.id.video_player);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
ActionBar bar = getSupportActionBar(); ActionBar bar = getSupportActionBar();
if (bar != null) { if (bar != null) {
@ -55,9 +66,28 @@ public class ViewVideoActivity extends BaseActivity {
public void onPrepared(MediaPlayer mp) { public void onPrepared(MediaPlayer mp) {
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
mp.setLooping(true); mp.setLooping(true);
hideToolbarAfterDelay();
} }
}); });
videoView.start(); videoView.start();
videoView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
handler.removeCallbacksAndMessages(null);
toolbar.animate().cancel();
toolbar.setAlpha(1);
toolbar.setVisibility(View.VISIBLE);
hideToolbarAfterDelay();
}
return false;
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(Color.BLACK);
}
} }
@Override @Override
@ -70,4 +100,22 @@ public class ViewVideoActivity extends BaseActivity {
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
void hideToolbarAfterDelay() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
toolbar.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_LOW_PROFILE;
decorView.setSystemUiVisibility(uiOptions);
toolbar.setVisibility(View.INVISIBLE);
animation.removeListener(this);
}
});
}
}, 3000);
}
} }

View file

@ -275,7 +275,7 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
previews[i].setOnClickListener(new View.OnClickListener() { previews[i].setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
listener.onViewMedia(urls, urlIndex, type); listener.onViewMedia(urls, urlIndex, type, v);
} }
}); });
} }
@ -359,7 +359,7 @@ public class StatusViewHolder extends RecyclerView.ViewHolder {
mediaLabel.setOnClickListener(new View.OnClickListener() { mediaLabel.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
listener.onViewMedia(urls, 0, type); listener.onViewMedia(urls, 0, type, null);
} }
}); });
} }

View file

@ -243,8 +243,9 @@ public class NotificationsFragment extends SFragment implements
} }
@Override @Override
public void onViewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type) { public void onViewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type,
super.viewMedia(urls, urlIndex, type); View view) {
super.viewMedia(urls, urlIndex, type, view);
} }
@Override @Override

View file

@ -19,7 +19,9 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.PopupMenu; import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.Spanned; import android.text.Spanned;
@ -293,13 +295,23 @@ public abstract class SFragment extends BaseFragment implements AdapterItemRemov
popup.show(); popup.show();
} }
protected void viewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type) { protected void viewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type,
@Nullable View view) {
switch (type) { switch (type) {
case IMAGE: { case IMAGE: {
Intent intent = new Intent(getContext(), ViewMediaActivity.class); Intent intent = new Intent(getContext(), ViewMediaActivity.class);
intent.putExtra("urls", urls); intent.putExtra("urls", urls);
intent.putExtra("urlIndex", urlIndex); intent.putExtra("urlIndex", urlIndex);
startActivity(intent); if (view != null) {
String url = urls[urlIndex];
ViewCompat.setTransitionName(view, url);
ActivityOptionsCompat options =
ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(),
view, url);
startActivity(intent, options.toBundle());
} else {
startActivity(intent);
}
break; break;
} }
case GIFV: case GIFV:

View file

@ -342,8 +342,9 @@ public class TimelineFragment extends SFragment implements
} }
@Override @Override
public void onViewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type) { public void onViewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type,
super.viewMedia(urls, urlIndex, type); View view) {
super.viewMedia(urls, urlIndex, type, view);
} }
@Override @Override

View file

@ -17,6 +17,7 @@ package com.keylesspalace.tusky.fragment;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
@ -29,20 +30,30 @@ import com.github.chrisbanes.photoview.PhotoView;
import com.github.chrisbanes.photoview.PhotoViewAttacher; import com.github.chrisbanes.photoview.PhotoViewAttacher;
import com.keylesspalace.tusky.R; import com.keylesspalace.tusky.R;
import com.squareup.picasso.Callback; import com.squareup.picasso.Callback;
import com.squareup.picasso.NetworkPolicy;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
public class ViewMediaFragment extends BaseFragment { public class ViewMediaFragment extends BaseFragment {
public interface OnDismissListener { public interface PhotoActionsListener {
void onDismiss(); void onDismiss();
void onPhotoTap();
} }
private PhotoViewAttacher attacher; private PhotoViewAttacher attacher;
private OnDismissListener onDismissListener; private PhotoActionsListener photoActionsListener;
View rootView;
PhotoView photoView;
public static ViewMediaFragment newInstance(String url) { private static final String ARG_URL = "url";
private static final String ARG_START_POSTPONED_TRANSITION = "startPostponedTransition";
public static ViewMediaFragment newInstance(String url, boolean shouldStartPostponedTransition) {
Bundle arguments = new Bundle(); Bundle arguments = new Bundle();
ViewMediaFragment fragment = new ViewMediaFragment(); ViewMediaFragment fragment = new ViewMediaFragment();
arguments.putString("url", url); arguments.putString("url", url);
arguments.putBoolean(ARG_START_POSTPONED_TRANSITION, shouldStartPostponedTransition);
fragment.setArguments(arguments); fragment.setArguments(arguments);
return fragment; return fragment;
} }
@ -50,18 +61,17 @@ public class ViewMediaFragment extends BaseFragment {
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
onDismissListener = (OnDismissListener) context; photoActionsListener = (PhotoActionsListener) context;
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container, public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.fragment_view_media, container, false); rootView = inflater.inflate(R.layout.fragment_view_media, container, false);
photoView = (PhotoView) rootView.findViewById(R.id.view_media_image);
PhotoView photoView = (PhotoView) rootView.findViewById(R.id.view_media_image); final Bundle arguments = getArguments();
final String url = arguments.getString("url");
Bundle arguments = getArguments();
String url = arguments.getString("url");
attacher = new PhotoViewAttacher(photoView); attacher = new PhotoViewAttacher(photoView);
@ -69,7 +79,14 @@ public class ViewMediaFragment extends BaseFragment {
attacher.setOnOutsidePhotoTapListener(new OnOutsidePhotoTapListener() { attacher.setOnOutsidePhotoTapListener(new OnOutsidePhotoTapListener() {
@Override @Override
public void onOutsidePhotoTap(ImageView imageView) { public void onOutsidePhotoTap(ImageView imageView) {
onDismissListener.onDismiss(); photoActionsListener.onDismiss();
}
});
attacher.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
photoActionsListener.onPhotoTap();
} }
}); });
@ -80,26 +97,82 @@ public class ViewMediaFragment extends BaseFragment {
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) { float velocityY) {
if (Math.abs(velocityY) > Math.abs(velocityX)) { if (Math.abs(velocityY) > Math.abs(velocityX)) {
onDismissListener.onDismiss(); photoActionsListener.onDismiss();
return true; return true;
} }
return false; return false;
} }
}); });
Picasso.with(getContext()) ViewCompat.setTransitionName(photoView, url);
.load(url)
.into(photoView, new Callback() {
@Override
public void onSuccess() {
rootView.findViewById(R.id.view_media_progress).setVisibility(View.GONE);
attacher.update();
}
@Override // If we are the view to be shown initially...
public void onError() {} if (arguments.getBoolean(ARG_START_POSTPONED_TRANSITION)) {
}); // Try to load image from disk.
Picasso.with(getContext())
.load(url)
.noFade()
.networkPolicy(NetworkPolicy.OFFLINE)
.into(photoView, new Callback() {
@Override
public void onSuccess() {
// if we loaded image from disk, we should check that view is attached.
if (ViewCompat.isAttachedToWindow(photoView)) {
finishLoadingSuccessfully();
} else {
// if view is not attached yet, wait for an attachment and
// start transition when it's finally ready.
photoView.addOnAttachStateChangeListener(
new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
finishLoadingSuccessfully();
photoView.removeOnAttachStateChangeListener(this);
}
@Override
public void onViewDetachedFromWindow(View v) {
}
});
}
}
@Override
public void onError() {
// if there's no image in cache, load from network and start trnasition
// immediately.
getActivity().supportStartPostponedEnterTransition();
loadImageFromNetwork(url, photoView);
}
});
} else {
// if we're not initial page, don't bother.
loadImageFromNetwork(url, photoView);
}
return rootView; return rootView;
} }
private void loadImageFromNetwork(String url, ImageView photoView) {
Picasso.with(getContext())
.load(url)
.noPlaceholder()
.into(photoView, new Callback() {
@Override
public void onSuccess() {
finishLoadingSuccessfully();
}
@Override
public void onError() {
rootView.findViewById(R.id.view_media_progress).setVisibility(View.GONE);
}
});
}
private void finishLoadingSuccessfully() {
rootView.findViewById(R.id.view_media_progress).setVisibility(View.GONE);
attacher.update();
getActivity().supportStartPostponedEnterTransition();
}
} }

View file

@ -199,8 +199,9 @@ public class ViewThreadFragment extends SFragment implements
} }
@Override @Override
public void onViewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type) { public void onViewMedia(String[] urls, int urlIndex, Status.MediaAttachment.Type type,
super.viewMedia(urls, urlIndex, type); View view) {
super.viewMedia(urls, urlIndex, type, view);
} }
@Override @Override

View file

@ -25,7 +25,7 @@ public interface StatusActionListener extends LinkListener {
void onReblog(final boolean reblog, final int position); void onReblog(final boolean reblog, final int position);
void onFavourite(final boolean favourite, final int position); void onFavourite(final boolean favourite, final int position);
void onMore(View view, final int position); void onMore(View view, final int position);
void onViewMedia(String[] urls, int index, Status.MediaAttachment.Type type); void onViewMedia(String[] urls, int index, Status.MediaAttachment.Type type, View view);
void onViewThread(int position); void onViewThread(int position);
void onOpenReblog(int position); void onOpenReblog(int position);
void onExpandedChange(boolean expanded, int position); void onExpandedChange(boolean expanded, int position);

View file

@ -10,16 +10,20 @@ import java.util.Locale;
public class ImagePagerAdapter extends FragmentPagerAdapter { public class ImagePagerAdapter extends FragmentPagerAdapter {
private String[] urls; private String[] urls;
private FragmentManager fragmentManager;
private int initialPosition;
public ImagePagerAdapter(FragmentManager fragmentManager, String[] urls) { public ImagePagerAdapter(FragmentManager fragmentManager, String[] urls, int initialPosition) {
super(fragmentManager); super(fragmentManager);
this.urls = urls; this.urls = urls;
this.fragmentManager = fragmentManager;
this.initialPosition = initialPosition;
} }
@Override @Override
public Fragment getItem(int position) { public Fragment getItem(int position) {
if (position >= 0 && position < urls.length) { if (position >= 0 && position < urls.length) {
return ViewMediaFragment.newInstance(urls[position]); return ViewMediaFragment.newInstance(urls[position], position == initialPosition);
} else { } else {
return null; return null;
} }

View file

@ -10,10 +10,9 @@
tools:context=".ViewVideoActivity"> tools:context=".ViewVideoActivity">
<VideoView <VideoView
android:id="@+id/video_player" android:id="@+id/video_player"
android:layout_marginTop="?attr/actionBarSize"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_centerInParent="true" /> android:layout_gravity="center" />
<ProgressBar <ProgressBar
android:id="@+id/video_progress" android:id="@+id/video_progress"
android:layout_gravity="center" android:layout_gravity="center"