ui refresher

replace "No." with "#".
show gray background for postthumbnails, to indicate that it's still
loading (like a placeholder).
round thumbnails more in thread and album view.
animate the rounding from/to the image viewer.
when browsing threads and clicking a thimbnail, hide the image for the
transitions, so it looks like the thumbnail itself is moved, instead of
a "copy".
use theme background for post replies popup.
feature/updater-pie
Floens 6 years ago
parent 3feb3d2c30
commit 9ad896ecd7
  1. 10
      Clover/app/src/main/java/org/floens/chan/ui/cell/AlbumViewCell.java
  2. 11
      Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java
  3. 1
      Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumDownloadController.java
  4. 42
      Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumViewController.java
  5. 18
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java
  6. 25
      Clover/app/src/main/java/org/floens/chan/ui/controller/PostRepliesController.java
  7. 37
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java
  8. 4
      Clover/app/src/main/java/org/floens/chan/ui/helper/PostPopupHelper.java
  9. 8
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  10. 21
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java
  11. 108
      Clover/app/src/main/java/org/floens/chan/ui/view/ThumbnailView.java
  12. 17
      Clover/app/src/main/java/org/floens/chan/ui/view/TransitionImageView.java
  13. 10
      Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java
  14. BIN
      Clover/app/src/main/res/drawable-hdpi/dialog_full_dark.9.png
  15. BIN
      Clover/app/src/main/res/drawable-hdpi/dialog_full_light.9.png
  16. BIN
      Clover/app/src/main/res/drawable-mdpi/dialog_full_dark.9.png
  17. BIN
      Clover/app/src/main/res/drawable-mdpi/dialog_full_light.9.png
  18. BIN
      Clover/app/src/main/res/drawable-xhdpi/dialog_full_dark.9.png
  19. BIN
      Clover/app/src/main/res/drawable-xhdpi/dialog_full_light.9.png
  20. BIN
      Clover/app/src/main/res/drawable-xxhdpi/dialog_full_dark.9.png
  21. BIN
      Clover/app/src/main/res/drawable-xxhdpi/dialog_full_light.9.png
  22. 24
      Clover/app/src/main/res/drawable/album_cell_info_background.xml
  23. 22
      Clover/app/src/main/res/drawable/dialog_full_light.xml
  24. 2
      Clover/app/src/main/res/layout/cell_album_view.xml
  25. 4
      Clover/app/src/main/res/layout/layout_post_replies.xml

@ -19,6 +19,7 @@ package org.floens.chan.ui.cell;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
@ -53,6 +54,7 @@ public class AlbumViewCell extends FrameLayout {
protected void onFinishInflate() { protected void onFinishInflate() {
super.onFinishInflate(); super.onFinishInflate();
thumbnailView = findViewById(R.id.thumbnail_view); thumbnailView = findViewById(R.id.thumbnail_view);
thumbnailView.setRounding(dp(8));
text = findViewById(R.id.text); text = findViewById(R.id.text);
} }
@ -89,4 +91,12 @@ public class AlbumViewCell extends FrameLayout {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} }
} }
public void hideLabel() {
text.setAlpha(0f);
}
public void showLabel() {
text.animate().alpha(1f).setDuration(200).setInterpolator(new DecelerateInterpolator(2f));
}
} }

