diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 18ad186a0..ff40cc8ec 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -42,6 +42,12 @@ + + + diff --git a/app/src/main/java/com/hippo/app/AppCompatPreferenceActivity.java b/app/src/main/java/com/hippo/app/AppCompatPreferenceActivity.java new file mode 100644 index 000000000..6a52fa901 --- /dev/null +++ b/app/src/main/java/com/hippo/app/AppCompatPreferenceActivity.java @@ -0,0 +1,386 @@ +/* + * 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.app; + +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.support.annotation.CallSuper; +import android.support.annotation.LayoutRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.NavUtils; +import android.support.v4.app.TaskStackBuilder; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AppCompatCallback; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.view.ActionMode; +import android.support.v7.widget.Toolbar; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +public class AppCompatPreferenceActivity extends PreferenceActivity implements AppCompatCallback, + TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider { + + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(@Nullable Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + /** + * Support library version of {@link android.app.Activity#getActionBar}. + * + *

Retrieve a reference to this activity's ActionBar. + * + * @return The Activity's ActionBar, or null if it does not have one. + */ + @Nullable + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + /** + * Set a {@link android.widget.Toolbar Toolbar} to act as the {@link android.support.v7.app.ActionBar} for this + * Activity window. + * + *

When set to a non-null value the {@link #getActionBar()} method will return + * an {@link android.support.v7.app.ActionBar} object that can be used to control the given toolbar as if it were + * a traditional window decor action bar. The toolbar's menu will be populated with the + * Activity's options menu and the navigation button will be wired through the standard + * {@link android.R.id#home home} menu select action.

+ * + *

In order to use a Toolbar within the Activity's window content the application + * must not request the window feature + * {@link android.view.Window#FEATURE_ACTION_BAR FEATURE_SUPPORT_ACTION_BAR}.

+ * + * @param toolbar Toolbar to set as the Activity's action bar + */ + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @NonNull + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) { + if (super.onMenuItemSelected(featureId, item)) { + return true; + } + + final ActionBar ab = getSupportActionBar(); + if (item.getItemId() == android.R.id.home && ab != null && + (ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + return onSupportNavigateUp(); + } + return false; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + /** + * Enable extended support library window features. + *

+ * This is a convenience for calling + * {@link android.view.Window#requestFeature getWindow().requestFeature()}. + *

+ * + * @param featureId The desired feature as defined in + * {@link android.view.Window} or {@link android.support.v4.view.WindowCompat}. + * @return Returns true if the requested feature is supported and now enabled. + * + * @see android.app.Activity#requestWindowFeature + * @see android.view.Window#requestFeature + */ + public boolean supportRequestWindowFeature(int featureId) { + return getDelegate().requestWindowFeature(featureId); + } + + public void supportInvalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + @Override + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + /** + * Notifies the Activity that a support action mode has been started. + * Activity subclasses overriding this method should call the superclass implementation. + * + * @param mode The new action mode. + */ + @Override + @CallSuper + public void onSupportActionModeStarted(ActionMode mode) { + } + + /** + * Notifies the activity that a support action mode has finished. + * Activity subclasses overriding this method should call the superclass implementation. + * + * @param mode The action mode that just finished. + */ + @Override + @CallSuper + public void onSupportActionModeFinished(ActionMode mode) { + } + + /** + * Called when a support action mode is being started for this window. Gives the + * callback an opportunity to handle the action mode in its own unique and + * beautiful way. If this method returns null the system can choose a way + * to present the mode or choose not to start the mode at all. + * + * @param callback Callback to control the lifecycle of this action mode + * @return The ActionMode that was started, or null if the system should present it + */ + @Nullable + @Override + public ActionMode onWindowStartingSupportActionMode(ActionMode.Callback callback) { + return null; + } + + public ActionMode startSupportActionMode(ActionMode.Callback callback) { + return getDelegate().startSupportActionMode(callback); + } + + /** + * Support version of {@link #onCreateNavigateUpTaskStack(android.app.TaskStackBuilder)}. + * This method will be called on all platform versions. + * + * Define the synthetic task stack that will be generated during Up navigation from + * a different task. + * + *

The default implementation of this method adds the parent chain of this activity + * as specified in the manifest to the supplied {@link android.support.v4.app.TaskStackBuilder}. Applications + * may choose to override this method to construct the desired task stack in a different + * way.

+ * + *

This method will be invoked by the default implementation of {@link #onNavigateUp()} + * if {@link #shouldUpRecreateTask(android.content.Intent)} returns true when supplied with the intent + * returned by {@link #getParentActivityIntent()}.

+ * + *

Applications that wish to supply extra Intent parameters to the parent stack defined + * by the manifest should override + * {@link #onPrepareSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)}.

+ * + * @param builder An empty TaskStackBuilder - the application should add intents representing + * the desired task stack + */ + public void onCreateSupportNavigateUpTaskStack(TaskStackBuilder builder) { + builder.addParentStack(this); + } + + /** + * Support version of {@link #onPrepareNavigateUpTaskStack(android.app.TaskStackBuilder)}. + * This method will be called on all platform versions. + * + * Prepare the synthetic task stack that will be generated during Up navigation + * from a different task. + * + *

This method receives the {@link android.support.v4.app.TaskStackBuilder} with the constructed series of + * Intents as generated by {@link #onCreateSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)}. + * If any extra data should be added to these intents before launching the new task, + * the application should override this method and add that data here.

+ * + * @param builder A TaskStackBuilder that has been populated with Intents by + * onCreateNavigateUpTaskStack. + */ + public void onPrepareSupportNavigateUpTaskStack(TaskStackBuilder builder) { + } + + /** + * This method is called whenever the user chooses to navigate Up within your application's + * activity hierarchy from the action bar. + * + *

If a parent was specified in the manifest for this activity or an activity-alias to it, + * default Up navigation will be handled automatically. See + * {@link #getSupportParentActivityIntent()} for how to specify the parent. If any activity + * along the parent chain requires extra Intent arguments, the Activity subclass + * should override the method {@link #onPrepareSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder)} + * to supply those arguments.

+ * + *

See Tasks and + * Back Stack from the developer guide and + * Navigation from the design guide + * for more information about navigating within your app.

+ * + *

See the {@link android.support.v4.app.TaskStackBuilder} class and the Activity methods + * {@link #getSupportParentActivityIntent()}, {@link #supportShouldUpRecreateTask(android.content.Intent)}, and + * {@link #supportNavigateUpTo(android.content.Intent)} for help implementing custom Up navigation.

+ * + * @return true if Up navigation completed successfully and this Activity was finished, + * false otherwise. + */ + public boolean onSupportNavigateUp() { + Intent upIntent = getSupportParentActivityIntent(); + + if (upIntent != null) { + if (supportShouldUpRecreateTask(upIntent)) { + TaskStackBuilder b = TaskStackBuilder.create(this); + onCreateSupportNavigateUpTaskStack(b); + onPrepareSupportNavigateUpTaskStack(b); + b.startActivities(); + + try { + ActivityCompat.finishAffinity(this); + } catch (IllegalStateException e) { + // This can only happen on 4.1+, when we don't have a parent or a result set. + // In that case we should just finish(). + finish(); + } + } else { + // This activity is part of the application's task, so simply + // navigate up to the hierarchical parent activity. + supportNavigateUpTo(upIntent); + } + return true; + } + return false; + } + + /** + * Obtain an {@link android.content.Intent} that will launch an explicit target activity + * specified by sourceActivity's {@link android.support.v4.app.NavUtils#PARENT_ACTIVITY} <meta-data> + * element in the application's manifest. If the device is running + * Jellybean or newer, the android:parentActivityName attribute will be preferred + * if it is present. + * + * @return a new Intent targeting the defined parent activity of sourceActivity + */ + @Override + @Nullable + public Intent getSupportParentActivityIntent() { + return NavUtils.getParentActivityIntent(this); + } + + /** + * Returns true if sourceActivity should recreate the task when navigating 'up' + * by using targetIntent. + * + *

If this method returns false the app can trivially call + * {@link #supportNavigateUpTo(android.content.Intent)} using the same parameters to correctly perform + * up navigation. If this method returns false, the app should synthesize a new task stack + * by using {@link android.support.v4.app.TaskStackBuilder} or another similar mechanism to perform up navigation.

+ * + * @param targetIntent An intent representing the target destination for up navigation + * @return true if navigating up should recreate a new task stack, false if the same task + * should be used for the destination + */ + public boolean supportShouldUpRecreateTask(Intent targetIntent) { + return NavUtils.shouldUpRecreateTask(this, targetIntent); + } + + /** + * Navigate from sourceActivity to the activity specified by upIntent, finishing sourceActivity + * in the process. upIntent will have the flag {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP} set + * by this method, along with any others required for proper up navigation as outlined + * in the Android Design Guide. + * + *

This method should be used when performing up navigation from within the same task + * as the destination. If up navigation should cross tasks in some cases, see + * {@link #supportShouldUpRecreateTask(android.content.Intent)}.

+ * + * @param upIntent An intent representing the target destination for up navigation + */ + public void supportNavigateUpTo(Intent upIntent) { + NavUtils.navigateUpTo(this, upIntent); + } + + @Nullable + @Override + public ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() { + return getDelegate().getDrawerToggleDelegate(); + } + + /** + * @return The {@link AppCompatDelegate} being used by this Activity. + */ + public AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, this); + } + return mDelegate; + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/Settings.java b/app/src/main/java/com/hippo/ehviewer/Settings.java index ea92df9d5..df7a60f8e 100644 --- a/app/src/main/java/com/hippo/ehviewer/Settings.java +++ b/app/src/main/java/com/hippo/ehviewer/Settings.java @@ -111,13 +111,14 @@ public class Settings { putBoolean(KEY_SHOW_WARNING, value); } - - public static EhConfig getEhConfig() { return sEhConfig; } + private static final String KEY_SHOW_JPN_TITLE = "show_jpn_title"; + private static final boolean DEFAULT_SHOW_JPN_TITLE = false; - - + public static boolean getShowJpnTitle() { + return getBoolean(KEY_SHOW_JPN_TITLE, DEFAULT_SHOW_JPN_TITLE); + } } 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 64804e5d5..ac3cc2afe 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhClient.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhClient.java @@ -124,7 +124,7 @@ public class EhClient { Call call = EhEngine.prepareGetGalleryList(mOkHttpClient, ehConfig, (String) params[0]); if (!mStop) { mCall = call; - return EhEngine.doGetGalleryList(call); + return EhEngine.doGetGalleryList(call, (Boolean) params[1], mOkHttpClient); } else { throw new CancelledException(); } 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 713acc735..2f5ada70f 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhEngine.java @@ -21,18 +21,24 @@ import android.util.Log; import android.util.Pair; import com.hippo.ehviewer.client.data.GalleryDetail; +import com.hippo.ehviewer.client.data.GalleryInfo; 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.parser.GalleryDetailParser; import com.hippo.ehviewer.client.parser.GalleryListParser; +import com.hippo.ehviewer.client.parser.ParserUtils; import com.hippo.ehviewer.client.parser.RateGalleryParser; import com.hippo.ehviewer.client.parser.SignInParser; import com.hippo.network.StatusCodeException; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.util.ArrayList; +import java.util.List; + import okhttp3.Call; import okhttp3.FormBody; import okhttp3.Headers; @@ -117,7 +123,62 @@ public class EhEngine { return okHttpClient.newCall(request); } - public static GalleryListParser.Result doGetGalleryList(Call call) throws Exception { + private static GalleryInfo getGalleryInfoByGid(List galleryInfos, int gid) { + for (int i = 0, size = galleryInfos.size(); i < size; i++) { + GalleryInfo gi = galleryInfos.get(i); + if (gi.gid == gid) { + return gi; + } + } + return null; + } + + private static void parseGalleryApi(String body, List galleryInfos) throws JSONException { + JSONObject jo = new JSONObject(body); + JSONArray ja = jo.getJSONArray("gmetadata"); + + for (int i = 0, length = ja.length(); i < length; i++) { + JSONObject g = ja.getJSONObject(i); + int gid = g.getInt("gid"); + GalleryInfo gi = getGalleryInfoByGid(galleryInfos, gid); + if (gi == null) { + continue; + } + gi.titleJpn = ParserUtils.trim(g.getString("title_jpn")); + // tags + JSONArray tagJa = g.getJSONArray("tags"); + int tagLength = tagJa.length(); + String[] tags = new String[tagLength]; + for (int j = 0; j < tagLength; j++) { + tags[j] = tagJa.getString(j); + } + gi.simpleTags = tags; + } + } + + private static void doRequestGalleryApi(List galleryInfos, OkHttpClient okHttpClient) + throws Exception { + JSONObject json = new JSONObject(); + json.put("method", "gdata"); + JSONArray ja = new JSONArray(); + for (int i = 0, size = galleryInfos.size(); i < size; i++) { + GalleryInfo gi = galleryInfos.get(i); + JSONArray g = new JSONArray(); + g.put(gi.gid); + g.put(gi.token); + ja.put(g); + } + json.put("gidlist", ja); + json.put("namespace", 1); + + String url = EhUrl.API_EX; + Log.d(TAG, url); + + Request request = new EhRequestBuilder(url) + .post(RequestBody.create(JSON, json.toString())) + .build(); + Call call = okHttpClient.newCall(request); + String body = null; Headers headers = null; int code = -1; @@ -126,7 +187,41 @@ public class EhEngine { code = response.code(); headers = response.headers(); body = response.body().string(); - return GalleryListParser.parse(body); + parseGalleryApi(body, galleryInfos); + } catch (Exception e) { + throwException(call, code, headers, body, e); + throw e; + } + } + + private static void requestGalleryApi(List galleryInfos, + OkHttpClient okHttpClient) throws Exception { + final int MAX_REQUEST_SIZE = 25; + List requestItems = new ArrayList<>(MAX_REQUEST_SIZE); + for (int i = 0, size = galleryInfos.size(); i < size; i++) { + requestItems.add(galleryInfos.get(i)); + if (requestItems.size() == MAX_REQUEST_SIZE || i == size - 1) { + doRequestGalleryApi(requestItems, okHttpClient); + requestItems.clear(); + } + } + } + + public static GalleryListParser.Result doGetGalleryList(Call call, boolean callApi, + OkHttpClient okHttpClient) throws Exception { + String body = null; + Headers headers = null; + int code = -1; + try { + Response response = call.execute(); + code = response.code(); + headers = response.headers(); + body = response.body().string(); + GalleryListParser.Result result = GalleryListParser.parse(body); + if (callApi && result.galleryInfos.size() > 0) { + requestGalleryApi(result.galleryInfos, okHttpClient); + } + return result; } catch (Exception e) { throwException(call, code, headers, body, e); throw e; @@ -216,4 +311,6 @@ public class EhEngine { throw e; } } + + // TODO Add get gallery api info } diff --git a/app/src/main/java/com/hippo/ehviewer/client/EhUtils.java b/app/src/main/java/com/hippo/ehviewer/client/EhUtils.java index 6fbd985cb..7385b4681 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/EhUtils.java +++ b/app/src/main/java/com/hippo/ehviewer/client/EhUtils.java @@ -121,10 +121,10 @@ public class EhUtils { } public static String getSuitableTitle(GalleryInfo gi) { - if (false) {//;Settings.getJpnTitle()) { + if (Settings.getShowJpnTitle()) { return TextUtils.isEmpty(gi.titleJpn) ? gi.title : gi.titleJpn; } else { - return gi.title; + return TextUtils.isEmpty(gi.title) ? gi.titleJpn : gi.title; } } diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/GalleryApiInfo.java b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryApiInfo.java new file mode 100644 index 000000000..1e38d84a6 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryApiInfo.java @@ -0,0 +1,98 @@ +/* + * 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.support.annotation.Nullable; + +public class GalleryApiInfo implements Parcelable { + + public int gid; + public String token; + public String archiverKey; + public String title; + public String titleJpn; + public int category; + public String thumb; + public String uploader; + public long posted; + public int filecount; + public long filesize; + public boolean expunged; + public float rating; + public int torrentcount; + @Nullable + public String[] tags; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.gid); + dest.writeString(this.token); + dest.writeString(this.archiverKey); + dest.writeString(this.title); + dest.writeString(this.titleJpn); + dest.writeInt(this.category); + dest.writeString(this.thumb); + dest.writeString(this.uploader); + dest.writeLong(this.posted); + dest.writeInt(this.filecount); + dest.writeLong(this.filesize); + dest.writeByte(expunged ? (byte) 1 : (byte) 0); + dest.writeFloat(this.rating); + dest.writeInt(this.torrentcount); + dest.writeStringArray(this.tags); + } + + public GalleryApiInfo() { + } + + protected GalleryApiInfo(Parcel in) { + this.gid = in.readInt(); + this.token = in.readString(); + this.archiverKey = in.readString(); + this.title = in.readString(); + this.titleJpn = in.readString(); + this.category = in.readInt(); + this.thumb = in.readString(); + this.uploader = in.readString(); + this.posted = in.readLong(); + this.filecount = in.readInt(); + this.filesize = in.readLong(); + this.expunged = in.readByte() != 0; + this.rating = in.readFloat(); + this.torrentcount = in.readInt(); + this.tags = in.createStringArray(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public GalleryApiInfo createFromParcel(Parcel source) { + return new GalleryApiInfo(source); + } + + @Override + public GalleryApiInfo[] newArray(int size) { + return new GalleryApiInfo[size]; + } + }; +} diff --git a/app/src/main/java/com/hippo/ehviewer/client/data/GalleryInfo.java b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryInfo.java index 8eba0beb2..3d2a59a02 100644 --- a/app/src/main/java/com/hippo/ehviewer/client/data/GalleryInfo.java +++ b/app/src/main/java/com/hippo/ehviewer/client/data/GalleryInfo.java @@ -86,6 +86,7 @@ public class GalleryInfo implements Parcelable { public String posted; public String uploader; public float rating; + public String[] simpleTags; /** * language from title 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 054a06c5c..9fce17ff7 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java @@ -16,7 +16,9 @@ package com.hippo.ehviewer.ui; +import android.content.Intent; import android.os.Bundle; +import android.os.PersistableBundle; import android.support.annotation.IdRes; import android.support.annotation.Nullable; import android.support.design.widget.NavigationView; @@ -36,8 +38,11 @@ import com.hippo.scene.StageActivity; public final class MainActivity extends StageActivity implements NavigationView.OnNavigationItemSelectedListener { + private static final String KEY_NAV_CHECKED_ITEM = "nav_checked_item"; + private DrawerLayout mDrawerLayout; private NavigationView mNavView; + private int mNavCheckedItem = 0; @Override public int getContainerViewId() { @@ -56,9 +61,43 @@ public final class MainActivity extends StageActivity if (savedInstanceState == null) { onInit(); + } else { + onRestore(savedInstanceState); } } + private void onInit() { + if (Settings.getShowWarning()) { + setDrawerLayoutEnable(false); + startScene(WarningScene.class); + } else if (!EhUtils.hasSignedIn(this)) { + setDrawerLayoutEnable(false); + startScene(LoginScene.class); + } else { + setDrawerLayoutEnable(true); + Bundle args = new Bundle(); + args.putString(GalleryListScene.KEY_ACTION, GalleryListScene.ACTION_HOMEPAGE); + startScene(GalleryListScene.class, args); + } + } + + private void onRestore(Bundle savedInstanceState) { + mNavCheckedItem = savedInstanceState.getInt(KEY_NAV_CHECKED_ITEM); + } + + @Override + public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) { + super.onSaveInstanceState(outState, outPersistentState); + outState.putInt(KEY_NAV_CHECKED_ITEM, mNavCheckedItem); + } + + @Override + protected void onResume() { + super.onResume(); + + setNavCheckedItem(mNavCheckedItem); + } + public void setDrawerLayoutEnable(boolean enable) { if (enable) { mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); @@ -76,6 +115,7 @@ public final class MainActivity extends StageActivity } public void setNavCheckedItem(@IdRes int resId) { + mNavCheckedItem = resId; if (resId == 0) { mNavView.setCheckedItem(R.id.nav_stub); } else { @@ -83,21 +123,6 @@ public final class MainActivity extends StageActivity } } - private void onInit() { - if (Settings.getShowWarning()) { - setDrawerLayoutEnable(false); - startScene(WarningScene.class); - } else if (!EhUtils.hasSignedIn(this)) { - setDrawerLayoutEnable(false); - startScene(LoginScene.class); - } else { - setDrawerLayoutEnable(true); - Bundle args = new Bundle(); - args.putString(GalleryListScene.KEY_ACTION, GalleryListScene.ACTION_HOMEPAGE); - startScene(GalleryListScene.class, args); - } - } - @Override public void onBackPressed() { if (mDrawerLayout.isDrawerOpen(Gravity.LEFT) || mDrawerLayout.isDrawerOpen(Gravity.RIGHT)) { @@ -109,7 +134,11 @@ public final class MainActivity extends StageActivity @Override public boolean onNavigationItemSelected(MenuItem item) { - // Handle navigation view item clicks here. + // Don't select twice + if (item.isChecked()) { + return false; + } + int id = item.getItemId(); if (id == R.id.nav_homepage) { @@ -125,7 +154,8 @@ public final class MainActivity extends StageActivity } else if (id == R.id.nav_download) { } else if (id == R.id.nav_settings) { - + Intent intent = new Intent(this, SettingsActivity.class); + startActivity(intent); } if (id != R.id.nav_stub) { diff --git a/app/src/main/java/com/hippo/ehviewer/ui/SettingsActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/SettingsActivity.java new file mode 100644 index 000000000..c404ddb5f --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/SettingsActivity.java @@ -0,0 +1,191 @@ +/* + * 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; + +import android.app.Fragment; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.StringRes; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarDrawerToggle; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListAdapter; + +import com.hippo.app.AppCompatPreferenceActivity; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.ui.fragment.EhFragment; +import com.hippo.vector.VectorDrawable; + +import java.lang.reflect.Field; +import java.util.List; + +public final class SettingsActivity extends AppCompatPreferenceActivity { + + private static final int REQUEST_CODE_FRAGMENT = 0; + + private static final String[] ENTRY_FRAGMENTS = { + EhFragment.class.getName(), + }; + + private class FakeLayoutInflater extends LayoutInflater { + + private LayoutInflater mInflater; + + protected FakeLayoutInflater(LayoutInflater inflater) { + super(null); + mInflater = inflater; + } + + @Override + public LayoutInflater cloneInContext(Context newContext) { + return null; + } + + @Override + public View inflate(int resource, ViewGroup root, boolean attachToRoot) { + return mInflater.inflate(R.layout.item_preference_header, root, attachToRoot); + } + } + + @SuppressWarnings("TryWithIdenticalCatches") + private void replaceHeaderLayoutResId() { + try { + ListAdapter adapter = getListAdapter(); + Class headerAdapterClazz = Class.forName("android.preference.PreferenceActivity$HeaderAdapter"); + if (!headerAdapterClazz.isInstance(adapter)) { + return; + } + + boolean ok = false; + + // For lollipop and above this work + try { + Field field = headerAdapterClazz.getDeclaredField("mLayoutResId"); + field.setAccessible(true); + field.setInt(adapter, R.layout.item_preference_header); + + ok = true; + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + + // For pre-lollipop this work + if (!ok) { + try { + Field field = headerAdapterClazz.getDeclaredField("mInflater"); + field.setAccessible(true); + field.set(adapter, new FakeLayoutInflater((LayoutInflater) field.get(adapter))); + + ok = true; + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (ClassCastException e) { + e.printStackTrace(); + } + } + + if (ok) { + getListView().setDivider(new ColorDrawable(Color.TRANSPARENT)); + getListView().setDividerHeight(0); + } + + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + private void setActionBarUpIndicator(Drawable drawable) { + ActionBarDrawerToggle.Delegate delegate = getDrawerToggleDelegate(); + if (delegate != null) { + delegate.setActionBarUpIndicator(drawable, 0); + } + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeButtonEnabled(true); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setActionBarUpIndicator(VectorDrawable.create(this, R.drawable.ic_arrow_left)); + + replaceHeaderLayoutResId(); + } + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onBuildHeaders(List
target) { + loadHeadersFromResource(R.xml.settings_headers, target); + } + + @Override + public void startWithFragment(String fragmentName, Bundle args, + Fragment resultTo, int resultRequestCode, @StringRes int titleRes, + @StringRes int shortTitleRes) { + Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes); + if (resultTo == null) { + startActivityForResult(intent, REQUEST_CODE_FRAGMENT); + } else { + resultTo.startActivityForResult(intent, resultRequestCode); + } + } + + @Override + protected boolean isValidFragment(String fragmentName) { + for (String fragment : ENTRY_FRAGMENTS) { + if (fragment.equals(fragmentName)) { + return true; + } + } + return false; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == REQUEST_CODE_FRAGMENT) { + if (resultCode == RESULT_OK) { + setResult(RESULT_OK, data); + } + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/ui/fragment/EhFragment.java b/app/src/main/java/com/hippo/ehviewer/ui/fragment/EhFragment.java new file mode 100644 index 000000000..c07741155 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/fragment/EhFragment.java @@ -0,0 +1,31 @@ +/* + * 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.fragment; + +import android.os.Bundle; +import android.preference.PreferenceFragment; + +import com.hippo.ehviewer.R; + +public class EhFragment extends PreferenceFragment { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.eh_settings); + } +} 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 c26b9e38a..c3fd5a524 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 @@ -578,7 +578,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene if (ACTION_GALLERY_INFO.equals(mAction) && mGalleryInfo != null) { GalleryInfo gi = mGalleryInfo; mThumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb, true); - mTitle.setText(gi.title); + mTitle.setText(EhUtils.getSuitableTitle(gi)); mUploader.setText(gi.uploader); mCategory.setText(EhUtils.getCategory(gi.category)); mCategory.setTextColor(EhUtils.getCategoryColor(gi.category)); @@ -618,7 +618,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene if (TextUtils.isEmpty(mCategory.getText())) { mThumb.load(EhCacheKeyFactory.getThumbKey(gd.gid), gd.thumb, true); - mTitle.setText(gd.title); + mTitle.setText(EhUtils.getSuitableTitle(gd)); mUploader.setText(gd.uploader); mCategory.setText(EhUtils.getCategory(gd.category)); mCategory.setTextColor(EhUtils.getCategoryColor(gd.category)); 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 c45c18e01..1a01ebf2d 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 @@ -53,6 +53,7 @@ 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; @@ -926,7 +927,7 @@ public final class GalleryListScene extends BaseScene public void onBindViewHolder(GalleryListHolder holder, int position) { GalleryInfo gi = mHelper.getDataAt(position); holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb, true); - holder.title.setText(gi.title); + holder.title.setText(EhUtils.getSuitableTitle(gi)); holder.uploader.setText(gi.uploader); holder.rating.setRating(gi.rating); TextView category = holder.category; @@ -965,7 +966,7 @@ public final class GalleryListScene extends BaseScene request.setMethod(EhClient.METHOD_GET_GALLERY_LIST); request.setCallback(new GetGalleryListListener(getContext(), ((StageActivity) getActivity()).getStageId(), getTag(), taskId)); - request.setArgs(url); + request.setArgs(url, Settings.getShowJpnTitle()); // TODO check tag filter mClient.execute(request); } diff --git a/app/src/main/java/com/hippo/preference/FixedSwitchPreference.java b/app/src/main/java/com/hippo/preference/FixedSwitchPreference.java new file mode 100644 index 000000000..e1a6cc951 --- /dev/null +++ b/app/src/main/java/com/hippo/preference/FixedSwitchPreference.java @@ -0,0 +1,190 @@ +/* + * 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.preference; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build; +import android.preference.TwoStatePreference; +import android.support.annotation.StringRes; +import android.support.v7.widget.SwitchCompat; +import android.util.AttributeSet; +import android.view.View; +import android.widget.Checkable; +import android.widget.CompoundButton; + +import com.hippo.ehviewer.R; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class FixedSwitchPreference extends TwoStatePreference { + + private static Method sSyncSummaryViewMethod; + + static { + try { + sSyncSummaryViewMethod = TwoStatePreference.class.getDeclaredMethod("syncSummaryView", View.class); + sSyncSummaryViewMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + sSyncSummaryViewMethod = null; + } + } + + // Switch text for on and off states + private CharSequence mSwitchOn; + private CharSequence mSwitchOff; + + private final Listener mListener = new Listener(); + + private class Listener implements CompoundButton.OnCheckedChangeListener { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (!callChangeListener(isChecked)) { + // Listener didn't like it, change it back. + // CompoundButton will make sure we don't recurse. + buttonView.setChecked(!isChecked); + return; + } + + FixedSwitchPreference.this.setChecked(isChecked); + } + } + + public FixedSwitchPreference(Context context) { + super(context); + init(context, null, 0, 0); + } + + public FixedSwitchPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0, 0); + } + + public FixedSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public FixedSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context, attrs, defStyleAttr, defStyleRes); + } + + public void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FixedSwitchPreference, defStyleAttr, defStyleRes); + setSummaryOn(a.getString(R.styleable.FixedSwitchPreference_summaryOn)); + setSummaryOff(a.getString(R.styleable.FixedSwitchPreference_summaryOff)); + setSwitchTextOn(a.getString(R.styleable.FixedSwitchPreference_switchTextOn)); + setSwitchTextOff(a.getString(R.styleable.FixedSwitchPreference_switchTextOff)); + setDisableDependentsState(a.getBoolean(R.styleable.FixedSwitchPreference_disableDependentsState, false)); + a.recycle(); + } + + @SuppressWarnings("TryWithIdenticalCatches") + @Override + protected void onBindView(View view) { + super.onBindView(view); + + View checkableView = view.findViewById(R.id.switchWidget); + if (checkableView != null && checkableView instanceof Checkable) { + if (checkableView instanceof SwitchCompat) { + final SwitchCompat switchView = (SwitchCompat) checkableView; + switchView.setOnCheckedChangeListener(null); + } + + ((Checkable) checkableView).setChecked(isChecked()); + + if (checkableView instanceof SwitchCompat) { + final SwitchCompat switchView = (SwitchCompat) checkableView; + switchView.setTextOn(mSwitchOn); + switchView.setTextOff(mSwitchOff); + switchView.setOnCheckedChangeListener(mListener); + } + } + + if (sSyncSummaryViewMethod != null) { + try { + sSyncSummaryViewMethod.invoke(this, view); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + } + + + /** + * Set the text displayed on the switch widget in the on state. + * This should be a very short string; one word if possible. + * + * @param onText Text to display in the on state + */ + public void setSwitchTextOn(CharSequence onText) { + mSwitchOn = onText; + notifyChanged(); + } + + /** + * Set the text displayed on the switch widget in the off state. + * This should be a very short string; one word if possible. + * + * @param offText Text to display in the off state + */ + public void setSwitchTextOff(CharSequence offText) { + mSwitchOff = offText; + notifyChanged(); + } + + /** + * Set the text displayed on the switch widget in the on state. + * This should be a very short string; one word if possible. + * + * @param resId The text as a string resource ID + */ + public void setSwitchTextOn(@StringRes int resId) { + setSwitchTextOn(getContext().getString(resId)); + } + + /** + * Set the text displayed on the switch widget in the off state. + * This should be a very short string; one word if possible. + * + * @param resId The text as a string resource ID + */ + public void setSwitchTextOff(@StringRes int resId) { + setSwitchTextOff(getContext().getString(resId)); + } + + /** + * @return The text that will be displayed on the switch widget in the on state + */ + public CharSequence getSwitchTextOn() { + return mSwitchOn; + } + + /** + * @return The text that will be displayed on the switch widget in the off state + */ + public CharSequence getSwitchTextOff() { + return mSwitchOff; + } +} diff --git a/app/src/main/res/color/preference_header_title_light.xml b/app/src/main/res/color/preference_header_title_light.xml new file mode 100644 index 000000000..4f7807db5 --- /dev/null +++ b/app/src/main/res/color/preference_header_title_light.xml @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_settings_eh.png b/app/src/main/res/drawable-hdpi/ic_settings_eh.png new file mode 100644 index 000000000..cc2a52bd2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_settings_eh.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_settings_eh.png b/app/src/main/res/drawable-mdpi/ic_settings_eh.png new file mode 100644 index 000000000..d25b80e2d Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_settings_eh.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_settings_eh.png b/app/src/main/res/drawable-xhdpi/ic_settings_eh.png new file mode 100644 index 000000000..cb9c99302 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_settings_eh.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_settings_eh.png b/app/src/main/res/drawable-xxhdpi/ic_settings_eh.png new file mode 100644 index 000000000..461f819d5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_settings_eh.png differ diff --git a/app/src/main/res/drawable/preference_header_background.xml b/app/src/main/res/drawable/preference_header_background.xml new file mode 100644 index 000000000..9be303ef1 --- /dev/null +++ b/app/src/main/res/drawable/preference_header_background.xml @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/item_preference_header.xml b/app/src/main/res/layout/item_preference_header.xml new file mode 100644 index 000000000..10442e5a5 --- /dev/null +++ b/app/src/main/res/layout/item_preference_header.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/preference_widget_fixed_switch.xml b/app/src/main/res/layout/preference_widget_fixed_switch.xml new file mode 100644 index 000000000..95c684e66 --- /dev/null +++ b/app/src/main/res/layout/preference_widget_fixed_switch.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 5b70866f8..e8e811bf4 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -88,4 +88,24 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 072456a6e..46698b2dd 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -28,6 +28,7 @@ #de000000 + #ffffffff #4dffffff #8a000000 #36ffffff @@ -50,6 +51,7 @@ #ff7cb342 #fff9a825 #ffff7043 + #ff9e9e9e @color/teal_500 @color/teal_700 diff --git a/app/src/main/res/values/drawables.xml b/app/src/main/res/values/drawables.xml new file mode 100644 index 000000000..e3271de02 --- /dev/null +++ b/app/src/main/res/values/drawables.xml @@ -0,0 +1,22 @@ + + + + + @color/grey_500 + @android:color/transparent + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 60621b5c4..3eb9f5b86 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -201,6 +201,11 @@ Go to Out of range + EH + Show Japanese title + Try to show Japanese title. It takes more time to get gallery list + Show default title. It is usually English title + From the future Just now diff --git a/app/src/main/res/xml/eh_settings.xml b/app/src/main/res/xml/eh_settings.xml new file mode 100644 index 000000000..9c515f967 --- /dev/null +++ b/app/src/main/res/xml/eh_settings.xml @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/app/src/main/res/xml/settings_headers.xml b/app/src/main/res/xml/settings_headers.xml new file mode 100644 index 000000000..0e0f18b9a --- /dev/null +++ b/app/src/main/res/xml/settings_headers.xml @@ -0,0 +1,26 @@ + + + + + +
+ +