64 Custom apps should validate their expansion files and re-download if corrupt/missing - add Custom Download flow

This commit is contained in:
Sean Mac Gillicuddy 2019-11-05 11:30:13 +00:00
parent fed540a390
commit c05742d02a
43 changed files with 844 additions and 158 deletions

View File

@ -19,40 +19,17 @@
package org.kiwix.kiwixmobile; package org.kiwix.kiwixmobile;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import java.util.Map; import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider; import javax.inject.Provider;
import org.kiwix.kiwixmobile.core.ViewModelFactory;
import org.kiwix.kiwixmobile.di.KiwixScope; import org.kiwix.kiwixmobile.di.KiwixScope;
@KiwixScope @KiwixScope
public class KiwixViewModelFactory implements ViewModelProvider.Factory { public class KiwixViewModelFactory extends ViewModelFactory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject @Inject
public KiwixViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) { public KiwixViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators; super(creators);
}
@SuppressWarnings("unchecked")
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) {
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> 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);
}
} }
} }

View File

@ -22,6 +22,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.processors.PublishProcessor import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter
@ -31,7 +32,6 @@ import org.kiwix.kiwixmobile.language.viewmodel.Action.UpdateLanguages
import org.kiwix.kiwixmobile.language.viewmodel.State.Content import org.kiwix.kiwixmobile.language.viewmodel.State.Content
import org.kiwix.kiwixmobile.language.viewmodel.State.Loading import org.kiwix.kiwixmobile.language.viewmodel.State.Loading
import org.kiwix.kiwixmobile.language.viewmodel.State.Saving import org.kiwix.kiwixmobile.language.viewmodel.State.Saving
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.SideEffect
import javax.inject.Inject import javax.inject.Inject
class LanguageViewModel @Inject constructor( class LanguageViewModel @Inject constructor(

View File

@ -22,7 +22,7 @@ import io.reactivex.Flowable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.zim_manager.Language import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.SideEffect import org.kiwix.kiwixmobile.core.base.SideEffect
data class SaveLanguagesAndFinish( data class SaveLanguagesAndFinish(
val languages: List<Language>, val languages: List<Language>,

View File

@ -66,7 +66,7 @@ import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.DeleteFiles
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.None import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.None
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.OpenFile import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.OpenFile
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.ShareFiles import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.ShareFiles
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.SideEffect import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.StartMultiSelection import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.StartMultiSelection
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem 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.BookItem

View File

@ -17,7 +17,6 @@
*/ */
package org.kiwix.kiwixmobile.zim_manager.download_view package org.kiwix.kiwixmobile.zim_manager.download_view
import android.content.Context
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
@ -29,12 +28,6 @@ import kotlinx.android.synthetic.main.download_item.favicon
import kotlinx.android.synthetic.main.download_item.stop import kotlinx.android.synthetic.main.download_item.stop
import kotlinx.android.synthetic.main.download_item.title import kotlinx.android.synthetic.main.download_item.title
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState.Failed
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState.Paused
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState.Pending
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState.Running
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState.Successful
import org.kiwix.kiwixmobile.core.extensions.setBitmap import org.kiwix.kiwixmobile.core.extensions.setBitmap
class DownloadViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), class DownloadViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView),
@ -50,21 +43,7 @@ class DownloadViewHolder(override val containerView: View) : RecyclerView.ViewHo
stop.setOnClickListener { stop.setOnClickListener {
itemClickListener.invoke(downloadItem) itemClickListener.invoke(downloadItem)
} }
downloadState.text = toReadableState(downloadItem.downloadState, containerView.context) downloadState.text = downloadItem.downloadState.toReadableState(containerView.context)
eta.text = downloadItem.eta.takeIf { it.seconds > 0L }?.toHumanReadableTime() ?: "" eta.text = downloadItem.readableEta
}
private fun toReadableState(
downloadState: DownloadState,
context: Context
) = when (downloadState) {
is Failed -> context.getString(
downloadState.stringId,
downloadState.reason.name
)
Pending,
Running,
Paused,
Successful -> context.getString(downloadState.stringId)
} }
} }

View File

@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
import android.app.Activity import android.app.Activity
import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer

View File