@ -84,6 +84,7 @@ import static android.text.TextUtils.isEmpty;
import static org.floens.chan.Chan.injector; import static org.floens.chan.Chan.injector;
import static org.floens.chan.utils.AndroidUtils.ROBOTO_CONDENSED_REGULAR; import static org.floens.chan.utils.AndroidUtils.ROBOTO_CONDENSED_REGULAR;
import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.enableHighEndAnimations;
import static org.floens.chan.utils.AndroidUtils.getString; import static org.floens.chan.utils.AndroidUtils.getString;
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground; import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
import static org.floens.chan.utils.AndroidUtils.sp; import static org.floens.chan.utils.AndroidUtils.sp;
@ -160,7 +161,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
filterMatchColor = findViewById(R.id.filter_match_color); filterMatchColor = findViewById(R.id.filter_match_color);
int textSizeSp = Integer.parseInt(ChanSettings.fontSize.get()); int textSizeSp = Integer.parseInt(ChanSettings.fontSize.get());
paddingPx = dp(textSizeSp - 6); paddingPx = dp(textSizeSp - 7);
detailsSizePx = sp(textSizeSp - 4); detailsSizePx = sp(textSizeSp - 4);
title.setTextSize(textSizeSp); title.setTextSize(textSizeSp);
title.setPadding(paddingPx, paddingPx, dp(52), 0); title.setPadding(paddingPx, paddingPx, dp(52), 0);
@ -378,7 +379,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
time = DateUtils.getRelativeTimeSpanString(post.time * 1000L, Time.get(), DateUtils.SECOND_IN_MILLIS, 0); time = DateUtils.getRelativeTimeSpanString(post.time * 1000L, Time.get(), DateUtils.SECOND_IN_MILLIS, 0);
} }
String noText = "No." + post.no; String noText = "#" + post.no;
SpannableString date = new SpannableString(noText + " " + time); SpannableString date = new SpannableString(noText + " " + time);
date.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, date.length(), 0); date.setSpan(new ForegroundColorSpanHashed(theme.detailsColor), 0, date.length(), 0);
date.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, date.length(), 0); date.setSpan(new AbsoluteSizeSpanHashed(detailsSizePx), 0, date.length(), 0);
@ -565,10 +566,14 @@ public class PostCell extends LinearLayout implements PostCellInterface {
p.addRule(RelativeLayout.BELOW, lastId); p.addRule(RelativeLayout.BELOW, lastId);
} }
p.topMargin = paddingPx;
p.leftMargin = paddingPx;
p.bottomMargin = paddingPx;
v.setPostImage(image, size, size); v.setPostImage(image, size, size);
v.setClickable(true); v.setClickable(true);
v.setOnClickListener(v2 -> callback.onThumbnailClicked(post, image, v)); v.setOnClickListener(v2 -> callback.onThumbnailClicked(post, image, v));
v.setRounding(dp(2)); v.setRounding(dp(enableHighEndAnimations() ? 8 : 2));
relativeLayoutContainer.addView(v, p); relativeLayoutContainer.addView(v, p);
thumbnailViews.add(v); thumbnailViews.add(v);

@ -225,6 +225,7 @@ public class AlbumDownloadController extends Controller implements View.OnClickL
itemView.getLayoutParams().height = recyclerView.getRealSpanWidth(); itemView.getLayoutParams().height = recyclerView.getRealSpanWidth();
checkbox = itemView.findViewById(R.id.checkbox); checkbox = itemView.findViewById(R.id.checkbox);
thumbnailView = itemView.findViewById(R.id.thumbnail_view); thumbnailView = itemView.findViewById(R.id.thumbnail_view);
thumbnailView.setRounding(dp(4));
itemView.setOnClickListener(this); itemView.setOnClickListener(this);
} }

@ -93,26 +93,44 @@ public class AlbumViewController extends Controller implements
@Override @Override
public ThumbnailView getPreviewImageTransitionView(ImageViewerController imageViewerController, PostImage postImage) { public ThumbnailView getPreviewImageTransitionView(ImageViewerController imageViewerController, PostImage postImage) {
ThumbnailView thumbnail = null; AlbumViewCell cell = findCellForImage(postImage);
for (int i = 0; i < recyclerView.getChildCount(); i++) { if (cell != null) {
View view = recyclerView.getChildAt(i); return cell.getThumbnailView();
if (view instanceof AlbumViewCell) {
AlbumViewCell cell = (AlbumViewCell) view;
if (postImage == cell.getPostImage()) {
thumbnail = cell.getThumbnailView();
break;
}
} }
return null;
} }
return thumbnail;
@Override
public void onPreviewCreate(ImageViewerController imageViewerController, PostImage postImage) {
} }
@Override @Override
public void onPreviewCreate(ImageViewerController imageViewerController) { public void onBeforePreviewDestroy(ImageViewerController imageViewerController, PostImage postImage) {
AlbumViewCell cell = findCellForImage(postImage);
if (cell != null) {
cell.hideLabel();
}
} }
@Override @Override
public void onPreviewDestroy(ImageViewerController imageViewerController) { public void onPreviewDestroy(ImageViewerController imageViewerController, PostImage postImage) {
AlbumViewCell cell = findCellForImage(postImage);
if (cell != null) {
cell.showLabel();
}
}
private AlbumViewCell findCellForImage(PostImage postImage) {
for (int i = 0; i < recyclerView.getChildCount(); i++) {
View view = recyclerView.getChildAt(i);
if (view instanceof AlbumViewCell) {
AlbumViewCell cell = (AlbumViewCell) view;
if (postImage == cell.getPostImage()) {
return cell;
}
}
}
return null;
} }
@Override @Override

