mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Almost showing a chapter reader
This commit is contained in:
		| @@ -8,10 +8,12 @@ import dagger.Component; | ||||
| import eu.kanade.mangafeed.data.DataModule; | ||||
| import eu.kanade.mangafeed.presenter.CataloguePresenter; | ||||
| import eu.kanade.mangafeed.presenter.LibraryPresenter; | ||||
| import eu.kanade.mangafeed.presenter.MainPresenter; | ||||
| import eu.kanade.mangafeed.presenter.MangaChaptersPresenter; | ||||
| import eu.kanade.mangafeed.presenter.MangaDetailPresenter; | ||||
| import eu.kanade.mangafeed.presenter.MangaInfoPresenter; | ||||
| import eu.kanade.mangafeed.presenter.SourcePresenter; | ||||
| import eu.kanade.mangafeed.presenter.ViewerPresenter; | ||||
|  | ||||
| @Singleton | ||||
| @Component( | ||||
| @@ -22,12 +24,14 @@ import eu.kanade.mangafeed.presenter.SourcePresenter; | ||||
| ) | ||||
| public interface AppComponent { | ||||
|  | ||||
|     void inject(MainPresenter mainPresenter); | ||||
|     void inject(LibraryPresenter libraryPresenter); | ||||
|     void inject(MangaDetailPresenter mangaDetailPresenter); | ||||
|     void inject(SourcePresenter sourcePresenter); | ||||
|     void inject(CataloguePresenter cataloguePresenter); | ||||
|     void inject(MangaInfoPresenter mangaInfoPresenter); | ||||
|     void inject(MangaChaptersPresenter mangaChaptersPresenter); | ||||
|     void inject(ViewerPresenter viewerPresenter); | ||||
|  | ||||
|     Application application(); | ||||
|  | ||||
|   | ||||
| @@ -5,20 +5,23 @@ import android.content.Context; | ||||
| import com.bumptech.glide.Glide; | ||||
| import com.bumptech.glide.request.FutureTarget; | ||||
| import com.bumptech.glide.request.target.Target; | ||||
| import com.google.gson.Gson; | ||||
| import com.google.gson.reflect.TypeToken; | ||||
| import com.jakewharton.disklrucache.DiskLruCache; | ||||
|  | ||||
| import java.io.BufferedOutputStream; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.io.OutputStream; | ||||
| import java.lang.reflect.Type; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.ExecutionException; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.TimeoutException; | ||||
|  | ||||
| import eu.kanade.mangafeed.data.models.Page; | ||||
| import eu.kanade.mangafeed.util.DiskUtils; | ||||
| import rx.Observable; | ||||
| import rx.functions.Action0; | ||||
|  | ||||
| public class CacheManager { | ||||
|  | ||||
| @@ -29,11 +32,13 @@ public class CacheManager { | ||||
|     private static final int READ_TIMEOUT = 60; | ||||
|  | ||||
|     private Context mContext; | ||||
|     private Gson mGson; | ||||
|  | ||||
|     private DiskLruCache mDiskCache; | ||||
|  | ||||
|     public CacheManager(Context context) { | ||||
|         mContext = context; | ||||
|         mGson = new Gson(); | ||||
|  | ||||
|         try { | ||||
|             mDiskCache = DiskLruCache.open( | ||||
| @@ -109,16 +114,11 @@ public class CacheManager { | ||||
|         return isSuccessful; | ||||
|     } | ||||
|  | ||||
|     public Observable<String> getImageUrlsFromDiskCache(final String chapterUrl) { | ||||
|     public Observable<List<Page>> getPageUrlsFromDiskCache(final String chapterUrl) { | ||||
|         return Observable.create(subscriber -> { | ||||
|             try { | ||||
|                 String[] imageUrls = getImageUrlsFromDiskCacheImpl(chapterUrl); | ||||
|  | ||||
|                 for (String imageUrl : imageUrls) { | ||||
|                     if (!subscriber.isUnsubscribed()) { | ||||
|                         subscriber.onNext(imageUrl); | ||||
|                     } | ||||
|                 } | ||||
|                 List<Page> pages = getPageUrlsFromDiskCacheImpl(chapterUrl); | ||||
|                 subscriber.onNext(pages); | ||||
|                 subscriber.onCompleted(); | ||||
|             } catch (Throwable 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; | ||||
|         List<Page> pages = null; | ||||
|  | ||||
|         try { | ||||
|             String key = DiskUtils.hashKeyForDisk(chapterUrl); | ||||
|  | ||||
|             snapshot = mDiskCache.get(key); | ||||
|  | ||||
|             String joinedImageUrls = snapshot.getString(0); | ||||
|             return joinedImageUrls.split(","); | ||||
|             Type collectionType = new TypeToken<List<Page>>() {}.getType(); | ||||
|             pages = mGson.fromJson(snapshot.getString(0), collectionType); | ||||
|         } catch (IOException e) { | ||||
|             // Do Nothing. | ||||
|         } finally { | ||||
|             if (snapshot != null) { | ||||
|                 snapshot.close(); | ||||
|             } | ||||
|         } | ||||
|         return pages; | ||||
|     } | ||||
|  | ||||
|     public Action0 putImageUrlsToDiskCache(final String chapterUrl, final List<String> imageUrls) { | ||||
|         return () -> { | ||||
|             try { | ||||
|                 putImageUrlsToDiskCacheImpl(chapterUrl, imageUrls); | ||||
|             } catch (IOException e) { | ||||
|                 // Do Nothing. | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     private void putImageUrlsToDiskCacheImpl(String chapterUrl, List<String> imageUrls) throws IOException { | ||||
|         String cachedValue = joinImageUrlsToCacheValue(imageUrls); | ||||
|     public void putPageUrlsToDiskCache(final String chapterUrl, final List<Page> pages) { | ||||
|         String cachedValue = mGson.toJson(pages); | ||||
|  | ||||
|         DiskLruCache.Editor editor = null; | ||||
|         OutputStream outputStream = null; | ||||
| @@ -171,13 +164,11 @@ public class CacheManager { | ||||
|  | ||||
|             mDiskCache.flush(); | ||||
|             editor.commit(); | ||||
|         } catch (Exception e) { | ||||
|             // Do Nothing. | ||||
|         } finally { | ||||
|             if (editor != null) { | ||||
|                 try { | ||||
|                     editor.abort(); | ||||
|                 } catch (IOException ignore) { | ||||
|                     // Do Nothing. | ||||
|                 } | ||||
|                 editor.abortUnlessCommitted(); | ||||
|             } | ||||
|             if (outputStream != null) { | ||||
|                 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() { | ||||
|         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.models.Chapter; | ||||
| import eu.kanade.mangafeed.data.models.Manga; | ||||
| import eu.kanade.mangafeed.sources.Source; | ||||
| import eu.kanade.mangafeed.ui.fragment.MangaChaptersFragment; | ||||
| import eu.kanade.mangafeed.util.EventBusHook; | ||||
| import eu.kanade.mangafeed.util.events.ChapterCountEvent; | ||||
| import eu.kanade.mangafeed.util.events.SourceChapterEvent; | ||||
| import rx.Observable; | ||||
| import rx.android.schedulers.AndroidSchedulers; | ||||
| import rx.schedulers.Schedulers; | ||||
| import timber.log.Timber; | ||||
|  | ||||
| public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment> { | ||||
|  | ||||
| @@ -27,6 +28,7 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment> | ||||
|     @Inject SourceManager sourceManager; | ||||
|  | ||||
|     private Manga manga; | ||||
|     private Source source; | ||||
|  | ||||
|     private static final int DB_CHAPTERS = 1; | ||||
|     private static final int ONLINE_CHAPTERS = 2; | ||||
| @@ -71,6 +73,7 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment> | ||||
|     public void onEventMainThread(Manga manga) { | ||||
|         if (this.manga == null) { | ||||
|             this.manga = manga; | ||||
|             source = sourceManager.get(manga.source); | ||||
|             start(DB_CHAPTERS); | ||||
|  | ||||
|             // Get chapters if it's an online source | ||||
| @@ -94,11 +97,14 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment> | ||||
|     } | ||||
|  | ||||
|     private Observable<PostResult> getOnlineChaptersObs() { | ||||
|         return sourceManager.get(manga.source) | ||||
|         return source | ||||
|                 .pullChaptersFromNetwork(manga.url) | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .flatMap(chapters -> db.insertOrRemoveChapters(manga, chapters)) | ||||
|                 .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.events.ChapterCountEvent; | ||||
| import rx.Observable; | ||||
| import timber.log.Timber; | ||||
|  | ||||
| public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> { | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,6 @@ package eu.kanade.mangafeed.presenter; | ||||
| import javax.inject.Inject; | ||||
|  | ||||
| import eu.kanade.mangafeed.data.helpers.SourceManager; | ||||
| import eu.kanade.mangafeed.sources.Source; | ||||
| 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.models.Chapter; | ||||
| import eu.kanade.mangafeed.data.models.Manga; | ||||
| import eu.kanade.mangafeed.data.models.Page; | ||||
| import rx.Observable; | ||||
| import rx.schedulers.Schedulers; | ||||
|  | ||||
| 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 CacheManager mCacheManager; | ||||
|     protected Headers mRequestHeaders; | ||||
| @@ -25,13 +63,6 @@ public abstract class Source { | ||||
|         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 | ||||
|     public Observable<List<Manga>> pullPopularMangasFromNetwork(int page) { | ||||
|         String url = getUrlFromPageNumber(page); | ||||
| @@ -62,56 +93,54 @@ public abstract class Source { | ||||
|                         Observable.just(parseHtmlToChapters(unparsedHtml))); | ||||
|     } | ||||
|  | ||||
|     // Get the URLs of the images of a chapter | ||||
|     public Observable<String> getImageUrlsFromNetwork(final String 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) | ||||
|     public Observable<List<Page>> pullPageListFromNetwork(final String chapterUrl) { | ||||
|         return mCacheManager.getPageUrlsFromDiskCache(chapterUrl) | ||||
|                 .onErrorResumeNext(throwable -> { | ||||
|                     return getImageUrlsFromNetwork(chapterUrl) | ||||
|                             .doOnNext(imageUrl -> temporaryCachedImageUrls.add(imageUrl)) | ||||
|                             .doOnCompleted(mCacheManager.putImageUrlsToDiskCache(chapterUrl, temporaryCachedImageUrls)); | ||||
|                     return mNetworkService | ||||
|                             .getStringResponse(chapterUrl, mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders) | ||||
|                             .flatMap(unparsedHtml -> Observable.just(parseHtmlToPageUrls(unparsedHtml))) | ||||
|                             .flatMap(this::convertToPages) | ||||
|                             .doOnNext(pages -> savePageList(chapterUrl, pages)); | ||||
|                 }) | ||||
|                 .onBackpressureBuffer(); | ||||
|     } | ||||
|  | ||||
|     // 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; | ||||
|     // Get the URLs of the images of a chapter | ||||
|     public Observable<Page> getRemainingImageUrlsFromPageList(final List<Page> pages) { | ||||
|         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(); | ||||
|     public abstract int getSourceId(); | ||||
|     private Observable<Page> getImageUrlFromPage(final Page page) { | ||||
|         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); | ||||
|     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); | ||||
|     public void savePageList(String chapterUrl, List<Page> pages) { | ||||
|         mCacheManager.putPageUrlsToDiskCache(chapterUrl, pages); | ||||
|     } | ||||
|  | ||||
|     private Observable<List<Page>> convertToPages(List<String> pageUrls) { | ||||
|         List<Page> pages = new ArrayList<>(); | ||||
|         for (int i = 0; i < pageUrls.size(); i++) { | ||||
|             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.presenter.Presenter; | ||||
| import nucleus.view.NucleusAppCompatActivity; | ||||
| import timber.log.Timber; | ||||
|  | ||||
| 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(); | ||||
|         setPresenterFactory(() -> { | ||||
|             P presenter = superFactory.createPresenter(); | ||||
|             try { | ||||
|                 App.getComponentReflection(getActivity()).inject(presenter); | ||||
|             } catch(Exception e) { | ||||
|                 Timber.w("No injection for " + presenter.getClass().toString()); | ||||
|             } | ||||
|             App.getComponentReflection(getActivity()).inject(presenter); | ||||
|             return presenter; | ||||
|         }); | ||||
|         super.onCreate(savedInstanceState); | ||||
|   | ||||
| @@ -13,13 +13,13 @@ import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; | ||||
| import butterknife.Bind; | ||||
| import butterknife.ButterKnife; | ||||
| 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.SourceFragment; | ||||
| import nucleus.factory.RequiresPresenter; | ||||
|  | ||||
| @RequiresPresenter(BasePresenter.class) | ||||
| public class MainActivity extends BaseActivity<BasePresenter> { | ||||
| @RequiresPresenter(MainPresenter.class) | ||||
| public class MainActivity extends BaseActivity<MainPresenter> { | ||||
|  | ||||
|     @Bind(R.id.toolbar) | ||||
|     Toolbar toolbar; | ||||
|   | ||||
| @@ -26,7 +26,7 @@ public class MangaDetailActivity extends BaseActivity<MangaDetailPresenter> { | ||||
|  | ||||
|     @Bind(R.id.toolbar) Toolbar toolbar; | ||||
|     @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 long manga_id; | ||||
| @@ -80,8 +80,7 @@ public class MangaDetailActivity extends BaseActivity<MangaDetailPresenter> { | ||||
|     private void setupViewPager() { | ||||
|         adapter = new MangaDetailAdapter( | ||||
|                 getSupportFragmentManager(), | ||||
|                 getActivity(), | ||||
|                 manga_id); | ||||
|                 getActivity()); | ||||
|  | ||||
|         view_pager.setAdapter(adapter); | ||||
|         tabs.setupWithViewPager(view_pager); | ||||
| @@ -107,19 +106,17 @@ public class MangaDetailActivity extends BaseActivity<MangaDetailPresenter> { | ||||
|         final int PAGE_COUNT = 2; | ||||
|         private String tab_titles[]; | ||||
|         private Context context; | ||||
|         private long manga_id; | ||||
|  | ||||
|         final static int INFO_FRAGMENT = 0; | ||||
|         final static int CHAPTERS_FRAGMENT = 1; | ||||
|  | ||||
|         public MangaDetailAdapter(FragmentManager fm, Context context, long manga_id) { | ||||
|         public MangaDetailAdapter(FragmentManager fm, Context context) { | ||||
|             super(fm); | ||||
|             this.context = context; | ||||
|             tab_titles = new String[]{ | ||||
|                     context.getString(R.string.manga_detail_tab), | ||||
|                     context.getString(R.string.manga_chapters_tab) | ||||
|             }; | ||||
|             this.manga_id = manga_id; | ||||
|         } | ||||
|  | ||||
|         @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) | ||||
|     ImageView download_icon; | ||||
|  | ||||
|     View view; | ||||
|  | ||||
|     public ChapterListHolder(View view) { | ||||
|         super(view); | ||||
|         this.view = view; | ||||
|     } | ||||
|  | ||||
|     public void onSetValues(Chapter chapter, PositionInfo positionInfo) { | ||||
|         title.setText(chapter.name); | ||||
|         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; | ||||
|  | ||||
| import android.content.Intent; | ||||
| import android.os.Bundle; | ||||
| import android.support.v4.app.Fragment; | ||||
| 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.presenter.MangaChaptersPresenter; | ||||
| import eu.kanade.mangafeed.ui.activity.MangaDetailActivity; | ||||
| import eu.kanade.mangafeed.ui.activity.ViewerActivity; | ||||
| import eu.kanade.mangafeed.ui.adapter.ChapterListHolder; | ||||
| import nucleus.factory.RequiresPresenter; | ||||
| import uk.co.ribot.easyadapter.EasyRecyclerAdapter; | ||||
| @@ -73,7 +75,13 @@ public class MangaChaptersFragment extends BaseFragment<MangaChaptersPresenter> | ||||
|     } | ||||
|  | ||||
|     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); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user