Allow reading downloaded chapters
@ -48,10 +48,9 @@ public class DownloadManager {
|
|||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observable<Page> downloadChapter(Manga manga, Chapter chapter) {
|
public Observable<Page> downloadChapter(Manga manga, Chapter chapter) {
|
||||||
final Source source = sourceManager.get(manga.source);
|
final Source source = sourceManager.get(manga.source);
|
||||||
final File chapterDirectory = new File(
|
final File chapterDirectory = getAbsoluteChapterDirectory(source, manga, chapter);
|
||||||
preferences.getDownloadsDirectory(), getChapterDirectory(source, manga, chapter));
|
|
||||||
|
|
||||||
return source
|
return source
|
||||||
.pullPageListFromNetwork(chapter.url)
|
.pullPageListFromNetwork(chapter.url)
|
||||||
@ -64,8 +63,13 @@ public class DownloadManager {
|
|||||||
// Start downloading images
|
// Start downloading images
|
||||||
.flatMap(page -> getDownloadedImage(page, source, chapterDirectory));
|
.flatMap(page -> getDownloadedImage(page, source, chapterDirectory));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public File getAbsoluteChapterDirectory(Source source, Manga manga, Chapter chapter) {
|
||||||
|
return new File(preferences.getDownloadsDirectory(),
|
||||||
|
getChapterDirectory(source, manga, chapter));
|
||||||
|
}
|
||||||
|
|
||||||
private String getChapterDirectory(Source source, Manga manga, Chapter chapter) {
|
public String getChapterDirectory(Source source, Manga manga, Chapter chapter) {
|
||||||
return source.getName() +
|
return source.getName() +
|
||||||
File.separator +
|
File.separator +
|
||||||
manga.title.replaceAll("[^a-zA-Z0-9.-]", "_") +
|
manga.title.replaceAll("[^a-zA-Z0-9.-]", "_") +
|
||||||
|
@ -32,6 +32,12 @@ public class Chapter {
|
|||||||
@StorIOSQLiteColumn(name = ChaptersTable.COLUMN_DATE_UPLOAD)
|
@StorIOSQLiteColumn(name = ChaptersTable.COLUMN_DATE_UPLOAD)
|
||||||
public long date_upload;
|
public long date_upload;
|
||||||
|
|
||||||
|
public int downloaded;
|
||||||
|
|
||||||
|
public static final int UNKNOWN = 0;
|
||||||
|
public static final int NOT_DOWNLOADED = 1;
|
||||||
|
public static final int DOWNLOADED = 2;
|
||||||
|
|
||||||
|
|
||||||
public Chapter() {}
|
public Chapter() {}
|
||||||
|
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
package eu.kanade.mangafeed.events;
|
package eu.kanade.mangafeed.events;
|
||||||
|
|
||||||
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.sources.base.Source;
|
import eu.kanade.mangafeed.sources.base.Source;
|
||||||
|
|
||||||
public class SourceChapterEvent {
|
public class SourceMangaChapterEvent {
|
||||||
|
|
||||||
private Source source;
|
private Source source;
|
||||||
|
private Manga manga;
|
||||||
private Chapter chapter;
|
private Chapter chapter;
|
||||||
|
|
||||||
public SourceChapterEvent(Source source, Chapter chapter) {
|
public SourceMangaChapterEvent(Source source, Manga manga, Chapter chapter) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
|
this.manga = manga;
|
||||||
this.chapter = chapter;
|
this.chapter = chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,6 +20,10 @@ public class SourceChapterEvent {
|
|||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Manga getManga() {
|
||||||
|
return manga;
|
||||||
|
}
|
||||||
|
|
||||||
public Chapter getChapter() {
|
public Chapter getChapter() {
|
||||||
return chapter;
|
return chapter;
|
||||||
}
|
}
|
@ -2,17 +2,20 @@ package eu.kanade.mangafeed.presenter;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
import de.greenrobot.event.EventBus;
|
||||||
import eu.kanade.mangafeed.data.helpers.DatabaseHelper;
|
import eu.kanade.mangafeed.data.helpers.DatabaseHelper;
|
||||||
|
import eu.kanade.mangafeed.data.helpers.DownloadManager;
|
||||||
|
import eu.kanade.mangafeed.data.helpers.PreferencesHelper;
|
||||||
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.events.ChapterCountEvent;
|
import eu.kanade.mangafeed.events.ChapterCountEvent;
|
||||||
import eu.kanade.mangafeed.events.SourceChapterEvent;
|
import eu.kanade.mangafeed.events.SourceMangaChapterEvent;
|
||||||
import eu.kanade.mangafeed.sources.base.Source;
|
import eu.kanade.mangafeed.sources.base.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;
|
||||||
@ -26,6 +29,8 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
|
|||||||
|
|
||||||
@Inject DatabaseHelper db;
|
@Inject DatabaseHelper db;
|
||||||
@Inject SourceManager sourceManager;
|
@Inject SourceManager sourceManager;
|
||||||
|
@Inject PreferencesHelper preferences;
|
||||||
|
@Inject DownloadManager downloadManager;
|
||||||
|
|
||||||
private Manga manga;
|
private Manga manga;
|
||||||
private Source source;
|
private Source source;
|
||||||
@ -111,7 +116,7 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onChapterClicked(Chapter chapter) {
|
public void onChapterClicked(Chapter chapter) {
|
||||||
EventBus.getDefault().postSticky(new SourceChapterEvent(source, chapter));
|
EventBus.getDefault().postSticky(new SourceMangaChapterEvent(source, manga, chapter));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markChaptersRead(Observable<Chapter> selectedChapters, boolean read) {
|
public void markChaptersRead(Observable<Chapter> selectedChapters, boolean read) {
|
||||||
@ -131,4 +136,14 @@ public class MangaChaptersPresenter extends BasePresenter<MangaChaptersFragment>
|
|||||||
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void checkIsChapterDownloaded(Chapter chapter) {
|
||||||
|
File dir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
|
||||||
|
|
||||||
|
if (dir.exists() && dir.listFiles().length > 0) {
|
||||||
|
chapter.downloaded = Chapter.DOWNLOADED;
|
||||||
|
} else {
|
||||||
|
chapter.downloaded = Chapter.NOT_DOWNLOADED;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,19 @@ package eu.kanade.mangafeed.presenter;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
import de.greenrobot.event.EventBus;
|
||||||
import eu.kanade.mangafeed.data.helpers.DatabaseHelper;
|
import eu.kanade.mangafeed.data.helpers.DatabaseHelper;
|
||||||
|
import eu.kanade.mangafeed.data.helpers.DownloadManager;
|
||||||
import eu.kanade.mangafeed.data.helpers.PreferencesHelper;
|
import eu.kanade.mangafeed.data.helpers.PreferencesHelper;
|
||||||
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.Page;
|
import eu.kanade.mangafeed.data.models.Page;
|
||||||
import eu.kanade.mangafeed.events.SourceChapterEvent;
|
import eu.kanade.mangafeed.events.SourceMangaChapterEvent;
|
||||||
import eu.kanade.mangafeed.sources.base.Source;
|
import eu.kanade.mangafeed.sources.base.Source;
|
||||||
import eu.kanade.mangafeed.ui.activity.ReaderActivity;
|
import eu.kanade.mangafeed.ui.activity.ReaderActivity;
|
||||||
import eu.kanade.mangafeed.util.EventBusHook;
|
import eu.kanade.mangafeed.util.EventBusHook;
|
||||||
@ -25,14 +28,17 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
|
|
||||||
@Inject PreferencesHelper prefs;
|
@Inject PreferencesHelper prefs;
|
||||||
@Inject DatabaseHelper db;
|
@Inject DatabaseHelper db;
|
||||||
|
@Inject DownloadManager downloadManager;
|
||||||
|
|
||||||
private Source source;
|
private Source source;
|
||||||
|
private Manga manga;
|
||||||
private Chapter chapter;
|
private Chapter chapter;
|
||||||
private List<Page> pageList;
|
private List<Page> pageList;
|
||||||
@State int currentPage;
|
@State int currentPage;
|
||||||
|
|
||||||
private static final int GET_PAGE_LIST = 1;
|
private static final int GET_PAGE_LIST = 1;
|
||||||
private static final int GET_PAGE_IMAGES = 2;
|
private static final int GET_PAGE_IMAGES = 2;
|
||||||
|
private static final int GET_LOCAL_IMAGES = 3;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedState) {
|
protected void onCreate(Bundle savedState) {
|
||||||
@ -41,7 +47,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
restartableLatestCache(GET_PAGE_LIST,
|
restartableLatestCache(GET_PAGE_LIST,
|
||||||
() -> getPageListObservable()
|
() -> getPageListObservable()
|
||||||
.doOnNext(pages -> pageList = pages)
|
.doOnNext(pages -> pageList = pages)
|
||||||
.doOnCompleted(() -> start(GET_PAGE_IMAGES)),
|
.doOnCompleted(this::prepareChapter),
|
||||||
(view, pages) -> {
|
(view, pages) -> {
|
||||||
view.onPageListReady(pages);
|
view.onPageListReady(pages);
|
||||||
if (currentPage != 0)
|
if (currentPage != 0)
|
||||||
@ -55,6 +61,10 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
(view, page) -> {
|
(view, page) -> {
|
||||||
},
|
},
|
||||||
(view, error) -> Timber.e("An error occurred while downloading an image"));
|
(view, error) -> Timber.e("An error occurred while downloading an image"));
|
||||||
|
|
||||||
|
restartableReplay(GET_LOCAL_IMAGES,
|
||||||
|
this::getLocalImagesObservable,
|
||||||
|
(view, page) -> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -77,14 +87,16 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@EventBusHook
|
@EventBusHook
|
||||||
public void onEventMainThread(SourceChapterEvent event) {
|
public void onEventMainThread(SourceMangaChapterEvent event) {
|
||||||
source = event.getSource();
|
source = event.getSource();
|
||||||
|
manga = event.getManga();
|
||||||
chapter = event.getChapter();
|
chapter = event.getChapter();
|
||||||
if (chapter.last_page_read != 0 && !chapter.read)
|
if (chapter.last_page_read != 0 && !chapter.read)
|
||||||
currentPage = chapter.last_page_read;
|
currentPage = chapter.last_page_read;
|
||||||
|
|
||||||
start(1);
|
start(GET_PAGE_LIST);
|
||||||
EventBus.getDefault().removeStickyEvent(SourceChapterEvent.class);
|
|
||||||
|
EventBus.getDefault().removeStickyEvent(SourceMangaChapterEvent.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observable<List<Page>> getPageListObservable() {
|
private Observable<List<Page>> getPageListObservable() {
|
||||||
@ -103,10 +115,26 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
.observeOn(AndroidSchedulers.mainThread());
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Observable<Page> getLocalImagesObservable() {
|
||||||
|
File chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
|
||||||
|
|
||||||
|
return Observable.from(pageList)
|
||||||
|
.flatMap(page -> downloadManager.getDownloadedImage(page, source, chapterDir))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
public void setCurrentPage(int currentPage) {
|
public void setCurrentPage(int currentPage) {
|
||||||
this.currentPage = currentPage;
|
this.currentPage = currentPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void prepareChapter() {
|
||||||
|
if (chapter.downloaded != Chapter.DOWNLOADED)
|
||||||
|
start(GET_PAGE_IMAGES);
|
||||||
|
else
|
||||||
|
start(GET_LOCAL_IMAGES);
|
||||||
|
}
|
||||||
|
|
||||||
private void saveChapter() {
|
private void saveChapter() {
|
||||||
chapter.last_page_read = currentPage;
|
chapter.last_page_read = currentPage;
|
||||||
if (currentPage == pageList.size() - 1) {
|
if (currentPage == pageList.size() - 1) {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.mangafeed.ui.adapter;
|
package eu.kanade.mangafeed.ui.adapter;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@ -11,16 +10,17 @@ import java.util.List;
|
|||||||
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
||||||
import eu.kanade.mangafeed.R;
|
import eu.kanade.mangafeed.R;
|
||||||
import eu.kanade.mangafeed.data.models.Chapter;
|
import eu.kanade.mangafeed.data.models.Chapter;
|
||||||
|
import eu.kanade.mangafeed.ui.fragment.MangaChaptersFragment;
|
||||||
import eu.kanade.mangafeed.ui.fragment.base.BaseFragment;
|
import eu.kanade.mangafeed.ui.fragment.base.BaseFragment;
|
||||||
import eu.kanade.mangafeed.ui.holder.ChaptersHolder;
|
import eu.kanade.mangafeed.ui.holder.ChaptersHolder;
|
||||||
|
|
||||||
public class ChaptersAdapter extends FlexibleAdapter<ChaptersHolder, Chapter> {
|
public class ChaptersAdapter extends FlexibleAdapter<ChaptersHolder, Chapter> {
|
||||||
|
|
||||||
private Context context;
|
private BaseFragment fragment;
|
||||||
public OnItemClickListener clickListener;
|
public OnItemClickListener clickListener;
|
||||||
|
|
||||||
public ChaptersAdapter(BaseFragment fragment) {
|
public ChaptersAdapter(BaseFragment fragment) {
|
||||||
this.context = fragment.getActivity();
|
this.fragment = fragment;
|
||||||
mItems = new ArrayList<>();
|
mItems = new ArrayList<>();
|
||||||
clickListener = (OnItemClickListener) fragment;
|
clickListener = (OnItemClickListener) fragment;
|
||||||
}
|
}
|
||||||
@ -30,14 +30,14 @@ public class ChaptersAdapter extends FlexibleAdapter<ChaptersHolder, Chapter> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChaptersHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
public ChaptersHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
View v = LayoutInflater.from(context).inflate(R.layout.item_chapter, parent, false);
|
View v = LayoutInflater.from(fragment.getActivity()).inflate(R.layout.item_chapter, parent, false);
|
||||||
return new ChaptersHolder(v, this);
|
return new ChaptersHolder(v, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(ChaptersHolder holder, int position) {
|
public void onBindViewHolder(ChaptersHolder holder, int position) {
|
||||||
final Chapter chapter = getItem(position);
|
final Chapter chapter = getItem(position);
|
||||||
holder.onSetValues(context, chapter);
|
holder.onSetValues(fragment.getActivity(), chapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<Chapter> chapters) {
|
public void setItems(List<Chapter> chapters) {
|
||||||
@ -49,4 +49,8 @@ public class ChaptersAdapter extends FlexibleAdapter<ChaptersHolder, Chapter> {
|
|||||||
boolean onListItemClick(int position);
|
boolean onListItemClick(int position);
|
||||||
void onListItemLongClick(int position);
|
void onListItemLongClick(int position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MangaChaptersFragment getMangaChaptersFragment() {
|
||||||
|
return (MangaChaptersFragment) fragment;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,8 @@ public class CatalogueHolder extends ItemViewHolder<Manga> {
|
|||||||
.diskCacheStrategy(DiskCacheStrategy.RESULT)
|
.diskCacheStrategy(DiskCacheStrategy.RESULT)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(image);
|
.into(image);
|
||||||
|
} else {
|
||||||
|
image.setImageResource(android.R.color.transparent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ public class ChaptersHolder extends RecyclerView.ViewHolder implements
|
|||||||
|
|
||||||
public void onSetValues(Context context, Chapter chapter) {
|
public void onSetValues(Context context, Chapter chapter) {
|
||||||
title.setText(chapter.name);
|
title.setText(chapter.name);
|
||||||
download_icon.setImageResource(R.drawable.ic_file_download_black_48dp);
|
|
||||||
|
|
||||||
if (chapter.read) {
|
if (chapter.read) {
|
||||||
title.setTextColor(ContextCompat.getColor(context, R.color.chapter_read_text));
|
title.setTextColor(ContextCompat.getColor(context, R.color.chapter_read_text));
|
||||||
@ -52,6 +52,14 @@ public class ChaptersHolder extends RecyclerView.ViewHolder implements
|
|||||||
pages.setText("");
|
pages.setText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chapter.downloaded == Chapter.UNKNOWN) {
|
||||||
|
adapter.getMangaChaptersFragment().getPresenter().checkIsChapterDownloaded(chapter);
|
||||||
|
}
|
||||||
|
if (chapter.downloaded == Chapter.DOWNLOADED)
|
||||||
|
download_icon.setImageResource(R.drawable.ic_action_delete_36dp);
|
||||||
|
else if (chapter.downloaded == Chapter.NOT_DOWNLOADED)
|
||||||
|
download_icon.setImageResource(R.drawable.ic_file_download_black_36dp);
|
||||||
|
|
||||||
toggleActivation();
|
toggleActivation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
app/src/main/res/drawable-hdpi/ic_action_delete_36dp.png
Normal file
After Width: | Height: | Size: 213 B |
BIN
app/src/main/res/drawable-hdpi/ic_file_download_black_36dp.png
Normal file
After Width: | Height: | Size: 204 B |
BIN
app/src/main/res/drawable-ldpi/ic_action_delete_36dp.png
Normal file
After Width: | Height: | Size: 252 B |
BIN
app/src/main/res/drawable-ldpi/ic_file_download_black_36dp.png
Normal file
After Width: | Height: | Size: 265 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_delete_36dp.png
Normal file
After Width: | Height: | Size: 160 B |
BIN
app/src/main/res/drawable-mdpi/ic_file_download_black_36dp.png
Normal file
After Width: | Height: | Size: 161 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_delete_36dp.png
Normal file
After Width: | Height: | Size: 201 B |
BIN
app/src/main/res/drawable-xhdpi/ic_file_download_black_36dp.png
Normal file
After Width: | Height: | Size: 200 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_delete_36dp.png
Normal file
After Width: | Height: | Size: 298 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_file_download_black_36dp.png
Normal file
After Width: | Height: | Size: 295 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_delete_36dp.png
Normal file
After Width: | Height: | Size: 365 B |
After Width: | Height: | Size: 348 B |