@ -388,7 +388,8 @@ public class ImageViewerController extends Controller implements ImageViewerPres
startAnimation.addListener(new AnimatorListenerAdapter() { startAnimation.addListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationStart(Animator animation) { public void onAnimationStart(Animator animation) {
imageViewerCallback.onPreviewCreate(ImageViewerController.this); imageViewerCallback.onPreviewCreate(
ImageViewerController.this, postImage);
} }
@Override @Override
@ -486,16 +487,17 @@ public class ImageViewerController extends Controller implements ImageViewerPres
endAnimation.addListener(new AnimatorListenerAdapter() { endAnimation.addListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
previewOutAnimationEnded(); previewOutAnimationEnded(postImage);
} }
}); });
endAnimation.start(); endAnimation.start();
imageViewerCallback.onBeforePreviewDestroy(this, postImage);
} }
private void previewOutAnimationEnded() { private void previewOutAnimationEnded(PostImage postImage) {
setBackgroundAlpha(0f); setBackgroundAlpha(0f);
imageViewerCallback.onPreviewDestroy(this); imageViewerCallback.onPreviewDestroy(this, postImage);
navigationController.stopPresenting(false); navigationController.stopPresenting(false);
} }
@ -513,7 +515,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres
startView.getLocationInWindow(loc); startView.getLocationInWindow(loc);
Point windowLocation = new Point(loc[0], loc[1]); Point windowLocation = new Point(loc[0], loc[1]);
Point size = new Point(startView.getWidth(), startView.getHeight()); Point size = new Point(startView.getWidth(), startView.getHeight());
previewImage.setSourceImageView(windowLocation, size, bitmap); previewImage.setSourceImageView(windowLocation, size, bitmap, startView.getRounding());
return true; return true;
} }
@ -556,9 +558,11 @@ public class ImageViewerController extends Controller implements ImageViewerPres
public interface ImageViewerCallback { public interface ImageViewerCallback {
ThumbnailView getPreviewImageTransitionView(ImageViewerController imageViewerController, PostImage postImage); ThumbnailView getPreviewImageTransitionView(ImageViewerController imageViewerController, PostImage postImage);
void onPreviewCreate(ImageViewerController imageViewerController); void onPreviewCreate(ImageViewerController imageViewerController, PostImage postImage);
void onPreviewDestroy(ImageViewerController imageViewerController); void onBeforePreviewDestroy(ImageViewerController imageViewerController, PostImage postImage);
void onPreviewDestroy(ImageViewerController imageViewerController, PostImage postImage);
void scrollToImage(PostImage postImage); void scrollToImage(PostImage postImage);
} }

