diff --git a/app/build.gradle b/app/build.gradle index 9e2c2ee4c..74ef05126 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,4 +77,5 @@ dependencies { compile 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.8.6' compile 'de.greenrobot:greendao:2.0.0' compile 'org.ccil.cowan.tagsoup:tagsoup:1.2.1' + compile 'org.jsoup:jsoup:1.8.3' } diff --git a/app/src/main/java/com/hippo/annotation/Implemented.java b/app/src/main/java/com/hippo/annotation/Implemented.java new file mode 100644 index 000000000..d71ecfd46 --- /dev/null +++ b/app/src/main/java/com/hippo/annotation/Implemented.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 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.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.METHOD}) +public @interface Implemented { + Class value(); +} diff --git a/app/src/main/java/com/hippo/ehviewer/Settings.java b/app/src/main/java/com/hippo/ehviewer/Settings.java index a94369a23..213ca2da7 100644 --- a/app/src/main/java/com/hippo/ehviewer/Settings.java +++ b/app/src/main/java/com/hippo/ehviewer/Settings.java @@ -24,9 +24,11 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.hippo.ehviewer.client.EhConfig; +import com.hippo.ehviewer.client.data.FavListUrlBuilder; import com.hippo.ehviewer.gallery.gl.GalleryView; import com.hippo.ehviewer.gallery.gl.ImageView; import com.hippo.unifile.UniFile; +import com.hippo.yorozuya.AssertUtils; import com.hippo.yorozuya.NumberUtils; public class Settings { @@ -117,17 +119,6 @@ public class Settings { putBoolean(KEY_SHOW_WARNING, value); } - private static final String KEY_RECENT_DOWNLOAD_LABEL = "recent_download_label"; - private static final String DEFAULT_RECENT_DOWNLOAD_LABEL = null; - - public static String getRecentDownloadLabel() { - return getString(KEY_RECENT_DOWNLOAD_LABEL, DEFAULT_RECENT_DOWNLOAD_LABEL); - } - - public static void putRecentDownloadLabel(String value) { - putString(KEY_RECENT_DOWNLOAD_LABEL, value); - } - private static final String KEY_REMOVE_IMAGE_FILES = "include_pic"; private static final boolean DEFAULT_REMOVE_IMAGE_FILES = true; @@ -236,6 +227,9 @@ public class Settings { putBoolean(KEY_VOLUME_PAGE, value); } + /******************** + ****** Download + ********************/ public static final String KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme"; public static final String KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority"; public static final String KEY_DOWNLOAD_SAVE_PATH = "image_path"; @@ -267,4 +261,81 @@ public class Settings { putString(KEY_DOWNLOAD_SAVE_QUERY, uri.getEncodedQuery()); putString(KEY_DOWNLOAD_SAVE_FRAGMENT, uri.getEncodedFragment()); } + + private static final String KEY_RECENT_DOWNLOAD_LABEL = "recent_download_label"; + private static final String DEFAULT_RECENT_DOWNLOAD_LABEL = null; + + public static String getRecentDownloadLabel() { + return getString(KEY_RECENT_DOWNLOAD_LABEL, DEFAULT_RECENT_DOWNLOAD_LABEL); + } + + public static void putRecentDownloadLabel(String value) { + putString(KEY_RECENT_DOWNLOAD_LABEL, value); + } + + /******************** + ****** Favorites + ********************/ + public static final String KEY_FAV_CAT_0 = "fav_cat_0"; + public static final String KEY_FAV_CAT_1 = "fav_cat_1"; + public static final String KEY_FAV_CAT_2 = "fav_cat_2"; + public static final String KEY_FAV_CAT_3 = "fav_cat_3"; + public static final String KEY_FAV_CAT_4 = "fav_cat_4"; + public static final String KEY_FAV_CAT_5 = "fav_cat_5"; + public static final String KEY_FAV_CAT_6 = "fav_cat_6"; + public static final String KEY_FAV_CAT_7 = "fav_cat_7"; + public static final String KEY_FAV_CAT_8 = "fav_cat_8"; + public static final String KEY_FAV_CAT_9 = "fav_cat_9"; + public static final String DEFAULT_FAV_CAT_0 = "Favorites 0"; + public static final String DEFAULT_FAV_CAT_1 = "Favorites 1"; + public static final String DEFAULT_FAV_CAT_2 = "Favorites 2"; + public static final String DEFAULT_FAV_CAT_3 = "Favorites 3"; + public static final String DEFAULT_FAV_CAT_4 = "Favorites 4"; + public static final String DEFAULT_FAV_CAT_5 = "Favorites 5"; + public static final String DEFAULT_FAV_CAT_6 = "Favorites 6"; + public static final String DEFAULT_FAV_CAT_7 = "Favorites 7"; + public static final String DEFAULT_FAV_CAT_8 = "Favorites 8"; + public static final String DEFAULT_FAV_CAT_9 = "Favorites 9"; + + public static String[] getFavCat() { + String[] favCat = new String[10]; + favCat[0] = sSettingsPre.getString(KEY_FAV_CAT_0, DEFAULT_FAV_CAT_0); + favCat[1] = sSettingsPre.getString(KEY_FAV_CAT_1, DEFAULT_FAV_CAT_1); + favCat[2] = sSettingsPre.getString(KEY_FAV_CAT_2, DEFAULT_FAV_CAT_2); + favCat[3] = sSettingsPre.getString(KEY_FAV_CAT_3, DEFAULT_FAV_CAT_3); + favCat[4] = sSettingsPre.getString(KEY_FAV_CAT_4, DEFAULT_FAV_CAT_4); + favCat[5] = sSettingsPre.getString(KEY_FAV_CAT_5, DEFAULT_FAV_CAT_5); + favCat[6] = sSettingsPre.getString(KEY_FAV_CAT_6, DEFAULT_FAV_CAT_6); + favCat[7] = sSettingsPre.getString(KEY_FAV_CAT_7, DEFAULT_FAV_CAT_7); + favCat[8] = sSettingsPre.getString(KEY_FAV_CAT_8, DEFAULT_FAV_CAT_8); + favCat[9] = sSettingsPre.getString(KEY_FAV_CAT_9, DEFAULT_FAV_CAT_9); + return favCat; + } + + public static void putFavCat(String[] value) { + AssertUtils.assertEquals(10, value.length); + sSettingsPre.edit() + .putString(KEY_FAV_CAT_0, value[0]) + .putString(KEY_FAV_CAT_1, value[1]) + .putString(KEY_FAV_CAT_2, value[2]) + .putString(KEY_FAV_CAT_3, value[3]) + .putString(KEY_FAV_CAT_4, value[4]) + .putString(KEY_FAV_CAT_5, value[5]) + .putString(KEY_FAV_CAT_6, value[6]) + .putString(KEY_FAV_CAT_7, value[7]) + .putString(KEY_FAV_CAT_8, value[8]) + .putString(KEY_FAV_CAT_9, value[9]) + .apply(); + } + + private static final String KEY_RECENT_FAV_CAT = "recent_fav_cat"; + private static final int DEFAULT_RECENT_FAV_CAT = FavListUrlBuilder.FAV_CAT_ALL; + + public static int getRecentFavCat() { + return getInt(KEY_RECENT_FAV_CAT, DEFAULT_RECENT_FAV_CAT); + } + + public static void putRecentFavCat(int value) { + putInt(KEY_RECENT_FAV_CAT, value); + } } 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 31b826f1a..e9652818a 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -22,6 +22,7 @@ import android.os.AsyncTask; import com.hippo.ehviewer.EhApplication; import com.hippo.ehviewer.client.data.GalleryInfo; import com.hippo.ehviewer.client.exception.CancelledException; +import com.hippo.yorozuya.IntList; import com.hippo.yorozuya.PriorityThreadFactory; import com.hippo.yorozuya.SimpleHandler; @@ -49,6 +50,8 @@ public class EhClient { public static final int METHOD_GET_RATE_GALLERY = 5; public static final int METHOD_GET_COMMENT_GALLERY = 6; public static final int METHOD_GET_GALLERY_TOKEN = 7; + public static final int METHOD_GET_FAVORITES = 8; + public static final int METHOD_MODIFY_FAVORITES = 9; private final ThreadPoolExecutor mRequestThreadPool; private final OkHttpClient mOkHttpClient; @@ -156,6 +159,10 @@ public class EhClient { return EhEngine.commentGallery(this, mOkHttpClient, (String) params[0], (String) params[1]); case METHOD_GET_GALLERY_TOKEN: return EhEngine.getGalleryToken(this, mOkHttpClient, (Integer) params[0], (String) params[1], (Integer) params[2]); + case METHOD_GET_FAVORITES: + return EhEngine.getFavorites(this, mOkHttpClient, (String) params[0], (Boolean) params[1]); + case METHOD_MODIFY_FAVORITES: + return EhEngine.modifyFavorites(this, mOkHttpClient, (String) params[0], (IntList) params[1], (Integer) params[2], (Boolean) params[3]); default: return new IllegalStateException("Can't detect method " + mMethod); } 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 e72d71fd2..5646f45a0 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -20,6 +20,8 @@ import android.support.annotation.Nullable; import android.util.Log; import android.util.Pair; +import com.hippo.ehviewer.GetText; +import com.hippo.ehviewer.R; import com.hippo.ehviewer.client.data.GalleryComment; import com.hippo.ehviewer.client.data.GalleryDetail; import com.hippo.ehviewer.client.data.GalleryInfo; @@ -27,6 +29,7 @@ import com.hippo.ehviewer.client.data.LargePreviewSet; import com.hippo.ehviewer.client.exception.CancelledException; import com.hippo.ehviewer.client.exception.EhException; import com.hippo.ehviewer.client.exception.ParseException; +import com.hippo.ehviewer.client.parser.FavoritesParser; import com.hippo.ehviewer.client.parser.GalleryApiParser; import com.hippo.ehviewer.client.parser.GalleryDetailParser; import com.hippo.ehviewer.client.parser.GalleryListParser; @@ -34,6 +37,7 @@ import com.hippo.ehviewer.client.parser.GalleryTokenApiParser; import com.hippo.ehviewer.client.parser.RateGalleryParser; import com.hippo.ehviewer.client.parser.SignInParser; import com.hippo.network.StatusCodeException; +import com.hippo.yorozuya.IntList; import org.json.JSONArray; import org.json.JSONObject; @@ -76,8 +80,12 @@ public class EhEngine { throw new EhException("Sad Panda"); } - if (e instanceof ParseException && body != null && !body.contains("<")) { - throw new EhException(body); + if (e instanceof ParseException) { + if (body != null && !body.contains("<")){ + throw new EhException(body); + } else { + throw new EhException(GetText.getString(R.string.error_parse_error)); + } } if (code >= 400) { @@ -349,4 +357,83 @@ public class EhEngine { throw e; } } + + public static FavoritesParser.Result getFavorites(EhClient.Task task, OkHttpClient okHttpClient, + String url, boolean callApi) throws Exception { + Log.d(TAG, url); + Request request = new EhRequestBuilder(url, task.getEhConfig()).build(); + Call call = okHttpClient.newCall(request); + + // Put call + task.setCall(call); + + String body = null; + Headers headers = null; + FavoritesParser.Result result; + int code = -1; + try { + Response response = call.execute(); + code = response.code(); + headers = response.headers(); + body = response.body().string(); + result = FavoritesParser.parse(body); + } catch (Exception e) { + throwException(call, code, headers, body, e); + throw e; + } + + if (callApi && result.galleryInfoList.size() > 0) { + fillGalleryListByApi(task, okHttpClient, result.galleryInfoList); + } + + return result; + } + + public static FavoritesParser.Result modifyFavorites(EhClient.Task task, OkHttpClient okHttpClient, + String url, IntList gidList, int dstCat, boolean callApi) throws Exception { + String catStr; + if (dstCat == -1) { + catStr = "delete"; + } else if (dstCat >= 0 && dstCat <= 9) { + catStr = "fav" + dstCat; + } else { + Log.w(TAG, "Invalid dstCat: " + dstCat); + return null; + } + FormBody.Builder builder = new FormBody.Builder(); + builder.add("ddact", catStr); + for (int i = 0, n = gidList.size(); i < n; i++) { + builder.add("modifygids[]", Integer.toString(gidList.get(i))); + } + builder.add("apply", "Apply"); + Log.d(TAG, url); + Request request = new EhRequestBuilder(url, task.getEhConfig()) + .post(builder.build()) + .build(); + Call call = okHttpClient.newCall(request); + + // Put call + task.setCall(call); + + String body = null; + Headers headers = null; + FavoritesParser.Result result; + int code = -1; + try { + Response response = call.execute(); + code = response.code(); + headers = response.headers(); + body = response.body().string(); + result = FavoritesParser.parse(body); + } catch (Exception e) { + throwException(call, code, headers, body, e); + throw e; + } + + if (callApi && result.galleryInfoList.size() > 0) { + fillGalleryListByApi(task, okHttpClient, result.galleryInfoList); + } + + return result; + } } 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 436c98c38..31b21cf99 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhUrl.java @@ -32,6 +32,8 @@ public class EhUrl { public static final String API_EX = HOST_EX + "api.php"; + public static final String URL_FAVORITES = HOST_EX + "favorites.php"; + public static String getGalleryDetailUrl(int gid, String token) { return getGalleryDetailUrl(gid, token, 0, false); } diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/FavListUrlBuilder.java b/app/src/main/java/com/hippo/ehviewer/client/data/FavListUrlBuilder.java new file mode 100644 index 000000000..5046ed3ad --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/data/FavListUrlBuilder.java @@ -0,0 +1,115 @@ +/* + * Copyright 2016 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.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; + +import com.hippo.ehviewer.client.EhUrl; +import com.hippo.network.UrlBuilder; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +public class FavListUrlBuilder implements Parcelable { + + private static final String TAG = FavListUrlBuilder.class.getSimpleName(); + public static final int FAV_CAT_ALL = -1; + + private int mIndex; + private String mKeyword; + private int mFavCat = FAV_CAT_ALL; + + public void setIndex(int index) { + mIndex = index; + } + + public void setKeyword(String keyword) { + mKeyword = keyword; + } + + public void setFavCat(int favCat) { + mFavCat = favCat; + } + + public String getKeyword() { + return mKeyword; + } + + public int getFavCat() { + return mFavCat; + } + + public static boolean isValidFavCat(int favCat) { + return favCat >= 0 && favCat <= 9; + } + + public String build() { + UrlBuilder ub = new UrlBuilder(EhUrl.URL_FAVORITES); + if (isValidFavCat(mFavCat)) { + ub.addQuery("favcat", Integer.toString(mFavCat)); + } + if (!TextUtils.isEmpty(mKeyword)) { + try { + ub.addQuery("f_search", URLEncoder.encode(mKeyword, "UTF-8")); + ub.addQuery("f_apply", "Search+Favorites"); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Can't URLEncoder.encode " + mKeyword); + } + } + if (mIndex > 0) { + ub.addQuery("page", Integer.toString(mIndex)); + } + return ub.build(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.mIndex); + dest.writeString(this.mKeyword); + dest.writeInt(this.mFavCat); + } + + public FavListUrlBuilder() { + } + + protected FavListUrlBuilder(Parcel in) { + this.mIndex = in.readInt(); + this.mKeyword = in.readString(); + this.mFavCat = in.readInt(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public FavListUrlBuilder createFromParcel(Parcel source) { + return new FavListUrlBuilder(source); + } + + @Override + public FavListUrlBuilder[] newArray(int size) { + return new FavListUrlBuilder[size]; + } + }; +} diff --git a/app/src/main/java/com/hippo/ehviewer/client/parser/FavoritesParser.java b/app/src/main/java/com/hippo/ehviewer/client/parser/FavoritesParser.java new file mode 100644 index 000000000..e68085343 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/parser/FavoritesParser.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016 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.parser; + +import com.hippo.ehviewer.client.data.GalleryInfo; +import com.hippo.ehviewer.client.exception.ParseException; +import com.hippo.util.JsoupUtils; +import com.hippo.yorozuya.AssertUtils; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FavoritesParser { + + private static final Pattern LIMIT_PATTERN = Pattern.compile("Currently Used Favorite Slots: ([\\d,]+) / ([\\d,]+)"); + + public static class Result { + public String[] catArray; // Size 10 + public int[] countArray; // Size 10 + public int current; // -1 for error + public int limit; // -1 for error + public int pages; + public List galleryInfoList; + } + + @SuppressWarnings("ConstantConditions") + public static Result parse(String body) throws Exception { + String[] catArray = new String[10]; + int[] countArray = new int[10]; + int current; + int limit; + + try { + Document d = Jsoup.parse(body); + Element ido = JsoupUtils.getElementByClass(d, "ido"); + Elements fps = ido.getElementsByClass("fp"); + // Last one is "fp fps" + AssertUtils.assertEqualsEx(11, fps.size()); + + for (int i = 0; i < 10; i++) { + Element fp = fps.get(i); + countArray[i] = ParserUtils.parseInt(fp.child(0).text()); + catArray[i] = ParserUtils.trim(fp.child(2).text()); + } + + Matcher m = LIMIT_PATTERN.matcher(body); + if (m.find()) { + current = ParserUtils.parseInt(m.group(1)); + limit = ParserUtils.parseInt(m.group(2)); + } else { + current = -1; + limit = -1; + } + } catch (Exception e) { + e.printStackTrace(); + throw new ParseException("Parse favorites error", body); + } + + GalleryListParser.Result result = GalleryListParser.parse(body); + + Result re = new Result(); + re.catArray = catArray; + re.countArray = countArray; + re.current = current; + re.limit = limit; + re.pages = result.pages; + re.galleryInfoList = result.galleryInfos; + + return re; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java index fcf519f0e..7c4ff4ac4 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java @@ -38,6 +38,7 @@ import com.hippo.ehviewer.client.EhUrlOpener; import com.hippo.ehviewer.client.EhUtils; import com.hippo.ehviewer.ui.scene.BaseScene; import com.hippo.ehviewer.ui.scene.DownloadScene; +import com.hippo.ehviewer.ui.scene.FavoritesScene; import com.hippo.ehviewer.ui.scene.GalleryListScene; import com.hippo.ehviewer.ui.scene.LoginScene; import com.hippo.ehviewer.ui.scene.WarningScene; @@ -272,7 +273,7 @@ public final class MainActivity extends StageActivity } else if (id == R.id.nav_whats_hot) { } else if (id == R.id.nav_favourite) { - + startScene(new Announcer(FavoritesScene.class)); } else if (id == R.id.nav_history) { } else if (id == R.id.nav_download) { diff --git a/app/src/main/java/com/hippo/ehviewer/ui/annotation/DrawerLifeCircle.java b/app/src/main/java/com/hippo/ehviewer/ui/annotation/DrawerLifeCircle.java new file mode 100644 index 000000000..0c6f7568c --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/annotation/DrawerLifeCircle.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 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.annotation; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Created in {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}, + * destroyed in {@link Fragment#onDestroyView()}. + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.FIELD}) +public @interface DrawerLifeCircle { +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/annotation/ViewLifeCircle.java b/app/src/main/java/com/hippo/ehviewer/ui/annotation/ViewLifeCircle.java new file mode 100644 index 000000000..4120fbd1b --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/annotation/ViewLifeCircle.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 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.annotation; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Created in {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}, + * destroyed in {@link Fragment#onDestroyView()}. + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.FIELD}) +public @interface ViewLifeCircle { +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/annotation/WholeLifeCircle.java b/app/src/main/java/com/hippo/ehviewer/ui/annotation/WholeLifeCircle.java new file mode 100644 index 000000000..efebf6151 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/annotation/WholeLifeCircle.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 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.annotation; + +import android.app.Activity; +import android.app.Fragment; +import android.os.Bundle; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Created in {@link Fragment#onCreate(Bundle)} or {@link Activity#onCreate(Bundle)}, + * destroyed in {@link Fragment#onDestroy()} or {@link Activity#onDestroy()}. + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.FIELD}) +public @interface WholeLifeCircle { +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadLabelScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadLabelScene.java index 6eb101112..d01c66c08 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadLabelScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadLabelScene.java @@ -51,7 +51,6 @@ import com.hippo.ehviewer.EhApplication; import com.hippo.ehviewer.R; import com.hippo.ehviewer.dao.DownloadLabelRaw; import com.hippo.util.ActivityHelper; -import com.hippo.util.DrawableManager; import com.hippo.view.ViewTransition; import com.hippo.yorozuya.ViewUtils; @@ -140,7 +139,7 @@ public class DownloadLabelScene extends ToolbarScene { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setTitle(R.string.label); - setNavigationIcon(DrawableManager.getDrawable(getContext(), R.drawable.v_arrow_left_dark_x24)); + setNavigationIcon(R.drawable.v_arrow_left_dark_x24); // Clear nav checked item setNavCheckedItem(0); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadScene.java index 92e264986..42c86287b 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadScene.java @@ -69,7 +69,6 @@ import com.hippo.scene.TransitionHelper; import com.hippo.unifile.UniFile; import com.hippo.util.ActivityHelper; import com.hippo.util.ApiHelper; -import com.hippo.util.DrawableManager; import com.hippo.view.ViewTransition; import com.hippo.widget.FabLayout; import com.hippo.widget.LoadImageView; @@ -226,7 +225,7 @@ public class DownloadScene extends ToolbarScene public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); updateTitle(); - setNavigationIcon(DrawableManager.getDrawable(getContext(), R.drawable.v_arrow_left_dark_x24)); + setNavigationIcon(R.drawable.v_arrow_left_dark_x24); // Clear nav checked item setNavCheckedItem(R.id.nav_download); @@ -286,7 +285,7 @@ public class DownloadScene extends ToolbarScene @Override public View onCreateDrawerView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.drawer_download, container, false); + View view = inflater.inflate(R.layout.drawer_list, container, false); Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar); toolbar.setTitle(R.string.drawer_download_label_title); @@ -883,7 +882,7 @@ public class DownloadScene extends ToolbarScene @Override public boolean onTransition(Context context, FragmentTransaction transaction, Fragment exit, Fragment enter) { - if (!(enter instanceof GalleryDetailScene)) { + if (mHolder == null || !(enter instanceof GalleryDetailScene)) { return false; } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/EhCallback.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/EhCallback.java index 6a0488c0a..0a3c23225 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/EhCallback.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/EhCallback.java @@ -25,9 +25,9 @@ import com.hippo.scene.StageActivity; public abstract class EhCallback implements EhClient.Callback { - private EhApplication mApplication; - private int mStageId; - private String mSceneTag; + private final EhApplication mApplication; + private final int mStageId; + private final String mSceneTag; public EhCallback(Context context, int stageId, String sceneTag) { mApplication = (EhApplication) context.getApplicationContext(); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.java new file mode 100644 index 000000000..4d01247a7 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.java @@ -0,0 +1,916 @@ +/* + * Copyright 2016 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.annotation.TargetApi; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.style.ImageSpan; +import android.transition.TransitionInflater; +import android.util.SparseBooleanArray; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.hippo.annotation.Implemented; +import com.hippo.drawable.DrawerArrowDrawable; +import com.hippo.easyrecyclerview.EasyRecyclerView; +import com.hippo.easyrecyclerview.FastScroller; +import com.hippo.ehviewer.EhApplication; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.Settings; +import com.hippo.ehviewer.client.EhCacheKeyFactory; +import com.hippo.ehviewer.client.EhClient; +import com.hippo.ehviewer.client.EhRequest; +import com.hippo.ehviewer.client.EhUtils; +import com.hippo.ehviewer.client.data.FavListUrlBuilder; +import com.hippo.ehviewer.client.data.GalleryInfo; +import com.hippo.ehviewer.client.parser.FavoritesParser; +import com.hippo.ehviewer.ui.annotation.DrawerLifeCircle; +import com.hippo.ehviewer.ui.annotation.ViewLifeCircle; +import com.hippo.ehviewer.ui.annotation.WholeLifeCircle; +import com.hippo.ehviewer.widget.SearchBar; +import com.hippo.ehviewer.widget.SimpleRatingView; +import com.hippo.rippleold.RippleSalon; +import com.hippo.scene.Announcer; +import com.hippo.scene.SceneFragment; +import com.hippo.scene.StageActivity; +import com.hippo.scene.TransitionHelper; +import com.hippo.util.ApiHelper; +import com.hippo.util.DrawableManager; +import com.hippo.widget.ContentLayout; +import com.hippo.widget.FabLayout; +import com.hippo.widget.LoadImageView; +import com.hippo.widget.SearchBarMover; +import com.hippo.yorozuya.AssertUtils; +import com.hippo.yorozuya.IntList; +import com.hippo.yorozuya.ObjectUtils; +import com.hippo.yorozuya.SimpleHandler; + +import java.util.ArrayList; +import java.util.List; + +public class FavoritesScene extends BaseScene implements + EasyRecyclerView.OnItemClickListener, EasyRecyclerView.OnItemLongClickListener, + FastScroller.OnDragHandlerListener, SearchBarMover.Helper, SearchBar.Helper, + FabLayout.OnClickFabListener, EasyRecyclerView.CustomChoiceListener, AdapterView.OnItemClickListener { + + private static final long ANIMATE_TIME = 300L; + + private static final String KEY_URL_BUILDER = "url_builder"; + + @Nullable + @ViewLifeCircle + private EasyRecyclerView mRecyclerView; + @Nullable + @ViewLifeCircle + private SearchBar mSearchBar; + @Nullable + @ViewLifeCircle + private FabLayout mFabLayout; + + @Nullable + @ViewLifeCircle + private FavoriteAdapter mAdapter; + @Nullable + @ViewLifeCircle + private FavoriteHelper mHelper; + @Nullable + @ViewLifeCircle + private SearchBarMover mSearchBarMover; + @Nullable + @ViewLifeCircle + private DrawerArrowDrawable mLeftDrawable; + + @Nullable + @DrawerLifeCircle + private ArrayAdapter mDrawerAdapter; + @Nullable + @DrawerLifeCircle + private List mDrawerList; + + @Nullable + @WholeLifeCircle + private EhClient mClient; + @Nullable + @WholeLifeCircle + private String[] mFavCat; + @Nullable + @WholeLifeCircle + private FavListUrlBuilder mUrlBuilder; + + private boolean mHasFirstRefresh; + private boolean mSearchMode; + // Avoid unnecessary search bar update + private String mOldFavCat; + // Avoid unnecessary search bar update + private String mOldKeyword; + // For modify action + private boolean mEnableModify; + // For modify action + private int mModifyFavCat; + // For modify action + private final IntList mModifyGidList = new IntList(); + + private final Runnable mReturnSearchBarRunnabel = new Runnable() { + @Override + public void run() { + if (mSearchBarMover != null) { + mSearchBarMover.returnSearchBarPosition(); + } + } + }; + + // TODO Only single instance + @Override + public int getLaunchMode() { + return LAUNCH_MODE_SINGLE_TOP; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mClient = EhApplication.getEhClient(getContext()); + mFavCat = Settings.getFavCat(); + + if (savedInstanceState == null) { + onInit(); + } else { + onRestore(savedInstanceState); + } + } + + private void onInit() { + mUrlBuilder = new FavListUrlBuilder(); + mUrlBuilder.setFavCat(Settings.getRecentFavCat()); + } + + private void onRestore(Bundle savedInstanceState) { + mUrlBuilder = savedInstanceState.getParcelable(KEY_URL_BUILDER); + if (mUrlBuilder == null) { + mUrlBuilder = new FavListUrlBuilder(); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(KEY_URL_BUILDER, mUrlBuilder); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + mClient = null; + mFavCat = null; + mUrlBuilder = null; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + Resources resources = getResources(); + + View view = inflater.inflate(R.layout.scene_favorite, container, false); + ContentLayout contentLayout = (ContentLayout) view.findViewById(R.id.content_layout); + mRecyclerView = contentLayout.getRecyclerView(); + AssertUtils.assertNotNull(mRecyclerView); + mSearchBar = (SearchBar) view.findViewById(R.id.search_bar); + AssertUtils.assertNotNull(mSearchBar); + mFabLayout = (FabLayout) view.findViewById(R.id.fab_layout); + AssertUtils.assertNotNull(mFabLayout); + + mHelper = new FavoriteHelper(); + mHelper.setEmptyString(getString(R.string.gallery_list_empty_hit)); // TODO wrong string + contentLayout.setHelper(mHelper); + contentLayout.getFastScroller().setOnDragHandlerListener(this); + + mAdapter = new FavoriteAdapter(); + mRecyclerView.setAdapter(mAdapter); + mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + mRecyclerView.setSelector(RippleSalon.generateRippleDrawable(false)); + mRecyclerView.setDrawSelectorOnTop(true); + mRecyclerView.hasFixedSize(); + mRecyclerView.setClipToPadding(false); + mRecyclerView.setOnItemClickListener(this); + mRecyclerView.setOnItemLongClickListener(this); + mRecyclerView.setChoiceMode(EasyRecyclerView.CHOICE_MODE_MULTIPLE_CUSTOM); + mRecyclerView.setCustomCheckedListener(this); + int paddingH = resources.getDimensionPixelOffset(R.dimen.list_content_margin_h); + int paddingV = resources.getDimensionPixelOffset(R.dimen.list_content_margin_v); + mRecyclerView.setPadding(paddingV, paddingH, paddingV, paddingH); + + mLeftDrawable = new DrawerArrowDrawable(getContext()); + mSearchBar.setLeftDrawable(mLeftDrawable); + mSearchBar.setRightDrawable(DrawableManager.getDrawable(getContext(), R.drawable.v_magnify_x24)); + mSearchBar.setHelper(this); + mSearchBar.setAllowEmptySearch(false); + updateSearchBar(); + mSearchBarMover = new SearchBarMover(this, mSearchBar, mRecyclerView); + + mFabLayout.setExpanded(false, false); + mFabLayout.setAutoCancel(false); + mFabLayout.setHidePrimaryFab(true); + mFabLayout.setOnClickFabListener(this); + + // Only refresh for the first time + if (!mHasFirstRefresh) { + mHasFirstRefresh = true; + mHelper.firstRefresh(); + } + + return view; + } + + // keyword of mUrlBuilder, fav cat of mUrlBuilder, mFavCat. + // They changed, call it + private void updateSearchBar() { + if (mUrlBuilder == null || mSearchBar == null || mFavCat == null) { + return; + } + + // Update title + int favCat = mUrlBuilder.getFavCat(); + String favCatName; + if (favCat >= 0 && favCat < 10) { + favCatName = mFavCat[favCat]; + } else { + favCatName = getString(R.string.all_favorites); + } + String keyword = mUrlBuilder.getKeyword(); + if (TextUtils.isEmpty(keyword)) { + if (!ObjectUtils.equal(favCatName, mOldFavCat)) { + mSearchBar.setTitle(getString(R.string.favorites_title, favCatName)); + } + } else { + if (!ObjectUtils.equal(favCatName, mOldFavCat) || !ObjectUtils.equal(keyword, mOldKeyword)) { + mSearchBar.setTitle(getString(R.string.favorites_title_2, favCatName, keyword)); + } + } + + // Update hint + if (!ObjectUtils.equal(favCatName, mOldFavCat)) { + Drawable searchImage = DrawableManager.getDrawable(getContext(), R.drawable.v_magnify_x24); + SpannableStringBuilder ssb = new SpannableStringBuilder(" "); + ssb.append(getString(R.string.favorites_search_bar_hint, favCatName)); + int textSize = (int) (mSearchBar.getEditTextTextSize() * 1.25); + if (searchImage != null) { + searchImage.setBounds(0, 0, textSize, textSize); + ssb.setSpan(new ImageSpan(searchImage), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + mSearchBar.setEditTextHint(ssb); + } + + mOldFavCat = favCatName; + mOldKeyword = keyword; + + // Save recent fav cat + Settings.putRecentFavCat(mUrlBuilder.getFavCat()); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setNavCheckedItem(R.id.nav_whats_hot); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + mRecyclerView = null; + mSearchBar = null; + mFabLayout = null; + + mAdapter = null; + mHelper = null; + mSearchBarMover = null; + mLeftDrawable = null; + + mOldFavCat = null; + mOldKeyword = null; + } + + + @Override + public View onCreateDrawerView(LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.drawer_list, container, false); + ListView listView = (ListView) view.findViewById(R.id.list_view); + + mDrawerList = new ArrayList<>(11); + mDrawerList.add(getString(R.string.all_favorites)); + if (mFavCat != null) { + for (String favCat: mFavCat) { + mDrawerList.add(favCat); + } + } + mDrawerAdapter = new ArrayAdapter<>(getContext(), R.layout.item_simple_list, mDrawerList); + listView.setAdapter(mDrawerAdapter); + listView.setOnItemClickListener(this); + + return view; + } + + @Override + public void onDestroyDrawerView() { + super.onDestroyDrawerView(); + + mDrawerAdapter = null; + mDrawerList = null; + } + + @Override + public void onBackPressed() { + if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) { + mRecyclerView.outOfCustomChoiceMode(); + } else if (mSearchMode) { + exitSearchMode(true); + } else { + finish(); + } + } + + @Override + @Implemented(FastScroller.OnDragHandlerListener.class) + public void onStartDragHandler() { + } + + @Override + @Implemented(FastScroller.OnDragHandlerListener.class) + public void onEndDragHandler() { + if (mSearchBarMover != null) { + mSearchBarMover.returnSearchBarPosition(); + } + } + + @Override + @Implemented(EasyRecyclerView.OnItemClickListener.class) + public boolean onItemClick(EasyRecyclerView parent, View view, int position, long id) { + if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) { + mRecyclerView.toggleItemChecked(position); + } else if (mHelper != null) { + GalleryInfo gi = mHelper.getDataAt(position); + Bundle args = new Bundle(); + args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO); + args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, gi); + Announcer announcer = new Announcer(GalleryDetailScene.class).setArgs(args); + if (ApiHelper.SUPPORT_TRANSITION) { + FavoriteHolder holder = (FavoriteHolder) mRecyclerView.getChildViewHolder(view); + announcer.setTranHelper(new EnterGalleryDetailTransaction(holder)); + } + startScene(announcer); + } + return true; + } + + @Override + @Implemented(EasyRecyclerView.OnItemLongClickListener.class) + public boolean onItemLongClick(EasyRecyclerView parent, View view, int position, long id) { + // Can not into + if (mRecyclerView != null && !mSearchMode) { + if (!mRecyclerView.isInCustomChoice()) { + mRecyclerView.intoCustomChoiceMode(); + } + mRecyclerView.toggleItemChecked(position); + } + return true; + } + + @Override + @Implemented(SearchBarMover.Helper.class) + public boolean isValidView(RecyclerView recyclerView) { + return recyclerView == mRecyclerView; + } + + @Override + @Implemented(SearchBarMover.Helper.class) + public RecyclerView getValidRecyclerView() { + return mRecyclerView; + } + + @Override + @Implemented(SearchBarMover.Helper.class) + public boolean forceShowSearchBar() { + return false; + } + + @Override + @Implemented(SearchBar.Helper.class) + public void onClickTitle() { + // Skip if in search mode + if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) { + return; + } + + if (!mSearchMode) { + enterSearchMode(true); + } + } + + @Override + @Implemented(SearchBar.Helper.class) + public void onClickLeftIcon() { + // Skip if in search mode + if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) { + return; + } + + if (mSearchMode) { + exitSearchMode(true); + } else { + toggleDrawer(Gravity.LEFT); + } + } + + @Override + @Implemented(SearchBar.Helper.class) + public void onClickRightIcon() { + // Skip if in search mode + if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) { + return; + } + + if (!mSearchMode) { + enterSearchMode(true); + } else { + if (mSearchBar != null) { + mSearchBar.applySearch(); + } + } + } + + @Override + @Implemented(SearchBar.Helper.class) + public void onSearchEditTextClick() { + } + + @Override + @Implemented(SearchBar.Helper.class) + public void onApplySearch(String query) { + // Skip if in search mode + if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) { + return; + } + + if (mUrlBuilder == null || mHelper == null) { + return; + } + + // Ensure outOfCustomChoiceMode to avoid error + if (mRecyclerView != null) { + mRecyclerView.isInCustomChoice(); + } + + exitSearchMode(true); + + mUrlBuilder.setKeyword(query); + updateSearchBar(); + mHelper.refresh(); + } + + @Override + @Implemented(SearchBar.Helper.class) + public void onSearchEditTextBackPressed() { + onBackPressed(); + } + + @Override + @Implemented(FabLayout.OnClickFabListener.class) + public void onClickPrimaryFab(FabLayout view, FloatingActionButton fab) { + if (mRecyclerView != null) { + mRecyclerView.outOfCustomChoiceMode(); + } + } + + @Override + @Implemented(FabLayout.OnClickFabListener.class) + public void onClickSecondaryFab(FabLayout view, FloatingActionButton fab, int position) { + if (mRecyclerView == null || mHelper == null || !mRecyclerView.isInCustomChoice()) { + return; + } + + mModifyGidList.clear(); + SparseBooleanArray stateArray = mRecyclerView.getCheckedItemPositions(); + for (int i = 0, n = stateArray.size(); i < n; i++) { + if (stateArray.valueAt(i)) { + mModifyGidList.add(mHelper.getDataAt(stateArray.keyAt(i)).gid); + } + } + + switch (position) { + case 0: { // Delete + new AlertDialog.Builder(getContext()) + .setTitle(R.string.delete_favorites_dialog_title) + .setMessage(getString(R.string.delete_favorites_dialog_message, mModifyGidList.size())) + .setPositiveButton(android.R.string.ok, new DeleteDialogHelper()) + .show(); + break; + } + case 1: { // Move + new AlertDialog.Builder(getContext()) + .setTitle(R.string.move_favorites_dialog_title) + .setItems(Settings.getFavCat(), new MoveDialogHelper()) + .show(); + break; + } + } + } + + @Override + @Implemented(EasyRecyclerView.CustomChoiceListener.class) + public void onIntoCustomChoice(EasyRecyclerView view) { + if (mFabLayout != null) { + mFabLayout.setExpanded(true); + } + if (mHelper != null) { + mHelper.setRefreshLayoutEnable(false); + } + // Lock drawer + setDrawerLayoutEnable(false); + } + + @Override + @Implemented(EasyRecyclerView.CustomChoiceListener.class) + public void onOutOfCustomChoice(EasyRecyclerView view) { + if (mFabLayout != null) { + mFabLayout.setExpanded(false); + } + if (mHelper != null) { + mHelper.setRefreshLayoutEnable(true); + } + // Unlock drawer + setDrawerLayoutEnable(true); + } + + @Override + @Implemented(EasyRecyclerView.CustomChoiceListener.class) + public void onItemCheckedStateChanged(EasyRecyclerView view, int position, long id, boolean checked) { + if (view.getCheckedItemCount() == 0) { + view.outOfCustomChoiceMode(); + } + } + + @Override + @Implemented(AdapterView.OnItemClickListener.class) + public void onItemClick(AdapterView parent, View view, int position, long id) { + // Skip if in search mode + if (mRecyclerView != null && mRecyclerView.isInCustomChoice()) { + return; + } + + if (mUrlBuilder == null || mHelper == null) { + return; + } + + // All favorite position is 0, so position - 1 is OK + int newFavCat = position - 1; + + // Check is the same + if (mUrlBuilder.getFavCat() == newFavCat || + (!FavListUrlBuilder.isValidFavCat(mUrlBuilder.getFavCat()) && + !FavListUrlBuilder.isValidFavCat(newFavCat))) { + return; + } + + // Ensure outOfCustomChoiceMode to avoid error + if (mRecyclerView != null) { + mRecyclerView.isInCustomChoice(); + } + + exitSearchMode(true); + + mUrlBuilder.setKeyword(null); + mUrlBuilder.setFavCat(newFavCat); + updateSearchBar(); + mHelper.refresh(); + + closeDrawer(Gravity.RIGHT); + } + + private void enterSearchMode(boolean animation) { + if (mSearchMode ||mSearchBar == null || mSearchBarMover == null || mLeftDrawable == null) { + return; + } + mSearchMode = true; + mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); + mSearchBarMover.returnSearchBarPosition(animation); + mLeftDrawable.setArrow(ANIMATE_TIME); + } + + private void exitSearchMode(boolean animation) { + if (!mSearchMode || mSearchBar == null || mSearchBarMover == null || mLeftDrawable == null) { + return; + } + mSearchMode = false; + mSearchBar.setState(SearchBar.STATE_NORMAL, animation); + mSearchBarMover.returnSearchBarPosition(); + mLeftDrawable.setMenu(ANIMATE_TIME); + } + + private void onGetFavoritesSuccess(FavoritesParser.Result result, int taskId) { + if (mHelper != null && mSearchBarMover != null && + mHelper.isCurrentTask(taskId)) { + + if (mFavCat != null && mDrawerList != null) { + for (int i = 0; i < 10; i++) { + mFavCat[i] = result.catArray[i]; + mDrawerList.set(i + 1, result.catArray[i]); + } + + if (mDrawerAdapter != null) { + mDrawerAdapter.notifyDataSetChanged(); + } + } + + updateSearchBar(); + mHelper.setPages(taskId, result.pages); + mHelper.onGetPageData(taskId, result.galleryInfoList); + SimpleHandler.getInstance().post(mReturnSearchBarRunnabel); + } + } + + private void onGetFavoritesFailure(Exception e, int taskId) { + if (mHelper != null && mSearchBarMover != null && + mHelper.isCurrentTask(taskId)) { + mHelper.onGetExpection(taskId, e); + } + } + + private class DeleteDialogHelper implements DialogInterface.OnClickListener { + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which != DialogInterface.BUTTON_POSITIVE) { + return; + } + if (mRecyclerView == null || mHelper == null) { + return; + } + + mRecyclerView.outOfCustomChoiceMode(); + + mEnableModify = true; + mModifyFavCat = -1; + mHelper.justRefresh(); + } + } + + private class MoveDialogHelper implements DialogInterface.OnClickListener { + + @Override + public void onClick(DialogInterface dialog, int which) { + if (mRecyclerView == null || mHelper == null) { + return; + } + + mRecyclerView.outOfCustomChoiceMode(); + + mEnableModify = true; + mModifyFavCat = which; + mHelper.justRefresh(); + } + } + + private static class EnterGalleryDetailTransaction implements TransitionHelper { + + private final FavoriteHolder mHolder; + + public EnterGalleryDetailTransaction(FavoriteHolder holder) { + mHolder = holder; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean onTransition(Context context, FragmentTransaction transaction, + Fragment exit, Fragment enter) { + if (mHolder == null || !(enter instanceof GalleryDetailScene)) { + return false; + } + + exit.setSharedElementReturnTransition( + TransitionInflater.from(context).inflateTransition(R.transition.trans_move)); + exit.setExitTransition( + TransitionInflater.from(context).inflateTransition(android.R.transition.fade)); + enter.setSharedElementEnterTransition( + TransitionInflater.from(context).inflateTransition(R.transition.trans_move)); + enter.setEnterTransition( + TransitionInflater.from(context).inflateTransition(android.R.transition.fade)); + transaction.addSharedElement(mHolder.thumb, mHolder.thumb.getTransitionName()); + transaction.addSharedElement(mHolder.title, mHolder.title.getTransitionName()); + transaction.addSharedElement(mHolder.uploader, mHolder.uploader.getTransitionName()); + transaction.addSharedElement(mHolder.category, mHolder.category.getTransitionName()); + return true; + } + } + + private class FavoriteHolder extends RecyclerView.ViewHolder { + + private final LoadImageView thumb; + private final TextView title; + private final TextView uploader; + private final SimpleRatingView rating; + private final TextView category; + private final TextView posted; + private final 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) { + return new FavoriteHolder(getActivity().getLayoutInflater() + .inflate(R.layout.item_gallery_list, parent, false)); + } + + @Override + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public void onBindViewHolder(FavoriteHolder holder, int position) { + if (mHelper == null) { + return; + } + + GalleryInfo gi = mHelper.getDataAt(position); + holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb, true); + holder.title.setText(EhUtils.getSuitableTitle(gi)); + 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); + + // Update transition name + if (ApiHelper.SUPPORT_TRANSITION) { + int gid = gi.gid; + holder.thumb.setTransitionName(TransitionNameFactory.getThumbTransitionName(gid)); + holder.title.setTransitionName(TransitionNameFactory.getTitleTransitionName(gid)); + holder.uploader.setTransitionName(TransitionNameFactory.getUploaderTransitionName(gid)); + holder.category.setTransitionName(TransitionNameFactory.getCategoryTransitionName(gid)); + } + } + + @Override + public int getItemCount() { + return mHelper != null ? mHelper.size() : 0; + } + } + + private class FavoriteHelper extends ContentLayout.ContentHelper { + + @Override + protected void getPageData(final int taskId, int type, int page) { + if (mUrlBuilder == null || mClient == null) { + return; + } + + if (mEnableModify) { + mEnableModify = false; + mUrlBuilder.setIndex(page); + String url = mUrlBuilder.build(); + EhRequest request = new EhRequest(); + request.setMethod(EhClient.METHOD_MODIFY_FAVORITES); + request.setCallback(new GetFavoritesListener(getContext(), + ((StageActivity) getActivity()).getStageId(), getTag(), taskId)); + request.setArgs(url, mModifyGidList, mModifyFavCat, Settings.getShowJpnTitle()); + mClient.execute(request); + } else { + mUrlBuilder.setIndex(page); + String url = mUrlBuilder.build(); + EhRequest request = new EhRequest(); + request.setMethod(EhClient.METHOD_GET_FAVORITES); + request.setCallback(new GetFavoritesListener(getContext(), + ((StageActivity) getActivity()).getStageId(), getTag(), taskId)); + request.setArgs(url, Settings.getShowJpnTitle()); + mClient.execute(request); + } + } + + @Override + protected void onHideRecyclerView() { + super.onHideRecyclerView(); + if (mSearchBarMover != null) { + mSearchBarMover.showSearchBar(); + } + } + + @Override + protected Context getContext() { + return FavoritesScene.this.getContext(); + } + + @Override + protected void notifyDataSetChanged() { + // Ensure outOfCustomChoiceMode to avoid error + if (mRecyclerView != null) { + mRecyclerView.outOfCustomChoiceMode(); + } + + if (mAdapter != null) { + mAdapter.notifyDataSetChanged(); + } + } + + @Override + protected void notifyItemRangeRemoved(int positionStart, int itemCount) { + if (mAdapter != null) { + mAdapter.notifyItemRangeRemoved(positionStart, itemCount); + } + } + + @Override + protected void notifyItemRangeInserted(int positionStart, int itemCount) { + if (mAdapter != null) { + mAdapter.notifyItemRangeInserted(positionStart, itemCount); + } + } + } + + private static class GetFavoritesListener extends EhCallback { + + private final int mTaskId; + + public GetFavoritesListener(Context context, int stageId, String sceneTag, int taskId) { + super(context, stageId, sceneTag); + mTaskId = taskId; + } + + @Override + public void onSuccess(FavoritesParser.Result result) { + // Put fav cat + Settings.putFavCat(result.catArray); + FavoritesScene scene = getScene(); + if (scene != null) { + scene.onGetFavoritesSuccess(result, mTaskId); + } + } + + @Override + public void onFailure(Exception e) { + FavoritesScene scene = getScene(); + if (scene != null) { + scene.onGetFavoritesFailure(e, mTaskId); + } + } + + @Override + public void onCancel() {} + + @Override + public boolean isInstance(SceneFragment scene) { + return scene instanceof FavoritesScene; + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java index 315c21e3b..d417f4393 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryCommentsScene.java @@ -54,7 +54,6 @@ import com.hippo.scene.StageActivity; import com.hippo.text.Html; import com.hippo.text.URLImageGetter; import com.hippo.util.ActivityHelper; -import com.hippo.util.DrawableManager; import com.hippo.util.ReadableTime; import com.hippo.util.TextUrl; import com.hippo.view.ViewTransition; @@ -181,7 +180,7 @@ public final class GalleryCommentsScene extends ToolbarScene public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setTitle(R.string.gallery_comments); - setNavigationIcon(DrawableManager.getDrawable(getContext(), R.drawable.v_arrow_left_dark_x24)); + setNavigationIcon(R.drawable.v_arrow_left_dark_x24); // Clear nav checked item setNavCheckedItem(0); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java index 683ae6674..d6e1b0dc9 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java @@ -996,7 +996,8 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene @TargetApi(Build.VERSION_CODES.LOLLIPOP) public boolean onTransition(Context context, FragmentTransaction transaction, Fragment exit, Fragment enter) { - if (!(enter instanceof GalleryListScene) && !(enter instanceof DownloadScene)) { + if (!(enter instanceof GalleryListScene) && !(enter instanceof DownloadScene) && + !(enter instanceof FavoritesScene)) { return false; } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryInfoScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryInfoScene.java index efae6bc7d..a27549e6a 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryInfoScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryInfoScene.java @@ -39,7 +39,6 @@ import com.hippo.ehviewer.client.EhUtils; import com.hippo.ehviewer.client.data.GalleryDetail; import com.hippo.rippleold.RippleSalon; import com.hippo.util.ActivityHelper; -import com.hippo.util.DrawableManager; import com.hippo.yorozuya.LayoutUtils; import java.util.ArrayList; @@ -170,7 +169,7 @@ public final class GalleryInfoScene extends ToolbarScene implements EasyRecycler public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setTitle(R.string.gallery_info); - setNavigationIcon(DrawableManager.getDrawable(getContext(), R.drawable.v_arrow_left_dark_x24)); + setNavigationIcon(R.drawable.v_arrow_left_dark_x24); // Clear nav checked item setNavCheckedItem(0); 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 c60d2dc82..08cac33b5 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 @@ -427,7 +427,7 @@ public final class GalleryListScene extends BaseScene @Override public boolean onTransition(Context context, FragmentTransaction transaction, Fragment exit, Fragment enter) { - if (!(enter instanceof GalleryDetailScene)) { + if (mHolder == null || !(enter instanceof GalleryDetailScene)) { return false; } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsScene.java index d83a92c9b..528be2e2e 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryPreviewsScene.java @@ -48,7 +48,6 @@ import com.hippo.ehviewer.client.exception.EhException; import com.hippo.scene.SceneFragment; import com.hippo.scene.StageActivity; import com.hippo.util.ActivityHelper; -import com.hippo.util.DrawableManager; import com.hippo.widget.ContentLayout; import com.hippo.widget.LoadImageView; import com.hippo.widget.Slider; @@ -152,7 +151,7 @@ public class GalleryPreviewsScene extends ToolbarScene { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setTitle(R.string.gallery_previews); - setNavigationIcon(DrawableManager.getDrawable(getContext(), R.drawable.v_arrow_left_dark_x24)); + setNavigationIcon(R.drawable.v_arrow_left_dark_x24); // Clear nav checked item setNavCheckedItem(0); diff --git a/app/src/main/java/com/hippo/ehviewer/widget/SearchBar.java b/app/src/main/java/com/hippo/ehviewer/widget/SearchBar.java index dcc8587eb..5d6f9a18d 100644 --- a/app/src/main/java/com/hippo/ehviewer/widget/SearchBar.java +++ b/app/src/main/java/com/hippo/ehviewer/widget/SearchBar.java @@ -27,6 +27,7 @@ import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.text.Editable; +import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.KeyEvent; @@ -67,7 +68,7 @@ public class SearchBar extends FrameLayout implements View.OnClickListener, private int mState = STATE_NORMAL; - private Path mPath = new Path(); + private final Path mPath = new Path(); private int mWidth; private int mHeight; private int mBaseHeight; @@ -79,7 +80,7 @@ public class SearchBar extends FrameLayout implements View.OnClickListener, private SearchEditText mEditText; private ListView mList; private View mListHeader; - private boolean mHeaderAttached = false; + private boolean mHeaderAttached; private ViewTransition mViewTransition; @@ -90,7 +91,9 @@ public class SearchBar extends FrameLayout implements View.OnClickListener, private Helper mHelper; private OnStateChangeListener mOnStateChangeListener; - private boolean mInAnimation = false; + private boolean mAllowEmptySearch = true; + + private boolean mInAnimation; public SearchBar(Context context) { super(context); @@ -187,6 +190,10 @@ public class SearchBar extends FrameLayout implements View.OnClickListener, mSuggestionAdapter.notifyDataSetChanged(); } + public void setAllowEmptySearch(boolean allowEmptySearch) { + mAllowEmptySearch = allowEmptySearch; + } + public float getEditTextTextSize() { return mEditText.getTextSize(); } @@ -245,6 +252,11 @@ public class SearchBar extends FrameLayout implements View.OnClickListener, public void applySearch() { String query = mEditText.getText().toString().trim(); + + if (!mAllowEmptySearch && TextUtils.isEmpty(query)) { + return; + } + // Put it into db mSearchDatabase.addQuery(query); // Callback diff --git a/app/src/main/java/com/hippo/util/JsoupUtils.java b/app/src/main/java/com/hippo/util/JsoupUtils.java new file mode 100644 index 000000000..d5371096b --- /dev/null +++ b/app/src/main/java/com/hippo/util/JsoupUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 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.util; + +import android.support.annotation.Nullable; + +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +public final class JsoupUtils { + + @Nullable + public static Element getElementByClass(Document doc, String className) { + Elements elements = doc.getElementsByClass(className); + if (elements != null && elements.size() > 0) { + return elements.get(0); + } else { + return null; + } + } + + @Nullable + public static Element getElementByClass(Element element, String className) { + Elements elements = element.getElementsByClass(className); + if (elements != null && elements.size() > 0) { + return elements.get(0); + } else { + return null; + } + } + + @Nullable + public static Element getElementByTag(Element element, String tagName) { + Elements elements = element.getElementsByTag(tagName); + if (elements != null && elements.size() > 0) { + return elements.get(0); + } else { + return null; + } + } +} diff --git a/app/src/main/java/com/hippo/widget/ContentLayout.java b/app/src/main/java/com/hippo/widget/ContentLayout.java index a2e8027fd..fd4946714 100644 --- a/app/src/main/java/com/hippo/widget/ContentLayout.java +++ b/app/src/main/java/com/hippo/widget/ContentLayout.java @@ -240,7 +240,7 @@ public class ContentLayout extends FrameLayout { private String mEmptyString = "No hint"; - private RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() { + private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (!mRefreshLayout.isRefreshing() && mRefreshLayout.isAlmostBottom() && mEndPage < mPages) { @@ -251,7 +251,7 @@ public class ContentLayout extends FrameLayout { } }; - private RefreshLayout.OnRefreshListener mOnRefreshListener = new RefreshLayout.OnRefreshListener() { + private final RefreshLayout.OnRefreshListener mOnRefreshListener = new RefreshLayout.OnRefreshListener() { @Override public void onHeaderRefresh() { if (mStartPage > 0) { @@ -284,7 +284,7 @@ public class ContentLayout extends FrameLayout { } }; - private LayoutManagerUtils.OnScrollToPositionListener mOnScrollToPositionListener = + private final LayoutManagerUtils.OnScrollToPositionListener mOnScrollToPositionListener = new LayoutManagerUtils.OnScrollToPositionListener() { @Override public void onScrollToPosition() { @@ -318,7 +318,6 @@ public class ContentLayout extends FrameLayout { }); } - /** * Call {@link #onGetPageData(int, List)} when get data * @@ -338,10 +337,11 @@ public class ContentLayout extends FrameLayout { protected void onScrollToPosition() { } - protected void onShowProgress() { + protected void onHideRecyclerView() { } - protected void onShowText() { + public void setRefreshLayoutEnable(boolean enable) { + mRefreshLayout.setEnabled(enable); } public void setEnable(boolean enable) { @@ -565,24 +565,24 @@ public class ContentLayout extends FrameLayout { mViewTransition.showView(0); } - private boolean isContentShowning() { + private boolean isContentShowing() { return mViewTransition.getShownViewIndex() == 0; } public void showProgressBar() { - mViewTransition.showView(1, false); + showProgressBar(true); } public void showProgressBar(boolean animation) { if (mViewTransition.showView(1, animation)) { - onShowProgress(); + onHideRecyclerView(); } } public void showText(CharSequence text) { mTextView.setText(text); if (mViewTransition.showView(2)) { - onShowText(); + onHideRecyclerView(); } } @@ -642,6 +642,14 @@ public class ContentLayout extends FrameLayout { doRefresh(); } + /** + * Refresh header and refresh + */ + public void justRefresh() { + mRefreshLayout.setHeaderRefreshing(true); + doRefresh(); + } + private void cancelCurrentTask() { mCurrentTaskId = mIdGenerator.nextId(); mRefreshLayout.setHeaderRefreshing(false); @@ -684,7 +692,7 @@ public class ContentLayout extends FrameLayout { } public boolean canGoTo() { - return isContentShowning(); + return isContentShowing(); } /** diff --git a/app/src/main/java/com/hippo/widget/SearchBarMover.java b/app/src/main/java/com/hippo/widget/SearchBarMover.java index ef082300b..7a8192a41 100644 --- a/app/src/main/java/com/hippo/widget/SearchBarMover.java +++ b/app/src/main/java/com/hippo/widget/SearchBarMover.java @@ -18,6 +18,7 @@ package com.hippo.widget; import android.animation.Animator; import android.animation.ValueAnimator; +import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.view.View; @@ -82,6 +83,9 @@ public class SearchBarMover extends RecyclerView.OnScrollListener { show = true; } else { RecyclerView recyclerView = mHelper.getValidRecyclerView(); + if (recyclerView == null) { + return; + } if (!recyclerView.isShown()) { show = true; } else if (recyclerView.computeVerticalScrollOffset() < mSearchBar.getBottom()){ @@ -206,6 +210,7 @@ public class SearchBarMover extends RecyclerView.OnScrollListener { boolean isValidView(RecyclerView recyclerView); + @Nullable RecyclerView getValidRecyclerView(); boolean forceShowSearchBar(); diff --git a/app/src/main/res/drawable/v_delete_dark_x24.xml b/app/src/main/res/drawable/v_delete_dark_x24.xml new file mode 100644 index 000000000..3881ad257 --- /dev/null +++ b/app/src/main/res/drawable/v_delete_dark_x24.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/layout-v21/drawer_list.xml b/app/src/main/res/layout-v21/drawer_list.xml new file mode 100644 index 000000000..a1ae404c4 --- /dev/null +++ b/app/src/main/res/layout-v21/drawer_list.xml @@ -0,0 +1,39 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/drawer_download.xml b/app/src/main/res/layout/drawer_list.xml similarity index 97% rename from app/src/main/res/layout/drawer_download.xml rename to app/src/main/res/layout/drawer_list.xml index c74469dfb..b124d974b 100644 --- a/app/src/main/res/layout/drawer_download.xml +++ b/app/src/main/res/layout/drawer_list.xml @@ -36,7 +36,7 @@ + android:background="@drawable/card_background"> + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/scene_favorite.xml b/app/src/main/res/menu/scene_favorite.xml new file mode 100644 index 000000000..f45c40255 --- /dev/null +++ b/app/src/main/res/menu/scene_favorite.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c0a557105..d712010e0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -303,6 +303,15 @@ Dir picker The directory is not writable + + All Favorites + Search %s + %s + %1$s - %2$s + Delete favorites + Delete %d items from favorites + Move favorites + Something wrong happened