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

This commit is contained in:
inorichi 2015-11-02 17:25:06 +01:00
parent faef785fc3
commit 04dfdba0b7
12 changed files with 301 additions and 6 deletions

View File

@ -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>

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -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)

View File

@ -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;
}
}
}

View File

@ -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();
} }

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
} }

View File

@ -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>