@ -44,6 +44,8 @@ import org.floens.chan.ui.helper.PostPopupHelper;
import org.floens.chan.ui.view.LoadView; import org.floens.chan.ui.view.LoadView;
import org.floens.chan.ui.view.ThumbnailView; import org.floens.chan.ui.view.ThumbnailView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import static org.floens.chan.ui.theme.ThemeHelper.theme; import static org.floens.chan.ui.theme.ThemeHelper.theme;
@ -125,6 +127,28 @@ public class PostRepliesController extends Controller {
} }
} }
public List<ThumbnailView> getThumbnails() {
if (listView == null) {
return Collections.emptyList();
} else {
List<ThumbnailView> thumbnails = new ArrayList<>(7);
for (int i = 0; i < listView.getChildCount(); i++) {
View view = listView.getChildAt(i);
if (view instanceof PostCellInterface) {
PostCellInterface postView = (PostCellInterface) view;
Post post = postView.getPost();
if (!post.images.isEmpty()) {
for (int j = 0; j < post.images.size(); j++) {
thumbnails.add(postView.getThumbnailView(post.images.get(j)));
}
}
}
}
return thumbnails;
}
}
public void setPostRepliesData(PostPopupHelper.RepliesData data) { public void setPostRepliesData(PostPopupHelper.RepliesData data) {
displayData(data); displayData(data);
} }
@ -180,7 +204,6 @@ public class PostRepliesController extends Controller {
} else { } else {
repliesBackText.setTextColor(0xffffffff); repliesBackText.setTextColor(0xffffffff);
repliesCloseText.setTextColor(0xffffffff); repliesCloseText.setTextColor(0xffffffff);
dataView.findViewById(R.id.container).setBackgroundResource(R.drawable.dialog_full_dark);
} }
ArrayAdapter<Post> adapter = new ArrayAdapter<Post>(context, 0) { ArrayAdapter<Post> adapter = new ArrayAdapter<Post>(context, 0) {

@ -194,18 +194,45 @@ public abstract class ThreadController extends Controller implements
return threadLayout.getThumbnail(postImage); return threadLayout.getThumbnail(postImage);
} }
public void onPreviewCreate(ImageViewerController imageViewerController) { public void onPreviewCreate(ImageViewerController imageViewerController, PostImage postImage) {
// presentingImageView.setVisibility(View.INVISIBLE); ThumbnailView thumbnailView = getPreviewImageTransitionView(imageViewerController, postImage);
if (thumbnailView != null) {
thumbnailView.hide(false);
}
}
@Override
public void onBeforePreviewDestroy(ImageViewerController imageViewerController, PostImage postImage) {
} }
@Override @Override
public void onPreviewDestroy(ImageViewerController imageViewerController) { public void onPreviewDestroy(ImageViewerController imageViewerController, PostImage postImage) {
// presentingImageView.setVisibility(View.VISIBLE); ThumbnailView thumbnail = threadLayout.getThumbnail(postImage);
// presentingImageView = null; if (thumbnail != null) {
thumbnail.show(false);
}
} }
@Override @Override
public void scrollToImage(PostImage postImage) { public void scrollToImage(PostImage postImage) {
ThumbnailView focused = threadLayout.getThumbnail(postImage);
if (focused != null) {
focused.hide(true);
} else {
AndroidUtils.waitForLayout(threadLayout, (v) -> {
ThumbnailView focused2 = threadLayout.getThumbnail(postImage);
if (focused2 != null) {
focused2.hide(true);
}
return true;
});
}
for (ThumbnailView visible : threadLayout.getAllVisibleThumbnails()) {
if (visible != focused) {
visible.show(true);
}
}
threadLayout.getPresenter().scrollToImage(postImage, true); threadLayout.getPresenter().scrollToImage(postImage, true);
} }

@ -87,6 +87,10 @@ public class PostPopupHelper {
return presentingController.getThumbnail(postImage); return presentingController.getThumbnail(postImage);
} }
public List<ThumbnailView> getThumbnails() {
return presentingController.getThumbnails();
}
public void postClicked(Post p) { public void postClicked(Post p) {
popAll(); popAll();
presenter.highlightPost(p); presenter.highlightPost(p);

@ -509,6 +509,14 @@ public class ThreadLayout extends CoordinatorLayout implements
} }
} }
public List<ThumbnailView> getAllVisibleThumbnails() {
if (postPopupHelper.isOpen()) {
return postPopupHelper.getThumbnails();
} else {
return threadListLayout.getThumbnails();
}
}
public boolean postRepliesOpen() { public boolean postRepliesOpen() {
return postPopupHelper.isOpen(); return postPopupHelper.isOpen();
} }

