Add album view

Closes #145z
multisite
Floens 9 years ago
parent 8e58b92592
commit 8679d0f222
  1. 36
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  2. 2
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  3. 91
      Clover/app/src/main/java/org/floens/chan/ui/cell/AlbumViewCell.java
  4. 175
      Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumViewController.java
  5. 3
      Clover/app/src/main/java/org/floens/chan/ui/controller/DrawerController.java
  6. 75
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java
  7. 12
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java
  8. 11
      Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java
  9. 16
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java
  10. 29
      Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java
  11. 12
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  12. 48
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java
  13. 4
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java
  14. BIN
      Clover/app/src/main/res/drawable-hdpi/ic_subdirectory_arrow_left_white_24dp.png
  15. BIN
      Clover/app/src/main/res/drawable-mdpi/ic_subdirectory_arrow_left_white_24dp.png
  16. BIN
      Clover/app/src/main/res/drawable-xhdpi/ic_subdirectory_arrow_left_white_24dp.png
  17. BIN
      Clover/app/src/main/res/drawable-xxhdpi/ic_subdirectory_arrow_left_white_24dp.png
  18. BIN
      Clover/app/src/main/res/drawable-xxxhdpi/ic_subdirectory_arrow_left_white_24dp.png
  19. 43
      Clover/app/src/main/res/layout/cell_album_view.xml
  20. 28
      Clover/app/src/main/res/layout/controller_album_view.xml
  21. 4
      Clover/app/src/main/res/values/strings.xml

@ -216,6 +216,26 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
} }
} }
public void showAlbum() {
List<Post> posts = threadPresenterCallback.getDisplayingPosts();
int[] pos = threadPresenterCallback.getCurrentPosition();
int displayPosition = pos[0];
List<PostImage> images = new ArrayList<>();
int index = 0;
for (int i = 0; i < posts.size(); i++) {
Post item = posts.get(i);
if (item.hasImage) {
images.add(item.image);
}
if (i == displayPosition) {
index = images.size();
}
}
threadPresenterCallback.showAlbum(images, index);
}
@Override @Override
public Loadable getLoadable() { public Loadable getLoadable() {
return loadable; return loadable;
@ -327,6 +347,18 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
threadPresenterCallback.selectPost(post); threadPresenterCallback.selectPost(post);
} }
public void selectPostImage(PostImage postImage) {
List<Post> posts = threadPresenterCallback.getDisplayingPosts();
for (int i = 0; i < posts.size(); i++) {
Post post = posts.get(i);
if (post.image == postImage) {
scrollToPost(post, false);
highlightPost(post);
break;
}
}
}
/* /*
* PostView callbacks * PostView callbacks
*/ */
@ -662,8 +694,12 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
List<Post> getDisplayingPosts(); List<Post> getDisplayingPosts();
int[] getCurrentPosition();
void showImages(List<PostImage> images, int index, Loadable loadable, ThumbnailView thumbnail); void showImages(List<PostImage> images, int index, Loadable loadable, ThumbnailView thumbnail);
void showAlbum(List<PostImage> images, int index);
void scrollTo(int displayPosition, boolean smooth); void scrollTo(int displayPosition, boolean smooth);
void highlightPost(Post post); void highlightPost(Post post);

@ -251,7 +251,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
thread = Loadable.emptyLoadable(); thread = Loadable.emptyLoadable();
} }
outState.putParcelable(STATE_KEY, new ChanState(board, thread)); outState.putParcelable(STATE_KEY, new ChanState(board.copy(), thread.copy()));
} }
} }

