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 @@
+
+
+
+
+
+
+
+