From 464ddf742b6dc9cee35e1ecd627e4dfba734efff Mon Sep 17 00:00:00 2001 From: Sean Mac Gillicuddy Date: Tue, 30 Apr 2019 12:21:06 +0100 Subject: [PATCH 01/20] Migrate DownloadFragment to kotlin and integrate with new Downloader --- app/build.gradle | 7 +- .../kiwix/kiwixmobile/KiwixApplication.java | 2 +- .../kiwixmobile/KiwixViewModelFactory.java | 57 +++ .../kiwix/kiwixmobile/base/BaseFragment.java | 32 -- .../kiwix/kiwixmobile/base/BaseFragment.kt | 26 ++ .../kiwixmobile/database/DownloadDao.java | 93 +++++ .../kiwixmobile/database/KiwixDatabase.java | 22 +- .../database/entity/DownloadsSpec.java | 27 ++ .../kiwix/kiwixmobile/di/ViewModelKey.java | 34 ++ .../di/components/ActivityComponent.kt | 43 +++ .../di/components/ApplicationComponent.java | 26 +- .../kiwixmobile/di/modules/ActivityModule.kt | 29 ++ .../di/modules/ApplicationModule.java | 27 +- .../di/modules/DownloaderModule.kt | 34 ++ .../kiwixmobile/di/modules/ViewModelModule.kt | 38 ++ .../kiwixmobile/downloader/DownloadAdapter.kt | 54 +++ .../downloader/DownloadFragment.java | 357 ------------------ .../downloader/DownloadFragment.kt | 107 ++++++ .../downloader/DownloadManagerRequester.kt | 92 +++++ .../downloader/DownloadRequester.kt | 29 ++ .../downloader/DownloadService.java | 210 +++++------ .../downloader/DownloadViewHolder.kt | 56 +++ .../kiwixmobile/downloader/Downloader.kt | 29 ++ .../kiwixmobile/downloader/DownloaderImpl.kt | 60 +++ .../downloader/OldDownloadAdapter.kt | 81 ++++ .../downloader/model/Base64String.kt | 17 + .../downloader/model/DownloadItem.kt | 42 +++ .../downloader/model/DownloadModel.kt | 36 ++ .../downloader/model/DownloadRequest.kt | 38 ++ .../downloader/model/DownloadStatus.kt | 96 +++++ .../kiwixmobile/downloader/model/Seconds.kt | 36 ++ .../extensions/CursorExtensions.kt | 43 +++ .../extensions/ViewGroupExtensions.kt | 28 ++ .../kiwixmobile/library/LibraryAdapter.java | 39 +- .../settings/KiwixSettingsActivity.java | 18 +- .../kiwixmobile/utils/AlertDialogShower.kt | 38 ++ .../kiwix/kiwixmobile/utils/DialogShower.kt | 25 ++ .../kiwix/kiwixmobile/utils/KiwixDialog.kt | 23 ++ .../zim_manager/ZimManageViewModel.kt | 81 ++++ .../ZimFileSelectFragment.java | 27 +- .../library_view/LibraryFragment.java | 137 +++---- .../library_view/LibraryPresenter.java | 17 +- app/src/main/res/layout/download_item.xml | 146 ++++--- .../main/res/layout/download_management.xml | 27 -- .../res/layout/layout_download_management.xml | 31 ++ 45 files changed, 1723 insertions(+), 794 deletions(-) create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/KiwixViewModelFactory.java delete mode 100644 app/src/main/java/org/kiwix/kiwixmobile/base/BaseFragment.java create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/base/BaseFragment.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/database/DownloadDao.java create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/database/entity/DownloadsSpec.java create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/di/ViewModelKey.java create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/di/components/ActivityComponent.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/di/modules/ActivityModule.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/di/modules/DownloaderModule.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/di/modules/ViewModelModule.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadAdapter.kt delete mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.java create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadManagerRequester.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadRequester.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadViewHolder.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/Downloader.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloaderImpl.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/OldDownloadAdapter.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/model/Base64String.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadItem.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadModel.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadRequest.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadStatus.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/downloader/model/Seconds.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/extensions/CursorExtensions.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/extensions/ViewGroupExtensions.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/utils/AlertDialogShower.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/utils/DialogShower.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt delete mode 100644 app/src/main/res/layout/download_management.xml create mode 100644 app/src/main/res/layout/layout_download_management.xml diff --git a/app/build.gradle b/app/build.gradle index 4b0f36a2b7..32ac7f20a9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,8 +14,8 @@ buildscript { } apply plugin: 'com.android.application' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' apply plugin: 'checkstyle' apply plugin: 'testdroid' @@ -140,6 +140,8 @@ dependencies { } } implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation "android.arch.lifecycle:extensions:1.1.1" } // Set custom app import directory @@ -368,6 +370,9 @@ android { } } */ + androidExtensions { + experimental = true + } } // Testdroid deployment configuration diff --git a/app/src/main/java/org/kiwix/kiwixmobile/KiwixApplication.java b/app/src/main/java/org/kiwix/kiwixmobile/KiwixApplication.java index e90fb6ac83..9173cec05a 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/KiwixApplication.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/KiwixApplication.java @@ -68,7 +68,7 @@ protected void attachBaseContext(Context base) { super.attachBaseContext(base); application = this; setApplicationComponent(DaggerApplicationComponent.builder() - .applicationModule(new ApplicationModule(this)) + .context(this) .build()); } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/KiwixViewModelFactory.java b/app/src/main/java/org/kiwix/kiwixmobile/KiwixViewModelFactory.java new file mode 100644 index 0000000000..e0afaa86df --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/KiwixViewModelFactory.java @@ -0,0 +1,57 @@ +package org.kiwix.kiwixmobile; + +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProvider; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; + +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +@Singleton +public class KiwixViewModelFactory implements ViewModelProvider.Factory { + private final Map, Provider> creators; + + @Inject + public KiwixViewModelFactory(Map, Provider> creators) { + this.creators = creators; + } + + @SuppressWarnings("unchecked") + @Override + public T create(Class modelClass) { + Provider creator = creators.get(modelClass); + if (creator == null) { + for (Map.Entry, Provider> entry : creators.entrySet()) { + if (modelClass.isAssignableFrom(entry.getKey())) { + creator = entry.getValue(); + break; + } + } + } + if (creator == null) { + throw new IllegalArgumentException("unknown model class " + modelClass); + } + try { + return (T) creator.get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/base/BaseFragment.java b/app/src/main/java/org/kiwix/kiwixmobile/base/BaseFragment.java deleted file mode 100644 index d5c18e1844..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/base/BaseFragment.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.kiwix.kiwixmobile.base; - -import android.app.Activity; -import android.content.Context; -import android.os.Build; -import android.support.v4.app.Fragment; - -import org.kiwix.kiwixmobile.KiwixApplication; - -/** - * All fragments should inherit from this fragment. - */ - -public abstract class BaseFragment extends Fragment { - - @Override - public void onAttach(Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - KiwixApplication.getApplicationComponent().inject(this); - } - super.onAttach(context); - } - - @SuppressWarnings("deprecation") - @Override - public void onAttach(Activity activity) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - KiwixApplication.getApplicationComponent().inject(this); - } - super.onAttach(activity); - } -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/base/BaseFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/base/BaseFragment.kt new file mode 100644 index 0000000000..3c582c2aaf --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/base/BaseFragment.kt @@ -0,0 +1,26 @@ +package org.kiwix.kiwixmobile.base + +import android.content.Context +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentActivity + +import org.kiwix.kiwixmobile.KiwixApplication +import org.kiwix.kiwixmobile.di.components.ActivityComponent + +/** + * All fragments should inherit from this fragment. + */ + +abstract class BaseFragment : Fragment() { + + override fun onAttach(context: Context?) { + super.onAttach(context) + inject( + KiwixApplication.getApplicationComponent().activityComponent() + .activity(activity as FragmentActivity) + .build() + ) + } + + abstract fun inject(activityComponent: ActivityComponent) +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/DownloadDao.java b/app/src/main/java/org/kiwix/kiwixmobile/database/DownloadDao.java new file mode 100644 index 0000000000..02abadb911 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/DownloadDao.java @@ -0,0 +1,93 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.database; + +import com.yahoo.squidb.data.SimpleDataChangedNotifier; +import com.yahoo.squidb.data.SquidCursor; +import com.yahoo.squidb.data.TableModel; +import com.yahoo.squidb.sql.Query; +import com.yahoo.squidb.sql.TableStatement; +import io.reactivex.Flowable; +import io.reactivex.processors.PublishProcessor; +import java.util.ArrayList; +import java.util.List; +import javax.inject.Inject; +import org.jetbrains.annotations.NotNull; +import org.kiwix.kiwixmobile.database.entity.DownloadDatabaseEntity; +import org.kiwix.kiwixmobile.downloader.model.DownloadModel; + +public class DownloadDao { + + private final KiwixDatabase kiwixDatabase; + private final PublishProcessor> downloadsProcessor = + PublishProcessor.create(); + + @Inject + public DownloadDao(KiwixDatabase kiwixDatabase) { + this.kiwixDatabase = kiwixDatabase; + kiwixDatabase.registerDataChangedNotifier( + new SimpleDataChangedNotifier(DownloadDatabaseEntity.TABLE) { + @Override + protected void onDataChanged() { + downloadsProcessor.onNext(getDownloads()); + } + }); + } + + public void insert(final DownloadModel downloadModel) { + kiwixDatabase.persistWithOnConflict(databaseEntity(downloadModel), + TableStatement.ConflictAlgorithm.REPLACE); + } + + public void delete(@NotNull Long... downloadIds) { + if (downloadIds.length > 0) { + kiwixDatabase.deleteWhere(DownloadDatabaseEntity.class, + DownloadDatabaseEntity.DOWNLOAD_ID.in((Object[]) downloadIds)); + } + } + + public Flowable> downloads() { + return downloadsProcessor.startWith(getDownloads()).distinctUntilChanged(); + } + + private TableModel databaseEntity(final DownloadModel downloadModel) { + return new DownloadDatabaseEntity() + .setDownloadId(downloadModel.getDownloadId()) + .setBookId(downloadModel.getBookId()) + .setFavIcon(downloadModel.getFavIcon()); + } + + private List getDownloads() { + return toList(kiwixDatabase.query(DownloadDatabaseEntity.class, Query.select())); + } + + private List toList(final SquidCursor cursor) { + final ArrayList downloadModels = new ArrayList<>(); + final DownloadDatabaseEntity downloadDatabaseEntity = new DownloadDatabaseEntity(); + while (cursor.moveToNext()) { + downloadDatabaseEntity.readPropertiesFromCursor(cursor); + downloadModels.add(new DownloadModel( + downloadDatabaseEntity.getDownloadId(), + downloadDatabaseEntity.getBookId(), + downloadDatabaseEntity.getFavIcon() + )); + } + cursor.close(); + return downloadModels; + } +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/KiwixDatabase.java b/app/src/main/java/org/kiwix/kiwixmobile/database/KiwixDatabase.java index e8be001e8a..eb4730edf9 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/database/KiwixDatabase.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/KiwixDatabase.java @@ -21,27 +21,24 @@ import android.content.Context; import android.util.Log; - import com.yahoo.squidb.data.SquidDatabase; import com.yahoo.squidb.data.adapter.SQLiteDatabaseWrapper; import com.yahoo.squidb.sql.Table; - -import org.kiwix.kiwixmobile.ZimContentProvider; -import org.kiwix.kiwixmobile.database.entity.BookDatabaseEntity; -import org.kiwix.kiwixmobile.database.entity.Bookmarks; -import org.kiwix.kiwixmobile.database.entity.LibraryDatabaseEntity; -import org.kiwix.kiwixmobile.database.entity.NetworkLanguageDatabaseEntity; -import org.kiwix.kiwixmobile.database.entity.RecentSearch; -import org.kiwix.kiwixmobile.utils.UpdateUtils; - import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; - import javax.inject.Inject; import javax.inject.Singleton; +import org.kiwix.kiwixmobile.ZimContentProvider; +import org.kiwix.kiwixmobile.database.entity.BookDatabaseEntity; +import org.kiwix.kiwixmobile.database.entity.Bookmarks; +import org.kiwix.kiwixmobile.database.entity.DownloadDatabaseEntity; +import org.kiwix.kiwixmobile.database.entity.LibraryDatabaseEntity; +import org.kiwix.kiwixmobile.database.entity.NetworkLanguageDatabaseEntity; +import org.kiwix.kiwixmobile.database.entity.RecentSearch; +import org.kiwix.kiwixmobile.utils.UpdateUtils; import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX; @@ -69,7 +66,8 @@ protected Table[] getTables() { LibraryDatabaseEntity.TABLE, RecentSearch.TABLE, Bookmarks.TABLE, - NetworkLanguageDatabaseEntity.TABLE + NetworkLanguageDatabaseEntity.TABLE, + DownloadDatabaseEntity.TABLE }; } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/entity/DownloadsSpec.java b/app/src/main/java/org/kiwix/kiwixmobile/database/entity/DownloadsSpec.java new file mode 100644 index 0000000000..b7851523a9 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/entity/DownloadsSpec.java @@ -0,0 +1,27 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.database.entity; + +import com.yahoo.squidb.annotations.TableModelSpec; + +@TableModelSpec(className = "DownloadDatabaseEntity", tableName = "downloads") +public class DownloadsSpec { + public Long downloadId; + public String bookId; + public String favIcon; +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/ViewModelKey.java b/app/src/main/java/org/kiwix/kiwixmobile/di/ViewModelKey.java new file mode 100644 index 0000000000..ea8e96c350 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/ViewModelKey.java @@ -0,0 +1,34 @@ +package org.kiwix.kiwixmobile.di; + +import android.arch.lifecycle.ViewModel; +import dagger.MapKey; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +@Documented +@Target({ ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@MapKey +public @interface ViewModelKey { + Class value(); +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/components/ActivityComponent.kt b/app/src/main/java/org/kiwix/kiwixmobile/di/components/ActivityComponent.kt new file mode 100644 index 0000000000..70350b1597 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/components/ActivityComponent.kt @@ -0,0 +1,43 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.di.components + +import android.app.Activity +import dagger.BindsInstance +import dagger.Subcomponent +import org.kiwix.kiwixmobile.di.modules.ActivityModule +import org.kiwix.kiwixmobile.downloader.DownloadFragment +import org.kiwix.kiwixmobile.zim_manager.fileselect_view.ZimFileSelectFragment +import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment + +@Subcomponent(modules = [ActivityModule::class]) +interface ActivityComponent { + fun inject(downloadFragment: DownloadFragment) + + fun inject(libraryFragment: LibraryFragment) + + fun inject(zimFileSelectFragment: ZimFileSelectFragment) + + @Subcomponent.Builder + interface Builder { + + @BindsInstance fun activity(activity: Activity): Builder + + fun build(): ActivityComponent + } +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/components/ApplicationComponent.java b/app/src/main/java/org/kiwix/kiwixmobile/di/components/ApplicationComponent.java index 333216a64e..ae57c7cadd 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/di/components/ApplicationComponent.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/components/ApplicationComponent.java @@ -17,9 +17,12 @@ */ package org.kiwix.kiwixmobile.di.components; +import android.content.Context; +import dagger.BindsInstance; +import dagger.Component; +import javax.inject.Singleton; import org.kiwix.kiwixmobile.KiwixApplication; import org.kiwix.kiwixmobile.ZimContentProvider; -import org.kiwix.kiwixmobile.base.BaseFragment; import org.kiwix.kiwixmobile.di.modules.ApplicationModule; import org.kiwix.kiwixmobile.di.modules.JNIModule; import org.kiwix.kiwixmobile.di.modules.NetworkModule; @@ -28,12 +31,6 @@ import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity; import org.kiwix.kiwixmobile.views.AutoCompleteAdapter; import org.kiwix.kiwixmobile.views.web.KiwixWebView; -import org.kiwix.kiwixmobile.zim_manager.fileselect_view.ZimFileSelectFragment; -import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment; - -import javax.inject.Singleton; - -import dagger.Component; @Singleton @Component(modules = { @@ -42,15 +39,20 @@ JNIModule.class, }) public interface ApplicationComponent { - void inject(KiwixApplication application); - void inject(DownloadService service); + @Component.Builder + interface Builder { + + @BindsInstance Builder context(Context context); - void inject(LibraryFragment libraryFragment); + ApplicationComponent build(); + } - void inject(BaseFragment baseFragment); + ActivityComponent.Builder activityComponent(); - void inject(ZimFileSelectFragment zimFileSelectFragment); + void inject(KiwixApplication application); + + void inject(DownloadService service); void inject(ZimContentProvider zimContentProvider); diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ActivityModule.kt b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ActivityModule.kt new file mode 100644 index 0000000000..a4216e46d8 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ActivityModule.kt @@ -0,0 +1,29 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.di.modules + +import dagger.Binds +import dagger.Module +import org.kiwix.kiwixmobile.utils.AlertDialogShower +import org.kiwix.kiwixmobile.utils.DialogShower + +@Module +abstract class ActivityModule { + @Binds + abstract fun bindDialogShower(alertDialogShower: AlertDialogShower): DialogShower +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java index 6ca032f547..ff009daad7 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java @@ -17,34 +17,29 @@ */ package org.kiwix.kiwixmobile.di.modules; +import android.app.DownloadManager; import android.app.NotificationManager; import android.content.Context; - -import org.kiwix.kiwixmobile.KiwixApplication; -import org.kiwix.kiwixmobile.utils.BookUtils; - -import javax.inject.Singleton; - import dagger.Module; import dagger.Provides; import dagger.android.AndroidInjectionModule; +import javax.inject.Singleton; +import org.kiwix.kiwixmobile.utils.BookUtils; -@Module(includes = {ActivityBindingModule.class, AndroidInjectionModule.class}) +@Module(includes = { + ActivityBindingModule.class, AndroidInjectionModule.class, DownloaderModule.class, + ViewModelModule.class +}) public class ApplicationModule { - private final KiwixApplication application; - - public ApplicationModule(KiwixApplication application) { - this.application = application; - } - - @Provides @Singleton Context provideApplicationContext() { - return this.application; - } @Provides @Singleton NotificationManager provideNotificationManager(Context context) { return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } + @Provides @Singleton DownloadManager provideDownloadManager(Context context) { + return (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); + } + @Provides @Singleton BookUtils provideBookUtils() { return new BookUtils(); diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/DownloaderModule.kt b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/DownloaderModule.kt new file mode 100644 index 0000000000..c96c92976a --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/DownloaderModule.kt @@ -0,0 +1,34 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.di.modules + +import dagger.Binds +import dagger.Module +import org.kiwix.kiwixmobile.downloader.DownloadManagerRequester +import org.kiwix.kiwixmobile.downloader.DownloadRequester +import org.kiwix.kiwixmobile.downloader.Downloader +import org.kiwix.kiwixmobile.downloader.DownloaderImpl + +@Module +abstract class DownloaderModule { + @Binds + abstract fun bindDownloader(downloaderImpl: DownloaderImpl): Downloader + + @Binds + abstract fun bindDownloaderRequester(downloaderImpl: DownloadManagerRequester): DownloadRequester +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ViewModelModule.kt b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ViewModelModule.kt new file mode 100644 index 0000000000..b349d0ef93 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ViewModelModule.kt @@ -0,0 +1,38 @@ +package org.kiwix.kiwixmobile.di.modules + +import android.arch.lifecycle.ViewModel +import android.arch.lifecycle.ViewModelProvider +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import org.kiwix.kiwixmobile.KiwixViewModelFactory +import org.kiwix.kiwixmobile.di.ViewModelKey +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel + +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +@Module +abstract class ViewModelModule { + @Binds + @IntoMap + @ViewModelKey(ZimManageViewModel::class) + internal abstract fun bindUserViewModel(userViewModel: ZimManageViewModel): ViewModel + + @Binds + internal abstract fun bindViewModelFactory(factory: KiwixViewModelFactory): ViewModelProvider.Factory +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadAdapter.kt new file mode 100644 index 0000000000..05bafef8f2 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadAdapter.kt @@ -0,0 +1,54 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.downloader + +import android.support.v7.widget.RecyclerView +import android.view.ViewGroup +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.downloader.model.DownloadItem +import org.kiwix.kiwixmobile.extensions.inflate + +class DownloadAdapter(val itemClickListener: (DownloadItem) -> Unit) : RecyclerView.Adapter() { + + init { + setHasStableIds(true) + } + + var itemList: List = mutableListOf() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun getItemId(position: Int) = itemList[position].downloadId + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = DownloadViewHolder(parent.inflate(R.layout.download_item, false)) + + override fun getItemCount() = itemList.size + + override fun onBindViewHolder( + holder: DownloadViewHolder, + position: Int + ) { + holder.bind(itemList[position], itemClickListener) + } +} + diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.java b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.java deleted file mode 100644 index db3594d1a7..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Kiwix Android - * Copyright (C) 2018 Kiwix - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.kiwix.kiwixmobile.downloader; - - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.database.DataSetObserver; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.Snackbar; -import android.support.v4.content.ContextCompat; -import android.util.Base64; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import org.kiwix.kiwixmobile.KiwixApplication; -import org.kiwix.kiwixmobile.KiwixMobileActivity; -import org.kiwix.kiwixmobile.R; -import org.kiwix.kiwixmobile.base.BaseFragment; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; -import org.kiwix.kiwixmobile.utils.NetworkUtils; -import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil; -import org.kiwix.kiwixmobile.utils.files.FileUtils; -import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity; -import org.kiwix.kiwixmobile.zim_manager.fileselect_view.ZimFileSelectFragment; -import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment; - -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Locale; - -import javax.inject.Inject; - -import static org.kiwix.kiwixmobile.downloader.ChunkUtils.getFileName; -import static org.kiwix.kiwixmobile.utils.StyleUtils.dialogStyle; - -public class DownloadFragment extends BaseFragment { - - public static LinkedHashMap mDownloads = new LinkedHashMap<>(); - public static LinkedHashMap mDownloadFiles = new LinkedHashMap<>(); - public RelativeLayout relLayout; - public ListView listView; - public static DownloadAdapter downloadAdapter; - private ZimManageActivity zimManageActivity; - CoordinatorLayout mainLayout; - private Activity faActivity; - private boolean hasArtificiallyPaused; - - @Inject - SharedPreferenceUtil sharedPreferenceUtil; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - faActivity = super.getActivity(); - relLayout = (RelativeLayout) inflater.inflate(R.layout.download_management, container, false); - - zimManageActivity = (ZimManageActivity) super.getActivity(); - listView = relLayout.findViewById(R.id.zim_downloader_list); - downloadAdapter = new DownloadAdapter(mDownloads); - downloadAdapter.registerDataSetObserver(this); - listView.setAdapter(downloadAdapter); - mainLayout = faActivity.findViewById(R.id.zim_manager_main_activity); - return relLayout; - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - updateNoDownloads(); - } - - private void updateNoDownloads() { - if (faActivity == null) { - return; - } - TextView noDownloadsText = faActivity.findViewById(R.id.download_management_no_downloads); - if (noDownloadsText == null) return; - if (listView.getCount() == 0) { - noDownloadsText.setVisibility(View.VISIBLE); - } else if (listView.getCount() > 0) { - noDownloadsText.setVisibility(View.GONE); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - downloadAdapter.unRegisterDataSetObserver(); - } - - public void showNoWiFiWarning(Context context, Runnable yesAction) { - new AlertDialog.Builder(context) - .setTitle(R.string.wifi_only_title) - .setMessage(R.string.wifi_only_msg) - .setPositiveButton(R.string.yes, (dialog, i) -> { - sharedPreferenceUtil.putPrefWifiOnly(false); - KiwixMobileActivity.wifiOnly = false; - yesAction.run(); - }) - .setNegativeButton(R.string.no, (dialog, i) -> {}) - .show(); - } - - public static String toHumanReadableTime(int seconds) { - final double MINUTES = 60; - final double HOURS = 60 * MINUTES; - final double DAYS = 24 * HOURS; - - if (Math.round(seconds / DAYS) > 0) - return String.format(Locale.getDefault(), "%d %s %s", Math.round(seconds / DAYS), - KiwixApplication.getInstance().getResources().getString(R.string.time_day), - KiwixApplication.getInstance().getResources().getString(R.string.time_left)); - if (Math.round(seconds / HOURS) > 0) - return String.format(Locale.getDefault(), "%d %s %s", Math.round(seconds / HOURS), - KiwixApplication.getInstance().getResources().getString(R.string.time_hour), - KiwixApplication.getInstance().getResources().getString(R.string.time_left)); - if (Math.round(seconds / MINUTES) > 0) - return String.format(Locale.getDefault(), "%d %s %s", Math.round(seconds / MINUTES), - KiwixApplication.getInstance().getResources().getString(R.string.time_minute), - KiwixApplication.getInstance().getResources().getString(R.string.time_left)); - return String.format(Locale.getDefault(), "%d %s %s", seconds, - KiwixApplication.getInstance().getResources().getString(R.string.time_second), - KiwixApplication.getInstance().getResources().getString(R.string.time_left)); - } - - public class DownloadAdapter extends BaseAdapter { - - private LinkedHashMap mData = new LinkedHashMap<>(); - private Integer[] mKeys; - private DataSetObserver dataSetObserver; - - public DownloadAdapter(LinkedHashMap data) { - mData = data; - mKeys = mData.keySet().toArray(new Integer[data.size()]); - } - - @Override - public int getCount() { - return mData.size(); - } - - @Override - public LibraryNetworkEntity.Book getItem(int position) { - return mData.get(mKeys[position]); - } - - @Override - public long getItemId(int arg0) { - return arg0; - } - - public void complete(int notificationID) { - if (!isAdded()) { - return; - } - int position = Arrays.asList(mKeys).indexOf(notificationID); - ViewGroup viewGroup = (ViewGroup) listView.getChildAt(position - listView.getFirstVisiblePosition()); - if (viewGroup == null) { - mDownloads.remove(mKeys[position]); - mDownloadFiles.remove(mKeys[position]); - downloadAdapter.notifyDataSetChanged(); - updateNoDownloads(); - } - ImageView pause = viewGroup.findViewById(R.id.pause); - pause.setEnabled(false); - String fileName = getFileName(mDownloadFiles.get(mKeys[position])); - { - Snackbar completeSnack = Snackbar.make(mainLayout, getResources().getString(R.string.download_complete_snackbar), Snackbar.LENGTH_LONG); - completeSnack.setAction(getResources().getString(R.string.open), v -> zimManageActivity.finishResult(fileName)).setActionTextColor(getResources().getColor(R.color.white)).show(); - } - ZimFileSelectFragment zimFileSelectFragment = (ZimFileSelectFragment) zimManageActivity.mSectionsPagerAdapter.getItem(0); - zimFileSelectFragment.addBook(fileName); - mDownloads.remove(mKeys[position]); - mDownloadFiles.remove(mKeys[position]); - downloadAdapter.notifyDataSetChanged(); - updateNoDownloads(); - } - - public void updateProgress(int progress, int notificationID) { - if (isAdded()) { - int position = Arrays.asList(mKeys).indexOf(notificationID); - ViewGroup viewGroup = (ViewGroup) listView.getChildAt(position - listView.getFirstVisiblePosition()); - if (viewGroup == null) { - return; - } - ProgressBar downloadProgress = viewGroup.findViewById(R.id.downloadProgress); - downloadProgress.setProgress(progress); - TextView timeRemaining = viewGroup.findViewById(R.id.time_remaining); - int secLeft = LibraryFragment.mService.timeRemaining.get(mKeys[position], -1); - if (secLeft != -1) - timeRemaining.setText(toHumanReadableTime(secLeft)); - } - } - - private void setPlayState(ImageView pauseButton, int position, int newPlayState) { - if (newPlayState == DownloadService.PLAY) { //Playing - if (LibraryFragment.mService.playDownload(mKeys[position])) - pauseButton.setImageDrawable(ContextCompat.getDrawable(getActivity(), R.drawable.ic_pause_black_24dp)); - } else { //Pausing - LibraryFragment.mService.pauseDownload(mKeys[position]); - pauseButton.setImageDrawable(ContextCompat.getDrawable(getActivity(), R.drawable.ic_play_arrow_black_24dp)); - } - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - // Get the data item for this position - // Check if an existing view is being reused, otherwise inflate the view - if (convertView == null) { - convertView = LayoutInflater.from(faActivity).inflate(R.layout.download_item, parent, false); - } - mKeys = mData.keySet().toArray(new Integer[mData.size()]); - // Lookup view for data population - //downloadProgress.setProgress(download.progress); - // Populate the data into the template view using the data object - TextView title = convertView.findViewById(R.id.title); - TextView description = convertView.findViewById(R.id.description); - TextView timeRemaining = convertView.findViewById(R.id.time_remaining); - ImageView imageView = convertView.findViewById(R.id.favicon); - title.setText(getItem(position).getTitle()); - description.setText(getItem(position).getDescription()); - imageView.setImageBitmap(StringToBitMap(getItem(position).getFavicon())); - - ProgressBar downloadProgress = convertView.findViewById(R.id.downloadProgress); - ImageView pause = convertView.findViewById(R.id.pause); - - if (LibraryFragment.mService.downloadStatus.get(mKeys[position]) == 0) { - downloadProgress.setProgress(0); - pause.setImageDrawable(ContextCompat.getDrawable(getActivity(), R.drawable.ic_pause_black_24dp)); - } else { - downloadProgress.setProgress(LibraryFragment.mService.downloadProgress.get(mKeys[position])); - if (LibraryFragment.mService.downloadStatus.get(mKeys[position]) == DownloadService.PAUSE) { - pause.setImageDrawable(ContextCompat.getDrawable(getActivity(), R.drawable.ic_play_arrow_black_24dp)); - } - if (LibraryFragment.mService.downloadStatus.get(mKeys[position]) == DownloadService.PLAY) { - pause.setImageDrawable(ContextCompat.getDrawable(getActivity(), R.drawable.ic_pause_black_24dp)); - } - } - - pause.setOnClickListener(v -> { - int newPlayPauseState = LibraryFragment.mService.downloadStatus.get(mKeys[position]) == DownloadService.PLAY ? DownloadService.PAUSE : DownloadService.PLAY; - - if (newPlayPauseState == DownloadService.PLAY && KiwixMobileActivity.wifiOnly && !NetworkUtils.isWiFi(getContext())) { - showNoWiFiWarning(getContext(), () -> { - setPlayState(pause, position, newPlayPauseState); - }); - return; - } - - timeRemaining.setText(""); - - setPlayState(pause, position, newPlayPauseState); - }); - - - ImageView stop = convertView.findViewById(R.id.stop); - stop.setOnClickListener(v -> { - hasArtificiallyPaused = LibraryFragment.mService.downloadStatus.get(mKeys[position]) == DownloadService.PLAY; - setPlayState(pause, position, DownloadService.PAUSE); - new AlertDialog.Builder(faActivity, dialogStyle()) - .setTitle(R.string.confirm_stop_download_title) - .setMessage(R.string.confirm_stop_download_msg) - .setPositiveButton(R.string.yes, (dialog, i) -> { - LibraryFragment.mService.stopDownload(mKeys[position]); - mDownloads.remove(mKeys[position]); - mDownloadFiles.remove(mKeys[position]); - downloadAdapter.notifyDataSetChanged(); - updateNoDownloads(); - if (zimManageActivity.mSectionsPagerAdapter.libraryFragment.libraryAdapter != null) { - zimManageActivity.mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(((ZimManageActivity) getActivity()).searchView.getQuery()); - } - }) - .setNegativeButton(R.string.no, (dialog, i) -> { - if (hasArtificiallyPaused) { - hasArtificiallyPaused = false; - setPlayState(pause, position, DownloadService.PLAY); - } - }) - .show(); - }); - - // Return the completed view to render on screen - return convertView; - } - - public void registerDataSetObserver(DownloadFragment downloadFragment) { - if (dataSetObserver == null) { - dataSetObserver = new DataSetObserver() { - @Override - public void onChanged() { - super.onChanged(); - downloadFragment.updateNoDownloads(); - } - - @Override - public void onInvalidated() { - super.onInvalidated(); - downloadFragment.updateNoDownloads(); - } - }; - - registerDataSetObserver(dataSetObserver); - } - } - - public void unRegisterDataSetObserver() { - if (dataSetObserver != null) { - unregisterDataSetObserver(dataSetObserver); - } - } - } - - public void addDownload(int position, LibraryNetworkEntity.Book book, String fileName) { - mDownloads.put(position, book); - mDownloadFiles.put(position, fileName); - downloadAdapter.notifyDataSetChanged(); - updateNoDownloads(); - } - - public Bitmap StringToBitMap(String encodedString) { - try { - byte[] encodeByte = Base64.decode(encodedString, Base64.DEFAULT); - return BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.length); - } catch (Exception e) { - e.getMessage(); - return null; - } - } - -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.kt new file mode 100644 index 0000000000..4f8e723f55 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.kt @@ -0,0 +1,107 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.downloader + +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProvider +import android.arch.lifecycle.ViewModelProviders +import android.content.Context +import android.os.Bundle +import android.support.v7.widget.LinearLayoutManager +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import kotlinx.android.synthetic.main.layout_download_management.download_management_no_downloads +import kotlinx.android.synthetic.main.layout_download_management.zim_downloader_list +import org.kiwix.kiwixmobile.KiwixMobileActivity +import org.kiwix.kiwixmobile.base.BaseFragment +import org.kiwix.kiwixmobile.di.components.ActivityComponent +import org.kiwix.kiwixmobile.downloader.model.DownloadItem +import org.kiwix.kiwixmobile.utils.DialogShower +import org.kiwix.kiwixmobile.utils.KiwixDialog +import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel +import javax.inject.Inject + +class DownloadFragment : BaseFragment() { + + @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject lateinit var dialogShower: DialogShower + @Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil + @Inject lateinit var downloader: Downloader + lateinit var zimManageViewModel: ZimManageViewModel + private val downloadAdapter = DownloadAdapter { + dialogShower.show(KiwixDialog.StopDownload, { downloader.cancelDownload(it) }) + } + + override fun inject(activityComponent: ActivityComponent) { + activityComponent.inject(this) + } + + override fun onAttach(context: Context?) { + super.onAttach(context) + zimManageViewModel = ViewModelProviders.of(activity!!, viewModelFactory) + .get(ZimManageViewModel::class.java) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = + inflater.inflate(org.kiwix.kiwixmobile.R.layout.layout_download_management, container, false) + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + zim_downloader_list.run { + adapter = downloadAdapter + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + setHasFixedSize(true) + } + zimManageViewModel.downloadItems.observe(this, Observer { + onDownloadItemsUpdate(it!!) + }) + } + + private fun onDownloadItemsUpdate(items: List) { + downloadAdapter.itemList = items + updateNoDownloads(items) + } + + private fun updateNoDownloads(downloadItems: List) { + download_management_no_downloads.visibility = + if (downloadItems.isEmpty()) + View.VISIBLE + else + View.GONE + } + + fun showNoWiFiWarning( + context: Context?, + yesAction: Runnable + ) { + dialogShower.show(KiwixDialog.NoWifi, { + sharedPreferenceUtil.putPrefWifiOnly(false) + KiwixMobileActivity.wifiOnly = false + yesAction.run() + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadManagerRequester.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadManagerRequester.kt new file mode 100644 index 0000000000..92e9ea90bc --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadManagerRequester.kt @@ -0,0 +1,92 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.downloader + +import android.app.DownloadManager +import android.app.DownloadManager.Request +import android.net.Uri +import android.os.Build +import org.kiwix.kiwixmobile.downloader.model.DownloadItem +import org.kiwix.kiwixmobile.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.downloader.model.DownloadRequest +import org.kiwix.kiwixmobile.downloader.model.DownloadStatus +import org.kiwix.kiwixmobile.extensions.forEachRow +import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.utils.StorageUtils +import java.io.File +import javax.inject.Inject + +class DownloadManagerRequester @Inject constructor( + private val downloadManager: DownloadManager, + private val sharedPreferenceUtil: SharedPreferenceUtil +) : DownloadRequester { + + override fun enqueue(downloadRequest: DownloadRequest) = + downloadManager.enqueue(downloadRequest.toDownloadManagerRequest(sharedPreferenceUtil)) + + override fun query(downloadModels: List): List { + val downloadStatuses = mutableListOf() + if (downloadModels.isNotEmpty()) { + downloadModels.forEach { model -> + downloadManager.query(model.toQuery()).forEachRow { + downloadStatuses.add(DownloadStatus(it, model)) + } + } + } + return downloadStatuses + } + + override fun cancel(downloadItem: DownloadItem) { + downloadManager.remove(downloadItem.downloadId) + } + + private fun DownloadRequest.toDownloadManagerRequest(sharedPreferenceUtil: SharedPreferenceUtil) = + Request(uri).apply { + setAllowedNetworkTypes( + if (sharedPreferenceUtil.prefWifiOnly) { + Request.NETWORK_WIFI + } else { + Request.NETWORK_MOBILE or Request.NETWORK_WIFI + } + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + setAllowedOverMetered(true) + } + setAllowedOverRoaming(true) + setTitle(title) + setDescription(description) + setDestinationUri(toDestinationUri(sharedPreferenceUtil)) + setNotificationVisibility(Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + setVisibleInDownloadsUi(true) + } + + private fun DownloadRequest.toDestinationUri(sharedPreferenceUtil: SharedPreferenceUtil) = + Uri.fromFile( + File( + "${sharedPreferenceUtil.prefStorage}/Kiwix/${ + StorageUtils.getFileNameFromUrl(uri.toString()) + }" + ) + ) + + private fun DownloadModel.toQuery() = + DownloadManager.Query().setFilterById(downloadId) + +} + + diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadRequester.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadRequester.kt new file mode 100644 index 0000000000..4749b6dbdb --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadRequester.kt @@ -0,0 +1,29 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.downloader + +import org.kiwix.kiwixmobile.downloader.model.DownloadItem +import org.kiwix.kiwixmobile.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.downloader.model.DownloadRequest +import org.kiwix.kiwixmobile.downloader.model.DownloadStatus + +interface DownloadRequester { + fun enqueue(downloadRequest: DownloadRequest): Long + fun query(downloadModels: List): List + fun cancel(downloadItem: DownloadItem) +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadService.java b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadService.java index d600fec5dd..c872f84441 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadService.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadService.java @@ -35,7 +35,20 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.widget.Toast; - +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; +import javax.inject.Inject; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okio.BufferedSource; import org.kiwix.kiwixmobile.KiwixApplication; import org.kiwix.kiwixmobile.KiwixMobileActivity; import org.kiwix.kiwixmobile.R; @@ -47,33 +60,11 @@ import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil; import org.kiwix.kiwixmobile.utils.StorageUtils; import org.kiwix.kiwixmobile.utils.TestingUtils; -import org.kiwix.kiwixmobile.utils.files.FileUtils; import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity; import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.concurrent.TimeUnit; - -import javax.inject.Inject; - -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.functions.Action; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okio.BufferedSource; - -import static org.kiwix.kiwixmobile.downloader.ChunkUtils.ALPHABET; -import static org.kiwix.kiwixmobile.downloader.ChunkUtils.ZIM_EXTENSION; import static org.kiwix.kiwixmobile.downloader.ChunkUtils.completeChunk; import static org.kiwix.kiwixmobile.downloader.ChunkUtils.completeDownload; -import static org.kiwix.kiwixmobile.downloader.ChunkUtils.completedChunk; import static org.kiwix.kiwixmobile.downloader.ChunkUtils.deleteAllParts; import static org.kiwix.kiwixmobile.downloader.ChunkUtils.getCurrentSize; import static org.kiwix.kiwixmobile.downloader.ChunkUtils.initialChunk; @@ -84,6 +75,7 @@ import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_ZIM_FILE; import static org.kiwix.kiwixmobile.utils.Constants.ONGOING_DOWNLOAD_CHANNEL_ID; +@Deprecated public class DownloadService extends Service { @Inject KiwixService kiwixService; @@ -163,7 +155,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } - SD_CARD = sharedPreferenceUtil.getPrefStorage(); KIWIX_ROOT = SD_CARD + "/Kiwix/"; @@ -185,7 +176,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { PendingIntent pendingIntent = PendingIntent.getActivity (getBaseContext(), notificationID, - target, PendingIntent.FLAG_CANCEL_CURRENT); + target, PendingIntent.FLAG_CANCEL_CURRENT); Intent pauseIntent = new Intent(this, this.getClass()).setAction(ACTION_PAUSE).putExtra(NOTIFICATION_ID, notificationID); Intent stopIntent = new Intent(this, this.getClass()).setAction(ACTION_STOP).putExtra(NOTIFICATION_ID, notificationID); @@ -223,11 +214,11 @@ public void stopDownload(int notificationID) { synchronized (pauseLock) { pauseLock.notify(); } - if (!DownloadFragment.mDownloads.isEmpty()) { - DownloadFragment.mDownloads.remove(notificationID); - DownloadFragment.mDownloadFiles.remove(notificationID); - DownloadFragment.downloadAdapter.notifyDataSetChanged(); - } + // if (!DownloadFragment.mDownloads.isEmpty()) { + // DownloadFragment.mDownloads.remove(notificationID); + // DownloadFragment.mDownloadFiles.remove(notificationID); + // DownloadFragment.downloadAdapter.notifyDataSetChanged(); + // } updateForeground(); notificationManager.cancel(notificationID); } @@ -263,14 +254,14 @@ public void toggleDownload (int notificationID) { public void pauseDownload(int notificationID) { Log.i(KIWIX_TAG, "Pausing ZIM Download for notificationID: " + notificationID); downloadStatus.put(notificationID, PAUSE); - notification.get(notificationID).mActions.get(0).title = getString(R.string.download_play); - notification.get(notificationID).mActions.get(0).icon = R.drawable.ic_play_arrow_black_24dp; + // notification.get(notificationID).mActions.get(0).title = getString(R.string.download_play); + // notification.get(notificationID).mActions.get(0).icon = R.drawable.ic_play_arrow_black_24dp; notification.get(notificationID).setContentText(getString(R.string.download_paused)); notificationManager.notify(notificationID, notification.get(notificationID).build()); - if (DownloadFragment.downloadAdapter != null) { - DownloadFragment.downloadAdapter.notifyDataSetChanged(); - downloadFragment.listView.invalidateViews(); - } + // if (DownloadFragment.downloadAdapter != null) { + // DownloadFragment.downloadAdapter.notifyDataSetChanged(); + // downloadFragment.listView.invalidateViews(); + // } } public boolean playDownload(int notificationID) { @@ -279,23 +270,23 @@ public boolean playDownload(int notificationID) { synchronized (pauseLock) { pauseLock.notify(); } - notification.get(notificationID).mActions.get(0).title = getString(R.string.download_pause); - notification.get(notificationID).mActions.get(0).icon = R.drawable.ic_pause_black_24dp; + // notification.get(notificationID).mActions.get(0).title = getString(R.string.download_pause); + // notification.get(notificationID).mActions.get(0).icon = R.drawable.ic_pause_black_24dp; notification.get(notificationID).setContentText(""); notificationManager.notify(notificationID, notification.get(notificationID).build()); - if (DownloadFragment.downloadAdapter != null) { - DownloadFragment.downloadAdapter.notifyDataSetChanged(); - downloadFragment.listView.invalidateViews(); - } + // if (DownloadFragment.downloadAdapter != null) { + // DownloadFragment.downloadAdapter.notifyDataSetChanged(); + // downloadFragment.listView.invalidateViews(); + // } return true; } private void downloadBook(String url, int notificationID, LibraryNetworkEntity.Book book) { - if (downloadFragment != null) { - downloadFragment.addDownload(notificationID, book, - KIWIX_ROOT + StorageUtils.getFileNameFromUrl(book.getUrl())); - } + //if (downloadFragment != null) { + // downloadFragment.addDownload(notificationID, book, + // KIWIX_ROOT + StorageUtils.getFileNameFromUrl(book.getUrl())); + //} TestingUtils.bindResource(DownloadService.class); if (book.file != null && isPresent(book.file.getPath())) { // Calculate initial download progress @@ -311,66 +302,67 @@ private void downloadBook(String url, int notificationID, LibraryNetworkEntity.B .flatMap(pair -> Observable.fromIterable(ChunkUtils.getChunks(pair.first, pair.second, notificationID))) .concatMap(this::downloadChunk) .distinctUntilChanged().doOnComplete(() -> updateDownloadFragmentComplete(notificationID, book)).doOnComplete(() -> { - notification.get(notificationID).setOngoing(false); - notification.get(notificationID).setContentTitle(notificationTitle + " " + getResources().getString(R.string.zim_file_downloaded)); - notification.get(notificationID).setContentText(getString(R.string.zim_file_downloaded)); - final Intent target = new Intent(this, KiwixMobileActivity.class); - target.putExtra(EXTRA_ZIM_FILE, KIWIX_ROOT + StorageUtils.getFileNameFromUrl(book.getUrl())); - File filec = book.file; - completeDownload(filec); - target.putExtra(EXTRA_NOTIFICATION_ID, notificationID); - PendingIntent pendingIntent = PendingIntent.getActivity - (getBaseContext(), 0, - target, PendingIntent.FLAG_CANCEL_CURRENT); - book.downloaded = true; - bookDao.deleteBook(book.id); - notification.get(notificationID).setContentIntent(pendingIntent); - notification.get(notificationID).mActions.clear(); - TestingUtils.unbindResource(DownloadService.class); - notification.get(notificationID).setProgress(100, 100, false); - notificationManager.notify(notificationID, notification.get(notificationID).build()); - updateForeground(); - updateDownloadFragmentProgress(100, notificationID, book); - stopSelf(); - }).subscribe(progress -> { - notification.get(notificationID).setProgress(100, progress, false); - if (progress != 100 && timeRemaining.get(notificationID) != -1) - notification.get(notificationID).setContentText(DownloadFragment.toHumanReadableTime(timeRemaining.get(notificationID))); - notificationManager.notify(notificationID, notification.get(notificationID).build()); - if (progress == 0 || progress == 100) { - // Tells android to not kill the service - updateForeground(); - } - updateDownloadFragmentProgress(progress, notificationID, book); - }, Throwable::printStackTrace); + notification.get(notificationID).setOngoing(false); + notification.get(notificationID).setContentTitle(notificationTitle + " " + getResources().getString(R.string.zim_file_downloaded)); + notification.get(notificationID).setContentText(getString(R.string.zim_file_downloaded)); + final Intent target = new Intent(this, KiwixMobileActivity.class); + target.putExtra(EXTRA_ZIM_FILE, KIWIX_ROOT + StorageUtils.getFileNameFromUrl(book.getUrl())); + File filec = book.file; + completeDownload(filec); + target.putExtra(EXTRA_NOTIFICATION_ID, notificationID); + PendingIntent pendingIntent = PendingIntent.getActivity + (getBaseContext(), 0, + target, PendingIntent.FLAG_CANCEL_CURRENT); + book.downloaded = true; + bookDao.deleteBook(book.id); + notification.get(notificationID).setContentIntent(pendingIntent); + // notification.get(notificationID).mActions.clear(); + TestingUtils.unbindResource(DownloadService.class); + notification.get(notificationID).setProgress(100, 100, false); + notificationManager.notify(notificationID, notification.get(notificationID).build()); + updateForeground(); + updateDownloadFragmentProgress(100, notificationID, book); + stopSelf(); + }).subscribe(progress -> { + notification.get(notificationID).setProgress(100, progress, false); + if (progress != 100 && timeRemaining.get(notificationID) != -1) { + // notification.get(notificationID).setContentText(DownloadFragment.toHumanReadableTime(timeRemaining.get(notificationID))); + } + notificationManager.notify(notificationID, notification.get(notificationID).build()); + if (progress == 0 || progress == 100) { + // Tells android to not kill the service + updateForeground(); + } + updateDownloadFragmentProgress(progress, notificationID, book); + }, Throwable::printStackTrace); } private void updateDownloadFragmentProgress(int progress, int notificationID, LibraryNetworkEntity.Book book) { - if (DownloadFragment.mDownloads != null) { - if (DownloadFragment.mDownloads.get(notificationID) != null) { - handler.post(() -> { - if (DownloadFragment.mDownloads.get(notificationID) != null) { - DownloadFragment.downloadAdapter.updateProgress(progress, notificationID); - } - }); - } else { - DownloadFragment.mDownloads.put(notificationID, book); - } - } + // if (DownloadFragment.mDownloads != null) { + // if (DownloadFragment.mDownloads.get(notificationID) != null) { + // handler.post(() -> { + // if (DownloadFragment.mDownloads.get(notificationID) != null) { + // DownloadFragment.downloadAdapter.updateProgress(progress, notificationID); + // } + // }); + // } else { + // DownloadFragment.mDownloads.put(notificationID, book); + // } + // } } private void updateDownloadFragmentComplete(int notificationID, LibraryNetworkEntity.Book book) { - if (DownloadFragment.mDownloads != null) { - if (DownloadFragment.mDownloads.get(notificationID) != null) { - handler.post(() -> { - if (DownloadFragment.mDownloads.get(notificationID) != null) { - DownloadFragment.downloadAdapter.complete(notificationID); - } - }); - } else { - DownloadFragment.mDownloads.put(notificationID, book); - } - } + // if (DownloadFragment.mDownloads != null) { + // if (DownloadFragment.mDownloads.get(notificationID) != null) { + // handler.post(() -> { + // if (DownloadFragment.mDownloads.get(notificationID) != null) { + // DownloadFragment.downloadAdapter.complete(notificationID); + // } + // }); + // } else { + // DownloadFragment.mDownloads.put(notificationID, book); + // } + // } } private void updateForeground() { @@ -449,13 +441,13 @@ private Observable downloadChunk(Chunk chunk) { downloaded += output.length(); if (chunk.getStartByte() == 0) { - if (!DownloadFragment.mDownloads.isEmpty()) { - LibraryNetworkEntity.Book book = DownloadFragment.mDownloads - .get(chunk.getNotificationID()); - book.remoteUrl = book.getUrl(); - book.file = fullFile; - bookDao.saveBook(book); - } + // if (!DownloadFragment.mDownloads.isEmpty()) { + // LibraryNetworkEntity.Book book = DownloadFragment.mDownloads + // .get(chunk.getNotificationID()); + // book.remoteUrl = book.getUrl(); + // book.file = fullFile; + // bookDao.saveBook(book); + // } downloadStatus.put(chunk.getNotificationID(), PLAY); downloadProgress.put(chunk.getNotificationID(), 0); } @@ -499,7 +491,7 @@ private Observable downloadChunk(Chunk chunk) { } if (KiwixMobileActivity.wifiOnly && !NetworkUtils.isWiFi(getApplicationContext()) || - !NetworkUtils.isNetworkAvailable(getApplicationContext())) { + !NetworkUtils.isNetworkAvailable(getApplicationContext())) { pauseDownload(chunk.getNotificationID()); } @@ -513,7 +505,6 @@ private Observable downloadChunk(Chunk chunk) { lastTime = System.currentTimeMillis(); lastSize = downloaded; - } catch (InterruptedException e) { // Happens if someone interrupts your thread. } @@ -607,5 +598,4 @@ public DownloadService getService() { public IBinder onBind(Intent intent) { return mBinder; } - } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadViewHolder.kt new file mode 100644 index 0000000000..8030574e73 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadViewHolder.kt @@ -0,0 +1,56 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.downloader + +import android.support.v7.widget.RecyclerView +import android.view.View +import android.widget.ImageView +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.download_item.description +import kotlinx.android.synthetic.main.download_item.downloadProgress +import kotlinx.android.synthetic.main.download_item.favicon +import kotlinx.android.synthetic.main.download_item.stop +import kotlinx.android.synthetic.main.download_item.title +import org.kiwix.kiwixmobile.downloader.model.Base64String +import org.kiwix.kiwixmobile.downloader.model.DownloadItem + +class DownloadViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), + LayoutContainer { + fun bind( + downloadItem: DownloadItem, + itemClickListener: (DownloadItem) -> Unit + ) { + favicon.setBitmap(downloadItem.favIcon) + title.text = downloadItem.title + description.text = downloadItem.description + downloadProgress.progress = downloadItem.progress + stop.setOnClickListener { + itemClickListener.invoke(downloadItem) + } + } + + private fun ImageView.setBitmap(base64String: Base64String) { + if (tag != base64String) { + base64String.toBitmap() + ?.let { + setImageBitmap(it) + tag = base64String + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/Downloader.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/Downloader.kt new file mode 100644 index 0000000000..3ea153053b --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/Downloader.kt @@ -0,0 +1,29 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.downloader + +import org.kiwix.kiwixmobile.downloader.model.DownloadItem +import org.kiwix.kiwixmobile.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.downloader.model.DownloadStatus +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity + +interface Downloader { + fun download(book: LibraryNetworkEntity.Book) + fun queryStatus(downloadModels: List): List + fun cancelDownload(downloadItem: DownloadItem) +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloaderImpl.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloaderImpl.kt new file mode 100644 index 0000000000..9ecfc25164 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloaderImpl.kt @@ -0,0 +1,60 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.downloader + +import io.reactivex.schedulers.Schedulers +import org.kiwix.kiwixmobile.database.DownloadDao +import org.kiwix.kiwixmobile.downloader.model.DownloadItem +import org.kiwix.kiwixmobile.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.downloader.model.DownloadRequest +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity +import org.kiwix.kiwixmobile.network.KiwixService +import javax.inject.Inject + +class DownloaderImpl @Inject constructor( + private val downloadRequester: DownloadRequester, + private val downloadDao: DownloadDao, + private val kiwixService: KiwixService +) : Downloader { + + override fun download(book: LibraryNetworkEntity.Book) { + kiwixService.getMetaLinks(book.url) + .subscribeOn(Schedulers.io()) + .take(1) + .subscribe( + { + val downloadId = downloadRequester.enqueue( + DownloadRequest(it, book) + ) + downloadDao.insert( + DownloadModel(downloadId, book) + ) + }, + Throwable::printStackTrace + ) + } + + override fun queryStatus(downloadModels: List) = + downloadRequester.query(downloadModels) + .sortedBy { it.downloadId } + + override fun cancelDownload(downloadItem: DownloadItem) { + downloadRequester.cancel(downloadItem) + downloadDao.delete(downloadItem.downloadId) + } +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/OldDownloadAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/OldDownloadAdapter.kt new file mode 100644 index 0000000000..4cbfe76949 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/OldDownloadAdapter.kt @@ -0,0 +1,81 @@ +package org.kiwix.kiwixmobile.downloader + +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import org.kiwix.kiwixmobile.R.id +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book + +class OldDownloadAdapter( + data: LinkedHashMap, + private val downloadFragment: DownloadFragment +) { + + private var mData = LinkedHashMap() + private var mKeys: Array? = null + + init { + mData = data + mKeys = mData.keys.toTypedArray() + } + + fun complete(notificationID: Int) { + /* val fileName = getFileName(mDownloadFiles[mKeys!![position]]) + run { + val completeSnack = Snackbar.make(view!!, resources.getString(R.string.download_complete_snackbar), Snackbar.LENGTH_LONG) + completeSnack.setAction(resources.getString(R.string.open)) { v -> zimManageActivity!!.finishResult(fileName) }.setActionTextColor(resources.getColor(R.color.white)).show() + } + val zimFileSelectFragment = zimManageActivity!!.mSectionsPagerAdapter.getItem(0) as ZimFileSelectFragment + zimFileSelectFragment.addBook(fileName) + */ + } + + fun updateProgress( + progress: Int, + notificationID: Int + ) { + if (downloadFragment.isAdded) { +// val position = Arrays.asList(*mKeys!!).indexOf(notificationID) +// val viewGroup = listView.getChildAt(position - listView.firstVisiblePosition) as ViewGroup +// ?: return +// val timeRemaining = viewGroup.findViewById(R.id.time_remaining) +// val secLeft = LibraryFragment.mService.timeRemaining.get(mKeys!![position], -1) +// if (secLeft != -1) +// timeRemaining.text = toHumanReadableTime(secLeft) + } + } + + fun getView( + position: Int, + convertView: View?, + parent: ViewGroup + ): View { + var convertView = convertView + // Get the data item for this position + // Check if an existing view is being reused, otherwise inflate the view + if (convertView == null) { +// convertView = LayoutInflater.from(faActivity).inflate(R.layout.download_item, parent, false) + } + mKeys = mData.keys.toTypedArray() +// +// pause.setOnClickListener { v -> +// val newPlayPauseState = if (LibraryFragment.mService.downloadStatus.get(mKeys!![position]) == DownloadService.PLAY) DownloadService.PAUSE else DownloadService.PLAY +// +// if (newPlayPauseState == DownloadService.PLAY && KiwixMobileActivity.wifiOnly && !NetworkUtils.isWiFi(context!!)) { +// showNoWiFiWarning(context, Runnable { setPlayState(pause, position, newPlayPauseState) }) +// return@setOnClickListener +// } +// +// timeRemaining.text = "" +// +// setPlayState(pause, position, newPlayPauseState) +// } + + +// } + + // Return the completed view to render on screen + return convertView!! + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/Base64String.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/Base64String.kt new file mode 100644 index 0000000000..7f26c92870 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/Base64String.kt @@ -0,0 +1,17 @@ +package org.kiwix.kiwixmobile.downloader.model + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.util.Base64 + +inline class Base64String(private val encodedString: String) { + fun toBitmap(): Bitmap? = try { + Base64.decode(encodedString, Base64.DEFAULT) + .let { + BitmapFactory.decodeByteArray(it, 0, it.size) + } + } catch (illegalArgumentException: IllegalArgumentException) { + null + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadItem.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadItem.kt new file mode 100644 index 0000000000..ec6ea85b13 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadItem.kt @@ -0,0 +1,42 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.downloader.model + +data class DownloadItem( + val downloadId: Long, + val favIcon: Base64String, + val title: String, + val description: String, + val bytesDownloaded: Long, + val totalSizeBytes: Long, + val downloadState: DownloadState, + val reason: String +) { + val progress get() = ((bytesDownloaded.toFloat() / totalSizeBytes) * 100).toInt() + + constructor(downloadStatus: DownloadStatus) : this( + downloadStatus.downloadId, + Base64String(downloadStatus.favIcon), + downloadStatus.title, + downloadStatus.description, + downloadStatus.bytesDownloadedSoFar, + downloadStatus.totalSizeBytes, + downloadStatus.state, + downloadStatus.reason + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadModel.kt new file mode 100644 index 0000000000..6c99fab7ca --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadModel.kt @@ -0,0 +1,36 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.downloader.model + +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity + +data class DownloadModel( + val downloadId: Long, + val bookId: String, + val favIcon: String +) { + + constructor( + downloadId: Long, + book: LibraryNetworkEntity.Book + ) : this( + downloadId, + book.id, + book.favicon + ) +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadRequest.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadRequest.kt new file mode 100644 index 0000000000..d188e27181 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadRequest.kt @@ -0,0 +1,38 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.downloader.model + +import android.net.Uri +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity +import org.kiwix.kiwixmobile.library.entity.MetaLinkNetworkEntity + +data class DownloadRequest( + val uri: Uri, + val title: String, + val description: String +) { + + constructor( + metaLinkNetworkEntity: MetaLinkNetworkEntity, + book: LibraryNetworkEntity.Book + ) : this( + Uri.parse(metaLinkNetworkEntity.relevantUrl.value), + book.title, + book.description + ) +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadStatus.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadStatus.kt new file mode 100644 index 0000000000..93edfecd12 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadStatus.kt @@ -0,0 +1,96 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.downloader.model + +import android.app.DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR +import android.app.DownloadManager.COLUMN_DESCRIPTION +import android.app.DownloadManager.COLUMN_ID +import android.app.DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP +import android.app.DownloadManager.COLUMN_LOCAL_URI +import android.app.DownloadManager.COLUMN_MEDIAPROVIDER_URI +import android.app.DownloadManager.COLUMN_MEDIA_TYPE +import android.app.DownloadManager.COLUMN_REASON +import android.app.DownloadManager.COLUMN_STATUS +import android.app.DownloadManager.COLUMN_TITLE +import android.app.DownloadManager.COLUMN_TOTAL_SIZE_BYTES +import android.app.DownloadManager.COLUMN_URI +import android.app.DownloadManager.STATUS_FAILED +import android.app.DownloadManager.STATUS_PAUSED +import android.app.DownloadManager.STATUS_PENDING +import android.app.DownloadManager.STATUS_RUNNING +import android.app.DownloadManager.STATUS_SUCCESSFUL +import android.database.Cursor +import org.kiwix.kiwixmobile.extensions.get + +class DownloadStatus( + val downloadId: Long, + val title: String, + val description: String, + val state: DownloadState, + val reason: String, + val bytesDownloadedSoFar: Long, + val totalSizeBytes: Long, + val lastModified: String, + val localUri: String?, + val mediaProviderUri: String?, + val mediaType: String?, + val uri: String?, + val favIcon: String +) { + constructor( + cursor: Cursor, + downloadModel: DownloadModel + ) : this( + cursor[COLUMN_ID], + cursor[COLUMN_TITLE], + cursor[COLUMN_DESCRIPTION], + DownloadState.from(cursor[COLUMN_STATUS]), + cursor[COLUMN_REASON], + cursor[COLUMN_BYTES_DOWNLOADED_SO_FAR], + cursor[COLUMN_TOTAL_SIZE_BYTES], + cursor[COLUMN_LAST_MODIFIED_TIMESTAMP], + cursor[COLUMN_LOCAL_URI], + cursor[COLUMN_MEDIAPROVIDER_URI], + cursor[COLUMN_MEDIA_TYPE], + cursor[COLUMN_URI], + downloadModel.favIcon + ) +} + +sealed class DownloadState { + companion object { + fun from(status: Int) = when (status) { + STATUS_FAILED -> Failed + STATUS_PAUSED -> Paused + STATUS_PENDING -> Pending + STATUS_RUNNING -> Running + STATUS_SUCCESSFUL -> Successful + else -> throw RuntimeException("invalid status $status") + } + } + + object Paused : DownloadState() + object Failed : DownloadState() + object Pending : DownloadState() + object Running : DownloadState() + object Successful : DownloadState() + + override fun toString(): String { + return javaClass.simpleName + } +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/Seconds.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/Seconds.kt new file mode 100644 index 0000000000..611b295c6a --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/Seconds.kt @@ -0,0 +1,36 @@ +package org.kiwix.kiwixmobile.downloader.model + +import org.kiwix.kiwixmobile.KiwixApplication +import java.util.Locale + +inline class Seconds(private val seconds: Int) { + fun toHumanReadableTime(): String { + val MINUTES = 60.0 + val HOURS = 60 * MINUTES + val DAYS = 24 * HOURS + + val context = KiwixApplication.getInstance() + return when { + Math.round(seconds / DAYS) > 0 -> String.format( + Locale.getDefault(), "%d %s %s", Math.round(seconds / DAYS), + context.getString(org.kiwix.kiwixmobile.R.string.time_day), + context.getString(org.kiwix.kiwixmobile.R.string.time_left) + ) + Math.round(seconds / HOURS) > 0 -> String.format( + Locale.getDefault(), "%d %s %s", Math.round(seconds / HOURS), + context.getString(org.kiwix.kiwixmobile.R.string.time_hour), + context.getString(org.kiwix.kiwixmobile.R.string.time_left) + ) + (Math.round(seconds / MINUTES) > 0) -> String.format( + Locale.getDefault(), "%d %s %s", Math.round(seconds / MINUTES), + context.getString(org.kiwix.kiwixmobile.R.string.time_minute), + context.getString(org.kiwix.kiwixmobile.R.string.time_left) + ) + else -> String.format( + Locale.getDefault(), "%d %s %s", seconds, + context.getString(org.kiwix.kiwixmobile.R.string.time_second), + context.getString(org.kiwix.kiwixmobile.R.string.time_left) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/extensions/CursorExtensions.kt b/app/src/main/java/org/kiwix/kiwixmobile/extensions/CursorExtensions.kt new file mode 100644 index 0000000000..baec591443 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/CursorExtensions.kt @@ -0,0 +1,43 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.extensions + +import android.database.Cursor + +inline fun Cursor.forEachRow(block: (Cursor) -> Unit) { + while (moveToNext()) { + block.invoke(this) + } + close() +} + +@Suppress("IMPLICIT_CAST_TO_ANY") +inline operator fun Cursor.get(columnName: String): T = + when (T::class) { + String::class -> getString(columnIndex(columnName)) + Long::class -> getLong(columnIndex(columnName)) + Integer::class -> getInt(columnIndex(columnName)) + else -> throw RuntimeException("Unexpected return type ${T::class.java.simpleName}") + } as T + +fun Cursor.columnIndex(columnName: String) = + if (columnNames.contains(columnName)) { + getColumnIndex(columnName) + } else { + throw RuntimeException("$columnName not found in $columnNames") + } \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/extensions/ViewGroupExtensions.kt b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ViewGroupExtensions.kt new file mode 100644 index 0000000000..3bf9a7580d --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ViewGroupExtensions.kt @@ -0,0 +1,28 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.extensions + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup + +fun ViewGroup.inflate( + layoutId: Int, + attachToRoot: Boolean = true +): View = + LayoutInflater.from(context).inflate(layoutId, this, attachToRoot) \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java b/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java index 6aaa6c7b5e..14db754bd9 100755 --- a/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java @@ -30,18 +30,12 @@ import android.widget.Filter; import android.widget.ImageView; import android.widget.TextView; - import com.google.common.collect.ImmutableList; - -import org.kiwix.kiwixmobile.KiwixApplication; -import org.kiwix.kiwixmobile.R; -import org.kiwix.kiwixmobile.database.BookDao; -import org.kiwix.kiwixmobile.database.NetworkLanguageDao; -import org.kiwix.kiwixmobile.downloader.DownloadFragment; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book; -import org.kiwix.kiwixmobile.utils.BookUtils; -import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment; - +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; @@ -52,14 +46,14 @@ import java.util.Locale; import java.util.Map; import java.util.Set; - import javax.inject.Inject; - -import io.reactivex.Completable; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; +import org.kiwix.kiwixmobile.KiwixApplication; +import org.kiwix.kiwixmobile.R; +import org.kiwix.kiwixmobile.database.BookDao; +import org.kiwix.kiwixmobile.database.NetworkLanguageDao; +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book; +import org.kiwix.kiwixmobile.utils.BookUtils; +import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment; import static org.kiwix.kiwixmobile.utils.NetworkUtils.parseURL; @@ -81,6 +75,7 @@ public class LibraryAdapter extends BaseAdapter { @Inject BookDao bookDao; + //TODO: restore functionality of commented out code public LibraryAdapter(Context context) { super(); KiwixApplication.getApplicationComponent().inject(this); @@ -244,7 +239,7 @@ protected FilterResults performFiltering(CharSequence s) { List selectedLanguages = Observable.fromIterable(allBooks) .filter(LibraryAdapter.this::languageActive) .filter(book -> !books.contains(book)) - .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) + // .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) .filter(book -> !LibraryFragment.downloadingBooks.contains(book)) .filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694 .toList() @@ -253,7 +248,7 @@ protected FilterResults performFiltering(CharSequence s) { List unselectedLanguages = Observable.fromIterable(allBooks) .filter(book -> !languageActive(book)) .filter(book -> !books.contains(book)) - .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) + // .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) .filter(book -> !LibraryFragment.downloadingBooks.contains(book)) .filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694 .toList() @@ -267,7 +262,7 @@ protected FilterResults performFiltering(CharSequence s) { List selectedLanguages = Observable.fromIterable(allBooks) .filter(LibraryAdapter.this::languageActive) .filter(book -> !books.contains(book)) - .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) + // .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) .filter(book -> !LibraryFragment.downloadingBooks.contains(book)) .filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694 .flatMap(book -> getMatches(book, s.toString())) @@ -279,7 +274,7 @@ protected FilterResults performFiltering(CharSequence s) { List unselectedLanguages = Observable.fromIterable(allBooks) .filter(book -> !languageActive(book)) .filter(book -> !books.contains(book)) - .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) + // .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) .filter(book -> !LibraryFragment.downloadingBooks.contains(book)) .filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694 .flatMap(book -> getMatches(book, s.toString())) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/settings/KiwixSettingsActivity.java b/app/src/main/java/org/kiwix/kiwixmobile/settings/KiwixSettingsActivity.java index 3c2d04dc6f..24d6bf1067 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/settings/KiwixSettingsActivity.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/settings/KiwixSettingsActivity.java @@ -36,7 +36,13 @@ import android.webkit.WebView; import android.widget.BaseAdapter; import android.widget.Toast; - +import eu.mhutti1.utils.storage.StorageDevice; +import eu.mhutti1.utils.storage.StorageSelectDialog; +import java.io.File; +import java.util.Calendar; +import java.util.List; +import java.util.Locale; +import javax.inject.Inject; import org.kiwix.kiwixmobile.BuildConfig; import org.kiwix.kiwixmobile.KiwixApplication; import org.kiwix.kiwixmobile.KiwixMobileActivity; @@ -49,16 +55,6 @@ import org.kiwix.kiwixmobile.views.SliderPreference; import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryUtils; -import java.io.File; -import java.util.Calendar; -import java.util.List; -import java.util.Locale; - -import javax.inject.Inject; - -import eu.mhutti1.utils.storage.StorageDevice; -import eu.mhutti1.utils.storage.StorageSelectDialog; - import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_WEBVIEWS_LIST; import static org.kiwix.kiwixmobile.utils.Constants.PREF_AUTONIGHTMODE; import static org.kiwix.kiwixmobile.utils.Constants.PREF_CLEAR_ALL_HISTORY; diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/AlertDialogShower.kt b/app/src/main/java/org/kiwix/kiwixmobile/utils/AlertDialogShower.kt new file mode 100644 index 0000000000..9e7dbf7db3 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/AlertDialogShower.kt @@ -0,0 +1,38 @@ +package org.kiwix.kiwixmobile.utils + +import android.app.Activity +import android.app.AlertDialog +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity +import javax.inject.Inject + +class AlertDialogShower @Inject constructor( + private val activity: Activity, + private val sharedPreferenceUtil: SharedPreferenceUtil +) : DialogShower { + override fun show( + dialog: KiwixDialog, + vararg clickListener: () -> Unit + ) { + + AlertDialog.Builder(activity, dialogStyle()) + .setTitle(dialog.title) + .setMessage(dialog.message) + .setPositiveButton(dialog.positiveMessage) { _, _ -> + clickListener.getOrNull(0) + ?.invoke() + } + .setNegativeButton(dialog.negativeMessage) { _, _ -> + clickListener.getOrNull(1) + ?.invoke() + } + .show() + } + + private fun dialogStyle() = + if (KiwixSettingsActivity.nightMode(sharedPreferenceUtil)) { + R.style.AppTheme_Dialog_Night + } else { + R.style.AppTheme_Dialog + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/DialogShower.kt b/app/src/main/java/org/kiwix/kiwixmobile/utils/DialogShower.kt new file mode 100644 index 0000000000..7280dc766a --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/DialogShower.kt @@ -0,0 +1,25 @@ +package org.kiwix.kiwixmobile.utils + +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +interface DialogShower { + fun show( + dialog: KiwixDialog, + vararg clickListener: () -> Unit + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt b/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt new file mode 100644 index 0000000000..6b12696dfc --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt @@ -0,0 +1,23 @@ +package org.kiwix.kiwixmobile.utils + +import org.kiwix.kiwixmobile.R + +sealed class KiwixDialog( + val title: Int, + val message: Int, + val positiveMessage: Int, + val negativeMessage: Int +) { + open class YesNoDialog( + title: Int, + message: Int + ) : KiwixDialog(title, message, R.string.yes, R.string.no) + + object NoWifi : YesNoDialog( + R.string.wifi_only_title, R.string.wifi_only_msg + ) + + object StopDownload : YesNoDialog( + R.string.confirm_stop_download_title, R.string.confirm_stop_download_msg + ) +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt new file mode 100644 index 0000000000..3f7be59f82 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt @@ -0,0 +1,81 @@ +package org.kiwix.kiwixmobile.zim_manager + +import android.arch.lifecycle.MutableLiveData +import android.arch.lifecycle.ViewModel +import io.reactivex.Flowable +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.functions.BiFunction +import io.reactivex.schedulers.Schedulers +import org.kiwix.kiwixmobile.database.DownloadDao +import org.kiwix.kiwixmobile.downloader.model.DownloadItem +import org.kiwix.kiwixmobile.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.downloader.model.DownloadState.Successful +import org.kiwix.kiwixmobile.downloader.model.DownloadStatus +import org.kiwix.kiwixmobile.downloader.Downloader +import java.util.concurrent.TimeUnit.SECONDS +import javax.inject.Inject + +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +class ZimManageViewModel @Inject constructor( + val downloadDao: DownloadDao, + val downloader: Downloader +) : ViewModel() { + val downloadItems: MutableLiveData> = MutableLiveData() + private val compositeDisposable = CompositeDisposable() + + init { + val downloadStatuses = downloadStatuses() + compositeDisposable.addAll( + updateDownloadItems(downloadStatuses), + removeCompletedDownloadsFromDb(downloadStatuses) + ) + } + + private fun removeCompletedDownloadsFromDb(downloadStatuses: Flowable>) = + downloadStatuses.subscribe( + { + downloadDao.delete( + *it.filter { status -> status.state == Successful }.map { status -> status.downloadId }.toTypedArray() + ) + }, + Throwable::printStackTrace + ) + + private fun updateDownloadItems(downloadStatuses: Flowable>) = + downloadStatuses + .map { statuses -> statuses.map { DownloadItem(it) } } + .subscribe( + downloadItems::postValue, + Throwable::printStackTrace + ) + + private fun downloadStatuses() = Flowable.combineLatest( + downloadDao.downloads(), + Flowable.interval(1, SECONDS), + BiFunction { downloadModels: List, _: Long -> downloadModels } + ) + .subscribeOn(Schedulers.io()) + .map(downloader::queryStatus) + .distinctUntilChanged() + + override fun onCleared() { + compositeDisposable.clear() + super.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.java index 23f4dc5973..3f43e1484e 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.java @@ -39,12 +39,20 @@ import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; - -import org.kiwix.kiwixmobile.KiwixApplication; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import javax.inject.Inject; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.kiwix.kiwixmobile.R; import org.kiwix.kiwixmobile.ZimContentProvider; import org.kiwix.kiwixmobile.base.BaseFragment; import org.kiwix.kiwixmobile.database.BookDao; +import org.kiwix.kiwixmobile.di.components.ActivityComponent; +import org.kiwix.kiwixmobile.di.components.ApplicationComponent; import org.kiwix.kiwixmobile.library.LibraryAdapter; import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; import org.kiwix.kiwixmobile.utils.BookUtils; @@ -55,14 +63,6 @@ import org.kiwix.kiwixmobile.utils.files.FileUtils; import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import javax.inject.Inject; - import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_STORAGE_PERMISSION; import static org.kiwix.kiwixmobile.utils.NetworkUtils.parseURL; import static org.kiwix.kiwixmobile.utils.StyleUtils.dialogStyle; @@ -88,7 +88,6 @@ public class ZimFileSelectFragment extends BaseFragment @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - KiwixApplication.getApplicationComponent().inject(this); zimManageActivity = (ZimManageActivity) super.getActivity(); presenter.attachView(this); // Replace LinearLayout by the type of the root element of the layout you're trying to load @@ -159,6 +158,12 @@ public void addBook(String path) { } } + @Override + public void inject(@NotNull ActivityComponent activityComponent) { + activityComponent.inject(this); + } + + private class FileComparator implements Comparator { @Override public int compare(LibraryNetworkEntity.Book b1, LibraryNetworkEntity.Book b2) { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.java index f6ba528b1e..a3c000538c 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.java @@ -19,16 +19,13 @@ import android.app.AlertDialog; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.ServiceConnection; import android.graphics.Color; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; -import android.os.IBinder; import android.support.design.widget.Snackbar; import android.support.v4.app.FragmentManager; import android.support.v4.widget.SwipeRefreshLayout; @@ -42,45 +39,40 @@ import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; - -import org.kiwix.kiwixmobile.KiwixApplication; +import butterknife.BindView; +import butterknife.ButterKnife; +import eu.mhutti1.utils.storage.StorageDevice; +import eu.mhutti1.utils.storage.support.StorageSelectDialog; +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import javax.inject.Inject; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.kiwix.kiwixmobile.KiwixMobileActivity; import org.kiwix.kiwixmobile.R; import org.kiwix.kiwixmobile.base.BaseFragment; -import org.kiwix.kiwixmobile.downloader.DownloadFragment; -import org.kiwix.kiwixmobile.downloader.DownloadIntent; +import org.kiwix.kiwixmobile.di.components.ActivityComponent; +import org.kiwix.kiwixmobile.di.components.ApplicationComponent; import org.kiwix.kiwixmobile.downloader.DownloadService; +import org.kiwix.kiwixmobile.downloader.Downloader; import org.kiwix.kiwixmobile.library.LibraryAdapter; import org.kiwix.kiwixmobile.utils.NetworkUtils; import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil; -import org.kiwix.kiwixmobile.utils.StorageUtils; import org.kiwix.kiwixmobile.utils.StyleUtils; import org.kiwix.kiwixmobile.utils.TestingUtils; import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity; -import java.io.File; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -import javax.inject.Inject; - -import butterknife.BindView; -import butterknife.ButterKnife; -import eu.mhutti1.utils.storage.StorageDevice; -import eu.mhutti1.utils.storage.support.StorageSelectDialog; - import static android.view.View.GONE; -import static org.kiwix.kiwixmobile.downloader.DownloadService.KIWIX_ROOT; import static org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book; -import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_BOOK; public class LibraryFragment extends BaseFragment implements AdapterView.OnItemClickListener, StorageSelectDialog.OnSelectListener, LibraryViewCallback { - @BindView(R.id.library_list) ListView libraryList; + @BindView(R.id.network_permission_text) TextView networkText; @BindView(R.id.network_permission_button) @@ -94,16 +86,14 @@ public class LibraryFragment extends BaseFragment private ArrayList books = new ArrayList<>(); public static DownloadService mService = new DownloadService(); - - private boolean mBound; - public LibraryAdapter libraryAdapter; - private DownloadServiceConnection mConnection = new DownloadServiceConnection(); - @Inject ConnectivityManager conMan; + @Inject + Downloader downloader; + private ZimManageActivity faActivity; public static NetworkBroadcastReceiver networkBroadcastReceiver; @@ -118,10 +108,14 @@ public class LibraryFragment extends BaseFragment @Inject SharedPreferenceUtil sharedPreferenceUtil; + @Override + public void inject(@NotNull ActivityComponent activityComponent) { + activityComponent.inject(this); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - KiwixApplication.getApplicationComponent().inject(this); TestingUtils.bindResource(LibraryFragment.class); llLayout = (LinearLayout) inflater.inflate(R.layout.activity_library, container, false); ButterKnife.bind(this, llLayout); @@ -134,7 +128,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, libraryAdapter = new LibraryAdapter(super.getContext()); libraryList.setAdapter(libraryAdapter); - DownloadService.setDownloadFragment(faActivity.mSectionsPagerAdapter.getDownloadFragment()); NetworkInfo network = conMan.getActiveNetworkInfo(); @@ -248,15 +241,6 @@ public void refreshFragment() { networkBroadcastReceiver.onReceive(super.getActivity(), null); } - @Override - public void onDestroyView() { - super.onDestroyView(); - if (mBound && super.getActivity() != null) { - super.getActivity().unbindService(mConnection.downloadServiceInterface); - mBound = false; - } - } - @Override public void onItemClick(AdapterView parent, View view, int position, long id) { if (!libraryAdapter.isDivider(position)) { @@ -284,19 +268,19 @@ public void onItemClick(AdapterView parent, View view, int position, long id) return; } - if (DownloadFragment.mDownloadFiles - .containsValue(KIWIX_ROOT + StorageUtils.getFileNameFromUrl(((Book) parent.getAdapter() - .getItem(position)).getUrl()))) { - Toast.makeText(super.getActivity(), getString(R.string.zim_already_downloading), Toast.LENGTH_LONG) - .show(); - } else { - - NetworkInfo network = conMan.getActiveNetworkInfo(); - if (network == null || !network.isConnected()) { - Toast.makeText(super.getActivity(), getString(R.string.no_network_connection), Toast.LENGTH_LONG) - .show(); - return; - } + // if (DownloadFragment.mDownloadFiles + // .containsValue(KIWIX_ROOT + StorageUtils.getFileNameFromUrl(((Book) parent.getAdapter() + // .getItem(position)).getUrl()))) { + // Toast.makeText(super.getActivity(), getString(R.string.zim_already_downloading), Toast.LENGTH_LONG) + // .show(); + // } else { + // + // NetworkInfo network = conMan.getActiveNetworkInfo(); + // if (network == null || !network.isConnected()) { + // Toast.makeText(super.getActivity(), getString(R.string.no_network_connection), Toast.LENGTH_LONG) + // .show(); + // return; + // } if (KiwixMobileActivity.wifiOnly && !NetworkUtils.isWiFi(getContext())) { new AlertDialog.Builder(getContext()) @@ -315,26 +299,19 @@ public void onItemClick(AdapterView parent, View view, int position, long id) } } } - } @Override public void downloadFile(Book book) { - downloadingBooks.add(book); - if (libraryAdapter != null && faActivity != null && faActivity.searchView != null) { - libraryAdapter.getFilter().filter(faActivity.searchView.getQuery()); - } - Toast.makeText(super.getActivity(), getString(R.string.download_started_library), Toast.LENGTH_LONG) - .show(); - Intent service = new Intent(super.getActivity(), DownloadService.class); - service.putExtra(DownloadIntent.DOWNLOAD_URL_PARAMETER, book.getUrl()); - service.putExtra(DownloadIntent.DOWNLOAD_ZIM_TITLE, book.getTitle()); - service.putExtra(EXTRA_BOOK, book); - super.getActivity().startService(service); - mConnection = new DownloadServiceConnection(); - super.getActivity() - .bindService(service, mConnection.downloadServiceInterface, Context.BIND_AUTO_CREATE); - ZimManageActivity manage = (ZimManageActivity) super.getActivity(); - manage.displayDownloadInterface(); + downloader.download(book); + // downloadingBooks.add(book); + // if (libraryAdapter != null && faActivity != null && faActivity.searchView != null) { + // libraryAdapter.getFilter().filter(faActivity.searchView.getQuery()); + // } + // Toast.makeText(super.getActivity(), getString(R.string.download_started_library), Toast.LENGTH_LONG) + // .show(); + // + // ZimManageActivity manage = (ZimManageActivity) super.getActivity(); + // manage.displayDownloadInterface(); } public long getSpaceAvailable() { @@ -351,28 +328,6 @@ public void selectionCallback(StorageDevice storageDevice) { } } - public class DownloadServiceConnection { - public DownloadServiceInterface downloadServiceInterface; - - public DownloadServiceConnection() { - downloadServiceInterface = new DownloadServiceInterface(); - } - - public class DownloadServiceInterface implements ServiceConnection { - - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - // We've bound to LocalService, cast the IBinder and get LocalService instance - DownloadService.LocalBinder binder = (DownloadService.LocalBinder) service; - mService = binder.getService(); - mBound = true; - } - - @Override - public void onServiceDisconnected(ComponentName arg0) { - } - } - } public class NetworkBroadcastReceiver extends BroadcastReceiver { @Override diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryPresenter.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryPresenter.java index 3db78e7da8..2d5a3cff32 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryPresenter.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryPresenter.java @@ -18,17 +18,13 @@ package org.kiwix.kiwixmobile.zim_manager.library_view; import android.util.Log; - +import io.reactivex.android.schedulers.AndroidSchedulers; +import javax.inject.Inject; import org.kiwix.kiwixmobile.base.BasePresenter; import org.kiwix.kiwixmobile.database.BookDao; -import org.kiwix.kiwixmobile.downloader.DownloadFragment; import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; import org.kiwix.kiwixmobile.network.KiwixService; -import javax.inject.Inject; - -import io.reactivex.android.schedulers.AndroidSchedulers; - /** * Created by EladKeyshawn on 06/04/2017. */ @@ -58,16 +54,11 @@ void loadBooks() { void loadRunningDownloadsFromDb() { for (LibraryNetworkEntity.Book book : bookDao.getDownloadingBooks()) { - if (!DownloadFragment.mDownloads.containsValue(book)) { + // if (!DownloadFragment.mDownloads.containsValue(book)) { book.url = book.remoteUrl; getMvpView().downloadFile(book); - } + // } } } - @Override - public void attachView(LibraryViewCallback view) { - super.attachView(view); - } - } diff --git a/app/src/main/res/layout/download_item.xml b/app/src/main/res/layout/download_item.xml index f7a433f6ca..59ff51528b 100644 --- a/app/src/main/res/layout/download_item.xml +++ b/app/src/main/res/layout/download_item.xml @@ -1,100 +1,90 @@ - - + android:orientation="horizontal" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + > + + + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center" + android:orientation="vertical" + > + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:textAppearance="?android:attr/textAppearanceListItem" + tools:text="Title" + /> + android:id="@+id/description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="start" + android:textAppearance="?android:attr/textAppearanceSmall" + tools:text="Description" + /> + android:id="@+id/downloadProgress" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:indeterminate="false" + android:padding="@dimen/download_progress_padding" + style="?android:attr/progressBarStyleHorizontal" + /> + android:id="@+id/time_remaining" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end" + android:textAppearance="?android:attr/textAppearanceSmall" + tools:text="3hrs 45 mins" + /> - - + android:layout_height="match_parent" + android:orientation="horizontal" + > + android:id="@+id/stop" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="@dimen/stop_horizontal_margin" + android:layout_marginRight="@dimen/stop_horizontal_margin" + android:layout_weight="0.5" + android:minHeight="@dimen/stop_min_height" + android:minWidth="@dimen/stop_min_width" + android:src="@drawable/ic_stop_black_24dp" + android:text="@string/download_stop" + /> diff --git a/app/src/main/res/layout/download_management.xml b/app/src/main/res/layout/download_management.xml deleted file mode 100644 index de0f667c9d..0000000000 --- a/app/src/main/res/layout/download_management.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/layout_download_management.xml b/app/src/main/res/layout/layout_download_management.xml new file mode 100644 index 0000000000..898a20d878 --- /dev/null +++ b/app/src/main/res/layout/layout_download_management.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file From 61697b502c1513bf01f80e046fdea558228c5896 Mon Sep 17 00:00:00 2001 From: Sean Mac Gillicuddy Date: Wed, 1 May 2019 14:04:39 +0100 Subject: [PATCH 02/20] ZimFileSelectFragment rewritten in kotlin and observing reactive streams of data --- .../kiwix/kiwixmobile/KiwixErrorActivity.java | 3 +- .../kiwix/kiwixmobile/database/BookDao.java | 29 +- .../downloader/DownloadFragment.kt | 7 +- .../downloader/DownloadViewHolder.kt | 13 +- .../extensions/ContextExtensions.kt | 14 + .../extensions/ImageViewExtensions.kt | 14 + .../kiwixmobile/library/LibraryAdapter.java | 2 +- .../library/entity/LibraryNetworkEntity.java | 9 +- .../kiwixmobile/utils/AlertDialogShower.kt | 20 +- .../kiwix/kiwixmobile/utils/KiwixDialog.kt | 26 +- .../zim_manager/ZimManageActivity.java | 9 +- .../zim_manager/ZimManageViewModel.kt | 56 ++- .../fileselect_view/RescanDataAdapter.kt | 42 ++ .../fileselect_view/RescanViewHolder.kt | 64 +++ .../fileselect_view/StorageObserver.kt | 35 ++ .../ZimFileSelectFragment.java | 428 ------------------ .../fileselect_view/ZimFileSelectFragment.kt | 185 ++++++++ .../ZimFileSelectPresenter.java | 50 -- .../ZimFileSelectViewCallback.java | 30 -- app/src/main/res/layout/zim_list.xml | 72 +-- 20 files changed, 507 insertions(+), 601 deletions(-) create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/extensions/ImageViewExtensions.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanDataAdapter.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanViewHolder.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt delete mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.java create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt delete mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectPresenter.java delete mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectViewCallback.java diff --git a/app/src/main/java/org/kiwix/kiwixmobile/KiwixErrorActivity.java b/app/src/main/java/org/kiwix/kiwixmobile/KiwixErrorActivity.java index caf5aaf4d6..396eeb4f35 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/KiwixErrorActivity.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/KiwixErrorActivity.java @@ -10,6 +10,7 @@ import android.widget.Button; import android.widget.CheckBox; +import java.util.List; import org.kiwix.kiwixmobile.base.BaseActivity; import org.kiwix.kiwixmobile.database.BookDao; import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; @@ -89,7 +90,7 @@ protected void onCreate(Bundle savedInstanceState) { } if(allowZimsCheckbox.isChecked()) { - ArrayList books = bookDao.getBooks(); + List books = bookDao.getBooks(); StringBuilder sb = new StringBuilder(); for(LibraryNetworkEntity.Book book: books) { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/BookDao.java b/app/src/main/java/org/kiwix/kiwixmobile/database/BookDao.java index 82b973dd0c..7496c11d97 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/database/BookDao.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/BookDao.java @@ -17,18 +17,17 @@ */ package org.kiwix.kiwixmobile.database; - +import com.yahoo.squidb.data.SimpleDataChangedNotifier; import com.yahoo.squidb.data.SquidCursor; import com.yahoo.squidb.sql.Query; - -import org.kiwix.kiwixmobile.database.entity.BookDatabaseEntity; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book; -import org.kiwix.kiwixmobile.utils.files.FileUtils; - +import io.reactivex.Flowable; +import io.reactivex.processors.PublishProcessor; import java.io.File; import java.util.ArrayList; - +import java.util.List; import javax.inject.Inject; +import org.kiwix.kiwixmobile.database.entity.BookDatabaseEntity; +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book; import static org.kiwix.kiwixmobile.downloader.ChunkUtils.hasParts; @@ -38,14 +37,26 @@ public class BookDao { private KiwixDatabase mDb; + private final PublishProcessor> booksProcessor = PublishProcessor.create(); @Inject public BookDao(KiwixDatabase kiwixDatabase) { this.mDb = kiwixDatabase; + kiwixDatabase.registerDataChangedNotifier( + new SimpleDataChangedNotifier(BookDatabaseEntity.TABLE) { + @Override + protected void onDataChanged() { + booksProcessor.onNext(getBooks()); + } + }); } + public Flowable> books() { + return booksProcessor.startWith(getBooks()).distinctUntilChanged(); + } public void setBookDetails(Book book, SquidCursor bookCursor) { + book.databaseId = bookCursor.get(BookDatabaseEntity.ID); book.id = bookCursor.get(BookDatabaseEntity.BOOK_ID); book.title = bookCursor.get(BookDatabaseEntity.TITLE); book.description = bookCursor.get(BookDatabaseEntity.DESCRIPTION); @@ -80,7 +91,7 @@ public void setBookDatabaseEntity(Book book, BookDatabaseEntity bookDatabaseEnti mDb.persist(bookDatabaseEntity); } - public ArrayList getBooks() { + public List getBooks() { SquidCursor bookCursor = mDb.query( BookDatabaseEntity.class, Query.select()); @@ -117,7 +128,7 @@ public ArrayList getDownloadingBooks() { return books; } - public void saveBooks(ArrayList books) { + public void saveBooks(List books) { for (Book book : books) { if (book != null) { BookDatabaseEntity bookDatabaseEntity = new BookDatabaseEntity(); diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.kt index 4f8e723f55..f764fea08d 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.kt @@ -33,7 +33,8 @@ import org.kiwix.kiwixmobile.base.BaseFragment import org.kiwix.kiwixmobile.di.components.ActivityComponent import org.kiwix.kiwixmobile.downloader.model.DownloadItem import org.kiwix.kiwixmobile.utils.DialogShower -import org.kiwix.kiwixmobile.utils.KiwixDialog +import org.kiwix.kiwixmobile.utils.KiwixDialog.YesNoDialog.NoWifi +import org.kiwix.kiwixmobile.utils.KiwixDialog.YesNoDialog.StopDownload import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel import javax.inject.Inject @@ -46,7 +47,7 @@ class DownloadFragment : BaseFragment() { @Inject lateinit var downloader: Downloader lateinit var zimManageViewModel: ZimManageViewModel private val downloadAdapter = DownloadAdapter { - dialogShower.show(KiwixDialog.StopDownload, { downloader.cancelDownload(it) }) + dialogShower.show(StopDownload, { downloader.cancelDownload(it) }) } override fun inject(activityComponent: ActivityComponent) { @@ -98,7 +99,7 @@ class DownloadFragment : BaseFragment() { context: Context?, yesAction: Runnable ) { - dialogShower.show(KiwixDialog.NoWifi, { + dialogShower.show(NoWifi, { sharedPreferenceUtil.putPrefWifiOnly(false) KiwixMobileActivity.wifiOnly = false yesAction.run() diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadViewHolder.kt index 8030574e73..1ae781514f 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadViewHolder.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadViewHolder.kt @@ -19,15 +19,14 @@ package org.kiwix.kiwixmobile.downloader import android.support.v7.widget.RecyclerView import android.view.View -import android.widget.ImageView import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.main.download_item.description import kotlinx.android.synthetic.main.download_item.downloadProgress import kotlinx.android.synthetic.main.download_item.favicon import kotlinx.android.synthetic.main.download_item.stop import kotlinx.android.synthetic.main.download_item.title -import org.kiwix.kiwixmobile.downloader.model.Base64String import org.kiwix.kiwixmobile.downloader.model.DownloadItem +import org.kiwix.kiwixmobile.extensions.setBitmap class DownloadViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer { @@ -43,14 +42,4 @@ class DownloadViewHolder(override val containerView: View) : RecyclerView.ViewHo itemClickListener.invoke(downloadItem) } } - - private fun ImageView.setBitmap(base64String: Base64String) { - if (tag != base64String) { - base64String.toBitmap() - ?.let { - setImageBitmap(it) - tag = base64String - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt new file mode 100644 index 0000000000..361112851f --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt @@ -0,0 +1,14 @@ +package org.kiwix.kiwixmobile.extensions + +import android.content.Context +import android.widget.Toast + +fun Context?.toast( + stringId: Int, + length: Int = Toast.LENGTH_LONG +) { + this?.let { + Toast.makeText(this, stringId, length) + .show() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/extensions/ImageViewExtensions.kt b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ImageViewExtensions.kt new file mode 100644 index 0000000000..a2f66cf943 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ImageViewExtensions.kt @@ -0,0 +1,14 @@ +package org.kiwix.kiwixmobile.extensions + +import android.widget.ImageView +import org.kiwix.kiwixmobile.downloader.model.Base64String + +public fun ImageView.setBitmap(base64String: Base64String) { + if (tag != base64String) { + base64String.toBitmap() + ?.let { + setImageBitmap(it) + tag = base64String + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java b/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java index 14db754bd9..b4fd127cfc 100755 --- a/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java @@ -233,7 +233,7 @@ private Observable getMatches(Book b, String s) { private class BookFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence s) { - ArrayList books = bookDao.getBooks(); + List books = bookDao.getBooks(); listItems.clear(); if (s.length() == 0) { List selectedLanguages = Observable.fromIterable(allBooks) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/library/entity/LibraryNetworkEntity.java b/app/src/main/java/org/kiwix/kiwixmobile/library/entity/LibraryNetworkEntity.java index 675f9c19c6..9b6348ee96 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/library/entity/LibraryNetworkEntity.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/library/entity/LibraryNetworkEntity.java @@ -18,13 +18,12 @@ */ package org.kiwix.kiwixmobile.library.entity; -import org.simpleframework.xml.Attribute; -import org.simpleframework.xml.ElementList; -import org.simpleframework.xml.Root; - import java.io.File; import java.io.Serializable; import java.util.LinkedList; +import org.simpleframework.xml.Attribute; +import org.simpleframework.xml.ElementList; +import org.simpleframework.xml.Root; @Root(name = "library", strict = false) public class LibraryNetworkEntity { @@ -45,6 +44,8 @@ public String getVersion() { @Root(name = "book", strict = false) public static class Book implements Serializable{ + @Attribute(name = "databaseId", required = false) + public long databaseId; @Attribute(name = "id", required = false) public String id; diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/AlertDialogShower.kt b/app/src/main/java/org/kiwix/kiwixmobile/utils/AlertDialogShower.kt index 9e7dbf7db3..7a0b5c84fb 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/utils/AlertDialogShower.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/AlertDialogShower.kt @@ -16,15 +16,17 @@ class AlertDialogShower @Inject constructor( ) { AlertDialog.Builder(activity, dialogStyle()) - .setTitle(dialog.title) - .setMessage(dialog.message) - .setPositiveButton(dialog.positiveMessage) { _, _ -> - clickListener.getOrNull(0) - ?.invoke() - } - .setNegativeButton(dialog.negativeMessage) { _, _ -> - clickListener.getOrNull(1) - ?.invoke() + .apply { + dialog.title?.let { setTitle(it) } + setMessage(dialog.message) + setPositiveButton(dialog.positiveMessage) { _, _ -> + clickListener.getOrNull(0) + ?.invoke() + } + setNegativeButton(dialog.negativeMessage) { _, _ -> + clickListener.getOrNull(1) + ?.invoke() + } } .show() } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt b/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt index 6b12696dfc..851b20acf0 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt @@ -3,21 +3,31 @@ package org.kiwix.kiwixmobile.utils import org.kiwix.kiwixmobile.R sealed class KiwixDialog( - val title: Int, + val title: Int?, val message: Int, val positiveMessage: Int, val negativeMessage: Int ) { + + object DeleteZim : KiwixDialog( + null, R.string.delete_specific_zim, R.string.delete, R.string.no + ) + open class YesNoDialog( title: Int, message: Int - ) : KiwixDialog(title, message, R.string.yes, R.string.no) + ) : KiwixDialog(title, message, R.string.yes, R.string.no){ + object NoWifi : YesNoDialog( + R.string.wifi_only_title, R.string.wifi_only_msg + ) + + object StopDownload : YesNoDialog( + R.string.confirm_stop_download_title, R.string.confirm_stop_download_msg + ) + + + } + - object NoWifi : YesNoDialog( - R.string.wifi_only_title, R.string.wifi_only_msg - ) - object StopDownload : YesNoDialog( - R.string.confirm_stop_download_title, R.string.confirm_stop_download_msg - ) } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java index e7ae0a6a5f..2aa3c14bf8 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java @@ -34,7 +34,8 @@ import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; - +import java.io.File; +import javax.inject.Inject; import org.kiwix.kiwixmobile.KiwixMobileActivity; import org.kiwix.kiwixmobile.R; import org.kiwix.kiwixmobile.base.BaseActivity; @@ -43,10 +44,6 @@ import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil; import org.kiwix.kiwixmobile.views.LanguageSelectDialog; -import java.io.File; - -import javax.inject.Inject; - import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX; import static org.kiwix.kiwixmobile.utils.StyleUtils.dialogStyle; @@ -273,4 +270,4 @@ private void showLanguageSelect() { }) .show(); } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt index 3f7be59f82..674daa5f85 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt @@ -4,14 +4,19 @@ import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.ViewModel import io.reactivex.Flowable import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable import io.reactivex.functions.BiFunction +import io.reactivex.processors.PublishProcessor import io.reactivex.schedulers.Schedulers +import org.kiwix.kiwixmobile.database.BookDao import org.kiwix.kiwixmobile.database.DownloadDao +import org.kiwix.kiwixmobile.downloader.Downloader import org.kiwix.kiwixmobile.downloader.model.DownloadItem import org.kiwix.kiwixmobile.downloader.model.DownloadModel import org.kiwix.kiwixmobile.downloader.model.DownloadState.Successful import org.kiwix.kiwixmobile.downloader.model.DownloadStatus -import org.kiwix.kiwixmobile.downloader.Downloader +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver import java.util.concurrent.TimeUnit.SECONDS import javax.inject.Inject @@ -33,20 +38,63 @@ import javax.inject.Inject * along with this program. If not, see . */ class ZimManageViewModel @Inject constructor( - val downloadDao: DownloadDao, - val downloader: Downloader + private val downloadDao: DownloadDao, + private val bookDao: BookDao, + private val downloader: Downloader, + private val storageObserver: StorageObserver ) : ViewModel() { + val downloadItems: MutableLiveData> = MutableLiveData() + val bookItems: MutableLiveData> = MutableLiveData() + val checkFileSystem = PublishProcessor.create() + val deviceListIsRefreshing = MutableLiveData() + private val compositeDisposable = CompositeDisposable() init { val downloadStatuses = downloadStatuses() + val booksFromDao = books() compositeDisposable.addAll( updateDownloadItems(downloadStatuses), - removeCompletedDownloadsFromDb(downloadStatuses) + removeCompletedDownloadsFromDb(downloadStatuses), + updateBookItems(booksFromDao), + checkFileSystemForBooksOnRequest(booksFromDao) ) } + private fun checkFileSystemForBooksOnRequest(booksFromDao: Flowable>): Disposable? { + return checkFileSystem + .doOnNext { deviceListIsRefreshing.postValue(true) } + .switchMap { + updateBookDaoFromFilesystem(booksFromDao) + } + .doOnNext { deviceListIsRefreshing.postValue(false) } + .subscribe( + bookDao::saveBooks, + Throwable::printStackTrace + ) + } + + private fun books() = bookDao.books() + .subscribeOn(Schedulers.io()) + .map { it.sortedBy { book -> book.title } } + + private fun updateBookDaoFromFilesystem(booksFromDao: Flowable>) = + storageObserver.booksOnFileSystem.withLatestFrom( + booksFromDao, + BiFunction, List, List> { booksFileSystem, booksDao -> + booksFileSystem.minus( + booksDao + ) + }) + + private fun updateBookItems(booksFromDao: Flowable>) = + booksFromDao + .subscribe( + bookItems::postValue, + Throwable::printStackTrace + ) + private fun removeCompletedDownloadsFromDb(downloadStatuses: Flowable>) = downloadStatuses.subscribe( { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanDataAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanDataAdapter.kt new file mode 100644 index 0000000000..c852ab9114 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanDataAdapter.kt @@ -0,0 +1,42 @@ +package org.kiwix.kiwixmobile.zim_manager.fileselect_view + +import android.support.v7.widget.RecyclerView +import android.view.ViewGroup +import org.kiwix.kiwixmobile.R.layout +import org.kiwix.kiwixmobile.extensions.inflate +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import org.kiwix.kiwixmobile.utils.BookUtils + +// The Adapter for the ListView for when the ListView is populated with the rescanned files +public class RescanDataAdapter( + val bookUtils: BookUtils, + val onItemClick: (Book) -> Unit, + val onItemLongClick: (Book) -> Unit +) : RecyclerView.Adapter() { + + init { + setHasStableIds(true) + } + + var itemList: List = mutableListOf() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun getItemId(position: Int) = itemList[position].databaseId + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = RescanViewHolder(parent.inflate(layout.library_item, false), bookUtils) + + override fun getItemCount() = itemList.size + + override fun onBindViewHolder( + holder: RescanViewHolder, + position: Int + ) { + holder.bind(itemList[position], onItemClick, onItemLongClick) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanViewHolder.kt new file mode 100644 index 0000000000..1b05840098 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanViewHolder.kt @@ -0,0 +1,64 @@ +package org.kiwix.kiwixmobile.zim_manager.fileselect_view + +import android.support.v7.widget.RecyclerView.ViewHolder +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.widget.TextView +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.library_item.creator +import kotlinx.android.synthetic.main.library_item.date +import kotlinx.android.synthetic.main.library_item.description +import kotlinx.android.synthetic.main.library_item.favicon +import kotlinx.android.synthetic.main.library_item.fileName +import kotlinx.android.synthetic.main.library_item.language +import kotlinx.android.synthetic.main.library_item.publisher +import kotlinx.android.synthetic.main.library_item.size +import kotlinx.android.synthetic.main.library_item.title +import org.kiwix.kiwixmobile.KiwixApplication +import org.kiwix.kiwixmobile.downloader.model.Base64String +import org.kiwix.kiwixmobile.extensions.setBitmap +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import org.kiwix.kiwixmobile.utils.BookUtils +import org.kiwix.kiwixmobile.utils.NetworkUtils + +class RescanViewHolder( + override val containerView: View, + val bookUtils: BookUtils +) : ViewHolder(containerView), + LayoutContainer { + fun bind( + book: Book, + clickAction: (Book) -> Unit, + longClickAction: (Book) -> Unit + ) { + title.setTextAndVisibility(book.title) + description.setTextAndVisibility(book.description) + creator.setTextAndVisibility(book.creator) + publisher.setTextAndVisibility(book.publisher) + date.setTextAndVisibility(book.date) + size.setTextAndVisibility(book.size) + language.text = bookUtils.getLanguage(book.getLanguage()) + fileName.text = NetworkUtils.parseURL( + KiwixApplication.getInstance(), book.file.path + ) + favicon.setBitmap(Base64String(book.favicon)) + + containerView.setOnClickListener { + clickAction.invoke(book) + } + containerView.setOnLongClickListener { + longClickAction.invoke(book) + return@setOnLongClickListener true + } + } + + private fun TextView.setTextAndVisibility(title: String?) = + if (title != null && title.isNotEmpty()) { + text = title + visibility = VISIBLE + } else { + visibility = GONE + } + +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt new file mode 100644 index 0000000000..5827eefa92 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt @@ -0,0 +1,35 @@ +package org.kiwix.kiwixmobile.zim_manager.fileselect_view + +import android.content.Context +import android.util.Log +import io.reactivex.processors.PublishProcessor +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.utils.files.FileSearch +import org.kiwix.kiwixmobile.utils.files.FileSearch.ResultListener +import javax.inject.Inject + +class StorageObserver @Inject constructor( + val context: Context, + val sharedPreferenceUtil: SharedPreferenceUtil +) { + + private val _booksOnFileSystem = PublishProcessor.create>() + val booksOnFileSystem = _booksOnFileSystem.distinctUntilChanged() + .doOnSubscribe { scanFiles() } + + fun scanFiles() { + FileSearch(context, object : ResultListener { + val foundBooks = mutableSetOf() + override fun onBookFound(book: Book) { + foundBooks.add(book) + Log.i("Scanner", "File Search: Found Book " + book.title) + } + + override fun onScanCompleted() { + _booksOnFileSystem.onNext(foundBooks) + + } + }).scan(sharedPreferenceUtil.prefStorage) + } +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.java deleted file mode 100644 index 3f43e1484e..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.java +++ /dev/null @@ -1,428 +0,0 @@ -/* - * Copyright 2013 Rashiq Ahmad - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ - -package org.kiwix.kiwixmobile.zim_manager.fileselect_view; - -import android.Manifest; -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Bundle; -import android.support.v4.content.ContextCompat; -import android.support.v4.widget.SwipeRefreshLayout; -import android.support.v7.app.AlertDialog; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.RelativeLayout; -import android.widget.TextView; -import android.widget.Toast; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import javax.inject.Inject; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.kiwix.kiwixmobile.R; -import org.kiwix.kiwixmobile.ZimContentProvider; -import org.kiwix.kiwixmobile.base.BaseFragment; -import org.kiwix.kiwixmobile.database.BookDao; -import org.kiwix.kiwixmobile.di.components.ActivityComponent; -import org.kiwix.kiwixmobile.di.components.ApplicationComponent; -import org.kiwix.kiwixmobile.library.LibraryAdapter; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; -import org.kiwix.kiwixmobile.utils.BookUtils; -import org.kiwix.kiwixmobile.utils.LanguageUtils; -import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil; -import org.kiwix.kiwixmobile.utils.TestingUtils; -import org.kiwix.kiwixmobile.utils.files.FileSearch; -import org.kiwix.kiwixmobile.utils.files.FileUtils; -import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity; - -import static org.kiwix.kiwixmobile.utils.Constants.REQUEST_STORAGE_PERMISSION; -import static org.kiwix.kiwixmobile.utils.NetworkUtils.parseURL; -import static org.kiwix.kiwixmobile.utils.StyleUtils.dialogStyle; - -public class ZimFileSelectFragment extends BaseFragment - implements OnItemClickListener, AdapterView.OnItemLongClickListener, ZimFileSelectViewCallback{ - - public RelativeLayout llLayout; - public SwipeRefreshLayout swipeRefreshLayout; - - private ZimManageActivity zimManageActivity; - private RescanDataAdapter mRescanAdapter; - private ArrayList mFiles; - private ListView mZimFileList; - private TextView mFileMessage; - private boolean mHasRefresh; - - @Inject ZimFileSelectPresenter presenter; - @Inject BookUtils bookUtils; - @Inject SharedPreferenceUtil sharedPreferenceUtil; - @Inject - BookDao bookDao; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - zimManageActivity = (ZimManageActivity) super.getActivity(); - presenter.attachView(this); - // Replace LinearLayout by the type of the root element of the layout you're trying to load - llLayout = (RelativeLayout) inflater.inflate(R.layout.zim_list, container, false); - new LanguageUtils(super.getActivity()).changeFont(super.getActivity().getLayoutInflater(), sharedPreferenceUtil); - - mFileMessage = llLayout.findViewById(R.id.file_management_no_files); - mZimFileList = llLayout.findViewById(R.id.zimfilelist); - - mFiles = new ArrayList<>(); - - // SwipeRefreshLayout for the list view - swipeRefreshLayout = llLayout.findViewById(R.id.zim_swiperefresh); - swipeRefreshLayout.setOnRefreshListener(this::refreshFragment); - - // A boolean to distinguish between a user refresh and a normal loading - mHasRefresh = false; - - mRescanAdapter = new RescanDataAdapter(zimManageActivity, 0, mFiles); - - // Allow temporary use of ZimContentProvider to query books - ZimContentProvider.canIterate = true; - return llLayout; // We must return the loaded Layout - } - - @Override - public void onResume() { - presenter.loadLocalZimFileFromDb(); - super.onResume(); - } - - - // Show files from database - @Override - public void showFiles(ArrayList books) { - if (mZimFileList == null) - return; - - mZimFileList.setOnItemClickListener(this); - mZimFileList.setOnItemLongClickListener(this); - Collections.sort(books, new FileComparator()); - mFiles.clear(); - mFiles.addAll(books); - mZimFileList.setAdapter(mRescanAdapter); - mRescanAdapter.notifyDataSetChanged(); - checkEmpty(); - checkPermissions(); - } - - public void refreshFragment() { - if (mZimFileList == null) { - swipeRefreshLayout.setRefreshing(false); - return; - } - - mHasRefresh = true; - presenter.loadLocalZimFileFromDb(); - } - - // Add book after download - public void addBook(String path) { - LibraryNetworkEntity.Book book = FileSearch.fileToBook(path); - if (book != null) { - mFiles.add(book); - mRescanAdapter.notifyDataSetChanged(); - bookDao.saveBooks(mFiles); - checkEmpty(); - } - } - - @Override - public void inject(@NotNull ActivityComponent activityComponent) { - activityComponent.inject(this); - } - - - private class FileComparator implements Comparator { - @Override - public int compare(LibraryNetworkEntity.Book b1, LibraryNetworkEntity.Book b2) { - return b1.getTitle().compareTo(b2.getTitle()); - } - } - - public void checkPermissions(){ - if (ContextCompat.checkSelfPermission(super.getActivity(), - Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED && Build.VERSION.SDK_INT > 18) { - Toast.makeText(super.getActivity(), getResources().getString(R.string.request_storage), Toast.LENGTH_LONG) - .show(); - requestPermissions( new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - REQUEST_STORAGE_PERMISSION); - } else { - getFiles(); - } - } - - public void getFiles() { - if (swipeRefreshLayout.isRefreshing() && !mHasRefresh) - return; - - TestingUtils.bindResource(ZimFileSelectFragment.class); - swipeRefreshLayout.setRefreshing(true); - mZimFileList.setAdapter(mRescanAdapter); - - // Set mHasRefresh to false to prevent loops - mHasRefresh = false; - - checkEmpty(); - - new FileSearch(zimManageActivity, new FileSearch.ResultListener() { - @Override - public void onBookFound(LibraryNetworkEntity.Book book) { - if (!mFiles.contains(book)) { - zimManageActivity.runOnUiThread(() -> { - Log.i("Scanner", "File Search: Found Book " + book.title); - mFiles.add(book); - mRescanAdapter.notifyDataSetChanged(); - checkEmpty(); - }); - } - } - - @Override - public void onScanCompleted() { - // Remove non-existent books - ArrayList books = new ArrayList<>(mFiles); - for (LibraryNetworkEntity.Book book : books) { - if (book.file == null || !book.file.canRead()) { - mFiles.remove(book); - } - } - - boolean cached = mFiles.containsAll(bookDao.getBooks()) && bookDao.getBooks().containsAll(mFiles); - - // If content changed then update the list of downloadable books - if (!cached && zimManageActivity.mSectionsPagerAdapter.libraryFragment.libraryAdapter != null && zimManageActivity.searchView != null) { - zimManageActivity.mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(zimManageActivity.searchView.getQuery()); - } - - // Save the current list of books - zimManageActivity.runOnUiThread(() -> { - mRescanAdapter.notifyDataSetChanged(); - bookDao.saveBooks(mFiles); - checkEmpty(); - TestingUtils.unbindResource(ZimFileSelectFragment.class); - - // Stop swipe refresh animation - swipeRefreshLayout.setRefreshing(false); - }); - } - }).scan(sharedPreferenceUtil.getPrefStorage()); - } - - @Override - public void onRequestPermissionsResult(int requestCode, - String permissions[], int[] grantResults) { - switch (requestCode) { - case REQUEST_STORAGE_PERMISSION: { - if (grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - getFiles(); - } else if (grantResults.length != 0) { - zimManageActivity.finish(); - } - } - - } - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - - // Stop file search from accessing content provider potentially opening wrong file - ZimContentProvider.canIterate = false; - - String file; - LibraryNetworkEntity.Book data = (LibraryNetworkEntity.Book) mZimFileList.getItemAtPosition(position); - file = data.file.getPath(); - - if (!data.file.canRead()) { - Toast.makeText(zimManageActivity, getString(R.string.error_filenotfound), Toast.LENGTH_LONG).show(); - return; - } - - zimManageActivity.finishResult(file); - } - - @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - deleteSpecificZimDialog(position); - return true; - } - - public void deleteSpecificZimDialog(int position) { - new AlertDialog.Builder(zimManageActivity, dialogStyle()) - .setMessage(getString(R.string.delete_specific_zim)) - .setPositiveButton(getResources().getString(R.string.delete), (dialog, which) -> { - if (deleteSpecificZimFile(position)) { - Toast.makeText(zimManageActivity, getResources().getString(R.string.delete_specific_zim_toast), Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(zimManageActivity, getResources().getString(R.string.delete_zim_failed), Toast.LENGTH_SHORT).show(); - } - }) - .setNegativeButton(android.R.string.no, (dialog, which) -> { - // do nothing - }) - .show(); - } - - public boolean deleteSpecificZimFile(int position) { - File file = mFiles.get(position).file; - FileUtils.deleteZimFile(file); - if (file.exists()) { - return false; - } - bookDao.deleteBook(mFiles.get(position).getId()); - mFiles.remove(position); - mRescanAdapter.notifyDataSetChanged(); - checkEmpty(); - if (zimManageActivity.mSectionsPagerAdapter.libraryFragment.libraryAdapter != null) { - zimManageActivity.mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(zimManageActivity.searchView.getQuery()); - } - return true; - } - - public void checkEmpty(){ - if (mZimFileList.getCount() == 0){ - mFileMessage.setVisibility(View.VISIBLE); - } else - mFileMessage.setVisibility(View.GONE); - } - - // The Adapter for the ListView for when the ListView is populated with the rescanned files - private class RescanDataAdapter extends ArrayAdapter { - - public RescanDataAdapter(Context context, int textViewResourceId, List objects) { - super(context, textViewResourceId, objects); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - ViewHolder holder; - LibraryNetworkEntity.Book book = getItem(position); - if (convertView == null) { - convertView = View.inflate(zimManageActivity, R.layout.library_item, null); - holder = new ViewHolder(); - holder.title = convertView.findViewById(R.id.title); - holder.description = convertView.findViewById(R.id.description); - holder.language = convertView.findViewById(R.id.language); - holder.creator = convertView.findViewById(R.id.creator); - holder.publisher = convertView.findViewById(R.id.publisher); - holder.date = convertView.findViewById(R.id.date); - holder.size = convertView.findViewById(R.id.size); - holder.fileName = convertView.findViewById(R.id.fileName); - holder.favicon = convertView.findViewById(R.id.favicon); - convertView.setTag(holder); - } else { - holder = (ViewHolder) convertView.getTag(); - } - - if (book == null) { - return convertView; - } - - holder.title.setText(book.getTitle()); - holder.description.setText(book.getDescription()); - holder.language.setText(bookUtils.getLanguage(book.getLanguage())); - holder.creator.setText(book.getCreator()); - holder.publisher.setText(book.getPublisher()); - holder.date.setText(book.getDate()); - holder.size.setText(LibraryAdapter.createGbString(book.getSize())); - holder.fileName.setText(parseURL(getActivity(), book.file.getPath())); - holder.favicon.setImageBitmap(LibraryAdapter.createBitmapFromEncodedString(book.getFavicon(), zimManageActivity)); - - - //// Check if no value is empty. Set the view to View.GONE, if it is. To View.VISIBLE, if not. - if (book.getTitle() == null || book.getTitle().isEmpty()) { - holder.title.setVisibility(View.GONE); - } else { - holder.title.setVisibility(View.VISIBLE); - } - - if (book.getDescription() == null || book.getDescription().isEmpty()) { - holder.description.setVisibility(View.GONE); - } else { - holder.description.setVisibility(View.VISIBLE); - } - - if (book.getCreator() == null || book.getCreator().isEmpty()) { - holder.creator.setVisibility(View.GONE); - } else { - holder.creator.setVisibility(View.VISIBLE); - } - - if (book.getPublisher() == null || book.getPublisher().isEmpty()) { - holder.publisher.setVisibility(View.GONE); - } else { - holder.publisher.setVisibility(View.VISIBLE); - } - - if (book.getDate() == null || book.getDate().isEmpty()) { - holder.date.setVisibility(View.GONE); - } else { - holder.date.setVisibility(View.VISIBLE); - } - - if (book.getSize() == null || book.getSize().isEmpty()) { - holder.size.setVisibility(View.GONE); - } else { - holder.size.setVisibility(View.VISIBLE); - } - - return convertView; - - } - - private class ViewHolder { - TextView title; - - TextView description; - - TextView language; - - TextView creator; - - TextView publisher; - - TextView date; - - TextView size; - - TextView fileName; - - ImageView favicon; - } - } -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt new file mode 100644 index 0000000000..2baae5f1c0 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt @@ -0,0 +1,185 @@ +/* + * Copyright 2013 Rashiq Ahmad + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +package org.kiwix.kiwixmobile.zim_manager.fileselect_view + +import android.Manifest +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProvider +import android.arch.lifecycle.ViewModelProviders +import android.content.pm.PackageManager +import android.os.Build +import android.os.Bundle +import android.support.v4.content.ContextCompat +import android.support.v7.widget.LinearLayoutManager +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import kotlinx.android.synthetic.main.zim_list.file_management_no_files +import kotlinx.android.synthetic.main.zim_list.zim_swiperefresh +import kotlinx.android.synthetic.main.zim_list.zimfilelist +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.R.string +import org.kiwix.kiwixmobile.ZimContentProvider +import org.kiwix.kiwixmobile.base.BaseFragment +import org.kiwix.kiwixmobile.database.BookDao +import org.kiwix.kiwixmobile.di.components.ActivityComponent +import org.kiwix.kiwixmobile.extensions.toast +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import org.kiwix.kiwixmobile.utils.BookUtils +import org.kiwix.kiwixmobile.utils.Constants.REQUEST_STORAGE_PERMISSION +import org.kiwix.kiwixmobile.utils.DialogShower +import org.kiwix.kiwixmobile.utils.KiwixDialog.DeleteZim +import org.kiwix.kiwixmobile.utils.LanguageUtils +import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.utils.files.FileUtils +import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel +import javax.inject.Inject + +class ZimFileSelectFragment : BaseFragment() { + + @Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil + @Inject lateinit var bookDao: BookDao + @Inject lateinit var dialogShower: DialogShower + @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject lateinit var bookUtils: BookUtils + + private lateinit var zimManageViewModel: ZimManageViewModel + + private val rescanAdapter: RescanDataAdapter by lazy { + RescanDataAdapter( + bookUtils, this::open, this::tryToDelete + ) + } + + override fun inject(activityComponent: ActivityComponent) { + activityComponent.inject(this) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Replace LinearLayout by the type of the root element of the layout you're trying to load + LanguageUtils(activity!!).changeFont(activity!!.layoutInflater, sharedPreferenceUtil) + return inflater.inflate(R.layout.zim_list, container, false) + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + zimManageViewModel = ViewModelProviders.of(activity!!, viewModelFactory) + .get(ZimManageViewModel::class.java) + zim_swiperefresh.setOnRefreshListener(this::requestFileSystemCheck) + zimfilelist.run { + adapter = rescanAdapter + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + setHasFixedSize(true) + } + zimManageViewModel.bookItems.observe(this, Observer { + rescanAdapter.itemList = it!! + checkEmpty(it) + }) + zimManageViewModel.deviceListIsRefreshing.observe(this, Observer { + zim_swiperefresh.isRefreshing = it!! + }) + } + + override fun onResume() { + super.onResume() + checkPermissions() + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + when (requestCode) { + REQUEST_STORAGE_PERMISSION -> { + if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + requestFileSystemCheck() + } else if (grantResults.size != 0) { + activity!!.finish() + } + } + } + } + + private fun checkEmpty(books: List) { + file_management_no_files.visibility = + if (books.isEmpty()) { + View.VISIBLE + } else + View.GONE + } + + private fun checkPermissions() { + if (ContextCompat.checkSelfPermission( + super.getActivity()!!, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) != PackageManager.PERMISSION_GRANTED && Build.VERSION.SDK_INT > 18 + ) { + context.toast(R.string.request_storage) + requestPermissions( + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + REQUEST_STORAGE_PERMISSION + ) + } else { + requestFileSystemCheck() + } + } + + private fun requestFileSystemCheck() { + zimManageViewModel.checkFileSystem.onNext(Unit) + } + + private fun open(it: Book) { + ZimContentProvider.canIterate = false + if (!it.file.canRead()) { + context.toast(string.error_filenotfound) + } else { + (activity as ZimManageActivity).finishResult(it.file.path) + } + } + + private fun tryToDelete(it: Book) { + dialogShower.show(DeleteZim, { + if (deleteSpecificZimFile(it)) { + context.toast(string.delete_specific_zim_toast) + } else { + context.toast(string.delete_zim_failed) + } + }) + } + + private fun deleteSpecificZimFile(book: Book): Boolean { + val file = book.file + FileUtils.deleteZimFile(file) + if (file.exists()) { + return false + } + bookDao.deleteBook(book.id) + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectPresenter.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectPresenter.java deleted file mode 100644 index d2e87021d6..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectPresenter.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Kiwix Android - * Copyright (C) 2018 Kiwix - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.kiwix.kiwixmobile.zim_manager.fileselect_view; - -import org.kiwix.kiwixmobile.base.BasePresenter; -import org.kiwix.kiwixmobile.database.BookDao; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; - -import java.util.ArrayList; - -import javax.inject.Inject; - -/** - * Created by EladKeyshawn on 06/04/2017. - */ -public class ZimFileSelectPresenter extends BasePresenter { - - @Inject - BookDao bookDao; - - @Inject - public ZimFileSelectPresenter() { - } - - @Override - public void attachView(ZimFileSelectViewCallback mvpView) { - super.attachView(mvpView); - } - - public void loadLocalZimFileFromDb() { - ArrayList books = bookDao.getBooks(); - getMvpView().showFiles(books); - } - -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectViewCallback.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectViewCallback.java deleted file mode 100644 index c5be8a024b..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectViewCallback.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Kiwix Android - * Copyright (C) 2018 Kiwix - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.kiwix.kiwixmobile.zim_manager.fileselect_view; - -import org.kiwix.kiwixmobile.base.ViewCallback; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; - -import java.util.ArrayList; - -/** - * Created by EladKeyshawn on 06/04/2017. - */ -public interface ZimFileSelectViewCallback extends ViewCallback { - void showFiles(ArrayList books); -} diff --git a/app/src/main/res/layout/zim_list.xml b/app/src/main/res/layout/zim_list.xml index 8e278daa73..3eceb0a808 100644 --- a/app/src/main/res/layout/zim_list.xml +++ b/app/src/main/res/layout/zim_list.xml @@ -1,46 +1,46 @@ - - - + > - + - + > + + - - + From 41a753a1e67083b384713be5ca47ebb0a66af7ae Mon Sep 17 00:00:00 2001 From: Sean Mac Gillicuddy Date: Tue, 7 May 2019 13:03:21 +0100 Subject: [PATCH 03/20] LibraryFragment converted to Kotlin and observing reactive streams --- .gitignore | 3 +- .../kiwix/kiwixmobile/tests/DownloadTest.java | 5 +- .../kiwix/kiwixmobile/tests/NetworkTest.java | 2 +- .../kiwixmobile/KiwixMobileActivity.java | 2 +- .../kiwixmobile/database/DownloadDao.java | 10 +- .../database/NetworkLanguageDao.java | 52 +- .../di/components/ApplicationComponent.java | 4 +- .../di/modules/ApplicationModule.java | 5 + .../downloader/DownloadService.java | 2 +- .../ConnectivityManagerExtensions.kt | 12 + .../extensions/ContextExtensions.kt | 17 +- .../extensions/TextViewExtensions.kt | 12 + .../kiwixmobile/extensions/ViewExtensions.kt | 21 + .../kiwixmobile/library/LibraryAdapter.java | 473 ------------------ .../library/entity/LibraryNetworkEntity.java | 2 +- .../kiwixmobile/network/KiwixService.java | 8 +- .../kiwix/kiwixmobile/utils/KiwixDialog.kt | 9 +- .../kiwixmobile/utils/LanguageUtils.java | 10 +- .../kiwixmobile/utils/files/FileSearch.java | 7 +- .../views/LanguageSelectDialog.java | 28 +- .../zim_manager/BaseBroadcastReceiver.kt | 25 + .../ConnectivityBroadcastReceiver.kt | 26 + .../kiwixmobile/zim_manager/NetworkState.kt | 6 + .../zim_manager/ZimManageActivity.java | 25 +- .../zim_manager/ZimManageViewModel.kt | 201 ++++++-- .../{RescanDataAdapter.kt => BooksAdapter.kt} | 14 +- ...RescanViewHolder.kt => BooksViewHolder.kt} | 21 +- .../fileselect_view/StorageObserver.kt | 6 +- .../fileselect_view/ZimFileSelectFragment.kt | 10 +- .../library_view/LibraryFragment.java | 350 ------------- .../library_view/LibraryFragment.kt | 232 +++++++++ .../library_view/LibraryPresenter.java | 64 --- .../adapter/AbsDelegateAdapter.kt | 38 ++ .../library_view/adapter/AdapterDelegate.kt | 16 + .../adapter/AdapterDelegateManager.kt | 38 ++ .../library_view/adapter/Language.kt | 24 + .../library_view/adapter/LibraryAdapter.kt | 82 +++ .../library_view/adapter/LibraryDelegate.kt | 68 +++ .../library_view/adapter/LibraryListItem.kt | 17 + .../library_view/adapter/LibraryViewHolder.kt | 63 +++ .../library_view/adapter/MegaByte.kt | 16 + app/src/main/res/layout/activity_library.xml | 79 ++- .../res/layout/layout_download_management.xml | 4 +- app/src/main/res/layout/library_item.xml | 167 ++++--- app/src/main/res/layout/zim_list.xml | 9 +- app/src/main/res/layout/zim_manager.xml | 68 +-- app/src/main/res/values/dimens.xml | 3 - build.gradle | 2 +- 48 files changed, 1165 insertions(+), 1193 deletions(-) create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/extensions/ConnectivityManagerExtensions.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/extensions/TextViewExtensions.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/extensions/ViewExtensions.kt delete mode 100755 app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/BaseBroadcastReceiver.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ConnectivityBroadcastReceiver.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/NetworkState.kt rename app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/{RescanDataAdapter.kt => BooksAdapter.kt} (75%) rename app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/{RescanViewHolder.kt => BooksViewHolder.kt} (83%) delete mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.java create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.kt delete mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryPresenter.java create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AbsDelegateAdapter.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AdapterDelegate.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AdapterDelegateManager.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/Language.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryAdapter.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryDelegate.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryListItem.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryViewHolder.kt create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/MegaByte.kt diff --git a/.gitignore b/.gitignore index bb9d46f622..bd6b595532 100644 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,5 @@ glassify .project .classpath -.vscode \ No newline at end of file +.vscode +captures/ diff --git a/app/src/androidTestKiwix/java/org/kiwix/kiwixmobile/tests/DownloadTest.java b/app/src/androidTestKiwix/java/org/kiwix/kiwixmobile/tests/DownloadTest.java index ca45dd161b..1ac2c8040e 100644 --- a/app/src/androidTestKiwix/java/org/kiwix/kiwixmobile/tests/DownloadTest.java +++ b/app/src/androidTestKiwix/java/org/kiwix/kiwixmobile/tests/DownloadTest.java @@ -51,7 +51,6 @@ import static android.support.test.espresso.matcher.ViewMatchers.withParent; import static com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed; import static com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickOn; -import static com.schibsted.spain.barista.interaction.BaristaDialogInteractions.clickDialogPositiveButton; import static com.schibsted.spain.barista.interaction.BaristaSwipeRefreshInteractions.refresh; import static junit.framework.Assert.fail; import static org.hamcrest.Matchers.allOf; @@ -115,13 +114,13 @@ public void downloadTest() { BaristaSleepInteractions.sleep(TEST_PAUSE_MS); try { - onData(withContent("ray_charles")).inAdapterView(withId(R.id.library_list)); + onData(withContent("ray_charles")).inAdapterView(withId(R.id.libraryList)); } catch (Exception e) { fail("Couldn't find downloaded file 'ray_charles'\n\nOriginal Exception:\n" + e.getLocalizedMessage() + "\n\n" ); } - deleteZimIfExists("ray_charles", R.id.library_list); + deleteZimIfExists("ray_charles", R.id.libraryList); assertDisplayed(R.string.local_zims); clickOn(R.string.local_zims); diff --git a/app/src/androidTestKiwix/java/org/kiwix/kiwixmobile/tests/NetworkTest.java b/app/src/androidTestKiwix/java/org/kiwix/kiwixmobile/tests/NetworkTest.java index 44d3fbf65e..1ea4f276a2 100644 --- a/app/src/androidTestKiwix/java/org/kiwix/kiwixmobile/tests/NetworkTest.java +++ b/app/src/androidTestKiwix/java/org/kiwix/kiwixmobile/tests/NetworkTest.java @@ -137,7 +137,7 @@ public void networkTest() { "Permission dialog was not shown, we probably already have required permissions"); } - onData(withContent("wikipedia_ab_all_2017-03")).inAdapterView(withId(R.id.library_list)).perform(click()); + onData(withContent("wikipedia_ab_all_2017-03")).inAdapterView(withId(R.id.libraryList)).perform(click()); try { onView(withId(android.R.id.button1)).perform(click()); diff --git a/app/src/main/java/org/kiwix/kiwixmobile/KiwixMobileActivity.java b/app/src/main/java/org/kiwix/kiwixmobile/KiwixMobileActivity.java index a542609dc2..f733f117de 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/KiwixMobileActivity.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/KiwixMobileActivity.java @@ -482,7 +482,7 @@ public void sectionsLoaded(String title, List sections) { } if (i.hasExtra(EXTRA_ZIM_FILE)) { File file = new File(getFileName(i.getStringExtra(EXTRA_ZIM_FILE))); - LibraryFragment.mService.cancelNotification(i.getIntExtra(EXTRA_NOTIFICATION_ID, 0)); + //LibraryFragment.mService.cancelNotification(i.getIntExtra(EXTRA_NOTIFICATION_ID, 0)); Uri uri = Uri.fromFile(file); finish(); diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/DownloadDao.java b/app/src/main/java/org/kiwix/kiwixmobile/database/DownloadDao.java index 02abadb911..ae8c0e68c5 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/database/DownloadDao.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/DownloadDao.java @@ -50,8 +50,14 @@ protected void onDataChanged() { } public void insert(final DownloadModel downloadModel) { - kiwixDatabase.persistWithOnConflict(databaseEntity(downloadModel), - TableStatement.ConflictAlgorithm.REPLACE); + if (doesNotAlreadyExist(downloadModel)) { + kiwixDatabase.persistWithOnConflict(databaseEntity(downloadModel), + TableStatement.ConflictAlgorithm.REPLACE); + } + } + + private boolean doesNotAlreadyExist(DownloadModel downloadModel) { + return kiwixDatabase.count(DownloadDatabaseEntity.class, DownloadDatabaseEntity.BOOK_ID.eq(downloadModel.getBookId())) == 0; } public void delete(@NotNull Long... downloadIds) { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/NetworkLanguageDao.java b/app/src/main/java/org/kiwix/kiwixmobile/database/NetworkLanguageDao.java index a7e2319f76..3e825dd8f8 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/database/NetworkLanguageDao.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/NetworkLanguageDao.java @@ -19,50 +19,62 @@ package org.kiwix.kiwixmobile.database; +import com.yahoo.squidb.data.SimpleDataChangedNotifier; import com.yahoo.squidb.data.SquidCursor; import com.yahoo.squidb.sql.Query; - -import javax.inject.Inject; -import org.kiwix.kiwixmobile.database.entity.NetworkLanguageDatabaseEntity; -import org.kiwix.kiwixmobile.library.LibraryAdapter; -import org.kiwix.kiwixmobile.library.LibraryAdapter.Language; - +import io.reactivex.Flowable; +import io.reactivex.processors.PublishProcessor; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import javax.inject.Inject; +import org.kiwix.kiwixmobile.database.entity.NetworkLanguageDatabaseEntity; +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language; public class NetworkLanguageDao { private KiwixDatabase mDb; + private final PublishProcessor> languageProcessor = + PublishProcessor.create(); @Inject public NetworkLanguageDao(KiwixDatabase kiwikDatabase) { this.mDb = kiwikDatabase; + mDb.registerDataChangedNotifier( + new SimpleDataChangedNotifier(NetworkLanguageDatabaseEntity.TABLE) { + @Override + protected void onDataChanged() { + languageProcessor.onNext(getActiveLanguages()); + } + }); + } + + public Flowable> activeLanguages() { + return languageProcessor.startWith(getActiveLanguages()).distinctUntilChanged(); } - public ArrayList getFilteredLanguages() { - SquidCursor languageCursor = mDb.query( + public List getActiveLanguages() { + ArrayList result = new ArrayList<>(); + try (SquidCursor languageCursor = mDb.query( NetworkLanguageDatabaseEntity.class, - Query.select()); - ArrayList result = new ArrayList<>(); - try { + Query.select().where(NetworkLanguageDatabaseEntity.ENABLED.eq(true)))) { while (languageCursor.moveToNext()) { String languageCode = languageCursor.get(NetworkLanguageDatabaseEntity.LANGUAGE_I_S_O_3); boolean enabled = languageCursor.get(NetworkLanguageDatabaseEntity.ENABLED); - result.add(new LibraryAdapter.Language(languageCode, enabled)); + result.add(new Language(languageCode, enabled)); } - } finally { - languageCursor.close(); } return result; } - public void saveFilteredLanguages(List languages){ + public void saveFilteredLanguages(List languages) { mDb.deleteAll(NetworkLanguageDatabaseEntity.class); - Collections.sort(languages, (language, t1) -> language.language.compareTo(t1.language)); - for (LibraryAdapter.Language language : languages){ - NetworkLanguageDatabaseEntity networkLanguageDatabaseEntity = new NetworkLanguageDatabaseEntity(); - networkLanguageDatabaseEntity.setLanguageISO3(language.languageCode); - networkLanguageDatabaseEntity.setIsEnabled(language.active); + Collections.sort(languages, + (language, t1) -> language.getLanguage().compareTo(t1.getLanguage())); + for (Language language : languages) { + NetworkLanguageDatabaseEntity networkLanguageDatabaseEntity = + new NetworkLanguageDatabaseEntity(); + networkLanguageDatabaseEntity.setLanguageISO3(language.getLanguageCode()); + networkLanguageDatabaseEntity.setIsEnabled(language.getActive()); mDb.persist(networkLanguageDatabaseEntity); } } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/components/ApplicationComponent.java b/app/src/main/java/org/kiwix/kiwixmobile/di/components/ApplicationComponent.java index ae57c7cadd..b385e26e37 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/di/components/ApplicationComponent.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/components/ApplicationComponent.java @@ -27,7 +27,7 @@ import org.kiwix.kiwixmobile.di.modules.JNIModule; import org.kiwix.kiwixmobile.di.modules.NetworkModule; import org.kiwix.kiwixmobile.downloader.DownloadService; -import org.kiwix.kiwixmobile.library.LibraryAdapter; +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryAdapter; import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity; import org.kiwix.kiwixmobile.views.AutoCompleteAdapter; import org.kiwix.kiwixmobile.views.web.KiwixWebView; @@ -56,8 +56,6 @@ interface Builder { void inject(ZimContentProvider zimContentProvider); - void inject(LibraryAdapter libraryAdapter); - void inject(KiwixWebView kiwixWebView); void inject(KiwixSettingsActivity.PrefsFragment prefsFragment); diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java index ff009daad7..14acf2c984 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/ApplicationModule.java @@ -17,6 +17,7 @@ */ package org.kiwix.kiwixmobile.di.modules; +import android.app.Application; import android.app.DownloadManager; import android.app.NotificationManager; import android.content.Context; @@ -32,6 +33,10 @@ }) public class ApplicationModule { + @Provides @Singleton Application provideApplication(Context context) { + return (Application) context; + } + @Provides @Singleton NotificationManager provideNotificationManager(Context context) { return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadService.java b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadService.java index c872f84441..d6266edf0c 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadService.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadService.java @@ -201,7 +201,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { notificationManager.notify(notificationID, notification.get(notificationID).build()); downloadStatus.put(notificationID, PLAY); - LibraryFragment.downloadingBooks.remove(book); + //LibraryFragment.downloadingBooks.remove(book); String url = intent.getExtras().getString(DownloadIntent.DOWNLOAD_URL_PARAMETER); downloadBook(url, notificationID, book); } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/extensions/ConnectivityManagerExtensions.kt b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ConnectivityManagerExtensions.kt new file mode 100644 index 0000000000..80aee2438c --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ConnectivityManagerExtensions.kt @@ -0,0 +1,12 @@ +package org.kiwix.kiwixmobile.extensions + +import android.net.ConnectivityManager +import org.kiwix.kiwixmobile.zim_manager.NetworkState +import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED +import org.kiwix.kiwixmobile.zim_manager.NetworkState.NOT_CONNECTED + +val ConnectivityManager.networkState: NetworkState + get() = if (activeNetworkInfo?.isConnected == true) + CONNECTED + else + NOT_CONNECTED \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt index 361112851f..548f8e2b7e 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt @@ -1,7 +1,9 @@ package org.kiwix.kiwixmobile.extensions import android.content.Context +import android.content.IntentFilter import android.widget.Toast +import org.kiwix.kiwixmobile.zim_manager.BaseBroadcastReceiver fun Context?.toast( stringId: Int, @@ -11,4 +13,17 @@ fun Context?.toast( Toast.makeText(this, stringId, length) .show() } -} \ No newline at end of file +} + +fun Context?.toast( + text: String, + length: Int = Toast.LENGTH_LONG +) { + this?.let { + Toast.makeText(this, text, length) + .show() + } +} + +fun Context.registerReceiver(baseBroadcastReceiver: BaseBroadcastReceiver) = + registerReceiver(baseBroadcastReceiver, IntentFilter(baseBroadcastReceiver.action)) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/extensions/TextViewExtensions.kt b/app/src/main/java/org/kiwix/kiwixmobile/extensions/TextViewExtensions.kt new file mode 100644 index 0000000000..aa48fb3598 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/TextViewExtensions.kt @@ -0,0 +1,12 @@ +package org.kiwix.kiwixmobile.extensions + +import android.view.View +import android.widget.TextView + +fun TextView.setTextAndVisibility(nullableText: String?) = + if (nullableText != null && nullableText.isNotEmpty()) { + text = nullableText + visibility = View.VISIBLE + } else { + visibility = View.GONE + } \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/extensions/ViewExtensions.kt b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ViewExtensions.kt new file mode 100644 index 0000000000..0fae85f289 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ViewExtensions.kt @@ -0,0 +1,21 @@ +package org.kiwix.kiwixmobile.extensions + +import android.graphics.Color +import android.support.design.widget.Snackbar +import android.view.View + +fun View.snack( + stringId: Int, + actionStringId: Int, + actionClick: () -> Unit, + actionTextColor: Int = Color.WHITE +) { + Snackbar.make( + this, stringId, Snackbar.LENGTH_LONG + ) + .setAction(actionStringId) { + actionClick.invoke() + } + .setActionTextColor(actionTextColor) + .show() +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java b/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java deleted file mode 100755 index b4fd127cfc..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java +++ /dev/null @@ -1,473 +0,0 @@ -/* - * Copyright 2013 Rashiq Ahmad - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ - -package org.kiwix.kiwixmobile.library; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.util.Base64; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.Filter; -import android.widget.ImageView; -import android.widget.TextView; -import com.google.common.collect.ImmutableList; -import io.reactivex.Completable; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import javax.inject.Inject; -import org.kiwix.kiwixmobile.KiwixApplication; -import org.kiwix.kiwixmobile.R; -import org.kiwix.kiwixmobile.database.BookDao; -import org.kiwix.kiwixmobile.database.NetworkLanguageDao; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book; -import org.kiwix.kiwixmobile.utils.BookUtils; -import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment; - -import static org.kiwix.kiwixmobile.utils.NetworkUtils.parseURL; - -public class LibraryAdapter extends BaseAdapter { - private static final int LIST_ITEM_TYPE_BOOK = 0; - private static final int LIST_ITEM_TYPE_DIVIDER = 1; - - private ImmutableList allBooks; - private List listItems = new ArrayList<>(); - private final Context context; - public Map languageCounts = new HashMap<>(); - public List languages = new ArrayList<>(); - private final LayoutInflater layoutInflater; - private final BookFilter bookFilter = new BookFilter(); - private Disposable saveNetworkLanguageDisposable; - @Inject BookUtils bookUtils; - @Inject - NetworkLanguageDao networkLanguageDao; - @Inject - BookDao bookDao; - - //TODO: restore functionality of commented out code - public LibraryAdapter(Context context) { - super(); - KiwixApplication.getApplicationComponent().inject(this); - this.context = context; - layoutInflater = LayoutInflater.from(context); - } - - public void setAllBooks(List books) { - allBooks = ImmutableList.copyOf(books); - updateLanguageCounts(); - updateLanguages(); - } - - public boolean isDivider(int position) { - return listItems.get(position).type == LIST_ITEM_TYPE_DIVIDER; - } - - @Override - public int getCount() { - return listItems.size(); - } - - @Override - public Object getItem(int i) { - return listItems.get(i).data; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - ViewHolder holder; - if (position >= listItems.size()) { - return convertView; - } - ListItem item = listItems.get(position); - - if (item.type == LIST_ITEM_TYPE_BOOK) { - if (convertView != null && convertView.findViewById(R.id.title) != null) { - holder = (ViewHolder) convertView.getTag(); - } else { - convertView = layoutInflater.inflate(R.layout.library_item, null); - holder = new ViewHolder(); - holder.title = convertView.findViewById(R.id.title); - holder.description = convertView.findViewById(R.id.description); - holder.language = convertView.findViewById(R.id.language); - holder.creator = convertView.findViewById(R.id.creator); - holder.publisher = convertView.findViewById(R.id.publisher); - holder.date = convertView.findViewById(R.id.date); - holder.size = convertView.findViewById(R.id.size); - holder.fileName = convertView.findViewById(R.id.fileName); - holder.favicon = convertView.findViewById(R.id.favicon); - convertView.setTag(holder); - } - - Book book = (Book) listItems.get(position).data; - - holder.title.setText(book.getTitle()); - holder.description.setText(book.getDescription()); - holder.language.setText(bookUtils.getLanguage(book.getLanguage())); - holder.creator.setText(book.getCreator()); - holder.publisher.setText(book.getPublisher()); - holder.date.setText(book.getDate()); - holder.size.setText(createGbString(book.getSize())); - holder.fileName.setText(parseURL(context, book.getUrl())); - holder.favicon.setImageBitmap(createBitmapFromEncodedString(book.getFavicon(), context)); - - // Check if no value is empty. Set the view to View.GONE, if it is. To View.VISIBLE, if not. - if (book.getTitle() == null || book.getTitle().isEmpty()) { - holder.title.setVisibility(View.GONE); - } else { - holder.title.setVisibility(View.VISIBLE); - } - - if (book.getDescription() == null || book.getDescription().isEmpty()) { - holder.description.setVisibility(View.GONE); - } else { - holder.description.setVisibility(View.VISIBLE); - } - - if (book.getCreator() == null || book.getCreator().isEmpty()) { - holder.creator.setVisibility(View.GONE); - } else { - holder.creator.setVisibility(View.VISIBLE); - } - - if (book.getPublisher() == null || book.getPublisher().isEmpty()) { - holder.publisher.setVisibility(View.GONE); - } else { - holder.publisher.setVisibility(View.VISIBLE); - } - - if (book.getDate() == null || book.getDate().isEmpty()) { - holder.date.setVisibility(View.GONE); - } else { - holder.date.setVisibility(View.VISIBLE); - } - - if (book.getSize() == null || book.getSize().isEmpty()) { - holder.size.setVisibility(View.GONE); - } else { - holder.size.setVisibility(View.VISIBLE); - } - - return convertView; - } else { - if (convertView != null && convertView.findViewById(R.id.divider_text) != null) { - holder = (ViewHolder) convertView.getTag(); - } else { - convertView = layoutInflater.inflate(R.layout.library_divider, null); - holder = new ViewHolder(); - holder.title = convertView.findViewById(R.id.divider_text); - convertView.setTag(holder); - } - - String dividerText = (String) listItems.get(position).data; - - holder.title.setText(dividerText); - - return convertView; - } - } - - private boolean languageActive(Book book) { - return Observable.fromIterable(languages) - .filter(language -> language.languageCode.equals(book.getLanguage())) - .firstElement() - .map(language -> language.active) - .blockingGet(false); - } - - private Observable getMatches(Book b, String s) { - StringBuilder text = new StringBuilder(); - text.append(b.getTitle()).append("|").append(b.getDescription()).append("|") - .append(parseURL(context, b.getUrl())).append("|"); - if (bookUtils.localeMap.containsKey(b.getLanguage())) { - text.append(bookUtils.localeMap.get(b.getLanguage()).getDisplayLanguage()).append("|"); - } - String[] words = s.toLowerCase().split("\\s+"); - b.searchMatches = Observable.fromArray(words) - .filter(text.toString().toLowerCase()::contains) - .count() - .blockingGet() - .intValue(); - if (b.searchMatches > 0) { - return Observable.just(b); - } else { - return Observable.empty(); - } - } - - private class BookFilter extends Filter { - @Override - protected FilterResults performFiltering(CharSequence s) { - List books = bookDao.getBooks(); - listItems.clear(); - if (s.length() == 0) { - List selectedLanguages = Observable.fromIterable(allBooks) - .filter(LibraryAdapter.this::languageActive) - .filter(book -> !books.contains(book)) - // .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) - .filter(book -> !LibraryFragment.downloadingBooks.contains(book)) - .filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694 - .toList() - .blockingGet(); - - List unselectedLanguages = Observable.fromIterable(allBooks) - .filter(book -> !languageActive(book)) - .filter(book -> !books.contains(book)) - // .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) - .filter(book -> !LibraryFragment.downloadingBooks.contains(book)) - .filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694 - .toList() - .blockingGet(); - - listItems.add(new ListItem(context.getResources().getString(R.string.your_languages), LIST_ITEM_TYPE_DIVIDER)); - addBooks(selectedLanguages); - listItems.add(new ListItem(context.getResources().getString(R.string.other_languages), LIST_ITEM_TYPE_DIVIDER)); - addBooks(unselectedLanguages); - } else { - List selectedLanguages = Observable.fromIterable(allBooks) - .filter(LibraryAdapter.this::languageActive) - .filter(book -> !books.contains(book)) - // .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) - .filter(book -> !LibraryFragment.downloadingBooks.contains(book)) - .filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694 - .flatMap(book -> getMatches(book, s.toString())) - .toList() - .blockingGet(); - - Collections.sort(selectedLanguages, new BookMatchComparator()); - - List unselectedLanguages = Observable.fromIterable(allBooks) - .filter(book -> !languageActive(book)) - .filter(book -> !books.contains(book)) - // .filter(book -> !DownloadFragment.mDownloads.values().contains(book)) - .filter(book -> !LibraryFragment.downloadingBooks.contains(book)) - .filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694 - .flatMap(book -> getMatches(book, s.toString())) - .toList() - .blockingGet(); - - Collections.sort(unselectedLanguages, new BookMatchComparator()); - - listItems.add(new ListItem("In your language:", LIST_ITEM_TYPE_DIVIDER)); - addBooks(selectedLanguages); - listItems.add(new ListItem("In other languages:", LIST_ITEM_TYPE_DIVIDER)); - addBooks(unselectedLanguages); - } - - FilterResults results = new FilterResults(); - results.values = listItems; - results.count = listItems.size(); - return results; - } - - @Override - protected void publishResults(CharSequence constraint, FilterResults results) { - List filtered = (List) results.values; - if (filtered != null) { - if (filtered.isEmpty()) { - addBooks(allBooks); - } - } - notifyDataSetChanged(); - } - } - - public Filter getFilter() { - return bookFilter; - } - - public void updateNetworkLanguages() { - saveNetworkLanguages(); - } - - private void updateLanguageCounts() { - languageCounts.clear(); - for (Book book : allBooks) { - Integer cnt = languageCounts.get(book.getLanguage()); - if (cnt == null) { - languageCounts.put(book.getLanguage(), 1); - } else { - languageCounts.put(book.getLanguage(), cnt + 1); - } - } - } - - private void updateLanguages() { - // Load previously stored languages and extract which ones were enabled. The new book list might - // have new languages, or be missing some old ones so we want to refresh it, but retain user's - // selections. - Set enabled_languages = new HashSet<>(); - for (Language language : networkLanguageDao.getFilteredLanguages()) { - if (language.active) { - enabled_languages.add(language.languageCode); - } - } - - // Populate languages with all available locales, which appear in the current list of all books. - this.languages = new ArrayList<>(); - for (String iso_language : Locale.getISOLanguages()) { - Locale locale = new Locale(iso_language); - if (languageCounts.get(locale.getISO3Language()) != null) { - // Enable this language either if it was enabled previously, or if it is the device language. - if (enabled_languages.contains(locale.getISO3Language()) || - context.getResources().getConfiguration().locale.getISO3Language().equals(locale.getISO3Language())) { - this.languages.add(new Language(locale, true)); - } else { - this.languages.add(new Language(locale, false)); - } - } - } - - saveNetworkLanguages(); - } - - private void addBooks(List books) { - for (Book book : books) { - listItems.add(new ListItem(book, LIST_ITEM_TYPE_BOOK)); - } - } - - // Create a string that represents the size of the zim file in a human readable way - public static String createGbString(String megaByte) { - - int size = 0; - try { - size = Integer.parseInt(megaByte); - } catch (NumberFormatException e) { - e.printStackTrace(); - } - - if (size <= 0) { - return ""; - } - - final String[] units = new String[]{"KB", "MB", "GB", "TB"}; - int conversion = (int) (Math.log10(size) / Math.log10(1024)); - return new DecimalFormat("#,##0.#") - .format(size / Math.pow(1024, conversion)) - + " " - + units[conversion]; - } - - // Decode and create a Bitmap from the 64-Bit encoded favicon string - public static Bitmap createBitmapFromEncodedString(String encodedString, Context context) { - - try { - byte[] decodedString = Base64.decode(encodedString, Base64.DEFAULT); - return BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length); - } catch (Exception e) { - e.printStackTrace(); - } - - return BitmapFactory.decodeResource(context.getResources(), R.mipmap.kiwix_icon); - } - - private static class ViewHolder { - - TextView title; - - TextView description; - - TextView language; - - TextView creator; - - TextView publisher; - - TextView date; - - TextView size; - - TextView fileName; - - ImageView favicon; - } - - private class ListItem { - public Object data; - public int type; - - public ListItem(Object data, int type) { - this.data = data; - this.type = type; - } - } - - private class BookMatchComparator implements Comparator { - public int compare(Book book1, Book book2) { - return book2.searchMatches - book1.searchMatches; - } - } - - public static class Language { - public String language; - public String languageLocalized; - public String languageCode; - public String languageCodeISO2; - public Boolean active; - - Language(Locale locale, Boolean active) { - this.language = locale.getDisplayLanguage(); - this.languageLocalized = locale.getDisplayLanguage(locale); - this.languageCode = locale.getISO3Language(); - this.languageCodeISO2 = locale.getLanguage(); - - this.active = active; - } - - public Language(String languageCode, Boolean active) { - this(new Locale(languageCode), active); - } - - @Override - public boolean equals(Object obj) { - return ((Language) obj).language.equals(language) && - ((Language) obj).active.equals(active); - } - } - - private void saveNetworkLanguages() { - if (saveNetworkLanguageDisposable != null && !saveNetworkLanguageDisposable.isDisposed()) { - saveNetworkLanguageDisposable.dispose(); - } - saveNetworkLanguageDisposable = Completable.fromAction(() -> networkLanguageDao.saveFilteredLanguages(languages)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(); - } -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/library/entity/LibraryNetworkEntity.java b/app/src/main/java/org/kiwix/kiwixmobile/library/entity/LibraryNetworkEntity.java index 9b6348ee96..2133859dd7 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/library/entity/LibraryNetworkEntity.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/library/entity/LibraryNetworkEntity.java @@ -29,7 +29,7 @@ public class LibraryNetworkEntity { @ElementList(name = "book", inline = true, required = false) - private LinkedList book; + public LinkedList book; @Attribute(name = "version", required = false) private String version; diff --git a/app/src/main/java/org/kiwix/kiwixmobile/network/KiwixService.java b/app/src/main/java/org/kiwix/kiwixmobile/network/KiwixService.java index 551bc8b4e9..2d1da4f578 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/network/KiwixService.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/network/KiwixService.java @@ -17,12 +17,12 @@ */ package org.kiwix.kiwixmobile.network; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; -import org.kiwix.kiwixmobile.library.entity.MetaLinkNetworkEntity; - +import io.reactivex.Flowable; import io.reactivex.Observable; import io.reactivex.schedulers.Schedulers; import okhttp3.OkHttpClient; +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; +import org.kiwix.kiwixmobile.library.entity.MetaLinkNetworkEntity; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.simplexml.SimpleXmlConverterFactory; @@ -30,7 +30,7 @@ import retrofit2.http.Url; public interface KiwixService { - @GET("/library/library_zim.xml") Observable getLibrary(); + @GET("/library/library_zim.xml") Flowable getLibrary(); @GET Observable getMetaLinks(@Url String url); diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt b/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt index 851b20acf0..e850b0e067 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt @@ -16,7 +16,7 @@ sealed class KiwixDialog( open class YesNoDialog( title: Int, message: Int - ) : KiwixDialog(title, message, R.string.yes, R.string.no){ + ) : KiwixDialog(title, message, R.string.yes, R.string.no) { object NoWifi : YesNoDialog( R.string.wifi_only_title, R.string.wifi_only_msg ) @@ -25,9 +25,8 @@ sealed class KiwixDialog( R.string.confirm_stop_download_title, R.string.confirm_stop_download_msg ) - + object WifiOnly : YesNoDialog( + R.string.wifi_only_title, R.string.wifi_only_msg + ) } - - - } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageUtils.java b/app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageUtils.java index f603402fb0..a27393c991 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageUtils.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/LanguageUtils.java @@ -19,7 +19,6 @@ package org.kiwix.kiwixmobile.utils; -import android.annotation.TargetApi; import android.content.Context; import android.content.res.Configuration; import android.graphics.Typeface; @@ -33,7 +32,7 @@ import android.view.View; import android.widget.TextView; -import org.kiwix.kiwixmobile.library.LibraryAdapter; +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language; import org.kiwix.kiwixmobile.utils.files.FileUtils; import java.lang.reflect.Field; @@ -45,7 +44,6 @@ import java.util.Locale; import java.util.MissingResourceException; -import static org.kiwix.kiwixmobile.utils.Constants.PREF_LANG; import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX; public class LanguageUtils { @@ -239,11 +237,11 @@ public List getValues() { return values; } - public List getLanguageList() { - List values = new ArrayList<>(); + public List getLanguageList() { + List values = new ArrayList<>(); for (LanguageContainer value : mLanguageList) { - values.add(new LibraryAdapter.Language(value.getLanguageCode(), false)); + values.add(new Language(value.getLanguageCode(), false)); } return values; diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/files/FileSearch.java b/app/src/main/java/org/kiwix/kiwixmobile/utils/files/FileSearch.java index ace5b49065..fe17026a54 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/utils/files/FileSearch.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/files/FileSearch.java @@ -27,6 +27,7 @@ import android.provider.MediaStore; import android.util.Log; +import java.util.ArrayList; import org.kiwix.kiwixmobile.ZimContentProvider; import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; @@ -109,11 +110,13 @@ public void scanFileSystem(String defaultPath) { FilenameFilter[] filter = new FilenameFilter[zimFiles.length]; // Search all external directories that we can find. - String[] tempRoots = new String[StorageDeviceUtils.getStorageDevices(context, false).size() + 2]; + final ArrayList storageDevices = + StorageDeviceUtils.getStorageDevices(context, false); + String[] tempRoots = new String[storageDevices.size() + 2]; int j = 0; tempRoots[j++] = "/mnt"; tempRoots[j++] = defaultPath; - for (StorageDevice storageDevice : StorageDeviceUtils.getStorageDevices(context, false)) { + for (StorageDevice storageDevice : storageDevices) { tempRoots[j++] = storageDevice.getName(); } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageSelectDialog.java b/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageSelectDialog.java index a808fdb3d1..5a8cb9378d 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageSelectDialog.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageSelectDialog.java @@ -30,7 +30,7 @@ import android.widget.TextView; import org.kiwix.kiwixmobile.R; -import org.kiwix.kiwixmobile.library.LibraryAdapter; +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language; import org.kiwix.kiwixmobile.utils.LanguageUtils; import java.util.List; @@ -46,7 +46,7 @@ protected LanguageSelectDialog(@NonNull Context context) { } public static class Builder extends AlertDialog.Builder { - private List languages; + private List languages; private Map languageCounts; private boolean singleSelect = false; private String selectedLanguage; @@ -60,7 +60,7 @@ public Builder(@NonNull Context context, int themeResId) { super(context, themeResId); } - public Builder setLanguages(List languages) { + public Builder setLanguages(List languages) { this.languages = languages; return this; } @@ -114,13 +114,13 @@ public AlertDialog create() { } } - private static class LanguageArrayAdapter extends ArrayAdapter { + private static class LanguageArrayAdapter extends ArrayAdapter { private Map languageCounts; private Context context; private boolean singleSelect; private String selectedLanguage; - public LanguageArrayAdapter(Context context, int textViewResourceId, List languages, + public LanguageArrayAdapter(Context context, int textViewResourceId, List languages, Map languageCounts, boolean singleSelect, String selectedLanguage) { super(context, textViewResourceId, languages); this.languageCounts = languageCounts; @@ -150,36 +150,36 @@ public View getView(int position, View convertView, @NonNull ViewGroup parent) { // Set event listeners first, since updating the default values can trigger them. holder.row.setOnClickListener((view) -> holder.checkBox.toggle()); holder.checkBox - .setOnCheckedChangeListener((compoundButton, b) -> getItem(position).active = b); + .setOnCheckedChangeListener((compoundButton, b) -> getItem(position).setActive(b)); - LibraryAdapter.Language language = getItem(position); - holder.language.setText(language.language); + Language language = getItem(position); + holder.language.setText(language.getLanguage()); holder.languageLocalized.setText(context.getString(R.string.language_localized, - language.languageLocalized)); + language.getLanguageLocalized())); holder.languageLocalized.setTypeface(Typeface.createFromAsset(context.getAssets(), - LanguageUtils.getTypeface(language.languageCode))); + LanguageUtils.getTypeface(language.getLanguageCode()))); if (languageCounts != null) { holder.languageEntriesCount.setText(context.getString(R.string.language_count, - languageCounts.get(language.languageCode))); + languageCounts.get(language.getLanguageCode()))); } else { holder.languageEntriesCount.setVisibility(View.GONE); } if (!singleSelect) { - holder.checkBox.setChecked(language.active); + holder.checkBox.setChecked(language.getActive()); } else { holder.checkBox.setClickable(false); holder.checkBox.setFocusable(false); - if (getSelectedLanguage().equalsIgnoreCase(language.languageCodeISO2)) { + if (getSelectedLanguage().equalsIgnoreCase(language.getLanguageCodeISO2())) { holder.checkBox.setChecked(true); } else { holder.checkBox.setChecked(false); } convertView.setOnClickListener((v -> { - setSelectedLanguage(language.languageCodeISO2); + setSelectedLanguage(language.getLanguageCodeISO2()); notifyDataSetChanged(); })); } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/BaseBroadcastReceiver.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/BaseBroadcastReceiver.kt new file mode 100644 index 0000000000..43e26236b1 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/BaseBroadcastReceiver.kt @@ -0,0 +1,25 @@ +package org.kiwix.kiwixmobile.zim_manager + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +abstract class BaseBroadcastReceiver : BroadcastReceiver() { + +abstract val action:String + + override fun onReceive( + context: Context, + intent: Intent? + ) { + if (intent?.action == action) { + onIntentWithActionReceived(context, intent) + } + } + + abstract fun onIntentWithActionReceived( + context: Context, + intent: Intent + ) + +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ConnectivityBroadcastReceiver.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ConnectivityBroadcastReceiver.kt new file mode 100644 index 0000000000..028712c7aa --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ConnectivityBroadcastReceiver.kt @@ -0,0 +1,26 @@ +package org.kiwix.kiwixmobile.zim_manager + +import android.content.Context +import android.content.Intent +import android.net.ConnectivityManager +import io.reactivex.processors.PublishProcessor +import org.kiwix.kiwixmobile.extensions.networkState +import javax.inject.Inject + +class ConnectivityBroadcastReceiver @Inject constructor(private val connectivityManager: ConnectivityManager) : + BaseBroadcastReceiver() { + + override val action: String = ConnectivityManager.CONNECTIVITY_ACTION + + private val _networkStates = + PublishProcessor.create() + val networkStates = _networkStates.startWith(connectivityManager.networkState) + + override fun onIntentWithActionReceived( + context: Context, + intent: Intent + ) { + _networkStates.onNext(connectivityManager.networkState) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/NetworkState.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/NetworkState.kt new file mode 100644 index 0000000000..6ec44f11e2 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/NetworkState.kt @@ -0,0 +1,6 @@ +package org.kiwix.kiwixmobile.zim_manager + +enum class NetworkState { + CONNECTED, + NOT_CONNECTED +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java index 2aa3c14bf8..5122da4157 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java @@ -33,7 +33,6 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; -import android.widget.Toast; import java.io.File; import javax.inject.Inject; import org.kiwix.kiwixmobile.KiwixMobileActivity; @@ -217,9 +216,9 @@ public boolean onQueryTextSubmit(String s) { public boolean onQueryTextChange(String s) { searchQuery = s; - if (mSectionsPagerAdapter.libraryFragment.libraryAdapter != null) { - mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(searchQuery); - } + //if (mSectionsPagerAdapter.libraryFragment.libraryAdapter != null) { + // mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(searchQuery); + //} mViewPager.setCurrentItem(1); return true; } @@ -235,11 +234,11 @@ public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.select_language: if (mViewPager.getCurrentItem() == 1) { - if(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languages.size() == 0) { - Toast.makeText(this, R.string.wait_for_load, Toast.LENGTH_LONG).show(); - } else { - showLanguageSelect(); - } + //if(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languages.size() == 0) { + // Toast.makeText(this, R.string.wait_for_load, Toast.LENGTH_LONG).show(); + //} else { + // showLanguageSelect(); + //} } default: return super.onOptionsItemSelected(item); @@ -262,11 +261,11 @@ public void finishResult(String path) { private void showLanguageSelect() { new LanguageSelectDialog.Builder(this, dialogStyle()) - .setLanguages(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languages) - .setLanguageCounts(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languageCounts) + //.setLanguages(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languages) + //.setLanguageCounts(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languageCounts) .setPositiveButton(android.R.string.ok, (dialogInterface, i) -> { - mSectionsPagerAdapter.libraryFragment.libraryAdapter.updateNetworkLanguages(); - mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(searchQuery); + //mSectionsPagerAdapter.libraryFragment.libraryAdapter.updateNetworkLanguages(); + //mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(searchQuery); }) .show(); } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt index 674daa5f85..afaa87917d 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt @@ -1,22 +1,35 @@ package org.kiwix.kiwixmobile.zim_manager +import android.app.Application import android.arch.lifecycle.MutableLiveData import android.arch.lifecycle.ViewModel import io.reactivex.Flowable import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable import io.reactivex.functions.BiFunction +import io.reactivex.functions.Function4 import io.reactivex.processors.PublishProcessor import io.reactivex.schedulers.Schedulers +import org.kiwix.kiwixmobile.R.string import org.kiwix.kiwixmobile.database.BookDao import org.kiwix.kiwixmobile.database.DownloadDao +import org.kiwix.kiwixmobile.database.NetworkLanguageDao import org.kiwix.kiwixmobile.downloader.Downloader import org.kiwix.kiwixmobile.downloader.model.DownloadItem import org.kiwix.kiwixmobile.downloader.model.DownloadModel import org.kiwix.kiwixmobile.downloader.model.DownloadState.Successful import org.kiwix.kiwixmobile.downloader.model.DownloadStatus +import org.kiwix.kiwixmobile.extensions.registerReceiver +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import org.kiwix.kiwixmobile.network.KiwixService import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.DividerItem +import java.util.LinkedList +import java.util.Locale import java.util.concurrent.TimeUnit.SECONDS import javax.inject.Inject @@ -40,30 +53,160 @@ import javax.inject.Inject class ZimManageViewModel @Inject constructor( private val downloadDao: DownloadDao, private val bookDao: BookDao, + private val languageDao: NetworkLanguageDao, private val downloader: Downloader, - private val storageObserver: StorageObserver + private val storageObserver: StorageObserver, + private val kiwixService: KiwixService, + private val context: Application, + private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver ) : ViewModel() { + val libraryItems: MutableLiveData> = MutableLiveData() val downloadItems: MutableLiveData> = MutableLiveData() val bookItems: MutableLiveData> = MutableLiveData() - val checkFileSystem = PublishProcessor.create() val deviceListIsRefreshing = MutableLiveData() + val libraryListIsRefreshing = MutableLiveData() + val networkStates = MutableLiveData() + + val requestFileSystemCheck = PublishProcessor.create() + val requestDownloadLibrary = PublishProcessor.create() private val compositeDisposable = CompositeDisposable() init { - val downloadStatuses = downloadStatuses() - val booksFromDao = books() - compositeDisposable.addAll( + compositeDisposable.addAll(*disposables()) + requestDownloadLibrary.onNext(Unit) + context.registerReceiver(connectivityBroadcastReceiver) + } + + override fun onCleared() { + compositeDisposable.clear() + context.unregisterReceiver(connectivityBroadcastReceiver) + super.onCleared() + } + + private fun disposables(): Array { + val downloads: Flowable> = downloadDao.downloads() + val downloadStatuses = downloadStatuses(downloads) + val booksFromDao: Flowable> = books() + val library = libraryFromNetwork() + return arrayOf( updateDownloadItems(downloadStatuses), removeCompletedDownloadsFromDb(downloadStatuses), updateBookItems(booksFromDao), - checkFileSystemForBooksOnRequest(booksFromDao) + checkFileSystemForBooksOnRequest(booksFromDao), + updateLibraryItems(booksFromDao, downloads, library), + updateActiveLanguages(library), + updateNetworkStates() + ) + } + + private fun updateNetworkStates() = + connectivityBroadcastReceiver.networkStates.subscribe( + networkStates::postValue, Throwable::printStackTrace ) + + private fun libraryFromNetwork() = + Flowable.combineLatest( + requestDownloadLibrary, + connectivityBroadcastReceiver.networkStates.filter( + NetworkState.CONNECTED::equals + ), + BiFunction { _, _ -> Unit } + ) + .subscribeOn(Schedulers.io()) + .doOnNext { libraryListIsRefreshing.postValue(true) } + .switchMap { kiwixService.library } + .doOnError(Throwable::printStackTrace) + .onErrorResumeNext(Flowable.just(LibraryNetworkEntity().apply { book = LinkedList() })) + .doOnNext { libraryListIsRefreshing.postValue(false) } + + private fun updateLibraryItems( + booksFromDao: Flowable>, + downloads: Flowable>, + library: Flowable? + ) = Flowable.combineLatest( + booksFromDao, + downloads, + languageDao.activeLanguages().filter { it.isNotEmpty() }, + library, + Function4(this::combineLibrarySources) + ) + .subscribeOn(Schedulers.io()) + .subscribe( + libraryItems::postValue, + Throwable::printStackTrace + ) + + private fun updateActiveLanguages(library: Flowable) = library + .subscribeOn(Schedulers.io()) + .map { it.books } + .withLatestFrom( + languageDao.activeLanguages(), + BiFunction(this::combineToLanguageList) + ) + .subscribe( + languageDao::saveFilteredLanguages, + Throwable::printStackTrace + ) + + private fun combineToLanguageList( + booksFromNetwork: List, + activeLanguages: List + ): List { + val languagesFromNetwork = booksFromNetwork.distinctBy { it.language } + .map { it.language } + return Locale.getISOLanguages() + .map { Locale(it) } + .filter { languagesFromNetwork.contains(it.isO3Language) } + .map { locale -> + Language( + locale.isO3Language, + languageWasPreviouslyActiveOrIsPrimaryLanguage(activeLanguages, locale) + ) + } + .ifEmpty { listOf(Language(context.resources.configuration.locale.isO3Language, true)) } } - private fun checkFileSystemForBooksOnRequest(booksFromDao: Flowable>): Disposable? { - return checkFileSystem + private fun languageWasPreviouslyActiveOrIsPrimaryLanguage( + activeLanguages: List, + locale: Locale + ) = activeLanguages.firstOrNull { it.languageCode == locale.isO3Language }?.let { true } + ?: isPrimaryLocale(locale) + + private fun isPrimaryLocale(locale: Locale) = + context.resources.configuration.locale.isO3Language == locale.isO3Language + + private fun combineLibrarySources( + booksOnFileSystem: List, + activeDownloads: List, + activeLanguages: List, + libraryNetworkEntity: LibraryNetworkEntity + ): List { + val downloadedBooksIds = booksOnFileSystem.map { it.id } + val downloadingBookIds = activeDownloads.map { it.bookId } + val activeLanguageCodes = activeLanguages.map { it.languageCode } + val booksUnfilteredByLanguage = libraryNetworkEntity.books + .filterNot { downloadedBooksIds.contains(it.id) } + .filterNot { downloadingBookIds.contains(it.id) } + .filterNot { it.url.contains("/stack_exchange/") }// Temp filter see #694 + return listOf( + DividerItem(Long.MAX_VALUE, context.getString(string.your_languages)), + *toBookItems( + booksUnfilteredByLanguage.filter { activeLanguageCodes.contains(it.language) } + ), + DividerItem(Long.MIN_VALUE, context.getString(string.other_languages)), + *toBookItems( + booksUnfilteredByLanguage.filterNot { activeLanguageCodes.contains(it.language) } + ) + ) + } + + private fun toBookItems(books: List) = + books.map { BookItem(it) }.toTypedArray() + + private fun checkFileSystemForBooksOnRequest(booksFromDao: Flowable>) = + requestFileSystemCheck .doOnNext { deviceListIsRefreshing.postValue(true) } .switchMap { updateBookDaoFromFilesystem(booksFromDao) @@ -73,20 +216,24 @@ class ZimManageViewModel @Inject constructor( bookDao::saveBooks, Throwable::printStackTrace ) - } private fun books() = bookDao.books() .subscribeOn(Schedulers.io()) .map { it.sortedBy { book -> book.title } } private fun updateBookDaoFromFilesystem(booksFromDao: Flowable>) = - storageObserver.booksOnFileSystem.withLatestFrom( - booksFromDao, - BiFunction, List, List> { booksFileSystem, booksDao -> - booksFileSystem.minus( - booksDao - ) - }) + storageObserver.booksOnFileSystem + .withLatestFrom( + booksFromDao, + BiFunction(this::removeBooksAlreadyInDao) + ) + + private fun removeBooksAlreadyInDao( + booksFromFileSystem: Collection, + booksFromDao: List + ) = booksFromFileSystem.minus( + booksFromDao + ) private fun updateBookItems(booksFromDao: Flowable>) = booksFromDao @@ -113,17 +260,13 @@ class ZimManageViewModel @Inject constructor( Throwable::printStackTrace ) - private fun downloadStatuses() = Flowable.combineLatest( - downloadDao.downloads(), - Flowable.interval(1, SECONDS), - BiFunction { downloadModels: List, _: Long -> downloadModels } - ) - .subscribeOn(Schedulers.io()) - .map(downloader::queryStatus) - .distinctUntilChanged() - - override fun onCleared() { - compositeDisposable.clear() - super.onCleared() - } + private fun downloadStatuses(downloads: Flowable>) = + Flowable.combineLatest( + downloads, + Flowable.interval(1, SECONDS), + BiFunction { downloadModels: List, _: Long -> downloadModels } + ) + .subscribeOn(Schedulers.io()) + .map(downloader::queryStatus) + .distinctUntilChanged() } \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanDataAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/BooksAdapter.kt similarity index 75% rename from app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanDataAdapter.kt rename to app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/BooksAdapter.kt index c852ab9114..96129212a4 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanDataAdapter.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/BooksAdapter.kt @@ -8,11 +8,11 @@ import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book import org.kiwix.kiwixmobile.utils.BookUtils // The Adapter for the ListView for when the ListView is populated with the rescanned files -public class RescanDataAdapter( - val bookUtils: BookUtils, - val onItemClick: (Book) -> Unit, - val onItemLongClick: (Book) -> Unit -) : RecyclerView.Adapter() { +class BooksAdapter( + private val bookUtils: BookUtils, + private val onItemClick: (Book) -> Unit, + private val onItemLongClick: (Book) -> Unit +) : RecyclerView.Adapter() { init { setHasStableIds(true) @@ -29,12 +29,12 @@ public class RescanDataAdapter( override fun onCreateViewHolder( parent: ViewGroup, viewType: Int - ) = RescanViewHolder(parent.inflate(layout.library_item, false), bookUtils) + ) = BooksViewHolder(parent.inflate(layout.library_item, false), bookUtils) override fun getItemCount() = itemList.size override fun onBindViewHolder( - holder: RescanViewHolder, + holder: BooksViewHolder, position: Int ) { holder.bind(itemList[position], onItemClick, onItemLongClick) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/BooksViewHolder.kt similarity index 83% rename from app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanViewHolder.kt rename to app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/BooksViewHolder.kt index 1b05840098..2f74007ded 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/RescanViewHolder.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/BooksViewHolder.kt @@ -2,9 +2,7 @@ package org.kiwix.kiwixmobile.zim_manager.fileselect_view import android.support.v7.widget.RecyclerView.ViewHolder import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import android.widget.TextView +import butterknife.internal.DebouncingOnClickListener import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.main.library_item.creator import kotlinx.android.synthetic.main.library_item.date @@ -18,13 +16,15 @@ import kotlinx.android.synthetic.main.library_item.title import org.kiwix.kiwixmobile.KiwixApplication import org.kiwix.kiwixmobile.downloader.model.Base64String import org.kiwix.kiwixmobile.extensions.setBitmap +import org.kiwix.kiwixmobile.extensions.setTextAndVisibility import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book import org.kiwix.kiwixmobile.utils.BookUtils import org.kiwix.kiwixmobile.utils.NetworkUtils +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.MegaByte -class RescanViewHolder( +class BooksViewHolder( override val containerView: View, - val bookUtils: BookUtils + private val bookUtils: BookUtils ) : ViewHolder(containerView), LayoutContainer { fun bind( @@ -37,7 +37,7 @@ class RescanViewHolder( creator.setTextAndVisibility(book.creator) publisher.setTextAndVisibility(book.publisher) date.setTextAndVisibility(book.date) - size.setTextAndVisibility(book.size) + size.setTextAndVisibility(MegaByte(book.size).humanReadable) language.text = bookUtils.getLanguage(book.getLanguage()) fileName.text = NetworkUtils.parseURL( KiwixApplication.getInstance(), book.file.path @@ -52,13 +52,6 @@ class RescanViewHolder( return@setOnLongClickListener true } } +} - private fun TextView.setTextAndVisibility(title: String?) = - if (title != null && title.isNotEmpty()) { - text = title - visibility = VISIBLE - } else { - visibility = GONE - } -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt index 5827eefa92..6343913f8c 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt @@ -10,15 +10,15 @@ import org.kiwix.kiwixmobile.utils.files.FileSearch.ResultListener import javax.inject.Inject class StorageObserver @Inject constructor( - val context: Context, - val sharedPreferenceUtil: SharedPreferenceUtil + private val context: Context, + private val sharedPreferenceUtil: SharedPreferenceUtil ) { private val _booksOnFileSystem = PublishProcessor.create>() val booksOnFileSystem = _booksOnFileSystem.distinctUntilChanged() .doOnSubscribe { scanFiles() } - fun scanFiles() { + private fun scanFiles() { FileSearch(context, object : ResultListener { val foundBooks = mutableSetOf() override fun onBookFound(book: Book) { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt index 2baae5f1c0..7b6fc9ae1e 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt @@ -63,8 +63,8 @@ class ZimFileSelectFragment : BaseFragment() { private lateinit var zimManageViewModel: ZimManageViewModel - private val rescanAdapter: RescanDataAdapter by lazy { - RescanDataAdapter( + private val booksAdapter: BooksAdapter by lazy { + BooksAdapter( bookUtils, this::open, this::tryToDelete ) } @@ -92,12 +92,12 @@ class ZimFileSelectFragment : BaseFragment() { .get(ZimManageViewModel::class.java) zim_swiperefresh.setOnRefreshListener(this::requestFileSystemCheck) zimfilelist.run { - adapter = rescanAdapter + adapter = booksAdapter layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) setHasFixedSize(true) } zimManageViewModel.bookItems.observe(this, Observer { - rescanAdapter.itemList = it!! + booksAdapter.itemList = it!! checkEmpty(it) }) zimManageViewModel.deviceListIsRefreshing.observe(this, Observer { @@ -151,7 +151,7 @@ class ZimFileSelectFragment : BaseFragment() { } private fun requestFileSystemCheck() { - zimManageViewModel.checkFileSystem.onNext(Unit) + zimManageViewModel.requestFileSystemCheck.onNext(Unit) } private fun open(it: Book) { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.java deleted file mode 100644 index a3c000538c..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Kiwix Android - * Copyright (C) 2018 Kiwix - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.kiwix.kiwixmobile.zim_manager.library_view; - -import android.app.AlertDialog; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Color; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.Bundle; -import android.support.design.widget.Snackbar; -import android.support.v4.app.FragmentManager; -import android.support.v4.widget.SwipeRefreshLayout; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; -import butterknife.BindView; -import butterknife.ButterKnife; -import eu.mhutti1.utils.storage.StorageDevice; -import eu.mhutti1.utils.storage.support.StorageSelectDialog; -import java.io.File; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import javax.inject.Inject; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.kiwix.kiwixmobile.KiwixMobileActivity; -import org.kiwix.kiwixmobile.R; -import org.kiwix.kiwixmobile.base.BaseFragment; -import org.kiwix.kiwixmobile.di.components.ActivityComponent; -import org.kiwix.kiwixmobile.di.components.ApplicationComponent; -import org.kiwix.kiwixmobile.downloader.DownloadService; -import org.kiwix.kiwixmobile.downloader.Downloader; -import org.kiwix.kiwixmobile.library.LibraryAdapter; -import org.kiwix.kiwixmobile.utils.NetworkUtils; -import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil; -import org.kiwix.kiwixmobile.utils.StyleUtils; -import org.kiwix.kiwixmobile.utils.TestingUtils; -import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity; - -import static android.view.View.GONE; -import static org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book; - -public class LibraryFragment extends BaseFragment - implements AdapterView.OnItemClickListener, StorageSelectDialog.OnSelectListener, LibraryViewCallback { - - @BindView(R.id.library_list) - ListView libraryList; - - @BindView(R.id.network_permission_text) - TextView networkText; - @BindView(R.id.network_permission_button) - Button permissionButton; - - public LinearLayout llLayout; - - @BindView(R.id.library_swiperefresh) - SwipeRefreshLayout swipeRefreshLayout; - - private ArrayList books = new ArrayList<>(); - - public static DownloadService mService = new DownloadService(); - public LibraryAdapter libraryAdapter; - - @Inject - ConnectivityManager conMan; - - @Inject - Downloader downloader; - - private ZimManageActivity faActivity; - - public static NetworkBroadcastReceiver networkBroadcastReceiver; - - public static List downloadingBooks = new ArrayList<>(); - - public static boolean isReceiverRegistered = false; - - @Inject - LibraryPresenter presenter; - - @Inject - SharedPreferenceUtil sharedPreferenceUtil; - - @Override - public void inject(@NotNull ActivityComponent activityComponent) { - activityComponent.inject(this); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - TestingUtils.bindResource(LibraryFragment.class); - llLayout = (LinearLayout) inflater.inflate(R.layout.activity_library, container, false); - ButterKnife.bind(this, llLayout); - presenter.attachView(this); - - networkText = llLayout.findViewById(R.id.network_text); - - faActivity = (ZimManageActivity) super.getActivity(); - swipeRefreshLayout.setOnRefreshListener(() -> refreshFragment()); - libraryAdapter = new LibraryAdapter(super.getContext()); - libraryList.setAdapter(libraryAdapter); - - - - NetworkInfo network = conMan.getActiveNetworkInfo(); - if (network == null || !network.isConnected()) { - displayNoNetworkConnection(); - } - - networkBroadcastReceiver = new NetworkBroadcastReceiver(); - faActivity.registerReceiver(networkBroadcastReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - isReceiverRegistered = true; - - presenter.loadRunningDownloadsFromDb(); - return llLayout; - } - - @Override - public void onStop() { - if (isReceiverRegistered) { - faActivity.unregisterReceiver(networkBroadcastReceiver); - isReceiverRegistered = false; - } - super.onStop(); - } - - @Override - public void showBooks(LinkedList books) { - if (books == null) { - displayNoItemsAvailable(); - return; - } - - Log.i("kiwix-showBooks", "Contains:" + books.size()); - libraryAdapter.setAllBooks(books); - if (faActivity.searchView != null) { - libraryAdapter.getFilter().filter( - faActivity.searchView.getQuery(), - i -> stopScanningContent()); - } else { - libraryAdapter.getFilter().filter("", i -> stopScanningContent()); - } - libraryAdapter.notifyDataSetChanged(); - libraryList.setOnItemClickListener(this); - } - - @Override - public void displayNoNetworkConnection() { - if (books.size() != 0) { - Toast.makeText(super.getActivity(), R.string.no_network_connection, Toast.LENGTH_LONG).show(); - return; - } - - networkText.setText(R.string.no_network_connection); - networkText.setVisibility(View.VISIBLE); - permissionButton.setVisibility(GONE); - swipeRefreshLayout.setRefreshing(false); - swipeRefreshLayout.setEnabled(false); - libraryList.setVisibility(View.INVISIBLE); - TestingUtils.unbindResource(LibraryFragment.class); - } - - @Override - public void displayNoItemsFound() { - networkText.setText(R.string.no_items_msg); - networkText.setVisibility(View.VISIBLE); - permissionButton.setVisibility(GONE); - swipeRefreshLayout.setRefreshing(false); - TestingUtils.unbindResource(LibraryFragment.class); - } - - @Override - public void displayNoItemsAvailable() { - if (books.size() != 0) { - Toast.makeText(super.getActivity(), R.string.no_items_available, Toast.LENGTH_LONG).show(); - return; - } - - networkText.setText(R.string.no_items_available); - networkText.setVisibility(View.VISIBLE); - permissionButton.setVisibility(View.GONE); - swipeRefreshLayout.setRefreshing(false); - TestingUtils.unbindResource(LibraryFragment.class); - } - - @Override - public void displayScanningContent() { - if (!swipeRefreshLayout.isRefreshing()) { - networkText.setVisibility(GONE); - permissionButton.setVisibility(GONE); - swipeRefreshLayout.setEnabled(true); - swipeRefreshLayout.setRefreshing(true); - TestingUtils.bindResource(LibraryFragment.class); - } - } - - - @Override - public void stopScanningContent() { - networkText.setVisibility(GONE); - permissionButton.setVisibility(GONE); - swipeRefreshLayout.setRefreshing(false); - TestingUtils.unbindResource(LibraryFragment.class); - } - - public void refreshFragment() { - NetworkInfo network = conMan.getActiveNetworkInfo(); - if (network == null || !network.isConnected()) { - Toast.makeText(super.getActivity(), R.string.no_network_connection, Toast.LENGTH_LONG).show(); - swipeRefreshLayout.setRefreshing(false); - return; - } - networkBroadcastReceiver.onReceive(super.getActivity(), null); - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - if (!libraryAdapter.isDivider(position)) { - if (getSpaceAvailable() - < Long.parseLong(((Book) (parent.getAdapter().getItem(position))).getSize()) * 1024f) { - Toast.makeText(super.getActivity(), getString(R.string.download_no_space) - + "\n" + getString(R.string.space_available) + " " - + LibraryUtils.bytesToHuman(getSpaceAvailable()), Toast.LENGTH_LONG).show(); - Snackbar snackbar = Snackbar.make(libraryList, - getString(R.string.download_change_storage), - Snackbar.LENGTH_LONG) - .setAction(getString(R.string.open), v -> { - FragmentManager fm = getFragmentManager(); - StorageSelectDialog dialogFragment = new StorageSelectDialog(); - Bundle b = new Bundle(); - b.putString(StorageSelectDialog.STORAGE_DIALOG_INTERNAL, getResources().getString(R.string.internal_storage)); - b.putString(StorageSelectDialog.STORAGE_DIALOG_EXTERNAL, getResources().getString(R.string.external_storage)); - b.putInt(StorageSelectDialog.STORAGE_DIALOG_THEME, StyleUtils.dialogStyle()); - dialogFragment.setArguments(b); - dialogFragment.setOnSelectListener(this); - dialogFragment.show(fm, getResources().getString(R.string.pref_storage)); - }); - snackbar.setActionTextColor(Color.WHITE); - snackbar.show(); - return; - } - - // if (DownloadFragment.mDownloadFiles - // .containsValue(KIWIX_ROOT + StorageUtils.getFileNameFromUrl(((Book) parent.getAdapter() - // .getItem(position)).getUrl()))) { - // Toast.makeText(super.getActivity(), getString(R.string.zim_already_downloading), Toast.LENGTH_LONG) - // .show(); - // } else { - // - // NetworkInfo network = conMan.getActiveNetworkInfo(); - // if (network == null || !network.isConnected()) { - // Toast.makeText(super.getActivity(), getString(R.string.no_network_connection), Toast.LENGTH_LONG) - // .show(); - // return; - // } - - if (KiwixMobileActivity.wifiOnly && !NetworkUtils.isWiFi(getContext())) { - new AlertDialog.Builder(getContext()) - .setTitle(R.string.wifi_only_title) - .setMessage(R.string.wifi_only_msg) - .setPositiveButton(R.string.yes, (dialog, i) -> { - sharedPreferenceUtil.putPrefWifiOnly(false); - KiwixMobileActivity.wifiOnly = false; - downloadFile((Book) parent.getAdapter().getItem(position)); - }) - .setNegativeButton(R.string.no, (dialog, i) -> { - }) - .show(); - } else { - downloadFile((Book) parent.getAdapter().getItem(position)); - } - } - } - - @Override - public void downloadFile(Book book) { - downloader.download(book); - // downloadingBooks.add(book); - // if (libraryAdapter != null && faActivity != null && faActivity.searchView != null) { - // libraryAdapter.getFilter().filter(faActivity.searchView.getQuery()); - // } - // Toast.makeText(super.getActivity(), getString(R.string.download_started_library), Toast.LENGTH_LONG) - // .show(); - // - // ZimManageActivity manage = (ZimManageActivity) super.getActivity(); - // manage.displayDownloadInterface(); - } - - public long getSpaceAvailable() { - return new File(sharedPreferenceUtil.getPrefStorage()).getFreeSpace(); - } - - @Override - public void selectionCallback(StorageDevice storageDevice) { - sharedPreferenceUtil.putPrefStorage(storageDevice.getName()); - if (storageDevice.isInternal()) { - sharedPreferenceUtil.putPrefStorageTitle(getResources().getString(R.string.internal_storage)); - } else { - sharedPreferenceUtil.putPrefStorageTitle(getResources().getString(R.string.external_storage)); - } - } - - - public class NetworkBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - NetworkInfo network = conMan.getActiveNetworkInfo(); - - if (network == null || !network.isConnected()) { - displayNoNetworkConnection(); - } - - if ((books == null || books.isEmpty()) && network != null && network.isConnected()) { - presenter.loadBooks(); - permissionButton.setVisibility(GONE); - networkText.setVisibility(GONE); - libraryList.setVisibility(View.VISIBLE); - } - - } - } -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.kt new file mode 100644 index 0000000000..41bd3ddd04 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.kt @@ -0,0 +1,232 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.zim_manager.library_view + +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProvider +import android.arch.lifecycle.ViewModelProviders +import android.content.Context +import android.net.ConnectivityManager +import android.os.Bundle +import android.support.v7.widget.LinearLayoutManager +import android.view.LayoutInflater +import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE +import android.view.ViewGroup +import eu.mhutti1.utils.storage.StorageDevice +import eu.mhutti1.utils.storage.support.StorageSelectDialog +import kotlinx.android.synthetic.main.activity_library.libraryErrorText +import kotlinx.android.synthetic.main.activity_library.libraryList +import kotlinx.android.synthetic.main.activity_library.librarySwipeRefresh +import org.kiwix.kiwixmobile.KiwixMobileActivity +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.R.string +import org.kiwix.kiwixmobile.base.BaseFragment +import org.kiwix.kiwixmobile.di.components.ActivityComponent +import org.kiwix.kiwixmobile.downloader.Downloader +import org.kiwix.kiwixmobile.extensions.snack +import org.kiwix.kiwixmobile.extensions.toast +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import org.kiwix.kiwixmobile.utils.BookUtils +import org.kiwix.kiwixmobile.utils.DialogShower +import org.kiwix.kiwixmobile.utils.KiwixDialog.YesNoDialog.WifiOnly +import org.kiwix.kiwixmobile.utils.NetworkUtils +import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.utils.StyleUtils +import org.kiwix.kiwixmobile.utils.TestingUtils +import org.kiwix.kiwixmobile.zim_manager.NetworkState +import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED +import org.kiwix.kiwixmobile.zim_manager.NetworkState.NOT_CONNECTED +import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryAdapter +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryDelegate.BookDelegate +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryDelegate.DividerDelegate +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem +import java.io.File +import javax.inject.Inject + +class LibraryFragment : BaseFragment() { + + @Inject lateinit var conMan: ConnectivityManager + @Inject lateinit var downloader: Downloader + @Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil + @Inject lateinit var dialogShower: DialogShower + @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject lateinit var bookUtils: BookUtils + private lateinit var zimManageViewModel: ZimManageViewModel + + private val libraryAdapter: LibraryAdapter by lazy { + LibraryAdapter( + delegates = *arrayOf(BookDelegate(bookUtils, this::onBookItemClick), DividerDelegate) + ) + } + + private val spaceAvailable: Long + get() = File(sharedPreferenceUtil.prefStorage).freeSpace + + private val noWifiWithWifiOnlyPreferenceSet + get() = sharedPreferenceUtil.prefWifiOnly && !NetworkUtils.isWiFi(context!!) + + private val isNotConnected get() = conMan.activeNetworkInfo?.isConnected?.not() ?: true + + override fun inject(activityComponent: ActivityComponent) { + activityComponent.inject(this) + } + + override fun onAttach(context: Context?) { + super.onAttach(context) + zimManageViewModel = ViewModelProviders.of(activity!!, viewModelFactory) + .get(ZimManageViewModel::class.java) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + TestingUtils.bindResource(LibraryFragment::class.java) + return inflater.inflate(R.layout.activity_library, container, false) + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + librarySwipeRefresh.setOnRefreshListener { refreshFragment() } + libraryList.run { + adapter = libraryAdapter + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + setHasFixedSize(true) + } + zimManageViewModel.libraryItems.observe(this, Observer(this::onLibraryItemsChange)) + zimManageViewModel.libraryListIsRefreshing.observe( + this, Observer(this::onRefreshStateChange) + ) + zimManageViewModel.networkStates.observe(this, Observer(this::onNetworkStateChange)) + } + + private fun onRefreshStateChange(isRefreshing: Boolean?) { + librarySwipeRefresh.isRefreshing = isRefreshing!! + } + + private fun onNetworkStateChange(networkState: NetworkState?) { + when (networkState) { + CONNECTED -> { + } + NOT_CONNECTED -> { + if (libraryAdapter.itemCount > 0) { + context.toast(R.string.no_network_connection) + } else { + libraryErrorText.setText(R.string.no_network_connection) + libraryErrorText.visibility = VISIBLE + } + } + } + } + + private fun onLibraryItemsChange(it: List?) { + libraryAdapter.itemList = it!! + if (it.isEmpty()) { + libraryErrorText.setText( + if (isNotConnected) R.string.no_network_connection + else R.string.no_items_msg + ) + libraryErrorText.visibility = VISIBLE + TestingUtils.unbindResource(LibraryFragment::class.java) + } else { + libraryErrorText.visibility = GONE + } + } + + private fun refreshFragment() { + if (isNotConnected) { + context.toast(R.string.no_network_connection) + } else { + zimManageViewModel.requestDownloadLibrary.onNext(Unit) + } + } + + private fun downloadFile(book: Book) { + downloader.download(book) + } + + private fun storeDeviceInPreferences(storageDevice: StorageDevice) { + sharedPreferenceUtil.putPrefStorage(storageDevice.name) + sharedPreferenceUtil.putPrefStorageTitle( + getString( + if (storageDevice.isInternal) R.string.internal_storage + else R.string.external_storage + ) + ) + } + + private fun onBookItemClick(item: BookItem) { + when { + notEnoughSpaceAvailable(item) -> { + context.toast( + getString(R.string.download_no_space) + + "\n" + getString(R.string.space_available) + " " + + LibraryUtils.bytesToHuman(spaceAvailable) + ) + libraryList.snack( + R.string.download_change_storage, + R.string.open, + this::showStorageSelectDialog + ) + return + } + isNotConnected -> { + context.toast(R.string.no_network_connection) + return + } + noWifiWithWifiOnlyPreferenceSet -> { + dialogShower.show(WifiOnly, { + sharedPreferenceUtil.putPrefWifiOnly(false) + KiwixMobileActivity.wifiOnly = false + downloadFile(item.book) + }) + return + } + else -> downloadFile(item.book) + } + } + + private fun notEnoughSpaceAvailable(item: BookItem) = + spaceAvailable < item.book.size.toLong() * 1024f + + private fun showStorageSelectDialog() { + val dialogFragment = StorageSelectDialog() + dialogFragment.arguments = Bundle().apply { + putString( + StorageSelectDialog.STORAGE_DIALOG_INTERNAL, + getString(string.internal_storage) + ) + putString( + StorageSelectDialog.STORAGE_DIALOG_EXTERNAL, + getString(string.external_storage) + ) + putInt(StorageSelectDialog.STORAGE_DIALOG_THEME, StyleUtils.dialogStyle()) + } + dialogFragment.setOnSelectListener(this::storeDeviceInPreferences) + dialogFragment.show(fragmentManager, getString(string.pref_storage)) + } +} + diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryPresenter.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryPresenter.java deleted file mode 100644 index 2d5a3cff32..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryPresenter.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Kiwix Android - * Copyright (C) 2018 Kiwix - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.kiwix.kiwixmobile.zim_manager.library_view; - -import android.util.Log; -import io.reactivex.android.schedulers.AndroidSchedulers; -import javax.inject.Inject; -import org.kiwix.kiwixmobile.base.BasePresenter; -import org.kiwix.kiwixmobile.database.BookDao; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; -import org.kiwix.kiwixmobile.network.KiwixService; - -/** - * Created by EladKeyshawn on 06/04/2017. - */ - -public class LibraryPresenter extends BasePresenter { - - @Inject - KiwixService kiwixService; - - @Inject - BookDao bookDao; - - @Inject - public LibraryPresenter() { - } - - void loadBooks() { - getMvpView().displayScanningContent(); - kiwixService.getLibrary() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(library -> getMvpView().showBooks(library.getBooks()), error -> { - String msg = error.getLocalizedMessage(); - Log.w("kiwixLibrary", "Error loading books:" + (msg != null ? msg : "(null)")); - getMvpView().displayNoItemsFound(); - }); - } - - void loadRunningDownloadsFromDb() { - for (LibraryNetworkEntity.Book book : bookDao.getDownloadingBooks()) { - // if (!DownloadFragment.mDownloads.containsValue(book)) { - book.url = book.remoteUrl; - getMvpView().downloadFile(book); - // } - } - } - -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AbsDelegateAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AbsDelegateAdapter.kt new file mode 100644 index 0000000000..64e28fa9d4 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AbsDelegateAdapter.kt @@ -0,0 +1,38 @@ +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import android.support.v7.widget.RecyclerView +import android.view.ViewGroup + +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +interface AbsDelegateAdapter : AdapterDelegate { + + override fun bind( + viewHolder: RecyclerView.ViewHolder, + itemToBind: SUPERTYPE + ) { + onBindViewHolder(itemToBind as INSTANCE, viewHolder as VIEWHOLDER) + } + + override fun createViewHolder(parent: ViewGroup): VIEWHOLDER + + fun onBindViewHolder( + item: INSTANCE, + holder: VIEWHOLDER + ) +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AdapterDelegate.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AdapterDelegate.kt new file mode 100644 index 0000000000..036c3565a9 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AdapterDelegate.kt @@ -0,0 +1,16 @@ +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import android.support.v7.widget.RecyclerView.ViewHolder +import android.view.ViewGroup + +interface AdapterDelegate { + fun createViewHolder(parent: ViewGroup): ViewHolder + + fun bind( + viewHolder: ViewHolder, + itemToBind: T + ) + + fun isFor(item: T): Boolean + +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AdapterDelegateManager.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AdapterDelegateManager.kt new file mode 100644 index 0000000000..d55f87bc6f --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/AdapterDelegateManager.kt @@ -0,0 +1,38 @@ +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import android.support.v4.util.SparseArrayCompat +import android.support.v7.widget.RecyclerView +import android.view.ViewGroup + +class AdapterDelegateManager() { + fun addDelegate(delegate: AdapterDelegate) { + delegates.put(delegates.size(), delegate) + } + + fun createViewHolder( + parent: ViewGroup, + viewType: Int + ) = delegates[viewType].createViewHolder(parent) + + fun onBindViewHolder( + libraryListItem: T, + holder: RecyclerView.ViewHolder + ) { + delegates[holder.itemViewType].bind(holder, libraryListItem) + } + + fun getViewTypeFor(item: T) = delegates.keyAt(getDelegateIndexFor(item)) + + private fun getDelegateIndexFor(item: T): Int { + for (index in 0..delegates.size()) { + val valueAt = delegates.valueAt(index) + if (valueAt.isFor(item)) { + return index; + } + } + throw RuntimeException("No delegate registered for $item") + } + + var delegates: SparseArrayCompat> = SparseArrayCompat() + +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/Language.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/Language.kt new file mode 100644 index 0000000000..857cefb79f --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/Language.kt @@ -0,0 +1,24 @@ +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import java.util.Locale + +class Language constructor( + locale: Locale, + var active: Boolean?, + var language: String = locale.displayLanguage, + var languageLocalized: String = locale.getDisplayLanguage(locale), + var languageCode: String = locale.isO3Language, + var languageCodeISO2: String = locale.language +) +{ + + constructor( + languageCode: String, + active: Boolean? + ) : this(Locale(languageCode), active) { + } + + override fun equals(obj: Any?): Boolean { + return (obj as Language).language == language && obj.active == active + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryAdapter.kt new file mode 100644 index 0000000000..9c19fd1e36 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryAdapter.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2013 Rashiq Ahmad + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import android.support.v7.widget.RecyclerView +import android.view.ViewGroup + +class LibraryAdapter( + private val delegateManager: AdapterDelegateManager = AdapterDelegateManager(), + vararg delegates: AdapterDelegate +) : RecyclerView.Adapter() { + + init { + delegates.forEach { + delegateManager.addDelegate(it) + } + setHasStableIds(true) + } + + var itemList: List = mutableListOf() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = delegateManager.createViewHolder(parent, viewType) + + override fun getItemCount() = itemList.size + + override fun onBindViewHolder( + holder: RecyclerView.ViewHolder, + position: Int + ) { + delegateManager.onBindViewHolder(itemList[position], holder) + } + + override fun getItemId(position: Int) = itemList[position].id + + override fun getItemViewType(position: Int) = + delegateManager.getViewTypeFor(itemList[position]) + +} + +//private Observable getMatches(Book b, String s) { +// StringBuilder text = new StringBuilder(); +// text.append(b.getTitle()).append("|").append(b.getDescription()).append("|") +// .append(parseURL(context, b.getUrl())).append("|"); +// if (bookUtils.localeMap.containsKey(b.getLanguage())) { +// text.append(bookUtils.localeMap.get(b.getLanguage()).getDisplayLanguage()).append("|"); +// } +// String[] words = s.toLowerCase().split("\\s+"); +// b.searchMatches = Observable.fromArray(words) +// .filter(text.toString().toLowerCase()::contains) +// .count() +// .blockingGet() +// .intValue(); +// if (b.searchMatches > 0) { +// return Observable.just(b); +// } else { +// return Observable.empty(); +// } +//}} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryDelegate.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryDelegate.kt new file mode 100644 index 0000000000..6e281eddd4 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryDelegate.kt @@ -0,0 +1,68 @@ +/* + * Kiwix Android + * Copyright (C) 2018 Kiwix + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import android.view.ViewGroup +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.R.layout +import org.kiwix.kiwixmobile.extensions.inflate +import org.kiwix.kiwixmobile.utils.BookUtils +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.DividerItem +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryViewHolder.LibraryBookViewHolder +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryViewHolder.LibraryDividerViewHolder + +sealed class LibraryDelegate> : + AbsDelegateAdapter { + + class BookDelegate( + private val bookUtils: BookUtils, + private val clickAction: (BookItem) -> Unit + ) : LibraryDelegate() { + + override fun createViewHolder(parent: ViewGroup) = + LibraryBookViewHolder( + parent.inflate(R.layout.library_item, false), + bookUtils, + clickAction + ) + + override fun onBindViewHolder( + item: BookItem, + holder: LibraryBookViewHolder + ) { + holder.bind(item) + } + + override fun isFor(item: LibraryListItem) = item is BookItem + } + + object DividerDelegate : LibraryDelegate() { + override fun createViewHolder(parent: ViewGroup) = + LibraryDividerViewHolder(parent.inflate(layout.library_divider, false)) + + override fun onBindViewHolder( + item: DividerItem, + holder: LibraryDividerViewHolder + ) { + holder.bind(item) + } + + override fun isFor(item: LibraryListItem) = item is DividerItem + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryListItem.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryListItem.kt new file mode 100644 index 0000000000..a8da03d9dc --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryListItem.kt @@ -0,0 +1,17 @@ +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book + +sealed class LibraryListItem() { + abstract val id: Long + + data class DividerItem constructor( + override val id: Long, + val text: String + ) : LibraryListItem() + + data class BookItem( + val book: Book, + override val id: Long = book.id.hashCode().toLong() + ) : LibraryListItem() +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryViewHolder.kt new file mode 100644 index 0000000000..b94d630b1c --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryViewHolder.kt @@ -0,0 +1,63 @@ +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import android.support.v7.widget.RecyclerView +import android.view.View +import butterknife.internal.DebouncingOnClickListener +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.library_divider.divider_text +import kotlinx.android.synthetic.main.library_item.creator +import kotlinx.android.synthetic.main.library_item.date +import kotlinx.android.synthetic.main.library_item.description +import kotlinx.android.synthetic.main.library_item.favicon +import kotlinx.android.synthetic.main.library_item.fileName +import kotlinx.android.synthetic.main.library_item.language +import kotlinx.android.synthetic.main.library_item.publisher +import kotlinx.android.synthetic.main.library_item.size +import kotlinx.android.synthetic.main.library_item.title +import org.kiwix.kiwixmobile.KiwixApplication +import org.kiwix.kiwixmobile.downloader.model.Base64String +import org.kiwix.kiwixmobile.extensions.setBitmap +import org.kiwix.kiwixmobile.extensions.setTextAndVisibility +import org.kiwix.kiwixmobile.utils.BookUtils +import org.kiwix.kiwixmobile.utils.NetworkUtils +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.DividerItem + +sealed class LibraryViewHolder(override val containerView: View) : RecyclerView.ViewHolder( + containerView +), LayoutContainer { + + abstract fun bind(item: T) + + class LibraryBookViewHolder( + view: View, + private val bookUtils: BookUtils, + private val clickAction: (BookItem) -> Unit + ) : LibraryViewHolder(view) { + override fun bind(item: BookItem) { + title.setTextAndVisibility(item.book.title) + description.setTextAndVisibility(item.book.description) + creator.setTextAndVisibility(item.book.creator) + publisher.setTextAndVisibility(item.book.publisher) + date.setTextAndVisibility(item.book.date) + size.setTextAndVisibility(MegaByte(item.book.size).humanReadable) + language.text = bookUtils.getLanguage(item.book.getLanguage()) + fileName.text = NetworkUtils.parseURL( + KiwixApplication.getInstance(), item.book.file?.path ?: "" + ) + favicon.setBitmap(Base64String(item.book.favicon)) + + containerView.setOnClickListener { + clickAction.invoke(item) + } + } + + } + + class LibraryDividerViewHolder(view: View) : LibraryViewHolder(view) { + override fun bind(item: DividerItem) { + divider_text.text = item.text + } + } + +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/MegaByte.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/MegaByte.kt new file mode 100644 index 0000000000..df84bddb06 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/MegaByte.kt @@ -0,0 +1,16 @@ +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import java.text.DecimalFormat + +inline class MegaByte(val megabyteString: String?) { + val humanReadable + get() = megabyteString?.toLongOrNull()?.let { + val units = arrayOf("KB", "MB", "GB", "TB") + val conversion = (Math.log10(it.toDouble()) / Math.log10(1024.0)).toInt() + (DecimalFormat("#,##0.#") + .format(it / Math.pow(1024.0, conversion.toDouble())) + + " " + + units[conversion]) + } ?: "" + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_library.xml b/app/src/main/res/layout/activity_library.xml index d6341ca6e9..95d2875542 100644 --- a/app/src/main/res/layout/activity_library.xml +++ b/app/src/main/res/layout/activity_library.xml @@ -1,61 +1,42 @@ - - - - - - + android:animateLayoutChanges="true" + tools:context=".zim_manager.library_view.LibraryFragment" + > - - -