@ -0,0 +1,91 @@
/*
* 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/>.
*/
package org.floens.chan.ui.cell;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.ui.view.ThumbnailView;
import org.floens.chan.utils.AndroidUtils;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getDimen;
import static org.floens.chan.utils.AndroidUtils.getString;
public class AlbumViewCell extends FrameLayout {
private PostImage postImage;
private ThumbnailView thumbnailView;
private TextView text;
public AlbumViewCell(Context context) {
this(context, null);
}
public AlbumViewCell(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AlbumViewCell(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
thumbnailView = (ThumbnailView) findViewById(R.id.thumbnail_view);
text = (TextView) findViewById(R.id.text);
}
public void setPostImage(PostImage postImage) {
this.postImage = postImage;
// Keep this the same as the normal thumbnails to improve performance
int thumbnailSize = getDimen(getContext(), R.dimen.cell_post_thumbnail_size);
thumbnailView.setUrl(postImage.thumbnailUrl, thumbnailSize, thumbnailSize);
String filename = postImage.spoiler ? getString(R.string.image_spoiler_filename) : postImage.filename + "." + postImage.extension;
String details = postImage.extension.toUpperCase() + " " + postImage.imageWidth + "x" + postImage.imageHeight +
" " + AndroidUtils.getReadableFileSize(postImage.size, false);
text.setText(details);
}
public PostImage getPostImage() {
return postImage;
}
public ThumbnailView getThumbnailView() {
return thumbnailView;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST)) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width + dp(32);
super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}

@ -0,0 +1,175 @@
/*
* 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/>.
*/
package org.floens.chan.ui.controller;
import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.ui.cell.AlbumViewCell;
import org.floens.chan.ui.view.GridRecyclerView;
import org.floens.chan.ui.view.ThumbnailView;
import java.util.List;
import static org.floens.chan.utils.AndroidUtils.dp;
public class AlbumViewController extends Controller implements ImageViewerController.ImageViewerCallback, ImageViewerController.GoPostCallback {
private GridRecyclerView recyclerView;
private GridLayoutManager gridLayoutManager;
private List<PostImage> postImages;
private int targetIndex = -1;
private AlbumAdapter albumAdapter;
private Loadable loadable;
public AlbumViewController(Context context) {
super(context);
}
@Override
public void onCreate() {
super.onCreate();
view = inflateRes(R.layout.controller_album_view);
recyclerView = (GridRecyclerView) view.findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
gridLayoutManager = new GridLayoutManager(context, 3);
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.setHasFixedSize(true);
recyclerView.setSpanWidth(dp(120));
recyclerView.setItemAnimator(null);
albumAdapter = new AlbumAdapter();
recyclerView.setAdapter(albumAdapter);
recyclerView.scrollToPosition(targetIndex);
}
public void setImages(Loadable loadable, List<PostImage> postImages, int index, String title) {
this.loadable = loadable;
this.postImages = postImages;
navigationItem.title = title;
targetIndex = index;
}
@Override
public ThumbnailView getPreviewImageTransitionView(ImageViewerController imageViewerController, PostImage postImage) {
ThumbnailView thumbnail = null;
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()) {
thumbnail = cell.getThumbnailView();
break;
}
}
}
return thumbnail;
}
@Override
public void onPreviewCreate(ImageViewerController imageViewerController) {
}
@Override
public void onPreviewDestroy(ImageViewerController imageViewerController) {
}
@Override
public void scrollToImage(PostImage postImage) {
int index = postImages.indexOf(postImage);
recyclerView.smoothScrollToPosition(index);
}
@Override
public ImageViewerController.ImageViewerCallback goToPost(PostImage postImage) {
if (previousSiblingController instanceof ThreadController) {
ThreadController sibling = (ThreadController) previousSiblingController;
sibling.selectPostImage(postImage);
navigationController.popController(false);
return sibling;
} else {
return null;
}
}
private void openImage(AlbumItemCellHolder albumItemCellHolder, PostImage postImage) {
// Just ignore the showImages request when the image is not loaded
if (albumItemCellHolder.thumbnailView.getBitmap() != null) {
final ImageViewerNavigationController imageViewerNavigationController = new ImageViewerNavigationController(context);
int index = postImages.indexOf(postImage);
presentController(imageViewerNavigationController, false);
imageViewerNavigationController.showImages(postImages, index, loadable, this, this);
}
}
private class AlbumAdapter extends RecyclerView.Adapter<AlbumItemCellHolder> {
public AlbumAdapter() {
setHasStableIds(true);
}
@Override
public AlbumItemCellHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new AlbumItemCellHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.cell_album_view, parent, false));
}
@Override
public void onBindViewHolder(AlbumItemCellHolder holder, int position) {
PostImage postImage = postImages.get(position);
holder.cell.setPostImage(postImage);
}
@Override
public int getItemCount() {
return postImages.size();
}
@Override
public long getItemId(int position) {
return position;
}
}
private class AlbumItemCellHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private AlbumViewCell cell;
private ThumbnailView thumbnailView;
public AlbumItemCellHolder(View itemView) {
super(itemView);
cell = (AlbumViewCell) itemView;
thumbnailView = (ThumbnailView) itemView.findViewById(R.id.thumbnail_view);
thumbnailView.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int adapterPosition = getAdapterPosition();
PostImage postImage = postImages.get(adapterPosition);
openImage(this, postImage);
}
}
}

