diff --git a/app/build.gradle b/app/build.gradle index 33810660f..ed3549aa5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.hippo.ehviewer" minSdkVersion 15 targetSdkVersion 23 - versionCode 1 - versionName "1.0" + versionCode 48 + versionName "0.9.0" } buildTypes { @@ -53,8 +53,8 @@ android { } clean { - //delete file('src/main/obj').absolutePath - //delete file('src/main/libs').absolutePath + delete file('src/main/obj').absolutePath + delete file('src/main/libs').absolutePath } dependencies { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 706f62c78..74a81e724 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -67,6 +67,11 @@ android:configChanges="screenSize|uiMode" android:theme="@style/AppTheme"/> + + diff --git a/app/src/main/java/com/hippo/ehviewer/EhApplication.java b/app/src/main/java/com/hippo/ehviewer/EhApplication.java index 38ae696ff..e22354641 100644 --- a/app/src/main/java/com/hippo/ehviewer/EhApplication.java +++ b/app/src/main/java/com/hippo/ehviewer/EhApplication.java @@ -22,6 +22,7 @@ import android.os.Debug; import android.support.annotation.NonNull; import android.util.Log; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.hippo.beerbelly.LruCacheEx; import com.hippo.conaco.Conaco; @@ -30,6 +31,8 @@ import com.hippo.ehviewer.client.EhClient; import com.hippo.ehviewer.client.EhCookieStore; import com.hippo.ehviewer.client.data.GalleryDetail; import com.hippo.ehviewer.client.data.LargePreviewSet; +import com.hippo.ehviewer.download.DownloadManager; +import com.hippo.ehviewer.download.DownloadService; import com.hippo.ehviewer.spider.SpiderDen; import com.hippo.network.StatusCodeException; import com.hippo.okhttp.CookieDB; @@ -37,12 +40,12 @@ import com.hippo.scene.SceneApplication; import com.hippo.text.Html; import com.hippo.util.ReadableTime; import com.hippo.yorozuya.FileUtils; -import com.hippo.yorozuya.IntIdGenerator; import com.hippo.yorozuya.SimpleHandler; import java.io.File; import java.util.Comparator; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import okhttp3.OkHttpClient; @@ -53,8 +56,8 @@ public class EhApplication extends SceneApplication { private static final boolean DEBUG_CONACO = false; private static final boolean DEBUG_NATIVE_MEMORY = false; - private IntIdGenerator mIdGenerator = new IntIdGenerator(); - private SparseArray mGlobalStuffMap = new SparseArray<>(); + private final AtomicInteger mIdGenerator = new AtomicInteger(); + private final SparseArray mGlobalStuffMap = new SparseArray<>(); private EhCookieStore mEhCookieStore; private EhClient mEhClient; private OkHttpClient mOkHttpClient; @@ -63,6 +66,13 @@ public class EhApplication extends SceneApplication { private LruCacheEx mGalleryDetailCache; private LruCacheEx mLargePreviewSetCache; private LruCacheEx mPreviewPagesCache; + private DownloadManager mDownloadManager; + + private SparseBooleanArray mItemStateArray; + private SparseArray mItemTitleArray; + private int mFailedCount; + private int mFinishedCount; + private int mDownloadedCount; @Override public void onCreate() { @@ -76,6 +86,11 @@ public class EhApplication extends SceneApplication { Html.initialize(this); AppConfig.initialize(this); SpiderDen.initialize(this); + EhDB.initialize(this); + + if (EhDB.needMerge()) { + EhDB.mergeOldDB(this); + } if (DEBUG_NATIVE_MEMORY) { debugNativeMemory(); @@ -94,7 +109,7 @@ public class EhApplication extends SceneApplication { } public int putGlobalStuff(@NonNull Object o) { - int id = mIdGenerator.nextId(); + int id = mIdGenerator.getAndDecrement(); mGlobalStuffMap.put(id, o); return id; } @@ -237,4 +252,43 @@ public class EhApplication extends SceneApplication { } return application.mPreviewPagesCache; } + + @NonNull + public static DownloadManager getDownloadManager(@NonNull Context context) { + EhApplication application = ((EhApplication) context.getApplicationContext()); + if (application.mDownloadManager == null) { + application.mDownloadManager = new DownloadManager(application); + } + return application.mDownloadManager; + } + + + public static void initDownloadService(Context context, DownloadService service) { + EhApplication application = ((EhApplication) context.getApplicationContext()); + if (application.mItemStateArray == null) { + application.mItemStateArray = new SparseBooleanArray(); + } + if (application.mItemTitleArray == null) { + application.mItemTitleArray = new SparseArray<>(); + } + + service.init(application.mItemStateArray, application.mItemTitleArray, + application.mFailedCount, application.mFinishedCount, application.mDownloadedCount); + } + + public static void clearDownloadService(Context context, DownloadService service) { + EhApplication application = ((EhApplication) context.getApplicationContext()); + application.mFailedCount = 0; + application.mFinishedCount = 0; + application.mDownloadedCount = 0; + service.clear(); + } + + public static void backupDownloadService(Context context, + int failedCount, int finishedCount, int downloadedCount) { + EhApplication application = ((EhApplication) context.getApplicationContext()); + application.mFailedCount = failedCount; + application.mFinishedCount = finishedCount; + application.mDownloadedCount = downloadedCount; + } } diff --git a/app/src/main/java/com/hippo/ehviewer/EhDB.java b/app/src/main/java/com/hippo/ehviewer/EhDB.java new file mode 100644 index 000000000..eb1a1eec7 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/EhDB.java @@ -0,0 +1,371 @@ +/* + * 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.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import com.hippo.ehviewer.client.data.GalleryInfo; +import com.hippo.ehviewer.client.data.ListUrlBuilder; +import com.hippo.ehviewer.dao.DaoMaster; +import com.hippo.ehviewer.dao.DaoSession; +import com.hippo.ehviewer.dao.DownloadInfoDao; +import com.hippo.ehviewer.dao.DownloadInfoRaw; +import com.hippo.ehviewer.dao.DownloadLabelDao; +import com.hippo.ehviewer.dao.DownloadLabelRaw; +import com.hippo.ehviewer.dao.GalleryInfoDao; +import com.hippo.ehviewer.dao.GalleryInfoRaw; +import com.hippo.ehviewer.dao.HistoryInfoDao; +import com.hippo.ehviewer.dao.HistoryInfoRaw; +import com.hippo.ehviewer.dao.LocalFavoriteDao; +import com.hippo.ehviewer.dao.LocalFavoriteRaw; +import com.hippo.ehviewer.dao.QuickSearchDao; +import com.hippo.ehviewer.dao.QuickSearchRaw; +import com.hippo.ehviewer.download.DownloadInfo; + +import java.util.ArrayList; +import java.util.List; + +import de.greenrobot.dao.query.LazyList; + +public class EhDB { + + private static DaoSession sDaoSession; + + private static boolean sHasOldDB; + private static boolean sNewDB; + + private static class DBOpenHelper extends DaoMaster.OpenHelper { + + public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) { + super(context, name, factory); + } + + @Override + public void onCreate(SQLiteDatabase db) { + super.onCreate(db); + sNewDB = true; + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } + } + + private static class OldDBHelper extends SQLiteOpenHelper { + + private static final String DB_NAME = "data"; + private static final int VERSION = 4; + + private static final String TABLE_GALLERY = "gallery"; + private static final String TABLE_LOCAL_FAVOURITE = "local_favourite"; + private static final String TABLE_TAG = "tag"; + private static final String TABLE_DOWNLOAD = "download"; + private static final String TABLE_HISTORY = "history"; + + public OldDBHelper(Context context) { + super(context, DB_NAME, null, VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } + } + + public static void initialize(Context context) { + sHasOldDB = context.getDatabasePath("data").exists(); + + DBOpenHelper helper = new DBOpenHelper( + context.getApplicationContext(), "eh.db", null); + + SQLiteDatabase db = helper.getWritableDatabase(); + DaoMaster daoMaster = new DaoMaster(db); + + sDaoSession = daoMaster.newSession(); + } + + public static boolean needMerge() { + return sNewDB && sHasOldDB; + } + + public static void mergeOldDB(Context context) { + sNewDB = false; + + OldDBHelper oldDBHelper = new OldDBHelper(context); + SQLiteDatabase oldDB; + try { + oldDB = oldDBHelper.getReadableDatabase(); + } catch (Exception e) { + return; + } + + // Merge gallery info + try { + Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_GALLERY, null); + if (cursor != null) { + GalleryInfoDao dao = sDaoSession.getGalleryInfoDao(); + if (cursor.moveToFirst()) { + while (!cursor.isAfterLast()) { + GalleryInfoRaw raw = new GalleryInfoRaw(); + raw.setGid((long) cursor.getInt(0)); + raw.setToken(cursor.getString(1)); + raw.setTitle(cursor.getString(2)); + raw.setPosted(cursor.getString(3)); + raw.setCategory(cursor.getInt(4)); + raw.setThumb(cursor.getString(5)); + raw.setUploader(cursor.getString(6)); + raw.setRating(cursor.getFloat(7)); + raw.setReference(cursor.getInt(8)); + dao.insert(raw); + cursor.moveToNext(); + } + } + cursor.close(); + } + } catch (Exception e) { + // Ignore + } + + // Merge local favorite + try { + Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_LOCAL_FAVOURITE, null); + if (cursor != null) { + LocalFavoriteDao dao = sDaoSession.getLocalFavoriteDao(); + if (cursor.moveToFirst()) { + long i = 0L; + while (!cursor.isAfterLast()) { + LocalFavoriteRaw raw = new LocalFavoriteRaw(); + raw.setGid((long) cursor.getInt(0)); + raw.setDate(i); + dao.insert(raw); + cursor.moveToNext(); + i++; + } + } + cursor.close(); + } + } catch (Exception e) { + // Ignore + } + + + // Merge quick search + try { + Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_TAG, null); + if (cursor != null) { + QuickSearchDao dao = sDaoSession.getQuickSearchDao(); + if (cursor.moveToFirst()) { + while (!cursor.isAfterLast()) { + QuickSearchRaw raw = new QuickSearchRaw(); + + int mode = cursor.getInt(2); + String search = cursor.getString(4); + String tag = cursor.getString(7); + if (mode == ListUrlBuilder.MODE_UPLOADER && search != null && + search.startsWith("uploader:")) { + search = search.substring("uploader:".length()); + } + + raw.setDate((long) cursor.getInt(0)); + raw.setName(cursor.getString(1)); + raw.setMode(mode); + raw.setCategory(cursor.getInt(3)); + raw.setKeyword(mode == ListUrlBuilder.MODE_TAG ? tag : search); + raw.setAdvanceSearch(cursor.getInt(5)); + raw.setMinRating(cursor.getInt(6)); + + dao.insert(raw); + cursor.moveToNext(); + } + } + cursor.close(); + } + } catch (Exception e) { + // Ignore + } + + // Merge download info + try { + Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_DOWNLOAD, null); + if (cursor != null) { + DownloadInfoDao dao = sDaoSession.getDownloadInfoDao(); + if (cursor.moveToFirst()) { + long i = 0L; + while (!cursor.isAfterLast()) { + DownloadInfoRaw raw = new DownloadInfoRaw(); + + int state = cursor.getInt(2); + int legacy = cursor.getInt(3); + if (state == DownloadInfo.STATE_FINISH && legacy > 0) { + state = DownloadInfo.STATE_FAILED; + } + + raw.setGid((long) cursor.getInt(0)); + raw.setState(state); + raw.setLegacy(legacy); + if (cursor.getColumnCount() == 5) { + raw.setDate(cursor.getLong(4)); + } else { + raw.setDate(i); + } + dao.insert(raw); + cursor.moveToNext(); + i++; + } + } + cursor.close(); + } + } catch (Exception e) { + // Ignore + } + + try { + // Merge history info + Cursor cursor = oldDB.rawQuery("select * from " + OldDBHelper.TABLE_HISTORY, null); + if (cursor != null) { + HistoryInfoDao dao = sDaoSession.getHistoryInfoDao(); + if (cursor.moveToFirst()) { + while (!cursor.isAfterLast()) { + HistoryInfoRaw raw = new HistoryInfoRaw(); + raw.setGid((long) cursor.getInt(0)); + raw.setMode(cursor.getInt(1)); + raw.setDate(cursor.getLong(2)); + dao.insert(raw); + cursor.moveToNext(); + } + } + cursor.close(); + } + } catch (Exception e) { + // Ignore + } + + try { + oldDBHelper.close(); + } catch (Exception e) { + // Ignore + } + } + + private static void addGalleryInfo(GalleryInfo galleryInfo) { + GalleryInfoDao dao = sDaoSession.getGalleryInfoDao(); + GalleryInfoRaw raw = dao.load((long) galleryInfo.gid); + if (raw == null) { + // add new item, set reference 1 + raw = new GalleryInfoRaw(); + raw.setGid((long) galleryInfo.gid); + raw.setToken(galleryInfo.token); + raw.setTitle(galleryInfo.title); + raw.setThumb(galleryInfo.thumb); + raw.setCategory(galleryInfo.category); + raw.setPosted(galleryInfo.posted); + raw.setUploader(galleryInfo.uploader); + raw.setRating(galleryInfo.rating); + raw.setReference(1); + dao.insert(raw); + } else { + // already in db, add reference + raw.setReference(raw.getReference() + 1); + dao.update(raw); + } + } + + private static void removeGalleryInfo(int gid) { + GalleryInfoDao dao = sDaoSession.getGalleryInfoDao(); + GalleryInfoRaw raw = dao.load((long) gid); + int reference = raw.getReference(); + if (reference <= 1) { + // No reference, delete it + dao.deleteByKey((long) gid); + } else { + // Still has reference, sub reference + raw.setReference(reference - 1); + dao.update(raw); + } + } + + public static GalleryInfo getGalleryInfo(int gid) { + GalleryInfoDao dao = sDaoSession.getGalleryInfoDao(); + GalleryInfoRaw raw = dao.load((long) gid); + if (raw != null) { + GalleryInfo gi = new GalleryInfo(); + gi.gid = (int) (long) raw.getGid(); + gi.token = raw.getToken(); + gi.title = raw.getTitle(); + gi.thumb = raw.getThumb(); + gi.category = raw.getCategory(); + gi.posted = raw.getPosted(); + gi.uploader = raw.getUploader(); + gi.rating = raw.getRating(); + gi.generateSLang(); + return gi; + } else { + return null; + } + } + + public static List getAllDownloadLabelList() { + DownloadLabelDao dao = sDaoSession.getDownloadLabelDao(); + List list = dao.queryBuilder().orderAsc(DownloadLabelDao.Properties.Time).list(); + List result = new ArrayList<>(list.size()); + for (int i = 0, n = list.size(); i < n; i++) { + result.add(list.get(i).getLabel()); + } + return result; + } + + public static LazyList getDownloadInfoLazyList() { + return sDaoSession.getDownloadInfoDao().queryBuilder() + .orderAsc(DownloadInfoDao.Properties.Date).listLazy(); + } + + public static void addDownloadInfo(DownloadInfo info) { + // Add gallery info first + addGalleryInfo(info.galleryInfo); + // Add download info + DownloadInfoDao dao = sDaoSession.getDownloadInfoDao(); + DownloadInfoRaw raw = new DownloadInfoRaw(); + raw.setGid((long) info.galleryInfo.gid); + raw.setState(info.state); + raw.setLegacy(info.legacy); + raw.setDate(info.date); + raw.setLabel(info.label); + dao.insert(raw); + } + + public static void updateDownloadInfo(DownloadInfo info) { + DownloadInfoDao dao = sDaoSession.getDownloadInfoDao(); + DownloadInfoRaw raw = dao.load((long) info.galleryInfo.gid); + if (raw == null) { + Log.e("TAG", "Can't find download info: " + info.galleryInfo.gid); + addDownloadInfo(info); + } else { + raw.setState(info.state); + raw.setLegacy(info.legacy); + raw.setDate(info.date); + raw.setLabel(info.label); + dao.update(raw); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/Settings.java b/app/src/main/java/com/hippo/ehviewer/Settings.java index 17646c63f..5447ea5ed 100644 --- a/app/src/main/java/com/hippo/ehviewer/Settings.java +++ b/app/src/main/java/com/hippo/ehviewer/Settings.java @@ -117,6 +117,18 @@ public class Settings { putBoolean(KEY_SHOW_WARNING, value); } + public static final String KEY_RECENT_DOWNLOAD_LABEL = "recent_download_label"; + private static final String DEFAULT_RECENT_DOWNLOAD_LABEL = null; + + public static String getRecentDownloadLabel() { + return getString(KEY_RECENT_DOWNLOAD_LABEL, DEFAULT_RECENT_DOWNLOAD_LABEL); + } + + public static void putRecentDownloadLabel(String value) { + putString(KEY_RECENT_DOWNLOAD_LABEL, value); + } + + public static EhConfig getEhConfig() { return sEhConfig; } diff --git a/app/src/main/java/com/hippo/ehviewer/download/DownloadInfo.java b/app/src/main/java/com/hippo/ehviewer/download/DownloadInfo.java new file mode 100644 index 000000000..3b31f4373 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/download/DownloadInfo.java @@ -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.download; + +import com.hippo.ehviewer.client.data.GalleryInfo; + +public class DownloadInfo { + + public static final int STATE_NONE = 0; + public static final int STATE_WAIT = 1; + public static final int STATE_DOWNLOAD = 2; + public static final int STATE_FINISH = 3; + public static final int STATE_FAILED = 4; + + public String label; + public GalleryInfo galleryInfo; + public int state = STATE_NONE; + // byte/second, -1 for can't get speed now + public long speed; + // -1 for unknown + public int download = 0; + // -1 for unknown + public int total = -1; + // -1 for unknown + public int legacy = -1; + // For order + public long date; +} diff --git a/app/src/main/java/com/hippo/ehviewer/download/DownloadManager.java b/app/src/main/java/com/hippo/ehviewer/download/DownloadManager.java new file mode 100644 index 000000000..4f29cfb78 --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/download/DownloadManager.java @@ -0,0 +1,686 @@ +/* + * 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.download; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; +import android.util.SparseArray; + +import com.hippo.ehviewer.EhDB; +import com.hippo.ehviewer.client.data.GalleryInfo; +import com.hippo.ehviewer.dao.DownloadInfoRaw; +import com.hippo.ehviewer.spider.SpiderQueen; +import com.hippo.image.Image; +import com.hippo.yorozuya.ConcurrentPool; +import com.hippo.yorozuya.SimpleHandler; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +import de.greenrobot.dao.query.LazyList; + +public class DownloadManager implements SpiderQueen.OnSpiderListener { + + private static final String TAG = DownloadManager.class.getSimpleName(); + + private final Context mContext; + + private final Map> mMap; + // All labels without default label + private final List mLabelList; + // Store download info with default label + private final LinkedList mDefaultInfoList; + // Store all download info + private final LinkedList mAllInfoList; + // For quick search + private final SparseArray mAllInfoMap; + // Store download info wait to start + private final LinkedList mWaitList; + + private final SpeedReminder mSpeedReminder; + + private DownloadListener mDownloadListener; + private DownloadInfoListener mDownloadInfoListener; + + @Nullable + private DownloadInfo mCurrentTask; + @Nullable + private SpiderQueen mCurrentSpider; + + private final ConcurrentPool mNotifyTaskPool = new ConcurrentPool<>(5); + + public DownloadManager(Context context) { + mContext = context; + + List labels = EhDB.getAllDownloadLabelList(); + mLabelList = labels; + + // Create list for each label + mMap = new HashMap<>(); + for (String label : labels) { + mMap.put(label, new LinkedList()); + } + // Create default for non tag + mDefaultInfoList = new LinkedList<>(); + // Create all download info list + mAllInfoList = new LinkedList<>(); + // Create all download info map + mAllInfoMap = new SparseArray<>(); + + // Fill download info list + LazyList lazyList = EhDB.getDownloadInfoLazyList(); + for (DownloadInfoRaw raw : lazyList) { + DownloadInfo info = createDownloadInfo(raw); + if (info == null) { + continue; + } + + // Add to all info list + mAllInfoList.add(info); + // Add to all info map + mAllInfoMap.put(info.galleryInfo.gid, info); + + // Add to list + List list = null; + if (info.label != null) { + list = mMap.get(info.label); + } + if (list == null) { + list = mDefaultInfoList; + } + list.add(info); + } + lazyList.close(); + + // Create wait list + mWaitList = new LinkedList<>(); + + mSpeedReminder = new SpeedReminder(); + } + + @Nullable + private DownloadInfo createDownloadInfo(DownloadInfoRaw raw) { + GalleryInfo gi = EhDB.getGalleryInfo((int) (long) raw.getGid()); + if (gi == null) { + Log.d(TAG, "Can't get GalleryInfo: " + raw.getGid()); + return null; + } + + DownloadInfo info = new DownloadInfo(); + int state = raw.getState(); + if (state == DownloadInfo.STATE_WAIT || state == DownloadInfo.STATE_DOWNLOAD) { + state = DownloadInfo.STATE_NONE; + } + info.galleryInfo = gi; + info.state = state; + info.legacy = raw.getLegacy(); + info.date = raw.getDate(); + info.label = raw.getLabel(); + return info; + } + + public boolean containLabel(String label) { + return mLabelList.contains(label); + } + + @NonNull + public List getLabelList() { + return mLabelList; + } + + @NonNull + public List getAllDownloadInfoList() { + return mAllInfoList; + } + + @NonNull + public List getDefaultDownloadInfoList() { + return mDefaultInfoList; + } + + @Nullable + public List getLabelDownloadInfoList(String label) { + return mMap.get(label); + } + + public void setDownloadInfoListener(DownloadInfoListener downloadInfoListener) { + mDownloadInfoListener = downloadInfoListener; + } + + public void setDownloadListener(DownloadListener listener) { + mDownloadListener = listener; + } + + private void ensureDownload() { + if (mCurrentTask != null) { + // Only one download + return; + } + + // Get download from wait list + if (!mWaitList.isEmpty()) { + DownloadInfo info = mWaitList.removeFirst(); + SpiderQueen spider = SpiderQueen.obtainSpiderQueen(mContext, info.galleryInfo, SpiderQueen.MODE_DOWNLOAD); + mCurrentTask = info; + mCurrentSpider = spider; + spider.addOnSpiderListener(this); + info.state = DownloadInfo.STATE_DOWNLOAD; + info.speed = -1; + info.total = -1; + info.download = 0; + info.legacy = -1; + // Update in DB + EhDB.updateDownloadInfo(info); + // Start speed count + mSpeedReminder.start(); + // Notify start downloading + if (mDownloadListener != null) { + mDownloadListener.onStart(info); + } + // Notify state update + if (mDownloadInfoListener != null) { + mDownloadInfoListener.onUpdate(info); + } + } + } + + void startDownload(GalleryInfo galleryInfo, @Nullable String label) { + if (mCurrentTask != null && mCurrentTask.galleryInfo.gid == galleryInfo.gid) { + // It is current task + return; + } + + // Check in download list + DownloadInfo info = mAllInfoMap.get(galleryInfo.gid); + if (info != null) { // Get it in download list + if (info.state != DownloadInfo.STATE_WAIT) { + // Set state DownloadInfo.STATE_WAIT + info.state = DownloadInfo.STATE_WAIT; + // Add to wait list + mWaitList.add(info); + // Update in DB + EhDB.updateDownloadInfo(info); + // Notify state update + if (mDownloadInfoListener != null) { + mDownloadInfoListener.onUpdate(info); + } + // Make sure download is running + ensureDownload(); + } + } else { + // It is new download info + info = new DownloadInfo(); + info.galleryInfo = galleryInfo; + info.label = label; + info.state = DownloadInfo.STATE_WAIT; + info.date = System.currentTimeMillis(); + // Add to all download list and map + mAllInfoList.add(info); + mAllInfoMap.put(galleryInfo.gid, info); + // Add to label download list + LinkedList list; + if (label == null) { + list = mDefaultInfoList; + } else { + list = mMap.get(label); + if (list == null) { + Log.e(TAG, "Can't find download info list with label: " + label); + list = new LinkedList<>(); + mMap.put(label, list); + // Maybe label is missing in mLabelList + if (!mLabelList.contains(label)) { + mLabelList.add(label); + } + } + } + list.add(info); + // Add to wait list + mWaitList.add(info); + // Save to + EhDB.addDownloadInfo(info); + // Notify + if (mDownloadInfoListener != null) { + mDownloadInfoListener.onAdd(info); + } + // Make sure download is running + ensureDownload(); + } + } + + void stopDownload(int gid) { + DownloadInfo info = stopDownloadInternal(gid); + if (info != null) { + // Update listener + if (mDownloadInfoListener != null) { + mDownloadInfoListener.onUpdate(info); + } + // Ensure download + ensureDownload(); + } + } + + void stopCurrentDownload() { + DownloadInfo info = stopCurrentDownloadInternal(); + if (info != null) { + // Update listener + if (mDownloadInfoListener != null) { + mDownloadInfoListener.onUpdate(info); + } + // Ensure download + ensureDownload(); + } + } + + void stopAllDownload() { + // Stop all in wait list + for (DownloadInfo info : mWaitList) { + info.state = DownloadInfo.STATE_NONE; + // Update in DB + EhDB.updateDownloadInfo(info); + } + mWaitList.clear(); + + // Stop current + stopCurrentDownloadInternal(); + + // Notify mDownloadInfoListener + if (mDownloadInfoListener != null) { + mDownloadInfoListener.onUpdateAll(); + } + } + + // Update in DB + // Update listener + // No ensureDownload + private DownloadInfo stopDownloadInternal(int gid) { + // Check current task + if (mCurrentTask != null && mCurrentTask.galleryInfo.gid == gid) { + // Stop current + return stopCurrentDownloadInternal(); + } + + for (Iterator iterator = mWaitList.iterator(); iterator.hasNext();) { + DownloadInfo info = iterator.next(); + if (info.galleryInfo.gid == gid) { + // Remove from wait list + iterator.remove(); + // Update state + info.state = DownloadInfo.STATE_NONE; + // Update in DB + EhDB.updateDownloadInfo(info); + return info; + } + } + return null; + } + + // Update in DB + // Update mDownloadListener + private DownloadInfo stopCurrentDownloadInternal() { + DownloadInfo info = mCurrentTask; + SpiderQueen spider = mCurrentSpider; + // Release spider + if (spider != null) { + spider.removeOnSpiderListener(DownloadManager.this); + SpiderQueen.releaseSpiderQueen(spider, SpiderQueen.MODE_DOWNLOAD); + } + mCurrentTask = null; + mCurrentSpider = null; + // Stop speed reminder + mSpeedReminder.stop(); + if (info == null) { + return null; + } + + // Update state + info.state = DownloadInfo.STATE_NONE; + // Update in DB + EhDB.updateDownloadInfo(info); + // Listener + if (mDownloadListener != null) { + mDownloadListener.onCancel(info); + } + return info; + } + + boolean isIdle() { + return mCurrentTask == null && mWaitList.isEmpty(); + } + + @Override + public void onGetPages(int pages) { + NotifyTask task = mNotifyTaskPool.pop(); + if (task == null) { + task = new NotifyTask(); + } + task.setOnGetPagesData(pages); + SimpleHandler.getInstance().post(task); + } + + @Override + public void onGet509(int index) { + NotifyTask task = mNotifyTaskPool.pop(); + if (task == null) { + task = new NotifyTask(); + } + task.setOnGet509Data(index); + SimpleHandler.getInstance().post(task); + } + + @Override + public void onPageDownload(int index, long contentLength, long receivedSize, int bytesRead) { + NotifyTask task = mNotifyTaskPool.pop(); + if (task == null) { + task = new NotifyTask(); + } + task.setOnPageDownloadData(index, contentLength, receivedSize, bytesRead); + SimpleHandler.getInstance().post(task); + } + + @Override + public void onPageSuccess(int index, int downloaded, int total) { + NotifyTask task = mNotifyTaskPool.pop(); + if (task == null) { + task = new NotifyTask(); + } + task.setOnPageSuccessData(index, downloaded, total); + SimpleHandler.getInstance().post(task); + } + + @Override + public void onPageFailure(int index, String error, int downloaded, int total) { + NotifyTask task = mNotifyTaskPool.pop(); + if (task == null) { + task = new NotifyTask(); + } + task.setOnPageFailureDate(index, error, downloaded, total); + SimpleHandler.getInstance().post(task); + } + + @Override + public void onFinish(int downloaded, int total) { + NotifyTask task = mNotifyTaskPool.pop(); + if (task == null) { + task = new NotifyTask(); + } + task.setOnFinishDate(downloaded, total); + SimpleHandler.getInstance().post(task); + } + + @Override + public void onGetImageSuccess(int index, Image image) { + // Ignore + } + + @Override + public void onGetImageFailure(int index, String error) { + // Ignore + } + + private class NotifyTask implements Runnable { + + public static final int TYPE_ON_GET_PAGES = 0; + public static final int TYPE_ON_GET_509 = 1; + public static final int TYPE_ON_PAGE_DOWNLOAD = 2; + public static final int TYPE_ON_PAGE_SUCCESS = 3; + public static final int TYPE_ON_PAGE_FAILURE = 4; + public static final int TYPE_ON_FINISH = 5; + + private int mType; + private int mPages; + private int mIndex; + private long mContentLength; + private long mReceivedSize; + private int mBytesRead; + private String mError; + private int mDownloaded; + private int mTotal; + + public void setOnGetPagesData(int pages) { + mType = TYPE_ON_GET_PAGES; + mPages = pages; + } + + public void setOnGet509Data(int index) { + mType = TYPE_ON_GET_509; + mIndex = index; + } + + public void setOnPageDownloadData(int index, long contentLength, long receivedSize, int bytesRead) { + mType = TYPE_ON_PAGE_DOWNLOAD; + mIndex = index; + mContentLength = contentLength; + mReceivedSize = receivedSize; + mBytesRead = bytesRead; + } + + public void setOnPageSuccessData(int index, int downloaded, int total) { + mType = TYPE_ON_PAGE_SUCCESS; + mIndex = index; + mDownloaded = downloaded; + mTotal = total; + } + + public void setOnPageFailureDate(int index, String error, int downloaded, int total) { + mType = TYPE_ON_PAGE_FAILURE; + mIndex = index; + mError = error; + mDownloaded = downloaded; + mTotal = total; + } + + public void setOnFinishDate(int downloaded, int total) { + mType = TYPE_ON_FINISH; + mDownloaded = downloaded; + mTotal = total; + } + + @Override + public void run() { + switch (mType) { + case TYPE_ON_GET_PAGES: { + DownloadInfo info = mCurrentTask; + if (info == null) { + Log.e(TAG, "Current task is null, but it should not be"); + } else { + info.total = mPages; + if (mDownloadInfoListener != null) { + mDownloadInfoListener.onUpdate(info); + } + } + break; + } + case TYPE_ON_GET_509: { + if (mDownloadListener != null) { + mDownloadListener.onGet509(); + } + break; + } + case TYPE_ON_PAGE_DOWNLOAD: { + mSpeedReminder.up(mBytesRead); + break; + } + case TYPE_ON_PAGE_SUCCESS: { + DownloadInfo info = mCurrentTask; + if (info == null) { + Log.e(TAG, "Current task is null, but it should not be"); + } else { + info.download = mDownloaded; + info.total = mTotal; + if (mDownloadListener != null) { + mDownloadListener.onGetPage(info); + } + if (mDownloadInfoListener != null) { + mDownloadInfoListener.onUpdate(info); + } + } + break; + } + case TYPE_ON_PAGE_FAILURE: { + DownloadInfo info = mCurrentTask; + if (info == null) { + Log.e(TAG, "Current task is null, but it should not be"); + } else { + info.download = mDownloaded; + info.total = mTotal; + if (mDownloadInfoListener != null) { + mDownloadInfoListener.onUpdate(info); + } + } + break; + } + case TYPE_ON_FINISH: { + // Download done + DownloadInfo info = mCurrentTask; + mCurrentTask = null; + SpiderQueen spider = mCurrentSpider; + mCurrentSpider = null; + // Release spider + if (spider != null) { + spider.removeOnSpiderListener(DownloadManager.this); + SpiderQueen.releaseSpiderQueen(spider, SpiderQueen.MODE_DOWNLOAD); + } + // Check null + if (info == null || spider == null) { + Log.e(TAG, "Current stuff is null, but it should not be"); + break; + } + // Stop speed count + mSpeedReminder.stop(); + // Update state + info.download = mDownloaded; + info.total = mTotal; + info.legacy = mTotal - mDownloaded; + if (info.legacy == 0) { + info.state = DownloadInfo.STATE_FINISH; + } else { + info.state = DownloadInfo.STATE_FAILED; + } + // Update in DB + EhDB.updateDownloadInfo(info); + // Notify + if (mDownloadListener != null) { + mDownloadListener.onFinish(info); + } + if (mDownloadInfoListener != null) { + mDownloadInfoListener.onUpdate(info); + } + // Start next download + ensureDownload(); + break; + } + } + + mNotifyTaskPool.push(this); + } + } + + + class SpeedReminder implements Runnable { + + private boolean mStop = true; + + private final AtomicLong mBytesRead = new AtomicLong(); + + public void start() { + if (mStop) { + mStop = false; + SimpleHandler.getInstance().post(this); + } + } + + public void stop() { + if (!mStop) { + mStop = true; + mBytesRead.lazySet(0L); + SimpleHandler.getInstance().removeCallbacks(this); + } + } + + public void up(int bytesRead) { + mBytesRead.addAndGet(bytesRead); + } + + @Override + public void run() { + DownloadInfo info = mCurrentTask; + if (info != null) { + info.speed = mBytesRead.getAndSet(0) / 2; + if (mDownloadListener != null) { + mDownloadListener.onDownload(info); + } + if (mDownloadInfoListener != null) { + mDownloadInfoListener.onUpdate(info); + } + } + + if (!mStop) { + SimpleHandler.getInstance().postDelayed(this, 2000); + } + } + } + + public interface DownloadInfoListener { + + void onAdd(DownloadInfo info); + + void onUpdate(DownloadInfo info); + + void onUpdateAll(); + + void onRemove(DownloadInfo info); + } + + public interface DownloadListener { + + /** + * Get 509 error + */ + void onGet509(); + + /** + * Start download + */ + void onStart(DownloadInfo info); + + /** + * Update download speed + */ + void onDownload(DownloadInfo info); + + /** + * Update page downloaded + */ + void onGetPage(DownloadInfo info); + + /** + * Download done + */ + void onFinish(DownloadInfo info); + + /** + * Download done + */ + void onCancel(DownloadInfo info); + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/download/DownloadService.java b/app/src/main/java/com/hippo/ehviewer/download/DownloadService.java new file mode 100644 index 000000000..8771e212a --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/download/DownloadService.java @@ -0,0 +1,369 @@ +/* + * 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.download; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; +import android.util.Log; +import android.util.SparseArray; +import android.util.SparseBooleanArray; + +import com.hippo.ehviewer.EhApplication; +import com.hippo.ehviewer.R; +import com.hippo.ehviewer.client.data.GalleryInfo; +import com.hippo.yorozuya.FileUtils; + +public class DownloadService extends Service implements DownloadManager.DownloadListener { + + public static final String ACTION_START = "start"; + public static final String ACTION_START_ALL = "start_all"; + public static final String ACTION_STOP = "stop"; + public static final String ACTION_STOP_CURRENT = "stop_current"; + public static final String ACTION_STOP_ALL = "stop_all"; + + public static final String ACTION_CLEAR = "clear"; + + public static final String KEY_GALLERY_INFO = "gallery_info"; + public static final String KEY_LABEL = "label"; + public static final String KEY_GID = "gid"; + + private static final int ID_DOWNLOADING = 1; + private static final int ID_DOWNLOADED = 2; + private static final int ID_509 = 3; + + @Nullable + private NotificationManager mNotifyManager; + @Nullable + private DownloadManager mDownloadManager; + private NotificationCompat.Builder mDownloadingBuilder; + private NotificationCompat.Builder mDownloadedBuilder; + private NotificationCompat.Builder m509dBuilder; + + @Nullable + private SparseBooleanArray mItemStateArray; + @Nullable + private SparseArray mItemTitleArray; + + private int mFailedCount; + private int mFinishedCount; + private int mDownloadedCount; + + public void init(SparseBooleanArray stateArray, SparseArray titleArray, + int failedCount, int finishedCount, int downloadedCount) { + mItemStateArray = stateArray; + mItemTitleArray = titleArray; + mFailedCount = failedCount; + mFinishedCount = finishedCount; + mDownloadedCount = downloadedCount; + } + + public void clear() { + mFailedCount = 0; + mFinishedCount = 0; + mDownloadedCount = 0; + if (mItemStateArray != null) { + mItemStateArray.clear(); + } + if (mItemTitleArray != null) { + mItemTitleArray.clear(); + } + } + + @Override + public void onCreate() { + super.onCreate(); + + mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + mDownloadManager = EhApplication.getDownloadManager(getApplicationContext()); + mDownloadManager.setDownloadListener(this); + EhApplication.initDownloadService(getApplicationContext(), this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + mNotifyManager = null; + if (mDownloadManager != null) { + mDownloadManager.setDownloadListener(null); + mDownloadManager = null; + } + mDownloadingBuilder = null; + mDownloadedBuilder = null; + m509dBuilder = null; + EhApplication.backupDownloadService(getApplicationContext(), + mFailedCount, mFinishedCount, mDownloadedCount); + mItemStateArray = null; + mItemTitleArray = null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + handleIntent(intent); + return START_STICKY; + } + + private void handleIntent(Intent intent) { + String action = null; + if (intent != null) { + action = intent.getAction(); + } + + if (ACTION_START.equals(action)) { + GalleryInfo gi= intent.getParcelableExtra(KEY_GALLERY_INFO); + String label = intent.getStringExtra(KEY_LABEL); + if (gi != null && mDownloadManager != null) { + mDownloadManager.startDownload(gi, label); + } + } else if (ACTION_STOP.equals(action)) { + int gid = intent.getIntExtra(KEY_GID, -1); + if (gid != -1 && mDownloadManager != null) { + mDownloadManager.stopDownload(gid); + } + } else if (ACTION_STOP_CURRENT.equals(action)) { + if (mDownloadManager != null) { + mDownloadManager.stopCurrentDownload(); + } + } else if (ACTION_STOP_ALL.equals(action)) { + if (mDownloadManager != null) { + mDownloadManager.stopAllDownload(); + } + } else if (ACTION_CLEAR.equals(action)) { + EhApplication.clearDownloadService(getApplicationContext(), this); + } + + checkStopSelf(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + throw new IllegalStateException("No bindService"); + } + + private void ensureDownloadingBuilder() { + if (mDownloadingBuilder != null) { + return; + } + + mDownloadingBuilder = new NotificationCompat.Builder(getApplicationContext()) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setOngoing(true) + .setAutoCancel(false); + } + + private void ensureDownloadedBuilder() { + if (mDownloadedBuilder != null) { + return; + } + + Intent clearIntent = new Intent(this, DownloadService.class); + clearIntent.setAction(ACTION_CLEAR); + PendingIntent piClear = PendingIntent.getService(this, 0, clearIntent, 0); + mDownloadedBuilder = new NotificationCompat.Builder(getApplicationContext()) + .setSmallIcon(android.R.drawable.stat_sys_download_done) + .setContentTitle(getString(R.string.stat_download_done_title)) + .setDeleteIntent(piClear) + .setOngoing(false) + .setAutoCancel(true); + } + + private void ensure509Builder() { + if (m509dBuilder != null) { + return; + } + + m509dBuilder = new NotificationCompat.Builder(getApplicationContext()) + .setSmallIcon(R.drawable.ic_stat_alert) + .setContentText(getString(R.string.stat_509_alert_title)) + .setContentText(getString(R.string.stat_509_alert_text)) + .setAutoCancel(true) + .setOngoing(false); + } + + @Override + public void onGet509() { + if (mNotifyManager == null) { + return; + } + + ensure509Builder(); + + mNotifyManager.notify(ID_509, m509dBuilder.build()); + } + + @Override + public void onStart(DownloadInfo info) { + if (mNotifyManager == null) { + return; + } + + ensureDownloadingBuilder(); + + mDownloadingBuilder.setContentTitle(info.galleryInfo.title) + .setContentText(null) + .setProgress(0, 0, true); + + startForeground(ID_DOWNLOADING, mDownloadingBuilder.build()); + } + + private void onUpdate(DownloadInfo info) { + if (mNotifyManager == null) { + return; + } + ensureDownloadingBuilder(); + + long speed = info.speed; + if (speed < 0) { + speed = 0; + } + String text = FileUtils.humanReadableByteCount(speed, false) + "/S"; + mDownloadingBuilder.setContentTitle(info.galleryInfo.title) + .setContentText(text) + .setProgress(info.total, info.download, false); + + startForeground(ID_DOWNLOADING, mDownloadingBuilder.build()); + } + + @Override + public void onDownload(DownloadInfo info) { + onUpdate(info); + } + + @Override + public void onGetPage(DownloadInfo info) { + onUpdate(info); + } + + @Override + public void onFinish(DownloadInfo info) { + if (mNotifyManager == null || mItemStateArray == null || mItemTitleArray == null) { + return; + } + + mNotifyManager.cancel(ID_DOWNLOADING); + + ensureDownloadedBuilder(); + + boolean finish = info.state == DownloadInfo.STATE_FINISH; + int gid = info.galleryInfo.gid; + int index = mItemStateArray.indexOfKey(gid); + if (index < 0) { // Not contain + mItemStateArray.put(gid, finish); + mItemTitleArray.put(gid, info.galleryInfo.title); + mDownloadedCount++; + if (finish) { + mFinishedCount++; + } else { + mFailedCount++; + } + } else { // Contain + boolean oldFinish = mItemStateArray.valueAt(index); + mItemStateArray.put(gid, finish); + mItemTitleArray.put(gid, info.galleryInfo.title); + if (oldFinish && !finish) { + mFinishedCount--; + mFailedCount++; + } else if (!oldFinish && finish) { + mFinishedCount++; + mFailedCount--; + } + } + + String text; + boolean needStyle; + if (mFinishedCount != 0 && mFailedCount == 0) { + if (mFinishedCount == 1) { + if (mItemTitleArray.size() >= 1) { + text = getString(R.string.stat_download_done_line_succeeded, mItemTitleArray.valueAt(0)); + } else { + Log.d("TAG", "WTF, mItemTitleArray is null"); + text = getString(R.string.error_unknown); + } + needStyle = false; + } else { + text = getString(R.string.stat_download_done_text_succeeded, mFinishedCount); + needStyle = true; + } + } else if (mFinishedCount == 0 && mFailedCount != 0) { + if (mFailedCount == 1) { + if (mItemTitleArray.size() >= 1) { + text = getString(R.string.stat_download_done_line_failed, mItemTitleArray.valueAt(0)); + } else { + Log.d("TAG", "WTF, mItemTitleArray is null"); + text = getString(R.string.error_unknown); + } + needStyle = false; + } else { + text = getString(R.string.stat_download_done_text_failed, mFailedCount); + needStyle = true; + } + } else { + text = getString(R.string.stat_download_done_text_mix, mFinishedCount, mFailedCount); + needStyle = true; + } + + NotificationCompat.InboxStyle style; + if (needStyle) { + style = new NotificationCompat.InboxStyle(); + style.setBigContentTitle(getString(R.string.stat_download_done_title)); + SparseBooleanArray stateArray = mItemStateArray; + SparseArray titleArray = mItemTitleArray; + for (int i = 0, n = stateArray.size(); i < n; i++) { + int id = stateArray.keyAt(i); + boolean fin = stateArray.valueAt(i); + String title = titleArray.get(id); + if (title == null) { + continue; + } + style.addLine(getString(fin ? R.string.stat_download_done_line_succeeded : + R.string.stat_download_done_line_failed, title)); + } + } else { + style = null; + } + + mDownloadedBuilder.setContentText(text).setStyle(style); + + mNotifyManager.notify(ID_DOWNLOADED, mDownloadedBuilder.build()); + + checkStopSelf(); + } + + @Override + public void onCancel(DownloadInfo info) { + if (mNotifyManager == null) { + return; + } + + mNotifyManager.cancel(ID_DOWNLOADING); + checkStopSelf(); + } + + private void checkStopSelf() { + if (mDownloadManager == null || mDownloadManager.isIdle()) { + stopForeground(true); + stopSelf(); + } + } +} diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java b/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java index 47f15ab3b..5e55cef30 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java +++ b/app/src/main/java/com/hippo/ehviewer/gallery/EhGalleryProvider.java @@ -119,22 +119,26 @@ public class EhGalleryProvider extends GalleryProvider implements SpiderQueen.On } @Override - public void onDownload(int index, long contentLength, long receivedSize, int bytesRead) { + public void onPageDownload(int index, long contentLength, long receivedSize, int bytesRead) { if (contentLength > 0) { notifyPagePercent(index, (float) receivedSize / contentLength); } } @Override - public void onSuccess(int index) { + public void onPageSuccess(int index, int downloaded, int total) { notifyDataChanged(index); } @Override - public void onFailure(int index, String error) { + public void onPageFailure(int index, String error, int downloaded, int total) { notifyPageFailed(index, error); } + @Override + public void onFinish(int downloaded, int total) { + } + @Override public void onGetImageSuccess(int index, Image image) { notifyPageSucceed(index, image); diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java index f66f76cfa..93614f767 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.java @@ -22,6 +22,7 @@ import android.support.annotation.Nullable; import android.webkit.MimeTypeMap; import com.hippo.beerbelly.SimpleDiskCache; +import com.hippo.ehviewer.EhDB; import com.hippo.ehviewer.Settings; import com.hippo.ehviewer.client.EhCacheKeyFactory; import com.hippo.ehviewer.client.data.GalleryInfo; @@ -42,7 +43,8 @@ import java.util.Locale; public final class SpiderDen { - private UniFile mDownloadDir; + @Nullable + private final UniFile mDownloadDir; private volatile int mMode = SpiderQueen.MODE_READ; private final int mGid; @@ -57,14 +59,24 @@ public final class SpiderDen { } } - public SpiderDen(GalleryInfo galleryInfo) { - mGid = galleryInfo.gid; + public static UniFile getGalleryDownloadDir(GalleryInfo galleryInfo) { UniFile dir = Settings.getDownloadLocation(); if (dir != null) { - mDownloadDir = dir.subFile(FileUtils.sanitizeFilename(galleryInfo.gid + "-" + galleryInfo.title)); + GalleryInfo info = EhDB.getGalleryInfo(galleryInfo.gid); + if (info == null) { + info = galleryInfo; + } + return dir.subFile(FileUtils.sanitizeFilename(info.gid + "-" + info.title)); + } else { + return null; } } + public SpiderDen(GalleryInfo galleryInfo) { + mGid = galleryInfo.gid; + mDownloadDir = getGalleryDownloadDir(galleryInfo); + } + public void setMode(@SpiderQueen.Mode int mode) { mMode = mode; diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java index 1148fcb56..b270456ee 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderQueen.java @@ -71,7 +71,6 @@ import java.util.Stack; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReferenceArray; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -82,6 +81,7 @@ public class SpiderQueen implements Runnable { private static final String TAG = SpiderQueen.class.getSimpleName(); private static final AtomicInteger sIdGenerator = new AtomicInteger(); + private static final boolean DEBUG_LOG = false; @IntDef({MODE_READ, MODE_DOWNLOAD}) @Retention(RetentionPolicy.SOURCE) @@ -132,7 +132,7 @@ public class SpiderQueen implements Runnable { private final Stack mDecodeRequestStack = new Stack<>(); private final AtomicInteger mDecodeIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX); - private volatile AtomicReferenceArray mWorkers; + private volatile Thread[] mWorkers; private final Object mWorkerLock = new Object(); private final Object mPTokenLock = new Object(); @@ -196,26 +196,49 @@ public class SpiderQueen implements Runnable { } } - private void notifyDownload(int index, long contentLength, long receivedSize, int bytesRead) { + private void notifyPageDownload(int index, long contentLength, long receivedSize, int bytesRead) { synchronized (mSpiderListeners) { for (OnSpiderListener listener : mSpiderListeners) { - listener.onDownload(index, contentLength, receivedSize, bytesRead); + listener.onPageDownload(index, contentLength, receivedSize, bytesRead); } } } - private void notifySuccess(int index) { + private void notifyPageSuccess(int index) { + int size = -1; + int[] temp = mPageStateArray; + if (temp != null) { + size = temp.length; + } synchronized (mSpiderListeners) { for (OnSpiderListener listener : mSpiderListeners) { - listener.onSuccess(index); + listener.onPageSuccess(index, mFinishedPages.get(), size); } } } - private void notifyFailure(int index, String error) { + private void notifyPageFailure(int index, String error) { + int size = -1; + int[] temp = mPageStateArray; + if (temp != null) { + size = temp.length; + } synchronized (mSpiderListeners) { for (OnSpiderListener listener : mSpiderListeners) { - listener.onFailure(index, error); + listener.onPageFailure(index, error, mFinishedPages.get(), size); + } + } + } + + private void notifyFinish() { + int size = -1; + int[] temp = mPageStateArray; + if (temp != null) { + size = temp.length; + } + synchronized (mSpiderListeners) { + for (OnSpiderListener listener : mSpiderListeners) { + listener.onFinish(mFinishedPages.get(), size); } } } @@ -283,10 +306,19 @@ public class SpiderQueen implements Runnable { } // Update download page - if (mMode == MODE_DOWNLOAD) { - mDownloadPage = 0; - } else { - mDownloadPage = -1; + boolean startWorkers; + synchronized (mRequestPageQueue) { + if (mMode == MODE_DOWNLOAD) { + mDownloadPage = 0; + startWorkers = true; + } else { + mDownloadPage = -1; + startWorkers = false; + } + } + + if (startWorkers && mPageStateArray != null) { + ensureWorkers(); } } @@ -439,14 +471,14 @@ public class SpiderQueen implements Runnable { private void ensureWorkers() { synchronized (mWorkerLock) { if (mWorkers == null) { - mWorkers = new AtomicReferenceArray<>(NUMBER_SPIDER_WORKER); + mWorkers = new Thread[NUMBER_SPIDER_WORKER]; } - for (int i = 0, n = mWorkers.length(); i < n; i++) { - if (mWorkers.get(i) == null) { + for (int i = 0, n = mWorkers.length; i < n; i++) { + if (mWorkers[i] == null) { SpiderWorker worker = new SpiderWorker(i); Thread thread = mThreadFactory.newThread(worker); - mWorkers.lazySet(i, thread); + mWorkers[i] = thread; thread.start(); } } @@ -521,7 +553,9 @@ public class SpiderQueen implements Runnable { try { String url = EhUrl.getGalleryDetailUrl( mGalleryInfo.gid, mGalleryInfo.token, previewIndex, false); - Log.d(TAG, url); + if (DEBUG_LOG) { + Log.d(TAG, url); + } Request request = new EhRequestBuilder(url, config).build(); Response response = mHttpClient.newCall(request).execute(); String body = response.body().string(); @@ -688,7 +722,9 @@ public class SpiderQueen implements Runnable { @Override public void run() { - Log.i(TAG, Thread.currentThread().getName() + ": start"); + if (DEBUG_LOG) { + Log.i(TAG, Thread.currentThread().getName() + ": start"); + } runInternal(); @@ -704,24 +740,28 @@ public class SpiderQueen implements Runnable { // Interrupt all workers synchronized (mWorkerLock) { if (mWorkers != null) { - for (int i = 0, n = mWorkers.length(); i < n; i++) { - Thread thread = mWorkers.get(i); + for (int i = 0, n = mWorkers.length; i < n; i++) { + Thread thread = mWorkers[i]; if (thread != null) { thread.interrupt(); - mWorkers.set(i, null); + mWorkers[i] = null; } } mWorkers = null; } } - Log.i(TAG, Thread.currentThread().getName() + ": end"); + notifyFinish(); + + if (DEBUG_LOG) { + Log.i(TAG, Thread.currentThread().getName() + ": end"); + } } private class SpiderWorker implements Runnable { - private int mWorkerIndex; - private int mGid; + private final int mWorkerIndex; + private final int mGid; public SpiderWorker(int workerIndex) { mWorkerIndex = workerIndex; @@ -732,6 +772,10 @@ public class SpiderQueen implements Runnable { updatePageState(index, state, null); } + private boolean isStateDone(int state) { + return state == STATE_FINISHED || state == STATE_FAILED; + } + private void updatePageState(int index, @State int state, String error) { int oldState; synchronized (mPageStateLock) { @@ -739,9 +783,9 @@ public class SpiderQueen implements Runnable { mPageStateArray[index] = state; } - if (oldState == STATE_NONE && state != STATE_NONE) { + if (!isStateDone(oldState) && isStateDone(state)) { mDownloadedPages.incrementAndGet(); - } else if (oldState != STATE_NONE && state == STATE_NONE) { + } else if (isStateDone(oldState) && !isStateDone(state)) { mDownloadedPages.decrementAndGet(); } if (oldState != STATE_FINISHED && state == STATE_FINISHED) { @@ -763,9 +807,9 @@ public class SpiderQueen implements Runnable { error = GetText.getString(R.string.error_unknown); } mPageErrorMap.put(index, error); - notifyFailure(index, error); + notifyPageFailure(index, error); } else if (state == STATE_FINISHED) { - notifySuccess(index); + notifyPageSuccess(index); } } @@ -775,7 +819,9 @@ public class SpiderQueen implements Runnable { if (skipHathKey != null) { url = url + "?nl=" + skipHathKey; } - Log.d(TAG, url); + if (DEBUG_LOG) { + Log.d(TAG, url); + } Response response = mHttpClient.newCall(new EhRequestBuilder(url).build()).execute(); String body = response.body().string(); GalleryPageParser.Result result = GalleryPageParser.parse(body); @@ -828,7 +874,9 @@ public class SpiderQueen implements Runnable { continue; } - Log.d(TAG, imageUrl); + if (DEBUG_LOG) { + Log.d(TAG, imageUrl); + } // Start download image OutputStreamPipe pipe = mSpiderDen.openOutputStreamPipe( @@ -842,7 +890,9 @@ public class SpiderQueen implements Runnable { // Download image InputStream is = null; try { - Log.d(TAG, "Start download image " + index); + if (DEBUG_LOG) { + Log.d(TAG, "Start download image " + index); + } Response response = mHttpClient.newCall(new EhRequestBuilder(imageUrl).build()).execute(); ResponseBody responseBody = response.body(); @@ -866,7 +916,7 @@ public class SpiderQueen implements Runnable { mPagePercentMap.put(index, (float) receivedSize / contentLength); } // Notify listener - notifyDownload(index, contentLength, receivedSize, bytesRead); + notifyPageDownload(index, contentLength, receivedSize, bytesRead); } os.flush(); @@ -876,7 +926,9 @@ public class SpiderQueen implements Runnable { break; } - Log.d(TAG, "Download image succeed " + index); + if (DEBUG_LOG) { + Log.d(TAG, "Download image succeed " + index); + } // Download finished updatePageState(index, STATE_FINISHED); @@ -888,7 +940,9 @@ public class SpiderQueen implements Runnable { pipe.close(); pipe.release(); - Log.d(TAG, "End download image " + index); + if (DEBUG_LOG) { + Log.d(TAG, "End download image " + index); + } } } @@ -976,7 +1030,9 @@ public class SpiderQueen implements Runnable { mWorkerLock.wait(); } catch (InterruptedException e) { // Interrupted - Log.d(TAG, Thread.currentThread().getName() + " Interrupted"); + if (DEBUG_LOG) { + Log.d(TAG, Thread.currentThread().getName() + " Interrupted"); + } break; } } @@ -1005,19 +1061,40 @@ public class SpiderQueen implements Runnable { @Override @SuppressWarnings("StatementWithEmptyBody") public void run() { - Log.i(TAG, Thread.currentThread().getName() + ": start"); + if (DEBUG_LOG) { + Log.i(TAG, Thread.currentThread().getName() + ": start"); + } while (!Thread.currentThread().isInterrupted() && runInternal()); + + boolean finish = true; // Clear in spider worker array synchronized (mWorkerLock) { - if (mWorkers != null && mWorkerIndex < mWorkers.length() && - mWorkers.get(mWorkerIndex) == Thread.currentThread()) { - mWorkers.lazySet(mWorkerIndex, null); + if (mWorkers != null && mWorkerIndex < mWorkers.length && + mWorkers[mWorkerIndex] == Thread.currentThread()) { + mWorkers[mWorkerIndex] = null; + } + if (mWorkers != null) { + for (Thread mWorker : mWorkers) { + if (null != mWorker) { + finish = false; + break; + } + } + } + if (finish) { + mWorkers = null; } } - Log.i(TAG, Thread.currentThread().getName() + ": end"); + if (finish) { + notifyFinish(); + } + + if (DEBUG_LOG) { + Log.i(TAG, Thread.currentThread().getName() + ": end"); + } } } @@ -1025,7 +1102,9 @@ public class SpiderQueen implements Runnable { @Override public void run() { - Log.i(TAG, Thread.currentThread().getName() + ": start"); + if (DEBUG_LOG) { + Log.i(TAG, Thread.currentThread().getName() + ": start"); + } while (!Thread.currentThread().isInterrupted()) { int index; @@ -1076,7 +1155,9 @@ public class SpiderQueen implements Runnable { } } - Log.i(TAG, Thread.currentThread().getName() + ": end"); + if (DEBUG_LOG) { + Log.i(TAG, Thread.currentThread().getName() + ": end"); + } } } @@ -1089,11 +1170,16 @@ public class SpiderQueen implements Runnable { /** * @param contentLength -1 for unknown */ - void onDownload(int index, long contentLength, long receivedSize, int bytesRead); + void onPageDownload(int index, long contentLength, long receivedSize, int bytesRead); - void onSuccess(int index); + void onPageSuccess(int index, int downloaded, int total); - void onFailure(int index, String error); + void onPageFailure(int index, String error, int downloaded, int total); + + /** + * All workers end + */ + void onFinish(int downloaded, int total); void onGetImageSuccess(int index, Image image); 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 ba303f248..6122fd995 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/MainActivity.java @@ -26,15 +26,18 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.NavigationView; import android.support.v4.widget.DrawerLayout; -import android.util.Log; import android.view.Gravity; import android.view.MenuItem; +import android.view.View; +import android.widget.FrameLayout; import android.widget.Toast; import com.hippo.ehviewer.R; import com.hippo.ehviewer.Settings; import com.hippo.ehviewer.client.EhUrlOpener; import com.hippo.ehviewer.client.EhUtils; +import com.hippo.ehviewer.ui.scene.BaseScene; +import com.hippo.ehviewer.ui.scene.DownloadScene; import com.hippo.ehviewer.ui.scene.GalleryListScene; import com.hippo.ehviewer.ui.scene.LoginScene; import com.hippo.ehviewer.ui.scene.WarningScene; @@ -50,8 +53,12 @@ public final class MainActivity extends StageActivity private static final String KEY_NAV_CHECKED_ITEM = "nav_checked_item"; + @Nullable private DrawerLayout mDrawerLayout; + @Nullable private NavigationView mNavView; + @Nullable + private FrameLayout mRightDrawer; private int mNavCheckedItem = 0; @Override @@ -66,8 +73,11 @@ public final class MainActivity extends StageActivity mDrawerLayout = (DrawerLayout) findViewById(R.id.draw_view); mNavView = (NavigationView) findViewById(R.id.nav_view); + mRightDrawer = (FrameLayout) findViewById(R.id.right_drawer); - mNavView.setNavigationItemSelectedListener(this); + if (mNavView != null) { + mNavView.setNavigationItemSelectedListener(this); + } if (savedInstanceState == null) { onInit(); @@ -76,6 +86,15 @@ public final class MainActivity extends StageActivity } } + @Override + protected void onDestroy() { + super.onDestroy(); + + mDrawerLayout = null; + mNavView = null; + mRightDrawer = null; + } + private boolean handleIntent(Intent intent) { if (intent == null) { return false; @@ -126,10 +145,6 @@ public final class MainActivity extends StageActivity } private void onRestore(Bundle savedInstanceState) { - Log.d("TAG", "onRestore"); - - Log.d("TAG", "action = " + getIntent().getAction()); - mNavCheckedItem = savedInstanceState.getInt(KEY_NAV_CHECKED_ITEM); } @@ -158,34 +173,69 @@ public final class MainActivity extends StageActivity } } + @Override + public void onSceneViewCreated(SceneFragment scene, Bundle savedInstanceState) { + super.onSceneViewCreated(scene, savedInstanceState); + + if (scene instanceof BaseScene && mRightDrawer != null && mDrawerLayout != null) { + BaseScene baseScene = (BaseScene) scene; + mRightDrawer.removeAllViews(); + View drawerView = baseScene.onCreateDrawerView( + getLayoutInflater(), mRightDrawer, savedInstanceState); + if (drawerView != null) { + mRightDrawer.addView(drawerView); + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, Gravity.RIGHT); + } else { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, Gravity.RIGHT); + } + } + } + + @Override + public void onSceneViewDestroyed(SceneFragment scene) { + super.onSceneViewDestroyed(scene); + + if (scene instanceof BaseScene) { + BaseScene baseScene = (BaseScene) scene; + baseScene.onDestroyDrawerView(); + } + } + public void setDrawerLayoutEnable(boolean enable) { - if (enable) { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); - } else { - mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + if (mDrawerLayout != null) { + if (enable) { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); + } else { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); + } } } public void toggleDrawer(int drawerGravity) { - if (mDrawerLayout.isDrawerOpen(drawerGravity)) { - mDrawerLayout.closeDrawer(drawerGravity); - } else { - mDrawerLayout.openDrawer(drawerGravity); + if (mDrawerLayout != null) { + if (mDrawerLayout.isDrawerOpen(drawerGravity)) { + mDrawerLayout.closeDrawer(drawerGravity); + } else { + mDrawerLayout.openDrawer(drawerGravity); + } } } public void setNavCheckedItem(@IdRes int resId) { mNavCheckedItem = resId; - if (resId == 0) { - mNavView.setCheckedItem(R.id.nav_stub); - } else { - mNavView.setCheckedItem(resId); + if (mNavView != null) { + if (resId == 0) { + mNavView.setCheckedItem(R.id.nav_stub); + } else { + mNavView.setCheckedItem(resId); + } } } @Override public void onBackPressed() { - if (mDrawerLayout.isDrawerOpen(Gravity.LEFT) || mDrawerLayout.isDrawerOpen(Gravity.RIGHT)) { + if (mDrawerLayout != null && (mDrawerLayout.isDrawerOpen(Gravity.LEFT) || + mDrawerLayout.isDrawerOpen(Gravity.RIGHT))) { mDrawerLayout.closeDrawers(); } else { super.onBackPressed(); @@ -214,13 +264,13 @@ public final class MainActivity extends StageActivity } else if (id == R.id.nav_history) { } else if (id == R.id.nav_download) { - + startScene(new Announcer(DownloadScene.class)); } else if (id == R.id.nav_settings) { Intent intent = new Intent(this, SettingsActivity.class); startActivity(intent); } - if (id != R.id.nav_stub) { + if (id != R.id.nav_stub && mDrawerLayout != null) { mDrawerLayout.closeDrawers(); } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/BaseScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/BaseScene.java index 2f6da9a61..36b51bfa3 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/BaseScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/BaseScene.java @@ -20,7 +20,9 @@ import android.os.Bundle; import android.support.annotation.IdRes; import android.support.annotation.Nullable; import android.support.v4.app.FragmentActivity; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import com.hippo.ehviewer.ui.MainActivity; import com.hippo.scene.SceneFragment; @@ -53,6 +55,14 @@ public class BaseScene extends SceneFragment { } } + public View onCreateDrawerView(LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return null; + } + + public void onDestroyDrawerView() { + } + @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadScene.java new file mode 100644 index 000000000..429c60a2d --- /dev/null +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/DownloadScene.java @@ -0,0 +1,503 @@ +/* + * Copyright 2016 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.ehviewer.ui.scene; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.transition.TransitionInflater; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.hippo.easyrecyclerview.EasyRecyclerView; +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.EhUtils; +import com.hippo.ehviewer.client.data.GalleryInfo; +import com.hippo.ehviewer.download.DownloadInfo; +import com.hippo.ehviewer.download.DownloadManager; +import com.hippo.ehviewer.download.DownloadService; +import com.hippo.ehviewer.ui.GalleryActivity; +import com.hippo.ehviewer.widget.SimpleRatingView; +import com.hippo.rippleold.RippleSalon; +import com.hippo.scene.Announcer; +import com.hippo.scene.TransitionHelper; +import com.hippo.util.ActivityHelper; +import com.hippo.util.ApiHelper; +import com.hippo.vector.VectorDrawable; +import com.hippo.view.ViewTransition; +import com.hippo.widget.LoadImageView; +import com.hippo.widget.SimpleImageView; +import com.hippo.yorozuya.FileUtils; +import com.hippo.yorozuya.ObjectUtils; + +import java.util.List; + +public class DownloadScene extends ToolbarScene + implements DownloadManager.DownloadInfoListener, + EasyRecyclerView.OnItemClickListener { + + private static final String KEY_LABEL = "label"; + + @Nullable + private EasyRecyclerView mRecyclerView; + @Nullable + private ViewTransition mViewTransition; + + @Nullable + private DownloadAdapter mAdapter; + + @Nullable + private String mLabel; + @Nullable + private List mList; + + // TODO Only single instance + @Override + public int getLaunchMode() { + return LAUNCH_MODE_SINGLE_TOP; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + DownloadManager manager = EhApplication.getDownloadManager(getContext()); + manager.setDownloadInfoListener(this); + + if (savedInstanceState == null) { + onInit(); + } else { + onRestore(savedInstanceState); + } + } + + private void onInit() { + mLabel = Settings.getRecentDownloadLabel(); + DownloadManager manager = EhApplication.getDownloadManager(getContext()); + if (mLabel == null) { + mList = manager.getDefaultDownloadInfoList(); + } else { + mList = manager.getLabelDownloadInfoList(mLabel); + if (mList == null) { + mLabel = null; + mList = manager.getDefaultDownloadInfoList(); + } + } + } + + private void onRestore(@NonNull Bundle savedInstanceState) { + mLabel = savedInstanceState.getString(KEY_LABEL); + DownloadManager manager = EhApplication.getDownloadManager(getContext()); + if (mLabel == null) { + mList = manager.getDefaultDownloadInfoList(); + } else { + mList = manager.getLabelDownloadInfoList(mLabel); + if (mList == null) { + mLabel = null; + mList = manager.getDefaultDownloadInfoList(); + } + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(KEY_LABEL, mLabel); + } + + @Nullable + @Override + public View onCreateView2(LayoutInflater inflater, + @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.scene_download, container, false); + mRecyclerView = (EasyRecyclerView) view.findViewById(R.id.recycler_view); + View tip = view.findViewById(R.id.tip); + SimpleImageView tipImage = (SimpleImageView) tip.findViewById(R.id.tip_image); + tipImage.setDrawable(VectorDrawable.create(getContext(), R.xml.sadpanda_head)); + mViewTransition = new ViewTransition(mRecyclerView, tip); + + if (mRecyclerView != null) { + mAdapter = new DownloadAdapter(); + mRecyclerView.setAdapter(mAdapter); + mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + mRecyclerView.setSelector(RippleSalon.generateRippleDrawable(false)); + mRecyclerView.setDrawSelectorOnTop(true); + mRecyclerView.hasFixedSize(); + mRecyclerView.setClipToPadding(false); + mRecyclerView.setOnItemClickListener(this); + int paddingH = getResources().getDimensionPixelOffset(R.dimen.list_content_margin_h); + int paddingV = getResources().getDimensionPixelOffset(R.dimen.list_content_margin_v); + mRecyclerView.setPadding(paddingV, paddingH, paddingV, paddingH); + } + + if (mList == null || mList.isEmpty()) { + mViewTransition.showView(1); + } else { + mViewTransition.showView(0); + } + + return view; + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setTitle(R.string.download); + setNavigationIcon(VectorDrawable.create(getContext(), R.xml.ic_arrow_left_dark_x24)); + + // Clear nav checked item + setNavCheckedItem(R.id.nav_download); + + // Hide IME + ActivityHelper.hideSoftInput(getActivity()); + } + + @Override + public void onNavigationClick() { + finish(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + DownloadManager manager = EhApplication.getDownloadManager(getContext()); + manager.setDownloadInfoListener(null); + mRecyclerView = null; + mViewTransition = null; + mAdapter = null; + } + + @Override + public void onDestroy() { + super.onDestroy(); + mList = null; + } + + @Override + public boolean onItemClick(EasyRecyclerView parent, View view, int position, long id) { + List list = mList; + if (list == null) { + return false; + } + if (position < 0 && position >= list.size()) { + return false; + } + + Intent intent = new Intent(getActivity(), GalleryActivity.class); + intent.setAction(GalleryActivity.ACTION_EH); + intent.putExtra(GalleryActivity.KEY_GALLERY_INFO, list.get(position).galleryInfo); + startActivity(intent); + return true; + } + + @Override + public void onAdd(DownloadInfo info) { + // TODO + } + + @Override + public void onUpdate(DownloadInfo info) { + if (!ObjectUtils.equal(info.label, mLabel)) { + return; + } + RecyclerView recyclerView = mRecyclerView; + if (recyclerView == null) { + return; + } + List list = mList; + if (list == null) { + return; + } + + int size = list.size(); + for (int i = 0, n = recyclerView.getChildCount(); i < n; i++) { + View child = recyclerView.getChildAt(i); + int index = recyclerView.getChildAdapterPosition(child); + if (index >= 0 && index < size && list.get(index) == info) { + DownloadHolder holder = (DownloadHolder) recyclerView.getChildViewHolder(child); + if (holder != null) { + bindForState(holder, info); + } + break; + } + } + } + + @Override + public void onUpdateAll() { + RecyclerView recyclerView = mRecyclerView; + if (recyclerView == null) { + return; + } + List list = mList; + if (list == null) { + return; + } + + int size = list.size(); + for (int i = 0, n = recyclerView.getChildCount(); i < n; i++) { + View child = recyclerView.getChildAt(i); + int index = recyclerView.getChildAdapterPosition(child); + if (index >= 0 && index < size) { + DownloadHolder holder = (DownloadHolder) recyclerView.getChildViewHolder(child); + bindForState(holder, list.get(index)); + } + } + } + + @Override + public void onRemove(DownloadInfo info) { + + } + + private void bindForState(DownloadHolder holder, DownloadInfo info) { + switch (info.state) { + case DownloadInfo.STATE_NONE: + bindState(holder, info, getContext().getString(R.string.download_state_none)); + break; + case DownloadInfo.STATE_WAIT: + bindState(holder, info, getContext().getString(R.string.download_state_wait)); + break; + case DownloadInfo.STATE_DOWNLOAD: + bindProgress(holder, info); + break; + case DownloadInfo.STATE_FAILED: + String text; + if (info.legacy <= 0) { + text = getContext().getString(R.string.download_state_failed); + } else { + text = getContext().getString(R.string.download_state_failed_2, info.legacy); + } + bindState(holder, info, text); + break; + case DownloadInfo.STATE_FINISH: + bindState(holder, info, getContext().getString(R.string.download_state_finish)); + break; + } + } + + private void bindState(DownloadHolder holder, DownloadInfo info, String state) { + holder.uploader.setVisibility(View.VISIBLE); + holder.rating.setVisibility(View.VISIBLE); + holder.category.setVisibility(View.VISIBLE); + holder.state.setVisibility(View.VISIBLE); + holder.progressBar.setVisibility(View.INVISIBLE); + holder.percent.setVisibility(View.INVISIBLE); + holder.speed.setVisibility(View.INVISIBLE); + + holder.state.setText(state); + } + + @SuppressLint("SetTextI18n") + private void bindProgress(DownloadHolder holder, DownloadInfo info) { + holder.uploader.setVisibility(View.INVISIBLE); + holder.rating.setVisibility(View.INVISIBLE); + holder.category.setVisibility(View.INVISIBLE); + holder.state.setVisibility(View.INVISIBLE); + holder.progressBar.setVisibility(View.VISIBLE); + holder.percent.setVisibility(View.VISIBLE); + holder.speed.setVisibility(View.VISIBLE); + + if (info.total <= 0 || info.download < 0) { + holder.percent.setText(null); + holder.progressBar.setIndeterminate(true); + } else { + holder.percent.setText(info.download + "/" + info.total); + holder.progressBar.setIndeterminate(false); + holder.progressBar.setMax(info.total); + holder.progressBar.setProgress(info.download); + } + long speed = info.speed; + if (speed < 0) { + speed = 0; + } + holder.speed.setText(FileUtils.humanReadableByteCount(speed, false) + "/S"); + } + + private class DownloadHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + public final LoadImageView thumb; + public final TextView title; + public final TextView uploader; + public final SimpleRatingView rating; + public final TextView category; + public final View start; + public final View stop; + public final View delete; + public final TextView state; + public final ProgressBar progressBar; + public final TextView percent; + public final TextView speed; + + public DownloadHolder(View itemView) { + super(itemView); + + thumb = (LoadImageView) itemView.findViewById(R.id.thumb); + title = (TextView) itemView.findViewById(R.id.title); + uploader = (TextView) itemView.findViewById(R.id.uploader); + rating = (SimpleRatingView) itemView.findViewById(R.id.rating); + category = (TextView) itemView.findViewById(R.id.category); + start = itemView.findViewById(R.id.start); + stop = itemView.findViewById(R.id.stop); + delete = itemView.findViewById(R.id.delete); + state = (TextView) itemView.findViewById(R.id.state); + progressBar = (ProgressBar) itemView.findViewById(R.id.progress_bar); + percent = (TextView) itemView.findViewById(R.id.percent); + speed = (TextView) itemView.findViewById(R.id.speed); + + thumb.setOnClickListener(this); + start.setOnClickListener(this); + stop.setOnClickListener(this); + delete.setOnClickListener(this); + RippleSalon.addRipple(start, false); + RippleSalon.addRipple(stop, false); + RippleSalon.addRipple(delete, false); + } + + @Override + public void onClick(View v) { + RecyclerView recyclerView = mRecyclerView; + if (recyclerView == null) { + return; + } + List list = mList; + if (list == null) { + return; + } + int size = list.size(); + int index = recyclerView.getChildAdapterPosition(itemView); + if (index < 0 && index >= size) { + return; + } + + if (thumb == v) { + Bundle args = new Bundle(); + args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO); + args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, list.get(index).galleryInfo); + Announcer announcer = new Announcer(GalleryDetailScene.class).setArgs(args); + if (ApiHelper.SUPPORT_TRANSITION) { + DownloadHolder holder = (DownloadHolder) recyclerView.getChildViewHolder(itemView); + announcer.setTranHelper(new EnterGalleryDetailTransaction(holder)); + } + startScene(announcer); + } else if (start == v) { + Intent intent = new Intent(getActivity(), DownloadService.class); + intent.setAction(DownloadService.ACTION_START); + intent.putExtra(DownloadService.KEY_GALLERY_INFO, list.get(index).galleryInfo); + getActivity().startService(intent); + } else if (stop == v) { + Intent intent = new Intent(getActivity(), DownloadService.class); + intent.setAction(DownloadService.ACTION_STOP); + intent.putExtra(DownloadService.KEY_GID, list.get(index).galleryInfo.gid); + getActivity().startService(intent); + } else if (delete == v) { + // TODO + } + } + } + + private static class EnterGalleryDetailTransaction implements TransitionHelper { + + private final DownloadHolder mHolder; + + public EnterGalleryDetailTransaction(DownloadHolder holder) { + mHolder = holder; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean onTransition(Context context, FragmentTransaction transaction, + Fragment exit, Fragment enter) { + if (!(enter instanceof GalleryDetailScene)) { + return false; + } + + exit.setSharedElementReturnTransition( + TransitionInflater.from(context).inflateTransition(R.transition.trans_move)); + exit.setExitTransition( + TransitionInflater.from(context).inflateTransition(android.R.transition.fade)); + enter.setSharedElementEnterTransition( + TransitionInflater.from(context).inflateTransition(R.transition.trans_move)); + enter.setEnterTransition( + TransitionInflater.from(context).inflateTransition(android.R.transition.fade)); + transaction.addSharedElement(mHolder.thumb, mHolder.thumb.getTransitionName()); + transaction.addSharedElement(mHolder.title, mHolder.title.getTransitionName()); + transaction.addSharedElement(mHolder.uploader, mHolder.uploader.getTransitionName()); + transaction.addSharedElement(mHolder.category, mHolder.category.getTransitionName()); + return true; + } + } + + private class DownloadAdapter extends RecyclerView.Adapter { + + @Override + public DownloadHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new DownloadHolder(getActivity().getLayoutInflater().inflate(R.layout.item_download, parent, false)); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public void onBindViewHolder(DownloadHolder holder, int position) { + if (mList == null) { + return; + } + DownloadInfo info = mList.get(position); + GalleryInfo gi = info.galleryInfo; + holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb, true); + holder.title.setText(gi.title); + holder.uploader.setText(gi.uploader); + holder.rating.setRating(gi.rating); + TextView category = holder.category; + String newCategoryText = EhUtils.getCategory(gi.category); + if (!newCategoryText.equals(category.getText())) { + category.setText(newCategoryText); + category.setBackgroundColor(EhUtils.getCategoryColor(gi.category)); + } + bindForState(holder, info); + + // Update transition name + if (ApiHelper.SUPPORT_TRANSITION) { + int gid = gi.gid; + holder.thumb.setTransitionName(TransitionNameFactory.getThumbTransitionName(gid)); + holder.title.setTransitionName(TransitionNameFactory.getTitleTransitionName(gid)); + holder.uploader.setTransitionName(TransitionNameFactory.getUploaderTransitionName(gid)); + holder.category.setTransitionName(TransitionNameFactory.getCategoryTransitionName(gid)); + } + } + + @Override + public int getItemCount() { + return mList == null ? 0 : mList.size(); + } + } +} 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 d45b05ac0..33c7da558 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 @@ -61,6 +61,7 @@ import com.hippo.ehviewer.client.data.GalleryTagGroup; import com.hippo.ehviewer.client.data.LargePreviewSet; import com.hippo.ehviewer.client.data.ListUrlBuilder; import com.hippo.ehviewer.client.parser.RateGalleryParser; +import com.hippo.ehviewer.download.DownloadService; import com.hippo.ehviewer.ui.GalleryActivity; import com.hippo.rippleold.RippleSalon; import com.hippo.scene.Announcer; @@ -870,7 +871,19 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene lub.setCategory(category); GalleryListScene.startScene(this, lub); } else if (mDownload == v) { - // TODO + // Add download + GalleryInfo galleryInfo = null; + if (mGalleryInfo != null) { + galleryInfo = mGalleryInfo; + } else if (mGalleryDetail != null) { + galleryInfo = mGalleryDetail; + } + if (galleryInfo != null) { + Intent intent = new Intent(getActivity(), DownloadService.class); + intent.setAction(DownloadService.ACTION_START); + intent.putExtra(DownloadService.KEY_GALLERY_INFO, galleryInfo); + getActivity().startService(intent); + } } else if (mRead == v) { GalleryInfo galleryInfo = null; if (mGalleryInfo != null) { @@ -946,7 +959,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene // Only show transaction when thumb can be seen if (location[1] + mThumb.getHeight() > 0) { setTransitionName(); - finish(new EnterGalleryListTransaction(mThumb, mTitle, mUploader, mCategory)); + finish(new ExitTransaction(mThumb, mTitle, mUploader, mCategory)); return; } } @@ -980,14 +993,14 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene } } - private static class EnterGalleryListTransaction implements TransitionHelper { + private static class ExitTransaction implements TransitionHelper { - private View mThumb; - private View mTitle; - private View mUploader; - private View mCategory; + private final View mThumb; + private final View mTitle; + private final View mUploader; + private final View mCategory; - public EnterGalleryListTransaction(View thumb, View title, View uploader, View category) { + public ExitTransaction(View thumb, View title, View uploader, View category) { mThumb = thumb; mTitle = title; mUploader = uploader; @@ -998,7 +1011,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene @TargetApi(Build.VERSION_CODES.LOLLIPOP) public boolean onTransition(Context context, FragmentTransaction transaction, Fragment exit, Fragment enter) { - if (!(enter instanceof GalleryListScene)) { + if (!(enter instanceof GalleryListScene) && !(enter instanceof DownloadScene)) { return false; } 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 d19b33f54..46b1ce6d9 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 @@ -204,6 +204,14 @@ public final class GalleryListScene extends BaseScene mState = savedInstanceState.getInt(KEY_STATE); } + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(KEY_HAS_FIRST_REFRESH, mHasFirstRefresh); + outState.putParcelable(KEY_LIST_URL_BUILDER, mUrlBuilder); + outState.putInt(KEY_STATE, mState); + } + private void setSearchBarHint(Context context, SearchBar searchBar) { Resources resources = context.getResources(); Drawable searchImage = VectorDrawable.create(context, R.xml.ic_magnify_x24); @@ -355,14 +363,6 @@ public final class GalleryListScene extends BaseScene mFabAnimatorListener = null; } - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(KEY_HAS_FIRST_REFRESH, mHasFirstRefresh); - outState.putParcelable(KEY_LIST_URL_BUILDER, mUrlBuilder); - outState.putInt(KEY_STATE, mState); - } - @Override public void onClick(View v) { if (mFab == v) { @@ -417,7 +417,7 @@ public final class GalleryListScene extends BaseScene private static class EnterGalleryDetailTransaction implements TransitionHelper { - private GalleryListHolder mHolder; + private final GalleryListHolder mHolder; public EnterGalleryDetailTransaction(GalleryListHolder holder) { mHolder = holder; @@ -494,85 +494,69 @@ public final class GalleryListScene extends BaseScene switch (oldState) { case STATE_NORMAL: - switch (state) { - case STATE_SIMPLE_SEARCH: - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - showFab(); - break; - case STATE_SEARCH: - mViewTransition.showView(1, animation); - mSearchLayout.scrollSearchContainerToTop(); - mSearchBar.setState(SearchBar.STATE_SEARCH, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - showFab(); - break; - case STATE_SEARCH_SHOW_LIST: - mViewTransition.showView(1, animation); - mSearchLayout.scrollSearchContainerToTop(); - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - showFab(); - break; + if (state == STATE_SIMPLE_SEARCH) { + mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); + showFab(); + } else if (state == STATE_SEARCH) { + mViewTransition.showView(1, animation); + mSearchLayout.scrollSearchContainerToTop(); + mSearchBar.setState(SearchBar.STATE_SEARCH, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); + showFab(); + } else if (state == STATE_SEARCH_SHOW_LIST) { + mViewTransition.showView(1, animation); + mSearchLayout.scrollSearchContainerToTop(); + mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); + showFab(); } break; case STATE_SIMPLE_SEARCH: - switch (state) { - case STATE_NORMAL: - mSearchBar.setState(SearchBar.STATE_NORMAL, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - hideFab(); - break; - case STATE_SEARCH: - mViewTransition.showView(1, animation); - mSearchLayout.scrollSearchContainerToTop(); - mSearchBar.setState(SearchBar.STATE_SEARCH, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - break; - case STATE_SEARCH_SHOW_LIST: - mViewTransition.showView(1, animation); - mSearchLayout.scrollSearchContainerToTop(); - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - break; + if (state == STATE_NORMAL) { + mSearchBar.setState(SearchBar.STATE_NORMAL, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); + hideFab(); + } else if (state == STATE_SEARCH) { + mViewTransition.showView(1, animation); + mSearchLayout.scrollSearchContainerToTop(); + mSearchBar.setState(SearchBar.STATE_SEARCH, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); + } else if (state == STATE_SEARCH_SHOW_LIST) { + mViewTransition.showView(1, animation); + mSearchLayout.scrollSearchContainerToTop(); + mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); } break; case STATE_SEARCH: - switch (state) { - case STATE_NORMAL: - mViewTransition.showView(0, animation); - mSearchBar.setState(SearchBar.STATE_NORMAL, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - hideFab(); - break; - case STATE_SIMPLE_SEARCH: - mViewTransition.showView(0, animation); - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - break; - case STATE_SEARCH_SHOW_LIST: - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - break; + if (state == STATE_NORMAL) { + mViewTransition.showView(0, animation); + mSearchBar.setState(SearchBar.STATE_NORMAL, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); + hideFab(); + } else if (state == STATE_SIMPLE_SEARCH) { + mViewTransition.showView(0, animation); + mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); + } else if (state == STATE_SEARCH_SHOW_LIST) { + mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); } break; case STATE_SEARCH_SHOW_LIST: - switch (state) { - case STATE_NORMAL: - mViewTransition.showView(0, animation); - mSearchBar.setState(SearchBar.STATE_NORMAL, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - hideFab(); - break; - case STATE_SIMPLE_SEARCH: - mViewTransition.showView(0, animation); - mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - break; - case STATE_SEARCH: - mSearchBar.setState(SearchBar.STATE_SEARCH, animation); - mSearchBarMoveHelper.returnSearchBarPosition(); - break; + if (state == STATE_NORMAL) { + mViewTransition.showView(0, animation); + mSearchBar.setState(SearchBar.STATE_NORMAL, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); + hideFab(); + } else if (state == STATE_SIMPLE_SEARCH) { + mViewTransition.showView(0, animation); + mSearchBar.setState(SearchBar.STATE_SEARCH_LIST, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); + } else if (state == STATE_SEARCH) { + mSearchBar.setState(SearchBar.STATE_SEARCH, animation); + mSearchBarMoveHelper.returnSearchBarPosition(); } break; } @@ -757,6 +741,7 @@ public final class GalleryListScene extends BaseScene returnSearchBarPosition(true); } + @SuppressWarnings("SimplifiableIfStatement") private void returnSearchBarPosition(boolean animation) { if (mSearchBar.getHeight() == 0) { // Layout not called @@ -919,13 +904,13 @@ public final class GalleryListScene extends BaseScene private class GalleryListHolder extends RecyclerView.ViewHolder { - private LoadImageView thumb; - private TextView title; - private TextView uploader; - private SimpleRatingView rating; - private TextView category; - private TextView posted; - private TextView simpleLanguage; + private final LoadImageView thumb; + private final TextView title; + private final TextView uploader; + private final SimpleRatingView rating; + private final TextView category; + private final TextView posted; + private final TextView simpleLanguage; public GalleryListHolder(View itemView) { super(itemView); @@ -942,7 +927,7 @@ public final class GalleryListScene extends BaseScene private class GalleryListAdapter extends RecyclerView.Adapter { - private LayoutInflater mInflater; + private final LayoutInflater mInflater; public GalleryListAdapter() { mInflater = getActivity().getLayoutInflater(); @@ -1053,7 +1038,7 @@ public final class GalleryListScene extends BaseScene private static class GetGalleryListListener extends EhCallback { - private int mTaskId; + private final int mTaskId; public GetGalleryListListener(Context context, int stageId, String sceneTag, int taskId) { super(context, stageId, sceneTag); diff --git a/app/src/main/java/com/hippo/okhttp/CookieDB.java b/app/src/main/java/com/hippo/okhttp/CookieDB.java index ea1f9e6c0..0306a9791 100644 --- a/app/src/main/java/com/hippo/okhttp/CookieDB.java +++ b/app/src/main/java/com/hippo/okhttp/CookieDB.java @@ -35,7 +35,7 @@ public class CookieDB { private static DaoSession sDaoSession; - public static class DBOpenHelper extends DaoMaster.OpenHelper { + private static class DBOpenHelper extends DaoMaster.OpenHelper { public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) { super(context, name, factory); @@ -56,7 +56,7 @@ public class CookieDB { sDaoSession = daoMaster.newSession(); } - public static Map> getAllCookies() { + static Map> getAllCookies() { OkHttp3CookieDao dao = sDaoSession.getOkHttp3CookieDao(); List list = dao.queryBuilder().list(); @@ -107,11 +107,11 @@ public class CookieDB { return result; } - public static void removeCookie(long id) { + static void removeCookie(long id) { sDaoSession.getOkHttp3CookieDao().deleteByKey(id); } - public static long addCookie(Cookie cookie) { + static long addCookie(Cookie cookie) { OkHttp3CookieDao dao = sDaoSession.getOkHttp3CookieDao(); OkHttp3CookieRaw raw = new OkHttp3CookieRaw(); diff --git a/app/src/main/java/com/hippo/scene/SceneFragment.java b/app/src/main/java/com/hippo/scene/SceneFragment.java index a216a11d0..8ff0fe3b9 100644 --- a/app/src/main/java/com/hippo/scene/SceneFragment.java +++ b/app/src/main/java/com/hippo/scene/SceneFragment.java @@ -25,6 +25,7 @@ import android.support.v4.app.FragmentActivity; import android.view.View; import com.hippo.ehviewer.R; +import com.hippo.ehviewer.ui.MainActivity; import com.hippo.yorozuya.IntList; import java.lang.annotation.Retention; @@ -113,6 +114,17 @@ public class SceneFragment extends Fragment { view.setTag(R.id.fragment_tag, getTag()); view.setBackgroundColor(getContext().getResources().getColor(R.color.background_light)); + + // Notify + ((MainActivity) getActivity()).onSceneViewCreated(this, savedInstanceState); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + + // Notify + ((MainActivity) getActivity()).onSceneViewDestroyed(this); } void addRequest(String requestSceneTag, int requestCode) { diff --git a/app/src/main/java/com/hippo/scene/StageActivity.java b/app/src/main/java/com/hippo/scene/StageActivity.java index e50d9d46b..16b578ce4 100644 --- a/app/src/main/java/com/hippo/scene/StageActivity.java +++ b/app/src/main/java/com/hippo/scene/StageActivity.java @@ -30,6 +30,7 @@ import com.hippo.ehviewer.R; import com.hippo.yorozuya.IntIdGenerator; import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; public abstract class StageActivity extends AppCompatActivity { @@ -44,7 +45,7 @@ public abstract class StageActivity extends AppCompatActivity { // TODO ArrayList or LinkedList private ArrayList mSceneTagList = new ArrayList<>(); - private IntIdGenerator mIdGenerator = new IntIdGenerator(); + private final AtomicInteger mIdGenerator = new AtomicInteger(); private int mStageId = IntIdGenerator.INVALID_ID; @@ -81,7 +82,7 @@ public abstract class StageActivity extends AppCompatActivity { if (savedInstanceState != null) { mStageId = savedInstanceState.getInt(KEY_STAGE_ID, IntIdGenerator.INVALID_ID); mSceneTagList = savedInstanceState.getStringArrayList(KEY_SCENE_TAG_LIST); - mIdGenerator.setNextId(savedInstanceState.getInt(KEY_NEXT_ID)); + mIdGenerator.lazySet(savedInstanceState.getInt(KEY_NEXT_ID)); } if (mStageId == IntIdGenerator.INVALID_ID) { @@ -97,6 +98,12 @@ public abstract class StageActivity extends AppCompatActivity { ((SceneApplication) getApplicationContext()).unregisterStageActivity(mStageId); } + public void onSceneViewCreated(SceneFragment scene, Bundle savedInstanceState) { + } + + public void onSceneViewDestroyed(SceneFragment scene) { + } + protected void onRegister(int id) { mStageId = id; } @@ -165,7 +172,7 @@ public abstract class StageActivity extends AppCompatActivity { newScene.setArguments(args); // Create new scene tag - newTag = Integer.toString(mIdGenerator.nextId()); + newTag = Integer.toString(mIdGenerator.getAndIncrement()); // Add new tag to list mSceneTagList.add(newTag); @@ -359,6 +366,6 @@ public abstract class StageActivity extends AppCompatActivity { super.onSaveInstanceState(outState); outState.putInt(KEY_STAGE_ID, mStageId); outState.putStringArrayList(KEY_SCENE_TAG_LIST, mSceneTagList); - outState.putInt(KEY_NEXT_ID, mIdGenerator.nextId()); + outState.putInt(KEY_NEXT_ID, mIdGenerator.getAndIncrement()); } } diff --git a/app/src/main/java/com/hippo/util/SqlUtils.java b/app/src/main/java/com/hippo/util/SqlUtils.java new file mode 100644 index 000000000..3370fd946 --- /dev/null +++ b/app/src/main/java/com/hippo/util/SqlUtils.java @@ -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.util; + +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; + +import java.util.ArrayList; +import java.util.List; + +public class SqlUtils { + + public static void exeSQLSafely(SQLiteDatabase db, String sql) { + try { + db.execSQL(sql); + } catch (SQLException e) { + // Ignore + } + } + + public static void dropTable(SQLiteDatabase db, String tableName) { + exeSQLSafely(db, "DROP TABLE IF EXISTS " + tableName); + } + + public static void dropAllTable(SQLiteDatabase db) { + List tables = new ArrayList(); + Cursor cursor = db.rawQuery("SELECT * FROM sqlite_master WHERE type='table';", null); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + String tableName = cursor.getString(1); + if (!tableName.equals("android_metadata") && + !tableName.equals("sqlite_sequence")) + tables.add(tableName); + cursor.moveToNext(); + } + cursor.close(); + + for(String tableName : tables) { + dropTable(db, tableName); + } + } +} diff --git a/app/src/main/java/com/hippo/widget/FabLayout.java b/app/src/main/java/com/hippo/widget/FabLayout.java new file mode 100644 index 000000000..683d84659 --- /dev/null +++ b/app/src/main/java/com/hippo/widget/FabLayout.java @@ -0,0 +1,351 @@ +/* + * 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.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.design.widget.FloatingActionButton; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Interpolator; + +import com.hippo.anani.AnimationUtils; +import com.hippo.anani.SimpleAnimatorListener; +import com.hippo.ehviewer.R; + +public class FabLayout extends ViewGroup implements View.OnClickListener { + + private static final long ANIMATE_TIME = 300L; + + private static final String STATE_KEY_SUPER = "super"; + private static final String STATE_KEY_AUTO_CANCEL = "auto_cancel"; + private static final String STATE_KEY_EXPANDED = "expanded"; + + private int mInterval; + + private boolean mFirst = true; + private boolean mExpanded = false; + private boolean mAutoCancel = true; + private float mMainFabCenterY = -1f; + + private OnExpandListener mOnExpandListener; + + public FabLayout(Context context) { + super(context); + init(context); + } + + public FabLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public FabLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + setSoundEffectsEnabled(false); + mInterval = context.getResources().getDimensionPixelOffset(R.dimen.keyline_margin); + } + + @Override + public void addView(@NonNull View child, int index, ViewGroup.LayoutParams params) { + if (!(child instanceof FloatingActionButton)) { + throw new IllegalStateException("FloatingActionBarLayout should only " + + "contain FloatingActionButton, but try to add "+ child.getClass().getName()); + } + super.addView(child, index, params); + } + + public FloatingActionButton getPrimaryFab() { + View v = getChildAt(getChildCount() - 1); + if (v == null) { + return null; + } else { + return (FloatingActionButton) v; + } + } + + public int getSecondaryFabCount() { + return Math.max(0, getChildCount() - 1); + } + + public FloatingActionButton getSecondaryFabAt(int index) { + if (index < 0 || index >= getSecondaryFabCount()) { + return null; + } + return (FloatingActionButton) getChildAt(index); + } + + private int getChildMeasureSpec(int parentMeasureSpec) { + int parentMode = MeasureSpec.getMode(parentMeasureSpec); + int parentSize = MeasureSpec.getSize(parentMeasureSpec); + int childMode; + int childSize; + switch (parentMode) { + default: + case MeasureSpec.AT_MOST: + case MeasureSpec.EXACTLY: + childMode = MeasureSpec.AT_MOST; + childSize = parentSize; + break; + case MeasureSpec.UNSPECIFIED: + childMode = MeasureSpec.UNSPECIFIED; + childSize = parentSize; + } + return MeasureSpec.makeMeasureSpec(childSize, childMode); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mFirst) { + mFirst = false; + boolean expanded = mExpanded; + int count = getChildCount() - 1; + for (int i = 0; i < count; i++) { + getChildAt(i).setVisibility(expanded ? View.VISIBLE : View.INVISIBLE); + } + } + + int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec); + int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec); + measureChildren(childWidthMeasureSpec, childHeightMeasureSpec); + + int maxWidth = 0; + int maxHeight = 0; + boolean firstChild = true; + int count = getChildCount(); + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (child.getVisibility() == View.GONE) { + continue; + } + + maxWidth = Math.max(maxWidth, child.getMeasuredWidth()); + maxHeight = maxHeight + child.getMeasuredHeight(); + if (firstChild) { + firstChild = false; + } else { + maxHeight += mInterval; + } + } + + // Account for padding too + maxWidth += getPaddingLeft() + getPaddingRight(); + maxHeight += getPaddingTop() + getPaddingBottom(); + + // Check against minimum height and width + maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); + maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); + + setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0), + resolveSizeAndState(maxHeight, heightMeasureSpec, 0)); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int interval = mInterval; + int centerX = 0; + int bottom = getMeasuredHeight() - getPaddingBottom(); + int count = getChildCount(); + int i = count; + while(--i >= 0) { + View child = getChildAt(i); + if (child.getVisibility() == View.GONE) { + continue; + } + + int childWidth = child.getMeasuredWidth(); + int childHeight = child.getMeasuredHeight(); + int right; + if (i == count - 1) { + right = getMeasuredWidth() - getPaddingRight(); + } else { + right = centerX + (childWidth / 2); + } + child.layout(right - childWidth, bottom - childHeight, right, bottom); + + if (i == count - 1) { + centerX = right - (childWidth / 2); + mMainFabCenterY = bottom - (childHeight / 2f); + } + + bottom -= (childHeight + interval); + } + } + + public void setOnExpandListener(OnExpandListener listener) { + mOnExpandListener = listener; + } + + public void setAutoCancel(boolean autoCancel) { + if (mAutoCancel != autoCancel) { + mAutoCancel = autoCancel; + + if (mExpanded) { + if (autoCancel) { + setOnClickListener(this); + } else { + setClickable(false); + } + } + } + } + + public void toggle() { + setExpanded(!mExpanded); + } + + public boolean isExpanded() { + return mExpanded; + } + + public void setExpanded(boolean expanded) { + setExpanded(expanded, true); + } + + public void setExpanded(boolean expanded, boolean animation) { + if (mExpanded != expanded) { + mExpanded = expanded; + + if (mAutoCancel) { + if (expanded) { + setOnClickListener(this); + } else { + setClickable(false); + } + } + + if (mMainFabCenterY == -1f) { + // It is before first onLayout + int count = getChildCount() - 1; + for (int i = 0; i < count; i++) { + getChildAt(i).setVisibility(expanded ? View.VISIBLE : View.INVISIBLE); + } + } else { + int count = getChildCount() - 1; + for (int i = 0; i < count; i++) { + View child = getChildAt(i); + if (animation) { + setChildAnimation(child, expanded); + } else { + child.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE); + if (expanded) { + child.setAlpha(1f); + } + } + } + } + + if (mOnExpandListener != null) { + mOnExpandListener.onExpand(expanded); + } + } + } + + private void setChildAnimation(final View child, final boolean expanded) { + float startTranslationY; + float endTranslationY; + float startAlpha; + float endAlpha; + Interpolator interpolator; + if (expanded) { + startTranslationY = mMainFabCenterY - + (child.getTop() + (child.getHeight() / 2)); + endTranslationY = 0f; + startAlpha = 0f; + endAlpha = 1f; + interpolator = AnimationUtils.FAST_SLOW_INTERPOLATOR; + } else { + startTranslationY = 0f; + endTranslationY = mMainFabCenterY - + (child.getTop() + (child.getHeight() / 2)); + startAlpha = 1f; + endAlpha = 0f; + interpolator = AnimationUtils.SLOW_FAST_INTERPOLATOR; + } + + PropertyValuesHolder translationYPvh = PropertyValuesHolder.ofFloat( + "translationY", startTranslationY, endTranslationY); + PropertyValuesHolder alphaPvh = PropertyValuesHolder.ofFloat( + "alpha", startAlpha, endAlpha); + ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(child, translationYPvh, alphaPvh); + oa.setDuration(ANIMATE_TIME); + oa.setInterpolator(interpolator); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + oa.setAutoCancel(true); + } + oa.addListener(new SimpleAnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + if (expanded) { + child.setVisibility(View.VISIBLE); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (!expanded) { + child.setVisibility(View.INVISIBLE); + } + } + }); + oa.start(); + } + + @Override + public void onClick(View v) { + setExpanded(false); + } + + @Override + protected void dispatchSetPressed(boolean pressed) { + // Don't dispatch it to children + } + + @Override + public Parcelable onSaveInstanceState() { + final Bundle state = new Bundle(); + state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState()); + state.putBoolean(STATE_KEY_AUTO_CANCEL, mAutoCancel); + state.putBoolean(STATE_KEY_EXPANDED, mExpanded); + return state; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (state instanceof Bundle) { + final Bundle savedState = (Bundle) state; + super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER)); + setAutoCancel(savedState.getBoolean(STATE_KEY_AUTO_CANCEL)); + setExpanded(savedState.getBoolean(STATE_KEY_EXPANDED), false); + } + } + + public interface OnExpandListener { + void onExpand(boolean expanded); + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_close_x24.png b/app/src/main/res/drawable-hdpi/ic_close_x24.png new file mode 100644 index 000000000..d1cfcaa91 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_close_x24.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_info_outline_x24.png b/app/src/main/res/drawable-hdpi/ic_info_outline_x24.png new file mode 100644 index 000000000..848324488 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_info_outline_x24.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_pause_x24.png b/app/src/main/res/drawable-hdpi/ic_pause_x24.png new file mode 100644 index 000000000..411c4987e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_pause_x24.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_play_x24.png b/app/src/main/res/drawable-hdpi/ic_play_x24.png new file mode 100644 index 000000000..f1fdff9c7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_play_x24.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_stat_alert.png b/app/src/main/res/drawable-hdpi/ic_stat_alert.png new file mode 100644 index 000000000..fcc614c04 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_stat_alert.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_close_x24.png b/app/src/main/res/drawable-mdpi/ic_close_x24.png new file mode 100644 index 000000000..21322cdbe Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_close_x24.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_info_outline_x24.png b/app/src/main/res/drawable-mdpi/ic_info_outline_x24.png new file mode 100644 index 000000000..7478dcb5e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_info_outline_x24.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_pause_x24.png b/app/src/main/res/drawable-mdpi/ic_pause_x24.png new file mode 100644 index 000000000..73c388b00 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_pause_x24.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_play_x24.png b/app/src/main/res/drawable-mdpi/ic_play_x24.png new file mode 100644 index 000000000..9aaabcbce Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_play_x24.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_stat_alert.png b/app/src/main/res/drawable-mdpi/ic_stat_alert.png new file mode 100644 index 000000000..d8d4f2fbb Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_stat_alert.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_close_x24.png b/app/src/main/res/drawable-xhdpi/ic_close_x24.png new file mode 100644 index 000000000..89bfb48d6 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_close_x24.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_info_outline_x24.png b/app/src/main/res/drawable-xhdpi/ic_info_outline_x24.png new file mode 100644 index 000000000..c34be33df Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_info_outline_x24.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_pause_x24.png b/app/src/main/res/drawable-xhdpi/ic_pause_x24.png new file mode 100644 index 000000000..0045704e3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_pause_x24.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_x24.png b/app/src/main/res/drawable-xhdpi/ic_play_x24.png new file mode 100644 index 000000000..25e57c052 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_x24.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_alert.png b/app/src/main/res/drawable-xhdpi/ic_stat_alert.png new file mode 100644 index 000000000..1eddd640d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_stat_alert.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_close_x24.png b/app/src/main/res/drawable-xxhdpi/ic_close_x24.png new file mode 100644 index 000000000..9b7fa46ed Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_close_x24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_info_outline_x24.png b/app/src/main/res/drawable-xxhdpi/ic_info_outline_x24.png new file mode 100644 index 000000000..f2ea92763 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_info_outline_x24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_pause_x24.png b/app/src/main/res/drawable-xxhdpi/ic_pause_x24.png new file mode 100644 index 000000000..e0762aff7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_pause_x24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_play_x24.png b/app/src/main/res/drawable-xxhdpi/ic_play_x24.png new file mode 100644 index 000000000..8c75c621d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_play_x24.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_alert.png b/app/src/main/res/drawable-xxhdpi/ic_stat_alert.png new file mode 100644 index 000000000..59ec28a99 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_stat_alert.png differ diff --git a/app/src/main/res/layout/activity_gallery.xml b/app/src/main/res/layout/activity_gallery.xml index cb4415a48..5e1501005 100644 --- a/app/src/main/res/layout/activity_gallery.xml +++ b/app/src/main/res/layout/activity_gallery.xml @@ -15,10 +15,8 @@ ~ limitations under the License. --> - + - + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 36e664e6d..5e2ce7678 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -38,4 +38,11 @@ app:headerLayout="@layout/nav_header_main" app:menu="@menu/nav_drawer_main"/> + + diff --git a/app/src/main/res/layout/activity_simple_fragment.xml b/app/src/main/res/layout/activity_simple_fragment.xml deleted file mode 100644 index f28a9f194..000000000 --- a/app/src/main/res/layout/activity_simple_fragment.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - diff --git a/app/src/main/res/layout/item_download.xml b/app/src/main/res/layout/item_download.xml new file mode 100644 index 000000000..47da739b2 --- /dev/null +++ b/app/src/main/res/layout/item_download.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/scene_download.xml b/app/src/main/res/layout/scene_download.xml new file mode 100644 index 000000000..133ce72e4 --- /dev/null +++ b/app/src/main/res/layout/scene_download.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/scene_login.xml b/app/src/main/res/layout/scene_login.xml index 510d2d53e..41a7a3b19 100644 --- a/app/src/main/res/layout/scene_login.xml +++ b/app/src/main/res/layout/scene_login.xml @@ -53,6 +53,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/username" + android:imeOptions="actionNext" android:inputType="text" android:maxLines="1" android:singleLine="true"/> diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 733b12226..87b5b4d0a 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -172,6 +172,22 @@ 画廊预览 跳页 + + 无下载项目 + 未启动 + 等待中 + 失败 + %d 未完成 + 完成 + 509 警告 + 图片配额已用尽。请停止下载,休息一下。 + 下载结束 + %d 成功 + %d 失败 + %1$d 成功,%2$d 失败 + 成功:%s + 失败:%s + 第 %d 页 @string/refresh diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index e62ff29da..2b6ba4eb6 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -172,6 +172,22 @@ 畫廊預覽 跳頁 + + 無下載項目 + 未啟動 + 等待中 + 失敗 + %d 未完成 + 完成 + 509 警告 + 圖片配額已用盡。請停止下載,休息一下。 + 下載結束 + %d 成功 + %d 失敗 + %1$d 成功,%2$d 失敗 + 成功:%s + 失敗:%s + 第 %d 頁 @string/refresh @@ -191,6 +207,21 @@ 從左至右 從右至左 從上至下 + 頁面縮放 + 原始尺寸 + 匹配寬度 + 匹配長度 + 匹配屏幕 + 固定縮放 + 開頁位置 + 左上角 + 右上角 + 左下角 + 右下角 + 中心 + 屏幕常亮 + 顯示時鐘 + 顯示電量 使用音量鍵翻頁 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 4b7e15cdc..89bb2b61f 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -172,6 +172,22 @@ 畫廊預覽 跳頁 + + 無下載項目 + 未啟動 + 等待中 + 失敗 + %d 未完成 + 完成 + 509 警告 + 圖片配額已用盡。請停止下載,休息一下。 + 下載結束 + %d 成功 + %d 失敗 + %1$d 成功,%2$d 失敗 + 成功:%s + 失敗:%s + 第 %d 頁 @string/refresh @@ -191,6 +207,21 @@ 從左至右 從右至左 從上至下 + 頁面縮放 + 原始尺寸 + 匹配寬度 + 匹配長度 + 匹配螢幕 + 固定縮放 + 開頁位置 + 左上角 + 右上角 + 左下角 + 右下角 + 中心 + 螢幕常亮 + 顯示時鐘 + 顯示電量 使用音量鍵翻頁 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0491e6d41..059670358 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -212,6 +212,22 @@ Gallery Previews Go to + + No download items + Idle + Waiting + Failed + %d incomplete + Done + 509 Alert + Image limit is reached. Please stop download and have a relax. + Download Finished + %d succeeded + %d failed + %1$d succeeded, %2$d failed + Succeeded: %s + Failed: %s + Page %d @string/refresh diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 456c636b5..57c03cc0b 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> - + + + diff --git a/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/EhDaoGenerator.java b/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/EhDaoGenerator.java new file mode 100644 index 000000000..4aef747e2 --- /dev/null +++ b/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/EhDaoGenerator.java @@ -0,0 +1,114 @@ +/* + * 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.daogenerator; + +import java.io.File; + +import de.greenrobot.daogenerator.DaoGenerator; +import de.greenrobot.daogenerator.Entity; +import de.greenrobot.daogenerator.Schema; + +public class EhDaoGenerator { + + private static final String PACKAGE = "com.hippo.ehviewer.dao"; + private static final String OUT_DIR = "../app/src/main/java-gen"; + private static final String DELETE_DIR = "../app/src/main/java-gen/com/hippo/ehviewer/dao"; + + private static final int VERSION = 1; + + public static void generate() throws Exception { + Utilities.deleteContents(new File(DELETE_DIR)); + File outDir = new File(OUT_DIR); + outDir.delete(); + outDir.mkdirs(); + + Schema schema = new Schema(VERSION, PACKAGE); + addGalleryInfo(schema); + addDownloadInfo(schema); + addDownloadLabel(schema); + addHistoryInfo(schema); + addQuickSearch(schema); + addLocalFavorite(schema); + new DaoGenerator().generateAll(schema, OUT_DIR); + } + + private static void addGalleryInfo(Schema schema) { + Entity entity = schema.addEntity("GalleryInfoRaw"); + entity.setTableName("GALLERY_INFO"); + entity.setClassNameDao("GalleryInfoDao"); + entity.addLongProperty("gid").primaryKey(); + entity.addStringProperty("token"); + entity.addStringProperty("title"); + entity.addStringProperty("posted"); + entity.addIntProperty("category"); + entity.addStringProperty("thumb"); + entity.addStringProperty("uploader"); + entity.addFloatProperty("rating"); + entity.addIntProperty("reference"); + } + + private static void addDownloadInfo(Schema schema) { + Entity entity = schema.addEntity("DownloadInfoRaw"); + entity.setTableName("DOWNLOAD_INFO"); + entity.setClassNameDao("DownloadInfoDao"); + entity.addLongProperty("gid").primaryKey(); + entity.addIntProperty("state"); + entity.addIntProperty("legacy"); + entity.addLongProperty("date"); + entity.addStringProperty("label"); + } + + private static void addDownloadLabel(Schema schema) { + Entity entity = schema.addEntity("DownloadLabelRaw"); + entity.setTableName("DOWNLOAD_LABEL"); + entity.setClassNameDao("DownloadLabelDao"); + entity.addIdProperty(); + entity.addStringProperty("label"); + entity.addLongProperty("time"); + } + + private static void addHistoryInfo(Schema schema) { + Entity entity = schema.addEntity("HistoryInfoRaw"); + entity.setTableName("HISTORY_INFO"); + entity.setClassNameDao("HistoryInfoDao"); + entity.addLongProperty("gid").primaryKey(); + entity.addIntProperty("mode"); + entity.addLongProperty("date"); + } + + private static void addQuickSearch(Schema schema) { + Entity entity = schema.addEntity("QuickSearchRaw"); + entity.setTableName("QUICK_SEARCH"); + entity.setClassNameDao("QuickSearchDao"); + entity.addIdProperty(); + entity.addStringProperty("name"); + entity.addIntProperty("mode"); + entity.addIntProperty("category"); + entity.addStringProperty("keyword"); + entity.addIntProperty("advanceSearch"); + entity.addIntProperty("minRating"); + entity.addLongProperty("date"); + } + + private static void addLocalFavorite(Schema schema) { + Entity entity = schema.addEntity("LocalFavoriteRaw"); + entity.setTableName("LOCAL_FAVORITE"); + entity.setClassNameDao("LocalFavoriteDao"); + entity.addLongProperty("gid").primaryKey(); + entity.addLongProperty("date"); + } +} diff --git a/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/Main.java b/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/Main.java index eda23e0a7..f55b22212 100644 --- a/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/Main.java +++ b/daogenerator/src/main/java/com/hippo/ehviewer/daogenerator/Main.java @@ -20,5 +20,6 @@ public class Main { public static void main(String args[]) throws Exception { CookieDaoGenerator.generate(); + EhDaoGenerator.generate(); } }