@ -56,6 +56,7 @@ import org.floens.chan.ui.view.FastScrollerHelper;
import org.floens.chan.ui.view.ThumbnailView; import org.floens.chan.ui.view.ThumbnailView;
import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.AndroidUtils;
import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
@ -457,6 +458,26 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
return null; return null;
} }
public List<ThumbnailView> getThumbnails() {
List<ThumbnailView> thumbnails = new ArrayList<>(7);
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
for (int i = 0; i < layoutManager.getChildCount(); i++) {
View view = layoutManager.getChildAt(i);
if (view instanceof PostCellInterface) {
PostCellInterface postView = (PostCellInterface) view;
Post post = postView.getPost();
if (!post.images.isEmpty()) {
for (PostImage image : post.images) {
thumbnails.add(postView.getThumbnailView(image));
}
}
}
}
return thumbnails;
}
public void scrollTo(int displayPosition, boolean smooth) { public void scrollTo(int displayPosition, boolean smooth) {
if (displayPosition < 0) { if (displayPosition < 0) {
int bottom = postAdapter.getItemCount() - 1; int bottom = postAdapter.getItemCount() - 1;

@ -17,6 +17,9 @@
*/ */
package org.floens.chan.ui.view; package org.floens.chan.ui.view;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -49,6 +52,8 @@ import static org.floens.chan.utils.AndroidUtils.sp;
public class ThumbnailView extends View implements ImageLoader.ImageListener { public class ThumbnailView extends View implements ImageLoader.ImageListener {
private ImageLoader.ImageContainer container; private ImageLoader.ImageContainer container;
private int fadeTime = 200; private int fadeTime = 200;
private ValueAnimator fadeAnimation;
private boolean hidden = false;
private boolean circular = false; private boolean circular = false;
private int rounding = 0; private int rounding = 0;
@ -62,7 +67,8 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener {
private Matrix matrix = new Matrix(); private Matrix matrix = new Matrix();
BitmapShader bitmapShader; BitmapShader bitmapShader;
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private boolean foregroundCalculate = false; private boolean foregroundCalculate = false;
private Drawable foreground; private Drawable foreground;
@ -90,6 +96,14 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener {
private void init() { private void init() {
textPaint.setColor(0xff000000); textPaint.setColor(0xff000000);
textPaint.setTextSize(sp(14)); textPaint.setTextSize(sp(14));
backgroundPaint.setColor(0x22000000);
endAnimations();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
endAnimations();
} }
public void setUrl(String url, int width, int height) { public void setUrl(String url, int width, int height) {
@ -117,6 +131,10 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener {
this.rounding = rounding; this.rounding = rounding;
} }
public int getRounding() {
return rounding;
}
@SuppressWarnings({"deprecation", "ConstantConditions"}) @SuppressWarnings({"deprecation", "ConstantConditions"})
@Override @Override
public void setClickable(boolean clickable) { public void setClickable(boolean clickable) {
@ -153,6 +171,30 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener {
return bitmap; return bitmap;
} }
public void hide(boolean animateIfNeeded) {
hidden = true;
if (getAlpha() == 0f) return;
if (animateIfNeeded && fadeAnimation == null && bitmap != null && !calculate) {
animate().alpha(0f).setDuration(150);
} else {
setAlpha(0f);
}
endAnimations();
}
public void show(boolean animateIfNeeded) {
hidden = false;
if (getAlpha() == 1f) return;
if (animateIfNeeded && fadeAnimation == null && bitmap != null && !calculate) {
animate().alpha(1f).setDuration(150);
} else {
setAlpha(1f);
}
endAnimations();
}
@Override @Override
public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) { public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) { if (response.getBitmap() != null) {
@ -180,7 +222,7 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener {
if (error) { if (error) {
textPaint.setAlpha(alpha); textPaint.setAlpha(alpha);
} else { } else {
paint.setAlpha(alpha); bitmapPaint.setAlpha(alpha);
} }
invalidate(); invalidate();
@ -196,14 +238,11 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener {
@Override @Override
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
if (getAlpha() == 0f) {
return;
}
int width = getWidth() - getPaddingLeft() - getPaddingRight(); int width = getWidth() - getPaddingLeft() - getPaddingRight();
int height = getHeight() - getPaddingTop() - getPaddingBottom(); int height = getHeight() - getPaddingTop() - getPaddingBottom();
if (error) { if (error) {
// Render a simple text if there was an error.
canvas.save(); canvas.save();
textPaint.getTextBounds(errorText, 0, errorText.length(), tmpTextRect); textPaint.getTextBounds(errorText, 0, errorText.length(), tmpTextRect);
@ -213,10 +252,23 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener {
canvas.restore(); canvas.restore();
} else { } else {
outputRect.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
// Gray background if thumbnail is not yet loaded.
if (bitmap == null || fadeAnimation != null) {
if (circular) {
canvas.drawRoundRect(outputRect, width / 2.0f, height / 2.0f, backgroundPaint);
} else {
canvas.drawRoundRect(outputRect, rounding, rounding, backgroundPaint);
}
}
if (bitmap == null) { if (bitmap == null) {
return; return;
} }
// If needed, calculate positions.
if (calculate) { if (calculate) {
calculate = false; calculate = false;
bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
@ -231,21 +283,20 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener {
drawRect.set(-offsetX, -offsetY, scaledX - offsetX, scaledY - offsetY); drawRect.set(-offsetX, -offsetY, scaledX - offsetX, scaledY - offsetY);
drawRect.offset(getPaddingLeft(), getPaddingTop()); drawRect.offset(getPaddingLeft(), getPaddingTop());
outputRect.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
matrix.setRectToRect(bitmapRect, drawRect, Matrix.ScaleToFit.FILL); matrix.setRectToRect(bitmapRect, drawRect, Matrix.ScaleToFit.FILL);
bitmapShader.setLocalMatrix(matrix); bitmapShader.setLocalMatrix(matrix);
paint.setShader(bitmapShader); bitmapPaint.setShader(bitmapShader);
} }
canvas.save(); canvas.save();
canvas.clipRect(outputRect); canvas.clipRect(outputRect);
// Draw rounded bitmap.
if (circular) { if (circular) {
canvas.drawRoundRect(outputRect, width / 2, height / 2, paint); canvas.drawRoundRect(outputRect, width / 2.0f, height / 2.0f, bitmapPaint);
} else { } else {
canvas.drawRoundRect(outputRect, rounding, rounding, paint); canvas.drawRoundRect(outputRect, rounding, rounding, bitmapPaint);
} }
canvas.restore(); canvas.restore();
@ -298,9 +349,8 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener {
private void onImageSet(boolean isImmediate) { private void onImageSet(boolean isImmediate) {
clearAnimation(); clearAnimation();
if (fadeTime > 0 && !isImmediate) { if (fadeTime > 0 && !isImmediate && !hidden) {
setAlpha(0f); runFadeInAnimation();
animate().alpha(1f).setDuration(fadeTime);
} else { } else {
setAlpha(1f); setAlpha(1f);
} }
@ -308,13 +358,41 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener {
private void setImageBitmap(Bitmap bitmap) { private void setImageBitmap(Bitmap bitmap) {
bitmapShader = null; bitmapShader = null;
paint.setShader(null); bitmapPaint.setShader(null);
this.bitmap = bitmap; this.bitmap = bitmap;
if (bitmap != null) { if (bitmap != null) {
calculate = true; calculate = true;
bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
} }
endAnimations();
invalidate(); invalidate();
} }
private void runFadeInAnimation() {
fadeAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
fadeAnimation.setDuration(fadeTime);
fadeAnimation.addUpdateListener(v -> {
bitmapPaint.setAlpha((int) (((float) v.getAnimatedValue()) * 255));
invalidate();
});
fadeAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
fadeAnimation = null;
if (bitmapPaint.getAlpha() != ((int) getAlpha() * 255)) {
bitmapPaint.setAlpha((int) (getAlpha() * 255));
invalidate();
}
}
});
fadeAnimation.start();
}
private void endAnimations() {
if (fadeAnimation != null) {
fadeAnimation.end();
fadeAnimation = null;
}
}
} }

@ -22,12 +22,15 @@ import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.PointF; import android.graphics.PointF;
import android.graphics.RectF; import android.graphics.RectF;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import static org.floens.chan.utils.AndroidUtils.enableHighEndAnimations;
public class TransitionImageView extends View { public class TransitionImageView extends View {
private static final String TAG = "TransitionImageView"; private static final String TAG = "TransitionImageView";
@ -44,6 +47,8 @@ public class TransitionImageView extends View {
private float stateBitmapScaleDiff; private float stateBitmapScaleDiff;
private PointF stateBitmapSize; private PointF stateBitmapSize;
private PointF statePos; private PointF statePos;
private float fromRounding = 0.0f;
private Path roundingPath = new Path();
public TransitionImageView(Context context) { public TransitionImageView(Context context) {
super(context); super(context);
@ -84,8 +89,10 @@ public class TransitionImageView extends View {
matrix.setRectToRect(bitmapRect, destRect, Matrix.ScaleToFit.FILL); matrix.setRectToRect(bitmapRect, destRect, Matrix.ScaleToFit.FILL);
} }
public void setSourceImageView(Point windowLocation, Point viewSize, Bitmap bitmap) { public void setSourceImageView(Point windowLocation, Point viewSize, Bitmap bitmap,
float rounding) {
this.bitmap = bitmap; this.bitmap = bitmap;
this.fromRounding = rounding;
bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
if (stateBitmapSize != null) { if (stateBitmapSize != null) {
@ -174,9 +181,17 @@ public class TransitionImageView extends View {
if (bitmap != null) { if (bitmap != null) {
canvas.save(); canvas.save();
if (progress < 1f) { if (progress < 1f) {
if (!enableHighEndAnimations()) {
canvas.clipRect(destClip); canvas.clipRect(destClip);
} else {
float rounding = lerp(fromRounding, 0.0f, progress);
roundingPath.reset();
roundingPath.addRoundRect(destClip, rounding, rounding, Path.Direction.CW);
canvas.clipPath(roundingPath);
}
} }
canvas.drawBitmap(bitmap, matrix, paint); canvas.drawBitmap(bitmap, matrix, paint);
canvas.restore(); canvas.restore();
} }
} }

@ -20,6 +20,7 @@ package org.floens.chan.utils;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager;
import android.app.Application; import android.app.Application;
import android.app.Dialog; import android.app.Dialog;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
@ -43,6 +44,7 @@ import android.os.Looper;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.customtabs.CustomTabsIntent; import android.support.customtabs.CustomTabsIntent;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityManagerCompat;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
@ -71,6 +73,7 @@ public class AndroidUtils {
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private static Application application; private static Application application;
private static ConnectivityManager connectivityManager; private static ConnectivityManager connectivityManager;
private static ActivityManager activityManager;
private static final Handler mainHandler = new Handler(Looper.getMainLooper()); private static final Handler mainHandler = new Handler(Looper.getMainLooper());
@ -84,6 +87,8 @@ public class AndroidUtils {
connectivityManager = (ConnectivityManager) connectivityManager = (ConnectivityManager)
application.getSystemService(Context.CONNECTIVITY_SERVICE); application.getSystemService(Context.CONNECTIVITY_SERVICE);
activityManager = (ActivityManager)
application.getSystemService(Context.ACTIVITY_SERVICE);
} }
} }
@ -452,4 +457,9 @@ public class AndroidUtils {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(type); NetworkInfo networkInfo = connectivityManager.getNetworkInfo(type);
return networkInfo != null && networkInfo.isConnected(); return networkInfo != null && networkInfo.isConnected();
} }
public static boolean enableHighEndAnimations() {
boolean lowRamDevice = ActivityManagerCompat.isLowRamDevice(activityManager);
return !lowRamDevice && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#8a000000" />
<corners
android:bottomLeftRadius="8dp"
android:bottomRightRadius="8dp" />
</shape>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="?attr/backcolor" />
<corners android:radius="8dp" />
</shape>

@ -31,7 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="32dp" android:layout_height="32dp"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:background="#8a000000" android:background="@drawable/album_cell_info_background"
android:gravity="center_vertical" android:gravity="center_vertical"
android:maxLines="2" android:maxLines="2"
android:paddingLeft="8dp" android:paddingLeft="8dp"

@ -26,10 +26,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_margin="10dp"
android:background="@drawable/dialog_full_light" android:background="@drawable/dialog_full_light"
android:elevation="8dp"
android:minWidth="320dp" android:minWidth="320dp"
android:orientation="vertical" android:orientation="vertical"
tools:ignore="UselessParent"> tools:ignore="UnusedAttribute,UselessParent">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

Loading…
Cancel
Save