@ -234,6 +234,9 @@ public class DrawerController extends Controller implements PinAdapter.Callback,
public void setDrawerEnabled(boolean enabled) { public void setDrawerEnabled(boolean enabled) {
drawerLayout.setDrawerLockMode(enabled ? DrawerLayout.LOCK_MODE_UNLOCKED : DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT); drawerLayout.setDrawerLockMode(enabled ? DrawerLayout.LOCK_MODE_UNLOCKED : DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT);
if (!enabled) {
drawerLayout.closeDrawer(drawer);
}
} }
private void updateBadge() { private void updateBadge() {

@ -70,7 +70,6 @@ import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger; import org.floens.chan.utils.Logger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.dp;
@ -81,17 +80,19 @@ public class ImageViewerController extends Controller implements ImageViewerPres
private static final int TRANSITION_DURATION = 300; private static final int TRANSITION_DURATION = 300;
private static final float TRANSITION_FINAL_ALPHA = 0.85f; private static final float TRANSITION_FINAL_ALPHA = 0.85f;
private static final int SAVE_ID = 101; private static final int GO_POST_ID = 1;
private static final int OPEN_BROWSER_ID = 102; private static final int SAVE_ID = 2;
private static final int SHARE_ID = 103; private static final int OPEN_BROWSER_ID = 103;
private static final int SEARCH_ID = 104; private static final int SHARE_ID = 104;
private static final int SAVE_ALBUM = 105; private static final int SEARCH_ID = 105;
private static final int SAVE_ALBUM = 106;
private int statusBarColorPrevious; private int statusBarColorPrevious;
private AnimatorSet startAnimation; private AnimatorSet startAnimation;
private AnimatorSet endAnimation; private AnimatorSet endAnimation;
private PreviewCallback previewCallback; private ImageViewerCallback imageViewerCallback;
private GoPostCallback goPostCallback;
private ImageViewerPresenter presenter; private ImageViewerPresenter presenter;
private final Toolbar toolbar; private final Toolbar toolbar;
@ -116,13 +117,17 @@ public class ImageViewerController extends Controller implements ImageViewerPres
navigationItem.subtitle = "0"; navigationItem.subtitle = "0";
navigationItem.menu = new ToolbarMenu(context); navigationItem.menu = new ToolbarMenu(context);
if (goPostCallback != null) {
navigationItem.menu.addItem(new ToolbarMenuItem(context, this, GO_POST_ID, R.drawable.ic_subdirectory_arrow_left_white_24dp));
}
navigationItem.menu.addItem(new ToolbarMenuItem(context, this, SAVE_ID, R.drawable.ic_file_download_white_24dp)); navigationItem.menu.addItem(new ToolbarMenuItem(context, this, SAVE_ID, R.drawable.ic_file_download_white_24dp));
overflowMenuItem = navigationItem.createOverflow(context, this, Arrays.asList(
new FloatingMenuItem(OPEN_BROWSER_ID, R.string.action_open_browser), List<FloatingMenuItem> items = new ArrayList<>();
new FloatingMenuItem(SHARE_ID, R.string.action_share), items.add(new FloatingMenuItem(OPEN_BROWSER_ID, R.string.action_open_browser));
new FloatingMenuItem(SEARCH_ID, R.string.action_search_image), items.add(new FloatingMenuItem(SHARE_ID, R.string.action_share));
new FloatingMenuItem(SAVE_ALBUM, R.string.action_download_album) items.add(new FloatingMenuItem(SEARCH_ID, R.string.action_search_image));
)); items.add(new FloatingMenuItem(SAVE_ALBUM, R.string.action_download_album));
overflowMenuItem = navigationItem.createOverflow(context, this, items);
view = inflateRes(R.layout.controller_image_viewer); view = inflateRes(R.layout.controller_image_viewer);
previewImage = (TransitionImageView) view.findViewById(R.id.preview_image); previewImage = (TransitionImageView) view.findViewById(R.id.preview_image);
@ -153,8 +158,26 @@ public class ImageViewerController extends Controller implements ImageViewerPres
@Override @Override
public void onMenuItemClicked(ToolbarMenuItem item) { public void onMenuItemClicked(ToolbarMenuItem item) {
if ((Integer) item.getId() == SAVE_ID) { switch ((Integer) item.getId()) {
saveShare(false, presenter.getCurrentPostImage()); case GO_POST_ID:
PostImage postImage = presenter.getCurrentPostImage();
ImageViewerCallback imageViewerCallback = goPostCallback.goToPost(postImage);
if (imageViewerCallback != null) {
// hax: we need to wait for the recyclerview to do a layout before we know
// where the new thumbnails are to get the bounds from to animate to
this.imageViewerCallback = imageViewerCallback;
AndroidUtils.waitForLayout(view, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
presenter.onExit();
return false;
}
});
}
break;
case SAVE_ID:
saveShare(false, presenter.getCurrentPostImage());
break;
} }
} }
@ -216,8 +239,12 @@ public class ImageViewerController extends Controller implements ImageViewerPres
return true; return true;
} }
public void setPreviewCallback(PreviewCallback previewCallback) { public void setImageViewerCallback(ImageViewerCallback imageViewerCallback) {
this.previewCallback = previewCallback; this.imageViewerCallback = imageViewerCallback;
}
public void setGoPostCallback(GoPostCallback goPostCallback) {
this.goPostCallback = goPostCallback;
} }
public ImageViewerPresenter getPresenter() { public ImageViewerPresenter getPresenter() {
@ -258,7 +285,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres
} }
public void scrollToImage(PostImage postImage) { public void scrollToImage(PostImage postImage) {
previewCallback.scrollToImage(postImage); imageViewerCallback.scrollToImage(postImage);
} }
public void showProgress(boolean show) { public void showProgress(boolean show) {
@ -324,7 +351,7 @@ 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) {
previewCallback.onPreviewCreate(ImageViewerController.this); imageViewerCallback.onPreviewCreate(ImageViewerController.this);
} }
@Override @Override
@ -431,7 +458,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres
private void previewOutAnimationEnded() { private void previewOutAnimationEnded() {
setBackgroundAlpha(0f); setBackgroundAlpha(0f);
previewCallback.onPreviewDestroy(this); imageViewerCallback.onPreviewDestroy(this);
navigationController.stopPresenting(false); navigationController.stopPresenting(false);
} }
@ -482,14 +509,14 @@ public class ImageViewerController extends Controller implements ImageViewerPres
} }
private ThumbnailView getTransitionImageView(PostImage postImage) { private ThumbnailView getTransitionImageView(PostImage postImage) {
return previewCallback.getPreviewImageTransitionView(this, postImage); return imageViewerCallback.getPreviewImageTransitionView(this, postImage);
} }
private Window getWindow() { private Window getWindow() {
return ((Activity) context).getWindow(); return ((Activity) context).getWindow();
} }
public interface PreviewCallback { public interface ImageViewerCallback {
ThumbnailView getPreviewImageTransitionView(ImageViewerController imageViewerController, PostImage postImage); ThumbnailView getPreviewImageTransitionView(ImageViewerController imageViewerController, PostImage postImage);
void onPreviewCreate(ImageViewerController imageViewerController); void onPreviewCreate(ImageViewerController imageViewerController);
@ -498,4 +525,8 @@ public class ImageViewerController extends Controller implements ImageViewerPres
void scrollToImage(PostImage postImage); void scrollToImage(PostImage postImage);
} }
public interface GoPostCallback {
ImageViewerCallback goToPost(PostImage postImage);
}
} }

