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/build.gradle b/app/build.gradle index 4b0f36a2b7..f7fbf6b5f2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -14,10 +14,12 @@ buildscript { } apply plugin: 'com.android.application' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' apply plugin: 'checkstyle' apply plugin: 'testdroid' +apply plugin: 'io.objectbox' repositories { mavenCentral() @@ -29,7 +31,6 @@ repositories { } String[] archs = ['arm64-v8a', 'armeabi', 'mips', 'mips64', 'x86', 'x86_64'] - dependencies { // Get kiwixlib online if it is not populated locally @@ -49,9 +50,6 @@ dependencies { implementation "com.android.support:support-v4:$supportLibraryVersion" implementation "com.android.support:design:$supportLibraryVersion" implementation "com.android.support:cardview-v7:$supportLibraryVersion" - implementation 'com.android.support:multidex:1.0.2' - - compile 'com.android.support.constraint:constraint-layout:1.0.2' androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3' @@ -77,25 +75,19 @@ dependencies { androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test:rules:1.0.1' - // Guava - implementation group: 'com.google.guava', name: 'guava', version: '21.0' - // Dagger compileOnly "javax.annotation:javax.annotation-api:$javaxAnnotationVersion" androidTestCompileOnly "javax.annotation:javax.annotation-api:$javaxAnnotationVersion" implementation "com.google.dagger:dagger:$daggerVersion" implementation "com.google.dagger:dagger-android:$daggerVersion" - annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" - annotationProcessor "com.google.dagger:dagger-android-processor:$daggerVersion" - androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion" + kapt "com.google.dagger:dagger-compiler:$daggerVersion" + kapt "com.google.dagger:dagger-android-processor:$daggerVersion" + kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion" // SquiDB implementation 'com.yahoo.squidb:squidb:2.0.0' implementation 'com.yahoo.squidb:squidb-annotations:2.0.0' - annotationProcessor 'com.yahoo.squidb:squidb-processor:2.0.0' - - // Apache - implementation 'commons-io:commons-io:2.5' + kapt 'com.yahoo.squidb:squidb-processor:2.0.0' // Square implementation "com.squareup.okhttp3:okhttp:$okHttpVersion" @@ -111,7 +103,7 @@ dependencies { // Butterknife implementation 'com.jakewharton:butterknife:8.0.1' - annotationProcessor 'com.jakewharton:butterknife-compiler:8.0.1' + kapt 'com.jakewharton:butterknife-compiler:8.0.1' // RxJava implementation "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion" @@ -126,7 +118,7 @@ dependencies { androidTestImplementation "org.mockito:mockito-android:2.24.5" // Leak canary - implementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' + implementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' // Only enable leak canary in debug builds configurations.all { config -> if (config.name.contains('debug') || config.name.contains("Debug")) { @@ -140,6 +132,10 @@ 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" + implementation "io.objectbox:objectbox-kotlin:$objectboxVersion" + implementation "io.objectbox:objectbox-rxjava:$objectboxVersion" } // Set custom app import directory @@ -165,12 +161,11 @@ android { compileSdkVersion 27 defaultConfig { - minSdkVersion 14 + minSdkVersion 15 targetSdkVersion 27 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" // See https://github.com/linkedin/dexmaker/issues/65 for why we need the following line. testInstrumentationRunnerArguments.notClass = 'com.android.dex.DexIndexOverflowException' - multiDexEnabled true vectorDrawables.useSupportLibrary = true } @@ -204,8 +199,6 @@ android { buildConfigField "boolean", "KIWIX_ERROR_ACTIVITY", "false" // True breaks local variables being shown in breakpoints testCoverageEnabled false - // Needed for instrumentation tests on Pre 5.0 - multiDexKeepProguard file('multidex-instrumentation-config.pro') } mock_network { @@ -252,12 +245,12 @@ android { def version_code = project.property('version_code') versionCode version_code.toInteger() } else { - versionCode 55 + versionCode 56 } if (project.hasProperty('version_name')) { versionName project.property('version_name') } else { - versionName "2.4" + versionName "2.5" } } // Custom apps built from a json file, zim file and icon set @@ -368,6 +361,9 @@ android { } } */ + androidExtensions { + experimental = true + } } // Testdroid deployment configuration diff --git a/app/objectbox-models/default.json b/app/objectbox-models/default.json new file mode 100644 index 0000000000..8eb30689a8 --- /dev/null +++ b/app/objectbox-models/default.json @@ -0,0 +1,203 @@ +{ + "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", + "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", + "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", + "entities": [ + { + "id": "1:7257718270326155947", + "lastPropertyId": "17:8085320504542486236", + "name": "DownloadEntity", + "properties": [ + { + "id": "1:2266566996008201697", + "name": "id" + }, + { + "id": "2:1953917250527765737", + "name": "downloadId" + }, + { + "id": "5:6575412958851693470", + "name": "bookId" + }, + { + "id": "6:1075612111256674117", + "name": "title" + }, + { + "id": "7:2831524841121029990", + "name": "description" + }, + { + "id": "8:2334902404590133038", + "name": "language" + }, + { + "id": "9:5087250349738158996", + "name": "creator" + }, + { + "id": "10:6128960350043895299", + "name": "publisher" + }, + { + "id": "11:3850323036475883785", + "name": "date" + }, + { + "id": "12:5288623325038033644", + "name": "url" + }, + { + "id": "13:2501711400901908648", + "name": "articleCount" + }, + { + "id": "14:3550975911715416030", + "name": "mediaCount" + }, + { + "id": "15:8949996430663588693", + "name": "size" + }, + { + "id": "16:7554483297276446029", + "name": "name" + }, + { + "id": "17:8085320504542486236", + "name": "favIcon" + } + ], + "relations": [] + }, + { + "id": "3:5536749840871435068", + "lastPropertyId": "16:6142333908132117423", + "name": "BookOnDiskEntity", + "properties": [ + { + "id": "1:4248832782795400383", + "name": "id" + }, + { + "id": "2:2644395282642821815", + "name": "file" + }, + { + "id": "4:3145196313443812205", + "name": "bookId" + }, + { + "id": "5:597997298666253723", + "name": "title" + }, + { + "id": "6:8028706022307902131", + "name": "description" + }, + { + "id": "7:4257578632233656657", + "name": "language" + }, + { + "id": "8:7771231471515752814", + "name": "creator" + }, + { + "id": "9:892859866782486178", + "name": "publisher" + }, + { + "id": "10:1925365063061602631", + "name": "date" + }, + { + "id": "11:1111395522977944209", + "name": "url" + }, + { + "id": "12:3765116904492031525", + "name": "articleCount" + }, + { + "id": "13:5901922417972273396", + "name": "mediaCount" + }, + { + "id": "14:1229023184984372602", + "name": "size" + }, + { + "id": "15:6851856791814492874", + "name": "name" + }, + { + "id": "16:6142333908132117423", + "name": "favIcon" + } + ], + "relations": [] + }, + { + "id": "4:6278838675135543734", + "lastPropertyId": "4:8812214350305159407", + "name": "LanguageEntity", + "properties": [ + { + "id": "1:7795244654012809404", + "name": "id" + }, + { + "id": "2:9116495537035444904", + "name": "locale" + }, + { + "id": "3:452531964346972307", + "name": "active" + }, + { + "id": "4:8812214350305159407", + "name": "occurencesOfLanguage" + } + ], + "relations": [] + } + ], + "lastEntityId": "4:6278838675135543734", + "lastIndexId": "4:4868787482832538530", + "lastRelationId": "0:0", + "lastSequenceId": "0:0", + "modelVersion": 4, + "modelVersionParserMinimum": 4, + "retiredEntityUids": [ + 349148274283701276 + ], + "retiredIndexUids": [ + 1293695782925933448, + 3655049272366703856, + 7576716732364166705, + 4868787482832538530 + ], + "retiredPropertyUids": [ + 4712434661554562781, + 1521665545502891268, + 1831899651198481824, + 8913656606098213241, + 4745760836781949968, + 9177466730609383913, + 6985467229796102081, + 4417830652027770707, + 3485079785941052658, + 2875347328622347138, + 96906195091428769, + 305997162787053035, + 8804682238892773896, + 3464301918251637220, + 5620508895870653354, + 7273406943564025911, + 428251106490095982 + ], + "retiredRelationUids": [], + "version": 1 +} \ No newline at end of file diff --git a/app/objectbox-models/default.json.bak b/app/objectbox-models/default.json.bak new file mode 100644 index 0000000000..8eb30689a8 --- /dev/null +++ b/app/objectbox-models/default.json.bak @@ -0,0 +1,203 @@ +{ + "_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.", + "_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.", + "_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.", + "entities": [ + { + "id": "1:7257718270326155947", + "lastPropertyId": "17:8085320504542486236", + "name": "DownloadEntity", + "properties": [ + { + "id": "1:2266566996008201697", + "name": "id" + }, + { + "id": "2:1953917250527765737", + "name": "downloadId" + }, + { + "id": "5:6575412958851693470", + "name": "bookId" + }, + { + "id": "6:1075612111256674117", + "name": "title" + }, + { + "id": "7:2831524841121029990", + "name": "description" + }, + { + "id": "8:2334902404590133038", + "name": "language" + }, + { + "id": "9:5087250349738158996", + "name": "creator" + }, + { + "id": "10:6128960350043895299", + "name": "publisher" + }, + { + "id": "11:3850323036475883785", + "name": "date" + }, + { + "id": "12:5288623325038033644", + "name": "url" + }, + { + "id": "13:2501711400901908648", + "name": "articleCount" + }, + { + "id": "14:3550975911715416030", + "name": "mediaCount" + }, + { + "id": "15:8949996430663588693", + "name": "size" + }, + { + "id": "16:7554483297276446029", + "name": "name" + }, + { + "id": "17:8085320504542486236", + "name": "favIcon" + } + ], + "relations": [] + }, + { + "id": "3:5536749840871435068", + "lastPropertyId": "16:6142333908132117423", + "name": "BookOnDiskEntity", + "properties": [ + { + "id": "1:4248832782795400383", + "name": "id" + }, + { + "id": "2:2644395282642821815", + "name": "file" + }, + { + "id": "4:3145196313443812205", + "name": "bookId" + }, + { + "id": "5:597997298666253723", + "name": "title" + }, + { + "id": "6:8028706022307902131", + "name": "description" + }, + { + "id": "7:4257578632233656657", + "name": "language" + }, + { + "id": "8:7771231471515752814", + "name": "creator" + }, + { + "id": "9:892859866782486178", + "name": "publisher" + }, + { + "id": "10:1925365063061602631", + "name": "date" + }, + { + "id": "11:1111395522977944209", + "name": "url" + }, + { + "id": "12:3765116904492031525", + "name": "articleCount" + }, + { + "id": "13:5901922417972273396", + "name": "mediaCount" + }, + { + "id": "14:1229023184984372602", + "name": "size" + }, + { + "id": "15:6851856791814492874", + "name": "name" + }, + { + "id": "16:6142333908132117423", + "name": "favIcon" + } + ], + "relations": [] + }, + { + "id": "4:6278838675135543734", + "lastPropertyId": "4:8812214350305159407", + "name": "LanguageEntity", + "properties": [ + { + "id": "1:7795244654012809404", + "name": "id" + }, + { + "id": "2:9116495537035444904", + "name": "locale" + }, + { + "id": "3:452531964346972307", + "name": "active" + }, + { + "id": "4:8812214350305159407", + "name": "occurencesOfLanguage" + } + ], + "relations": [] + } + ], + "lastEntityId": "4:6278838675135543734", + "lastIndexId": "4:4868787482832538530", + "lastRelationId": "0:0", + "lastSequenceId": "0:0", + "modelVersion": 4, + "modelVersionParserMinimum": 4, + "retiredEntityUids": [ + 349148274283701276 + ], + "retiredIndexUids": [ + 1293695782925933448, + 3655049272366703856, + 7576716732364166705, + 4868787482832538530 + ], + "retiredPropertyUids": [ + 4712434661554562781, + 1521665545502891268, + 1831899651198481824, + 8913656606098213241, + 4745760836781949968, + 9177466730609383913, + 6985467229796102081, + 4417830652027770707, + 3485079785941052658, + 2875347328622347138, + 96906195091428769, + 305997162787053035, + 8804682238892773896, + 3464301918251637220, + 5620508895870653354, + 7273406943564025911, + 428251106490095982 + ], + "retiredRelationUids": [], + "version": 1 +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/database/KiwixDatabaseTest.java b/app/src/androidTest/java/org/kiwix/kiwixmobile/database/KiwixDatabaseTest.java index d01ee9eec2..d902ef0bfa 100644 --- a/app/src/androidTest/java/org/kiwix/kiwixmobile/database/KiwixDatabaseTest.java +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/database/KiwixDatabaseTest.java @@ -23,10 +23,6 @@ import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; @@ -34,6 +30,8 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; +import org.junit.Test; +import org.junit.runner.RunWith; import static org.junit.Assert.assertArrayEquals; @@ -50,7 +48,7 @@ public KiwixDatabaseTest (){ @Test public void testMigrateDatabase() throws IOException { - KiwixDatabase kiwixDatabase = new KiwixDatabase(mContext); + KiwixDatabase kiwixDatabase = new KiwixDatabase(mContext,null,null); kiwixDatabase.recreate(); String testId = "8ce5775a-10a9-bbf3-178a-9df69f23263c"; String[] testBookmarks = new String[] {"Test1","Test2","Test3"}; 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..ed135d6595 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; @@ -106,7 +105,7 @@ public void downloadTest() { captureAndSaveScreenshot("Before-checking-for-ZimManager-Main-Activity"); ViewInteraction viewPager2 = onView( - allOf(withId(R.id.container), + allOf(withId(R.id.manageViewPager), withParent(allOf(withId(R.id.zim_manager_main_activity), withParent(withId(android.R.id.content)))), isDisplayed())); @@ -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/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9424486ac8..558d1d1614 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -130,6 +130,7 @@ @@ -192,6 +193,11 @@ android:resource="@xml/provider_paths" /> + + + + + \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/KiwixApplication.java b/app/src/main/java/org/kiwix/kiwixmobile/KiwixApplication.java index e90fb6ac83..d21217f6af 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/KiwixApplication.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/KiwixApplication.java @@ -18,26 +18,22 @@ package org.kiwix.kiwixmobile; import android.app.Activity; +import android.app.Application; import android.content.Context; import android.os.Environment; -import android.support.multidex.MultiDexApplication; -import android.util.Log; import android.support.v7.app.AppCompatDelegate; +import android.util.Log; import com.squareup.leakcanary.LeakCanary; -import org.kiwix.kiwixmobile.di.components.ApplicationComponent; -import org.kiwix.kiwixmobile.di.components.DaggerApplicationComponent; -import org.kiwix.kiwixmobile.di.modules.ApplicationModule; - -import java.io.File; -import java.io.IOException; - -import javax.inject.Inject; - import dagger.android.AndroidInjector; import dagger.android.DispatchingAndroidInjector; import dagger.android.HasActivityInjector; +import java.io.File; +import java.io.IOException; +import javax.inject.Inject; +import org.kiwix.kiwixmobile.di.components.ApplicationComponent; +import org.kiwix.kiwixmobile.di.components.DaggerApplicationComponent; -public class KiwixApplication extends MultiDexApplication implements HasActivityInjector { +public class KiwixApplication extends Application implements HasActivityInjector { private static KiwixApplication application; private static ApplicationComponent applicationComponent; @@ -68,13 +64,18 @@ protected void attachBaseContext(Context base) { super.attachBaseContext(base); application = this; setApplicationComponent(DaggerApplicationComponent.builder() - .applicationModule(new ApplicationModule(this)) + .context(this) .build()); } @Override public void onCreate() { super.onCreate(); + if (LeakCanary.isInAnalyzerProcess(this)) { + // This process is dedicated to LeakCanary for heap analysis. + // You should not init your app in this process. + return; + } if (isExternalStorageWritable()) { File appDirectory = new File(Environment.getExternalStorageDirectory() + "/Kiwix"); logFile = new File(appDirectory, "logcat.txt"); @@ -105,13 +106,7 @@ public void onCreate() { } Log.d("KIWIX", "Started KiwixApplication"); - applicationComponent.inject(this); - if (LeakCanary.isInAnalyzerProcess(this)) { - // This process is dedicated to LeakCanary for heap analysis. - // You should not init your app in this process. - return; - } LeakCanary.install(this); } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/KiwixErrorActivity.java b/app/src/main/java/org/kiwix/kiwixmobile/KiwixErrorActivity.java index caf5aaf4d6..6d0714514b 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/KiwixErrorActivity.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/KiwixErrorActivity.java @@ -9,26 +9,23 @@ import android.support.v4.content.FileProvider; import android.widget.Button; import android.widget.CheckBox; - +import butterknife.BindView; +import butterknife.ButterKnife; +import java.io.File; +import java.util.List; +import javax.inject.Inject; import org.kiwix.kiwixmobile.base.BaseActivity; -import org.kiwix.kiwixmobile.database.BookDao; +import org.kiwix.kiwixmobile.database.newdb.dao.NewBookDao; +import org.kiwix.kiwixmobile.downloader.model.BookOnDisk; import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; import org.kiwix.kiwixmobile.utils.SplashActivity; -import java.io.File; -import java.util.ArrayList; - -import javax.inject.Inject; - -import butterknife.BindView; -import butterknife.ButterKnife; - import static org.kiwix.kiwixmobile.utils.LanguageUtils.getCurrentLocale; public class KiwixErrorActivity extends BaseActivity { @Inject - BookDao bookDao; + NewBookDao bookDao; @BindView(R.id.reportButton) Button reportButton; @@ -89,10 +86,11 @@ 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) { + for (BookOnDisk bookOnDisk : books) { + final LibraryNetworkEntity.Book book = bookOnDisk.getBook(); String bookString = book.getTitle() + ":\nArticles: ["+ book.getArticleCount() + "]\nCreator: [" + book.getCreator() + 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/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/BookDao.java b/app/src/main/java/org/kiwix/kiwixmobile/database/BookDao.java index 82b973dd0c..660512728b 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,13 @@ */ package org.kiwix.kiwixmobile.database; - 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 java.io.File; import java.util.ArrayList; - 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; @@ -36,6 +31,7 @@ * Dao class for books */ +@Deprecated public class BookDao { private KiwixDatabase mDb; @@ -44,7 +40,7 @@ public BookDao(KiwixDatabase kiwixDatabase) { this.mDb = kiwixDatabase; } - + public void setBookDetails(Book book, SquidCursor bookCursor) { book.id = bookCursor.get(BookDatabaseEntity.BOOK_ID); book.title = bookCursor.get(BookDatabaseEntity.TITLE); @@ -60,26 +56,7 @@ public void setBookDetails(Book book, SquidCursor bookCursor book.favicon = bookCursor.get(BookDatabaseEntity.FAVICON); book.bookName = bookCursor.get(BookDatabaseEntity.NAME); } - - public void setBookDatabaseEntity(Book book, BookDatabaseEntity bookDatabaseEntity) { - bookDatabaseEntity.setBookId(book.getId()); - bookDatabaseEntity.setTitle(book.getTitle()); - bookDatabaseEntity.setDescription(book.getDescription()); - bookDatabaseEntity.setLanguage(book.getLanguage()); - bookDatabaseEntity.setBookCreator(book.getCreator()); - bookDatabaseEntity.setPublisher(book.getPublisher()); - bookDatabaseEntity.setDate(book.getDate()); - bookDatabaseEntity.setUrl(book.file.getPath()); - bookDatabaseEntity.setArticleCount(book.getArticleCount()); - bookDatabaseEntity.setMediaCount(book.getMediaCount()); - bookDatabaseEntity.setSize(book.getSize()); - bookDatabaseEntity.setFavicon(book.getFavicon()); - bookDatabaseEntity.setName(book.getName()); - String filePath = book.file.getPath(); - mDb.deleteWhere(BookDatabaseEntity.class, BookDatabaseEntity.URL.eq(filePath)); - mDb.persist(bookDatabaseEntity); - } - + public ArrayList getBooks() { SquidCursor bookCursor = mDb.query( BookDatabaseEntity.class, @@ -91,48 +68,10 @@ public ArrayList getBooks() { if (!hasParts(book.file)) { if (book.file.exists()) { books.add(book); - } else { - mDb.deleteWhere(BookDatabaseEntity.class, BookDatabaseEntity.URL.eq(book.file)); } } } bookCursor.close(); return books; } - - public ArrayList getDownloadingBooks() { - SquidCursor bookCursor = mDb.query( - BookDatabaseEntity.class, - Query.select()); - ArrayList books = new ArrayList<>(); - while (bookCursor.moveToNext()) { - Book book = new Book(); - setBookDetails(book, bookCursor); - book.remoteUrl = bookCursor.get(BookDatabaseEntity.REMOTE_URL); - if (hasParts(book.file)) { - books.add(book); - } - } - bookCursor.close(); - return books; - } - - public void saveBooks(ArrayList books) { - for (Book book : books) { - if (book != null) { - BookDatabaseEntity bookDatabaseEntity = new BookDatabaseEntity(); - setBookDatabaseEntity(book, bookDatabaseEntity); - } - } - } - - public void saveBook(Book book) { - BookDatabaseEntity bookDatabaseEntity = new BookDatabaseEntity(); - bookDatabaseEntity.setRemoteUrl(book.remoteUrl); - setBookDatabaseEntity(book, bookDatabaseEntity); - } - - public void deleteBook(String id) { - mDb.deleteWhere(BookDatabaseEntity.class, BookDatabaseEntity.BOOK_ID.eq(id)); - } -} +} \ No newline at end of file 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..236e9a30d7 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/database/KiwixDatabase.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/KiwixDatabase.java @@ -21,40 +21,42 @@ 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.LibraryDatabaseEntity; +import org.kiwix.kiwixmobile.database.entity.NetworkLanguageDatabaseEntity; +import org.kiwix.kiwixmobile.database.entity.RecentSearch; +import org.kiwix.kiwixmobile.database.newdb.dao.NewBookDao; +import org.kiwix.kiwixmobile.database.newdb.dao.NewLanguagesDao; +import org.kiwix.kiwixmobile.utils.UpdateUtils; import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX; @Singleton public class KiwixDatabase extends SquidDatabase { - private static final int VERSION = 15; + private static final int VERSION = 16; private Context context; + private final NewBookDao bookDao; + private final NewLanguagesDao languagesDao; @Inject - public KiwixDatabase(Context context) { + public KiwixDatabase(Context context, NewBookDao bookDao, NewLanguagesDao languagesDao) { super(context); this.context = context; + this.bookDao = bookDao; + this.languagesDao = languagesDao; } @Override @@ -64,17 +66,26 @@ public String getName() { @Override protected Table[] getTables() { - return new Table[]{ - BookDatabaseEntity.TABLE, - LibraryDatabaseEntity.TABLE, + return new Table[] { RecentSearch.TABLE, Bookmarks.TABLE, - NetworkLanguageDatabaseEntity.TABLE }; } @Override protected boolean onUpgrade(SQLiteDatabaseWrapper db, int oldVersion, int newVersion) { + if (newVersion >= 16) { //2.5 attempt reading values from old db before they get dropped + try { + bookDao.migrationInsert(new BookDao(this).getBooks()); + } catch (Exception e) { + e.printStackTrace(); + } + try { + languagesDao.insert(new NetworkLanguageDao(this).getFilteredLanguages()); + } catch (Exception e) { + e.printStackTrace(); + } + } if (newVersion >= 3 && oldVersion < 3) { db.execSQL("DROP TABLE IF EXISTS recents"); tryCreateTable(RecentSearch.TABLE); @@ -133,6 +144,11 @@ protected boolean onUpgrade(SQLiteDatabaseWrapper db, int oldVersion, int newVer if (newVersion >= 15 && oldVersion < 15) { reformatBookmarks(); } + if (newVersion >= 16) { //2.5 drop tables + tryDropTable(BookDatabaseEntity.TABLE); + tryDropTable(NetworkLanguageDatabaseEntity.TABLE); + tryDropTable(LibraryDatabaseEntity.TABLE); + } return true; } @@ -177,4 +193,3 @@ private void reformatBookmarks() { } } - 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..34323c531f 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/database/NetworkLanguageDao.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/NetworkLanguageDao.java @@ -21,16 +21,12 @@ import com.yahoo.squidb.data.SquidCursor; import com.yahoo.squidb.sql.Query; - +import java.util.ArrayList; 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 java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language; +@Deprecated public class NetworkLanguageDao { private KiwixDatabase mDb; @@ -39,31 +35,17 @@ public NetworkLanguageDao(KiwixDatabase kiwikDatabase) { this.mDb = kiwikDatabase; } - public ArrayList getFilteredLanguages() { - SquidCursor languageCursor = mDb.query( + public ArrayList getFilteredLanguages() { + ArrayList result = new ArrayList<>(); + try (SquidCursor languageCursor = mDb.query( NetworkLanguageDatabaseEntity.class, - Query.select()); - ArrayList result = new ArrayList<>(); - try { + Query.select())) { 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, 0)); } - } finally { - languageCursor.close(); } return result; } - - 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); - mDb.persist(networkLanguageDatabaseEntity); - } - } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/entity/BookDataSource.java b/app/src/main/java/org/kiwix/kiwixmobile/database/entity/BookDataSource.java index 221284f6ff..3ba8b38c7b 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/database/entity/BookDataSource.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/entity/BookDataSource.java @@ -54,4 +54,4 @@ public class BookDataSource { public boolean downloaded; -} +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/entity/NetworkLanguageSpec.java b/app/src/main/java/org/kiwix/kiwixmobile/database/entity/NetworkLanguageSpec.java index 69bc49267a..ba4defd628 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/database/entity/NetworkLanguageSpec.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/entity/NetworkLanguageSpec.java @@ -25,4 +25,5 @@ public class NetworkLanguageSpec { public String languageISO3; public boolean enabled; + public int numberOfOccurences; } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/dao/NewBookDao.kt b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/dao/NewBookDao.kt new file mode 100644 index 0000000000..2daef7d6cf --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/dao/NewBookDao.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.database.newdb.dao + +import io.objectbox.Box +import io.objectbox.kotlin.inValues +import io.objectbox.kotlin.query +import org.kiwix.kiwixmobile.database.newdb.entities.BookOnDiskEntity +import org.kiwix.kiwixmobile.database.newdb.entities.BookOnDiskEntity_ +import org.kiwix.kiwixmobile.downloader.model.BookOnDisk +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import java.util.ArrayList +import javax.inject.Inject + +class NewBookDao @Inject constructor(private val box: Box) { + + fun books() = box.asFlowable() + .map { it.map(::BookOnDisk) } + + fun getBooks() = box.all.map(::BookOnDisk) + + fun insert(booksOnDisk: List) { + box.store.callInTx { + box + .query { + inValues(BookOnDiskEntity_.bookId, booksOnDisk.map { it.book.id }.toTypedArray()) + } + .remove() + box.put(booksOnDisk.map(::BookOnDiskEntity)) + } + + } + + fun delete(databaseId: Long) { + box.remove(databaseId) + } + + fun migrationInsert(books: ArrayList) { + insert(books.map { BookOnDisk(book = it, file = it.file) }) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/dao/NewDownloadDao.kt b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/dao/NewDownloadDao.kt new file mode 100644 index 0000000000..66628c0d8a --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/dao/NewDownloadDao.kt @@ -0,0 +1,45 @@ +package org.kiwix.kiwixmobile.database.newdb.dao + +import android.util.Log +import io.objectbox.Box +import io.objectbox.kotlin.inValues +import io.objectbox.kotlin.query +import io.objectbox.rx.RxQuery +import io.reactivex.BackpressureStrategy.LATEST +import org.kiwix.kiwixmobile.database.newdb.entities.DownloadEntity +import org.kiwix.kiwixmobile.database.newdb.entities.DownloadEntity_ +import org.kiwix.kiwixmobile.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import javax.inject.Inject + +class NewDownloadDao @Inject constructor(private val box: Box) { + + fun downloads() = box.asFlowable() + .map { it.map(DownloadEntity::toDownloadModel) } + + fun delete(vararg downloadIds: Long) { + box + .query { + inValues(DownloadEntity_.downloadId, downloadIds) + } + .remove() + } + + fun containsAny(vararg downloadIds: Long) = + box + .query { + inValues(DownloadEntity_.downloadId, downloadIds) + } + .count() > 0 + + fun doesNotAlreadyExist(book: Book) = + box + .query { + equal(DownloadEntity_.bookId, book.id) + } + .count() == 0L + + fun insert(downloadModel: DownloadModel) { + box.put(DownloadEntity(downloadModel)) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/dao/NewLanguagesDao.kt b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/dao/NewLanguagesDao.kt new file mode 100644 index 0000000000..5af000a9f6 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/dao/NewLanguagesDao.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.database.newdb.dao + +import io.objectbox.Box +import io.objectbox.kotlin.query +import io.objectbox.rx.RxQuery +import io.reactivex.BackpressureStrategy +import io.reactivex.BackpressureStrategy.LATEST +import org.kiwix.kiwixmobile.database.newdb.entities.LanguageEntity +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language +import javax.inject.Inject + +class NewLanguagesDao @Inject constructor(private val box: Box) { + fun languages() = box.asFlowable() + .map { it.map(LanguageEntity::toLanguageModel) } + + fun insert(languages: List) { + box.store.callInTx { + box.removeAll() + box.put(languages.map(::LanguageEntity)) + } + } +} + +internal fun Box.asFlowable(backpressureStrategy: BackpressureStrategy = LATEST) = + RxQuery.observable(query {}).toFlowable(backpressureStrategy) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/entities/BookOnDiskEntity.kt b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/entities/BookOnDiskEntity.kt new file mode 100644 index 0000000000..f09fa9d716 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/entities/BookOnDiskEntity.kt @@ -0,0 +1,71 @@ +package org.kiwix.kiwixmobile.database.newdb.entities + +import io.objectbox.annotation.Convert +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import io.objectbox.converter.PropertyConverter +import org.kiwix.kiwixmobile.downloader.model.BookOnDisk +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import java.io.File + +@Entity +data class BookOnDiskEntity( + @Id var id: Long = 0, + @Convert(converter = StringToFileConverter::class, dbType = String::class) + val file: File = File(""), + val bookId: String, + val title: String, + val description: String, + val language: String, + val creator: String, + val publisher: String, + val date: String, + val url: String?, + val articleCount: String?, + val mediaCount: String?, + val size: String, + val name: String?, + val favIcon: String +) { + constructor(bookOnDisk: BookOnDisk) : this( + 0, + bookOnDisk.file, + bookOnDisk.book.getId(), + bookOnDisk.book.getTitle(), + bookOnDisk.book.getDescription(), + bookOnDisk.book.getLanguage(), + bookOnDisk.book.getCreator(), + bookOnDisk.book.getPublisher(), + bookOnDisk.book.getDate(), + bookOnDisk.book.getUrl(), + bookOnDisk.book.getArticleCount(), + bookOnDisk.book.getMediaCount(), + bookOnDisk.book.getSize(), + bookOnDisk.book.getName(), + bookOnDisk.book.getFavicon() + ) + + fun toBook() = Book().apply { + id = this@BookOnDiskEntity.bookId + title = this@BookOnDiskEntity.title + description = this@BookOnDiskEntity.description + language = this@BookOnDiskEntity.language + creator = this@BookOnDiskEntity.creator + publisher = this@BookOnDiskEntity.publisher + date = this@BookOnDiskEntity.date + url = this@BookOnDiskEntity.url + articleCount = this@BookOnDiskEntity.articleCount + mediaCount = this@BookOnDiskEntity.mediaCount + size = this@BookOnDiskEntity.size + bookName = this@BookOnDiskEntity.name + favicon = this@BookOnDiskEntity.favIcon + } + +} + +class StringToFileConverter : PropertyConverter { + override fun convertToDatabaseValue(entityProperty: File?) = entityProperty?.path ?: "" + + override fun convertToEntityProperty(databaseValue: String?) = File(databaseValue ?: "") + +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/entities/DownloadEntity.kt b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/entities/DownloadEntity.kt new file mode 100644 index 0000000000..ce8f0166ec --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/entities/DownloadEntity.kt @@ -0,0 +1,78 @@ +/* + * 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.newdb.entities + +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import org.kiwix.kiwixmobile.downloader.model.DownloadModel +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book + +@Entity +data class DownloadEntity( + @Id var id: Long = 0, + val downloadId: Long, + val bookId: String, + val title: String, + val description: String, + val language: String, + val creator: String, + val publisher: String, + val date: String, + val url: String?, + val articleCount: String?, + val mediaCount: String?, + val size: String, + val name: String?, + val favIcon: String +) { + constructor(downloadModel: DownloadModel) : this( + 0, + downloadModel.downloadId, + downloadModel.book.getId(), + downloadModel.book.getTitle(), + downloadModel.book.getDescription(), + downloadModel.book.getLanguage(), + downloadModel.book.getCreator(), + downloadModel.book.getPublisher(), + downloadModel.book.getDate(), + downloadModel.book.getUrl(), + downloadModel.book.getArticleCount(), + downloadModel.book.getMediaCount(), + downloadModel.book.getSize(), + downloadModel.book.getName(), + downloadModel.book.getFavicon() + ) + + fun toDownloadModel() = DownloadModel(id, downloadId, toBook()) + + private fun toBook() = Book().apply { + id = this@DownloadEntity.bookId + title = this@DownloadEntity.title + description = this@DownloadEntity.description + language = this@DownloadEntity.language + creator = this@DownloadEntity.creator + publisher = this@DownloadEntity.publisher + date = this@DownloadEntity.date + url = this@DownloadEntity.url + articleCount = this@DownloadEntity.articleCount + mediaCount = this@DownloadEntity.mediaCount + size = this@DownloadEntity.size + bookName = this@DownloadEntity.name + favicon = this@DownloadEntity.favIcon + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/entities/LanguageEntity.kt b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/entities/LanguageEntity.kt new file mode 100644 index 0000000000..956263c008 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/database/newdb/entities/LanguageEntity.kt @@ -0,0 +1,36 @@ +package org.kiwix.kiwixmobile.database.newdb.entities + +import io.objectbox.annotation.Convert +import io.objectbox.annotation.Entity +import io.objectbox.annotation.Id +import io.objectbox.converter.PropertyConverter +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language +import java.util.Locale + +@Entity +data class LanguageEntity( + @Id var id: Long = 0, + @Convert(converter = StringToLocaleConverter::class, dbType = String::class) + val locale: Locale = Locale.ENGLISH, + val active: Boolean = false, + val occurencesOfLanguage: Int = 0 +) { + + constructor(language: Language) : this( + 0, + Locale(language.languageCode), + language.active, + language.occurencesOfLanguage + ) + + fun toLanguageModel() = Language(locale, active, occurencesOfLanguage) +} + +class StringToLocaleConverter : PropertyConverter { + override fun convertToDatabaseValue(entityProperty: Locale?) = + entityProperty?.isO3Language ?: Locale.ENGLISH.isO3Language + + override fun convertToEntityProperty(databaseValue: String?) = + databaseValue?.let(::Locale) ?: Locale.ENGLISH + +} 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..081b6cd3a0 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,23 +17,20 @@ */ 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; import org.kiwix.kiwixmobile.downloader.DownloadService; -import org.kiwix.kiwixmobile.library.LibraryAdapter; 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; +import org.kiwix.kiwixmobile.zim_manager.DownloadNotificationClickedReceiver; @Singleton @Component(modules = { @@ -42,23 +39,28 @@ JNIModule.class, }) public interface ApplicationComponent { - void inject(KiwixApplication application); - void inject(DownloadService service); + @Component.Builder + interface Builder { - void inject(LibraryFragment libraryFragment); + @BindsInstance Builder context(Context context); - void inject(BaseFragment baseFragment); + ApplicationComponent build(); + } - void inject(ZimFileSelectFragment zimFileSelectFragment); + ActivityComponent.Builder activityComponent(); - void inject(ZimContentProvider zimContentProvider); + void inject(KiwixApplication application); - void inject(LibraryAdapter libraryAdapter); + void inject(DownloadService service); + + void inject(ZimContentProvider zimContentProvider); void inject(KiwixWebView kiwixWebView); void inject(KiwixSettingsActivity.PrefsFragment prefsFragment); void inject(AutoCompleteAdapter autoCompleteAdapter); + + void inject(DownloadNotificationClickedReceiver downloadNotificationClickedReceiver); } 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..794afc49b3 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,37 @@ */ package org.kiwix.kiwixmobile.di.modules; +import android.app.Application; +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, + DatabaseModule.class +}) public class ApplicationModule { - private final KiwixApplication application; - - public ApplicationModule(KiwixApplication application) { - this.application = application; - } - @Provides @Singleton Context provideApplicationContext() { - return this.application; + @Provides @Singleton Application provideApplication(Context context) { + return (Application) context; } @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/DatabaseModule.kt b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/DatabaseModule.kt new file mode 100644 index 0000000000..70cbb398a6 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/DatabaseModule.kt @@ -0,0 +1,44 @@ +/* + * 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 android.content.Context +import dagger.Module +import dagger.Provides +import io.objectbox.BoxStore +import io.objectbox.kotlin.boxFor +import org.kiwix.kiwixmobile.database.newdb.dao.NewBookDao +import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao +import org.kiwix.kiwixmobile.database.newdb.dao.NewLanguagesDao +import org.kiwix.kiwixmobile.database.newdb.entities.MyObjectBox +import javax.inject.Singleton + +@Module +class DatabaseModule { + @Provides @Singleton fun providesBoxStore(context: Context): BoxStore = + MyObjectBox.builder().androidContext(context.applicationContext).build() + + @Provides @Singleton fun providesNewDownloadDao(boxStore: BoxStore): NewDownloadDao = + NewDownloadDao(boxStore.boxFor()) + + @Provides @Singleton fun providesNewBookDao(boxStore: BoxStore): NewBookDao = + NewBookDao(boxStore.boxFor()) + + @Provides @Singleton fun providesNewLanguagesDao(boxStore: BoxStore): NewLanguagesDao = + NewLanguagesDao(boxStore.boxFor()) +} \ No newline at end of file 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/NetworkModule.java b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/NetworkModule.java index 62e831ba85..8e49fc9955 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/di/modules/NetworkModule.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/di/modules/NetworkModule.java @@ -19,17 +19,15 @@ import android.content.Context; import android.net.ConnectivityManager; - -import org.kiwix.kiwixmobile.BuildConfig; -import org.kiwix.kiwixmobile.network.KiwixService; -import org.kiwix.kiwixmobile.network.UserAgentInterceptor; - -import javax.inject.Singleton; - import dagger.Module; import dagger.Provides; +import java.util.concurrent.TimeUnit; +import javax.inject.Singleton; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; +import org.kiwix.kiwixmobile.BuildConfig; +import org.kiwix.kiwixmobile.network.KiwixService; +import org.kiwix.kiwixmobile.network.UserAgentInterceptor; @Module public class NetworkModule { @@ -41,6 +39,8 @@ logging.setLevel(HttpLoggingInterceptor.Level.BASIC); return new OkHttpClient().newBuilder().followRedirects(true).followSslRedirects(true) + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) .addNetworkInterceptor(logging) .addNetworkInterceptor(new UserAgentInterceptor(useragent)).build(); } 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/ChunkUtils.java b/app/src/main/java/org/kiwix/kiwixmobile/downloader/ChunkUtils.java index 106fc4d0ef..885fe449c6 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/downloader/ChunkUtils.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/ChunkUtils.java @@ -17,13 +17,10 @@ */ package org.kiwix.kiwixmobile.downloader; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; -import org.kiwix.kiwixmobile.utils.StorageUtils; - import java.io.File; -import java.io.FilenameFilter; import java.util.ArrayList; import java.util.List; +import org.kiwix.kiwixmobile.utils.StorageUtils; public class ChunkUtils { @@ -80,14 +77,14 @@ public static void completeDownload(File file) { } } - public static long getCurrentSize(LibraryNetworkEntity.Book book) { - long size = 0; - File[] files = getAllZimParts(book.file); - for (File file : files) { - size += file.length(); - } - return size; - } + //public static long getCurrentSize(LibraryNetworkEntity.Book book) { + // long size = 0; + // File[] files = getAllZimParts(book.file); + // for (File file : files) { + // size += file.length(); + // } + // return size; + //} private static File[] getAllZimParts(File file) { final String baseName = baseNameFromParts(file); 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..8226441114 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadFragment.kt @@ -0,0 +1,90 @@ +/* + * 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.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.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.YesNoDialog.StopDownload +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 + + private val zimManageViewModel: ZimManageViewModel by lazy { + ViewModelProviders.of(activity!!, viewModelFactory) + .get(ZimManageViewModel::class.java) + } + private val downloadAdapter = DownloadAdapter { + dialogShower.show(StopDownload, { downloader.cancelDownload(it) }) + } + + override fun inject(activityComponent: ActivityComponent) { + activityComponent.inject(this) + } + + 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 + } +} \ 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..d5985e750e --- /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(urlString) + }" + ) + ) + + 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..e3f91e2711 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadService.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadService.java @@ -35,22 +35,8 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.widget.Toast; - -import org.kiwix.kiwixmobile.KiwixApplication; -import org.kiwix.kiwixmobile.KiwixMobileActivity; -import org.kiwix.kiwixmobile.R; -import org.kiwix.kiwixmobile.database.BookDao; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; -import org.kiwix.kiwixmobile.network.KiwixService; -import org.kiwix.kiwixmobile.utils.Constants; -import org.kiwix.kiwixmobile.utils.NetworkUtils; -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 io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; @@ -58,32 +44,34 @@ 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 org.kiwix.kiwixmobile.KiwixApplication; +import org.kiwix.kiwixmobile.KiwixMobileActivity; +import org.kiwix.kiwixmobile.R; +import org.kiwix.kiwixmobile.database.newdb.dao.NewBookDao; +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; +import org.kiwix.kiwixmobile.network.KiwixService; +import org.kiwix.kiwixmobile.utils.Constants; +import org.kiwix.kiwixmobile.utils.NetworkUtils; +import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil; +import org.kiwix.kiwixmobile.utils.StorageUtils; +import org.kiwix.kiwixmobile.utils.TestingUtils; +import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity; -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; -import static org.kiwix.kiwixmobile.downloader.ChunkUtils.isPresent; import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_BOOK; import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_LIBRARY; import static org.kiwix.kiwixmobile.utils.Constants.EXTRA_NOTIFICATION_ID; 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; @@ -118,7 +106,7 @@ public class DownloadService extends Service { SharedPreferenceUtil sharedPreferenceUtil; @Inject - BookDao bookDao; + NewBookDao bookDao; public static void setDownloadFragment(DownloadFragment dFragment) { downloadFragment = dFragment; @@ -163,7 +151,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 +172,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); @@ -195,7 +182,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { NotificationCompat.Action pause = new NotificationCompat.Action(R.drawable.ic_pause_black_24dp, getString(R.string.download_pause), pausePending); NotificationCompat.Action stop = new NotificationCompat.Action(R.drawable.ic_stop_black_24dp, getString(R.string.download_stop), stopPending); - if(flags == START_FLAG_REDELIVERY && book.file == null) { + if (flags == START_FLAG_REDELIVERY /*&& book.file == null*/) { return START_NOT_STICKY; } else { notification.put(notificationID , new NotificationCompat.Builder(this, ONGOING_DOWNLOAD_CHANNEL_ID) @@ -210,7 +197,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); } @@ -223,11 +210,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 +250,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,31 +266,31 @@ 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 - int initial = (int) (getCurrentSize(book) / (Long.valueOf(book.getSize()) * BOOK_SIZE_OFFSET)); - notification.get(notificationID).setProgress(100, initial, false); - updateDownloadFragmentProgress(initial, notificationID, book); - notificationManager.notify(notificationID, notification.get(notificationID).build()); - } + //if (book.file != null && isPresent(book.file.getPath())) { + // // Calculate initial download progress + // int initial = (int) (getCurrentSize(book) / (Long.valueOf(book.getSize()) * BOOK_SIZE_OFFSET)); + // notification.get(notificationID).setProgress(100, initial, false); + // updateDownloadFragmentProgress(initial, notificationID, book); + // notificationManager.notify(notificationID, notification.get(notificationID).build()); + //} kiwixService.getMetaLinks(url) .retryWhen(errors -> errors.flatMap(error -> Observable.timer(5, TimeUnit.SECONDS))) .subscribeOn(AndroidSchedulers.mainThread()) @@ -311,66 +298,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 +437,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 +487,7 @@ private Observable downloadChunk(Chunk chunk) { } if (KiwixMobileActivity.wifiOnly && !NetworkUtils.isWiFi(getApplicationContext()) || - !NetworkUtils.isNetworkAvailable(getApplicationContext())) { + !NetworkUtils.isNetworkAvailable(getApplicationContext())) { pauseDownload(chunk.getNotificationID()); } @@ -513,7 +501,6 @@ private Observable downloadChunk(Chunk chunk) { lastTime = System.currentTimeMillis(); lastSize = downloaded; - } catch (InterruptedException e) { // Happens if someone interrupts your thread. } @@ -607,5 +594,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..021e7329e6 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloadViewHolder.kt @@ -0,0 +1,85 @@ +/* + * 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.content.Context +import android.support.v7.widget.RecyclerView +import android.view.View +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.downloadState +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.DownloadItem +import org.kiwix.kiwixmobile.downloader.model.DownloadState +import org.kiwix.kiwixmobile.downloader.model.DownloadState.Failed +import org.kiwix.kiwixmobile.downloader.model.DownloadState.Paused +import org.kiwix.kiwixmobile.downloader.model.DownloadState.Pending +import org.kiwix.kiwixmobile.downloader.model.DownloadState.Running +import org.kiwix.kiwixmobile.downloader.model.DownloadState.Successful +import org.kiwix.kiwixmobile.downloader.model.FailureReason.Rfc2616HttpCode +import org.kiwix.kiwixmobile.extensions.setBitmap + +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) + } + downloadState.text = toReadableState( + downloadItem.downloadState, containerView.context + ) + } + + private fun toReadableState( + downloadState: DownloadState, + context: Context + ) = when (downloadState) { + is Paused -> context.getString( + downloadState.stringId, + context.getString(downloadState.reason.stringId) + ) + is Failed -> context.getString( + downloadState.stringId, + getTemplateString(downloadState, context) + ) + Pending, + Running, + Successful -> context.getString(downloadState.stringId) + } + + private fun getTemplateString( + downloadState: Failed, + context: Context + ) = when (downloadState.reason) { + is Rfc2616HttpCode -> context.getString( + downloadState.reason.stringId, + downloadState.reason.code + ) + else -> context.getString(downloadState.reason.stringId) + } +} \ 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..0ba4c5a25d --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/DownloaderImpl.kt @@ -0,0 +1,61 @@ +/* + * 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.database.newdb.dao.NewDownloadDao +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: NewDownloadDao, + private val kiwixService: KiwixService +) : Downloader { + + override fun download(book: LibraryNetworkEntity.Book) { + kiwixService.getMetaLinks(book.url) + .take(1) + .subscribe( + { + if(downloadDao.doesNotAlreadyExist(book)){ + val downloadId = downloadRequester.enqueue( + DownloadRequest(it, book) + ) + downloadDao.insert( + DownloadModel(downloadId = downloadId, book = 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/model/Base64String.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/Base64String.kt new file mode 100644 index 0000000000..6f0f8d17cf --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/Base64String.kt @@ -0,0 +1,19 @@ +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 { + encodedString?.let { nonNullString -> + Base64.decode(nonNullString, 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/BookOnDisk.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/BookOnDisk.kt new file mode 100644 index 0000000000..a5076d04ac --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/BookOnDisk.kt @@ -0,0 +1,17 @@ +package org.kiwix.kiwixmobile.downloader.model + +import org.kiwix.kiwixmobile.database.newdb.entities.BookOnDiskEntity +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import java.io.File + +data class BookOnDisk( + val databaseId: Long? = null, + val book: Book, + val file: File +) { + constructor(bookOnDiskEntity: BookOnDiskEntity) : this( + bookOnDiskEntity.id, + bookOnDiskEntity.toBook(), + bookOnDiskEntity.file + ) +} \ 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..9e36c78a0b --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadItem.kt @@ -0,0 +1,40 @@ +/* + * 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 progress get() = ((bytesDownloaded.toFloat() / totalSizeBytes) * 100).toInt() + + constructor(downloadStatus: DownloadStatus) : this( + downloadStatus.downloadId, + Base64String(downloadStatus.book.favicon), + downloadStatus.title, + downloadStatus.description, + downloadStatus.bytesDownloadedSoFar, + downloadStatus.totalSizeBytes, + downloadStatus.state + ) +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectViewCallback.java b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadModel.kt similarity index 71% rename from app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectViewCallback.java rename to app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadModel.kt index c5be8a024b..8fdc34594e 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectViewCallback.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadModel.kt @@ -15,16 +15,14 @@ * 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; +package org.kiwix.kiwixmobile.downloader.model -import org.kiwix.kiwixmobile.base.ViewCallback; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; +import android.view.WindowId +import org.kiwix.kiwixmobile.database.newdb.entities.DownloadEntity +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); -} +data class DownloadModel( + val databaseId: Long? = null, + val downloadId: Long, + val book: LibraryNetworkEntity.Book +) diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectPresenter.java b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadRequest.kt similarity index 53% rename from app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectPresenter.java rename to app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadRequest.kt index d2e87021d6..d4ac4be046 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectPresenter.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadRequest.kt @@ -15,36 +15,26 @@ * 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); - } - +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 urlString: String, + val title: String, + val description: String +) { + + val uri get() = Uri.parse(urlString) + + constructor( + metaLinkNetworkEntity: MetaLinkNetworkEntity, + book: LibraryNetworkEntity.Book + ) : this( + 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..f2c83d8e8f --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadStatus.kt @@ -0,0 +1,163 @@ +/* + * 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.ERROR_CANNOT_RESUME +import android.app.DownloadManager.ERROR_DEVICE_NOT_FOUND +import android.app.DownloadManager.ERROR_FILE_ALREADY_EXISTS +import android.app.DownloadManager.ERROR_FILE_ERROR +import android.app.DownloadManager.ERROR_HTTP_DATA_ERROR +import android.app.DownloadManager.ERROR_INSUFFICIENT_SPACE +import android.app.DownloadManager.ERROR_TOO_MANY_REDIRECTS +import android.app.DownloadManager.ERROR_UNHANDLED_HTTP_CODE +import android.app.DownloadManager.ERROR_UNKNOWN +import android.app.DownloadManager.PAUSED_QUEUED_FOR_WIFI +import android.app.DownloadManager.PAUSED_UNKNOWN +import android.app.DownloadManager.PAUSED_WAITING_FOR_NETWORK +import android.app.DownloadManager.PAUSED_WAITING_TO_RETRY +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 android.net.Uri +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.extensions.get +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import java.io.File + +class DownloadStatus( + val downloadId: Long, + val title: String, + val description: String, + val state: DownloadState, + val bytesDownloadedSoFar: Long, + val totalSizeBytes: Long, + val lastModified: String, + val localUri: String?, + val mediaProviderUri: String?, + val mediaType: String?, + val uri: String?, + val book: Book +) { + + fun toBookOnDisk() = BookOnDisk(book = book, file = File(Uri.parse(localUri).path)) + + 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.book + ) +} + +sealed class DownloadState(val stringId: Int) { + companion object { + fun from( + status: Int, + reason: Int + ) = when (status) { + STATUS_PAUSED -> Paused(PausedReason.from(reason)) + STATUS_FAILED -> Failed(FailureReason.from(reason)) + STATUS_PENDING -> Pending + STATUS_RUNNING -> Running + STATUS_SUCCESSFUL -> Successful + else -> throw RuntimeException("invalid status $status") + } + } + + data class Paused(val reason: PausedReason) : DownloadState(R.string.paused_state) + data class Failed(val reason: FailureReason) : DownloadState(R.string.failed_state) + object Pending : DownloadState(R.string.pending_state) + object Running : DownloadState(R.string.running_state) + object Successful : DownloadState(R.string.successful_state) + + override fun toString(): String { + return javaClass.simpleName + } +} + +sealed class FailureReason(val stringId: Int) { + companion object { + fun from(reason: Int) = when (reason) { + in 100..505 -> Rfc2616HttpCode(reason) + ERROR_CANNOT_RESUME -> CannotResume + ERROR_DEVICE_NOT_FOUND -> StorageNotFound + ERROR_FILE_ALREADY_EXISTS -> FileAlreadyExists + ERROR_FILE_ERROR -> UnknownFileError + ERROR_HTTP_DATA_ERROR -> HttpError + ERROR_INSUFFICIENT_SPACE -> InsufficientSpace + ERROR_TOO_MANY_REDIRECTS -> TooManyRedirects + ERROR_UNHANDLED_HTTP_CODE -> UnhandledHttpCode + ERROR_UNKNOWN -> Unknown + else -> Unknown + } + } + + object CannotResume : FailureReason(R.string.failed_cannot_resume) + object StorageNotFound : FailureReason(R.string.failed_storage_not_found) + object FileAlreadyExists : FailureReason(R.string.failed_file_already_exists) + object UnknownFileError : FailureReason(R.string.failed_unknown_file_error) + object HttpError : FailureReason(R.string.failed_http_error) + object InsufficientSpace : FailureReason(R.string.failed_insufficient_space) + object TooManyRedirects : FailureReason(R.string.failed_too_many_redirects) + object UnhandledHttpCode : FailureReason(R.string.failed_unhandled_http_code) + object Unknown : FailureReason(R.string.failed_unknown) + data class Rfc2616HttpCode(val code: Int) : FailureReason(R.string.failed_http_code) +} + +sealed class PausedReason(val stringId: Int) { + companion object { + fun from(reason: Int) = when (reason) { + PAUSED_QUEUED_FOR_WIFI -> WaitingForWifi + PAUSED_WAITING_FOR_NETWORK -> WaitingForConnectivity + PAUSED_WAITING_TO_RETRY -> WaitingForRetry + PAUSED_UNKNOWN -> Unknown + else -> Unknown + } + } + + object WaitingForWifi : PausedReason(R.string.paused_wifi) + object WaitingForConnectivity : PausedReason(R.string.paused_connectivity) + object WaitingForRetry : PausedReason(R.string.paused_retry) + object Unknown : PausedReason(R.string.paused_unknown) +} \ No newline at end of file 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/BookExtensions.kt b/app/src/main/java/org/kiwix/kiwixmobile/extensions/BookExtensions.kt new file mode 100644 index 0000000000..233d4c67fc --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/BookExtensions.kt @@ -0,0 +1,33 @@ +package org.kiwix.kiwixmobile.extensions + +import org.kiwix.kiwixmobile.KiwixApplication +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book +import org.kiwix.kiwixmobile.utils.BookUtils +import org.kiwix.kiwixmobile.utils.NetworkUtils + +fun Book.calculateSearchMatches( + filter: String, + bookUtils: BookUtils +) { + val searchableText = buildSearchableText(bookUtils) + searchMatches = filter.split("\\s+") + .foldRight(0, + { filterWord, acc -> + if (searchableText.contains(filterWord, true)) acc + 1 + else acc + }) +} + +fun Book.buildSearchableText(bookUtils: BookUtils): String = + StringBuilder().apply { + append(title) + append("|") + append(description) + append("|") + append(NetworkUtils.parseURL(KiwixApplication.getInstance(), url)) + append("|"); + if (bookUtils.localeMap.containsKey(language)) { + append(bookUtils.localeMap[language]!!.displayLanguage) + append("|") + } + }.toString() \ No newline at end of file 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 new file mode 100644 index 0000000000..548f8e2b7e --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ContextExtensions.kt @@ -0,0 +1,29 @@ +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, + length: Int = Toast.LENGTH_LONG +) { + this?.let { + Toast.makeText(this, stringId, length) + .show() + } +} + +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/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/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/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/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 deleted file mode 100755 index 6aaa6c7b5e..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/library/LibraryAdapter.java +++ /dev/null @@ -1,478 +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 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 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 io.reactivex.Completable; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; - -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; - - 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) { - ArrayList 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 675f9c19c6..1610f46114 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,19 +18,18 @@ */ 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 { @ElementList(name = "book", inline = true, required = false) - private LinkedList book; + public LinkedList book; @Attribute(name = "version", required = false) private String version; @@ -45,7 +44,6 @@ public String getVersion() { @Root(name = "book", strict = false) public static class Book implements Serializable{ - @Attribute(name = "id", required = false) public String id; @@ -91,13 +89,11 @@ public static class Book implements Serializable{ @Attribute(name = "tags", required = false) public String tags; - public boolean downloaded = false; - - public String remoteUrl; - public int searchMatches = 0; - + @Deprecated public File file; + @Deprecated + public String remoteUrl; public String getId() { return this.id; 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..d3de9eaf15 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,13 @@ */ 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.Single; 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 +31,7 @@ import retrofit2.http.Url; public interface KiwixService { - @GET("/library/library_zim.xml") Observable getLibrary(); + @GET("/library/library_zim.xml") Single getLibrary(); @GET Observable getMetaLinks(@Url String url); 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..7a0b5c84fb --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/AlertDialogShower.kt @@ -0,0 +1,40 @@ +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()) + .apply { + dialog.title?.let { setTitle(it) } + 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/zim_manager/ZimManageViewCallback.java b/app/src/main/java/org/kiwix/kiwixmobile/utils/DialogShower.kt similarity index 78% rename from app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewCallback.java rename to app/src/main/java/org/kiwix/kiwixmobile/utils/DialogShower.kt index c46deb6533..7280dc766a 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewCallback.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/DialogShower.kt @@ -1,3 +1,5 @@ +package org.kiwix.kiwixmobile.utils + /* * Kiwix Android * Copyright (C) 2018 Kiwix @@ -15,13 +17,9 @@ * 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; - -import org.kiwix.kiwixmobile.base.ViewCallback; - -/** - * Created by srv_twry on 15/2/18. - */ - -public interface ZimManageViewCallback extends ViewCallback { -} +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..febcabc48d --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/KiwixDialog.kt @@ -0,0 +1,28 @@ +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 +) { + + 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) { + object StopDownload : YesNoDialog( + 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..8509b29256 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; @@ -32,10 +31,6 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; - -import org.kiwix.kiwixmobile.library.LibraryAdapter; -import org.kiwix.kiwixmobile.utils.files.FileUtils; - import java.lang.reflect.Field; import java.text.Collator; import java.util.ArrayList; @@ -44,8 +39,9 @@ import java.util.List; import java.util.Locale; import java.util.MissingResourceException; +import org.kiwix.kiwixmobile.utils.files.FileUtils; +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language; -import static org.kiwix.kiwixmobile.utils.Constants.PREF_LANG; import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX; public class LanguageUtils { @@ -239,11 +235,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, 0)); } return values; diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/NetworkUtils.java b/app/src/main/java/org/kiwix/kiwixmobile/utils/NetworkUtils.java index f0c822635c..e72e8d3077 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/utils/NetworkUtils.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/NetworkUtils.java @@ -22,10 +22,8 @@ import android.net.NetworkInfo; import android.os.Build; import android.util.Log; - -import org.kiwix.kiwixmobile.R; - import java.util.UUID; +import org.kiwix.kiwixmobile.R; import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX; @@ -86,9 +84,11 @@ public static String getFileNameFromUrl(String url) { } public static String parseURL(Context context, String url) { - String details; + if (url == null) { + return ""; + } try { - details = url.substring(url.lastIndexOf("/") + 1); + String details = url.substring(url.lastIndexOf("/") + 1); int beginIndex = details.indexOf("_", details.indexOf("_") + 1) + 1; int endIndex = details.lastIndexOf("_"); if (beginIndex < 0 || endIndex > details.length() || beginIndex > endIndex) { diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/SharedPreferenceUtil.java b/app/src/main/java/org/kiwix/kiwixmobile/utils/SharedPreferenceUtil.java index 188b2edaa4..fbd4450afa 100644 --- a/app/src/main/java/org/kiwix/kiwixmobile/utils/SharedPreferenceUtil.java +++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/SharedPreferenceUtil.java @@ -4,7 +4,7 @@ import android.content.SharedPreferences; import android.os.Environment; import android.preference.PreferenceManager; - +import io.reactivex.processors.BehaviorProcessor; import javax.inject.Inject; import javax.inject.Singleton; @@ -13,7 +13,6 @@ import static org.kiwix.kiwixmobile.utils.Constants.PREF_BOTTOM_TOOLBAR; import static org.kiwix.kiwixmobile.utils.Constants.PREF_EXTERNAL_LINK_POPUP; import static org.kiwix.kiwixmobile.utils.Constants.PREF_FULLSCREEN; -import static org.kiwix.kiwixmobile.utils.Constants.PREF_FULL_TEXT_SEARCH; import static org.kiwix.kiwixmobile.utils.Constants.PREF_HIDE_TOOLBAR; import static org.kiwix.kiwixmobile.utils.Constants.PREF_IS_FIRST_RUN; import static org.kiwix.kiwixmobile.utils.Constants.PREF_LANG; @@ -33,11 +32,13 @@ public class SharedPreferenceUtil { private SharedPreferences sharedPreferences; private SharedPreferences.Editor editor; + public final BehaviorProcessor prefStorages; @Inject public SharedPreferenceUtil(Context context) { sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); editor = sharedPreferences.edit(); + prefStorages = BehaviorProcessor.createDefault(getPrefStorage()); } public void remove(String key) { @@ -131,6 +132,7 @@ public void putPrefStorageTitle(String storageTitle) { public void putPrefStorage(String storage) { editor.putString(PREF_STORAGE, storage).apply(); + prefStorages.onNext(storage); } public void putPrefFullScreen(boolean fullScreen) { 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..95935965f7 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 @@ -26,17 +26,19 @@ import android.os.Environment; import android.provider.MediaStore; import android.util.Log; - -import org.kiwix.kiwixmobile.ZimContentProvider; -import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; - +import eu.mhutti1.utils.storage.StorageDevice; +import eu.mhutti1.utils.storage.StorageDeviceUtils; import java.io.File; import java.io.FilenameFilter; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Vector; - -import eu.mhutti1.utils.storage.StorageDevice; -import eu.mhutti1.utils.storage.StorageDeviceUtils; +import org.kiwix.kiwixmobile.ZimContentProvider; +import org.kiwix.kiwixmobile.downloader.model.BookOnDisk; +import org.kiwix.kiwixmobile.downloader.model.DownloadModel; +import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity; +import org.kiwix.kiwixmobile.utils.StorageUtils; import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX; @@ -47,13 +49,15 @@ public class FileSearch { private final Context context; + private final List downloads; private final ResultListener listener; private boolean fileSystemScanCompleted = false; private boolean mediaStoreScanCompleted = false; - public FileSearch(Context ctx, ResultListener listener) { + public FileSearch(Context ctx, List downloads, ResultListener listener) { this.context = ctx; + this.downloads = downloads; this.listener = listener; } @@ -95,7 +99,6 @@ public void scanMediaStore() { try { while (query.moveToNext()) { File file = new File(query.getString(0)); - if (file.canRead()) onFileFound(file.getAbsolutePath()); } @@ -109,11 +112,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(); } @@ -171,7 +176,7 @@ private File[] listFilesAsArray(File directory, FilenameFilter[] filter, int rec return files.toArray(arr); } - public static synchronized LibraryNetworkEntity.Book fileToBook(String filePath) { + public static synchronized BookOnDisk fileToBookOnDisk(String filePath) { LibraryNetworkEntity.Book book = null; if (ZimContentProvider.zimFileName != null) { @@ -185,7 +190,6 @@ public static synchronized LibraryNetworkEntity.Book fileToBook(String filePath) book = new LibraryNetworkEntity.Book(); book.title = ZimContentProvider.getZimFileTitle(); book.id = ZimContentProvider.getId(); - book.file = new File(filePath); book.size = String.valueOf(ZimContentProvider.getFileSize()); book.favicon = ZimContentProvider.getFavicon(); book.creator = ZimContentProvider.getCreator(); @@ -207,7 +211,8 @@ public static synchronized LibraryNetworkEntity.Book fileToBook(String filePath) } ZimContentProvider.originalFileName = ""; - return book; + return book == null ? null + : new BookOnDisk(null, book, new File(filePath)); } // Fill fileList with files found in the specific directory @@ -222,14 +227,26 @@ private void scanDirectory(String directory, FilenameFilter[] filter) { // Callback that a new file has been found public void onFileFound(String filePath) { - LibraryNetworkEntity.Book book = fileToBook(filePath); + if (fileIsDownloading(filePath)) { + return; + } + BookOnDisk book = fileToBookOnDisk(filePath); if (book != null) listener.onBookFound(book); } + private boolean fileIsDownloading(String filePath) { + for (DownloadModel download : downloads) { + if (filePath.endsWith(StorageUtils.getFileNameFromUrl(download.getBook().getUrl()))) { + return true; + } + } + return false; + } + public interface ResultListener { - void onBookFound(LibraryNetworkEntity.Book book); + void onBookFound(BookOnDisk book); void onScanCompleted(); } diff --git a/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageAdapter.kt new file mode 100644 index 0000000000..b5f6116fbc --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageAdapter.kt @@ -0,0 +1,33 @@ +package org.kiwix.kiwixmobile.views + +import android.support.v7.widget.RecyclerView +import android.view.ViewGroup +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.extensions.inflate +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language + +class LanguageAdapter(val listItems: MutableList) : RecyclerView.Adapter() { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = LanguageViewHolder( + parent.inflate(R.layout.language_check_item, false), + this::toggleItemAt + ) + + override fun getItemCount() = listItems.size + + override fun onBindViewHolder( + holder: LanguageViewHolder, + position: Int + ) { + holder.bind(listItems[position], position) + } + + private fun toggleItemAt(position: Int) { + listItems[position] = listItems[position].also { it.active = !it.active } + notifyItemChanged(position) + } +} + diff --git a/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageSelectDialog.java b/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageSelectDialog.java deleted file mode 100644 index a808fdb3d1..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageSelectDialog.java +++ /dev/null @@ -1,213 +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.views; - -import android.content.Context; -import android.graphics.Typeface; -import android.support.annotation.NonNull; -import android.support.v7.app.AlertDialog; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.CheckBox; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TextView; - -import org.kiwix.kiwixmobile.R; -import org.kiwix.kiwixmobile.library.LibraryAdapter; -import org.kiwix.kiwixmobile.utils.LanguageUtils; - -import java.util.List; -import java.util.Map; - -/** - * Created by judebrauer on 12/6/17 - */ - -public class LanguageSelectDialog extends AlertDialog { - protected LanguageSelectDialog(@NonNull Context context) { - super(context); - } - - public static class Builder extends AlertDialog.Builder { - private List languages; - private Map languageCounts; - private boolean singleSelect = false; - private String selectedLanguage; - private OnLanguageSelectedListener languageSelectedListener; - - public Builder(@NonNull Context context) { - super(context); - } - - public Builder(@NonNull Context context, int themeResId) { - super(context, themeResId); - } - - public Builder setLanguages(List languages) { - this.languages = languages; - return this; - } - - public Builder setLanguageCounts(Map languageCounts) { - this.languageCounts = languageCounts; - return this; - } - - public Builder setSingleSelect(boolean singleSelect) { - this.singleSelect = singleSelect; - return this; - } - - // Should only be called if setSingleSelect has previously been called with a value of true - public Builder setSelectedLanguage(String languageCode) { - this.selectedLanguage = languageCode; - return this; - } - - // Should only be called if setSingleSelect has previously been called with a value of true - public Builder setOnLanguageSelectedListener(OnLanguageSelectedListener listener) { - languageSelectedListener = listener; - return this; - } - - @Override - public AlertDialog create() { - LinearLayout view = (LinearLayout) View - .inflate(getContext(), R.layout.language_selection, null); - ListView listView = view.findViewById(R.id.language_check_view); - int size = 0; - try { - size = languages.size(); - } catch (NullPointerException e) { - e.printStackTrace(); - } - - LanguageArrayAdapter languageArrayAdapter = new LanguageArrayAdapter(getContext(), 0, - languages, languageCounts, singleSelect, selectedLanguage); - listView.setAdapter(languageArrayAdapter); - setView(view); - - if (languageSelectedListener != null) { - setPositiveButton(android.R.string.ok, ((dialog, which) -> { - languageSelectedListener.onLanguageSelected(languageArrayAdapter.getSelectedLanguage()); - })); - } - - return super.create(); - } - } - - 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, - Map languageCounts, boolean singleSelect, String selectedLanguage) { - super(context, textViewResourceId, languages); - this.languageCounts = languageCounts; - this.context = context; - this.singleSelect = singleSelect; - this.selectedLanguage = selectedLanguage; - } - - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - LanguageArrayAdapter.ViewHolder holder; - - if (convertView == null) { - convertView = View.inflate(getContext(), R.layout.language_check_item, null); - holder = new LanguageArrayAdapter.ViewHolder(); - holder.row = convertView.findViewById(R.id.language_row); - holder.checkBox = convertView.findViewById(R.id.language_checkbox); - holder.language = convertView.findViewById(R.id.language_name); - holder.languageLocalized = convertView.findViewById(R.id.language_name_localized); - holder.languageEntriesCount = convertView.findViewById(R.id.language_entries_count); - convertView.setTag(holder); - } else { - holder = (LanguageArrayAdapter.ViewHolder) convertView.getTag(); - } - - // 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); - - LibraryAdapter.Language language = getItem(position); - holder.language.setText(language.language); - holder.languageLocalized.setText(context.getString(R.string.language_localized, - language.languageLocalized)); - holder.languageLocalized.setTypeface(Typeface.createFromAsset(context.getAssets(), - LanguageUtils.getTypeface(language.languageCode))); - - if (languageCounts != null) { - holder.languageEntriesCount.setText(context.getString(R.string.language_count, - languageCounts.get(language.languageCode))); - } else { - holder.languageEntriesCount.setVisibility(View.GONE); - } - - if (!singleSelect) { - holder.checkBox.setChecked(language.active); - } else { - holder.checkBox.setClickable(false); - holder.checkBox.setFocusable(false); - - if (getSelectedLanguage().equalsIgnoreCase(language.languageCodeISO2)) { - holder.checkBox.setChecked(true); - } else { - holder.checkBox.setChecked(false); - } - - convertView.setOnClickListener((v -> { - setSelectedLanguage(language.languageCodeISO2); - notifyDataSetChanged(); - })); - } - - return convertView; - } - - public String getSelectedLanguage() { - return selectedLanguage; - } - - public void setSelectedLanguage(String selectedLanguage) { - this.selectedLanguage = selectedLanguage; - } - - // We are using the ViewHolder pattern in order to optimize the ListView by reusing - // Views and saving them to this mLibrary class, and not inflating the layout every time - // we need to create a row. - private class ViewHolder { - ViewGroup row; - CheckBox checkBox; - TextView language; - TextView languageLocalized; - TextView languageEntriesCount; - } - } - - public interface OnLanguageSelectedListener { - void onLanguageSelected(String languageCode); - } -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageSelectDialog.kt b/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageSelectDialog.kt new file mode 100644 index 0000000000..d58ffb3f89 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageSelectDialog.kt @@ -0,0 +1,65 @@ +/* + * 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.views + +import android.content.Context +import android.support.v7.app.AlertDialog +import android.support.v7.widget.LinearLayoutManager +import android.view.View +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.language_selection.language_check_view +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language + +/** + * Created by judebrauer on 12/6/17 + */ + +class LanguageSelectDialog constructor( + context: Context +) : AlertDialog(context) { + + class Builder : AlertDialog.Builder, LayoutContainer { + lateinit var dialogView: View + override val containerView: View? by lazy { dialogView } + lateinit var onOkClicked: (List) -> Unit + var languages: List = listOf() + + constructor(context: Context) : super(context) + + constructor( + context: Context, + themeResId: Int + ) : super(context, themeResId) + + override fun create(): AlertDialog { + dialogView = View.inflate(context, R.layout.language_selection, null) + val languageArrayAdapter = LanguageAdapter(languages.toMutableList()) + language_check_view.run { + adapter = languageArrayAdapter + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + setHasFixedSize(true) + } + setView(dialogView) + setPositiveButton(android.R.string.ok) { _, _ -> + onOkClicked.invoke(languageArrayAdapter.listItems) + } + return super.create() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageViewHolder.kt new file mode 100644 index 0000000000..5b79f81193 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/views/LanguageViewHolder.kt @@ -0,0 +1,46 @@ +package org.kiwix.kiwixmobile.views + +import android.graphics.Typeface +import android.support.v7.widget.RecyclerView.ViewHolder +import android.view.View +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.language_check_item.language_checkbox +import kotlinx.android.synthetic.main.language_check_item.language_entries_count +import kotlinx.android.synthetic.main.language_check_item.language_name +import kotlinx.android.synthetic.main.language_check_item.language_name_localized +import kotlinx.android.synthetic.main.language_check_item.language_row +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.utils.LanguageUtils +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language + +class LanguageViewHolder( + override val containerView: View, + private val onCheckboxChecked: (Int) -> Unit +) : ViewHolder(containerView), + LayoutContainer { + fun bind( + language: Language, + position: Int + ) { + val context = containerView.context + language_name.text = language.language + language_name_localized.text = context.getString( + R.string.language_localized, + language.languageLocalized + ) + language_name_localized.typeface = Typeface.createFromAsset( + context.assets, + LanguageUtils.getTypeface(language.languageCode) + ) + language_entries_count.text = + context.getString(R.string.language_count, language.occurencesOfLanguage) + language_checkbox.setOnCheckedChangeListener(null) + language_checkbox.isChecked = language.active + language_checkbox.setOnCheckedChangeListener { _, _ -> + onCheckboxChecked.invoke(position) + } + language_row.setOnClickListener { + language_checkbox.toggle() + } + } +} \ No newline at end of file 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..ff8c9ab5af --- /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.BehaviorProcessor +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 + + val networkStates = + BehaviorProcessor.createDefault(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/DownloadNotificationClickedReceiver.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/DownloadNotificationClickedReceiver.kt new file mode 100644 index 0000000000..40c937a6c0 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/DownloadNotificationClickedReceiver.kt @@ -0,0 +1,51 @@ +/* + * 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 + +import android.app.DownloadManager +import android.content.Context +import android.content.Intent +import android.os.Bundle +import org.kiwix.kiwixmobile.KiwixApplication +import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao +import javax.inject.Inject + +class DownloadNotificationClickedReceiver : BaseBroadcastReceiver() { + override val action: String = DownloadManager.ACTION_NOTIFICATION_CLICKED + + @Inject lateinit var downloadDao: NewDownloadDao + + override fun onIntentWithActionReceived( + context: Context, + intent: Intent + ) { + KiwixApplication.getApplicationComponent() + .inject(this) + if (downloadDao.containsAny(*longArrayFrom(intent.extras))) { + context.startActivity( + Intent(context, ZimManageActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + putExtra(ZimManageActivity.TAB_EXTRA, 2) + } + ) + } + } + + private fun longArrayFrom(extras: Bundle?) = + extras?.getLongArray(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS) ?: longArrayOf() +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/Fat32Checker.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/Fat32Checker.kt new file mode 100644 index 0000000000..706b7ac87e --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/Fat32Checker.kt @@ -0,0 +1,118 @@ +/* + * 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 + +import android.Manifest.permission +import android.content.pm.PackageManager +import android.os.FileObserver +import android.support.v4.content.ContextCompat +import android.util.Log +import io.reactivex.Flowable +import io.reactivex.functions.Function3 +import io.reactivex.processors.BehaviorProcessor +import org.kiwix.kiwixmobile.KiwixApplication +import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4GbFile +import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CannotWrite4GbFile +import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.NotEnoughSpaceFor4GbFile +import java.io.File +import java.io.RandomAccessFile +import java.util.concurrent.TimeUnit.SECONDS +import javax.inject.Inject + +class Fat32Checker @Inject constructor(sharedPreferenceUtil: SharedPreferenceUtil) { + private val _fileSystemStates: BehaviorProcessor = BehaviorProcessor.create() + val fileSystemStates = _fileSystemStates.distinctUntilChanged() + var fileObserver: FileObserver? = null + private val requestCheckSystemFileType = BehaviorProcessor.createDefault(Unit) + + init { + Flowable.combineLatest( + sharedPreferenceUtil.prefStorages.distinctUntilChanged(), + requestCheckSystemFileType, + pollForExternalStoragePermissionGranted(), + Function3 { storage: String, _: Unit, _: Boolean -> storage } + ) + .subscribe( + { + val systemState = toFileSystemState(it) + _fileSystemStates.onNext(systemState) + fileObserver = if (systemState == NotEnoughSpaceFor4GbFile) fileObserver(it) else null + + }, + Throwable::printStackTrace + ) + } + + private fun pollForExternalStoragePermissionGranted() = + Flowable.interval(1, SECONDS) + .map { + ContextCompat.checkSelfPermission( + KiwixApplication.getInstance(), permission.WRITE_EXTERNAL_STORAGE + ) == PackageManager.PERMISSION_GRANTED + } + .filter { it } + .take(1) + + private fun fileObserver(it: String?): FileObserver { + return object : FileObserver(it, MOVED_FROM or DELETE) { + override fun onEvent( + event: Int, + path: String? + ) { + requestCheckSystemFileType.onNext(Unit) + } + }.apply { startWatching() } + } + + private fun toFileSystemState(it: String) = + when { + File(it).freeSpace > FOUR_GIGABYTES_IN_BYTES -> + if (canCreate4GbFile(it)) CanWrite4GbFile + else CannotWrite4GbFile + else -> NotEnoughSpaceFor4GbFile + } + + private fun canCreate4GbFile(storage: String): Boolean { + val path = "$storage/large_file_test.txt" + File(path).delete() + try { + RandomAccessFile(path, "rw").use { + it.setLength(FOUR_GIGABYTES_IN_BYTES) + return true + } + } catch (e: Exception) { + e.printStackTrace() + Log.d("Fat32Checker", e.message) + return false + } finally { + File(path).delete() + } + } + + companion object { + const val FOUR_GIGABYTES_IN_BYTES = 4L * 1024L * 1024L * 1024L + const val FOUR_GIGABYTES_IN_KILOBYTES = 4L * 1024L * 1024L + } + + sealed class FileSystemState() { + object NotEnoughSpaceFor4GbFile : FileSystemState() + object CanWrite4GbFile : FileSystemState() + object CannotWrite4GbFile : FileSystemState() + } +} \ 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/SectionsPagerAdapter.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/SectionsPagerAdapter.java deleted file mode 100644 index 8b8ca5d95a..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/SectionsPagerAdapter.java +++ /dev/null @@ -1,85 +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; - -import android.content.Context; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; - -import org.kiwix.kiwixmobile.R; -import org.kiwix.kiwixmobile.downloader.DownloadFragment; -import org.kiwix.kiwixmobile.zim_manager.fileselect_view.ZimFileSelectFragment; -import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment; - -/** - * A {@link FragmentPagerAdapter} that returns a fragment corresponding to - * one of the sections/tabs/pages. - */ -public class SectionsPagerAdapter extends FragmentPagerAdapter { - - private ZimFileSelectFragment zimFileSelectFragment = new ZimFileSelectFragment(); - - public LibraryFragment libraryFragment = new LibraryFragment(); - - private DownloadFragment downloadFragment = new DownloadFragment(); - - private Context context; - - public DownloadFragment getDownloadFragment() { - return downloadFragment; - } - - public SectionsPagerAdapter(Context context, FragmentManager fm) { - super(fm); - this.context = context; - } - - @Override - public Fragment getItem(int position) { - // getItem is called to instantiate the fragment for the given page. - switch (position) { - case 0: - return zimFileSelectFragment; - case 1: - return libraryFragment; - case 2: - return downloadFragment; - default: - return null; - } - } - @Override - public int getCount() { - // Show 3 total pages. - return 3; - } - - @Override - public CharSequence getPageTitle(int position) { - switch (position) { - case 0: - return context.getResources().getString(R.string.local_zims); - case 1: - return context.getResources().getString(R.string.remote_zims); - case 2: - return context.getResources().getString(R.string.zim_downloads); - } - return null; - } -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/SectionsPagerAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/SectionsPagerAdapter.kt new file mode 100644 index 0000000000..96fcdc9448 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/SectionsPagerAdapter.kt @@ -0,0 +1,50 @@ +/* + * 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 + +import android.content.Context +import android.support.v4.app.FragmentManager +import android.support.v4.app.FragmentPagerAdapter +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.downloader.DownloadFragment +import org.kiwix.kiwixmobile.zim_manager.fileselect_view.ZimFileSelectFragment +import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment + +class SectionsPagerAdapter( + private val context: Context, + fm: FragmentManager +) : FragmentPagerAdapter(fm) { + + override fun getItem(position: Int) = when (position) { + 0 -> ZimFileSelectFragment() + 1 -> LibraryFragment() + 2 -> DownloadFragment() + else -> throw RuntimeException("No matching fragment for position: $position") + } + + override fun getCount() = 3 + + override fun getPageTitle(position: Int) = context.getString( + when (position) { + 0 -> R.string.local_zims + 1 -> R.string.remote_zims + 2 -> R.string.zim_downloads + else -> throw RuntimeException("No matching title for position: $position") + } + ) +} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/SimplePageChangeListener.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/SimplePageChangeListener.kt new file mode 100644 index 0000000000..c8192ae56b --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/SimplePageChangeListener.kt @@ -0,0 +1,21 @@ +package org.kiwix.kiwixmobile.zim_manager + +import android.support.v4.view.ViewPager.OnPageChangeListener + +class SimplePageChangeListener(val onPageSelectedAction: (Int) -> Unit) : OnPageChangeListener { + override fun onPageScrolled( + position: Int, + positionOffset: Float, + positionOffsetPixels: Int + ) { + + } + + override fun onPageSelected(position: Int) { + onPageSelectedAction.invoke(position) + } + + override fun onPageScrollStateChanged(state: Int) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/SimpleTextListener.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/SimpleTextListener.kt new file mode 100644 index 0000000000..6b8cae619a --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/SimpleTextListener.kt @@ -0,0 +1,14 @@ +package org.kiwix.kiwixmobile.zim_manager + +import android.support.v7.widget.SearchView.OnQueryTextListener + +class SimpleTextListener(val onQueryTextChangeAction: (String) -> Unit) : OnQueryTextListener { + override fun onQueryTextSubmit(s: String): Boolean { + return false + } + + override fun onQueryTextChange(s: String): Boolean { + onQueryTextChangeAction.invoke(s) + return true + } +} \ 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 deleted file mode 100644 index e7ae0a6a5f..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java +++ /dev/null @@ -1,276 +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; - -import android.app.Activity; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.provider.Settings; -import android.support.annotation.NonNull; -import android.support.design.widget.AppBarLayout; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.TabLayout; -import android.support.v4.app.FragmentPagerAdapter; -import android.support.v4.view.ViewPager; -import android.support.v7.widget.SearchView; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.Toast; - -import org.kiwix.kiwixmobile.KiwixMobileActivity; -import org.kiwix.kiwixmobile.R; -import org.kiwix.kiwixmobile.base.BaseActivity; -import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity; -import org.kiwix.kiwixmobile.utils.LanguageUtils; -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; - -public class ZimManageActivity extends BaseActivity implements ZimManageViewCallback { - - public static final String TAB_EXTRA = "TAB"; - /** - * The {@link android.support.v4.view.PagerAdapter} that will provide - * fragments for each of the sections. We use a - * {@link FragmentPagerAdapter} derivative, which will keep every - * loaded fragment in memory. If this becomes too memory intensive, it - * may be best to switch to a - * {@link android.support.v4.app.FragmentStatePagerAdapter}. - */ - public SectionsPagerAdapter mSectionsPagerAdapter; - - /** - * The {@link ViewPager} that will host the section contents. - */ - private ViewPager mViewPager; - - public Toolbar toolbar; - - private MenuItem searchItem; - - private MenuItem languageItem; - - public SearchView searchView; - - private String searchQuery = ""; - - static String KIWIX_TAG = "kiwix"; - - @Inject - ZimManagePresenter zimManagePresenter; - @Inject - SharedPreferenceUtil sharedPreferenceUtil; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - LanguageUtils.handleLocaleChange(this, sharedPreferenceUtil); - - if (KiwixSettingsActivity.nightMode(sharedPreferenceUtil)) { - setTheme(R.style.AppTheme_Night); - } - setContentView(R.layout.zim_manager); - - setUpToolbar(); - zimManagePresenter.attachView(this); - - zimManagePresenter.showNoWifiWarning(this, getIntent().getAction()); - - // Create the adapter that will return a fragment for each of the three - // primary sections of the activity. - mSectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager()); - - // Set up the ViewPager with the sections adapter. - mViewPager = findViewById(R.id.container); - mViewPager.setAdapter(mSectionsPagerAdapter); - mViewPager.setOffscreenPageLimit(2); - - TabLayout tabLayout = findViewById(R.id.tabs); - tabLayout.setupWithViewPager(mViewPager); - - mViewPager.setCurrentItem(getIntent().getIntExtra(TAB_EXTRA,0)); - mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - - } - - @Override - public void onPageSelected(int position) { - updateMenu(position); - } - - @Override - public void onPageScrollStateChanged(int state) { - - } - }); - - // Disable scrolling for the AppBarLayout on top of the screen - // User can only scroll the PageViewer component - AppBarLayout appBarLayout = findViewById(R.id.appbar); - if (appBarLayout.getLayoutParams() != null) { - CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams(); - AppBarLayout.Behavior appBarLayoutBehaviour = new AppBarLayout.Behavior(); - appBarLayoutBehaviour.setDragCallback(new AppBarLayout.Behavior.DragCallback() { - @Override - public boolean canDrag(@NonNull AppBarLayout appBarLayout) { - return false; - } - }); - layoutParams.setBehavior(appBarLayoutBehaviour); - } - - Log.i(KIWIX_TAG, "ZimManageActivity successfully bootstrapped"); - } - - private void updateMenu(int position) { - if (searchItem == null) - return; - switch (position) { - case 0: - searchItem.setVisible(false); - languageItem.setVisible(false); - break; - case 1: - searchItem.setVisible(true); - languageItem.setVisible(true); - break; - case 2: - searchItem.setVisible(false); - languageItem.setVisible(false); - break; - } - } - - private void setUpToolbar() { - toolbar = findViewById(R.id.toolbar); - - setSupportActionBar(toolbar); - - getSupportActionBar().setHomeButtonEnabled(true); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setTitle(R.string.zim_manager); - - toolbar.setNavigationOnClickListener(v -> onBackPressed()); - } - - - public void displayDownloadInterface() { - mSectionsPagerAdapter.notifyDataSetChanged(); - mViewPager.setCurrentItem(2); - } - - @Override - public void onBackPressed() { - int value = Settings.System.getInt(getContentResolver(), Settings.System.ALWAYS_FINISH_ACTIVITIES, 0); - if (value == 1) { - Intent startIntent = new Intent(this, KiwixMobileActivity.class); - // startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(startIntent); - } else { - super.onBackPressed(); // optional depending on your needs - } - - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_zim_manager, menu); - searchItem = menu.findItem(R.id.action_search); - languageItem = menu.findItem(R.id.select_language); - searchView = (SearchView) searchItem.getActionView(); - updateMenu(mViewPager.getCurrentItem()); - toolbar.setOnClickListener(v -> { - if (mViewPager.getCurrentItem() == 1) - menu.findItem(R.id.action_search).expandActionView(); - }); - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String s) { - return false; - } - - @Override - public boolean onQueryTextChange(String s) { - searchQuery = s; - - if (mSectionsPagerAdapter.libraryFragment.libraryAdapter != null) { - mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(searchQuery); - } - mViewPager.setCurrentItem(1); - return true; - } - }); - - - return true; - } - - @Override - 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(); - } - } - default: - return super.onOptionsItemSelected(item); - } - } - - // Set zim file and return - public void finishResult(String path) { - if (path != null) { - File file = new File(path); - Uri uri = Uri.fromFile(file); - Log.i(TAG_KIWIX, "Opening Zim File: " + uri); - setResult(Activity.RESULT_OK, new Intent().setData(uri)); - finish(); - } else { - setResult(Activity.RESULT_CANCELED); - finish(); - } - } - - private void showLanguageSelect() { - new LanguageSelectDialog.Builder(this, dialogStyle()) - .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); - }) - .show(); - } -} diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.kt new file mode 100644 index 0000000000..efce6c10bf --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.kt @@ -0,0 +1,186 @@ +/* + * 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 + +import android.app.Activity +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProvider +import android.arch.lifecycle.ViewModelProviders +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.provider.Settings.System +import android.support.v7.widget.SearchView +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import io.reactivex.Flowable +import io.reactivex.schedulers.Schedulers +import kotlinx.android.synthetic.main.zim_manager.manageViewPager +import kotlinx.android.synthetic.main.zim_manager.tabs +import kotlinx.android.synthetic.main.zim_manager.toolbar +import org.kiwix.kiwixmobile.KiwixMobileActivity +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.base.BaseActivity +import org.kiwix.kiwixmobile.database.newdb.dao.NewLanguagesDao +import org.kiwix.kiwixmobile.extensions.toast +import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity +import org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX +import org.kiwix.kiwixmobile.utils.LanguageUtils +import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil +import org.kiwix.kiwixmobile.utils.StyleUtils.dialogStyle +import org.kiwix.kiwixmobile.views.LanguageSelectDialog +import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language +import java.io.File +import javax.inject.Inject + +class ZimManageActivity : BaseActivity() { + + private val zimManageViewModel: ZimManageViewModel by lazy { + ViewModelProviders.of(this, viewModelFactory) + .get(ZimManageViewModel::class.java) + } + private val mSectionsPagerAdapter: SectionsPagerAdapter by lazy { + SectionsPagerAdapter(this, supportFragmentManager) + } + + private var searchItem: MenuItem? = null + private var languageItem: MenuItem? = null + + @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil + @Inject lateinit var languagesDao: NewLanguagesDao + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + LanguageUtils.handleLocaleChange(this, sharedPreferenceUtil) + + if (KiwixSettingsActivity.nightMode(sharedPreferenceUtil)) { + setTheme(R.style.AppTheme_Night) + } + setContentView(R.layout.zim_manager) + + setUpToolbar() + manageViewPager.run { + adapter = mSectionsPagerAdapter + offscreenPageLimit = 2 + tabs.setupWithViewPager(this) + addOnPageChangeListener(SimplePageChangeListener(this@ZimManageActivity::updateMenu)) + } + zimManageViewModel.languageItems.observe(this, Observer { + onLanguageItemsForDialogUpdated(it!!) + }) + setViewPagerPositionFromIntent(intent) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + setViewPagerPositionFromIntent(intent) + } + + private fun setViewPagerPositionFromIntent(intent: Intent?) { + if (intent?.hasExtra(TAB_EXTRA) == true) { + manageViewPager.currentItem = intent.getIntExtra(TAB_EXTRA, 0) + } + } + + private fun onLanguageItemsForDialogUpdated(languages: List) { + if (languages.isEmpty()) { + toast(R.string.wait_for_load) + } else { + LanguageSelectDialog.Builder(this, dialogStyle()) + .apply { + onOkClicked = { + Flowable.fromCallable { + languagesDao.insert(it) + } + .subscribeOn(Schedulers.io()) + .subscribe() + } + this.languages = languages + } + .show() + } + } + + private fun updateMenu(position: Int) { + searchItem?.isVisible = position == 1 + languageItem?.isVisible = position == 1 + } + + private fun setUpToolbar() { + setSupportActionBar(toolbar) + supportActionBar!!.setHomeButtonEnabled(true) + supportActionBar!!.setDisplayHomeAsUpEnabled(true) + supportActionBar!!.setTitle(R.string.zim_manager) + toolbar.setNavigationOnClickListener { _ -> onBackPressed() } + toolbar.setOnClickListener { _ -> + if (manageViewPager.currentItem == 1) + searchItem?.expandActionView() + } + } + + override fun onBackPressed() { + val value = System.getInt(contentResolver, System.ALWAYS_FINISH_ACTIVITIES, 0) + if (value == 1) { + startActivity(Intent(this, KiwixMobileActivity::class.java)) + } else { + super.onBackPressed() // optional depending on your needs + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the menu; this adds items to the action bar if it is present. + menuInflater.inflate(R.menu.menu_zim_manager, menu) + searchItem = menu.findItem(R.id.action_search) + languageItem = menu.findItem(R.id.select_language) + val searchView = searchItem!!.actionView as SearchView + updateMenu(manageViewPager.currentItem) + searchView.setOnQueryTextListener(SimpleTextListener { + zimManageViewModel.requestFiltering.onNext(it) + }) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.select_language -> { + zimManageViewModel.requestLanguagesDialog.onNext(Unit) + } + } + return super.onOptionsItemSelected(item) + } + + // Set zim file and return + fun finishResult(path: String?) { + if (path != null) { + val file = File(path) + val uri = Uri.fromFile(file) + Log.i(TAG_KIWIX, "Opening Zim File: $uri") + setResult(Activity.RESULT_OK, Intent().setData(uri)) + finish() + } else { + setResult(Activity.RESULT_CANCELED) + finish() + } + } + + companion object { + const val TAB_EXTRA = "TAB" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManagePresenter.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManagePresenter.java deleted file mode 100644 index 2ebe09ec70..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManagePresenter.java +++ /dev/null @@ -1,62 +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; - -import android.app.AlertDialog; -import android.content.Context; -import android.util.Log; - -import org.kiwix.kiwixmobile.KiwixMobileActivity; -import org.kiwix.kiwixmobile.R; -import org.kiwix.kiwixmobile.base.BasePresenter; -import org.kiwix.kiwixmobile.downloader.DownloadService; -import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil; - -import javax.inject.Inject; - -import static org.kiwix.kiwixmobile.zim_manager.ZimManageActivity.KIWIX_TAG; - -/** - * Presenter for {@link ZimManageActivity} - */ - -class ZimManagePresenter extends BasePresenter { - - @Inject - SharedPreferenceUtil mSharedPreferenceUtil; - - @Inject - ZimManagePresenter() { - } - - void showNoWifiWarning(Context context, String action) { - if (DownloadService.ACTION_NO_WIFI.equals(action)) { - new AlertDialog.Builder(context) - .setTitle(R.string.wifi_only_title) - .setMessage(R.string.wifi_only_msg) - .setPositiveButton(R.string.yes, (dialog, i) -> { - mSharedPreferenceUtil.putPrefWifiOnly(false); - KiwixMobileActivity.wifiOnly = false; - }) - .setNegativeButton(R.string.no, (dialog, i) -> { - }) - .show(); - Log.i(KIWIX_TAG, "No WiFi, showing warning"); - } - } -} 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..22997ee784 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt @@ -0,0 +1,442 @@ +/* + * 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 + +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.Function6 +import io.reactivex.processors.BehaviorProcessor +import io.reactivex.processors.PublishProcessor +import io.reactivex.schedulers.Schedulers +import org.kiwix.kiwixmobile.R +import org.kiwix.kiwixmobile.database.newdb.dao.NewBookDao +import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao +import org.kiwix.kiwixmobile.database.newdb.dao.NewLanguagesDao +import org.kiwix.kiwixmobile.downloader.Downloader +import org.kiwix.kiwixmobile.downloader.model.BookOnDisk +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.calculateSearchMatches +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.utils.BookUtils +import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState +import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4GbFile +import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CannotWrite4GbFile +import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.NotEnoughSpaceFor4GbFile +import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED +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.MILLISECONDS +import java.util.concurrent.TimeUnit.SECONDS +import javax.inject.Inject + +class ZimManageViewModel @Inject constructor( + private val downloadDao: NewDownloadDao, + private val bookDao: NewBookDao, + private val languageDao: NewLanguagesDao, + private val downloader: Downloader, + private val storageObserver: StorageObserver, + private val kiwixService: KiwixService, + private val context: Application, + private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver, + private val bookUtils: BookUtils, + private val fat32Checker: Fat32Checker +) : ViewModel() { + + val libraryItems: MutableLiveData> = MutableLiveData() + val downloadItems: MutableLiveData> = MutableLiveData() + val bookItems: MutableLiveData> = MutableLiveData() + val deviceListIsRefreshing = MutableLiveData() + val libraryListIsRefreshing = MutableLiveData() + val networkStates = MutableLiveData() + val languageItems = MutableLiveData>() + + val requestFileSystemCheck = PublishProcessor.create() + val requestDownloadLibrary = BehaviorProcessor.createDefault(Unit) + val requestFiltering = BehaviorProcessor.createDefault("") + val requestLanguagesDialog = PublishProcessor.create() + + private val compositeDisposable = CompositeDisposable() + + init { + compositeDisposable.addAll(*disposables()) + context.registerReceiver(connectivityBroadcastReceiver) + } + + override fun onCleared() { + compositeDisposable.clear() + context.unregisterReceiver(connectivityBroadcastReceiver) + super.onCleared() + } + + private fun disposables(): Array { + val downloads = downloadDao.downloads() + val downloadStatuses = downloadStatuses(downloads) + val booksFromDao = books() + val networkLibrary = PublishProcessor.create() + return arrayOf( + updateDownloadItems(downloadStatuses), + removeCompletedDownloadsFromDb(downloadStatuses), + removeNonExistingDownloadsFromDb(downloadStatuses, downloads), + updateBookItems(booksFromDao), + checkFileSystemForBooksOnRequest(booksFromDao), + updateLibraryItems(booksFromDao, downloads, networkLibrary), + updateLanguagesInDao(networkLibrary), + updateNetworkStates(), + updateLanguageItemsForDialog(), + requestsAndConnectivtyChangesToLibraryRequests(networkLibrary) + ) + } + + private fun requestsAndConnectivtyChangesToLibraryRequests(library: PublishProcessor) = + Flowable.combineLatest( + requestDownloadLibrary, + connectivityBroadcastReceiver.networkStates.distinctUntilChanged().filter( + CONNECTED::equals + ), + BiFunction { _, _ -> Unit } + ) + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .subscribe( + { + kiwixService.library + .timeout(10, SECONDS) + .retry(5) + .subscribe( + { library.onNext(it) }, + { + it.printStackTrace() + library.onNext(LibraryNetworkEntity().apply { book = LinkedList() }) + } + ) + }, + Throwable::printStackTrace + ) + + private fun removeNonExistingDownloadsFromDb( + downloadStatuses: Flowable>, + downloads: Flowable> + ) = downloadStatuses + .withLatestFrom( + downloads, + BiFunction(this::combineToDownloadsWithoutStatuses) + ) + .buffer(3, SECONDS) + .map(this::downloadIdsWithNoStatusesOverBufferPeriod) + .subscribe( + { + downloadDao.delete(*it.toLongArray()) + }, + Throwable::printStackTrace + ) + + private fun downloadIdsWithNoStatusesOverBufferPeriod(noStatusIds: List>) = + noStatusIds.flatten() + .fold(mutableMapOf(), { acc, id -> acc.increment(id) }) + .filter { (_, count) -> count == noStatusIds.size } + .map { (id, _) -> id } + + private fun combineToDownloadsWithoutStatuses( + statuses: List, + downloads: List + ): MutableList { + val downloadIdsWithStatuses = statuses.map { it.downloadId } + return downloads.fold( + mutableListOf(), + { acc, downloadModel -> + if (!downloadIdsWithStatuses.contains(downloadModel.downloadId)) { + acc.add(downloadModel.downloadId) + } + acc + } + ) + } + + private fun updateLanguageItemsForDialog() = requestLanguagesDialog + .withLatestFrom(languageDao.languages(), + BiFunction, List> { _, languages -> languages }) + .subscribe( + languageItems::postValue, + Throwable::printStackTrace + ) + + private fun updateNetworkStates() = + connectivityBroadcastReceiver.networkStates.subscribe( + networkStates::postValue, Throwable::printStackTrace + ) + + private fun updateLibraryItems( + booksFromDao: Flowable>, + downloads: Flowable>, + library: Flowable + ) = Flowable.combineLatest( + booksFromDao, + downloads, + languageDao.languages().filter { it.isNotEmpty() }, + library, + requestFiltering + .doOnNext { libraryListIsRefreshing.postValue(true) } + .debounce(500, MILLISECONDS) + .observeOn(Schedulers.io()), + fat32Checker.fileSystemStates, + Function6(this::combineLibrarySources) + ) + .doOnNext { libraryListIsRefreshing.postValue(false) } + .subscribeOn(Schedulers.io()) + .subscribe( + libraryItems::postValue, + Throwable::printStackTrace + ) + + private fun updateLanguagesInDao( + library: Flowable + ) = library + .subscribeOn(Schedulers.io()) + .map { it.books } + .withLatestFrom( + languageDao.languages(), + BiFunction(this::combineToLanguageList) + ) + .map { it.sortedBy(Language::language) } + .subscribe( + languageDao::insert, + Throwable::printStackTrace + ) + + private fun combineToLanguageList( + booksFromNetwork: List, + allLanguages: List + ) = when { + booksFromNetwork.isEmpty() && allLanguages.isEmpty() -> defaultLanguage() + booksFromNetwork.isEmpty() && allLanguages.isNotEmpty() -> emptyList() + booksFromNetwork.isNotEmpty() && allLanguages.isEmpty() -> + fromLocalesWithNetworkMatchesSetActiveBy( + networkLanguageCounts(booksFromNetwork), defaultLanguage() + ) + booksFromNetwork.isNotEmpty() && allLanguages.isNotEmpty() -> + fromLocalesWithNetworkMatchesSetActiveBy( + networkLanguageCounts(booksFromNetwork), allLanguages + ) + else -> throw RuntimeException("Impossible state") + } + + private fun networkLanguageCounts(booksFromNetwork: List) = + booksFromNetwork.mapNotNull { it.language } + .fold( + mutableMapOf(), + { acc, language -> acc.increment(language) } + ) + + private fun MutableMap.increment(key: K) = + apply { set(key, getOrElse(key, { 0 }) + 1) } + + private fun fromLocalesWithNetworkMatchesSetActiveBy( + networkLanguageCounts: MutableMap, + listToActivateBy: List + ) = Locale.getISOLanguages() + .map { Locale(it) } + .filter { networkLanguageCounts.containsKey(it.isO3Language) } + .map { locale -> + Language( + locale.isO3Language, + languageIsActive(listToActivateBy, locale), + networkLanguageCounts.getOrElse(locale.isO3Language, { 0 }) + ) + } + + private fun defaultLanguage() = + listOf( + Language( + context.resources.configuration.locale.isO3Language, + true, + 1 + ) + ) + + private fun languageIsActive( + allLanguages: List, + locale: Locale + ) = allLanguages.firstOrNull { it.languageCode == locale.isO3Language }?.active == true + + private fun combineLibrarySources( + booksOnFileSystem: List, + activeDownloads: List, + allLanguages: List, + libraryNetworkEntity: LibraryNetworkEntity, + filter: String, + fileSystemState: FileSystemState + ): List { + val downloadedBooksIds = booksOnFileSystem.map { it.book.id } + val downloadingBookIds = activeDownloads.map { it.book.id } + val activeLanguageCodes = allLanguages.filter(Language::active) + .map { it.languageCode } + val booksUnfilteredByLanguage = + applyUserFilter( + libraryNetworkEntity.books + .filter { + when (fileSystemState) { + CannotWrite4GbFile -> it.size.toLongOrNull() ?: 0L < Fat32Checker.FOUR_GIGABYTES_IN_KILOBYTES + NotEnoughSpaceFor4GbFile, + CanWrite4GbFile -> true + } + } + .filterNot { downloadedBooksIds.contains(it.id) } + .filterNot { downloadingBookIds.contains(it.id) } + .filterNot { it.url.contains("/stack_exchange/") },// Temp filter see #694, filter) + filter + ) + + return listOf( + *createLibrarySection( + booksUnfilteredByLanguage.filter { activeLanguageCodes.contains(it.language) }, + R.string.your_languages, + Long.MAX_VALUE + ), + *createLibrarySection( + booksUnfilteredByLanguage.filterNot { activeLanguageCodes.contains(it.language) }, + R.string.other_languages, + Long.MIN_VALUE + ) + ) + } + + private fun createLibrarySection( + books: List, + sectionStringId: Int, + sectionId: Long + ) = + if (books.isNotEmpty()) + arrayOf( + DividerItem(sectionId, context.getString(sectionStringId)), + *toBookItems(books) + ) + else emptyArray() + + private fun applyUserFilter( + booksUnfilteredByLanguage: List, + filter: String + ) = if (filter.isEmpty()) { + booksUnfilteredByLanguage + } else { + booksUnfilteredByLanguage.forEach { it.calculateSearchMatches(filter, bookUtils) } + booksUnfilteredByLanguage.filter { it.searchMatches > 0 } + } + + private fun toBookItems(books: List) = + books.map { BookItem(it) }.toTypedArray() + + private fun checkFileSystemForBooksOnRequest(booksFromDao: Flowable>) = + requestFileSystemCheck + .subscribeOn(Schedulers.io()) + .observeOn(Schedulers.io()) + .onBackpressureDrop() + .doOnNext { deviceListIsRefreshing.postValue(true) } + .switchMap( + { + booksFromStorageNotIn(booksFromDao) + }, + 1 + ) + .onBackpressureDrop() + .doOnNext { deviceListIsRefreshing.postValue(false) } + .filter { it.isNotEmpty() } + .map { it.distinctBy { it.book.id } } + .subscribe( + bookDao::insert, + Throwable::printStackTrace + ) + + private fun books() = bookDao.books() + .subscribeOn(Schedulers.io()) + .map { it.sortedBy { book -> book.book.title } } + + private fun booksFromStorageNotIn(booksFromDao: Flowable>) = + storageObserver.booksOnFileSystem + .withLatestFrom( + booksFromDao.map { it.map { bookOnDisk -> bookOnDisk.book.id } }, + BiFunction(this::removeBooksAlreadyInDao) + ) + + private fun removeBooksAlreadyInDao( + booksFromFileSystem: Collection, + idsInDao: List + ) = booksFromFileSystem.filterNot { idsInDao.contains(it.book.id) } + + private fun updateBookItems( + booksFromDao: Flowable> + ) = + booksFromDao + .subscribe( + bookItems::postValue, + Throwable::printStackTrace + ) + + private fun removeCompletedDownloadsFromDb(downloadStatuses: Flowable>) = + downloadStatuses + .observeOn(Schedulers.io()) + .subscribeOn(Schedulers.io()) + .map { it.filter { status -> status.state == Successful } } + .filter { it.isNotEmpty() } + .subscribe( + { + bookDao.insert(it.map { downloadStatus -> downloadStatus.toBookOnDisk() }) + downloadDao.delete( + *it.map { status -> status.downloadId }.toLongArray() + ) + }, + Throwable::printStackTrace + ) + + private fun updateDownloadItems(downloadStatuses: Flowable>) = + downloadStatuses + .map { statuses -> statuses.map { DownloadItem(it) } } + .subscribe( + downloadItems::postValue, + Throwable::printStackTrace + ) + + private fun downloadStatuses(downloads: Flowable>) = + Flowable.combineLatest( + downloads, + Flowable.interval(1, SECONDS), + BiFunction { downloadModels: List, _: Long -> downloadModels } + ) + .subscribeOn(Schedulers.io()) + .map(downloader::queryStatus) + .distinctUntilChanged() + +} + + diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/BooksOnDiskAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/BooksOnDiskAdapter.kt new file mode 100644 index 0000000000..a5799aa73e --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/BooksOnDiskAdapter.kt @@ -0,0 +1,41 @@ +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.downloader.model.BookOnDisk +import org.kiwix.kiwixmobile.extensions.inflate +import org.kiwix.kiwixmobile.utils.BookUtils + +class BooksOnDiskAdapter( + private val bookUtils: BookUtils, + private val onItemClick: (BookOnDisk) -> Unit, + private val onItemLongClick: (BookOnDisk) -> 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 + ) = BooksOnDiskViewHolder(parent.inflate(layout.library_item, false), bookUtils) + + override fun getItemCount() = itemList.size + + override fun onBindViewHolder( + holder: BooksOnDiskViewHolder, + 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/BooksOnDiskViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/BooksOnDiskViewHolder.kt new file mode 100644 index 0000000000..88f028a2a5 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/BooksOnDiskViewHolder.kt @@ -0,0 +1,57 @@ +package org.kiwix.kiwixmobile.zim_manager.fileselect_view + +import android.support.v7.widget.RecyclerView.ViewHolder +import android.view.View +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.downloader.model.BookOnDisk +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.KiloByte + +class BooksOnDiskViewHolder( + override val containerView: View, + private val bookUtils: BookUtils +) : ViewHolder(containerView), + LayoutContainer { + fun bind( + bookOnDisk: BookOnDisk, + clickAction: (BookOnDisk) -> Unit, + longClickAction: (BookOnDisk) -> Unit + ) { + val book = bookOnDisk.book + title.setTextAndVisibility(book.title) + description.setTextAndVisibility(book.description) + creator.setTextAndVisibility(book.creator) + publisher.setTextAndVisibility(book.publisher) + date.setTextAndVisibility(book.date) + size.setTextAndVisibility(KiloByte(book.size).humanReadable) + language.text = bookUtils.getLanguage(book.getLanguage()) + fileName.text = NetworkUtils.parseURL( + KiwixApplication.getInstance(), book.url ?: bookOnDisk.file.path + ) + favicon.setBitmap(Base64String(book.favicon)) + + containerView.setOnClickListener { + clickAction.invoke(bookOnDisk) + } + containerView.setOnLongClickListener { + longClickAction.invoke(bookOnDisk) + return@setOnLongClickListener true + } + } +} + + 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..9fd69e5c80 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt @@ -0,0 +1,45 @@ +package org.kiwix.kiwixmobile.zim_manager.fileselect_view + +import android.content.Context +import android.util.Log +import io.reactivex.processors.PublishProcessor +import io.reactivex.schedulers.Schedulers +import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao +import org.kiwix.kiwixmobile.downloader.model.BookOnDisk +import org.kiwix.kiwixmobile.downloader.model.DownloadModel +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( + private val context: Context, + private val sharedPreferenceUtil: SharedPreferenceUtil, + private val downloadDao: NewDownloadDao +) { + + private val _booksOnFileSystem = PublishProcessor.create>() + val booksOnFileSystem = _booksOnFileSystem.distinctUntilChanged() + .doOnSubscribe { + downloadDao.downloads() + .subscribeOn(Schedulers.io()) + .take(1) + .subscribe(this::scanFiles, Throwable::printStackTrace) + } + + private fun scanFiles(downloads: List) { + FileSearch(context, downloads, object : ResultListener { + val foundBooks = mutableSetOf() + + override fun onBookFound(book: BookOnDisk) { + foundBooks.add(book) + Log.i("Scanner", "File Search: Found Book " + 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 23f4dc5973..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.java +++ /dev/null @@ -1,423 +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 org.kiwix.kiwixmobile.KiwixApplication; -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.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 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; - -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) { - 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 - 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(); - } - } - - 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..b400f90786 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt @@ -0,0 +1,167 @@ +/* + * 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.newdb.dao.NewBookDao +import org.kiwix.kiwixmobile.di.components.ActivityComponent +import org.kiwix.kiwixmobile.downloader.model.BookOnDisk +import org.kiwix.kiwixmobile.extensions.toast +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: NewBookDao + @Inject lateinit var dialogShower: DialogShower + @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject lateinit var bookUtils: BookUtils + + private val zimManageViewModel: ZimManageViewModel by lazy { + ViewModelProviders.of(activity!!, viewModelFactory) + .get(ZimManageViewModel::class.java) + } + + private val booksOnDiskAdapter: BooksOnDiskAdapter by lazy { + BooksOnDiskAdapter( + bookUtils, this::open, this::tryToDelete + ) + } + + override fun inject(activityComponent: ActivityComponent) { + activityComponent.inject(this) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + 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) + zim_swiperefresh.setOnRefreshListener(this::requestFileSystemCheck) + zimfilelist.run { + adapter = booksOnDiskAdapter + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + setHasFixedSize(true) + } + zimManageViewModel.bookItems.observe(this, Observer { + booksOnDiskAdapter.itemList = it!! + checkEmpty(it) + }) + zimManageViewModel.deviceListIsRefreshing.observe(this, Observer { + zim_swiperefresh.isRefreshing = it!! + }) + } + + override fun onResume() { + super.onResume() + checkPermissions() + } + + private fun checkEmpty(books: List) { + file_management_no_files.visibility = + if (books.isEmpty()) View.VISIBLE + else View.GONE + } + + private fun checkPermissions() { + if (ContextCompat.checkSelfPermission( + activity!!, + 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.requestFileSystemCheck.onNext(Unit) + } + + private fun open(it: BookOnDisk) { + ZimContentProvider.canIterate = false + if (!it.file.canRead()) { + context.toast(string.error_filenotfound) + } else { + (activity as ZimManageActivity).finishResult(it.file.path) + } + } + + private fun tryToDelete(it: BookOnDisk) { + dialogShower.show(DeleteZim, { + if (deleteSpecificZimFile(it)) { + context.toast(string.delete_specific_zim_toast) + } else { + context.toast(string.delete_zim_failed) + } + }) + } + + private fun deleteSpecificZimFile(book: BookOnDisk): Boolean { + val file = book.file + FileUtils.deleteZimFile(file) + if (file.exists()) { + return false + } + bookDao.delete(book.databaseId!!) + return true + } +} \ No newline at end of file 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 f6ba528b1e..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.java +++ /dev/null @@ -1,395 +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.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; -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 org.kiwix.kiwixmobile.KiwixApplication; -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.downloader.DownloadService; -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) - Button permissionButton; - - public LinearLayout llLayout; - - @BindView(R.id.library_swiperefresh) - SwipeRefreshLayout swipeRefreshLayout; - - 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; - - 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 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); - 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); - - DownloadService.setDownloadFragment(faActivity.mSectionsPagerAdapter.getDownloadFragment()); - - - 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 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)) { - 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) { - 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(); - } - - 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 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 - 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..fabc6ea9f7 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.kt @@ -0,0 +1,228 @@ +/* + * 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.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 val zimManageViewModel: ZimManageViewModel by lazy { + ViewModelProviders.of(activity!!, viewModelFactory) + .get(ZimManageViewModel::class.java) + } + 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 == false + + override fun inject(activityComponent: ActivityComponent) { + activityComponent.inject(this) + } + + 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() { + StorageSelectDialog().apply { + arguments = Bundle().apply { + putString( + StorageSelectDialog.STORAGE_DIALOG_INTERNAL, + this@LibraryFragment.getString(string.internal_storage) + ) + putString( + StorageSelectDialog.STORAGE_DIALOG_EXTERNAL, + this@LibraryFragment.getString(string.external_storage) + ) + putInt(StorageSelectDialog.STORAGE_DIALOG_THEME, StyleUtils.dialogStyle()) + } + setOnSelectListener(this@LibraryFragment::storeDeviceInPreferences) + } + .show(fragmentManager, getString(string.pref_storage)) + } +} \ No newline at end of file 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 3db78e7da8..0000000000 --- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryPresenter.java +++ /dev/null @@ -1,73 +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 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. - */ - -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); - } - } - } - - @Override - public void attachView(LibraryViewCallback view) { - super.attachView(view); - } - -} 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/KiloByte.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/KiloByte.kt new file mode 100644 index 0000000000..bd157b3f0f --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/KiloByte.kt @@ -0,0 +1,16 @@ +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import java.text.DecimalFormat + +inline class KiloByte(val kilobyteString: String?) { + val humanReadable + get() = kilobyteString?.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/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..b48a28ec53 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/Language.kt @@ -0,0 +1,25 @@ +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import java.util.Locale + +class Language constructor( + locale: Locale, + var active: Boolean, + var occurencesOfLanguage: Int, + 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, + occurrencesOfLanguage: Int + ) : this(Locale(languageCode), active, occurrencesOfLanguage) { + } + + override fun equals(other: Any?): Boolean { + return (other as Language).language == language && other.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..892cdbcc1f --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryAdapter.kt @@ -0,0 +1,60 @@ +/* + * 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) + 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]) + +} \ 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..2ce5e632d1 --- /dev/null +++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/LibraryViewHolder.kt @@ -0,0 +1,62 @@ +package org.kiwix.kiwixmobile.zim_manager.library_view.adapter + +import android.support.v7.widget.RecyclerView +import android.view.View +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(KiloByte(item.book.size).humanReadable) + language.text = bookUtils.getLanguage(item.book.getLanguage()) + fileName.text = NetworkUtils.parseURL( + KiwixApplication.getInstance(), item.book.url + ) + 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/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" + > - - -