From 04dfdba0b7a23224a2a8df91a962816b623935f3 Mon Sep 17 00:00:00 2001 From: inorichi Date: Mon, 2 Nov 2015 17:25:06 +0100 Subject: [PATCH] Refresh button in library is now looking for new chapters in sources and notifying the user --- app/src/main/AndroidManifest.xml | 11 ++ .../data/helpers/DatabaseHelper.java | 5 + .../mangafeed/data/helpers/SourceManager.java | 3 - .../mangafeed/data/managers/MangaManager.java | 2 + .../data/managers/MangaManagerImpl.java | 13 ++ .../data/services/LibraryUpdateService.java | 168 ++++++++++++++++++ .../injection/component/AppComponent.java | 3 + .../ui/fragment/LibraryFragment.java | 16 ++ .../mangafeed/util/AndroidComponentUtil.java | 33 ++++ .../mangafeed/util/NotificationUtil.java | 44 +++++ .../eu/kanade/mangafeed/util/PostResult.java | 3 - app/src/main/res/values/strings.xml | 6 + 12 files changed, 301 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/eu/kanade/mangafeed/data/services/LibraryUpdateService.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/util/AndroidComponentUtil.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/util/NotificationUtil.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 99c68eb9bb..73870258b0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -42,6 +42,17 @@ android:label="@string/title_activity_settings" android:parentActivityName=".ui.activity.MainActivity" > + + + + + + + + diff --git a/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java b/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java index beb7ad8f50..76ecfadea8 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/helpers/DatabaseHelper.java @@ -103,6 +103,11 @@ public class DatabaseHelper implements MangaManager, ChapterManager { return mMangaManager.getMangasWithUnread(); } + @Override + public Observable> getFavoriteMangas() { + return mMangaManager.getFavoriteMangas(); + } + @Override public Observable> getManga(String url) { return mMangaManager.getManga(url); diff --git a/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java b/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java index 0444cc167c..25dbc3a0a2 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/helpers/SourceManager.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import eu.kanade.mangafeed.data.caches.CacheManager; import eu.kanade.mangafeed.sources.Batoto; import eu.kanade.mangafeed.sources.MangaHere; import eu.kanade.mangafeed.sources.base.Source; @@ -17,8 +16,6 @@ public class SourceManager { public static final int MANGAHERE = 2; private HashMap mSourcesMap; - private NetworkHelper mNetworkHelper; - private CacheManager mCacheManager; private Context context; public SourceManager(Context context) { diff --git a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java index 1ff99c2317..6aa717da09 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java @@ -16,6 +16,8 @@ public interface MangaManager { Observable> getMangasWithUnread(); + Observable> getFavoriteMangas(); + Observable> getManga(String url); Observable> getManga(long id); diff --git a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java index 5d546c3042..a4ae052fd2 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManagerImpl.java @@ -55,6 +55,19 @@ public class MangaManagerImpl extends BaseManager implements MangaManager { .createObservable(); } + @Override + public Observable> getFavoriteMangas() { + return db.get() + .listOfObjects(Manga.class) + .withQuery(Query.builder() + .table(MangasTable.TABLE) + .where(MangasTable.COLUMN_FAVORITE + "=?") + .whereArgs(1) + .build()) + .prepare() + .createObservable(); + } + public Observable> getManga(String url) { return db.get() .listOfObjects(Manga.class) diff --git a/app/src/main/java/eu/kanade/mangafeed/data/services/LibraryUpdateService.java b/app/src/main/java/eu/kanade/mangafeed/data/services/LibraryUpdateService.java new file mode 100644 index 0000000000..a692e32f50 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/data/services/LibraryUpdateService.java @@ -0,0 +1,168 @@ +package eu.kanade.mangafeed.data.services; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.inject.Inject; + +import eu.kanade.mangafeed.App; +import eu.kanade.mangafeed.BuildConfig; +import eu.kanade.mangafeed.R; +import eu.kanade.mangafeed.data.helpers.DatabaseHelper; +import eu.kanade.mangafeed.data.helpers.SourceManager; +import eu.kanade.mangafeed.data.models.Manga; +import eu.kanade.mangafeed.util.AndroidComponentUtil; +import eu.kanade.mangafeed.util.NetworkUtil; +import eu.kanade.mangafeed.util.NotificationUtil; +import eu.kanade.mangafeed.util.PostResult; +import rx.Observable; +import rx.Subscription; +import timber.log.Timber; + +public class LibraryUpdateService extends Service { + + @Inject DatabaseHelper db; + @Inject SourceManager sourceManager; + + private Subscription updateSubscription; + private Subscription favoriteMangasSubscription; + + public static final int UPDATE_NOTIFICATION_ID = 1; + + public static Intent getStartIntent(Context context) { + return new Intent(context, LibraryUpdateService.class); + } + + public static boolean isRunning(Context context) { + return AndroidComponentUtil.isServiceRunning(context, LibraryUpdateService.class); + } + + @Override + public void onCreate() { + super.onCreate(); + App.get(this).getComponent().inject(this); + } + + @Override + public void onDestroy() { + if (updateSubscription != null) + updateSubscription.unsubscribe(); + super.onDestroy(); + } + + @Override + public int onStartCommand(Intent intent, int flags, final int startId) { + Timber.i("Starting sync..."); + + if (!NetworkUtil.isNetworkConnected(this)) { + Timber.i("Sync canceled, connection not available"); + AndroidComponentUtil.toggleComponent(this, SyncOnConnectionAvailable.class, true); + stopSelf(startId); + return START_NOT_STICKY; + } + + if (favoriteMangasSubscription != null && !favoriteMangasSubscription.isUnsubscribed()) + favoriteMangasSubscription.unsubscribe(); + + favoriteMangasSubscription = db.getFavoriteMangas() + .subscribe(mangas -> { + // Don't receive further db updates + favoriteMangasSubscription.unsubscribe(); + this.startUpdating(mangas, startId); + }); + + return START_STICKY; + } + + private void startUpdating(final List mangas, final int startId) { + if (updateSubscription != null && !updateSubscription.isUnsubscribed()) + updateSubscription.unsubscribe(); + + final AtomicInteger count = new AtomicInteger(0); + + List updates = new ArrayList<>(); + + updateSubscription = Observable.from(mangas) + .doOnNext(manga -> { + NotificationUtil.create(this, UPDATE_NOTIFICATION_ID, + getString(R.string.notification_progress, count.incrementAndGet(), mangas.size()), + manga.title); + }) + .concatMap(manga -> sourceManager.get(manga.source) + .pullChaptersFromNetwork(manga.url) + .flatMap(chapters -> db.insertOrRemoveChapters(manga, chapters)) + .filter(result -> result.getNumberOfRowsInserted() > 0) + .flatMap(result -> Observable.just(new MangaUpdate(manga, result))) + ) + .subscribe(update -> { + updates.add(update); + }, error -> { + Timber.e("Error syncing"); + stopSelf(startId); + }, () -> { + NotificationUtil.createBigText(this, UPDATE_NOTIFICATION_ID, + getString(R.string.notification_completed), getUpdatedMangas(updates)); + stopSelf(startId); + }); + } + + private String getUpdatedMangas(List updates) { + final StringBuilder result = new StringBuilder(); + if (updates.isEmpty()) { + result.append(getString(R.string.notification_no_new_chapters)).append("\n"); + } else { + result.append(getString(R.string.notification_new_chapters)); + + for (MangaUpdate update : updates) { + result.append("\n").append(update.getManga().title); + } + } + + return result.toString(); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + public static class SyncOnConnectionAvailable extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if (NetworkUtil.isNetworkConnected(context)) { + if (BuildConfig.DEBUG) { + Timber.i("Connection is now available, triggering sync..."); + } + AndroidComponentUtil.toggleComponent(context, this.getClass(), false); + context.startService(getStartIntent(context)); + } + } + } + + private static class MangaUpdate { + private Manga manga; + private PostResult result; + + public MangaUpdate(Manga manga, PostResult result) { + this.manga = manga; + this.result = result; + } + + public Manga getManga() { + return manga; + } + + public PostResult getResult() { + return result; + } + } + +} diff --git a/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java b/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java index 0a93bb7550..90a117d18c 100644 --- a/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java +++ b/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java @@ -5,6 +5,7 @@ import android.app.Application; import javax.inject.Singleton; import dagger.Component; +import eu.kanade.mangafeed.data.services.LibraryUpdateService; import eu.kanade.mangafeed.injection.module.AppModule; import eu.kanade.mangafeed.injection.module.DataModule; import eu.kanade.mangafeed.presenter.CataloguePresenter; @@ -40,6 +41,8 @@ public interface AppComponent { void inject(Source source); + void inject(LibraryUpdateService libraryUpdateService); + Application application(); } diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/LibraryFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/LibraryFragment.java index 6d6ee81c6e..e1191bf94c 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/fragment/LibraryFragment.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/fragment/LibraryFragment.java @@ -19,6 +19,7 @@ import butterknife.ButterKnife; import butterknife.OnItemClick; import eu.kanade.mangafeed.R; import eu.kanade.mangafeed.data.models.Manga; +import eu.kanade.mangafeed.data.services.LibraryUpdateService; import eu.kanade.mangafeed.presenter.LibraryPresenter; import eu.kanade.mangafeed.ui.activity.MainActivity; import eu.kanade.mangafeed.ui.activity.MangaDetailActivity; @@ -68,6 +69,21 @@ public class LibraryFragment extends BaseRxFragment { initializeSearch(menu); } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_refresh: + if (!LibraryUpdateService.isRunning(activity)) { + Intent intent = LibraryUpdateService.getStartIntent(activity); + activity.startService(intent); + } + + return true; + } + + return super.onOptionsItemSelected(item); + } + private void initializeSearch(Menu menu) { final SearchView sv = (SearchView) menu.findItem(R.id.action_search).getActionView(); sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { diff --git a/app/src/main/java/eu/kanade/mangafeed/util/AndroidComponentUtil.java b/app/src/main/java/eu/kanade/mangafeed/util/AndroidComponentUtil.java new file mode 100644 index 0000000000..3789160fec --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/util/AndroidComponentUtil.java @@ -0,0 +1,33 @@ +package eu.kanade.mangafeed.util; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningServiceInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; + +import timber.log.Timber; + +public class AndroidComponentUtil { + + public static void toggleComponent(Context context, Class componentClass, boolean enable) { + Timber.i((enable ? "Enabling " : "Disabling ") + componentClass.getSimpleName()); + ComponentName componentName = new ComponentName(context, componentClass); + PackageManager pm = context.getPackageManager(); + pm.setComponentEnabledSetting(componentName, + enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + } + + public static boolean isServiceRunning(Context context, Class serviceClass) { + ActivityManager manager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if (serviceClass.getName().equals(service.service.getClassName())) { + return true; + } + } + return false; + } +} diff --git a/app/src/main/java/eu/kanade/mangafeed/util/NotificationUtil.java b/app/src/main/java/eu/kanade/mangafeed/util/NotificationUtil.java new file mode 100644 index 0000000000..3f20bf7297 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/util/NotificationUtil.java @@ -0,0 +1,44 @@ +package eu.kanade.mangafeed.util; + +import android.app.NotificationManager; +import android.content.Context; +import android.support.v4.app.NotificationCompat; + +import eu.kanade.mangafeed.R; + +public class NotificationUtil { + + public static void create(Context context, int nId, String title, String body, int iconRes) { + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context) + .setSmallIcon(iconRes == -1 ? R.drawable.ic_action_refresh : iconRes) + .setContentTitle(title) + .setContentText(body); + + + NotificationManager mNotificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + mNotificationManager.notify(nId, mBuilder.build()); + } + + public static void createBigText(Context context, int nId, String title, String body, int iconRes) { + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context) + .setSmallIcon(iconRes == -1 ? R.drawable.ic_action_refresh : iconRes) + .setContentTitle(title) + .setStyle(new NotificationCompat.BigTextStyle().bigText(body)); + + NotificationManager mNotificationManager = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + mNotificationManager.notify(nId, mBuilder.build()); + } + + public static void create(Context context, int nId, String title, String body) { + create(context, nId, title, body, -1); + } + + public static void createBigText(Context context, int nId, String title, String body) { + createBigText(context, nId, title, body, -1); + } + +} diff --git a/app/src/main/java/eu/kanade/mangafeed/util/PostResult.java b/app/src/main/java/eu/kanade/mangafeed/util/PostResult.java index d64c3fd866..f76bacdd1c 100644 --- a/app/src/main/java/eu/kanade/mangafeed/util/PostResult.java +++ b/app/src/main/java/eu/kanade/mangafeed/util/PostResult.java @@ -19,17 +19,14 @@ public class PostResult { this.numberOfRowsDeleted = numberOfRowsDeleted; } - @Nullable public Integer getNumberOfRowsUpdated() { return numberOfRowsUpdated; } - @Nullable public Integer getNumberOfRowsInserted() { return numberOfRowsInserted; } - @Nullable public Integer getNumberOfRowsDeleted() { return numberOfRowsDeleted; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ba03e13781..747235e062 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -82,4 +82,10 @@ Mark as unread Selected chapters: %1$d + Update progress: %1$d/%2$d + Update completed + No new chapters found + Found new chapters for: + +