Bladeren bron

Refresh button in library is now looking for new chapters in sources and notifying the user

inorichi 9 jaren geleden
bovenliggende
commit
04dfdba0b7

+ 11 - 0
app/src/main/AndroidManifest.xml

@@ -42,6 +42,17 @@
             android:label="@string/title_activity_settings"
             android:parentActivityName=".ui.activity.MainActivity" >
         </activity>
+
+        <service android:name=".data.services.LibraryUpdateService"
+            android:exported="false"/>
+
+        <receiver
+            android:name=".data.services.LibraryUpdateService$SyncOnConnectionAvailable"
+            android:enabled="false">
+            <intent-filter>
+                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+            </intent-filter>
+        </receiver>
     </application>
 
 </manifest>

+ 5 - 0
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<List<Manga>> getFavoriteMangas() {
+        return mMangaManager.getFavoriteMangas();
+    }
+
     @Override
     public Observable<List<Manga>> getManga(String url) {
         return mMangaManager.getManga(url);

+ 0 - 3
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<Integer, Source> mSourcesMap;
-    private NetworkHelper mNetworkHelper;
-    private CacheManager mCacheManager;
     private Context context;
 
     public SourceManager(Context context) {

+ 2 - 0
app/src/main/java/eu/kanade/mangafeed/data/managers/MangaManager.java

@@ -16,6 +16,8 @@ public interface MangaManager {
 
     Observable<List<Manga>> getMangasWithUnread();
 
+    Observable<List<Manga>> getFavoriteMangas();
+
     Observable<List<Manga>> getManga(String url);
 
     Observable<List<Manga>> getManga(long id);

+ 13 - 0
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<List<Manga>> getFavoriteMangas() {
+        return db.get()
+                .listOfObjects(Manga.class)
+                .withQuery(Query.builder()
+                        .table(MangasTable.TABLE)
+                        .where(MangasTable.COLUMN_FAVORITE + "=?")
+                        .whereArgs(1)
+                        .build())
+                .prepare()
+                .createObservable();
+    }
+
     public Observable<List<Manga>> getManga(String url) {
         return db.get()
                 .listOfObjects(Manga.class)

+ 168 - 0
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<Manga> mangas, final int startId) {
+        if (updateSubscription != null && !updateSubscription.isUnsubscribed())
+            updateSubscription.unsubscribe();
+
+        final AtomicInteger count = new AtomicInteger(0);
+
+        List<MangaUpdate> 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<MangaUpdate> 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;
+        }
+    }
+
+}

+ 3 - 0
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();
 
 }

+ 16 - 0
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<LibraryPresenter> {
         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() {

+ 33 - 0
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;
+    }
+}

+ 44 - 0
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);
+    }
+
+}

+ 0 - 3
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;
     }

+ 6 - 0
app/src/main/res/values/strings.xml

@@ -82,4 +82,10 @@
     <string name="action_mark_as_unread">Mark as unread</string>
     <string name="selected_chapters_title">Selected chapters: %1$d</string>
 
+    <string name="notification_progress">Update progress: %1$d/%2$d</string>
+    <string name="notification_completed">Update completed</string>
+    <string name="notification_no_new_chapters">No new chapters found</string>
+    <string name="notification_new_chapters">Found new chapters for:</string>
+
+
 </resources>