From ee7a3ac3b33dc5338c794f46c5fae409e5be548c Mon Sep 17 00:00:00 2001 From: seven332 Date: Wed, 26 Aug 2015 13:08:33 +0800 Subject: [PATCH] Add favorite --- .../com/hippo/ehviewer/client/EhClient.java | 31 ++ .../com/hippo/ehviewer/client/EhEngine.java | 8 + .../java/com/hippo/ehviewer/client/EhUrl.java | 11 + .../client/data/FavoriteUrlBuilder.java | 84 +++ .../hippo/ehviewer/network/EhHttpClient.java | 9 + .../hippo/ehviewer/ui/ContentActivity.java | 2 + .../ehviewer/ui/scene/AbsDragSortScene.java | 4 +- .../ehviewer/ui/scene/FavoriteScene.java | 519 ++++++++++++++++++ .../ehviewer/ui/scene/GalleryListScene.java | 211 ++++--- .../hippo/ehviewer/ui/scene/HistoryScene.java | 5 +- .../hippo/ehviewer/widget/ContentLayout.java | 51 +- app/src/main/java/com/hippo/scene/Scene.java | 15 +- .../main/java/com/hippo/scene/SceneView.java | 25 + app/src/main/res/layout/scene_favorite.xml | 38 ++ app/src/main/res/layout/widget_search_bar.xml | 1 - 15 files changed, 855 insertions(+), 159 deletions(-) create mode 100644 app/src/main/java/com/hippo/ehviewer/client/data/FavoriteUrlBuilder.java create mode 100644 app/src/main/java/com/hippo/ehviewer/ui/scene/FavoriteScene.java create mode 100644 app/src/main/res/layout/scene_favorite.xml diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java index 49c7f6e63..470a8fafa 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -226,6 +226,35 @@ public final class EhClient { */ public static final int METHOD_RATE_GALLERY = 7; + /** + * Get favorite + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
paraminfo
catfavorite category, -1 for all
pagethe page index
sourcethe source, one of {@link EhUrl#SOURCE_G}, + * {@link EhUrl#SOURCE_EX} and {@link EhUrl#SOURCE_LOFI}
listenerthe listener for callback
+ */ + public static final int METHOD_GET_FAVORITE = 8; + private final ThreadPoolExecutor mRequestThreadPool; private final HttpClient mHttpClient; @@ -352,6 +381,8 @@ public final class EhClient { return EhEngine.getPreviewSet(mHttpClient, mEhHttpRequest, (String) params[0], (Integer) params[1]); case METHOD_RATE_GALLERY: return EhEngine.rateGallery(mHttpClient, mEhHttpRequest, (Integer) params[0], (String) params[1], (Float) params[2], (Integer) params[3]); + case METHOD_GET_FAVORITE: + return EhEngine.getFavorite(mHttpClient, mEhHttpRequest, (Integer) params[0], (Integer) params[1], (Integer) params[2]); default: return new IllegalStateException("Can't detect method"); } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java index e6c714f06..a7bd4c84c 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -81,6 +81,14 @@ class EhEngine { } } + /** + * @param cat -1 for all + */ + public static GalleryListParser.Result getFavorite(HttpClient httpClient, + HttpRequest httpRequest, int cat, int page, int source) throws Exception { + return getGalleryList(httpClient, httpRequest, EhUrl.getFavoriteUrl(source, cat, page), source); + } + public static PopularParser.Result getPopular(HttpClient httpClient, HttpRequest httpRequest) throws Exception { try { diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java b/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java index 962bcfc72..f77663e31 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java @@ -83,4 +83,15 @@ public class EhUrl { public static String getPageUrl(int source, int gid, String token, int pageIndex) { return getUrlHeader(source) + "s/" + token + '/' + gid + '-' + (pageIndex + 1); } + + /** + * @param cat -1 for all + */ + public static String getFavoriteUrl(int source, int cat, int pageIndex) { + if (cat == -1) { + return getUrlHeader(source) + "favorites.php?page=" + pageIndex; + } else { + return getUrlHeader(source) + "favorites.php?favcat=" + cat + "&page=" + pageIndex; + } + } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/FavoriteUrlBuilder.java b/app/src/main/java/com/hippo/ehviewer/client/data/FavoriteUrlBuilder.java new file mode 100644 index 000000000..ec2fa9ee1 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/data/FavoriteUrlBuilder.java @@ -0,0 +1,84 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.client.data; + +import android.text.TextUtils; + +import com.hippo.ehviewer.client.EhUrl; +import com.hippo.network.UrlBuilder; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +public class FavoriteUrlBuilder implements Cloneable { + + public static final int FAV_CAT_LOCAL = -2; + public static final int FAV_CAT_ALL = -1; + + private int mCat = -1; + private String mKeyword = null; + + public int getCat() { + return mCat; + } + + public void setCat(int cat) { + mCat = cat; + } + + public String getKeyword() { + return mKeyword; + } + + public void setKeyword(String keyword) { + mKeyword = keyword; + } + + public boolean isLocal() { + return mCat == FAV_CAT_LOCAL; + } + + public String build(int source, int page) { + UrlBuilder ub = new UrlBuilder(EhUrl.getUrlHeader(source) + "favorites.php"); + + if (mCat != FAV_CAT_ALL) { + ub.addQuery("favcat", mCat); + } + + if (!TextUtils.isEmpty(mKeyword)) { + try { + ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + ub.addQuery("f_apply", "Search+Favorites"); + } + + ub.addQuery("page", page); + + return ub.build(); + } + + @Override + protected FavoriteUrlBuilder clone() { + try { + return (FavoriteUrlBuilder) super.clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/network/EhHttpClient.java b/app/src/main/java/com/hippo/ehviewer/network/EhHttpClient.java index 925357884..39c95c2b5 100644 --- a/app/src/main/java/com/hippo/ehviewer/network/EhHttpClient.java +++ b/app/src/main/java/com/hippo/ehviewer/network/EhHttpClient.java @@ -94,6 +94,15 @@ public class EhHttpClient extends HttpClient { protected void fillCookie(URL url, Cookie cookie) { cookie.putRaw(mCookieManager.getCookie(url.toString())); + // Load these from config + cookie.remove(KEY_IPD_MEMBER_ID); + cookie.remove(KEY_IPD_MEMBER_ID); + cookie.remove(KEY_IGNEOUS); + cookie.remove(KEY_YAY); + cookie.remove(EhConfig.KEY_UCONFIG); + cookie.remove(EhConfig.KEY_LOFI_RESOLUTION); + cookie.remove(EhConfig.KEY_CONTENT_WARNING); + if (Utilities.contain(EHENTAI_DOMAIN, getDomain(url))) { if (Settings.getSignIn()) { // Put login info diff --git a/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java index 987b3f764..a868ff9e8 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/ContentActivity.java @@ -33,6 +33,7 @@ import com.hippo.ehviewer.R; import com.hippo.ehviewer.service.DownloadService; import com.hippo.ehviewer.ui.scene.DownloadScene; import com.hippo.ehviewer.ui.scene.DrawerProvider; +import com.hippo.ehviewer.ui.scene.FavoriteScene; import com.hippo.ehviewer.ui.scene.GalleryListScene; import com.hippo.ehviewer.ui.scene.HistoryScene; import com.hippo.ehviewer.ui.scene.MainSettingsScene; @@ -100,6 +101,7 @@ public final class ContentActivity extends StageActivity startScene(HistoryScene.class); break; case DRAWER_LIST_FAVORITE: + startScene(FavoriteScene.class); break; case DRAWER_LIST_DOWNLOAD: startScene(DownloadScene.class); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/AbsDragSortScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/AbsDragSortScene.java index 145d7d40e..d9ce2c2a9 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/AbsDragSortScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/AbsDragSortScene.java @@ -162,8 +162,8 @@ public abstract class AbsDragSortScene extends AppbarScene { } @Override - protected void onDetachFromeStage() { - super.onDetachFromeStage(); + protected void onDetachedFromeWindow() { + super.onDetachedFromeWindow(); if (mRecyclerViewDragDropManager != null) { mRecyclerViewDragDropManager.release(); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoriteScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoriteScene.java new file mode 100644 index 000000000..ab9562b73 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoriteScene.java @@ -0,0 +1,519 @@ +/* + * Copyright 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui.scene; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.StaggeredGridLayoutManager; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.hippo.animation.SimpleAnimatorListener; +import com.hippo.conaco.Conaco; +import com.hippo.drawable.DrawerArrowDrawable; +import com.hippo.ehviewer.EhApplication; +import com.hippo.ehviewer.EhCacheKeyFactory; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.client.EhClient; +import com.hippo.ehviewer.client.EhRequest; +import com.hippo.ehviewer.client.GalleryListParser; +import com.hippo.ehviewer.client.data.FavoriteUrlBuilder; +import com.hippo.ehviewer.client.data.GalleryBase; +import com.hippo.ehviewer.client.data.GalleryInfo; +import com.hippo.ehviewer.util.EhUtils; +import com.hippo.ehviewer.util.Settings; +import com.hippo.ehviewer.widget.ContentLayout; +import com.hippo.ehviewer.widget.LoadImageView; +import com.hippo.ehviewer.widget.OffsetLayout; +import com.hippo.ehviewer.widget.SearchBar; +import com.hippo.ehviewer.widget.SimpleRatingView; +import com.hippo.scene.Scene; +import com.hippo.widget.recyclerview.EasyRecyclerView; +import com.hippo.yorozuya.LayoutUtils; +import com.hippo.yorozuya.MathUtils; +import com.hippo.yorozuya.SimpleHandler; +import com.hippo.yorozuya.ViewUtils; + +public class FavoriteScene extends Scene implements SearchBar.Helper, SearchBar.OnStateChangeListener { + + private static final long ANIMATE_TIME = 300l; + + private static final int STATE_NORMAL = 0; + private static final int STATE_SEARCH = 1; + + private FavoriteUrlBuilder mUrlBuilder; + + private ContentLayout mContentLayout; + private EasyRecyclerView mRecyclerView; + private SearchBar mSearchBar; + + private DrawerArrowDrawable mLeftDrawable; + + private FavoriteHelper mFavoriteHelper; + private FavoriteAdapter mFavoriteAdapter; + + private int mSearchBarOriginalTop; + private int mSearchBarOriginalBottom; + private ValueAnimator mSearchBarMoveAnimator; + + private int mState = STATE_NORMAL; + + private EhRequest mEhRequest; + + private int mFavCat = -1; + + @Override + protected void onInit() { + super.onInit(); + + mUrlBuilder = new FavoriteUrlBuilder(); + mFavoriteHelper = new FavoriteHelper(); + } + + @Override + protected void onDie() { + super.onDie(); + + if (mEhRequest != null) { + mEhRequest.cancel(); + mEhRequest = null; + } + } + + @Override + protected void onCreate(boolean rebirth) { + super.onCreate(rebirth); + + setContentView(R.layout.scene_favorite); + + Resources resources = getResources(); + + mContentLayout = (ContentLayout) findViewById(R.id.content_layout); + mSearchBar = (SearchBar) findViewById(R.id.search_bar); + mRecyclerView = mContentLayout.getRecyclerView(); + + ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mSearchBar.getLayoutParams(); + ViewUtils.measureView(mSearchBar, 200, ViewGroup.LayoutParams.WRAP_CONTENT); + int fitPaddingTop = mSearchBar.getMeasuredHeight() + lp.topMargin; + mContentLayout.setFitPaddingTop(fitPaddingTop); + mSearchBarOriginalTop = lp.topMargin; + mSearchBarOriginalBottom = lp.topMargin + mSearchBar.getMeasuredHeight(); + + mFavoriteAdapter = new FavoriteAdapter(); + mContentLayout.setHelper(mFavoriteHelper); + mContentLayout.setAdapter(mFavoriteAdapter); + + mRecyclerView.addOnScrollListener(new SearchBarMoveHelper()); + + mLeftDrawable = new DrawerArrowDrawable(getContext()); + mSearchBar.setLeftDrawable(mLeftDrawable); + mSearchBar.setRightIconVisibility(View.GONE); + mSearchBar.setOnStateChangeListener(this); + mSearchBar.setTitle(resources.getString(R.string.favorite)); + mSearchBar.setHelper(this); + mSearchBar.setEditTextMargin(LayoutUtils.dp2pix(getContext(), 56), LayoutUtils.dp2pix(getContext(), 16)); + } + + @Override + protected void onBind() { + super.onBind(); + + mFavoriteHelper.firstRefresh(); + } + + @Override + protected void onRestore() { + super.onRestore(); + + int state = mState; + // Reset state for set state + mState = STATE_NORMAL; + setState(state, false); + } + + @Override + protected void onGetFitPaddingBottom(int b) { + mContentLayout.setFitPaddingBottom(b); + } + + @Override + public void onBackPressed() { + switch (mState) { + case STATE_SEARCH: + setState(STATE_NORMAL); + break; + default: + case STATE_NORMAL: + super.onBackPressed(); + break; + } + } + + private void setState(int state) { + setState(state, true); + } + + private void setState(int state, boolean animation) { + if (mState != state) { + mState = state; + + if (state == STATE_SEARCH) { + // STATE_NORMAL => STATE_SEARCH + mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); + } else { + // STATE_SEARCH => STATE_NORMAL + mSearchBar.setState(SearchBar.STATE_NORMAL, animation); + } + } + } + + @Override + public void onClickTitle() { + setState(STATE_SEARCH, true); + } + + @Override + public void onClickLeftIcon() { + + } + + @Override + public void onClickRightIcon() { + + } + + @Override + public void onSearchEditTextClick() { + + } + + @Override + public void onApplySearch(String query) { + mUrlBuilder.setKeyword(query); + mFavoriteHelper.refresh(); + } + + private void showSearchBar(boolean animation) { + if (mSearchBar.isLayoutRequested()) { + return; + } + + // Cancel old animator + if (mSearchBarMoveAnimator != null) { + mSearchBarMoveAnimator.cancel(); + } + + int offset = mSearchBarOriginalTop - mSearchBar.getTop(); + if (offset != 0) { + if (animation) { + final boolean needRequestLayout = mSearchBar.getBottom() <= 0 && offset > 0; + final ValueAnimator va = ValueAnimator.ofInt(0, offset); + va.setDuration(ANIMATE_TIME); + va.addListener(new SimpleAnimatorListener() { + @Override + public void onAnimationEnd(Animator animation) { + mSearchBarMoveAnimator = null; + } + }); + va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + int lastValue; + boolean hasRequestLayout; + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + int offsetStep = value - lastValue; + lastValue = value; + mSearchBar.offsetTopAndBottom(offsetStep); + ((OffsetLayout.LayoutParams) mSearchBar.getLayoutParams()).offsetY += offsetStep; + + if (!hasRequestLayout && needRequestLayout && mSearchBar.getBottom() > 0) { + hasRequestLayout = true; + mSearchBar.requestLayout(); + } + } + }); + if (mSearchBarMoveAnimator != null) { + mSearchBarMoveAnimator.cancel(); + } + mSearchBarMoveAnimator = va; + va.start(); + } else { + mSearchBar.offsetTopAndBottom(offset); + ((OffsetLayout.LayoutParams) mSearchBar.getLayoutParams()).offsetY += offset; + } + } + } + + private void returnSearchBarPosition() { + // Avoid weird shake + if (mSearchBar.isLayoutRequested()) { + return; + } + + boolean show; + if (mState == STATE_SEARCH) { + show = true; + } else { + RecyclerView recyclerView = mContentLayout.getRecyclerView(); + if (!recyclerView.isShown()) { + show = true; + } else if (recyclerView.computeVerticalScrollOffset() < mSearchBarOriginalBottom){ + show = true; + } else { + show = mSearchBar.getBottom() > (mSearchBarOriginalBottom - mSearchBarOriginalTop) / 2; + } + } + + int offset; + if (show) { + offset = mSearchBarOriginalTop - mSearchBar.getTop(); + } else { + offset = -mSearchBar.getBottom(); + } + + if (offset != 0) { + final boolean needRequestLayout = mSearchBar.getBottom() <= 0 && offset > 0; + final ValueAnimator va = ValueAnimator.ofInt(0, offset); + va.setDuration(ANIMATE_TIME); + va.addListener(new SimpleAnimatorListener() { + @Override + public void onAnimationEnd(Animator animation) { + mSearchBarMoveAnimator = null; + } + }); + va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + int lastValue; + boolean hasRequestLayout; + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + int offsetStep = value - lastValue; + lastValue = value; + mSearchBar.offsetTopAndBottom(offsetStep); + ((OffsetLayout.LayoutParams) mSearchBar.getLayoutParams()).offsetY += offsetStep; + + if (!hasRequestLayout && needRequestLayout && mSearchBar.getBottom() > 0) { + hasRequestLayout = true; + mSearchBar.requestLayout(); + } + } + }); + if (mSearchBarMoveAnimator != null) { + mSearchBarMoveAnimator.cancel(); + } + mSearchBarMoveAnimator = va; + va.start(); + } + } + + @Override + public void onStateChange(int newState, int oldState, boolean animation) { + switch (oldState) { + default: + case SearchBar.STATE_NORMAL: + mLeftDrawable.setArrow(animation ? ANIMATE_TIME : 0); + break; + case SearchBar.STATE_SEARCH: + if (newState == SearchBar.STATE_NORMAL) { + mLeftDrawable.setMenu(animation ? ANIMATE_TIME : 0); + } + break; + case SearchBar.STATE_SEARCH_LIST: + if (newState == STATE_NORMAL) { + mLeftDrawable.setMenu(animation ? ANIMATE_TIME : 0); + } + break; + } + } + + private class SearchBarMoveHelper extends RecyclerView.OnScrollListener { + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState){ + if (newState == RecyclerView.SCROLL_STATE_IDLE) { + returnSearchBarPosition(); + } + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + int oldBottom = mSearchBar.getBottom(); + int offsetYStep = MathUtils.clamp(-dy, -mSearchBar.getBottom(), mSearchBarOriginalTop - mSearchBar.getTop()); + mSearchBar.offsetTopAndBottom(offsetYStep); + ((OffsetLayout.LayoutParams) mSearchBar.getLayoutParams()).offsetY += offsetYStep; + int newBottom = mSearchBar.getBottom(); + + // Sometimes if it is out of screen than go into again, it do not show, + // so I need to requestLayout + if (oldBottom <= 0 && newBottom > 0) { + mSearchBar.requestLayout(); + } + } + } + + private class FavoriteHolder extends RecyclerView.ViewHolder { + + public LoadImageView thumb; + public TextView title; + public TextView uploader; + public SimpleRatingView rating; + public TextView category; + public TextView posted; + public TextView simpleLanguage; + + public FavoriteHolder(View itemView) { + super(itemView); + thumb = (LoadImageView) itemView.findViewById(R.id.thumb); + title = (TextView) itemView.findViewById(R.id.title); + uploader = (TextView) itemView.findViewById(R.id.uploader); + rating = (SimpleRatingView) itemView.findViewById(R.id.rating); + category = (TextView) itemView.findViewById(R.id.category); + posted = (TextView) itemView.findViewById(R.id.posted); + simpleLanguage = (TextView) itemView.findViewById(R.id.simple_language); + } + } + + private class FavoriteAdapter extends RecyclerView.Adapter { + + @Override + public FavoriteHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = getStageActivity().getLayoutInflater().inflate(R.layout.item_gallery_list_detail, parent, false); + return new FavoriteHolder(view); + } + + @Override + public void onBindViewHolder(FavoriteHolder holder, int position) { + GalleryBase gb = mFavoriteHelper.getDataAt(position); + Conaco conaco = EhApplication.getConaco(getStageActivity()); + holder.thumb.load(conaco, EhCacheKeyFactory.getThumbKey(gb.gid), gb.thumb); + holder.title.setText(EhUtils.getSuitableTitle(gb)); + holder.uploader.setText(gb.uploader); + holder.rating.setRating(gb.rating); + TextView category = holder.category; + String newCategoryText = EhUtils.getCategory(gb.category); + if (!newCategoryText.equals(category.getText())) { + category.setText(newCategoryText); + category.setBackgroundColor(EhUtils.getCategoryColor(gb.category)); + } + holder.posted.setText(gb.posted); + holder.simpleLanguage.setText(gb.simpleLanguage); + } + + @Override + public int getItemCount() { + return mFavoriteHelper.size(); + } + } + + private class FavoriteHelper extends ContentLayout.ContentHelper { + + private Runnable mSearchBarPositionTask = new Runnable() { + @Override + public void run() { + returnSearchBarPosition(); + } + }; + + @Override + protected Context getContext() { + return getStageActivity(); + } + + @Override + protected RecyclerView.LayoutManager generateLayoutManager() { + return new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL); + } + + @Override + protected void onScrollToPosition() { + SimpleHandler.getInstance().post(mSearchBarPositionTask); + } + + @Override + protected void onShowProgress() { + showSearchBar(true); + } + + @Override + protected void onShowText() { + showSearchBar(true); + } + + @Override + protected void getPageData(int taskId, int type, int page) { + EhClient client = EhApplication.getEhClient(getStageActivity()); + int source = Settings.getEhSource(); + FavoriteListener listener = new FavoriteListener(taskId); + EhRequest request = new EhRequest(); + request.setMethod(Settings.getJpnTitle() ? EhClient.METHOD_GET_GALLERY_LIST_JPN : + EhClient.METHOD_GET_GALLERY_LIST); + request.setEhListener(listener); + request.setArgs(mUrlBuilder.build(source, page), source, listener); + client.execute(request); + mEhRequest = request; + } + + @Override + protected void notifyDataSetChanged() { + mFavoriteAdapter.notifyDataSetChanged(); + } + + @Override + protected void notifyItemRangeRemoved(int positionStart, int itemCount) { + mFavoriteAdapter.notifyItemRangeRemoved(positionStart, itemCount); + } + + @Override + protected void notifyItemRangeInserted(int positionStart, int itemCount) { + mFavoriteAdapter.notifyItemRangeInserted(positionStart, itemCount); + } + } + + + private class FavoriteListener extends EhClient.EhListener { + + private int mTaskId; + + public FavoriteListener(int taskId) { + mTaskId = taskId; + } + + @Override + public void onSuccess(GalleryListParser.Result result) { + mEhRequest = null; + + mFavoriteHelper.setPageCount(result.pages); + mFavoriteHelper.onGetPageData(mTaskId, result.galleryInfos); + } + + @Override + public void onFailure(Exception e) { + mEhRequest = null; + + mFavoriteHelper.onGetPageData(mTaskId, e); + } + + @Override + public void onCanceled() { + mEhRequest = null; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java index 1ba5288dd..9cb810d87 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java @@ -127,6 +127,7 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe private ViewTransition mViewTransition; + private GalleryAdapter mAdapter; private GalleryListHelper mGalleryListHelper; private int mSearchBarOriginalTop; @@ -156,6 +157,8 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe private DrawerArrowDrawable mLeftDrawable; private AddDeleteDrawable mRightDrawable; + private EhRequest mEhRequest; + private SimpleDialog.OnCreateCustomViewListener mGoToCreateCustomViewListener = new SimpleDialog.OnCreateCustomViewListener() { @Override @@ -258,13 +261,6 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe handleAnnouncer(announcer); } - @Override - protected void onRebirth() { - super.onRebirth(); - - mGalleryListHelper = new GalleryListHelper(getStageActivity(), mGalleryListHelper); - } - @SuppressWarnings("deprecation") @Override protected void onCreate(boolean rebirth) { @@ -289,6 +285,7 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe mViewTransition = new ViewTransition(mContentLayout, mSearchLayout); // Init + mSearchBar.setTitle(mResources.getString(R.string.app_name)); mSearchBar.setHelper(this); ViewUtils.measureView(mSearchBar, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); SearchBarMoveHelper sbmHelper = new SearchBarMoveHelper(); @@ -313,11 +310,8 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe mSearchBarOriginalBottom = lp.topMargin + mSearchBar.getMeasuredHeight(); // Content Layout - // onRebirth may create new GalleryListHelper - if (mGalleryListHelper == null) { - mGalleryListHelper = new GalleryListHelper(mActivity); - } - + mAdapter = new GalleryAdapter(); + mContentLayout.setAdapter(mAdapter); mContentLayout.setHelper(mGalleryListHelper); mContentRecyclerView.setOnItemClickListener(this); @@ -373,6 +367,8 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe mJpnTitle = Settings.getJpnTitle(); Messenger.getInstance().register(Constants.MESSENGER_ID_JPN_TITLE, this); Messenger.getInstance().register(Constants.MESSENGER_ID_UPDATE_QUICK_SEARCH, this); + + mGalleryListHelper = new GalleryListHelper(); } @Override @@ -382,9 +378,9 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe Messenger.getInstance().unregister(Constants.MESSENGER_ID_JPN_TITLE, this); Messenger.getInstance().unregister(Constants.MESSENGER_ID_UPDATE_QUICK_SEARCH, this); - // Avoid memory leak - if (mGalleryListHelper.mListener != null) { - mGalleryListHelper.mListener.setGalleryListHelper(null); + if (mEhRequest != null) { + mEhRequest.cancel(); + mEhRequest = null; } } @@ -1069,9 +1065,46 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe } } - private class GalleryListHelper extends ContentLayout.ContentHelper { + private class GalleryAdapter extends RecyclerView.Adapter { - private GalleryListHelperSettable mListener; + @Override + public GalleryHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = mActivity.getLayoutInflater().inflate(R.layout.item_gallery_list_detail, parent, false); + return new GalleryHolder(view); + } + + @Override + public void onBindViewHolder(GalleryHolder holder, int position) { + GalleryInfo gi = mGalleryListHelper.getDataAt(position); + + Conaco conaco = EhApplication.getConaco(getStageActivity()); + holder.thumb.load(conaco, EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb); + String title; + if (mJpnTitle) { + title = TextUtils.isEmpty(gi.titleJpn) ? gi.title : gi.titleJpn; + } else { + title = gi.title; + } + holder.title.setText(title); + holder.uploader.setText(gi.uploader); + holder.rating.setRating(gi.rating); + TextView category = holder.category; + String newCategoryText = EhUtils.getCategory(gi.category); + if (!newCategoryText.equals(category.getText())) { + category.setText(newCategoryText); + category.setBackgroundColor(EhUtils.getCategoryColor(gi.category)); + } + holder.posted.setText(gi.posted); + holder.simpleLanguage.setText(gi.simpleLanguage); + } + + @Override + public int getItemCount() { + return mGalleryListHelper.size(); + } + } + + private class GalleryListHelper extends ContentLayout.ContentHelper { private Runnable mSearchBarPositionTask = new Runnable() { @Override @@ -1080,24 +1113,10 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe } }; - public GalleryListHelper(Context context) { - super(context); - } - - public GalleryListHelper(Context context, GalleryListHelper older) { - super(context, older); - // Update gallery listener - mListener = older.mListener; - if (mListener != null) { - mListener.setGalleryListHelper(this); - older.mListener = null; - } - } - - private void clearLastGalleryListHelperSettable(GalleryListHelperSettable listener) { - if (mListener == listener) { - mListener = null; - } + @Override + protected Context getContext() { + // TODO it may raise exception + return getStageActivity(); } @Override @@ -1121,6 +1140,21 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe showSearchBar(true); } + @Override + protected void notifyDataSetChanged() { + mAdapter.notifyDataSetChanged(); + } + + @Override + protected void notifyItemRangeRemoved(int positionStart, int itemCount) { + mAdapter.notifyItemRangeRemoved(positionStart, itemCount); + } + + @Override + protected void notifyItemRangeInserted(int positionStart, int itemCount) { + mAdapter.notifyItemRangeInserted(positionStart, itemCount); + } + @Override protected void getPageData(int taskId, int type, int page) { EhClient client = EhApplication.getEhClient(getStageActivity()); @@ -1129,78 +1163,41 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe int mode = mListUrlBuilder.getMode(); if (mode == ListUrlBuilder.MODE_POPULAR) { PopularListener listener = new PopularListener(taskId); - listener.setGalleryListHelper(this); - mListener = listener; EhRequest request = new EhRequest(); request.setMethod(mJpnTitle ? EhClient.METHOD_GET_POPULAR_JPN : EhClient.METHOD_GET_POPULAR); request.setEhListener(listener); request.setArgs(source); client.execute(request); + mEhRequest = request; } else { try { mListUrlBuilder.setPageIndex(page); String url = mListUrlBuilder.build(source); GalleryListListener listener = new GalleryListListener(taskId, type, page, source); - listener.setGalleryListHelper(this); - mListener = listener; EhRequest request = new EhRequest(); request.setMethod(mJpnTitle ? EhClient.METHOD_GET_GALLERY_LIST_JPN : EhClient.METHOD_GET_GALLERY_LIST); request.setEhListener(listener); request.setArgs(url, source); - client.execute(request); + mEhRequest = request; } catch (UnsupportedSearchException e) { onGetPageData(taskId, e); } } } - - @Override - public GalleryHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = mActivity.getLayoutInflater().inflate(R.layout.item_gallery_list_detail, parent, false); - return new GalleryHolder(view); - } - - @Override - public void onBindViewHolder(GalleryHolder holder, int position) { - GalleryInfo gi = getDataAt(position); - - Conaco conaco = EhApplication.getConaco(getStageActivity()); - holder.thumb.load(conaco, EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb); - String title; - if (mJpnTitle) { - title = TextUtils.isEmpty(gi.titleJpn) ? gi.title : gi.titleJpn; - } else { - title = gi.title; - } - holder.title.setText(title); - holder.uploader.setText(gi.uploader); - holder.rating.setRating(gi.rating); - TextView category = holder.category; - String newCategoryText = EhUtils.getCategory(gi.category); - if (!newCategoryText.equals(category.getText())) { - category.setText(newCategoryText); - category.setBackgroundColor(EhUtils.getCategoryColor(gi.category)); - } - holder.posted.setText(gi.posted); - holder.simpleLanguage.setText(gi.simpleLanguage); - } } - private static class GalleryListListener extends EhClient.EhListener - implements GalleryListHelperSettable { + private class GalleryListListener extends EhClient.EhListener { private int mTaskId; private int mTaskType; private int mTargetPage; private int mSource; - private GalleryListHelper mHelper; - public GalleryListListener(int taskId, int taskType, int targetPage, int source) { mTaskId = taskId; mTaskType = taskType; @@ -1208,88 +1205,64 @@ public final class GalleryListScene extends Scene implements ListSearchBar.Helpe mSource = source; } - @Override - public void setGalleryListHelper(GalleryListHelper helper) { - mHelper = helper; - } - @Override public void onSuccess(GalleryListParser.Result result) { - if (mHelper != null) { - if (mSource == EhUrl.SOURCE_LOFI) { - if (result.pages == GalleryListParser.Result.CURRENT_PAGE_IS_LAST) { - mHelper.setPageCount(mTargetPage); - } else if (mTaskType == ContentLayout.ContentHelper.TYPE_REFRESH) { - mHelper.setPageCount(Integer.MAX_VALUE); - } - } else { - mHelper.setPageCount(result.pages); - } - mHelper.onGetPageData(mTaskId, result.galleryInfos); + mEhRequest = null; - mHelper.clearLastGalleryListHelperSettable(this); + if (mSource == EhUrl.SOURCE_LOFI) { + if (result.pages == GalleryListParser.Result.CURRENT_PAGE_IS_LAST) { + mGalleryListHelper.setPageCount(mTargetPage); + } else if (mTaskType == ContentLayout.ContentHelper.TYPE_REFRESH) { + mGalleryListHelper.setPageCount(Integer.MAX_VALUE); + } + } else { + mGalleryListHelper.setPageCount(result.pages); } + mGalleryListHelper.onGetPageData(mTaskId, result.galleryInfos); } @Override public void onFailure(Exception e) { - if (mHelper != null) { - mHelper.onGetPageData(mTaskId, e); + mEhRequest = null; - mHelper.clearLastGalleryListHelperSettable(this); - } + mGalleryListHelper.onGetPageData(mTaskId, e); } @Override public void onCanceled() { - + mEhRequest = null; } } - private static class PopularListener extends EhClient.EhListener - implements GalleryListHelperSettable { + private class PopularListener extends EhClient.EhListener { private int mTaskId; - private GalleryListHelper mHelper; public PopularListener(int taskId) { mTaskId = taskId; } - @Override - public void setGalleryListHelper(GalleryListHelper helper) { - mHelper = helper; - } - @Override public void onSuccess(PopularParser.Result result) { - if (mHelper != null) { - mHelper.setPageCount(1); - mHelper.onGetPageData(mTaskId, result.galleryInfos); + mEhRequest = null; - mHelper.clearLastGalleryListHelperSettable(this); - } + mGalleryListHelper.setPageCount(1); + mGalleryListHelper.onGetPageData(mTaskId, result.galleryInfos); } @Override public void onFailure(Exception e) { - if (mHelper != null) { - mHelper.onGetPageData(mTaskId, e); + mEhRequest = null; - mHelper.clearLastGalleryListHelperSettable(this); - } + mGalleryListHelper.onGetPageData(mTaskId, e); } @Override public void onCanceled() { - + mEhRequest = null; } } - private interface GalleryListHelperSettable { - void setGalleryListHelper(GalleryListHelper helper); - } - private class SimpleHolder extends RecyclerView.ViewHolder { public SimpleHolder(View itemView) { diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/HistoryScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/HistoryScene.java index e3d6c3aa2..dbebab662 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/HistoryScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/HistoryScene.java @@ -47,6 +47,7 @@ import com.hippo.yorozuya.Messenger; import java.util.List; +// TODO Avoid get history in UI thread public class HistoryScene extends AppbarScene implements EasyRecyclerView.OnItemClickListener, Messenger.Receiver { @@ -133,8 +134,8 @@ public class HistoryScene extends AppbarScene implements EasyRecyclerView.OnItem } @Override - protected void onDetachFromeStage() { - super.onDetachFromeStage(); + protected void onDetachedFromeWindow() { + super.onDetachedFromeWindow(); if (mRecyclerViewSwipeManager != null) { mRecyclerViewSwipeManager.release(); diff --git a/app/src/main/java/com/hippo/ehviewer/widget/ContentLayout.java b/app/src/main/java/com/hippo/ehviewer/widget/ContentLayout.java index 1ceaca8c7..cd32be0b4 100644 --- a/app/src/main/java/com/hippo/ehviewer/widget/ContentLayout.java +++ b/app/src/main/java/com/hippo/ehviewer/widget/ContentLayout.java @@ -106,6 +106,10 @@ public class ContentLayout extends FrameLayout { return mRecyclerView; } + public void setAdapter(RecyclerView.Adapter adapter) { + mRecyclerView.setAdapter(adapter); + } + public void setHelper(ContentHelper helper) { mContentHelper = helper; helper.init(this); @@ -135,8 +139,7 @@ public class ContentLayout extends FrameLayout { super.onRestoreInstanceState(mContentHelper.onRestoreInstanceState(state)); } - public abstract static class ContentHelper - extends EasyRecyclerView.Adapter + public abstract static class ContentHelper implements RefreshLayout.OnHeaderRefreshListener, RefreshLayout.OnFooterRefreshListener, View.OnClickListener { @@ -163,8 +166,6 @@ public class ContentLayout extends FrameLayout { public static final int TYPE_NEXT_PAGE_KEEP_POS = 4; public static final int TYPE_SOMEWHERE = 5; - private Context mContext; - private ProgressView mProgressView; private ViewGroup mTipView; private RefreshLayout mRefreshLayout; @@ -253,29 +254,11 @@ public class ContentLayout extends FrameLayout { } }; - public ContentHelper(Context context) { - mContext = context; + public ContentHelper() { mData = new ArrayList<>(); mIdGenerator = new IdIntGenerator(); } - @SuppressWarnings("unchecked") - protected ContentHelper(Context context, ContentHelper older) { - this(context); - mData.addAll(older.mData); - - mFirstPage = older.mFirstPage; - mLastPage = older.mLastPage; - mFirstIndex = older.mFirstIndex; - mLastIndex = older.mLastIndex; - mPageCount = older.mPageCount; - mCurrentPage = older.mCurrentPage; - mPageVolume = older.mPageVolume; - mCurrentTaskId = older.mCurrentTaskId; - mCurrentTaskType = older.mCurrentTaskType; - mCurrentTaskPage = older.mCurrentTaskPage; - } - private void init(ContentLayout contentLayout) { mProgressView = contentLayout.mProgressView; mTipView = contentLayout.mTipView; @@ -289,7 +272,6 @@ public class ContentLayout extends FrameLayout { mViewTransition.showView(2, false); mRecyclerView.setLayoutManager(mLayoutManager); - mRecyclerView.setAdapter(this); mRecyclerView.addOnScrollListener(mOnScrollListener); mRefreshLayout.setOnHeaderRefreshListener(this); mRefreshLayout.setOnFooterRefreshListener(this); @@ -297,6 +279,8 @@ public class ContentLayout extends FrameLayout { mTipView.setOnClickListener(this); } + protected abstract Context getContext(); + protected abstract RecyclerView.LayoutManager generateLayoutManager(); protected abstract void onScrollToPosition(); @@ -305,6 +289,12 @@ public class ContentLayout extends FrameLayout { protected abstract void onShowText(); + protected abstract void notifyDataSetChanged(); + + protected abstract void notifyItemRangeRemoved(int positionStart, int itemCount); + + protected abstract void notifyItemRangeInserted(int positionStart, int itemCount); + public void showContent() { mViewTransition.showView(0); } @@ -390,7 +380,7 @@ public class ContentLayout extends FrameLayout { mLastIndex += pageVolume; mRecyclerView.stopScroll(); - LayoutManagerUtils.scrollToPositionProperly(mLayoutManager, mContext, mFirstIndex - 1, mOnScrollToPositionListener); + LayoutManagerUtils.scrollToPositionProperly(mLayoutManager, getContext(), mFirstIndex - 1, mOnScrollToPositionListener); } else { mCurrentPage = mFirstPage; mFirstIndex = 0; @@ -445,13 +435,13 @@ public class ContentLayout extends FrameLayout { mRefreshLayout.setHeaderRefreshing(false); mRefreshLayout.setFooterRefreshing(false); Say.d(TAG, "Get page data failed " + e.getClass().getName() + " " + e.getMessage()); - String readableError = ExceptionUtils.getReadableString(mContext, e); - String reason = ExceptionUtils.getReasonString(mContext, e); + String readableError = ExceptionUtils.getReadableString(getContext(), e); + String reason = ExceptionUtils.getReasonString(getContext(), e); if (reason != null) { readableError += '\n' + reason; } if (mViewTransition.getShownViewIndex() == 0) { - Toast.makeText(mContext, readableError, Toast.LENGTH_SHORT).show(); + Toast.makeText(getContext(), readableError, Toast.LENGTH_SHORT).show(); } else { showText(readableError); } @@ -515,7 +505,7 @@ public class ContentLayout extends FrameLayout { // Go to top mRecyclerView.stopScroll(); LayoutManagerUtils.scrollToPositionProperly(mLayoutManager, - mContext, 0, mOnScrollToPositionListener); + getContext(), 0, mOnScrollToPositionListener); // Show header refresh mRefreshLayout.setFooterRefreshing(false); mRefreshLayout.setHeaderRefreshing(true); @@ -591,8 +581,7 @@ public class ContentLayout extends FrameLayout { onScrollToPosition(); } - @Override - public int getItemCount() { + public int size() { return mData.size(); } diff --git a/app/src/main/java/com/hippo/scene/Scene.java b/app/src/main/java/com/hippo/scene/Scene.java index 59a31e156..2a67ca6e7 100644 --- a/app/src/main/java/com/hippo/scene/Scene.java +++ b/app/src/main/java/com/hippo/scene/Scene.java @@ -211,6 +211,7 @@ public abstract class Scene { // Not call setContentView in onCreate, create scene view by ourself mSceneView = createSceneView(getStageActivity()); } + mSceneView.setScene(this); mSceneView.setBackgroundColor(mBackgroundColor); @@ -429,17 +430,23 @@ public abstract class Scene { return getStageActivity().getStageLayout(); } - protected void onAttachToStage() { + protected void onAttachedToWindow() { } - protected void onDetachFromeStage() { + protected void onDetachedFromeWindow() { + } + + protected void onAttachedToStage() { + } + + protected void onDetachedFromeStage() { } // Add scene view to stage layout void attachToStage() { if (mSceneView != null && !isInStage()) { doAttachToStage(); - onAttachToStage(); + onAttachedToStage(); } } @@ -447,7 +454,7 @@ public abstract class Scene { void detachFromeStage() { if (mSceneView != null && isInStage()) { doDetachFromeStage(); - onDetachFromeStage(); + onDetachedFromeStage(); } } diff --git a/app/src/main/java/com/hippo/scene/SceneView.java b/app/src/main/java/com/hippo/scene/SceneView.java index d09a4ba23..170e986e7 100644 --- a/app/src/main/java/com/hippo/scene/SceneView.java +++ b/app/src/main/java/com/hippo/scene/SceneView.java @@ -35,6 +35,8 @@ public class SceneView extends AbsoluteLayout { private float mStartX = -1f; private float mStartY = -1f; + private Scene mScene; + public SceneView(Context context) { super(context); init(); @@ -55,6 +57,29 @@ public class SceneView extends AbsoluteLayout { mPath = new Path(); } + public void setScene(Scene scene) { + mScene = scene; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (mScene != null) { + mScene.onAttachedToWindow(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mScene != null) { + mScene.onDetachedFromeWindow(); + mScene = null; + } + } + public void setEnableTouch(boolean enableTouch) { mEnableTouch = enableTouch; } diff --git a/app/src/main/res/layout/scene_favorite.xml b/app/src/main/res/layout/scene_favorite.xml new file mode 100644 index 000000000..ccb7370b4 --- /dev/null +++ b/app/src/main/res/layout/scene_favorite.xml @@ -0,0 +1,38 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/widget_search_bar.xml b/app/src/main/res/layout/widget_search_bar.xml index 53b2c3dde..af51382b4 100644 --- a/app/src/main/res/layout/widget_search_bar.xml +++ b/app/src/main/res/layout/widget_search_bar.xml @@ -41,7 +41,6 @@ android:layout_marginLeft="56dp" android:layout_marginRight="56dp" android:gravity="center_vertical" - android:text="@string/app_name" android:textSize="@dimen/text_large" android:textColor="@color/secondary_text_light"/>