@ -46,10 +46,18 @@ public class ImageViewerNavigationController extends ToolbarNavigationController
toolbar.setCallback(this); toolbar.setCallback(this);
} }
public void showImages(final List<PostImage> images, final int index, final Loadable loadable, final ImageViewerController.PreviewCallback previewCallback) { public void showImages(final List<PostImage> images, final int index, final Loadable loadable,
ImageViewerController.ImageViewerCallback imageViewerCallback) {
showImages(images, index, loadable, imageViewerCallback, null);
}
public void showImages(final List<PostImage> images, final int index, final Loadable loadable,
ImageViewerController.ImageViewerCallback imageViewerCallback,
ImageViewerController.GoPostCallback goPostCallback) {
imageViewerController = new ImageViewerController(context, toolbar); imageViewerController = new ImageViewerController(context, toolbar);
imageViewerController.setGoPostCallback(goPostCallback);
pushController(imageViewerController, false); pushController(imageViewerController, false);
imageViewerController.setPreviewCallback(previewCallback); imageViewerController.setImageViewerCallback(imageViewerCallback);
imageViewerController.getPresenter().showImages(images, index, loadable); imageViewerController.getPresenter().showImages(images, index, loadable);
} }
} }

@ -45,6 +45,17 @@ public class StyledToolbarNavigationController extends ToolbarNavigationControll
toolbar.setCallback(this); toolbar.setCallback(this);
} }
@Override
public boolean popController(ControllerTransition controllerTransition) {
return !toolbar.isTransitioning() && super.popController(controllerTransition);
}
@Override
public boolean pushController(Controller to, ControllerTransition controllerTransition) {
return !toolbar.isTransitioning() && super.pushController(to, controllerTransition);
}
@Override @Override
public void transition(Controller from, Controller to, boolean pushing, ControllerTransition controllerTransition) { public void transition(Controller from, Controller to, boolean pushing, ControllerTransition controllerTransition) {
super.transition(from, to, pushing, controllerTransition); super.transition(from, to, pushing, controllerTransition);

@ -47,7 +47,7 @@ import de.greenrobot.event.EventBus;
import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.dp;
public abstract class ThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback, ImageViewerController.PreviewCallback, SwipeRefreshLayout.OnRefreshListener, ToolbarNavigationController.ToolbarSearchCallback, NfcAdapter.CreateNdefMessageCallback { public abstract class ThreadController extends Controller implements ThreadLayout.ThreadLayoutCallback, ImageViewerController.ImageViewerCallback, SwipeRefreshLayout.OnRefreshListener, ToolbarNavigationController.ToolbarSearchCallback, NfcAdapter.CreateNdefMessageCallback {
private static final String TAG = "ThreadController"; private static final String TAG = "ThreadController";
protected ThreadLayout threadLayout; protected ThreadLayout threadLayout;
@ -162,6 +162,10 @@ public abstract class ThreadController extends Controller implements ThreadLayou
presentController(controller); presentController(controller);
} }
public void selectPostImage(PostImage postImage) {
threadLayout.getPresenter().selectPostImage(postImage);
}
@Override @Override
public void showImages(List<PostImage> images, int index, Loadable loadable, final ThumbnailView thumbnail) { public void showImages(List<PostImage> images, int index, Loadable loadable, final ThumbnailView thumbnail) {
// Just ignore the showImages request when the image is not loaded // Just ignore the showImages request when the image is not loaded
@ -187,10 +191,20 @@ public abstract class ThreadController extends Controller implements ThreadLayou
// presentingImageView = null; // presentingImageView = null;
} }
@Override
public void scrollToImage(PostImage postImage) { public void scrollToImage(PostImage postImage) {
threadLayout.getPresenter().scrollToImage(postImage, true); threadLayout.getPresenter().scrollToImage(postImage, true);
} }
@Override
public void showAlbum(List<PostImage> images, int index) {
if (threadLayout.getPresenter().getChanThread() != null) {
AlbumViewController albumViewController = new AlbumViewController(context);
albumViewController.setImages(getLoadable(), images, index, navigationItem.title);
navigationController.pushController(albumViewController);
}
}
@Override @Override
public void onShowPosts() { public void onShowPosts() {
} }

@ -47,14 +47,15 @@ import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrColor; import static org.floens.chan.utils.AndroidUtils.getAttrColor;
public class ViewThreadController extends ThreadController implements ThreadLayout.ThreadLayoutCallback, ToolbarMenuItem.ToolbarMenuItemCallback { public class ViewThreadController extends ThreadController implements ThreadLayout.ThreadLayoutCallback, ToolbarMenuItem.ToolbarMenuItemCallback {
private static final int ALBUM_ID = 1;
private static final int PIN_ID = 2; private static final int PIN_ID = 2;
private static final int REPLY_ID = 101; private static final int REPLY_ID = 101;
private static final int REFRESH_ID = 102; private static final int REFRESH_ID = 103;
private static final int SEARCH_ID = 103; private static final int SEARCH_ID = 104;
private static final int SHARE_ID = 104; private static final int SHARE_ID = 105;
private static final int UP_ID = 105; private static final int UP_ID = 106;
private static final int DOWN_ID = 106; private static final int DOWN_ID = 107;
private static final int OPEN_BROWSER_ID = 107; private static final int OPEN_BROWSER_ID = 108;
private ToolbarMenuItem pinItem; private ToolbarMenuItem pinItem;
private ToolbarMenuItem overflowItem; private ToolbarMenuItem overflowItem;
@ -79,17 +80,18 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
navigationItem.hasDrawer = true; navigationItem.hasDrawer = true;
navigationItem.menu = new ToolbarMenu(context); navigationItem.menu = new ToolbarMenu(context);
navigationItem.menu.addItem(new ToolbarMenuItem(context, this, ALBUM_ID, R.drawable.ic_image_white_24dp));
pinItem = navigationItem.menu.addItem(new ToolbarMenuItem(context, this, PIN_ID, R.drawable.ic_bookmark_outline_white_24dp)); pinItem = navigationItem.menu.addItem(new ToolbarMenuItem(context, this, PIN_ID, R.drawable.ic_bookmark_outline_white_24dp));
List<FloatingMenuItem> items = new ArrayList<>(); List<FloatingMenuItem> items = new ArrayList<>();
if (!ChanSettings.enableReplyFab.get()) { if (!ChanSettings.enableReplyFab.get()) {
items.add(new FloatingMenuItem(REPLY_ID, context.getString(R.string.action_reply))); items.add(new FloatingMenuItem(REPLY_ID, context.getString(R.string.action_reply)));
} }
items.add(new FloatingMenuItem(REFRESH_ID, context.getString(R.string.action_reload))); items.add(new FloatingMenuItem(REFRESH_ID, R.string.action_reload));
items.add(new FloatingMenuItem(SEARCH_ID, context.getString(R.string.action_search))); items.add(new FloatingMenuItem(SEARCH_ID, R.string.action_search));
items.add(new FloatingMenuItem(OPEN_BROWSER_ID, context.getString(R.string.action_open_browser))); items.add(new FloatingMenuItem(OPEN_BROWSER_ID, R.string.action_open_browser));
items.add(new FloatingMenuItem(SHARE_ID, context.getString(R.string.action_share))); items.add(new FloatingMenuItem(SHARE_ID, R.string.action_share));
items.add(new FloatingMenuItem(UP_ID, context.getString(R.string.action_up))); items.add(new FloatingMenuItem(UP_ID, R.string.action_up));
items.add(new FloatingMenuItem(DOWN_ID, context.getString(R.string.action_down))); items.add(new FloatingMenuItem(DOWN_ID, R.string.action_down));
overflowItem = navigationItem.createOverflow(context, this, items); overflowItem = navigationItem.createOverflow(context, this, items);
loadThread(loadable); loadThread(loadable);
@ -170,6 +172,9 @@ public class ViewThreadController extends ThreadController implements ThreadLayo
@Override @Override
public void onMenuItemClicked(ToolbarMenuItem item) { public void onMenuItemClicked(ToolbarMenuItem item) {
switch ((Integer) item.getId()) { switch ((Integer) item.getId()) {
case ALBUM_ID:
threadLayout.getPresenter().showAlbum();
break;
case PIN_ID: case PIN_ID:
setPinIconState(threadLayout.getPresenter().pin()); setPinIconState(threadLayout.getPresenter().pin());
updateDrawerHighlighting(loadable); updateDrawerHighlighting(loadable);

@ -320,11 +320,21 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
} }
} }
@Override
public int[] getCurrentPosition() {
return threadListLayout.getIndexAndTop();
}
@Override @Override
public void showImages(List<PostImage> images, int index, Loadable loadable, ThumbnailView thumbnail) { public void showImages(List<PostImage> images, int index, Loadable loadable, ThumbnailView thumbnail) {
callback.showImages(images, index, loadable, thumbnail); callback.showImages(images, index, loadable, thumbnail);
} }
@Override
public void showAlbum(List<PostImage> images, int index) {
callback.showAlbum(images, index);
}
@Override @Override
public void scrollTo(int displayPosition, boolean smooth) { public void scrollTo(int displayPosition, boolean smooth) {
if (postPopupHelper.isOpen()) { if (postPopupHelper.isOpen()) {
@ -544,6 +554,8 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
void showImages(List<PostImage> images, int index, Loadable loadable, ThumbnailView thumbnail); void showImages(List<PostImage> images, int index, Loadable loadable, ThumbnailView thumbnail);
void showAlbum(List<PostImage> images, int index);
void onShowPosts(); void onShowPosts();
void presentRepliesController(Controller controller); void presentRepliesController(Controller controller);

@ -107,19 +107,10 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
// onScrolled can be called after cleanup() // onScrolled can be called after cleanup()
if (showingThread != null) { if (showingThread != null) {
int index = 0; int[] indexTop = getIndexAndTop();
int top = 0;
if (recyclerView.getLayoutManager().getChildCount() > 0) {
View topChild = recyclerView.getLayoutManager().getChildAt(0);
index = ((RecyclerView.LayoutParams) topChild.getLayoutParams()).getViewLayoutPosition(); showingThread.loadable.setListViewIndex(indexTop[0]);
showingThread.loadable.setListViewTop(indexTop[1]);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) topChild.getLayoutParams();
top = layoutManager.getDecoratedTop(topChild) - params.topMargin - recyclerView.getPaddingTop();
}
showingThread.loadable.setListViewIndex(index);
showingThread.loadable.setListViewTop(top);
int last = getCompleteBottomAdapterPosition(); int last = getCompleteBottomAdapterPosition();
if (last == postAdapter.getUnfilteredDisplaySize() - 1 && last > lastPostCount) { if (last == postAdapter.getUnfilteredDisplaySize() - 1 && last > lastPostCount) {
@ -377,6 +368,15 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
recyclerView.smoothScrollToPosition(bottom); recyclerView.smoothScrollToPosition(bottom);
} else { } else {
recyclerView.scrollToPosition(bottom); recyclerView.scrollToPosition(bottom);
// No animation means no animation, wait for the layout to finish and skip all animations
final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator();
AndroidUtils.waitForLayout(recyclerView, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
itemAnimator.endAnimations();
return true;
}
});
} }
} else { } else {
int scrollPosition = postAdapter.getScrollPosition(displayPosition); int scrollPosition = postAdapter.getScrollPosition(displayPosition);
@ -390,6 +390,15 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
recyclerView.smoothScrollToPosition(scrollPosition); recyclerView.smoothScrollToPosition(scrollPosition);
} else { } else {
recyclerView.scrollToPosition(scrollPosition); recyclerView.scrollToPosition(scrollPosition);
// No animation means no animation, wait for the layout to finish and skip all animations
final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator();
AndroidUtils.waitForLayout(recyclerView, new AndroidUtils.OnMeasuredCallback() {
@Override
public boolean onMeasured(View view) {
itemAnimator.endAnimations();
return true;
}
});
} }
} }
} }
@ -430,6 +439,21 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
return showingThread; return showingThread;
} }
public int[] getIndexAndTop() {
int index = 0;
int top = 0;
if (recyclerView.getLayoutManager().getChildCount() > 0) {
View topChild = recyclerView.getLayoutManager().getChildAt(0);
index = ((RecyclerView.LayoutParams) topChild.getLayoutParams()).getViewLayoutPosition();
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) topChild.getLayoutParams();
top = layoutManager.getDecoratedTop(topChild) - params.topMargin - recyclerView.getPaddingTop();
}
return new int[]{index, top};
}
private void attachToolbarScroll(boolean attach) { private void attachToolbarScroll(boolean attach) {
if (!AndroidUtils.isTablet(getContext()) && !ChanSettings.neverHideToolbar.get()) { if (!AndroidUtils.isTablet(getContext()) && !ChanSettings.neverHideToolbar.get()) {
Toolbar toolbar = threadListLayoutCallback.getToolbar(); Toolbar toolbar = threadListLayoutCallback.getToolbar();

@ -178,6 +178,10 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
setNavigationItemInternal(animate, pushing, item); setNavigationItemInternal(animate, pushing, item);
} }
public boolean isTransitioning() {
return transitioning;
}
public void beginTransition(NavigationItem newItem) { public void beginTransition(NavigationItem newItem) {
if (transitioning) { if (transitioning) {
throw new IllegalStateException("beginTransition called when already transitioning"); throw new IllegalStateException("beginTransition called when already transitioning");

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

@ -0,0 +1,43 @@
<?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/>.
-->
<org.floens.chan.ui.cell.AlbumViewCell xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="1dp">
<org.floens.chan.ui.view.ThumbnailView
android:id="@+id/thumbnail_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="32dp"
android:layout_gravity="bottom"
android:background="#8a000000"
android:gravity="center_vertical"
android:maxLines="2"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textColor="#ffffffff"
android:textSize="10sp"
tools:ignore="SmallSp" />
</org.floens.chan.ui.cell.AlbumViewCell>

@ -0,0 +1,28 @@
<?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/>.
-->
<org.floens.chan.ui.view.GridRecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?backcolor"
android:clipToPadding="false"
android:padding="1dp"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical">
</org.floens.chan.ui.view.GridRecyclerView>

@ -100,8 +100,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="card_stats">%1$dR %2$dI</string> <string name="card_stats">%1$dR %2$dI</string>
<string name="action_view_album">View album</string>
<string name="action_reload">Reload</string> <string name="action_reload">Reload</string>
<string name="action_pin">Pin</string> <string name="action_pin">Pin</string>
<string name="action_go_post">Go to post</string>
<string name="action_open_browser">Open in browser</string> <string name="action_open_browser">Open in browser</string>
<string name="action_share">Share</string> <string name="action_share">Share</string>
<string name="action_download_album">Download album</string> <string name="action_download_album">Download album</string>
@ -309,6 +311,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="album_download_none_checked">Please select images to download</string> <string name="album_download_none_checked">Please select images to download</string>
<string name="album_download_confirm">%1$s will be downloaded to the folder %2$s</string> <string name="album_download_confirm">%1$s will be downloaded to the folder %2$s</string>
<string name="album_view_screen">Album</string>
<string name="image_save_notification_downloading">Downloading images</string> <string name="image_save_notification_downloading">Downloading images</string>
<string name="image_save_notification_cancel">Tap to cancel</string> <string name="image_save_notification_cancel">Tap to cancel</string>
<string name="image_save_saved">Image saved</string> <string name="image_save_saved">Image saved</string>

Loading…
Cancel
Save