Improve the use and caching of the covers' cache. Use restartables in LibraryPresenter
This commit is contained in:
parent
976f010d64
commit
54a715640d
7 changed files with 92 additions and 83 deletions
|
@ -36,16 +36,24 @@ public class CoverCache {
|
||||||
return !cacheDir.exists() && cacheDir.mkdirs();
|
return !cacheDir.exists() && cacheDir.mkdirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void save(String thumbnailUrl, LazyHeaders headers) {
|
||||||
|
save(thumbnailUrl, headers, null);
|
||||||
|
}
|
||||||
|
|
||||||
// Download the cover with Glide (it can avoid repeating requests) and save the file on this cache
|
// Download the cover with Glide (it can avoid repeating requests) and save the file on this cache
|
||||||
public void save(String cover, LazyHeaders headers) {
|
// Optionally, load the image in the given image view when the resource is ready, if not null
|
||||||
GlideUrl url = new GlideUrl(cover, headers);
|
public void save(String thumbnailUrl, LazyHeaders headers, ImageView imageView) {
|
||||||
|
GlideUrl url = new GlideUrl(thumbnailUrl, headers);
|
||||||
Glide.with(context)
|
Glide.with(context)
|
||||||
.load(url)
|
.load(url)
|
||||||
.downloadOnly(new SimpleTarget<File>() {
|
.downloadOnly(new SimpleTarget<File>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResourceReady(File resource, GlideAnimation<? super File> anim) {
|
public void onResourceReady(File resource, GlideAnimation<? super File> anim) {
|
||||||
try {
|
try {
|
||||||
add(cover, resource);
|
add(thumbnailUrl, resource);
|
||||||
|
if (imageView != null) {
|
||||||
|
loadFromCache(imageView, resource);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
@ -54,8 +62,9 @@ public class CoverCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the cover from Glide's cache to this cache
|
// Copy the cover from Glide's cache to this cache
|
||||||
public void add(String key, File source) throws IOException {
|
public void add(String thumbnailUrl, File source) throws IOException {
|
||||||
File dest = new File(cacheDir, DiskUtils.hashKeyForDisk(key));
|
createCacheDir();
|
||||||
|
File dest = new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl));
|
||||||
if (dest.exists())
|
if (dest.exists())
|
||||||
dest.delete();
|
dest.delete();
|
||||||
|
|
||||||
|
@ -78,37 +87,29 @@ public class CoverCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the cover from cache
|
// Get the cover from cache
|
||||||
public File get(String key) {
|
public File get(String thumbnailUrl) {
|
||||||
return new File(cacheDir, DiskUtils.hashKeyForDisk(key));
|
return new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the cover from cache
|
// Delete the cover from cache
|
||||||
public boolean delete(String key) {
|
public boolean delete(String thumbnailUrl) {
|
||||||
File file = new File(cacheDir, DiskUtils.hashKeyForDisk(key));
|
File file = new File(cacheDir, DiskUtils.hashKeyForDisk(thumbnailUrl));
|
||||||
return file.exists() && file.delete();
|
return file.exists() && file.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the cover from cache or network if it doesn't exist
|
// Save and load the image from cache
|
||||||
public void loadOrFetchInto(ImageView imageView, String cover, LazyHeaders headers) {
|
public void saveAndLoadFromCache(ImageView imageView, String thumbnailUrl, LazyHeaders headers) {
|
||||||
File localCover = get(cover);
|
File localCover = get(thumbnailUrl);
|
||||||
if (localCover.exists()) {
|
if (localCover.exists()) {
|
||||||
loadLocalInto(context, imageView, localCover);
|
loadFromCache(imageView, localCover);
|
||||||
} else {
|
} else {
|
||||||
loadRemoteInto(context, imageView, cover, headers);
|
save(thumbnailUrl, headers, imageView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the cover from cache
|
// Helper method to load the cover from the cache directory into the specified image view
|
||||||
public static void loadLocalInto(Context context, ImageView imageView, String cover) {
|
// The file must exist
|
||||||
File cacheDir = new File(context.getCacheDir(), PARAMETER_CACHE_DIRECTORY);
|
private void loadFromCache(ImageView imageView, File file) {
|
||||||
File localCover = new File(cacheDir, DiskUtils.hashKeyForDisk(cover));
|
|
||||||
if (localCover.exists()) {
|
|
||||||
loadLocalInto(context, imageView, localCover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the cover from the cache directory into the specified image view
|
|
||||||
private static void loadLocalInto(Context context, ImageView imageView, File file) {
|
|
||||||
Glide.with(context)
|
Glide.with(context)
|
||||||
.load(file)
|
.load(file)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
@ -116,9 +117,10 @@ public class CoverCache {
|
||||||
.into(imageView);
|
.into(imageView);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the cover from network into the specified image view. It does NOT save the image in cache
|
// Helper method to load the cover from network into the specified image view.
|
||||||
private static void loadRemoteInto(Context context, ImageView imageView, String cover, LazyHeaders headers) {
|
// It does NOT save the image in cache
|
||||||
GlideUrl url = new GlideUrl(cover, headers);
|
public void loadFromNetwork(ImageView imageView, String thumbnailUrl, LazyHeaders headers) {
|
||||||
|
GlideUrl url = new GlideUrl(thumbnailUrl, headers);
|
||||||
Glide.with(context)
|
Glide.with(context)
|
||||||
.load(url)
|
.load(url)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
|
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
|
||||||
|
|
|
@ -25,7 +25,6 @@ public class DownloadQueue {
|
||||||
|
|
||||||
public void remove(Download download) {
|
public void remove(Download download) {
|
||||||
queue.remove(download);
|
queue.remove(download);
|
||||||
download.setStatus(Download.NOT_DOWNLOADED);
|
|
||||||
download.setStatusSubject(null);
|
download.setStatusSubject(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.kanade.mangafeed.ui.library;
|
package eu.kanade.mangafeed.ui.library;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.Filter;
|
import android.widget.Filter;
|
||||||
import android.widget.Filterable;
|
import android.widget.Filterable;
|
||||||
|
|
||||||
|
@ -14,10 +15,12 @@ public class LibraryAdapter extends EasyAdapter<Manga> implements Filterable {
|
||||||
|
|
||||||
List<Manga> mangas;
|
List<Manga> mangas;
|
||||||
Filter filter;
|
Filter filter;
|
||||||
|
private LibraryPresenter presenter;
|
||||||
|
|
||||||
public LibraryAdapter(Context context) {
|
public LibraryAdapter(LibraryFragment fragment) {
|
||||||
super(context, LibraryHolder.class);
|
super(fragment.getActivity(), LibraryHolder.class);
|
||||||
filter = new LibraryFilter();
|
filter = new LibraryFilter();
|
||||||
|
presenter = fragment.getPresenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNewItems(List<Manga> list) {
|
public void setNewItems(List<Manga> list) {
|
||||||
|
@ -57,9 +60,16 @@ public class LibraryAdapter extends EasyAdapter<Manga> implements Filterable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void publishResults(CharSequence constraint, FilterResults results) {
|
public void publishResults(CharSequence constraint, FilterResults results) {
|
||||||
setItems((List<Manga >) results.values);
|
setItems((List<Manga>) results.values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
View view = super.getView(position, convertView, parent);
|
||||||
|
LibraryHolder holder = (LibraryHolder) view.getTag();
|
||||||
|
Manga manga = getItem(position);
|
||||||
|
holder.loadCover(manga, presenter.sourceManager.get(manga.source), presenter.coverCache);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package eu.kanade.mangafeed.ui.library;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.widget.SearchView;
|
import android.support.v7.widget.SearchView;
|
||||||
|
import android.util.SparseBooleanArray;
|
||||||
import android.view.ActionMode;
|
import android.view.ActionMode;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
@ -20,9 +21,10 @@ import butterknife.OnItemClick;
|
||||||
import eu.kanade.mangafeed.R;
|
import eu.kanade.mangafeed.R;
|
||||||
import eu.kanade.mangafeed.data.database.models.Manga;
|
import eu.kanade.mangafeed.data.database.models.Manga;
|
||||||
import eu.kanade.mangafeed.data.sync.LibraryUpdateService;
|
import eu.kanade.mangafeed.data.sync.LibraryUpdateService;
|
||||||
import eu.kanade.mangafeed.ui.manga.MangaActivity;
|
|
||||||
import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment;
|
import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment;
|
||||||
|
import eu.kanade.mangafeed.ui.manga.MangaActivity;
|
||||||
import nucleus.factory.RequiresPresenter;
|
import nucleus.factory.RequiresPresenter;
|
||||||
|
import rx.Observable;
|
||||||
|
|
||||||
@RequiresPresenter(LibraryPresenter.class)
|
@RequiresPresenter(LibraryPresenter.class)
|
||||||
public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
|
public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
|
||||||
|
@ -31,10 +33,7 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
|
||||||
private LibraryAdapter adapter;
|
private LibraryAdapter adapter;
|
||||||
|
|
||||||
public static LibraryFragment newInstance() {
|
public static LibraryFragment newInstance() {
|
||||||
LibraryFragment fragment = new LibraryFragment();
|
return new LibraryFragment();
|
||||||
Bundle args = new Bundle();
|
|
||||||
fragment.setArguments(args);
|
|
||||||
return fragment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -95,7 +94,7 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createAdapter() {
|
private void createAdapter() {
|
||||||
adapter = new LibraryAdapter(getActivity());
|
adapter = new LibraryAdapter(this);
|
||||||
grid.setAdapter(adapter);
|
grid.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +135,7 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
|
||||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_delete:
|
case R.id.action_delete:
|
||||||
getPresenter().onDelete(grid.getCheckedItemPositions(), adapter);
|
getPresenter().deleteMangas(getSelectedMangas());
|
||||||
mode.finish();
|
mode.finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -150,4 +149,11 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Observable<Manga> getSelectedMangas() {
|
||||||
|
SparseBooleanArray checkedItems = grid.getCheckedItemPositions();
|
||||||
|
return Observable.range(0, checkedItems.size())
|
||||||
|
.map(checkedItems::keyAt)
|
||||||
|
.map(adapter::getItem);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.widget.TextView;
|
||||||
import eu.kanade.mangafeed.R;
|
import eu.kanade.mangafeed.R;
|
||||||
import eu.kanade.mangafeed.data.cache.CoverCache;
|
import eu.kanade.mangafeed.data.cache.CoverCache;
|
||||||
import eu.kanade.mangafeed.data.database.models.Manga;
|
import eu.kanade.mangafeed.data.database.models.Manga;
|
||||||
|
import eu.kanade.mangafeed.data.source.base.Source;
|
||||||
import uk.co.ribot.easyadapter.ItemViewHolder;
|
import uk.co.ribot.easyadapter.ItemViewHolder;
|
||||||
import uk.co.ribot.easyadapter.PositionInfo;
|
import uk.co.ribot.easyadapter.PositionInfo;
|
||||||
import uk.co.ribot.easyadapter.annotations.LayoutId;
|
import uk.co.ribot.easyadapter.annotations.LayoutId;
|
||||||
|
@ -17,11 +18,8 @@ import uk.co.ribot.easyadapter.annotations.ViewId;
|
||||||
public class LibraryHolder extends ItemViewHolder<Manga> {
|
public class LibraryHolder extends ItemViewHolder<Manga> {
|
||||||
|
|
||||||
@ViewId(R.id.thumbnail) ImageView thumbnail;
|
@ViewId(R.id.thumbnail) ImageView thumbnail;
|
||||||
|
|
||||||
@ViewId(R.id.title) TextView title;
|
@ViewId(R.id.title) TextView title;
|
||||||
|
|
||||||
@ViewId(R.id.author) TextView author;
|
@ViewId(R.id.author) TextView author;
|
||||||
|
|
||||||
@ViewId(R.id.unreadText) TextView unreadText;
|
@ViewId(R.id.unreadText) TextView unreadText;
|
||||||
|
|
||||||
public LibraryHolder(View view) {
|
public LibraryHolder(View view) {
|
||||||
|
@ -38,12 +36,11 @@ public class LibraryHolder extends ItemViewHolder<Manga> {
|
||||||
} else {
|
} else {
|
||||||
unreadText.setVisibility(View.GONE);
|
unreadText.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manga.thumbnail_url != null) {
|
|
||||||
CoverCache.loadLocalInto(getContext(), thumbnail, manga.thumbnail_url);
|
|
||||||
} else {
|
|
||||||
thumbnail.setImageResource(android.R.color.transparent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void loadCover(Manga manga, Source source, CoverCache coverCache) {
|
||||||
|
if (manga.thumbnail_url != null)
|
||||||
|
coverCache.saveAndLoadFromCache(thumbnail, manga.thumbnail_url, source.getGlideHeaders());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
package eu.kanade.mangafeed.ui.library;
|
package eu.kanade.mangafeed.ui.library;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.SparseBooleanArray;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import eu.kanade.mangafeed.data.cache.CoverCache;
|
||||||
import eu.kanade.mangafeed.data.database.DatabaseHelper;
|
import eu.kanade.mangafeed.data.database.DatabaseHelper;
|
||||||
|
import eu.kanade.mangafeed.data.database.models.Manga;
|
||||||
import eu.kanade.mangafeed.data.preference.PreferencesHelper;
|
import eu.kanade.mangafeed.data.preference.PreferencesHelper;
|
||||||
|
import eu.kanade.mangafeed.data.source.SourceManager;
|
||||||
import eu.kanade.mangafeed.ui.base.presenter.BasePresenter;
|
import eu.kanade.mangafeed.ui.base.presenter.BasePresenter;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.Subscription;
|
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
import rx.schedulers.Schedulers;
|
import rx.schedulers.Schedulers;
|
||||||
|
|
||||||
|
@ -17,44 +18,28 @@ public class LibraryPresenter extends BasePresenter<LibraryFragment> {
|
||||||
|
|
||||||
@Inject DatabaseHelper db;
|
@Inject DatabaseHelper db;
|
||||||
@Inject PreferencesHelper prefs;
|
@Inject PreferencesHelper prefs;
|
||||||
|
@Inject CoverCache coverCache;
|
||||||
|
@Inject SourceManager sourceManager;
|
||||||
|
|
||||||
private Subscription mFavoriteMangasSubscription;
|
private static final int GET_MANGAS = 1;
|
||||||
private Subscription mDeleteMangaSubscription;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedState) {
|
protected void onCreate(Bundle savedState) {
|
||||||
super.onCreate(savedState);
|
super.onCreate(savedState);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
restartableLatestCache(GET_MANGAS,
|
||||||
protected void onTakeView(LibraryFragment view) {
|
() -> db.getMangasWithUnread().createObservable()
|
||||||
super.onTakeView(view);
|
|
||||||
getFavoriteMangas();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void getFavoriteMangas() {
|
|
||||||
if (mFavoriteMangasSubscription != null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
add(mFavoriteMangasSubscription = db.getMangasWithUnread().createObservable()
|
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread()),
|
||||||
.compose(deliverLatestCache())
|
LibraryFragment::onNextMangas);
|
||||||
.subscribe(this.split(LibraryFragment::onNextMangas)));
|
|
||||||
|
start(GET_MANGAS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onDelete(SparseBooleanArray checkedItems, LibraryAdapter adapter) {
|
public void deleteMangas(Observable<Manga> selectedMangas) {
|
||||||
if (mDeleteMangaSubscription != null)
|
add(selectedMangas
|
||||||
remove(mDeleteMangaSubscription);
|
.subscribeOn(Schedulers.io())
|
||||||
|
.doOnNext(manga -> manga.favorite = false)
|
||||||
add(mDeleteMangaSubscription = Observable.range(0, checkedItems.size())
|
|
||||||
.observeOn(Schedulers.io())
|
|
||||||
.map(checkedItems::keyAt)
|
|
||||||
.map(adapter::getItem)
|
|
||||||
.map(manga -> {
|
|
||||||
manga.favorite = false;
|
|
||||||
return manga;
|
|
||||||
})
|
|
||||||
.toList()
|
.toList()
|
||||||
.flatMap(mangas -> db.insertMangas(mangas).createObservable())
|
.flatMap(mangas -> db.insertMangas(mangas).createObservable())
|
||||||
.subscribe());
|
.subscribe());
|
||||||
|
|
|
@ -8,9 +8,12 @@ import android.widget.Button;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.model.LazyHeaders;
|
||||||
|
|
||||||
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.data.cache.CoverCache;
|
||||||
import eu.kanade.mangafeed.data.database.models.Manga;
|
import eu.kanade.mangafeed.data.database.models.Manga;
|
||||||
import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment;
|
import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment;
|
||||||
import nucleus.factory.RequiresPresenter;
|
import nucleus.factory.RequiresPresenter;
|
||||||
|
@ -62,8 +65,15 @@ public class MangaInfoFragment extends BaseRxFragment<MangaInfoPresenter> {
|
||||||
|
|
||||||
setFavoriteText(manga.favorite);
|
setFavoriteText(manga.favorite);
|
||||||
|
|
||||||
getPresenter().coverCache.loadOrFetchInto(mCover,
|
if (mCover.getDrawable() == null) {
|
||||||
manga.thumbnail_url, getPresenter().source.getGlideHeaders());
|
CoverCache coverCache = getPresenter().coverCache;
|
||||||
|
LazyHeaders headers = getPresenter().source.getGlideHeaders();
|
||||||
|
if (manga.favorite) {
|
||||||
|
coverCache.saveAndLoadFromCache(mCover, manga.thumbnail_url, headers);
|
||||||
|
} else {
|
||||||
|
coverCache.loadFromNetwork(mCover, manga.thumbnail_url, headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Reference in a new issue