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
12 changed files with 301 additions and 6 deletions
|
@ -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>
|
||||||
|
|
Reference in a new issue