This commit is contained in:
seven332 committed 2016-01-24 00:17:57 +08:00
commit 52209017a3
198 files changed
+25167

No files matched your search

@@ -0,0 +1,77 @@
/*
* 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;
import android.app.Application;
import android.content.Context;
import android.support.annotation.NonNull;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.network.StatusCodeException;
import com.hippo.okhttp.CookieDB;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
public class EhApplication extends Application {
private EhCookieStore mEhCookieStore;
private EhClient mEhClient;
private OkHttpClient mOkHttpClient;
@Override
public void onCreate() {
super.onCreate();
GetText.initialize(this);
CookieDB.initialize(this);
StatusCodeException.initialize(this);
Settings.initialize(this);
}
public static EhCookieStore getEhCookieStore(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mEhCookieStore == null) {
application.mEhCookieStore = new EhCookieStore();
}
return application.mEhCookieStore;
}
@NonNull
public static EhClient getEhClient(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mEhClient == null) {
application.mEhClient = new EhClient(application);
}
return application.mEhClient;
}
public static OkHttpClient getOkHttpClient(@NonNull Context context) {
EhApplication application = ((EhApplication) context.getApplicationContext());
if (application.mOkHttpClient == null) {
application.mOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.cookieJar(getEhCookieStore(application))
.build();
}
return application.mOkHttpClient;
}
}
@@ -0,0 +1,41 @@
/*
* 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;
import android.content.Context;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
public class GetText {
private static Resources sResources;
public static void initialize(Context context) {
sResources = context.getApplicationContext().getResources();
}
@NonNull
public static String getString(@StringRes int id) {
return sResources.getString(id);
}
@NonNull
public static String getString(@StringRes int id, Object... formatArgs) {
return sResources.getString(id, formatArgs);
}
}
@@ -0,0 +1,104 @@
/*
* 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;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import com.hippo.yorozuya.NumberUtils;
public class Settings {
private static Context sContext;
private static SharedPreferences sSettingsPre;
public static void initialize(Context context) {
sContext = context.getApplicationContext();
sSettingsPre = PreferenceManager.getDefaultSharedPreferences(sContext);
}
public static boolean getBoolean(String key, boolean defValue) {
return sSettingsPre.getBoolean(key, defValue);
}
public static void putBoolean(String key, boolean value) {
sSettingsPre.edit().putBoolean(key, value).apply();
}
public static int getInt(String key, int defValue) {
return sSettingsPre.getInt(key, defValue);
}
public static void putInt(String key, int value) {
sSettingsPre.edit().putInt(key, value).apply();
}
public static long getLong(String key, long defValue) {
return sSettingsPre.getLong(key, defValue);
}
public static void putLong(String key, long value) {
sSettingsPre.edit().putLong(key, value).apply();
}
public static float getFloat(String key, float defValue) {
return sSettingsPre.getFloat(key, defValue);
}
public static void putFloat(String key, float value) {
sSettingsPre.edit().putFloat(key, value).apply();
}
public static String getString(String key, String defValue) {
return sSettingsPre.getString(key, defValue);
}
public static void putString(String key, String value) {
sSettingsPre.edit().putString(key, value).apply();
}
public static int getIntFromStr(String key, int defValue) {
return NumberUtils.parseIntSafely(sSettingsPre.getString(key, Integer.toString(defValue)), defValue);
}
public static void putIntToStr(String key, int value) {
sSettingsPre.edit().putString(key, Integer.toString(value)).apply();
}
private static final String KEY_DISPLAY_NAME = "display_name";
private static final String DEFAULT_DISPLAY_NAME = null;
public static String getDisplayName() {
return getString(KEY_DISPLAY_NAME, DEFAULT_DISPLAY_NAME);
}
public static void putDisplayName(String value) {
putString(KEY_DISPLAY_NAME, value);
}
private static final String KEY_SHOW_WARNING = "show_warning";
private static final boolean DEFAULT_SHOW_WARNING = true;
public static boolean getShowWarning() {
return getBoolean(KEY_SHOW_WARNING, DEFAULT_SHOW_WARNING);
}
public static void putShowWarning(boolean value) {
putBoolean(KEY_SHOW_WARNING, value);
}
}
@@ -0,0 +1,24 @@
/*
* 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;
public class CancelledException extends Exception {
public CancelledException() {
super("CANCELED");
}
}
@@ -0,0 +1,160 @@
/*
* 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;
import android.content.Context;
import android.os.AsyncTask;
import com.hippo.ehviewer.EhApplication;
import com.hippo.yorozuya.PriorityThreadFactory;
import com.hippo.yorozuya.SimpleHandler;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.OkHttpClient;
public class EhClient {
public static final String TAG = EhClient.class.getSimpleName();
public static final int METHOD_SIGN_IN = 0;
private final ThreadPoolExecutor mRequestThreadPool;
private final OkHttpClient mOkHttpClient;
public EhClient(Context context) {
int poolSize = 3;
BlockingQueue<Runnable> requestWorkQueue = new LinkedBlockingQueue<>();
ThreadFactory threadFactory = new PriorityThreadFactory(TAG,
android.os.Process.THREAD_PRIORITY_BACKGROUND);
mRequestThreadPool = new ThreadPoolExecutor(poolSize, poolSize,
1L, TimeUnit.SECONDS, requestWorkQueue, threadFactory);
mOkHttpClient = EhApplication.getOkHttpClient(context);
}
public void execute(EhRequest request) {
if (!request.isCancelled()) {
Task task = new Task(request.method, request.callback);
task.executeOnExecutor(mRequestThreadPool, request.args);
request.task = task;
} else {
request.callback.onCancel();
}
}
public class Task extends AsyncTask<Object, Void, Object> {
private int mMethod;
private Callback mCallback;
private Call mCall;
private boolean mStop;
public Task(int method, Callback callback) {
mMethod = method;
mCallback = callback;
}
public void stop() {
if (!mStop) {
mStop = true;
if (mCallback != null) {
final Callback finalCallback = mCallback;
SimpleHandler.getInstance().post(new Runnable() {
@Override
public void run() {
finalCallback.onCancel();
}
});
}
Status status = getStatus();
if (status == Status.PENDING) {
cancel(false);
} else if (status == Status.RUNNING) {
if (mCall != null) {
mCall.cancel();
}
}
// Clear
mCall = null;
mCallback = null;
}
}
private Object signIn(Object... params) throws Exception {
Call call = EhEngine.prepareSignIn(mOkHttpClient, (String) params[0], (String) params[1]);
if (!mStop) {
mCall = call;
return EhEngine.doSignIn(call);
} else {
throw new CancelledException();
}
}
@Override
protected Object doInBackground(Object... params) {
try {
switch (mMethod) {
case METHOD_SIGN_IN:
return signIn(params);
default:
return new IllegalStateException("Can't detect method " + mMethod);
}
} catch (Exception e) {
return e;
}
}
@SuppressWarnings("unchecked")
@Override
protected void onPostExecute(Object result) {
if (mCallback != null) {
//noinspection StatementWithEmptyBody
if (!(result instanceof CancelledException)) {
if (result instanceof Exception) {
mCallback.onFailure((Exception) result);
} else {
mCallback.onSuccess(result);
}
} else {
// onCancel is called in stop
}
}
// Clear
mCall = null;
mCallback = null;
}
}
public interface Callback<E> {
void onSuccess(E result);
void onFailure(Exception e);
void onCancel();
}
}
@@ -0,0 +1,665 @@
/*
* Copyright 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client;
import android.text.TextUtils;
/**
* Some configurable stuff about EH. It ends up cookie.
*/
public class EhConfig implements Cloneable {
/**
* The Cookie key of uconfig
*/
public static final String KEY_UCONFIG = "uconfig";
/**
* The key of load images through the Hentai@Home Network
*/
private static final String KEY_LOAD_FROM_HAH = "uh";
/**
* The key of scale images width
*/
private static final String KEY_SCALE_WIDTH = "rx";
/**
* The key of scale images height
*/
private static final String KEY_SCALE_HEIGHT = "ry";
/**
* The key of the behavior for downloading archiver
*/
private static final String KEY_ARCHIVER_DOWNLOAD = "ar";
/**
* The key of display mode would you like to use on the front and search pages
*/
private static final String KEY_LAYOUT_MODE = "dm";
/**
* The key of categories would you like to view as default on the front page
*/
private static final String KEY_DEFAULT_CATEGORIES = "cats";
/**
* The key of exclude certain namespaces from a default tag search
*/
private static final String KEY_EXCLUDED_NAMESPACES = "xns";
/**
* The key of hide galleries in certain languages from the gallery list and searches
*/
private static final String KEY_EXCLUDED_LANGUAGES = "xl";
/**
* The key of how many results would you like per page for the index/search page and torrent search pages
*/
private static final String KEY_RESULT_COUNT = "rc";
/**
* The key of mouse-over thumb
*/
private static final String KEY_MOUSE_OVER = "tl";
/**
* The key of preview size
*/
private static final String KEY_PREVIEW_SIZE = "ts";
/**
* The key of preview row per page
*/
private static final String KEY_PREVIEW_ROW = "tr";
/**
* The key of sort order for gallery comments
*/
private static final String KEY_COMMENTS_SORT = "cs";
/**
* The key of show gallery comment votes
*/
private static final String KEY_COMMENTS_VOTES = "sc";
/**
* The key of sort order for gallery tags
*/
private static final String KEY_TAGS_SORT = "to";
/**
* The key of show gallery page numbers
*/
private static final String KEY_SHOW_GALLERY_INDEX = "pn";
/**
* The key of the IP:Port of a proxy-enabled Hentai@Home Client
* to load all images
*/
private static final String KEY_HAH_CLIENT_IP_PORT = "hp";
/**
* The key of the passkey of a proxy-enabled Hentai@Home Client
* to load all images
*/
private static final String KEY_HAH_CLIENT_PASSKEY = "hk";
/**
* The key of enable Tag Flagging
*/
private static final String KEY_ENABLE_TAG_FLAGGING = "tf";
/**
* The key of always display the original images instead of the resampled versions
*/
private static final String KEY_ALWAYS_ORIGINAL = "oi";
/**
* The key of enable the multi-Page Viewer
*/
private static final String KEY_MULTI_PAGE = "qb";
/**
* The key of multi-Page Viewer Display Style
*/
private static final String KEY_MULTI_PAGE_STYLE = "ms";
/**
* The key of multi-Page Viewer Thumbnail Pane
*/
private static final String KEY_MULTI_PAGE_THUMB = "mt";
/**
* The Cookie key of lofi resolution
*/
public static final String KEY_LOFI_RESOLUTION = "xres";
/**
* The Cookie key of show warning
*/
public static final String KEY_CONTENT_WARNING = "nw";
/**
* load images through the Hentai@Home Network
*/
public static final String LOAD_FROM_HAH_YES = "y";
/**
* do not load images through the Hentai@Home Network
*/
public static final String LOAD_FROM_HAH_NO = "n";
/**
* Manual Accept, Manual Start
*/
public static final String ARCHIVER_DOWNLOAD_MAMS = "0";
/**
* >Manual Accept, Auto Start
*/
public static final String ARCHIVER_DOWNLOAD_AAMS = "1";
/**
* Auto Accept, Manual Start
*/
public static final String ARCHIVER_DOWNLOAD_MAAS = "2";
/**
* Auto Accept, Auto Start
*/
public static final String ARCHIVER_DOWNLOAD_AAAS = "3";
/**
* List View on the front and search pages
*/
public static final String LAYOUT_MODE_LIST = "l";
/**
* Thumbnail View on the front and search pages
*/
public static final String LAYOUT_MODE_THUMB = "t";
public static final int MISC = 0x1;
public static final int DOUJINSHI = 0x2;
public static final int MANGA = 0x4;
public static final int ARTIST_CG = 0x8;
public static final int GAME_CG = 0x10;
public static final int IMAGE_SET = 0x20;
public static final int COSPLAY = 0x40;
public static final int ASIAN_PORN = 0x80;
public static final int NON_H = 0x100;
public static final int WESTERN = 0x200;
public static final int NAMESPACES_RECLASS = 0x1;
public static final int NAMESPACES_LANGUAGE = 0x2;
public static final int NAMESPACES_PARODY = 0x4;
public static final int NAMESPACES_CHARACTER = 0x8;
public static final int NAMESPACES_GROUP = 0x10;
public static final int NAMESPACES_ARTIST = 0x20;
public static final int NAMESPACES_MALE = 0x40;
public static final int NAMESPACES_FEMALE = 0x80;
public static final String JAPANESE_TRANSLATED = "1024";
public static final String JAPANESE_REWRITE = "2048";
public static final String ENGLISH_ORIGINAL = "1";
public static final String ENGLISH_TRANSLATED = "1025";
public static final String ENGLISH_REWRITE = "2049";
public static final String CHINESE_ORIGINAL = "10";
public static final String CHINESE_TRANSLATED = "1034";
public static final String CHINESE_REWRITE = "2058";
public static final String DUTCH_ORIGINAL = "20";
public static final String DUTCH_TRANSLATED = "1044";
public static final String DUTCH_REWRITE = "2068";
public static final String FRENCH_ORIGINAL = "30";
public static final String FRENCH_TRANSLATED = "1054";
public static final String FRENCH_REWRITE = "2078";
public static final String GERMAN_ORIGINAL = "40";
public static final String GERMAN_TRANSLATED = "1064";
public static final String GERMAN_REWRITE = "2088";
public static final String HUNGARIAN_ORIGINAL = "50";
public static final String HUNGARIAN_TRANSLATED = "1074";
public static final String HUNGARIAN_REWRITE = "2098";
public static final String ITALIAN_ORIGINAL = "60";
public static final String ITALIAN_TRANSLATED = "1084";
public static final String ITALIAN_REWRITE = "2108";
public static final String KOREAN_ORIGINAL = "70";
public static final String KOREAN_TRANSLATED = "1094";
public static final String KOREAN_REWRITE = "2118";
public static final String POLISH_ORIGINAL = "80";
public static final String POLISH_TRANSLATED = "1104";
public static final String POLISH_REWRITE = "2128";
public static final String PORTUGUESE_ORIGINAL = "90";
public static final String PORTUGUESE_TRANSLATED = "1114";
public static final String PORTUGUESE_REWRITE = "2138";
public static final String RUSSIAN_ORIGINAL = "100";
public static final String RUSSIAN_TRANSLATED = "1124";
public static final String RUSSIAN_REWRITE = "2148";
public static final String SPANISH_ORIGINAL = "110";
public static final String SPANISH_TRANSLATED = "1134";
public static final String SPANISH_REWRITE = "2158";
public static final String THAI_ORIGINAL = "120";
public static final String THAI_TRANSLATED = "1144";
public static final String THAI_REWRITE = "2168";
public static final String VIETNAMESE_ORIGINAL = "130";
public static final String VIETNAMESE_TRANSLATED = "1154";
public static final String VIETNAMESE_REWRITE = "2178";
public static final String NA_ORIGINAL = "254";
public static final String NA_TRANSLATED = "1278";
public static final String NA_REWRITE = "2302";
public static final String OTHER_ORIGINAL = "255";
public static final String OTHER_TRANSLATED = "1279";
public static final String OTHER_REWRITE = "2303";
/**
* 25 results per page for the index/search page and torrent search pages
*/
public static final String RESULT_COUNT_25 = "0";
/**
* 50 results per page for the index/search page and torrent search pages
*/
public static final String RESULT_COUNT_50 = "1";
/**
* 100 results per page for the index/search page and torrent search pages
*/
public static final String RESULT_COUNT_100 = "2";
/**
* 200 results per page for the index/search page and torrent search pages
*/
public static final String RESULT_COUNT_200 = "3";
/**
* On mouse-over
*/
public static final String MOUSE_OVER_YES = "m";
/**
* On page load
*/
public static final String MOUSE_OVER_NO = "p";
/**
* Preview normal size
*/
public static final String PREVIEW_SIZE_NORMAL = "m";
/**
* Preview large size
*/
public static final String PREVIEW_SIZE_LARGE = "l";
/**
* 4 row preview per page
*/
public static final String PREVIEW_ROW_4 = "2";
/**
* 10 row preview per page
*/
public static final String PREVIEW_ROW_10 = "5";
/**
* 20 row preview per page
*/
public static final String PREVIEW_ROW_20 = "10";
/**
* 40 row preview per page
*/
public static final String PREVIEW_ROW_40 = "20";
/**
* Oldest comments first
*/
public static final String COMMENTS_SORT_OLDEST_FIRST = "a";
/**
* Recent comments first
*/
public static final String COMMENTS_SORT_RECENT_FIRST = "d";
/**
* By highest score
*/
public static final String COMMENTS_SORT_HIGHEST_SCORE_FIRST = "s";
/**
* Show gallery comment votes On score hover or click
*/
public static final String COMMENTS_VOTES_POP = "0";
/**
* Always show gallery comment votes
*/
public static final String COMMENTS_VOTES_ALWAYS = "1";
/**
* Sort order for gallery tags alphabetically
*/
public static final String TAGS_SORT_ALPHABETICAL = "a";
/**
* Sort order for gallery tags by tag power
*/
public static final String TAGS_SORT_POWER = "p";
/**
* Show gallery page numbers
*/
public static final String SHOW_GALLERY_INDEX_YES = "1";
/**
* Do not show gallery page numbers
*/
public static final String SHOW_GALLERY_INDEX_NO = "0";
/**
* Enable Tag Flagging
*/
public static final String ENABLE_TAG_FLAGGING_YES = "y";
/**
* Do not enable Tag Flagging
*/
public static final String ENABLE_TAG_FLAGGING_NO = "n";
/**
* Always display the original images
*/
public static final String ALWAYS_ORIGINAL_YES = "y";
/**
* Do not Always display the original images
*/
public static final String ALWAYS_ORIGINAL_NO = "n";
/**
* Enable the Multi-Page Viewe
*/
public static final String MULTI_PAGE_YES = "y";
/**
* Do not enable the Multi-Page Viewe
*/
public static final String MULTI_PAGE_NO = "n";
/**
* Align left, only scale if image is larger than browser width
*/
public static final String MULTI_PAGE_STYLE_N = "n";
/**
* Align center, only scale if image is larger than browser width
*/
public static final String MULTI_PAGE_STYLE_C = "c";
/**
* Align center, Always scale images to fit browser width
*/
public static final String MULTI_PAGE_STYLE_Y = "y";
/**
* Show Multi-Page Viewer Thumbnail Pane
*/
public static final String MULTI_PAGE_THUMB_SHOW = "n";
/**
* Hide Multi-Page Viewer Thumbnail Pane
*/
public static final String MULTI_PAGE_THUMB_HIDE = "y";
/**
* 460x for lofi resolution
*/
public static final String LOFI_RESOLUTION_460X = "1";
/**
* 780X for lofi resolution
*/
public static final String LOFI_RESOLUTION_780X = "2";
/**
* 980X for lofi resolution
*/
public static final String LOFI_RESOLUTION_980X = "3";
/**
* show warning
*/
private static final String CONTENT_WARNING_SHOW = "0";
/**
* not show warning
*/
private static final String CONTENT_WARNING_NOT_SHOW = "1";
/**
* Load images through the Hentai@Home Network<br/>
* key: {@link #KEY_LOAD_FROM_HAH}<br/>
* value: {@link #LOAD_FROM_HAH_YES}, {@link #LOAD_FROM_HAH_NO}
*/
public String loadFromHAH = LOAD_FROM_HAH_YES;
/**
* Scale width<br/>
* key: {@link #KEY_SCALE_WIDTH}<br/>
* value: 0 for no limit
*/
public int scaleWidth = 0;
/**
* Scale height<br/>
* key: {@link #KEY_SCALE_HEIGHT}<br/>
* value: 0 for no limit
*/
public int scaleHeight = 0;
/**
* The default behavior for downloading an archiver<br/>
* key: {@link #KEY_ARCHIVER_DOWNLOAD}<br/>
* value: {@link #ARCHIVER_DOWNLOAD_MAMS}, {@link #ARCHIVER_DOWNLOAD_AAMS},
* {@link #ARCHIVER_DOWNLOAD_MAAS}, {@link #ARCHIVER_DOWNLOAD_AAAS}
*/
public String archiverDownload = ARCHIVER_DOWNLOAD_MAMS;
/**
* Display mode used on the front and search pages<br/>
* false for list, true for thumb<br/>
* key: {@link #KEY_LAYOUT_MODE}<br/>
* value: {@link #LAYOUT_MODE_LIST}, {@link #LAYOUT_MODE_THUMB}
*/
public String layoutMode = LAYOUT_MODE_LIST;
/**
* Default categories on the front page<br/>
* key: {@link #KEY_DEFAULT_CATEGORIES}<br/>
* value: the value of categories, for multiple use & operation,
* 0 for none
*/
public int defaultCategories = 0;
/**
* Certain namespaces excluded from a default tag search<br/>
* key: {@link #KEY_EXCLUDED_NAMESPACES}<br/>
* value: the value of namespaces, for multiple use & operation,
* 0 for none
*/
public int excludedNamespaces = 0;
/**
* Certain languages excluded from list and searches<br/>
* key: {@link #KEY_EXCLUDED_LANGUAGES}<br/>
* value: {@link #JAPANESE_TRANSLATED}, {@link #JAPANESE_REWRITE} ...
* For multiple languages, use <code>x<code/> to combine them, like 1x1024x2048
*/
public String excludedLanguages = "";
/**
* How many results would you like per page for the index/search page
* and torrent search pages<br/>
* key: {@link #KEY_RESULT_COUNT}<br/>
* value: {@link #RESULT_COUNT_25}, {@link #RESULT_COUNT_50},
* {@link #RESULT_COUNT_100}, {@link #RESULT_COUNT_200}<br/>
* Require <code>Hath Perk:Paging Enlargement</code>
*/
public String resultCount = RESULT_COUNT_25;
/**
* mouse-over thumb<br/>
* key: {@link #KEY_MOUSE_OVER}<br/>
* value: {@link #MOUSE_OVER_YES}, {@link #MOUSE_OVER_NO}
*/
public String mouseOver = MOUSE_OVER_YES;
/**
* Default preview mode<br/>
* key: {@link #KEY_PREVIEW_SIZE}<br/>
* value: {@link #PREVIEW_SIZE_NORMAL}, {@link #PREVIEW_SIZE_LARGE}
*/
public String previewSize = PREVIEW_SIZE_NORMAL;
/**
* Preview row<br/>
* key: {@link #KEY_PREVIEW_ROW}<br/>
* value: {@link #PREVIEW_ROW_4}, {@link #PREVIEW_ROW_10},
* {@link #PREVIEW_ROW_20}, {@link #PREVIEW_ROW_40}
*/
public String previewRow = PREVIEW_ROW_4;
/**
* Sort order for gallery comments<br/>
* key: {@link #KEY_COMMENTS_SORT}<br/>
* value: {@link #COMMENTS_SORT_OLDEST_FIRST}, {@link #COMMENTS_SORT_RECENT_FIRST},
* {@link #COMMENTS_SORT_HIGHEST_SCORE_FIRST}
*/
public String commentSort = COMMENTS_SORT_OLDEST_FIRST;
/**
* Show gallery comment votes mode<br/>
* key: {@link #KEY_COMMENTS_VOTES}<br/>
* value: {@link #COMMENTS_VOTES_POP}, {@link #COMMENTS_VOTES_ALWAYS}
*/
public String commentVotes = COMMENTS_VOTES_POP;
/**
* Sort order for gallery tags<br/>
* key: {@link #KEY_TAGS_SORT}<br/>
* value: {@link #TAGS_SORT_ALPHABETICAL}, {@link #TAGS_SORT_POWER}
*/
public String tagSort = TAGS_SORT_ALPHABETICAL;
/**
* Show gallery page numbers<br/>
* key: {@link #KEY_SHOW_GALLERY_INDEX}<br/>
* value: {@link #SHOW_GALLERY_INDEX_YES}, {@link #SHOW_GALLERY_INDEX_NO}
*/
public String showGalleryIndex = SHOW_GALLERY_INDEX_YES;
/**
* The IP of a proxy-enabled Hentai@Home Client
* to load all images<br/>
* key: {@link #KEY_HAH_CLIENT_IP_PORT}<br/>
*/
public String hahClientIp = "";
/**
* The PORT of a proxy-enabled Hentai@Home Client
* to load all images<br/>
* key: {@link #KEY_HAH_CLIENT_IP_PORT}<br/>
*/
public int hahClientPort = -1;
/**
* The passkey of a proxy-enabled Hentai@Home Client
* to load all images<br/>
* key: {@link #KEY_HAH_CLIENT_PASSKEY}<br/>
*/
public String hahClientPasskey = "";
/**
* Enable tag flagging
* key: {@link #KEY_ENABLE_TAG_FLAGGING}<br/>
* value: {@link #ENABLE_TAG_FLAGGING_YES}, {@link #ENABLE_TAG_FLAGGING_NO}<br/>
* <code>Bronze Star</code> or <code>Hath Perk: Tag Flagging</code> Required
*/
public String enableTagFlagging = ENABLE_TAG_FLAGGING_NO;
/**
* Always display the original images instead of the resampled versions<br/>
* key: {@link #KEY_ALWAYS_ORIGINAL}<br/>
* value: {@link #ALWAYS_ORIGINAL_YES}, {@link #ALWAYS_ORIGINAL_NO}<br/>
* <code>Silver Star</code> or <code>Hath Perk: Source Nexus</code> Required
*/
public String alwaysOriginal = ALWAYS_ORIGINAL_NO;
/**
* Enable the multi-Page Viewer<br/>
* key: {@link #KEY_MULTI_PAGE}<br/>
* value: {@link #MULTI_PAGE_YES}, {@link #MULTI_PAGE_NO}<br/>
* <code>Gold Star</code> or <code>Hath Perk: Multi-Page Viewer</code> Required
*/
public String multiPage = MULTI_PAGE_NO;
/**
* Multi-Page Viewer Display Style<br/>
* key: {@link #KEY_MULTI_PAGE_STYLE}<br/>
* value: {@link #MULTI_PAGE_STYLE_N}, {@link #MULTI_PAGE_STYLE_C},
* {@link #MULTI_PAGE_STYLE_Y}<br/>
* <code>Gold Star</code> or <code>Hath Perk: Multi-Page Viewer</code> Required
*/
public String multiPageStyle = MULTI_PAGE_STYLE_N;
/**
* Multi-Page Viewer Thumbnail Pane<br/>
* key: {@link #KEY_MULTI_PAGE_THUMB}<br/>
* value: {@link #MULTI_PAGE_THUMB_SHOW}, {@link #MULTI_PAGE_THUMB_HIDE}<br/>
* <code>Gold Star</code> or <code>Hath Perk: Multi-Page Viewer</code> Required
*/
public String multiPageThumb = MULTI_PAGE_THUMB_SHOW;
/**
* Lofi resolution
* key: {@link #KEY_LOFI_RESOLUTION}<br/>
* value: {@link #LOFI_RESOLUTION_460X}, {@link #LOFI_RESOLUTION_780X},
* {@link #LOFI_RESOLUTION_980X}
*/
public String lofiResolution = LOFI_RESOLUTION_980X;
/**
* Show content warning
* key: {@link #KEY_CONTENT_WARNING}<br/>
* value: {@link #CONTENT_WARNING_SHOW}, {@link #CONTENT_WARNING_NOT_SHOW}
*/
public String contentWarning = CONTENT_WARNING_NOT_SHOW;
private String mUconfig;
private boolean mDirty = true;
@Override
public EhConfig clone() {
try {
return (EhConfig) super.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException(e);
}
}
public void setDirty() {
mDirty = true;
}
private void updateUconfig() {
String hahClientIpPort = (TextUtils.isEmpty(hahClientIp) || hahClientPort == -1) ?
"" : hahClientIp + "%3A" + hahClientPort;
mUconfig = KEY_LOAD_FROM_HAH + "_" + loadFromHAH + "-" +
KEY_SCALE_WIDTH + "_" + scaleWidth + "-" +
KEY_SCALE_HEIGHT + "_" + scaleHeight + "-" +
KEY_ARCHIVER_DOWNLOAD + "_" + archiverDownload + "-" +
KEY_LAYOUT_MODE + "_" + layoutMode + "-" +
KEY_DEFAULT_CATEGORIES + "_" + defaultCategories + "-" +
KEY_EXCLUDED_NAMESPACES + "_" + excludedNamespaces + "-" +
KEY_EXCLUDED_LANGUAGES + "_" + excludedLanguages + "-" +
KEY_RESULT_COUNT + "_" + resultCount + "-" +
KEY_MOUSE_OVER + "_" + mouseOver + "-" +
KEY_PREVIEW_SIZE + "_" + previewSize + "-" +
KEY_PREVIEW_ROW + "_" + previewRow + "-" +
KEY_COMMENTS_SORT + "_" + commentSort + "-" +
KEY_COMMENTS_VOTES + "_" + commentVotes + "-" +
KEY_TAGS_SORT + "_" + tagSort + "-" +
KEY_SHOW_GALLERY_INDEX + "_" + showGalleryIndex + "-" +
KEY_HAH_CLIENT_IP_PORT + "_" + hahClientIpPort + "-" +
KEY_HAH_CLIENT_PASSKEY + "_" + hahClientPasskey + "-" +
KEY_ENABLE_TAG_FLAGGING + "_" + enableTagFlagging + "-" +
KEY_ALWAYS_ORIGINAL + "_" + alwaysOriginal + "-" +
KEY_MULTI_PAGE + "_" + multiPage + "-" +
KEY_MULTI_PAGE_STYLE + "_" + multiPageStyle + "-" +
KEY_MULTI_PAGE_THUMB + "_" + multiPageThumb;
}
/*
public void fillCookie(Cookie cookie) {
if (mDirty) {
mDirty = false;
updateUconfig();
}
cookie.put(KEY_UCONFIG, mUconfig);
cookie.put(KEY_LOFI_RESOLUTION, lofiResolution);
cookie.put(KEY_CONTENT_WARNING, contentWarning);
}
*/
}
@@ -0,0 +1,84 @@
/*
* 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;
import com.hippo.okhttp.CookieDB;
import com.hippo.okhttp.CookieDBStore;
import java.util.ArrayList;
import java.util.List;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
public class EhCookieStore extends CookieDBStore {
private static final String KEY_IPD_MEMBER_ID = "ipb_member_id";
private static final String KEY_IPD_PASS_HASH = "ipb_pass_hash";
private static final String KEY_YAY = "yay";
public void cleanUpForSignIn() {
remove(EhUrl.HOST_E, KEY_IPD_MEMBER_ID);
remove(EhUrl.HOST_E, KEY_IPD_PASS_HASH);
remove(EhUrl.HOST_EX, KEY_IPD_MEMBER_ID);
remove(EhUrl.HOST_EX, KEY_IPD_PASS_HASH);
remove(EhUrl.HOST_EX, KEY_YAY);
}
public boolean hasSignedIn() {
return contain(EhUrl.HOST_EX, KEY_IPD_MEMBER_ID) &&
contain(EhUrl.HOST_EX, KEY_IPD_PASS_HASH);
}
private Cookie newCookie(Cookie cookie, String newDomain) {
Cookie.Builder builder = new Cookie.Builder();
builder.name(cookie.name());
builder.value(cookie.value());
if (cookie.persistent()) {
builder.expiresAt(cookie.expiresAt());
}
if (cookie.hostOnly()) {
builder.hostOnlyDomain(newDomain);
} else {
builder.domain(newDomain);
}
builder.path(cookie.path());
if (cookie.secure()) {
builder.secure();
}
if (cookie.httpOnly()) {
builder.httpOnly();
}
return builder.build();
}
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
List<Cookie> cs = new ArrayList<>(cookies.size() + 2);
for (Cookie cookie: cookies) {
cs.add(cookie);
if (EhUrl.HOST_E.equals(cookie.domain()) &&
(KEY_IPD_MEMBER_ID.equals(cookie.name()) ||
KEY_IPD_PASS_HASH.equals(cookie.name()))) {
cs.add(newCookie(cookie, CookieDB.cookiesDomain(EhUrl.HOST_EX)));
}
}
super.saveFromResponse(url, cs);
}
}
@@ -0,0 +1,85 @@
/*
* 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;
import android.util.Log;
import com.hippo.ehviewer.client.parser.SignInParser;
import com.hippo.network.StatusCodeException;
import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class EhEngine {
private static final String TAG = EhEngine.class.getSimpleName();
public static final String UNKNOWN = "Unknown";
private static void throwException(Call call, int code, String body, Exception e) throws Exception {
if (call.isCanceled()) {
throw new CancelledException();
}
if (e instanceof EhException) {
if (!UNKNOWN.equals(e.getMessage())) {
throw e;
}
}
if (!body.contains("<")) {
throw new EhException(body);
}
if (code >= 400) {
throw new StatusCodeException(code);
}
}
public static Call prepareSignIn(OkHttpClient okHttpClient, String username, String password) {
FormBody.Builder builder = new FormBody.Builder()
.add("UserName", username)
.add("PassWord", password)
.add("submit", "Log me in")
.add("CookieDate", "1")
.add("temporary_https", "off");
String url = EhUrl.API_SIGN_IN;
Log.d(TAG, url);
Request request = new EhRequestBuilder(url)
.post(builder.build())
.build();
return okHttpClient.newCall(request);
}
public static String doSignIn(Call call) throws Exception {
String body = null;
int code = -1;
try {
Response response = call.execute();
code = response.code();
body = response.body().string();
return SignInParser.parse(body);
} catch (Exception e) {
throwException(call, code, body, e);
throw e;
}
}
}
@@ -0,0 +1,24 @@
/*
* 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;
public class EhException extends Exception {
public EhException(String detailMessage) {
super(detailMessage);
}
}
@@ -0,0 +1,57 @@
/*
* 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;
public class EhRequest {
int method;
Object[] args;
EhClient.Callback callback;
EhClient.Task task;
private boolean mCancel = false;
public EhRequest setMethod(int method) {
this.method = method;
return this;
}
public EhRequest setArgs(Object... args) {
this.args = args;
return this;
}
public EhRequest setCallback(EhClient.Callback callback) {
this.callback = callback;
return this;
}
public void cancel() {
if (!mCancel) {
mCancel = true;
if (task != null) {
task.stop();
task = null;
}
}
}
public boolean isCancelled() {
return mCancel;
}
}
@@ -0,0 +1,29 @@
/*
* 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;
import com.hippo.okhttp.ChromeRequestBuilder;
public class EhRequestBuilder extends ChromeRequestBuilder {
public EhRequestBuilder(String url) {
super(url);
}
}
@@ -0,0 +1,30 @@
/*
* 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;
public class EhUrl {
public static final String HOST_EX = "exhentai.org";
public static final String HOST_E = "e-hentai.org";
public static final String API_SIGN_IN = "http://forums.e-hentai.org/index.php?act=Login&CODE=01";
}
@@ -0,0 +1,30 @@
/*
* 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;
import android.content.Context;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.Settings;
public class EhUtils {
public static boolean hasSignedIn(Context context) {
return EhApplication.getEhCookieStore(context).hasSignedIn() &&
Settings.getDisplayName() != null;
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.parser;
import com.hippo.ehviewer.client.EhEngine;
import com.hippo.ehviewer.client.EhException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SignInParser {
private static final Pattern NAME_PATTERN = Pattern.compile("<p>You are now logged in as: (.+?)<");
private static final Pattern ERROR_PATTERN = Pattern.compile(
"(?:<h4>The error returned was:</h4>\\s*<p>(.+?)</p>)"
+ "|(?:<span class=\"postcolor\">(.+?)</span>)");
public static String parse(String body) throws Exception {
Matcher m = NAME_PATTERN.matcher(body);
if (m.find()) {
return m.group(1);
} else {
m = ERROR_PATTERN.matcher(body);
if (m.find()) {
throw new EhException(m.group(1) == null ? m.group(2) : m.group(1));
} else {
throw new EhException(EhEngine.UNKNOWN);
}
}
}
}
@@ -0,0 +1,100 @@
/*
* 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.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.NavigationView;
import android.support.v4.widget.DrawerLayout;
import android.view.MenuItem;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.ui.scene.GalleryListScene;
import com.hippo.ehviewer.ui.scene.LoginScene;
import com.hippo.ehviewer.ui.scene.WarningScene;
import com.hippo.scene.StageActivity;
public final class MainActivity extends StageActivity
implements NavigationView.OnNavigationItemSelectedListener {
private DrawerLayout mDrawerLayout;
@Override
public int getContainerViewId() {
return R.id.fragment_container;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDrawerLayout = (DrawerLayout) findViewById(R.id.draw_view);
NavigationView navView = (NavigationView) findViewById(R.id.nav_view);
navView.setNavigationItemSelectedListener(this);
if (savedInstanceState == null) {
onInit();
}
}
public void setDrawerLayoutEnable(boolean enable) {
if (enable) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
} else {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
}
}
private void onInit() {
if (Settings.getShowWarning()) {
setDrawerLayoutEnable(false);
startScene(WarningScene.class);
} else if (!EhUtils.hasSignedIn(this)) {
setDrawerLayoutEnable(false);
startScene(LoginScene.class);
} else {
setDrawerLayoutEnable(true);
startScene(GalleryListScene.class);
}
}
@Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
if (id == R.id.nav_homepage) {
} else if (id == R.id.nav_whats_hot) {
} else if (id == R.id.nav_favourite) {
} else if (id == R.id.nav_history) {
} else if (id == R.id.nav_download) {
} else if (id == R.id.nav_settings) {
}
return true;
}
}
@@ -0,0 +1,32 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.support.v4.app.FragmentActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
public class BaseScene extends SceneFragment {
public void setDrawerLayoutEnable(boolean enable) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).setDrawerLayoutEnable(enable);
}
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.hippo.ehviewer.R;
public class GalleryListScene extends BaseScene {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_gallery_list, container, false);
return view;
}
}
@@ -0,0 +1,227 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Paint;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v7.app.AlertDialog;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.utils.ExceptionUtils;
public final class LoginScene extends BaseScene implements EditText.OnEditorActionListener,
View.OnClickListener {
private View mProgress;
private TextInputLayout mUsernameLayout;
private TextInputLayout mPasswordLayout;
private EditText mUsername;
private EditText mPassword;
private View mRegister;
private View mSignIn;
private TextView mSignInViaWebview;
private TextView mSignInViaCookies;
private EhClient mClient;
private EhCookieStore mCookieStore;
private EhRequest mSignInRequest;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mClient = EhApplication.getEhClient(getContext());
mCookieStore = EhApplication.getEhCookieStore(getContext());
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_login, container, false);
View loginForm = view.findViewById(R.id.login_form);
mProgress = view.findViewById(R.id.progress);
mUsernameLayout = (TextInputLayout) loginForm.findViewById(R.id.username_layout);
mUsername = mUsernameLayout.getEditText();
mPasswordLayout = (TextInputLayout) loginForm.findViewById(R.id.password_layout);
mPassword = mPasswordLayout.getEditText();
mRegister = loginForm.findViewById(R.id.register);
mSignIn = loginForm.findViewById(R.id.sign_in);
mSignInViaWebview = (TextView) loginForm.findViewById(R.id.sign_in_via_webview);
mSignInViaCookies = (TextView) loginForm.findViewById(R.id.sign_in_via_cookies);
mSignInViaWebview.setPaintFlags(mSignInViaWebview.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
mSignInViaCookies.setPaintFlags(mSignInViaCookies.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
mPassword.setOnEditorActionListener(this);
mRegister.setOnClickListener(this);
mSignIn.setOnClickListener(this);
mSignInViaWebview.setOnClickListener(this);
mSignInViaCookies.setOnClickListener(this);
return view;
}
@Override
public void onDestroy() {
super.onDestroy();
if (mSignInRequest != null) {
mSignInRequest.cancel();
mSignInRequest = null;
}
}
@Override
public void onClick(View v) {
if (mRegister == v) {
} else if (mSignIn == v) {
signIn();
} else if (mSignInViaWebview == v) {
} else if (mSignInViaCookies == v) {
}
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (v == mPassword) {
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
signIn();
return true;
}
}
return false;
}
private void signIn() {
if (mSignInRequest != null) {
return;
}
String username = mUsername.getText().toString();
String password = mPassword.getText().toString();
if (username.isEmpty()) {
mUsernameLayout.setError(getString(R.string.error_username_cannot_empty));
return;
} else {
mUsernameLayout.setError(null);
}
if (password.isEmpty()) {
mPasswordLayout.setError(getString(R.string.error_password_cannot_empty));
return;
} else {
mPasswordLayout.setError(null);
}
// Hide ime keyboard
View view = getActivity().getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
mProgress.setAlpha(0.0f);
mProgress.setVisibility(View.VISIBLE);
mProgress.animate().alpha(1.0f).setDuration(500).start();
// Clean up for sign in
mCookieStore.cleanUpForSignIn();
mSignInRequest = new EhRequest()
.setMethod(EhClient.METHOD_SIGN_IN)
.setArgs(username, password)
.setCallback(new EhClient.Callback<String>() {
@Override
public void onSuccess(String result) {
mSignInRequest = null;
Settings.putDisplayName(result);
if (mCookieStore.hasSignedIn()) {
// Has signed in
redirectTo();
} else {
mProgress.setVisibility(View.GONE);
whetherToSkip();
}
}
@Override
public void onFailure(Exception e) {
mSignInRequest = null;
mProgress.setVisibility(View.GONE);
Toast.makeText(getContext(), ExceptionUtils.getReadableString(getContext(), e),
Toast.LENGTH_SHORT).show();
whetherToSkip();
}
@Override
public void onCancel() {
mSignInRequest = null;
// User close this Scene
}
});
mClient.execute(mSignInRequest);
}
private void redirectTo() {
startScene(GalleryListScene.class);
finish();
// Enable drawer
setDrawerLayoutEnable(true);
}
private void whetherToSkip() {
new AlertDialog.Builder(getContext())
.setTitle(R.string.skip_signing_in)
.setMessage(R.string.skip_signing_in_plain)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == AlertDialog.BUTTON_POSITIVE) {
redirectTo();
}
}
}).show();
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.vector.VectorDrawable;
import com.hippo.widget.SimpleImageView;
public final class WarningScene extends BaseScene implements View.OnClickListener {
private View mCancel;
private View mOk;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_warning, container, false);
SimpleImageView alert = (SimpleImageView) view.findViewById(R.id.icon_alert);
mCancel = view.findViewById(R.id.cancel);
mOk = view.findViewById(R.id.ok);
Drawable drawable = VectorDrawable.create(getContext(), R.drawable.ic_alert);
alert.setDrawable(drawable);
mCancel.setOnClickListener(this);
mOk.setOnClickListener(this);
return view;
}
@Override
public void onClick(View v) {
if (mCancel == v) {
finishStage();
} else if (mOk == v) {
// Never show this warning anymore
Settings.putShowWarning(false);
if (EhUtils.hasSignedIn(getContext())) {
startScene(GalleryListScene.class);
finish();
// Enable drawer
setDrawerLayoutEnable(true);
} else {
startScene(LoginScene.class);
finish();
}
}
}
}
@@ -0,0 +1,90 @@
/*
* 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.network;
import android.content.Context;
import android.content.res.Resources;
import android.util.SparseArray;
import com.hippo.ehviewer.R;
public class StatusCodeException extends Exception {
private static final SparseArray<String> ERROR_MESSAGE_ARRAY = new SparseArray<>(24);
private static final String DEFAULT_ERROR_MESSAGE = "Error response code";
private final int mResponseCode;
private final String mMessage;
public static void initialize(Context context) {
Resources resources = context.getApplicationContext().getResources();
ERROR_MESSAGE_ARRAY.append(400, resources.getString(R.string.error_status_code_400));
ERROR_MESSAGE_ARRAY.append(401, resources.getString(R.string.error_status_code_401));
ERROR_MESSAGE_ARRAY.append(402, resources.getString(R.string.error_status_code_402));
ERROR_MESSAGE_ARRAY.append(403, resources.getString(R.string.error_status_code_403));
ERROR_MESSAGE_ARRAY.append(404, resources.getString(R.string.error_status_code_404));
ERROR_MESSAGE_ARRAY.append(405, resources.getString(R.string.error_status_code_405));
ERROR_MESSAGE_ARRAY.append(406, resources.getString(R.string.error_status_code_406));
ERROR_MESSAGE_ARRAY.append(407, resources.getString(R.string.error_status_code_407));
ERROR_MESSAGE_ARRAY.append(408, resources.getString(R.string.error_status_code_408));
ERROR_MESSAGE_ARRAY.append(409, resources.getString(R.string.error_status_code_409));
ERROR_MESSAGE_ARRAY.append(410, resources.getString(R.string.error_status_code_410));
ERROR_MESSAGE_ARRAY.append(411, resources.getString(R.string.error_status_code_411));
ERROR_MESSAGE_ARRAY.append(412, resources.getString(R.string.error_status_code_412));
ERROR_MESSAGE_ARRAY.append(413, resources.getString(R.string.error_status_code_413));
ERROR_MESSAGE_ARRAY.append(414, resources.getString(R.string.error_status_code_414));
ERROR_MESSAGE_ARRAY.append(415, resources.getString(R.string.error_status_code_415));
ERROR_MESSAGE_ARRAY.append(416, resources.getString(R.string.error_status_code_416));
ERROR_MESSAGE_ARRAY.append(417, resources.getString(R.string.error_status_code_417));
ERROR_MESSAGE_ARRAY.append(500, resources.getString(R.string.error_status_code_500));
ERROR_MESSAGE_ARRAY.append(501, resources.getString(R.string.error_status_code_501));
ERROR_MESSAGE_ARRAY.append(502, resources.getString(R.string.error_status_code_502));
ERROR_MESSAGE_ARRAY.append(503, resources.getString(R.string.error_status_code_503));
ERROR_MESSAGE_ARRAY.append(504, resources.getString(R.string.error_status_code_504));
ERROR_MESSAGE_ARRAY.append(505, resources.getString(R.string.error_status_code_505));
}
public StatusCodeException(int responseCode) {
mResponseCode = responseCode;
mMessage = ERROR_MESSAGE_ARRAY.get(responseCode, DEFAULT_ERROR_MESSAGE);
}
public StatusCodeException(int responseCode, String message) {
mResponseCode = responseCode;
mMessage = message;
}
public int getResponseCode() {
return mResponseCode;
}
public boolean isIdentifiedResponseCode() {
return !DEFAULT_ERROR_MESSAGE.equals(mMessage);
}
@Override
public String getMessage() {
return mMessage;
}
@Override
public String getLocalizedMessage() {
return getMessage();
}
}
@@ -0,0 +1,41 @@
/*
* 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.okhttp;
import java.net.MalformedURLException;
import java.net.URL;
import okhttp3.Request;
public class ChromeRequestBuilder extends Request.Builder {
private static final String CHROME_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36";
public ChromeRequestBuilder(String url) {
URL u;
try {
u = new URL(url);
} catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
url(u);
addHeader("User-Agent", CHROME_USER_AGENT);
}
}
@@ -0,0 +1,158 @@
/*
* 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.okhttp;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import com.hippo.okhttp.dao.DaoMaster;
import com.hippo.okhttp.dao.DaoSession;
import com.hippo.okhttp.dao.OkHttp3CookieDao;
import com.hippo.okhttp.dao.OkHttp3CookieRaw;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.Cookie;
public class CookieDB {
private static DaoSession sDaoSession;
public static class DBOpenHelper extends DaoMaster.OpenHelper {
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
public static void initialize(Context context) {
DBOpenHelper helper = new DBOpenHelper(
context.getApplicationContext(), "okhttp3-cookie.db", null);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
sDaoSession = daoMaster.newSession();
}
public static String cookiesDomain(URL url) {
return cookiesDomain(url.getHost());
}
public static String cookiesDomain(String domain) {
if (TextUtils.isEmpty(domain)) {
return "";
}
int count = 0;
for (int i = domain.length() - 1; i >= 0; i--) {
char ch = domain.charAt(i);
if (ch == '.') {
count++;
}
if (count == 2) {
return domain.substring(i + 1);
}
}
return domain;
}
public static Map<String, List<CookieWithID>> getAllCookies() {
OkHttp3CookieDao dao = sDaoSession.getOkHttp3CookieDao();
List<OkHttp3CookieRaw> list = dao.queryBuilder().list();
long now = System.currentTimeMillis();
Map<String, List<CookieWithID>> result = new HashMap<>();
for (OkHttp3CookieRaw raw : list) {
// Check expired
if (!raw.getPersistent() || raw.getExpiresAt() <= now) {
dao.delete(raw);
continue;
}
// Avoid null error
String name = raw.getName();
String value = raw.getValue();
String domain = raw.getDomain();
if (name == null || value == null || domain == null) {
dao.delete(raw);
continue;
}
Cookie.Builder builder = new Cookie.Builder();
builder.name(name);
builder.value(value);
builder.expiresAt(raw.getExpiresAt());
if (raw.getHostOnly()) {
builder.hostOnlyDomain(domain);
} else {
builder.domain(raw.getDomain());
}
builder.path(raw.getPath());
if (raw.getSecure()) {
builder.secure();
}
if (raw.getHttpOnly()) {
builder.httpOnly();
}
String cookiesDomain = cookiesDomain(domain);
List<CookieWithID> cookies = result.get(cookiesDomain);
if (cookies == null) {
cookies = new ArrayList<>();
result.put(cookiesDomain, cookies);
}
cookies.add(new CookieWithID(raw.getId(), builder.build()));
}
return result;
}
public static void removeCookie(long id) {
sDaoSession.getOkHttp3CookieDao().deleteByKey(id);
}
public static long addCookie(Cookie cookie) {
OkHttp3CookieDao dao = sDaoSession.getOkHttp3CookieDao();
OkHttp3CookieRaw raw = new OkHttp3CookieRaw();
raw.setName(cookie.name());
raw.setValue(cookie.value());
raw.setExpiresAt(cookie.expiresAt());
raw.setDomain(cookie.domain());
raw.setPath(cookie.path());
raw.setSecure(cookie.secure());
raw.setHttpOnly(cookie.httpOnly());
raw.setPersistent(cookie.persistent());
raw.setHostOnly(cookie.hostOnly());
return dao.insert(raw);
}
}
@@ -0,0 +1,176 @@
/*
* 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.okhttp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
import okhttp3.Request;
public class CookieDBStore implements CookieJar {
private final Map<String, List<CookieWithID>> map;
public CookieDBStore() {
map = CookieDB.getAllCookies();
}
private boolean hasExpired(Cookie cookie) {
return cookie.expiresAt() < System.currentTimeMillis();
}
private static CookieWithID removeCookie(List<CookieWithID> list, Cookie cookie) {
for (int i = 0, n = list.size(); i < n; i++) {
CookieWithID cwi = list.get(i);
if (cwi.cookie.name().equals(cookie.name())) {
list.remove(i);
return cwi;
}
}
return null;
}
private static CookieWithID removeCookie(List<CookieWithID> list, String name) {
for (int i = 0, n = list.size(); i < n; i++) {
CookieWithID cwi = list.get(i);
if (cwi.cookie.name().equals(name)) {
list.remove(i);
return cwi;
}
}
return null;
}
private boolean containInternal(String domain, String name) {
List<CookieWithID> cookies = map.get(domain);
if (cookies != null) {
for (int i = 0, n = cookies.size(); i < n; i++) {
CookieWithID cwi = cookies.get(i);
if (cwi.cookie.name().equals(name)) {
return true;
}
}
}
return false;
}
private synchronized void removeInternal(String domain, Cookie cookie) {
List<CookieWithID> cookies = map.get(domain);
if (cookies != null) {
// Remove from list
CookieWithID cwi = removeCookie(cookies, cookie);
if (cwi != null) {
// Remove from DB
CookieDB.removeCookie(cwi.id);
}
}
}
private synchronized void removeInternal(String domain, String name) {
List<CookieWithID> cookies = map.get(domain);
if (cookies != null) {
// Remove from list
CookieWithID cwi = removeCookie(cookies, name);
if (cwi != null) {
// Remove from DB
CookieDB.removeCookie(cwi.id);
}
}
}
private synchronized void addInternal(String domain, Cookie cookie) {
// Check expired
if (hasExpired(cookie)) {
removeInternal(domain, cookie);
return;
}
List<CookieWithID> cookies = map.get(domain);
if (cookies == null) {
cookies = new ArrayList<>();
map.put(domain, cookies);
} else {
// Remove from list
CookieWithID cwi = removeCookie(cookies, cookie);
if (cwi != null) {
// Remove from DB
CookieDB.removeCookie(cwi.id);
}
}
// Add to DB
long id = CookieDB.addCookie(cookie);
// Add to list
cookies.add(new CookieWithID(id, cookie));
}
public boolean contain(String url, String name) {
return containInternal(CookieDB.cookiesDomain(url), name);
}
public void remove(String url, String name) {
removeInternal(CookieDB.cookiesDomain(url), name);
}
public void add(Cookie cookie) {
addInternal(CookieDB.cookiesDomain(cookie.domain()), cookie);
}
public List<Cookie> get(HttpUrl url) {
List<Cookie> result = new ArrayList<>();
String domain = CookieDB.cookiesDomain(url.url());
List<CookieWithID> cookies = map.get(domain);
if (cookies != null) {
for (Iterator<CookieWithID> i = cookies.iterator(); i.hasNext();) {
CookieWithID cwi = i.next();
Cookie cookie = cwi.cookie;
// Check expired
if (hasExpired(cookie)) {
// Remove from list
i.remove();
// Remove from DB
CookieDB.removeCookie(cwi.id);
} else if (cookie.matches(url)) {
result.add(cookie);
}
}
}
return Collections.unmodifiableList(result);
}
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
for (Cookie cookie: cookies) {
add(cookie);
}
}
@Override
public List<Cookie> loadForRequest(HttpUrl url, Request request) {
return get(url);
}
}
@@ -0,0 +1,30 @@
/*
* 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.okhttp;
import okhttp3.Cookie;
public class CookieWithID {
public long id;
public Cookie cookie;
public CookieWithID(long id, Cookie cookie) {
this.id = id;
this.cookie = cookie;
}
}
@@ -0,0 +1,44 @@
/*
* 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.scene;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
public class SceneFragment extends Fragment {
public <T extends SceneFragment> void startScene(Class<T> clazz) {
FragmentActivity activity = getActivity();
if (activity instanceof StageActivity) {
((StageActivity) activity).startScene(clazz);
}
}
public void finish() {
FragmentActivity activity = getActivity();
if (activity instanceof StageActivity) {
((StageActivity) activity).finishScene(this);
}
}
public void finishStage() {
FragmentActivity activity = getActivity();
if (activity != null) {
activity.finish();
}
}
}
@@ -0,0 +1,163 @@
/*
* 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.scene;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.IdIntGenerator;
import java.util.ArrayList;
import java.util.Collections;
public abstract class StageActivity extends AppCompatActivity {
private static final String TAG = StageActivity.class.getSimpleName();
private static final String KEY_STAGE_ACTIVITY_SCENE_TAG_LIST = "stage_activity_scene_tag_list";
// TODO ArrayList or LinkedList
private ArrayList<String> mSceneTagList = new ArrayList<>();
private IdIntGenerator mIdGenerator = new IdIntGenerator();
public abstract int getContainerViewId();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
private <T extends SceneFragment> SceneFragment newSceneInstance(Class<T> clazz) {
try {
return clazz.newInstance();
} catch (InstantiationException e) {
throw new IllegalStateException("Can't instance " + clazz.getName(), e);
} catch (IllegalAccessException e) {
throw new IllegalStateException("The constructor of " +
clazz.getName() + " is not visible", e);
} catch (ClassCastException e) {
throw new IllegalStateException(clazz.getName() + " can not cast to scene", e);
}
}
public <T extends SceneFragment> void startScene(Class<T> clazz) {
startScene(clazz, null);
}
public <T extends SceneFragment> void startScene(Class<T> clazz, Bundle args) {
FragmentManager fragmentManager = getSupportFragmentManager();
// Old scene
Fragment old = null;
if (mSceneTagList.size() > 0) {
// Get last tag
String tag = mSceneTagList.get(mSceneTagList.size() - 1);
old = fragmentManager.findFragmentByTag(tag);
}
// New scene
SceneFragment scene = newSceneInstance(clazz);
scene.setArguments(args);
// New scene tag
String newTag = Integer.toString(mIdGenerator.nextId());
mSceneTagList.add(newTag);
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.fragment_translate_in, R.anim.fragment_translate_out);
if (old != null) {
transaction.detach(old);
}
transaction.add(getContainerViewId(), scene, newTag);
transaction.commit();
}
public void finishScene(SceneFragment scene) {
finishScene(scene.getTag());
}
private void finishScene(String tag) {
FragmentManager fragmentManager = getSupportFragmentManager();
// Get scene
Fragment scene = fragmentManager.findFragmentByTag(tag);
if (scene == null) {
Log.e(TAG, "finishScene: Can't find scene by tag: " + tag);
return;
}
// FIXME It will fail if mIdGenerator.nextId() called more than Integer.MAX_VALUE times
// Get scene index
int index = Collections.binarySearch(mSceneTagList, tag);
if (index < 0) {
Log.e(TAG, "finishScene: Can't find the tag in tag list: " + tag);
return;
}
if (mSceneTagList.size() == 1) {
// It is the last fragment, finish Activity now
Log.i(TAG, "finishScene: It is the last scene, finish activity now");
finish();
return;
}
Fragment next = null;
// It is first fragment, show the next one
if (index == mSceneTagList.size() - 1) {
next = fragmentManager.findFragmentByTag(mSceneTagList.get(index - 1));
}
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.fragment_translate_in, R.anim.fragment_translate_out);
transaction.remove(scene);
if (next != null && next.isDetached()) {
transaction.attach(next);
}
transaction.commit();
// Remove tag
mSceneTagList.remove(index);
}
@Override
public void onBackPressed() {
int size = mSceneTagList.size();
if (size > 0) {
finishScene(mSceneTagList.get(size - 1));
} else {
finish();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putStringArrayList(KEY_STAGE_ACTIVITY_SCENE_TAG_LIST, mSceneTagList);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
mSceneTagList = savedInstanceState.getStringArrayList(KEY_STAGE_ACTIVITY_SCENE_TAG_LIST);
}
}
@@ -0,0 +1,63 @@
/*
* 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.utils;
import android.content.Context;
import android.support.annotation.NonNull;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhException;
import com.hippo.network.StatusCodeException;
import org.apache.http.conn.ConnectTimeoutException;
import java.net.ProtocolException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
public final class ExceptionUtils {
private static final String TAG = ExceptionUtils.class.getSimpleName();
@NonNull
public static String getReadableString(@NonNull Context context, @NonNull Exception e) {
if (e instanceof ConnectTimeoutException ||
e instanceof SocketTimeoutException) {
return context.getString(R.string.error_timeout);
} else if (e instanceof UnknownHostException) {
return context.getString(R.string.error_unknown_host);
} else if (e instanceof StatusCodeException) {
StatusCodeException sce = (StatusCodeException) e;
StringBuilder sb = new StringBuilder();
sb.append(context.getString(R.string.error_bad_status_code, sce.getResponseCode()));
if (sce.isIdentifiedResponseCode()) {
sb.append(", ").append(sce.getMessage());
}
return sb.toString();
} else if (e instanceof ProtocolException && e.getMessage().startsWith("Too many follow-up requests:")) {
return context.getString(R.string.error_redirection);
} else if (e instanceof ProtocolException || e instanceof SocketException) {
return context.getString(R.string.error_socket);
} else if (e instanceof EhException) {
return e.getMessage();
} else {
return context.getString(R.string.error_unknown);
}
}
}
@@ -0,0 +1,93 @@
/*
* 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.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.AssertUtils;
public class MaxSizeContainer extends ViewGroup {
private int mMaxWidth;
private int mMaxHeight;
public MaxSizeContainer(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public MaxSizeContainer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MaxSizeContainer);
mMaxWidth = a.getDimensionPixelOffset(R.styleable.MaxSizeContainer_maxWidth, -1);
mMaxHeight = a.getDimensionPixelOffset(R.styleable.MaxSizeContainer_maxHeight, -1);
a.recycle();
}
private int getMeasureSpec(int measureSpec, int max) {
if (max < 0) {
return measureSpec;
}
int size = MeasureSpec.getSize(measureSpec);
int mode = MeasureSpec.getMode(measureSpec);
switch (mode) {
case MeasureSpec.AT_MOST:
size = Math.min(size, max);
break;
case MeasureSpec.UNSPECIFIED:
size = max;
mode = MeasureSpec.AT_MOST;
break;
}
return MeasureSpec.makeMeasureSpec(size, mode);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
AssertUtils.assertEquals("getChildCount() must be 1", 1, getChildCount());
View child = getChildAt(0);
if (child.getVisibility() != GONE) {
child.measure(getMeasureSpec(widthMeasureSpec, mMaxWidth),
getMeasureSpec(heightMeasureSpec, mMaxHeight));
setMeasuredDimension(child.getMeasuredWidth(), child.getMeasuredHeight());
} else {
setMeasuredDimension(0, 0);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
AssertUtils.assertEquals("getChildCount() must be 1", 1, getChildCount());
View child = getChildAt(0);
if (child.getVisibility() != GONE) {
child.layout(0, 0, r - l, b - t);
}
}
}
@@ -0,0 +1,317 @@
/*
* 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.widget;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import com.hippo.anani.PathInterpolator;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.ViewUtils;
import java.util.ArrayList;
// Base on android.graphics.drawable.MaterialProgressDrawable in L preview
public class ProgressView extends View {
private static final Interpolator TRIM_START_INTERPOLATOR;
private static final Interpolator TRIM_END_INTERPOLATOR;
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
static {
Path trimStartPath = new Path();
trimStartPath.moveTo(0.0f, 0.0f);
trimStartPath.lineTo(0.5f, 0.0f);
trimStartPath.cubicTo(0.7f, 0.0f, 0.6f, 1f, 1f, 1f);
TRIM_START_INTERPOLATOR = PathInterpolator.build(trimStartPath);
Path trimEndPath = new Path();
trimEndPath.moveTo(0.0f, 0.0f);
trimEndPath.cubicTo(0.2f, 0.0f, 0.1f, 1f, 0.5f, 1f);
trimEndPath.lineTo(1f, 1f);
TRIM_END_INTERPOLATOR = PathInterpolator.build(trimEndPath);
}
private final ArrayList<Animator> mAnimators = new ArrayList<>();
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final RectF mRectF = new RectF();
private boolean mIndeterminate;
private float mTrimStart = 0.0f;
private float mTrimEnd = 0.0f;
private float mTrimOffset = 0.0f;
private float mTrimRotation = 0.0f;
// It is a trick to avoid first sight stuck. Get it from ProgressBar
private boolean mShouldStartAnimationDrawable = false;
public ProgressView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public ProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProgressView);
int color = a.getColor(R.styleable.ProgressView_color, Color.BLACK);
mPaint.setColor(color);
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setStrokeJoin(Paint.Join.MITER);
mPaint.setStyle(Paint.Style.STROKE);
mIndeterminate = a.getBoolean(R.styleable.ProgressView_indeterminate, true);
setProgress(a.getFloat(R.styleable.ProgressView_progress, 0f));
a.recycle();
setupAnimators();
}
private void setupAnimators() {
ObjectAnimator trimStart = ObjectAnimator.ofFloat(this, "trimStart", 0.0f, 0.75f);
trimStart.setDuration(1333L);
trimStart.setInterpolator(TRIM_START_INTERPOLATOR);
trimStart.setRepeatCount(Animation.INFINITE);
ObjectAnimator trimEnd = ObjectAnimator.ofFloat(this, "trimEnd", 0.0f, 0.75f);
trimEnd.setDuration(1333L);
trimEnd.setInterpolator(TRIM_END_INTERPOLATOR);
trimEnd.setRepeatCount(Animation.INFINITE);
ObjectAnimator trimOffset = ObjectAnimator.ofFloat(this, "trimOffset", 0.0f, 0.25f);
trimOffset.setDuration(1333L);
trimOffset.setInterpolator(LINEAR_INTERPOLATOR);
trimOffset.setRepeatCount(Animation.INFINITE);
ObjectAnimator trimRotation = ObjectAnimator.ofFloat(this, "trimRotation", 0.0f, 720.0f);
trimRotation.setDuration(6665L);
trimRotation.setInterpolator(LINEAR_INTERPOLATOR);
trimRotation.setRepeatCount(Animation.INFINITE);
mAnimators.add(trimStart);
mAnimators.add(trimEnd);
mAnimators.add(trimOffset);
mAnimators.add(trimRotation);
}
private void startAnimation() {
mShouldStartAnimationDrawable = true;
postInvalidate();
}
private void startAnimationActually() {
ArrayList<Animator> animators = mAnimators;
int N = animators.size();
for (int i = 0; i < N; i++) {
Animator animator = animators.get(i);
if (!animator.isRunning()) {
animator.start();
}
}
}
private void stopAnimation() {
mShouldStartAnimationDrawable = false;
ArrayList<Animator> animators = mAnimators;
int N = animators.size();
for (int i = 0; i < N; i++) {
animators.get(i).cancel();
}
}
public boolean isRunning() {
ArrayList<Animator> animators = mAnimators;
int N = animators.size();
for (int i = 0; i < N; i++) {
Animator animator = animators.get(i);
if (animator.isRunning()) {
return true;
}
}
return false;
}
@SuppressWarnings("unused")
public float getTrimStart() {
return mTrimStart;
}
@SuppressWarnings("unused")
public void setTrimStart(float trimStart) {
mTrimStart = trimStart;
invalidate();
}
@SuppressWarnings("unused")
public float getTrimEnd() {
return mTrimEnd;
}
@SuppressWarnings("unused")
public void setTrimEnd(float trimEnd) {
mTrimEnd = trimEnd;
invalidate();
}
@SuppressWarnings("unused")
public float getTrimOffset() {
return mTrimOffset;
}
@SuppressWarnings("unused")
public void setTrimOffset(float trimOffset) {
mTrimOffset = trimOffset;
invalidate();
}
@SuppressWarnings("unused")
public float getTrimRotation() {
return mTrimRotation;
}
@SuppressWarnings("unused")
public void setTrimRotation(float trimRotation) {
mTrimRotation = trimRotation;
invalidate();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mIndeterminate && getVisibility() == VISIBLE) {
startAnimation();
}
}
@Override
protected void onDetachedFromWindow() {
if (mIndeterminate) {
stopAnimation();
}
// This should come after stopAnimation(), otherwise an invalidate message remains in the
// queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
super.onDetachedFromWindow();
}
@Override
public void setVisibility(int v) {
if (getVisibility() != v) {
super.setVisibility(v);
if (mIndeterminate) {
if (v == GONE || v == INVISIBLE) {
stopAnimation();
} else if (ViewCompat.isAttachedToWindow(this)) {
startAnimation();
}
}
}
}
@Override
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (mIndeterminate && ViewCompat.isAttachedToWindow(this)) {
if (visibility == GONE || visibility == INVISIBLE) {
stopAnimation();
} else if (ViewCompat.isAttachedToWindow(this)) {
startAnimation();
}
}
}
public void setColor(int color) {
mPaint.setColor(color);
invalidate();
}
public void setIndeterminate(boolean indeterminate) {
if (mIndeterminate != indeterminate) {
mIndeterminate = indeterminate;
if (indeterminate) {
if (isShown() && ViewCompat.isAttachedToWindow(this)) {
startAnimation();
}
} else {
stopAnimation();
}
}
}
public boolean isIndeterminate() {
return mIndeterminate;
}
public void setProgress(float progress) {
if (!mIndeterminate) {
mTrimStart = 0f;
mTrimEnd = progress;
mTrimOffset = 0f;
mTrimRotation = 0f;
invalidate();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(ViewUtils.getSuitableSize(getSuggestedMinimumWidth(), widthMeasureSpec),
ViewUtils.getSuitableSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mPaint.setStrokeWidth(Math.min(w, h) / 12.0f);
mRectF.set(0, 0, w, h);
mRectF.inset(w / 48.0f * 5.0f, h / 48.0f * 5.0f);
}
@Override
protected void onDraw(Canvas canvas) {
int saved = canvas.save();
canvas.rotate(mTrimRotation, mRectF.centerX(), mRectF.centerY());
float startAngle = (mTrimStart + mTrimOffset) * 360.0f - 90;
float sweepAngle = (mTrimEnd - mTrimStart) * 360.0f;
canvas.drawArc(mRectF, startAngle, sweepAngle, false, mPaint);
canvas.restoreToCount(saved);
if (mShouldStartAnimationDrawable) {
mShouldStartAnimationDrawable = false;
startAnimationActually();
}
}
}
@@ -0,0 +1,127 @@
/*
* Copyright (C) 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.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import com.hippo.yorozuya.ViewUtils;
public class SimpleImageView extends View implements Drawable.Callback {
private Drawable mDrawable;
private static final int[] LAYOUT_ATTRS = new int[] {
android.R.attr.src
};
public SimpleImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public SimpleImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
setDrawable(a.getDrawable(0));
a.recycle();
// Mostly drawable is VectorDrawable, it has cache already
setWillNotCacheDrawing(true);
}
@SuppressWarnings("deprecation")
public void setDrawable(@DrawableRes int resId) {
setDrawable(getContext().getResources().getDrawable(resId));
}
public void setDrawable(Drawable drawable) {
if (mDrawable != null) {
mDrawable.setCallback(null);
}
mDrawable = drawable;
if (drawable != null) {
drawable.setCallback(this);
if (drawable.isStateful()) {
drawable.setState(getDrawableState());
}
}
requestLayout();
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
Drawable d = mDrawable;
if (d != null && d.isStateful()) {
d.setState(getDrawableState());
}
}
@Override
public void invalidateDrawable(@NonNull Drawable who) {
invalidate();
}
@Override
protected int getSuggestedMinimumWidth() {
int superMinWidth = super.getSuggestedMinimumWidth();
return (mDrawable == null) ? superMinWidth :
Math.max(superMinWidth, mDrawable.getMinimumWidth() + getPaddingLeft() + getPaddingRight());
}
@Override
protected int getSuggestedMinimumHeight() {
int superMinHeight = super.getSuggestedMinimumHeight();
return (mDrawable == null) ? superMinHeight :
Math.max(superMinHeight, mDrawable.getMinimumHeight() + getPaddingTop() + getPaddingBottom());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(ViewUtils.getSuitableSize(getSuggestedMinimumWidth(), widthMeasureSpec),
ViewUtils.getSuitableSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
int width = right - left;
int height = bottom - top;
if (mDrawable != null) {
mDrawable.setBounds(getPaddingLeft(), getPaddingTop(),
width - getPaddingRight(), height - getPaddingBottom());
}
}
@Override
protected void onDraw(Canvas canvas) {
if (mDrawable != null) {
mDrawable.draw(canvas);
}
}
}