@ -19,6 +19,7 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
import android.app.Activity import android.app.Activity
import org.kiwix.kiwixmobile.core.base.SideEffect
object None : SideEffect<Unit> { object None : SideEffect<Unit> {
override fun invokeWith(activity: Activity) { override fun invokeWith(activity: Activity) {

View File

@ -20,12 +20,14 @@ package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
import android.app.Activity import android.app.Activity
import androidx.core.net.toUri import androidx.core.net.toUri
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.extensions.start import org.kiwix.kiwixmobile.core.extensions.start
import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.main.KiwixMainActivity import org.kiwix.kiwixmobile.main.KiwixMainActivity
class OpenFile(private val bookOnDisk: BookOnDisk) : SideEffect<Unit> { class OpenFile(private val bookOnDisk: BookOnDisk) :
SideEffect<Unit> {
override fun invokeWith(activity: Activity) { override fun invokeWith(activity: Activity) {
val file = bookOnDisk.file val file = bookOnDisk.file

View File

@ -24,9 +24,11 @@ import android.net.Uri
import android.os.Build import android.os.Build
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
class ShareFiles(private val selectedBooks: List<BookOnDisk>) : SideEffect<Unit> { class ShareFiles(private val selectedBooks: List<BookOnDisk>) :
SideEffect<Unit> {
override fun invokeWith(activity: Activity) { override fun invokeWith(activity: Activity) {
val selectedFileShareIntent = Intent() val selectedFileShareIntent = Intent()
selectedFileShareIntent.action = Intent.ACTION_SEND_MULTIPLE selectedFileShareIntent.action = Intent.ACTION_SEND_MULTIPLE

View File

@ -22,6 +22,7 @@ import android.app.Activity
import android.view.ActionMode import android.view.ActionMode
import io.reactivex.processors.PublishProcessor import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.extensions.startActionMode import org.kiwix.kiwixmobile.core.extensions.startActionMode
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions

View File

@ -143,6 +143,7 @@ class AllProjectConfigurer {
implementation(Libs.kotlin_stdlib_jdk7) implementation(Libs.kotlin_stdlib_jdk7)
implementation(Libs.appcompat) implementation(Libs.appcompat)
implementation(Libs.material) implementation(Libs.material)
implementation(Libs.constraintlayout)
implementation(Libs.androidx_multidex_multidex) implementation(Libs.androidx_multidex_multidex)
implementation(Libs.okhttp) implementation(Libs.okhttp)
implementation(Libs.logging_interceptor) implementation(Libs.logging_interceptor)

View File

@ -46,8 +46,6 @@ dependencies {
// Android Support // Android Support
implementation(Libs.cardview) implementation(Libs.cardview)
implementation(Libs.constraintlayout)
// Tab indicator // Tab indicator
implementation(Libs.ink_page_indicator) implementation(Libs.ink_page_indicator)

View File

@ -21,10 +21,10 @@
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@mipmap/kiwix_icon" android:icon="@mipmap/kiwix_icon"
android:label="@string/app_name" android:label="@string/app_name"
android:usesCleartextTraffic="true"
android:roundIcon="@mipmap/kiwix_icon_round" android:roundIcon="@mipmap/kiwix_icon_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:targetApi="m"> tools:targetApi="m">
<activity <activity
@ -35,6 +35,7 @@
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY" /> <category android:name="android.intent.category.MONKEY" />
</intent-filter> </intent-filter>

View File

@ -0,0 +1,55 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import java.util.Map;
import javax.inject.Provider;
public class ViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
public ViewModelFactory(
Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
@SuppressWarnings("unchecked")
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) {
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> 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);
}
}
}

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects package org.kiwix.kiwixmobile.core.base
import android.app.Activity import android.app.Activity

View File

@ -17,6 +17,7 @@
*/ */
package org.kiwix.kiwixmobile.core.downloader.model package org.kiwix.kiwixmobile.core.downloader.model
import android.content.Context
import com.tonyodev.fetch2.Error import com.tonyodev.fetch2.Error
import com.tonyodev.fetch2.Status import com.tonyodev.fetch2.Status
import com.tonyodev.fetch2.Status.ADDED import com.tonyodev.fetch2.Status.ADDED
@ -43,6 +44,8 @@ data class DownloadItem(
val downloadState: DownloadState val downloadState: DownloadState
) { ) {
val readableEta: CharSequence = eta.takeIf { it.seconds > 0L }?.toHumanReadableTime() ?: ""
constructor(downloadModel: DownloadModel) : this( constructor(downloadModel: DownloadModel) : this(
downloadModel.downloadId, downloadModel.downloadId,
Base64String(downloadModel.book.favicon), Base64String(downloadModel.book.favicon),
@ -84,4 +87,12 @@ sealed class DownloadState(val stringId: Int) {
data class Failed(val reason: Error) : DownloadState(R.string.failed_state) data class Failed(val reason: Error) : DownloadState(R.string.failed_state)
override fun toString(): String = javaClass.simpleName override fun toString(): String = javaClass.simpleName
fun toReadableState(context: Context): CharSequence = when (this) {
is Failed -> context.getString(stringId, reason.name)
Pending,
Running,
Paused,
Successful -> context.getString(stringId)
}
} }

View File

@ -0,0 +1,27 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.extensions
import android.widget.ViewAnimator
fun ViewAnimator.setDistinctDisplayedChild(index: Int) {
if (displayedChild != index) {
displayedChild = index
}
}

View File

@ -1091,8 +1091,7 @@ public abstract class CoreMainActivity extends BaseActivity implements WebViewCa
@NonNull String[] permissions, @NonNull int[] grantResults) { @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) { switch (requestCode) {
case REQUEST_STORAGE_PERMISSION: { case REQUEST_STORAGE_PERMISSION: {
if (grantResults.length > 0 if (hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
&& grantResults[0] == PERMISSION_GRANTED) {
finish(); finish();
Intent newZimFile = Intents.internal(CoreMainActivity.class); Intent newZimFile = Intents.internal(CoreMainActivity.class);
newZimFile.setData(Uri.fromFile(file)); newZimFile.setData(Uri.fromFile(file));
@ -1222,7 +1221,6 @@ public abstract class CoreMainActivity extends BaseActivity implements WebViewCa
}, 300); }, 300);
} }
@OnClick(R2.id.bottom_toolbar_bookmark) @OnClick(R2.id.bottom_toolbar_bookmark)
public void toggleBookmark() { public void toggleBookmark() {
//Check maybe need refresh //Check maybe need refresh
@ -1605,7 +1603,7 @@ public abstract class CoreMainActivity extends BaseActivity implements WebViewCa
return true; return true;
} }
protected boolean urlIsInvalid(){ protected boolean urlIsInvalid() {
return getCurrentWebView().getUrl() == null; return getCurrentWebView().getUrl() == null;
} }
@ -1827,14 +1825,10 @@ public abstract class CoreMainActivity extends BaseActivity implements WebViewCa
} }
private void searchFiles() { private void searchFiles() {
if (Build.VERSION.SDK_INT >= VERSION_CODES.M && ContextCompat.checkSelfPermission(this, if (hasPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
REQUEST_READ_STORAGE_PERMISSION);
} else {
scanStorageForZims(); scanStorageForZims();
} else {
requestExternalStoragePermission();
} }
} }

View File

@ -53,6 +53,7 @@
<item name="android:textColorSecondary">@color/textDarkSecondary</item> <item name="android:textColorSecondary">@color/textDarkSecondary</item>
<item name="android:textColorTertiary">@color/textDarkTertiary</item> <item name="android:textColorTertiary">@color/textDarkTertiary</item>
<item name="colorControlNormal">@color/accent</item> <item name="colorControlNormal">@color/accent</item>
<item name="buttonStyle">@style/KiwixButtonStyle</item>
</style> </style>
<style name="AppTheme.Night" parent="AppTheme.Base"> <style name="AppTheme.Night" parent="AppTheme.Base">
@ -162,4 +163,8 @@
<item name="layout_constraintBottom_toBottomOf">parent</item> <item name="layout_constraintBottom_toBottomOf">parent</item>
</style> </style>
<style name="KiwixButtonStyle" parent="Widget.MaterialComponents.Button">
<item name="android:textAppearance">@style/TextAppearance.AppCompat.Medium</item>
</style>
</resources> </resources>

View File

@ -109,6 +109,7 @@ android {
buildConfigField "int", "CONTENT_VERSION_CODE", "$parsedJson.version_code" buildConfigField "int", "CONTENT_VERSION_CODE", "$parsedJson.version_code"
} }
buildConfigField "String", "ENFORCED_LANG", "\"$parsedJson.enforced_lang\"" buildConfigField "String", "ENFORCED_LANG", "\"$parsedJson.enforced_lang\""
buildConfigField "String", "ZIM_URL", "\"$parsedJson.zim_url\""
resValue "string", "app_name", "$parsedJson.app_name" resValue "string", "app_name", "$parsedJson.app_name"
resValue "string", "app_search_string", "Search " + "$parsedJson.app_name" resValue "string", "app_search_string", "Search " + "$parsedJson.app_name"
} }

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission
android:name="android.permission.INTERNET"
tools:node="remove" />
</manifest>

View File

@ -3,7 +3,8 @@
"package": "org.kiwix.kiwixcustomexample", "package": "org.kiwix.kiwixcustomexample",
"version_name": "2017-07", "version_name": "2017-07",
"version_code": "1", "version_code": "1",
"zim_file": "wikipedia_fr_test_2017-07.zim", "zim_file": "kiwix.granbluefantasy_en_all_all.zim",
"zim_url": "http://download.kiwix.org/zim/other/granbluefantasy_en_all_nopic_2018-10.zim.meta4",
"embed_zim": false, "embed_zim": false,
"ic_launcher": "icon.png", "ic_launcher": "icon.png",
"enforced_lang": "en" "enforced_lang": "en"

View File

@ -39,5 +39,6 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".download.CustomDownloadActivity" />
</application> </application>
</manifest> </manifest>

View File

@ -0,0 +1,35 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.custom;
import androidx.lifecycle.ViewModel;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Provider;
import org.kiwix.kiwixmobile.core.ViewModelFactory;
import org.kiwix.kiwixmobile.custom.di.CustomScope;
@CustomScope
public class CustomViewModelFactory extends ViewModelFactory {
@Inject
public CustomViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
super(creators);
}
}

View File

@ -22,6 +22,7 @@ import android.app.Activity
import dagger.BindsInstance import dagger.BindsInstance
import dagger.Subcomponent import dagger.Subcomponent
import org.kiwix.kiwixmobile.core.di.ActivityScope import org.kiwix.kiwixmobile.core.di.ActivityScope
import org.kiwix.kiwixmobile.custom.download.CustomDownloadActivity
import org.kiwix.kiwixmobile.custom.main.CustomMainActivity import org.kiwix.kiwixmobile.custom.main.CustomMainActivity
import org.kiwix.kiwixmobile.custom.settings.CustomSettingsActivity import org.kiwix.kiwixmobile.custom.settings.CustomSettingsActivity
@ -30,6 +31,7 @@ import org.kiwix.kiwixmobile.custom.settings.CustomSettingsActivity
interface CustomActivityComponent { interface CustomActivityComponent {
fun inject(customMainActivity: CustomMainActivity) fun inject(customMainActivity: CustomMainActivity)
fun inject(customSettingsActivity: CustomSettingsActivity) fun inject(customSettingsActivity: CustomSettingsActivity)
fun inject(customDownloadActivity: CustomDownloadActivity)
@Subcomponent.Builder @Subcomponent.Builder
interface Builder { interface Builder {

View File

@ -21,7 +21,7 @@ package org.kiwix.kiwixmobile.custom.di
import dagger.Component import dagger.Component
import org.kiwix.kiwixmobile.core.di.components.CoreComponent import org.kiwix.kiwixmobile.core.di.components.CoreComponent
@Component(dependencies = [CoreComponent::class]) @Component(dependencies = [CoreComponent::class], modules = [CustomViewModelModule::class])
@CustomScope @CustomScope
interface CustomComponent { interface CustomComponent {
fun activityComponentBuilder(): CustomActivityComponent.Builder fun activityComponentBuilder(): CustomActivityComponent.Builder

View File

@ -0,0 +1,40 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.custom.di
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
import org.kiwix.kiwixmobile.custom.CustomViewModelFactory
import org.kiwix.kiwixmobile.custom.download.CustomDownloadViewModel
import org.kiwix.kiwixmobile.di.ViewModelKey
@Module
abstract class CustomViewModelModule {
@Binds
@IntoMap
@ViewModelKey(CustomDownloadViewModel::class)
abstract fun bindCustomDownloadViewModel(zimManageViewModel: CustomDownloadViewModel): ViewModel
@Binds
abstract fun bindViewModelFactory(factory: CustomViewModelFactory):
ViewModelProvider.Factory
}

View File

@ -0,0 +1,27 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.custom.download
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
sealed class Action {
data class DatabaseEmission(val downloads: List<DownloadItem>) : Action()
object ClickedDownload : Action()
object ClickedRetry : Action()
}

View File

@ -0,0 +1,99 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.custom.download
import android.os.Bundle
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.activity_custom_download.cd_view_animator
import kotlinx.android.synthetic.main.layout_custom_download_error.cd_error_text
import kotlinx.android.synthetic.main.layout_custom_download_error.cd_retry_button
import kotlinx.android.synthetic.main.layout_custom_download_in_progress.cd_download_state
import kotlinx.android.synthetic.main.layout_custom_download_in_progress.cd_eta
import kotlinx.android.synthetic.main.layout_custom_download_in_progress.cd_progress
import kotlinx.android.synthetic.main.layout_custom_download_required.cd_download_button
import org.kiwix.kiwixmobile.core.base.BaseActivity
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.extensions.setDistinctDisplayedChild
import org.kiwix.kiwixmobile.core.extensions.viewModel
import org.kiwix.kiwixmobile.custom.R
import org.kiwix.kiwixmobile.custom.customActivityComponent
import org.kiwix.kiwixmobile.custom.download.Action.ClickedDownload
import org.kiwix.kiwixmobile.custom.download.Action.ClickedRetry
import org.kiwix.kiwixmobile.custom.download.State.DownloadComplete
import org.kiwix.kiwixmobile.custom.download.State.DownloadFailed
import org.kiwix.kiwixmobile.custom.download.State.DownloadInProgress
import org.kiwix.kiwixmobile.custom.download.State.DownloadRequired
import javax.inject.Inject
class CustomDownloadActivity : BaseActivity() {
private val downloadViewModel by lazy {
viewModel<CustomDownloadViewModel>(viewModelFactory)
}
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
private val compositeDisposable = CompositeDisposable()
override fun injection() {
customActivityComponent.inject(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_custom_download)
downloadViewModel.state.observe(this, Observer(::render))
compositeDisposable.add(
downloadViewModel.effects.subscribe(
{ it.invokeWith(this) },
Throwable::printStackTrace
)
)
cd_download_button.setOnClickListener { downloadViewModel.actions.offer(ClickedDownload) }
cd_retry_button.setOnClickListener { downloadViewModel.actions.offer(ClickedRetry) }
}
override fun onDestroy() {
super.onDestroy()
compositeDisposable.clear()
}
private fun render(state: State) {
return when (state) {
DownloadRequired -> cd_view_animator.setDistinctDisplayedChild(0)
is DownloadInProgress -> {
cd_view_animator.setDistinctDisplayedChild(1)
render(state.downloads[0])
}
is DownloadFailed -> {
cd_view_animator.setDistinctDisplayedChild(2)
cd_error_text.text = state.downloadState.toReadableState(this)
}
DownloadComplete -> cd_view_animator.setDistinctDisplayedChild(3)
}
}
private fun render(downloadItem: DownloadItem) {
cd_progress.progress = downloadItem.progress
cd_eta.text = downloadItem.readableEta
cd_download_state.text = downloadItem.downloadState.toReadableState(this)
}
}

View File

@ -0,0 +1,96 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.custom.download
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.dao.FetchDownloadDao
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState.Failed
import org.kiwix.kiwixmobile.custom.download.Action.ClickedDownload
import org.kiwix.kiwixmobile.custom.download.Action.ClickedRetry
import org.kiwix.kiwixmobile.custom.download.Action.DatabaseEmission
import org.kiwix.kiwixmobile.custom.download.State.DownloadComplete
import org.kiwix.kiwixmobile.custom.download.State.DownloadFailed
import org.kiwix.kiwixmobile.custom.download.State.DownloadInProgress
import org.kiwix.kiwixmobile.custom.download.State.DownloadRequired
import org.kiwix.kiwixmobile.custom.download.effects.DownloadCustom
import org.kiwix.kiwixmobile.custom.download.effects.FinishAndStartMain
import org.kiwix.kiwixmobile.custom.download.effects.SetPreferredStorageWithMostSpace
import javax.inject.Inject
class CustomDownloadViewModel @Inject constructor(
downloadDao: FetchDownloadDao,
setPreferredStorageWithMostSpace: SetPreferredStorageWithMostSpace,
private val downloadCustom: DownloadCustom
) : ViewModel() {
val state = MutableLiveData<State>().apply { value = DownloadRequired }
val actions = PublishProcessor.create<Action>()
private val _effects = PublishProcessor.create<SideEffect<*>>()
val effects = _effects.startWith(setPreferredStorageWithMostSpace)
private val compositeDisposable = CompositeDisposable()
init {
compositeDisposable.addAll(
reducer(),
downloadsAsActions(downloadDao)
)
}
private fun reducer() = actions.map { reduce(it, state.value!!) }
.distinctUntilChanged()
.subscribe(state::postValue, Throwable::printStackTrace)
private fun downloadsAsActions(downloadDao: FetchDownloadDao) =
downloadDao.downloads()
.map { it.map(::DownloadItem) }
.subscribe(
{ actions.offer(DatabaseEmission(it)) },
Throwable::printStackTrace
)
private fun reduce(action: Action, state: State): State {
return when (action) {
is DatabaseEmission -> reduceDatabaseEmission(state, action)
ClickedRetry,
ClickedDownload -> state.also { _effects.offer(downloadCustom) }
}
}
private fun reduceDatabaseEmission(state: State, action: DatabaseEmission) = when (state) {
is DownloadFailed,
DownloadRequired ->
if (action.downloads.isNotEmpty()) DownloadInProgress(action.downloads)
else state
is DownloadInProgress ->
if (action.downloads.isNotEmpty())
if (action.downloads[0].downloadState is Failed)
DownloadFailed(action.downloads[0].downloadState)
else
DownloadInProgress(action.downloads)
else
DownloadComplete.also { _effects.offer(FinishAndStartMain()) }
DownloadComplete -> state
}
}

View File

@ -0,0 +1,29 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.custom.download
import org.kiwix.kiwixmobile.core.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState
sealed class State {
object DownloadRequired : State()
data class DownloadInProgress(val downloads: List<DownloadItem>) : State()
data class DownloadFailed(val downloadState: DownloadState) : State()
object DownloadComplete : State()
}

View File

@ -0,0 +1,63 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.custom.download.effects
import android.app.Activity
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.downloader.Downloader
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.custom.BuildConfig
import javax.inject.Inject
data class DownloadCustom @Inject constructor(val downloader: Downloader) : SideEffect<Unit> {
override fun invokeWith(activity: Activity) {
downloader.download(emptyBook(id = "custom", url = BuildConfig.ZIM_URL))
}
private fun emptyBook(
id: String = "",
title: String = "",
description: String = "",
language: String = "",
creator: String = "",
publisher: String = "",
date: String = "",
url: String = "",
articleCount: String = "",
mediaCount: String = "",
size: String = "",
name: String = "",
favIcon: String = ""
) =
Book().apply {
this.id = id
this.title = title
this.description = description
this.language = language
this.creator = creator
this.publisher = publisher
this.date = date
this.url = url
this.articleCount = articleCount
this.mediaCount = mediaCount
this.size = size
bookName = name
favicon = favIcon
}
}

View File

@ -0,0 +1,31 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.custom.download.effects
import android.app.Activity
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.extensions.start
import org.kiwix.kiwixmobile.custom.main.CustomMainActivity
class FinishAndStartMain() : SideEffect<Unit> {
override fun invokeWith(activity: Activity) {
activity.finish()
activity.start<CustomMainActivity>()
}
}

View File

@ -0,0 +1,38 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.custom.download.effects
import android.app.Activity
import androidx.core.content.ContextCompat
import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.settings.StorageCalculator
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import javax.inject.Inject
class SetPreferredStorageWithMostSpace @Inject constructor(
private val storageCalculator: StorageCalculator,
private val sharedPreferenceUtil: SharedPreferenceUtil
) : SideEffect<Unit> {
override fun invokeWith(activity: Activity) {
ContextCompat.getExternalFilesDirs(activity, null)
.filterNotNull()
.maxBy(storageCalculator::availableBytes)
?.let { sharedPreferenceUtil.putPrefStorage(it.path) }
}
}

View File

@ -0,0 +1,65 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.custom.main
import android.content.Context
import androidx.core.content.ContextCompat
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasBothFiles
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasFile
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasNothing
import java.io.File
import javax.inject.Inject
class CustomFileValidator @Inject constructor(private val context: Context) {
fun validate(onFilesFound: (ValidationState) -> Unit, onNoFilesFound: () -> Unit) =
when (val installationState = detectInstallationState()) {
is HasBothFiles,
is HasFile -> onFilesFound(installationState)
HasNothing -> onNoFilesFound()
}
private fun detectInstallationState(
obbFiles: List<File> = obbFiles(),
zimFiles: List<File> = zimFiles()
): ValidationState {
return when {
obbFiles.isNotEmpty() && zimFiles().isNotEmpty() -> HasBothFiles(obbFiles[0], zimFiles[0])
obbFiles.isNotEmpty() -> HasFile(obbFiles[0])
zimFiles.isNotEmpty() -> HasFile(zimFiles[0])
else -> HasNothing
}
}
private fun obbFiles() = scanDirs(ContextCompat.getObbDirs(context), "obb")
private fun zimFiles() =
scanDirs(ContextCompat.getExternalFilesDirs(context, null), "zim")
private fun scanDirs(dirs: Array<out File>, extensionToMatch: String): List<File> =
dirs.fold(listOf()) { acc, dir ->
acc + dir.walk().filter { it.extension.startsWith(extensionToMatch) }.toList()
}
}
sealed class ValidationState {
data class HasBothFiles(val obbFile: File, val zimFile: File) : ValidationState()
data class HasFile(val file: File) : ValidationState()
object HasNothing : ValidationState()
}

View File

@ -22,23 +22,24 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.Menu import android.view.Menu
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.extensions.start
import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.main.WebViewCallback import org.kiwix.kiwixmobile.core.main.WebViewCallback
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.utils.Constants.TAG_KIWIX
import org.kiwix.kiwixmobile.core.utils.LanguageUtils import org.kiwix.kiwixmobile.core.utils.LanguageUtils
import org.kiwix.kiwixmobile.core.utils.StyleUtils.dialogStyle
import org.kiwix.kiwixmobile.core.utils.files.FileUtils
import org.kiwix.kiwixmobile.custom.BuildConfig import org.kiwix.kiwixmobile.custom.BuildConfig
import org.kiwix.kiwixmobile.custom.customActivityComponent import org.kiwix.kiwixmobile.custom.customActivityComponent
import org.kiwix.kiwixmobile.custom.download.CustomDownloadActivity
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasBothFiles
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasFile
import java.io.File import java.io.File
import java.util.Locale import java.util.Locale
import javax.inject.Inject
class CustomMainActivity : CoreMainActivity() { class CustomMainActivity : CoreMainActivity() {
@Inject lateinit var customFileValidator: CustomFileValidator
override fun showHomePage() { override fun showHomePage() {
Log.e("CustomMain", "tried to show home page") Log.e("CustomMain", "tried to show home page")
@ -54,17 +55,22 @@ class CustomMainActivity : CoreMainActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(TAG_KIWIX, "This is a custom app:$packageName") requireEnforcedLanguage()
if (loadCustomAppContent()) { customFileValidator.validate(
Log.d(TAG_KIWIX, "Found custom content, continuing...") {
// Continue when (it) {
} else { is HasFile -> openZimFile(it.file)
Log.e(TAG_KIWIX, "Problem finding the content, no more OnCreate() code") is HasBothFiles -> {
// What should we do here? exit? I'll investigate empirically. it.zimFile.delete()
// It seems unpredictable behaviour if the code returns at this point as yesterday openZimFile(it.obbFile)
// it didn't crash yet today the app crashes because it tries to load books }
// in onResume(); }
} },
{
finish()
start<CustomDownloadActivity>()
}
)
} }
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
@ -79,74 +85,15 @@ class CustomMainActivity : CoreMainActivity() {
zimReaderContainer: ZimReaderContainer zimReaderContainer: ZimReaderContainer
) = CustomWebViewClient(webViewCallback, zimReaderContainer) ) = CustomWebViewClient(webViewCallback, zimReaderContainer)
/** private fun requireEnforcedLanguage(): Boolean {
* loadCustomAppContent Return true if all's well, else false.
*/
private fun loadCustomAppContent(): Boolean {
Log.d(
TAG_KIWIX,
"Kiwix Custom App starting for the first time. Checking Companion ZIM: " +
BuildConfig.ZIM_FILE_NAME
)
val currentLocaleCode = Locale.getDefault().toString() val currentLocaleCode = Locale.getDefault().toString()
// Custom App recommends to start off a specific language if (BuildConfig.ENFORCED_LANG.isNotEmpty() && BuildConfig.ENFORCED_LANG != currentLocaleCode) {
if (BuildConfig.ENFORCED_LANG.length > 0 && BuildConfig.ENFORCED_LANG != currentLocaleCode) {
// change the locale machinery
LanguageUtils.handleLocaleChange(this, BuildConfig.ENFORCED_LANG) LanguageUtils.handleLocaleChange(this, BuildConfig.ENFORCED_LANG)
// save new locale into preferences for next startup
sharedPreferenceUtil.putPrefLanguage(BuildConfig.ENFORCED_LANG) sharedPreferenceUtil.putPrefLanguage(BuildConfig.ENFORCED_LANG)
startActivity(Intent(this, this.javaClass))
// restart activity for new locale to take effect
this.setResult(1236)
this.finish()
this.startActivity(Intent(this, this.javaClass))
return false
}
var filePath = ""
if (BuildConfig.HAS_EMBEDDED_ZIM) {
val appPath = packageResourcePath
val libDir = File(appPath.substring(0, appPath.lastIndexOf("/")) + "/lib/")
if (libDir.exists() && libDir.listFiles().size > 0) {
filePath = libDir.listFiles()[0].path + "/" + BuildConfig.ZIM_FILE_NAME
}
if (filePath.isEmpty() || !File(filePath).exists()) {
filePath = String.format(
"/data/data/%s/lib/%s", packageName,
BuildConfig.ZIM_FILE_NAME
)
}
} else {
filePath = generateExpansionFilePath()
}
Log.d(TAG_KIWIX, "BuildConfig.ZIM_FILE_SIZE = " + BuildConfig.ZIM_FILE_SIZE)
if (!FileUtils.doesFileExist(filePath, BuildConfig.ZIM_FILE_SIZE, false)) {
val zimFileMissingBuilder = AlertDialog.Builder(this, dialogStyle())
zimFileMissingBuilder.setTitle(R.string.app_name)
zimFileMissingBuilder.setMessage(R.string.custom_app_missing_content)
zimFileMissingBuilder.setIcon(R.mipmap.kiwix_icon)
val activity = this
zimFileMissingBuilder.setPositiveButton(
getString(R.string.go_to_play_store)
) { dialog, which ->
startActivity(Intent(Intent.ACTION_VIEW).apply {
data = "market://details?id=$packageName".toUri()
})
activity.finish()
}
zimFileMissingBuilder.setCancelable(false)
val zimFileMissingDialog = zimFileMissingBuilder.create()
zimFileMissingDialog.show()
return false
} else {
openZimFile(File(filePath))
return true return true
} }
return false
} }
override fun manageZimFiles(tab: Int) { override fun manageZimFiles(tab: Int) {

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true">
<ImageView
android:id="@+id/cd_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:src="@drawable/kiwix_icon_with_title"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ViewAnimator
android:id="@+id/cd_view_animator"
android:layout_width="0dp"
android:layout_height="0dp"
android:inAnimation="@anim/fade_in"
android:outAnimation="@anim/fade_out"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cd_icon">
<include layout="@layout/layout_custom_download_required" />
<include layout="@layout/layout_custom_download_in_progress" />
<include layout="@layout/layout_custom_download_error" />
<include layout="@layout/layout_custom_download_complete" />
</ViewAnimator>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="@layout/activity_custom_download">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/complete"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".3" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="@layout/activity_custom_download">
<TextView
android:id="@+id/cd_error_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".3" />
<Button
android:id="@+id/cd_retry_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/retry"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cd_error_text" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/cd_download_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/cd_progress"
app:layout_constraintLeft_toLeftOf="@+id/cd_progress"
app:layout_constraintRight_toRightOf="@+id/cd_progress" />
<ProgressBar
android:id="@+id/cd_progress"
style="?android:progressBarStyleHorizontal"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:max="100"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".3" />
<TextView
android:id="@+id/cd_eta"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
app:layout_constraintBottom_toBottomOf="@id/cd_progress"
app:layout_constraintStart_toEndOf="@id/cd_progress"
app:layout_constraintTop_toTopOf="@id/cd_progress" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:showIn="@layout/activity_custom_download">
<TextView
android:id="@+id/cd_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/invalid_installation"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".3" />
<Button
android:id="@+id/cd_download_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/download"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cd_text" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="retry">Retry</string>
<string name="download">Download</string>
<string name="invalid_installation">Invalid Install. Please Download Zim.\n Ensure WiFi is on and you have enough storage</string>
<string name="complete">Complete</string>
</resources>