mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Refresh button in library is now looking for new chapters in sources and notifying the user
This commit is contained in:
		@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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 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();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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() {
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Integer getNumberOfRowsUpdated() {
 | 
			
		||||
        return numberOfRowsUpdated;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Integer getNumberOfRowsInserted() {
 | 
			
		||||
        return numberOfRowsInserted;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Integer getNumberOfRowsDeleted() {
 | 
			
		||||
        return numberOfRowsDeleted;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user