Refresh button in library is now looking for new chapters in sources and notifying the user
This commit is contained in:
		
							parent
							
								
									faef785fc3
								
							
						
					
					
						commit
						04dfdba0b7
					
				| @ -42,6 +42,17 @@ | |||||||
|             android:label="@string/title_activity_settings" |             android:label="@string/title_activity_settings" | ||||||
|             android:parentActivityName=".ui.activity.MainActivity" > |             android:parentActivityName=".ui.activity.MainActivity" > | ||||||
|         </activity> |         </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> |     </application> | ||||||
| 
 | 
 | ||||||
| </manifest> | </manifest> | ||||||
|  | |||||||
| @ -103,6 +103,11 @@ public class DatabaseHelper implements MangaManager, ChapterManager { | |||||||
|         return mMangaManager.getMangasWithUnread(); |         return mMangaManager.getMangasWithUnread(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @Override | ||||||
|  |     public Observable<List<Manga>> getFavoriteMangas() { | ||||||
|  |         return mMangaManager.getFavoriteMangas(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public Observable<List<Manga>> getManga(String url) { |     public Observable<List<Manga>> getManga(String url) { | ||||||
|         return mMangaManager.getManga(url); |         return mMangaManager.getManga(url); | ||||||
|  | |||||||
| @ -6,7 +6,6 @@ import java.util.ArrayList; | |||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import eu.kanade.mangafeed.data.caches.CacheManager; |  | ||||||
| import eu.kanade.mangafeed.sources.Batoto; | import eu.kanade.mangafeed.sources.Batoto; | ||||||
| import eu.kanade.mangafeed.sources.MangaHere; | import eu.kanade.mangafeed.sources.MangaHere; | ||||||
| import eu.kanade.mangafeed.sources.base.Source; | import eu.kanade.mangafeed.sources.base.Source; | ||||||
| @ -17,8 +16,6 @@ public class SourceManager { | |||||||
|     public static final int MANGAHERE = 2; |     public static final int MANGAHERE = 2; | ||||||
| 
 | 
 | ||||||
|     private HashMap<Integer, Source> mSourcesMap; |     private HashMap<Integer, Source> mSourcesMap; | ||||||
|     private NetworkHelper mNetworkHelper; |  | ||||||
|     private CacheManager mCacheManager; |  | ||||||
|     private Context context; |     private Context context; | ||||||
| 
 | 
 | ||||||
|     public SourceManager(Context context) { |     public SourceManager(Context context) { | ||||||
|  | |||||||
| @ -16,6 +16,8 @@ public interface MangaManager { | |||||||
| 
 | 
 | ||||||
|     Observable<List<Manga>> getMangasWithUnread(); |     Observable<List<Manga>> getMangasWithUnread(); | ||||||
| 
 | 
 | ||||||
|  |     Observable<List<Manga>> getFavoriteMangas(); | ||||||
|  | 
 | ||||||
|     Observable<List<Manga>> getManga(String url); |     Observable<List<Manga>> getManga(String url); | ||||||
| 
 | 
 | ||||||
|     Observable<List<Manga>> getManga(long id); |     Observable<List<Manga>> getManga(long id); | ||||||
|  | |||||||
| @ -55,6 +55,19 @@ public class MangaManagerImpl extends BaseManager implements MangaManager { | |||||||
|                 .createObservable(); |                 .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) { |     public Observable<List<Manga>> getManga(String url) { | ||||||
|         return db.get() |         return db.get() | ||||||
|                 .listOfObjects(Manga.class) |                 .listOfObjects(Manga.class) | ||||||
|  | |||||||
| @ -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; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -5,6 +5,7 @@ import android.app.Application; | |||||||
| import javax.inject.Singleton; | import javax.inject.Singleton; | ||||||
| 
 | 
 | ||||||
| import dagger.Component; | import dagger.Component; | ||||||
|  | import eu.kanade.mangafeed.data.services.LibraryUpdateService; | ||||||
| import eu.kanade.mangafeed.injection.module.AppModule; | import eu.kanade.mangafeed.injection.module.AppModule; | ||||||
| import eu.kanade.mangafeed.injection.module.DataModule; | import eu.kanade.mangafeed.injection.module.DataModule; | ||||||
| import eu.kanade.mangafeed.presenter.CataloguePresenter; | import eu.kanade.mangafeed.presenter.CataloguePresenter; | ||||||
| @ -40,6 +41,8 @@ public interface AppComponent { | |||||||
| 
 | 
 | ||||||
|     void inject(Source source); |     void inject(Source source); | ||||||
| 
 | 
 | ||||||
|  |     void inject(LibraryUpdateService libraryUpdateService); | ||||||
|  | 
 | ||||||
|     Application application(); |     Application application(); | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ import butterknife.ButterKnife; | |||||||
| import butterknife.OnItemClick; | import butterknife.OnItemClick; | ||||||
| import eu.kanade.mangafeed.R; | import eu.kanade.mangafeed.R; | ||||||
| import eu.kanade.mangafeed.data.models.Manga; | import eu.kanade.mangafeed.data.models.Manga; | ||||||
|  | import eu.kanade.mangafeed.data.services.LibraryUpdateService; | ||||||
| import eu.kanade.mangafeed.presenter.LibraryPresenter; | import eu.kanade.mangafeed.presenter.LibraryPresenter; | ||||||
| import eu.kanade.mangafeed.ui.activity.MainActivity; | import eu.kanade.mangafeed.ui.activity.MainActivity; | ||||||
| import eu.kanade.mangafeed.ui.activity.MangaDetailActivity; | import eu.kanade.mangafeed.ui.activity.MangaDetailActivity; | ||||||
| @ -68,6 +69,21 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter> { | |||||||
|         initializeSearch(menu); |         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) { |     private void initializeSearch(Menu menu) { | ||||||
|         final SearchView sv = (SearchView) menu.findItem(R.id.action_search).getActionView(); |         final SearchView sv = (SearchView) menu.findItem(R.id.action_search).getActionView(); | ||||||
|         sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { |         sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { | ||||||
|  | |||||||
| @ -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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -19,17 +19,14 @@ public class PostResult { | |||||||
|         this.numberOfRowsDeleted = numberOfRowsDeleted; |         this.numberOfRowsDeleted = numberOfRowsDeleted; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Nullable |  | ||||||
|     public Integer getNumberOfRowsUpdated() { |     public Integer getNumberOfRowsUpdated() { | ||||||
|         return numberOfRowsUpdated; |         return numberOfRowsUpdated; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Nullable |  | ||||||
|     public Integer getNumberOfRowsInserted() { |     public Integer getNumberOfRowsInserted() { | ||||||
|         return numberOfRowsInserted; |         return numberOfRowsInserted; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Nullable |  | ||||||
|     public Integer getNumberOfRowsDeleted() { |     public Integer getNumberOfRowsDeleted() { | ||||||
|         return numberOfRowsDeleted; |         return numberOfRowsDeleted; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -82,4 +82,10 @@ | |||||||
|     <string name="action_mark_as_unread">Mark as unread</string> |     <string name="action_mark_as_unread">Mark as unread</string> | ||||||
|     <string name="selected_chapters_title">Selected chapters: %1$d</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> | </resources> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 inorichi
						inorichi