Almost showing a chapter reader
This commit is contained in:
parent
49c69be38e
commit
5142df103b
@ -62,6 +62,7 @@ dependencies {
|
|||||||
compile 'com.squareup.okhttp:okhttp-urlconnection:2.4.0'
|
compile 'com.squareup.okhttp:okhttp-urlconnection:2.4.0'
|
||||||
compile 'com.squareup.okhttp:okhttp:2.4.0'
|
compile 'com.squareup.okhttp:okhttp:2.4.0'
|
||||||
compile 'com.squareup.okio:okio:1.6.0'
|
compile 'com.squareup.okio:okio:1.6.0'
|
||||||
|
compile 'com.google.code.gson:gson:2.4'
|
||||||
compile 'com.jakewharton:disklrucache:2.0.2'
|
compile 'com.jakewharton:disklrucache:2.0.2'
|
||||||
compile 'org.jsoup:jsoup:1.8.3'
|
compile 'org.jsoup:jsoup:1.8.3'
|
||||||
compile 'io.reactivex:rxandroid:1.0.1'
|
compile 'io.reactivex:rxandroid:1.0.1'
|
||||||
@ -76,6 +77,7 @@ dependencies {
|
|||||||
compile 'com.jakewharton.timber:timber:3.1.0'
|
compile 'com.jakewharton.timber:timber:3.1.0'
|
||||||
compile 'uk.co.ribot:easyadapter:1.5.0@aar'
|
compile 'uk.co.ribot:easyadapter:1.5.0@aar'
|
||||||
compile 'ch.acra:acra:4.6.2'
|
compile 'ch.acra:acra:4.6.2'
|
||||||
|
compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.4.1'
|
||||||
compile "frankiesardo:icepick:$ICEPICK_VERSION"
|
compile "frankiesardo:icepick:$ICEPICK_VERSION"
|
||||||
provided "frankiesardo:icepick-processor:$ICEPICK_VERSION"
|
provided "frankiesardo:icepick-processor:$ICEPICK_VERSION"
|
||||||
|
|
||||||
|
@ -37,6 +37,15 @@
|
|||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="eu.kanade.mangafeed.ui.activity.MainActivity" />
|
android:value="eu.kanade.mangafeed.ui.activity.MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.activity.ViewerActivity"
|
||||||
|
android:label="@string/title_activity_viewer"
|
||||||
|
android:parentActivityName=".ui.activity.MangaDetailActivity"
|
||||||
|
android:theme="@style/AppTheme" >
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="eu.kanade.mangafeed.ui.activity.MangaDetailActivity" />
|
||||||
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -8,10 +8,12 @@ import dagger.Component;
|
|||||||
import eu.kanade.mangafeed.data.DataModule;
|
import eu.kanade.mangafeed.data.DataModule;
|
||||||
import eu.kanade.mangafeed.presenter.CataloguePresenter;
|
import eu.kanade.mangafeed.presenter.CataloguePresenter;
|
||||||
import eu.kanade.mangafeed.presenter.LibraryPresenter;
|
import eu.kanade.mangafeed.presenter.LibraryPresenter;
|
||||||
|
import eu.kanade.mangafeed.presenter.MainPresenter;
|
||||||
import eu.kanade.mangafeed.presenter.MangaChaptersPresenter;
|
import eu.kanade.mangafeed.presenter.MangaChaptersPresenter;
|
||||||
import eu.kanade.mangafeed.presenter.MangaDetailPresenter;
|
import eu.kanade.mangafeed.presenter.MangaDetailPresenter;
|
||||||
import eu.kanade.mangafeed.presenter.MangaInfoPresenter;
|
import eu.kanade.mangafeed.presenter.MangaInfoPresenter;
|
||||||
import eu.kanade.mangafeed.presenter.SourcePresenter;
|
import eu.kanade.mangafeed.presenter.SourcePresenter;
|
||||||
|
import eu.kanade.mangafeed.presenter.ViewerPresenter;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Component(
|
@Component(
|
||||||
@ -22,12 +24,14 @@ import eu.kanade.mangafeed.presenter.SourcePresenter;
|
|||||||
)
|
)
|
||||||
public interface AppComponent {
|
public interface AppComponent {
|
||||||
|
|
||||||
|
void inject(MainPresenter mainPresenter);
|
||||||
void inject(LibraryPresenter libraryPresenter);
|
void inject(LibraryPresenter libraryPresenter);
|
||||||
void inject(MangaDetailPresenter mangaDetailPresenter);
|
void inject(MangaDetailPresenter mangaDetailPresenter);
|
||||||
void inject(SourcePresenter sourcePresenter);
|
void inject(SourcePresenter sourcePresenter);
|
||||||
void inject(CataloguePresenter cataloguePresenter);
|
void inject(CataloguePresenter cataloguePresenter);
|
||||||
void inject(MangaInfoPresenter mangaInfoPresenter);
|
void inject(MangaInfoPresenter mangaInfoPresenter);
|
||||||
void inject(MangaChaptersPresenter mangaChaptersPresenter);
|
void inject(MangaChaptersPresenter mangaChaptersPresenter);
|
||||||
|
void inject(ViewerPresenter viewerPresenter);
|
||||||
|
|
||||||
Application application();
|
Application application();
|
||||||
|
|
||||||
|
@ -5,20 +5,23 @@ import android.content.Context;
|
|||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.bumptech.glide.request.FutureTarget;
|
import com.bumptech.glide.request.FutureTarget;
|
||||||
import com.bumptech.glide.request.target.Target;
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.jakewharton.disklrucache.DiskLruCache;
|
import com.jakewharton.disklrucache.DiskLruCache;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import eu.kanade.mangafeed.data.models.Page;
|
||||||
import eu.kanade.mangafeed.util.DiskUtils;
|
import eu.kanade.mangafeed.util.DiskUtils;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.functions.Action0;
|
|
||||||
|
|
||||||
public class CacheManager {
|
public class CacheManager {
|
||||||
|
|
||||||
@ -29,11 +32,13 @@ public class CacheManager {
|
|||||||
private static final int READ_TIMEOUT = 60;
|
private static final int READ_TIMEOUT = 60;
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
|
private Gson mGson;
|
||||||
|
|
||||||
private DiskLruCache mDiskCache;
|
private DiskLruCache mDiskCache;
|
||||||
|
|
||||||
public CacheManager(Context context) {
|
public CacheManager(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
mGson = new Gson();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mDiskCache = DiskLruCache.open(
|
mDiskCache = DiskLruCache.open(
|
||||||
@ -109,16 +114,11 @@ public class CacheManager {
|
|||||||
return isSuccessful;
|
return isSuccessful;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Observable<String> getImageUrlsFromDiskCache(final String chapterUrl) {
|
public Observable<List<Page>> getPageUrlsFromDiskCache(final String chapterUrl) {
|
||||||
return Observable.create(subscriber -> {
|
return Observable.create(subscriber -> {
|
||||||
try {
|
try {
|
||||||
String[] imageUrls = getImageUrlsFromDiskCacheImpl(chapterUrl);
|
List<Page> pages = getPageUrlsFromDiskCacheImpl(chapterUrl);
|
||||||
|
subscriber.onNext(pages);
|
||||||
for (String imageUrl : imageUrls) {
|
|
||||||
if (!subscriber.isUnsubscribed()) {
|
|
||||||
subscriber.onNext(imageUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subscriber.onCompleted();
|
subscriber.onCompleted();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
subscriber.onError(e);
|
subscriber.onError(e);
|
||||||
@ -126,35 +126,28 @@ public class CacheManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] getImageUrlsFromDiskCacheImpl(String chapterUrl) throws IOException {
|
private List<Page> getPageUrlsFromDiskCacheImpl(String chapterUrl) throws IOException {
|
||||||
DiskLruCache.Snapshot snapshot = null;
|
DiskLruCache.Snapshot snapshot = null;
|
||||||
|
List<Page> pages = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String key = DiskUtils.hashKeyForDisk(chapterUrl);
|
String key = DiskUtils.hashKeyForDisk(chapterUrl);
|
||||||
|
|
||||||
snapshot = mDiskCache.get(key);
|
snapshot = mDiskCache.get(key);
|
||||||
|
|
||||||
String joinedImageUrls = snapshot.getString(0);
|
Type collectionType = new TypeToken<List<Page>>() {}.getType();
|
||||||
return joinedImageUrls.split(",");
|
pages = mGson.fromJson(snapshot.getString(0), collectionType);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Do Nothing.
|
||||||
} finally {
|
} finally {
|
||||||
if (snapshot != null) {
|
if (snapshot != null) {
|
||||||
snapshot.close();
|
snapshot.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return pages;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action0 putImageUrlsToDiskCache(final String chapterUrl, final List<String> imageUrls) {
|
public void putPageUrlsToDiskCache(final String chapterUrl, final List<Page> pages) {
|
||||||
return () -> {
|
String cachedValue = mGson.toJson(pages);
|
||||||
try {
|
|
||||||
putImageUrlsToDiskCacheImpl(chapterUrl, imageUrls);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Do Nothing.
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void putImageUrlsToDiskCacheImpl(String chapterUrl, List<String> imageUrls) throws IOException {
|
|
||||||
String cachedValue = joinImageUrlsToCacheValue(imageUrls);
|
|
||||||
|
|
||||||
DiskLruCache.Editor editor = null;
|
DiskLruCache.Editor editor = null;
|
||||||
OutputStream outputStream = null;
|
OutputStream outputStream = null;
|
||||||
@ -171,13 +164,11 @@ public class CacheManager {
|
|||||||
|
|
||||||
mDiskCache.flush();
|
mDiskCache.flush();
|
||||||
editor.commit();
|
editor.commit();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Do Nothing.
|
||||||
} finally {
|
} finally {
|
||||||
if (editor != null) {
|
if (editor != null) {
|
||||||
try {
|
editor.abortUnlessCommitted();
|
||||||
editor.abort();
|
|
||||||
} catch (IOException ignore) {
|
|
||||||
// Do Nothing.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (outputStream != null) {
|
if (outputStream != null) {
|
||||||
try {
|
try {
|
||||||
@ -189,22 +180,9 @@ public class CacheManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String joinImageUrlsToCacheValue(List<String> imageUrls) {
|
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
|
||||||
for (int index = 0; index < imageUrls.size(); index++) {
|
|
||||||
if (index == 0) {
|
|
||||||
stringBuilder.append(imageUrls.get(index));
|
|
||||||
} else {
|
|
||||||
stringBuilder.append(",");
|
|
||||||
stringBuilder.append(imageUrls.get(index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringBuilder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getCacheDir() {
|
public File getCacheDir() {
|
||||||
return mDiskCache.getDirectory();
|
return mDiskCache.getDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
43
app/src/main/java/eu/kanade/mangafeed/data/models/Page.java
Normal file
43
app/src/main/java/eu/kanade/mangafeed/data/models/Page.java
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package eu.kanade.mangafeed.data.models;
|
||||||
|
|
||||||
|
public class Page {
|
||||||
|
|
||||||
|
private int pageNumber;
|
||||||
|
private String url;
|
||||||
|
private String imageUrl;
|
||||||
|
|
||||||
|
public Page(int pageNumber, String url, String imageUrl) {
|
||||||
|
this.pageNumber = pageNumber;
|
||||||
|
this.url = url;
|
||||||
|
this.imageUrl = imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page(int pageNumber, String url) {
|
||||||
|
this(pageNumber, url, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPageNumber() {
|
||||||
|
return pageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImageUrl() {
|
||||||
|
return imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageUrl(String imageUrl) {
|
||||||
|
this.imageUrl = imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Page{" +
|
||||||
|
"pageNumber=" + pageNumber +
|
||||||
|
", url='" + url + '\'' +
|
||||||
|
", imageUrl='" + imageUrl + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package eu.kanade.mangafeed.presenter;
|
||||||
|
|
||||||
|
import eu.kanade.mangafeed.ui.activity.MainActivity;
|
||||||
|
|
||||||
|
public class MainPresenter extends BasePresenter<MainActivity> {
|
||||||
|
|
||||||
|
}
|
@ -13,13 +13,14 @@ import eu.kanade.mangafeed.data.helpers.DatabaseHelper;
|
|||||||
import eu.kanade.mangafeed.data.helpers.SourceManager;
|
import eu.kanade.mangafeed.data.helpers.SourceManager;
|
||||||
import eu.kanade.mangafeed.data.models.Chapter;
|
import eu.kanade.mangafeed.data.models.Chapter;
|
||||||
import eu.kanade.mangafeed.data.models.Manga;
|
import eu.kanade.mangafeed.data.models.Manga;
|
||||||
|
import eu.kanade.mangafeed.sources.Source;
|
||||||
import eu.kanade.mangafeed.ui.fragment.MangaChaptersFragment;
|
import eu.kanade.mangafeed.ui.fragment.MangaChaptersFragment;
|
||||||
import eu.kanade.mangafeed.util.EventBusHook;
|
import eu.kanade.mangafeed.util.EventBusHook;
|
||||||
import eu.kanade.mangafeed.util.events.ChapterCountEvent;
|
import eu.kanade.mangafeed.util.events.ChapterCountEvent;
|
||||||
|
import eu.kanade.mangafeed.util.events.SourceChapterEvent;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
import rx.schedulers.Schedulers;
|
import rx.schedulers.Schedulers;
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment> {
|
public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment> {
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
|
|||||||
@Inject SourceManager sourceManager;
|
@Inject SourceManager sourceManager;
|
||||||
|
|
||||||
private Manga manga;
|
private Manga manga;
|
||||||
|
private Source source;
|
||||||
|
|
||||||
private static final int DB_CHAPTERS = 1;
|
private static final int DB_CHAPTERS = 1;
|
||||||
private static final int ONLINE_CHAPTERS = 2;
|
private static final int ONLINE_CHAPTERS = 2;
|
||||||
@ -71,6 +73,7 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
|
|||||||
public void onEventMainThread(Manga manga) {
|
public void onEventMainThread(Manga manga) {
|
||||||
if (this.manga == null) {
|
if (this.manga == null) {
|
||||||
this.manga = manga;
|
this.manga = manga;
|
||||||
|
source = sourceManager.get(manga.source);
|
||||||
start(DB_CHAPTERS);
|
start(DB_CHAPTERS);
|
||||||
|
|
||||||
// Get chapters if it's an online source
|
// Get chapters if it's an online source
|
||||||
@ -94,11 +97,14 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Observable<PostResult> getOnlineChaptersObs() {
|
private Observable<PostResult> getOnlineChaptersObs() {
|
||||||
return sourceManager.get(manga.source)
|
return source
|
||||||
.pullChaptersFromNetwork(manga.url)
|
.pullChaptersFromNetwork(manga.url)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.flatMap(chapters -> db.insertOrRemoveChapters(manga, chapters))
|
.flatMap(chapters -> db.insertOrRemoveChapters(manga, chapters))
|
||||||
.observeOn(AndroidSchedulers.mainThread());
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onChapterClicked(Chapter chapter) {
|
||||||
|
EventBus.getDefault().postSticky(new SourceChapterEvent(source, chapter));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import eu.kanade.mangafeed.ui.fragment.MangaInfoFragment;
|
|||||||
import eu.kanade.mangafeed.util.EventBusHook;
|
import eu.kanade.mangafeed.util.EventBusHook;
|
||||||
import eu.kanade.mangafeed.util.events.ChapterCountEvent;
|
import eu.kanade.mangafeed.util.events.ChapterCountEvent;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
|
public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ package eu.kanade.mangafeed.presenter;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import eu.kanade.mangafeed.data.helpers.SourceManager;
|
import eu.kanade.mangafeed.data.helpers.SourceManager;
|
||||||
import eu.kanade.mangafeed.sources.Source;
|
|
||||||
import eu.kanade.mangafeed.ui.fragment.SourceFragment;
|
import eu.kanade.mangafeed.ui.fragment.SourceFragment;
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
package eu.kanade.mangafeed.presenter;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import de.greenrobot.event.EventBus;
|
||||||
|
import eu.kanade.mangafeed.data.caches.CacheManager;
|
||||||
|
import eu.kanade.mangafeed.data.models.Chapter;
|
||||||
|
import eu.kanade.mangafeed.data.models.Page;
|
||||||
|
import eu.kanade.mangafeed.sources.Source;
|
||||||
|
import eu.kanade.mangafeed.ui.activity.ViewerActivity;
|
||||||
|
import eu.kanade.mangafeed.util.EventBusHook;
|
||||||
|
import eu.kanade.mangafeed.util.events.SourceChapterEvent;
|
||||||
|
import rx.Observable;
|
||||||
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
|
import rx.schedulers.Schedulers;
|
||||||
|
|
||||||
|
public class ViewerPresenter extends BasePresenter<ViewerActivity> {
|
||||||
|
|
||||||
|
private static final int GET_PAGE_LIST = 1;
|
||||||
|
private Source source;
|
||||||
|
private Chapter chapter;
|
||||||
|
private List<Page> pageList;
|
||||||
|
|
||||||
|
@Inject CacheManager cacheManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedState) {
|
||||||
|
super.onCreate(savedState);
|
||||||
|
|
||||||
|
restartableReplay(GET_PAGE_LIST,
|
||||||
|
this::getPageListObservable,
|
||||||
|
(view, page) -> {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onTakeView(ViewerActivity view) {
|
||||||
|
super.onTakeView(view);
|
||||||
|
registerForStickyEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDropView() {
|
||||||
|
unregisterForEvents();
|
||||||
|
super.onDropView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
EventBus.getDefault().removeStickyEvent(SourceChapterEvent.class);
|
||||||
|
source.savePageList(chapter.url, pageList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventBusHook
|
||||||
|
public void onEventMainThread(SourceChapterEvent event) {
|
||||||
|
if (source == null || chapter == null) {
|
||||||
|
source = event.getSource();
|
||||||
|
chapter = event.getChapter();
|
||||||
|
|
||||||
|
start(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Observable<Page> getPageListObservable() {
|
||||||
|
return source.pullPageListFromNetwork(chapter.url)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.flatMap(pageList -> {
|
||||||
|
this.pageList = pageList;
|
||||||
|
|
||||||
|
return Observable.merge(
|
||||||
|
Observable.from(pageList)
|
||||||
|
.filter(page -> page.getImageUrl() != null),
|
||||||
|
|
||||||
|
source.getRemainingImageUrlsFromPageList(pageList)
|
||||||
|
.doOnNext(this::replacePageUrl));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void replacePageUrl(Page page) {
|
||||||
|
for (int i = 0; i < pageList.size(); i++) {
|
||||||
|
if (pageList.get(i).getPageNumber() == page.getPageNumber()) {
|
||||||
|
pageList.set(i, page);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,11 +10,49 @@ import eu.kanade.mangafeed.data.caches.CacheManager;
|
|||||||
import eu.kanade.mangafeed.data.helpers.NetworkHelper;
|
import eu.kanade.mangafeed.data.helpers.NetworkHelper;
|
||||||
import eu.kanade.mangafeed.data.models.Chapter;
|
import eu.kanade.mangafeed.data.models.Chapter;
|
||||||
import eu.kanade.mangafeed.data.models.Manga;
|
import eu.kanade.mangafeed.data.models.Manga;
|
||||||
|
import eu.kanade.mangafeed.data.models.Page;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.schedulers.Schedulers;
|
import rx.schedulers.Schedulers;
|
||||||
|
|
||||||
public abstract class Source {
|
public abstract class Source {
|
||||||
|
|
||||||
|
// Methods to implement or optionally override
|
||||||
|
|
||||||
|
// Name of the source to display
|
||||||
|
public abstract String getName();
|
||||||
|
|
||||||
|
// Id of the source (must be declared and obtained from SourceManager to avoid conflicts)
|
||||||
|
public abstract int getSourceId();
|
||||||
|
|
||||||
|
protected abstract String getUrlFromPageNumber(int page);
|
||||||
|
protected abstract String getSearchUrl(String query, int page);
|
||||||
|
protected abstract List<Manga> parsePopularMangasFromHtml(String unparsedHtml);
|
||||||
|
protected abstract List<Manga> parseSearchFromHtml(String unparsedHtml);
|
||||||
|
protected abstract Manga parseHtmlToManga(String mangaUrl, String unparsedHtml);
|
||||||
|
protected abstract List<Chapter> parseHtmlToChapters(String unparsedHtml);
|
||||||
|
protected abstract List<String> parseHtmlToPageUrls(String unparsedHtml);
|
||||||
|
protected abstract String parseHtmlToImageUrl(String unparsedHtml);
|
||||||
|
|
||||||
|
// Get the URL to the details of a manga, useful if the source provides some kind of API or fast calls
|
||||||
|
protected String getMangaUrl(String defaultMangaUrl) {
|
||||||
|
return defaultMangaUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default headers, it can be overriden by children or just add new keys
|
||||||
|
protected Headers.Builder headersBuilder() {
|
||||||
|
Headers.Builder builder = new Headers.Builder();
|
||||||
|
builder.add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)");
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of images to download at the same time
|
||||||
|
protected int getNumberOfConcurrentImageDownloads() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ***** Source class implementation *****
|
||||||
|
|
||||||
protected NetworkHelper mNetworkService;
|
protected NetworkHelper mNetworkService;
|
||||||
protected CacheManager mCacheManager;
|
protected CacheManager mCacheManager;
|
||||||
protected Headers mRequestHeaders;
|
protected Headers mRequestHeaders;
|
||||||
@ -25,13 +63,6 @@ public abstract class Source {
|
|||||||
mRequestHeaders = headersBuilder().build();
|
mRequestHeaders = headersBuilder().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default headers, it can be overriden by children or add new keys
|
|
||||||
protected Headers.Builder headersBuilder() {
|
|
||||||
Headers.Builder builder = new Headers.Builder();
|
|
||||||
builder.add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)");
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the most popular mangas from the source
|
// Get the most popular mangas from the source
|
||||||
public Observable<List<Manga>> pullPopularMangasFromNetwork(int page) {
|
public Observable<List<Manga>> pullPopularMangasFromNetwork(int page) {
|
||||||
String url = getUrlFromPageNumber(page);
|
String url = getUrlFromPageNumber(page);
|
||||||
@ -62,56 +93,54 @@ public abstract class Source {
|
|||||||
Observable.just(parseHtmlToChapters(unparsedHtml)));
|
Observable.just(parseHtmlToChapters(unparsedHtml)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the URLs of the images of a chapter
|
public Observable<List<Page>> pullPageListFromNetwork(final String chapterUrl) {
|
||||||
public Observable<String> getImageUrlsFromNetwork(final String chapterUrl) {
|
return mCacheManager.getPageUrlsFromDiskCache(chapterUrl)
|
||||||
return mNetworkService
|
|
||||||
.getStringResponse(chapterUrl, mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders)
|
|
||||||
.flatMap(unparsedHtml -> Observable.from(parseHtmlToPageUrls(unparsedHtml)))
|
|
||||||
.buffer(3)
|
|
||||||
.concatMap(batchedPageUrls -> {
|
|
||||||
List<Observable<String>> imageUrlObservables = new ArrayList<>();
|
|
||||||
for (String pageUrl : batchedPageUrls) {
|
|
||||||
Observable<String> temporaryObservable = mNetworkService
|
|
||||||
.getStringResponse(pageUrl, mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders)
|
|
||||||
.flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)))
|
|
||||||
.subscribeOn(Schedulers.io());
|
|
||||||
|
|
||||||
imageUrlObservables.add(temporaryObservable);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Observable.merge(imageUrlObservables);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the URLs of a chapter in the cache
|
|
||||||
public Observable<String> pullImageUrlsFromNetwork(final String chapterUrl) {
|
|
||||||
final List<String> temporaryCachedImageUrls = new ArrayList<>();
|
|
||||||
|
|
||||||
return mCacheManager.getImageUrlsFromDiskCache(chapterUrl)
|
|
||||||
.onErrorResumeNext(throwable -> {
|
.onErrorResumeNext(throwable -> {
|
||||||
return getImageUrlsFromNetwork(chapterUrl)
|
return mNetworkService
|
||||||
.doOnNext(imageUrl -> temporaryCachedImageUrls.add(imageUrl))
|
.getStringResponse(chapterUrl, mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders)
|
||||||
.doOnCompleted(mCacheManager.putImageUrlsToDiskCache(chapterUrl, temporaryCachedImageUrls));
|
.flatMap(unparsedHtml -> Observable.just(parseHtmlToPageUrls(unparsedHtml)))
|
||||||
|
.flatMap(this::convertToPages)
|
||||||
|
.doOnNext(pages -> savePageList(chapterUrl, pages));
|
||||||
})
|
})
|
||||||
.onBackpressureBuffer();
|
.onBackpressureBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the URL to the details of a manga, useful if the source provides some kind of API or fast calls
|
// Get the URLs of the images of a chapter
|
||||||
protected String getMangaUrl(String defaultMangaUrl) {
|
public Observable<Page> getRemainingImageUrlsFromPageList(final List<Page> pages) {
|
||||||
return defaultMangaUrl;
|
return Observable.from(pages)
|
||||||
|
.filter(page -> page.getImageUrl() == null)
|
||||||
|
.buffer(getNumberOfConcurrentImageDownloads())
|
||||||
|
.concatMap(batchedPages -> {
|
||||||
|
List<Observable<Page>> pageObservable = new ArrayList<>();
|
||||||
|
for (Page page : batchedPages) {
|
||||||
|
pageObservable.add(getImageUrlFromPage(page));
|
||||||
|
}
|
||||||
|
return Observable.merge(pageObservable);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract String getName();
|
private Observable<Page> getImageUrlFromPage(final Page page) {
|
||||||
public abstract int getSourceId();
|
return mNetworkService
|
||||||
|
.getStringResponse(page.getUrl(), mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders)
|
||||||
|
.flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)))
|
||||||
|
.flatMap(imageUrl -> {
|
||||||
|
page.setImageUrl(imageUrl);
|
||||||
|
return Observable.just(page);
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io());
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract String getUrlFromPageNumber(int page);
|
public void savePageList(String chapterUrl, List<Page> pages) {
|
||||||
protected abstract String getSearchUrl(String query, int page);
|
mCacheManager.putPageUrlsToDiskCache(chapterUrl, pages);
|
||||||
protected abstract List<Manga> parsePopularMangasFromHtml(String unparsedHtml);
|
}
|
||||||
protected abstract List<Manga> parseSearchFromHtml(String unparsedHtml);
|
|
||||||
protected abstract Manga parseHtmlToManga(String mangaUrl, String unparsedHtml);
|
private Observable<List<Page>> convertToPages(List<String> pageUrls) {
|
||||||
protected abstract List<Chapter> parseHtmlToChapters(String unparsedHtml);
|
List<Page> pages = new ArrayList<>();
|
||||||
protected abstract List<String> parseHtmlToPageUrls(String unparsedHtml);
|
for (int i = 0; i < pageUrls.size(); i++) {
|
||||||
protected abstract String parseHtmlToImageUrl(String unparsedHtml);
|
pages.add(new Page(i, pageUrls.get(i)));
|
||||||
|
}
|
||||||
|
return Observable.just(pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import eu.kanade.mangafeed.App;
|
|||||||
import nucleus.factory.PresenterFactory;
|
import nucleus.factory.PresenterFactory;
|
||||||
import nucleus.presenter.Presenter;
|
import nucleus.presenter.Presenter;
|
||||||
import nucleus.view.NucleusAppCompatActivity;
|
import nucleus.view.NucleusAppCompatActivity;
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class BaseActivity<P extends Presenter> extends NucleusAppCompatActivity<P> {
|
public class BaseActivity<P extends Presenter> extends NucleusAppCompatActivity<P> {
|
||||||
|
|
||||||
@ -17,11 +16,7 @@ public class BaseActivity<P extends Presenter> extends NucleusAppCompatActivity<
|
|||||||
final PresenterFactory<P> superFactory = super.getPresenterFactory();
|
final PresenterFactory<P> superFactory = super.getPresenterFactory();
|
||||||
setPresenterFactory(() -> {
|
setPresenterFactory(() -> {
|
||||||
P presenter = superFactory.createPresenter();
|
P presenter = superFactory.createPresenter();
|
||||||
try {
|
App.getComponentReflection(getActivity()).inject(presenter);
|
||||||
App.getComponentReflection(getActivity()).inject(presenter);
|
|
||||||
} catch(Exception e) {
|
|
||||||
Timber.w("No injection for " + presenter.getClass().toString());
|
|
||||||
}
|
|
||||||
return presenter;
|
return presenter;
|
||||||
});
|
});
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -13,13 +13,13 @@ import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
|
|||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import eu.kanade.mangafeed.R;
|
import eu.kanade.mangafeed.R;
|
||||||
import eu.kanade.mangafeed.presenter.BasePresenter;
|
import eu.kanade.mangafeed.presenter.MainPresenter;
|
||||||
import eu.kanade.mangafeed.ui.fragment.LibraryFragment;
|
import eu.kanade.mangafeed.ui.fragment.LibraryFragment;
|
||||||
import eu.kanade.mangafeed.ui.fragment.SourceFragment;
|
import eu.kanade.mangafeed.ui.fragment.SourceFragment;
|
||||||
import nucleus.factory.RequiresPresenter;
|
import nucleus.factory.RequiresPresenter;
|
||||||
|
|
||||||
@RequiresPresenter(BasePresenter.class)
|
@RequiresPresenter(MainPresenter.class)
|
||||||
public class MainActivity extends BaseActivity<BasePresenter> {
|
public class MainActivity extends BaseActivity<MainPresenter> {
|
||||||
|
|
||||||
@Bind(R.id.toolbar)
|
@Bind(R.id.toolbar)
|
||||||
Toolbar toolbar;
|
Toolbar toolbar;
|
||||||
|
@ -26,7 +26,7 @@ public class MangaDetailActivity extends BaseActivity<MangaDetailPresenter> {
|
|||||||
|
|
||||||
@Bind(R.id.toolbar) Toolbar toolbar;
|
@Bind(R.id.toolbar) Toolbar toolbar;
|
||||||
@Bind(R.id.tabs) TabLayout tabs;
|
@Bind(R.id.tabs) TabLayout tabs;
|
||||||
@Bind(R.id.viewpager) ViewPager view_pager;
|
@Bind(R.id.view_pager) ViewPager view_pager;
|
||||||
|
|
||||||
private MangaDetailAdapter adapter;
|
private MangaDetailAdapter adapter;
|
||||||
private long manga_id;
|
private long manga_id;
|
||||||
@ -80,8 +80,7 @@ public class MangaDetailActivity extends BaseActivity<MangaDetailPresenter> {
|
|||||||
private void setupViewPager() {
|
private void setupViewPager() {
|
||||||
adapter = new MangaDetailAdapter(
|
adapter = new MangaDetailAdapter(
|
||||||
getSupportFragmentManager(),
|
getSupportFragmentManager(),
|
||||||
getActivity(),
|
getActivity());
|
||||||
manga_id);
|
|
||||||
|
|
||||||
view_pager.setAdapter(adapter);
|
view_pager.setAdapter(adapter);
|
||||||
tabs.setupWithViewPager(view_pager);
|
tabs.setupWithViewPager(view_pager);
|
||||||
@ -107,19 +106,17 @@ public class MangaDetailActivity extends BaseActivity<MangaDetailPresenter> {
|
|||||||
final int PAGE_COUNT = 2;
|
final int PAGE_COUNT = 2;
|
||||||
private String tab_titles[];
|
private String tab_titles[];
|
||||||
private Context context;
|
private Context context;
|
||||||
private long manga_id;
|
|
||||||
|
|
||||||
final static int INFO_FRAGMENT = 0;
|
final static int INFO_FRAGMENT = 0;
|
||||||
final static int CHAPTERS_FRAGMENT = 1;
|
final static int CHAPTERS_FRAGMENT = 1;
|
||||||
|
|
||||||
public MangaDetailAdapter(FragmentManager fm, Context context, long manga_id) {
|
public MangaDetailAdapter(FragmentManager fm, Context context) {
|
||||||
super(fm);
|
super(fm);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
tab_titles = new String[]{
|
tab_titles = new String[]{
|
||||||
context.getString(R.string.manga_detail_tab),
|
context.getString(R.string.manga_detail_tab),
|
||||||
context.getString(R.string.manga_chapters_tab)
|
context.getString(R.string.manga_chapters_tab)
|
||||||
};
|
};
|
||||||
this.manga_id = manga_id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package eu.kanade.mangafeed.ui.activity;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.view.ViewPager;
|
||||||
|
|
||||||
|
import butterknife.Bind;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import eu.kanade.mangafeed.R;
|
||||||
|
import eu.kanade.mangafeed.presenter.ViewerPresenter;
|
||||||
|
import eu.kanade.mangafeed.ui.adapter.ViewerPageAdapter;
|
||||||
|
import nucleus.factory.RequiresPresenter;
|
||||||
|
|
||||||
|
@RequiresPresenter(ViewerPresenter.class)
|
||||||
|
public class ViewerActivity extends BaseActivity<ViewerPresenter> {
|
||||||
|
|
||||||
|
@Bind(R.id.view_pager) ViewPager viewPager;
|
||||||
|
|
||||||
|
private ViewerPageAdapter adapter;
|
||||||
|
|
||||||
|
public static Intent newInstance(Context context) {
|
||||||
|
return new Intent(context, ViewerActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedState) {
|
||||||
|
super.onCreate(savedState);
|
||||||
|
setContentView(R.layout.activity_viewer);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
|
createAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createAdapter() {
|
||||||
|
adapter = new ViewerPageAdapter(getSupportFragmentManager());
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -20,12 +20,29 @@ public class ChapterListHolder extends ItemViewHolder<Chapter> {
|
|||||||
@ViewId(R.id.chapter_download_image)
|
@ViewId(R.id.chapter_download_image)
|
||||||
ImageView download_icon;
|
ImageView download_icon;
|
||||||
|
|
||||||
|
View view;
|
||||||
|
|
||||||
public ChapterListHolder(View view) {
|
public ChapterListHolder(View view) {
|
||||||
super(view);
|
super(view);
|
||||||
|
this.view = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSetValues(Chapter chapter, PositionInfo positionInfo) {
|
public void onSetValues(Chapter chapter, PositionInfo positionInfo) {
|
||||||
title.setText(chapter.name);
|
title.setText(chapter.name);
|
||||||
download_icon.setImageResource(R.drawable.ic_file_download_black_48dp);
|
download_icon.setImageResource(R.drawable.ic_file_download_black_48dp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetListeners() {
|
||||||
|
view.setOnClickListener(view -> {
|
||||||
|
ChapterListener listener = getListener(ChapterListener.class);
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onRowClicked(getItem());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ChapterListener {
|
||||||
|
void onRowClicked(Chapter chapter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package eu.kanade.mangafeed.ui.adapter;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
public abstract class SmartFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
|
||||||
|
// Sparse array to keep track of registered fragments in memory
|
||||||
|
private SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
|
||||||
|
|
||||||
|
public SmartFragmentStatePagerAdapter(FragmentManager fragmentManager) {
|
||||||
|
super(fragmentManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the fragment when the item is instantiated
|
||||||
|
@Override
|
||||||
|
public Object instantiateItem(ViewGroup container, int position) {
|
||||||
|
Fragment fragment = (Fragment) super.instantiateItem(container, position);
|
||||||
|
registeredFragments.put(position, fragment);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unregister when the item is inactive
|
||||||
|
@Override
|
||||||
|
public void destroyItem(ViewGroup container, int position, Object object) {
|
||||||
|
registeredFragments.remove(position);
|
||||||
|
super.destroyItem(container, position, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the fragment for the position (if instantiated)
|
||||||
|
public Fragment getRegisteredFragment(int position) {
|
||||||
|
return registeredFragments.get(position);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package eu.kanade.mangafeed.ui.adapter;
|
||||||
|
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import eu.kanade.mangafeed.ui.fragment.ViewerPageFragment;
|
||||||
|
|
||||||
|
public class ViewerPageAdapter extends SmartFragmentStatePagerAdapter {
|
||||||
|
|
||||||
|
private List<String> imageUrls;
|
||||||
|
|
||||||
|
public ViewerPageAdapter(FragmentManager fragmentManager) {
|
||||||
|
super(fragmentManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCount() {
|
||||||
|
if (imageUrls != null)
|
||||||
|
return imageUrls.size();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Fragment getItem(int position) {
|
||||||
|
return ViewerPageFragment.newInstance(imageUrls.get(position), position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getImageUrls() {
|
||||||
|
return imageUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageUrls(List<String> imageUrls) {
|
||||||
|
this.imageUrls = imageUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.mangafeed.ui.fragment;
|
package eu.kanade.mangafeed.ui.fragment;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
@ -20,6 +21,7 @@ import eu.kanade.mangafeed.R;
|
|||||||
import eu.kanade.mangafeed.data.models.Chapter;
|
import eu.kanade.mangafeed.data.models.Chapter;
|
||||||
import eu.kanade.mangafeed.presenter.MangaChaptersPresenter;
|
import eu.kanade.mangafeed.presenter.MangaChaptersPresenter;
|
||||||
import eu.kanade.mangafeed.ui.activity.MangaDetailActivity;
|
import eu.kanade.mangafeed.ui.activity.MangaDetailActivity;
|
||||||
|
import eu.kanade.mangafeed.ui.activity.ViewerActivity;
|
||||||
import eu.kanade.mangafeed.ui.adapter.ChapterListHolder;
|
import eu.kanade.mangafeed.ui.adapter.ChapterListHolder;
|
||||||
import nucleus.factory.RequiresPresenter;
|
import nucleus.factory.RequiresPresenter;
|
||||||
import uk.co.ribot.easyadapter.EasyRecyclerAdapter;
|
import uk.co.ribot.easyadapter.EasyRecyclerAdapter;
|
||||||
@ -73,7 +75,13 @@ public class MangaChaptersFragment extends BaseFragment<MangaChaptersPresenter>
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createAdapter() {
|
private void createAdapter() {
|
||||||
adapter = new EasyRecyclerAdapter<>(getActivity(), ChapterListHolder.class);
|
ChapterListHolder.ChapterListener listener = chapter -> {
|
||||||
|
getPresenter().onChapterClicked(chapter);
|
||||||
|
Intent intent = ViewerActivity.newInstance(getActivity());
|
||||||
|
startActivity(intent);
|
||||||
|
};
|
||||||
|
|
||||||
|
adapter = new EasyRecyclerAdapter<>(getActivity(), ChapterListHolder.class, listener);
|
||||||
chapters.setAdapter(adapter);
|
chapters.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,84 @@
|
|||||||
|
package eu.kanade.mangafeed.ui.fragment;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
||||||
|
|
||||||
|
import eu.kanade.mangafeed.R;
|
||||||
|
import eu.kanade.mangafeed.util.PageFileTarget;
|
||||||
|
|
||||||
|
public class ViewerPageFragment extends Fragment {
|
||||||
|
public static final String URL_ARGUMENT_KEY = "UrlArgumentKey";
|
||||||
|
|
||||||
|
private SubsamplingScaleImageView mPageImageView;
|
||||||
|
|
||||||
|
private String mUrl;
|
||||||
|
|
||||||
|
public static ViewerPageFragment newInstance(String url, int position) {
|
||||||
|
ViewerPageFragment newInstance = new ViewerPageFragment();
|
||||||
|
Bundle arguments = new Bundle();
|
||||||
|
arguments.putString(URL_ARGUMENT_KEY, url);
|
||||||
|
newInstance.setArguments(arguments);
|
||||||
|
return newInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Bundle arguments = getArguments();
|
||||||
|
if (arguments != null) {
|
||||||
|
if (arguments.containsKey(URL_ARGUMENT_KEY)) {
|
||||||
|
mUrl = arguments.getString(URL_ARGUMENT_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
mPageImageView = (SubsamplingScaleImageView)inflater.inflate(R.layout.fragment_page, container, false);
|
||||||
|
mPageImageView.setVisibility(View.INVISIBLE);
|
||||||
|
mPageImageView.setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED);
|
||||||
|
mPageImageView.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE);
|
||||||
|
mPageImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE);
|
||||||
|
mPageImageView.setOnImageEventListener(new SubsamplingScaleImageView.OnImageEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onReady() {
|
||||||
|
mPageImageView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageLoaded() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPreviewLoadError(Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageLoadError(Exception e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTileLoadError(Exception e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return mPageImageView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
Glide.with(getActivity())
|
||||||
|
.load(mUrl)
|
||||||
|
.downloadOnly(new PageFileTarget(mPageImageView));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package eu.kanade.mangafeed.util;
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||||
|
import com.bumptech.glide.request.target.ViewTarget;
|
||||||
|
import com.davemorrissey.labs.subscaleview.ImageSource;
|
||||||
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import eu.kanade.mangafeed.R;
|
||||||
|
|
||||||
|
public class PageFileTarget extends ViewTarget<SubsamplingScaleImageView, File> {
|
||||||
|
public static final String TAG = PageFileTarget.class.getSimpleName();
|
||||||
|
|
||||||
|
public PageFileTarget(SubsamplingScaleImageView view) {
|
||||||
|
super(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadCleared(Drawable placeholder) {
|
||||||
|
view.setImage(ImageSource.resource(R.drawable.ic_action_refresh));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadStarted(Drawable placeholder) {
|
||||||
|
view.setImage(ImageSource.resource(R.drawable.ic_action_refresh));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResourceReady(File resource, GlideAnimation<? super File> glideAnimation) {
|
||||||
|
view.setImage(ImageSource.uri(Uri.fromFile(resource)));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package eu.kanade.mangafeed.util.events;
|
||||||
|
|
||||||
|
import eu.kanade.mangafeed.data.models.Chapter;
|
||||||
|
import eu.kanade.mangafeed.sources.Source;
|
||||||
|
|
||||||
|
public class SourceChapterEvent {
|
||||||
|
|
||||||
|
private Source source;
|
||||||
|
private Chapter chapter;
|
||||||
|
|
||||||
|
public SourceChapterEvent(Source source, Chapter chapter) {
|
||||||
|
this.source = source;
|
||||||
|
this.chapter = chapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Source getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chapter getChapter() {
|
||||||
|
return chapter;
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,7 @@
|
|||||||
</android.support.design.widget.AppBarLayout>
|
</android.support.design.widget.AppBarLayout>
|
||||||
|
|
||||||
<android.support.v4.view.ViewPager
|
<android.support.v4.view.ViewPager
|
||||||
android:id="@+id/viewpager"
|
android:id="@+id/view_pager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0px"
|
android:layout_height="0px"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
13
app/src/main/res/layout/activity_viewer.xml
Normal file
13
app/src/main/res/layout/activity_viewer.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<android.support.v4.view.ViewPager
|
||||||
|
android:id="@+id/view_pager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
</android.support.v4.view.ViewPager>
|
||||||
|
|
||||||
|
|
||||||
|
</FrameLayout>
|
6
app/src/main/res/layout/fragment_page.xml
Normal file
6
app/src/main/res/layout/fragment_page.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/page_image_view" />
|
@ -45,5 +45,6 @@
|
|||||||
<string name="description">Description</string>
|
<string name="description">Description</string>
|
||||||
<string name="manga_detail_tab">Info</string>
|
<string name="manga_detail_tab">Info</string>
|
||||||
<string name="manga_chapters_tab">Chapters</string>
|
<string name="manga_chapters_tab">Chapters</string>
|
||||||
|
<string name="title_activity_viewer">ViewerActivity</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -19,9 +19,6 @@ import eu.kanade.mangafeed.data.models.Chapter;
|
|||||||
import eu.kanade.mangafeed.data.models.Manga;
|
import eu.kanade.mangafeed.data.models.Manga;
|
||||||
import eu.kanade.mangafeed.sources.Batoto;
|
import eu.kanade.mangafeed.sources.Batoto;
|
||||||
import eu.kanade.mangafeed.sources.Source;
|
import eu.kanade.mangafeed.sources.Source;
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
|
||||||
import rx.observers.TestSubscriber;
|
|
||||||
import rx.schedulers.Schedulers;
|
|
||||||
|
|
||||||
@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP)
|
@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP)
|
||||||
@RunWith(RobolectricGradleTestRunner.class)
|
@RunWith(RobolectricGradleTestRunner.class)
|
||||||
@ -44,7 +41,7 @@ public class BatotoTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testImageList() {
|
public void testImageList() {
|
||||||
List<String> imageUrls = b.getImageUrlsFromNetwork(chapterUrl)
|
List<String> imageUrls = b.getRemainingImageUrlsFromPageList(chapterUrl)
|
||||||
.toList().toBlocking().single();
|
.toList().toBlocking().single();
|
||||||
|
|
||||||
Assert.assertTrue(imageUrls.size() > 5);
|
Assert.assertTrue(imageUrls.size() > 5);
|
||||||
|
@ -39,7 +39,7 @@ public class MangahereTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testImageList() {
|
public void testImageList() {
|
||||||
List<String> imageUrls = b.getImageUrlsFromNetwork(chapterUrl)
|
List<String> imageUrls = b.getRemainingImageUrlsFromPageList(chapterUrl)
|
||||||
.toList().toBlocking().single();
|
.toList().toBlocking().single();
|
||||||
|
|
||||||
Assert.assertTrue(imageUrls.size() > 5);
|
Assert.assertTrue(imageUrls.size() > 5);
|
||||||
|
Loading…
Reference in New Issue
Block a user