diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java index bbf446b3..72311aa1 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java @@ -216,6 +216,26 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt } } + public void showAlbum() { + List posts = threadPresenterCallback.getDisplayingPosts(); + int[] pos = threadPresenterCallback.getCurrentPosition(); + int displayPosition = pos[0]; + + List 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 public Loadable getLoadable() { return loadable; @@ -327,6 +347,18 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt threadPresenterCallback.selectPost(post); } + public void selectPostImage(PostImage postImage) { + List 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 */ @@ -662,8 +694,12 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt List getDisplayingPosts(); + int[] getCurrentPosition(); + void showImages(List images, int index, Loadable loadable, ThumbnailView thumbnail); + void showAlbum(List images, int index); + void scrollTo(int displayPosition, boolean smooth); void highlightPost(Post post); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java index fec63447..2af52ce4 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java @@ -251,7 +251,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat thread = Loadable.emptyLoadable(); } - outState.putParcelable(STATE_KEY, new ChanState(board, thread)); + outState.putParcelable(STATE_KEY, new ChanState(board.copy(), thread.copy())); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/cell/AlbumViewCell.java b/Clover/app/src/main/java/org/floens/chan/ui/cell/AlbumViewCell.java new file mode 100644 index 00000000..2869400c --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/cell/AlbumViewCell.java @@ -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 . + */ +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); + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumViewController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumViewController.java new file mode 100644 index 00000000..db75fdaf --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumViewController.java @@ -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 . + */ +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 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 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 { + 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); + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/DrawerController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/DrawerController.java index 4939b8b9..3c4e4a41 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/DrawerController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/DrawerController.java @@ -234,6 +234,9 @@ public class DrawerController extends Controller implements PinAdapter.Callback, public void setDrawerEnabled(boolean enabled) { drawerLayout.setDrawerLockMode(enabled ? DrawerLayout.LOCK_MODE_UNLOCKED : DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.LEFT); + if (!enabled) { + drawerLayout.closeDrawer(drawer); + } } private void updateBadge() { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java index eb2a8165..85104b2a 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java @@ -70,7 +70,6 @@ import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.Logger; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; 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 float TRANSITION_FINAL_ALPHA = 0.85f; - private static final int SAVE_ID = 101; - private static final int OPEN_BROWSER_ID = 102; - private static final int SHARE_ID = 103; - private static final int SEARCH_ID = 104; - private static final int SAVE_ALBUM = 105; + private static final int GO_POST_ID = 1; + private static final int SAVE_ID = 2; + private static final int OPEN_BROWSER_ID = 103; + private static final int SHARE_ID = 104; + private static final int SEARCH_ID = 105; + private static final int SAVE_ALBUM = 106; private int statusBarColorPrevious; private AnimatorSet startAnimation; private AnimatorSet endAnimation; - private PreviewCallback previewCallback; + private ImageViewerCallback imageViewerCallback; + private GoPostCallback goPostCallback; private ImageViewerPresenter presenter; private final Toolbar toolbar; @@ -116,13 +117,17 @@ public class ImageViewerController extends Controller implements ImageViewerPres navigationItem.subtitle = "0"; 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)); - overflowMenuItem = navigationItem.createOverflow(context, this, Arrays.asList( - new FloatingMenuItem(OPEN_BROWSER_ID, R.string.action_open_browser), - new FloatingMenuItem(SHARE_ID, R.string.action_share), - new FloatingMenuItem(SEARCH_ID, R.string.action_search_image), - new FloatingMenuItem(SAVE_ALBUM, R.string.action_download_album) - )); + + List items = new ArrayList<>(); + items.add(new FloatingMenuItem(OPEN_BROWSER_ID, R.string.action_open_browser)); + items.add(new FloatingMenuItem(SHARE_ID, R.string.action_share)); + 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); previewImage = (TransitionImageView) view.findViewById(R.id.preview_image); @@ -153,8 +158,26 @@ public class ImageViewerController extends Controller implements ImageViewerPres @Override public void onMenuItemClicked(ToolbarMenuItem item) { - if ((Integer) item.getId() == SAVE_ID) { - saveShare(false, presenter.getCurrentPostImage()); + switch ((Integer) item.getId()) { + 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; } - public void setPreviewCallback(PreviewCallback previewCallback) { - this.previewCallback = previewCallback; + public void setImageViewerCallback(ImageViewerCallback imageViewerCallback) { + this.imageViewerCallback = imageViewerCallback; + } + + public void setGoPostCallback(GoPostCallback goPostCallback) { + this.goPostCallback = goPostCallback; } public ImageViewerPresenter getPresenter() { @@ -258,7 +285,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres } public void scrollToImage(PostImage postImage) { - previewCallback.scrollToImage(postImage); + imageViewerCallback.scrollToImage(postImage); } public void showProgress(boolean show) { @@ -324,7 +351,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres startAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - previewCallback.onPreviewCreate(ImageViewerController.this); + imageViewerCallback.onPreviewCreate(ImageViewerController.this); } @Override @@ -431,7 +458,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres private void previewOutAnimationEnded() { setBackgroundAlpha(0f); - previewCallback.onPreviewDestroy(this); + imageViewerCallback.onPreviewDestroy(this); navigationController.stopPresenting(false); } @@ -482,14 +509,14 @@ public class ImageViewerController extends Controller implements ImageViewerPres } private ThumbnailView getTransitionImageView(PostImage postImage) { - return previewCallback.getPreviewImageTransitionView(this, postImage); + return imageViewerCallback.getPreviewImageTransitionView(this, postImage); } private Window getWindow() { return ((Activity) context).getWindow(); } - public interface PreviewCallback { + public interface ImageViewerCallback { ThumbnailView getPreviewImageTransitionView(ImageViewerController imageViewerController, PostImage postImage); void onPreviewCreate(ImageViewerController imageViewerController); @@ -498,4 +525,8 @@ public class ImageViewerController extends Controller implements ImageViewerPres void scrollToImage(PostImage postImage); } + + public interface GoPostCallback { + ImageViewerCallback goToPost(PostImage postImage); + } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java index 1e691a32..845d0ed6 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java @@ -46,10 +46,18 @@ public class ImageViewerNavigationController extends ToolbarNavigationController toolbar.setCallback(this); } - public void showImages(final List images, final int index, final Loadable loadable, final ImageViewerController.PreviewCallback previewCallback) { + public void showImages(final List images, final int index, final Loadable loadable, + ImageViewerController.ImageViewerCallback imageViewerCallback) { + showImages(images, index, loadable, imageViewerCallback, null); + } + + public void showImages(final List images, final int index, final Loadable loadable, + ImageViewerController.ImageViewerCallback imageViewerCallback, + ImageViewerController.GoPostCallback goPostCallback) { imageViewerController = new ImageViewerController(context, toolbar); + imageViewerController.setGoPostCallback(goPostCallback); pushController(imageViewerController, false); - imageViewerController.setPreviewCallback(previewCallback); + imageViewerController.setImageViewerCallback(imageViewerCallback); imageViewerController.getPresenter().showImages(images, index, loadable); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java index fd0253bc..ffad563c 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java @@ -45,6 +45,17 @@ public class StyledToolbarNavigationController extends ToolbarNavigationControll 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 public void transition(Controller from, Controller to, boolean pushing, ControllerTransition controllerTransition) { super.transition(from, to, pushing, controllerTransition); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java index 806833dc..6774a925 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java @@ -47,7 +47,7 @@ import de.greenrobot.event.EventBus; 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"; protected ThreadLayout threadLayout; @@ -162,6 +162,10 @@ public abstract class ThreadController extends Controller implements ThreadLayou presentController(controller); } + public void selectPostImage(PostImage postImage) { + threadLayout.getPresenter().selectPostImage(postImage); + } + @Override public void showImages(List images, int index, Loadable loadable, final ThumbnailView thumbnail) { // 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; } + @Override public void scrollToImage(PostImage postImage) { threadLayout.getPresenter().scrollToImage(postImage, true); } + @Override + public void showAlbum(List images, int index) { + if (threadLayout.getPresenter().getChanThread() != null) { + AlbumViewController albumViewController = new AlbumViewController(context); + albumViewController.setImages(getLoadable(), images, index, navigationItem.title); + navigationController.pushController(albumViewController); + } + } + @Override public void onShowPosts() { } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java index 5e7f602a..93e28e12 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ViewThreadController.java @@ -47,14 +47,15 @@ import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.getAttrColor; 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 REPLY_ID = 101; - private static final int REFRESH_ID = 102; - private static final int SEARCH_ID = 103; - private static final int SHARE_ID = 104; - private static final int UP_ID = 105; - private static final int DOWN_ID = 106; - private static final int OPEN_BROWSER_ID = 107; + private static final int REFRESH_ID = 103; + private static final int SEARCH_ID = 104; + private static final int SHARE_ID = 105; + private static final int UP_ID = 106; + private static final int DOWN_ID = 107; + private static final int OPEN_BROWSER_ID = 108; private ToolbarMenuItem pinItem; private ToolbarMenuItem overflowItem; @@ -79,17 +80,18 @@ public class ViewThreadController extends ThreadController implements ThreadLayo navigationItem.hasDrawer = true; 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)); List items = new ArrayList<>(); if (!ChanSettings.enableReplyFab.get()) { 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(SEARCH_ID, context.getString(R.string.action_search))); - items.add(new FloatingMenuItem(OPEN_BROWSER_ID, context.getString(R.string.action_open_browser))); - items.add(new FloatingMenuItem(SHARE_ID, context.getString(R.string.action_share))); - items.add(new FloatingMenuItem(UP_ID, context.getString(R.string.action_up))); - items.add(new FloatingMenuItem(DOWN_ID, context.getString(R.string.action_down))); + items.add(new FloatingMenuItem(REFRESH_ID, R.string.action_reload)); + items.add(new FloatingMenuItem(SEARCH_ID, R.string.action_search)); + items.add(new FloatingMenuItem(OPEN_BROWSER_ID, R.string.action_open_browser)); + items.add(new FloatingMenuItem(SHARE_ID, R.string.action_share)); + items.add(new FloatingMenuItem(UP_ID, R.string.action_up)); + items.add(new FloatingMenuItem(DOWN_ID, R.string.action_down)); overflowItem = navigationItem.createOverflow(context, this, items); loadThread(loadable); @@ -170,6 +172,9 @@ public class ViewThreadController extends ThreadController implements ThreadLayo @Override public void onMenuItemClicked(ToolbarMenuItem item) { switch ((Integer) item.getId()) { + case ALBUM_ID: + threadLayout.getPresenter().showAlbum(); + break; case PIN_ID: setPinIconState(threadLayout.getPresenter().pin()); updateDrawerHighlighting(loadable); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java index b11f41dd..8aebc8d6 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java @@ -320,11 +320,21 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T } } + @Override + public int[] getCurrentPosition() { + return threadListLayout.getIndexAndTop(); + } + @Override public void showImages(List images, int index, Loadable loadable, ThumbnailView thumbnail) { callback.showImages(images, index, loadable, thumbnail); } + @Override + public void showAlbum(List images, int index) { + callback.showAlbum(images, index); + } + @Override public void scrollTo(int displayPosition, boolean smooth) { if (postPopupHelper.isOpen()) { @@ -544,6 +554,8 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T void showImages(List images, int index, Loadable loadable, ThumbnailView thumbnail); + void showAlbum(List images, int index); + void onShowPosts(); void presentRepliesController(Controller controller); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java index eaad9e46..ab2c4507 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java @@ -107,19 +107,10 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa public void onScrolled(RecyclerView recyclerView, int dx, int dy) { // onScrolled can be called after cleanup() if (showingThread != null) { - int index = 0; - int top = 0; - if (recyclerView.getLayoutManager().getChildCount() > 0) { - View topChild = recyclerView.getLayoutManager().getChildAt(0); + int[] indexTop = getIndexAndTop(); - index = ((RecyclerView.LayoutParams) topChild.getLayoutParams()).getViewLayoutPosition(); - - RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) topChild.getLayoutParams(); - top = layoutManager.getDecoratedTop(topChild) - params.topMargin - recyclerView.getPaddingTop(); - } - - showingThread.loadable.setListViewIndex(index); - showingThread.loadable.setListViewTop(top); + showingThread.loadable.setListViewIndex(indexTop[0]); + showingThread.loadable.setListViewTop(indexTop[1]); int last = getCompleteBottomAdapterPosition(); if (last == postAdapter.getUnfilteredDisplaySize() - 1 && last > lastPostCount) { @@ -377,6 +368,15 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa recyclerView.smoothScrollToPosition(bottom); } else { 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 { int scrollPosition = postAdapter.getScrollPosition(displayPosition); @@ -390,6 +390,15 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa recyclerView.smoothScrollToPosition(scrollPosition); } else { 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; } + 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) { if (!AndroidUtils.isTablet(getContext()) && !ChanSettings.neverHideToolbar.get()) { Toolbar toolbar = threadListLayoutCallback.getToolbar(); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java index 164c088d..d5341327 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java @@ -178,6 +178,10 @@ public class Toolbar extends LinearLayout implements View.OnClickListener { setNavigationItemInternal(animate, pushing, item); } + public boolean isTransitioning() { + return transitioning; + } + public void beginTransition(NavigationItem newItem) { if (transitioning) { throw new IllegalStateException("beginTransition called when already transitioning"); diff --git a/Clover/app/src/main/res/drawable-hdpi/ic_subdirectory_arrow_left_white_24dp.png b/Clover/app/src/main/res/drawable-hdpi/ic_subdirectory_arrow_left_white_24dp.png new file mode 100644 index 00000000..0d9f5a62 Binary files /dev/null and b/Clover/app/src/main/res/drawable-hdpi/ic_subdirectory_arrow_left_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-mdpi/ic_subdirectory_arrow_left_white_24dp.png b/Clover/app/src/main/res/drawable-mdpi/ic_subdirectory_arrow_left_white_24dp.png new file mode 100644 index 00000000..3502e62c Binary files /dev/null and b/Clover/app/src/main/res/drawable-mdpi/ic_subdirectory_arrow_left_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xhdpi/ic_subdirectory_arrow_left_white_24dp.png b/Clover/app/src/main/res/drawable-xhdpi/ic_subdirectory_arrow_left_white_24dp.png new file mode 100644 index 00000000..5424e971 Binary files /dev/null and b/Clover/app/src/main/res/drawable-xhdpi/ic_subdirectory_arrow_left_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xxhdpi/ic_subdirectory_arrow_left_white_24dp.png b/Clover/app/src/main/res/drawable-xxhdpi/ic_subdirectory_arrow_left_white_24dp.png new file mode 100644 index 00000000..6e57317d Binary files /dev/null and b/Clover/app/src/main/res/drawable-xxhdpi/ic_subdirectory_arrow_left_white_24dp.png differ diff --git a/Clover/app/src/main/res/drawable-xxxhdpi/ic_subdirectory_arrow_left_white_24dp.png b/Clover/app/src/main/res/drawable-xxxhdpi/ic_subdirectory_arrow_left_white_24dp.png new file mode 100644 index 00000000..38288462 Binary files /dev/null and b/Clover/app/src/main/res/drawable-xxxhdpi/ic_subdirectory_arrow_left_white_24dp.png differ diff --git a/Clover/app/src/main/res/layout/cell_album_view.xml b/Clover/app/src/main/res/layout/cell_album_view.xml new file mode 100644 index 00000000..bdde3e4f --- /dev/null +++ b/Clover/app/src/main/res/layout/cell_album_view.xml @@ -0,0 +1,43 @@ + + + + + + + + diff --git a/Clover/app/src/main/res/layout/controller_album_view.xml b/Clover/app/src/main/res/layout/controller_album_view.xml new file mode 100644 index 00000000..63d7428a --- /dev/null +++ b/Clover/app/src/main/res/layout/controller_album_view.xml @@ -0,0 +1,28 @@ + + + + diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index 90439961..865771a8 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -100,8 +100,10 @@ along with this program. If not, see . %1$dR %2$dI + View album Reload Pin + Go to post Open in browser Share Download album @@ -309,6 +311,8 @@ along with this program. If not, see . Please select images to download %1$s will be downloaded to the folder %2$s + Album + Downloading images Tap to cancel Image saved