Added UI to show the progress on fetching online content.

* Refactored the viewModel code to show the downloading updates, e.g. content is fetching, downloading the online content, etc.
This commit is contained in:
MohitMaliFtechiz 2024-09-16 18:48:34 +05:30
parent 3ab0dcd8b9
commit 222e86840a
8 changed files with 237 additions and 20 deletions

View File

@ -195,6 +195,13 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions {
showInternetAccessViaMobileNetworkDialog() showInternetAccessViaMobileNetworkDialog()
} }
} }
zimManageViewModel.downloadProgress.observe(viewLifecycleOwner) { progress ->
fragmentDestinationDownloadBinding?.onlineLibraryProgressBar?.progress = progress
}
zimManageViewModel.downloadStatus.observe(viewLifecycleOwner) { status ->
fragmentDestinationDownloadBinding?.libraryErrorText?.text = status
}
setupMenu() setupMenu()
// hides keyboard when scrolled // hides keyboard when scrolled
@ -268,6 +275,7 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions {
libraryErrorText.visibility = View.GONE libraryErrorText.visibility = View.GONE
libraryList.visibility = View.VISIBLE libraryList.visibility = View.VISIBLE
} }
showProgressBarOfFetchingOnlineLibrary()
} }
private fun hideRecyclerviewAndShowSwipeDownForLibraryErrorText() { private fun hideRecyclerviewAndShowSwipeDownForLibraryErrorText() {
@ -277,6 +285,17 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions {
) )
libraryErrorText.visibility = View.VISIBLE libraryErrorText.visibility = View.VISIBLE
libraryList.visibility = View.GONE libraryList.visibility = View.GONE
onlineLibraryProgressBar.visibility = View.GONE
}
}
private fun showProgressBarOfFetchingOnlineLibrary() {
fragmentDestinationDownloadBinding?.apply {
onlineLibraryProgressBar.visibility = View.VISIBLE
libraryErrorText.apply {
visibility = View.VISIBLE
setText(string.reaching_remote_library)
}
} }
} }
@ -345,6 +364,7 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions {
if (it != null) { if (it != null) {
libraryAdapter.items = it libraryAdapter.items = it
} }
fragmentDestinationDownloadBinding?.onlineLibraryProgressBar?.visibility = View.GONE
if (it?.isEmpty() == true) { if (it?.isEmpty() == true) {
fragmentDestinationDownloadBinding?.libraryErrorText?.setText( fragmentDestinationDownloadBinding?.libraryErrorText?.setText(
if (isNotConnected) string.no_network_connection if (isNotConnected) string.no_network_connection

View File

@ -0,0 +1,31 @@
/*
* Kiwix Android
* Copyright (c) 2024 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.zimManager
import org.kiwix.kiwixmobile.core.data.remote.OnlineLibraryProgressListener
class AppProgressListenerProvider(
private val zimManageViewModel: ZimManageViewModel
) : OnlineLibraryProgressListener {
@Suppress("MagicNumber")
override fun onProgress(bytesRead: Long, contentLength: Long, done: Boolean) {
val progress = if (contentLength == -1L) 0 else (bytesRead * 100 / contentLength).toInt()
zimManageViewModel.downloadProgress.postValue(progress)
}
}

View File

@ -33,6 +33,11 @@ import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.processors.BehaviorProcessor import io.reactivex.processors.BehaviorProcessor
import io.reactivex.processors.PublishProcessor import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC
import okhttp3.logging.HttpLoggingInterceptor.Level.NONE
import org.kiwix.kiwixmobile.BuildConfig.DEBUG
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.StorageObserver import org.kiwix.kiwixmobile.core.StorageObserver
import org.kiwix.kiwixmobile.core.base.SideEffect import org.kiwix.kiwixmobile.core.base.SideEffect
@ -42,6 +47,13 @@ import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.data.DataSource import org.kiwix.kiwixmobile.core.data.DataSource
import org.kiwix.kiwixmobile.core.data.remote.KiwixService import org.kiwix.kiwixmobile.core.data.remote.KiwixService
import org.kiwix.kiwixmobile.core.data.remote.ProgressResponseBody
import org.kiwix.kiwixmobile.core.data.remote.UserAgentInterceptor
import org.kiwix.kiwixmobile.core.di.modules.CALL_TIMEOUT
import org.kiwix.kiwixmobile.core.di.modules.CONNECTION_TIMEOUT
import org.kiwix.kiwixmobile.core.di.modules.KIWIX_DOWNLOAD_URL
import org.kiwix.kiwixmobile.core.di.modules.READ_TIMEOUT
import org.kiwix.kiwixmobile.core.di.modules.USER_AGENT
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity.Book
@ -81,6 +93,7 @@ import java.io.IOException
import java.util.LinkedList import java.util.LinkedList
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.SECONDS
import javax.inject.Inject import javax.inject.Inject
const val DEFAULT_PROGRESS = 0 const val DEFAULT_PROGRESS = 0
@ -92,7 +105,7 @@ class ZimManageViewModel @Inject constructor(
private val bookDao: NewBookDao, private val bookDao: NewBookDao,
private val languageDao: NewLanguagesDao, private val languageDao: NewLanguagesDao,
private val storageObserver: StorageObserver, private val storageObserver: StorageObserver,
private val kiwixService: KiwixService, private var kiwixService: KiwixService,
private val context: Application, private val context: Application,
private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver, private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver,
private val bookUtils: BookUtils, private val bookUtils: BookUtils,
@ -127,12 +140,44 @@ class ZimManageViewModel @Inject constructor(
val requestFiltering = BehaviorProcessor.createDefault("") val requestFiltering = BehaviorProcessor.createDefault("")
private var compositeDisposable: CompositeDisposable? = CompositeDisposable() private var compositeDisposable: CompositeDisposable? = CompositeDisposable()
val downloadProgress = MutableLiveData<Int>()
val downloadStatus = MutableLiveData<String>()
init { init {
// add listener to retrofit to get updates of downloading online library
kiwixService = createKiwixServiceWithProgressListener()
compositeDisposable?.addAll(*disposables()) compositeDisposable?.addAll(*disposables())
context.registerReceiver(connectivityBroadcastReceiver) context.registerReceiver(connectivityBroadcastReceiver)
} }
private fun createKiwixServiceWithProgressListener(): KiwixService {
val customOkHttpClient = OkHttpClient().newBuilder()
.followRedirects(true)
.followSslRedirects(true)
.connectTimeout(CONNECTION_TIMEOUT, SECONDS)
.readTimeout(READ_TIMEOUT, SECONDS)
.callTimeout(CALL_TIMEOUT, SECONDS)
.addNetworkInterceptor(
HttpLoggingInterceptor().apply {
level = if (DEBUG) BASIC else NONE
}
)
.addNetworkInterceptor(UserAgentInterceptor(USER_AGENT))
.addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
.body(
ProgressResponseBody(
originalResponse.body!!,
AppProgressListenerProvider(this)
)
)
.build()
}
.build()
return KiwixService.ServiceCreator.newHackListService(customOkHttpClient, KIWIX_DOWNLOAD_URL)
}
@VisibleForTesting @VisibleForTesting
fun onClearedExposed() { fun onClearedExposed() {
onCleared() onCleared()
@ -232,7 +277,7 @@ class ZimManageViewModel @Inject constructor(
} }
private fun requestsAndConnectivtyChangesToLibraryRequests( private fun requestsAndConnectivtyChangesToLibraryRequests(
library: PublishProcessor<LibraryNetworkEntity> library: PublishProcessor<LibraryNetworkEntity>,
) = ) =
Flowable.combineLatest( Flowable.combineLatest(
requestDownloadLibrary, requestDownloadLibrary,
@ -254,21 +299,37 @@ class ZimManageViewModel @Inject constructor(
.map { } .map { }
} }
} }
.doOnEach {
downloadStatus.postValue("Reaching remote library")
}
.switchMap {
Flowable.fromCallable {
downloadStatus.postValue("Starting download of the online library")
}
}
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.io()) .observeOn(Schedulers.io())
.subscribe( .flatMap {
{ kiwixService.library
compositeDisposable?.add( .toFlowable()
kiwixService.library .retry(5)
.retry(5) .doOnSubscribe {
.subscribe(library::onNext) { downloadStatus.postValue("Downloading library 0%")
it.printStackTrace() }
library.onNext(LibraryNetworkEntity().apply { book = LinkedList() }) .map { response ->
} downloadStatus.postValue("Downloading library... parsing response")
) response
}, }
Throwable::printStackTrace .doFinally {
) downloadStatus.postValue("Remote library downloaded, parsing data")
}
.onErrorReturn {
it.printStackTrace()
downloadStatus.postValue("Failed to download the library")
LibraryNetworkEntity().apply { book = LinkedList() }
}
}
.subscribe(library::onNext, Throwable::printStackTrace)
private fun updateNetworkStates() = private fun updateNetworkStates() =
connectivityBroadcastReceiver.networkStates.subscribe( connectivityBroadcastReceiver.networkStates.subscribe(

View File

@ -66,4 +66,13 @@
app:layout_constraintVertical_bias="0.45" app:layout_constraintVertical_bias="0.45"
tools:ignore="RequiredSize" /> tools:ignore="RequiredSize" />
<ProgressBar
android:id="@+id/onlineLibraryProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/libraryErrorText" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,23 @@
/*
* Kiwix Android
* Copyright (c) 2024 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.data.remote
interface OnlineLibraryProgressListener {
fun onProgress(bytesRead: Long, contentLength: Long, done: Boolean)
}

View File

@ -0,0 +1,72 @@
/*
* Kiwix Android
* Copyright (c) 2024 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.data.remote
import okhttp3.MediaType
import okhttp3.ResponseBody
import okio.Buffer
import okio.BufferedSource
import okio.ForwardingSource
import okio.Source
import okio.buffer
import org.kiwix.kiwixmobile.core.utils.files.Log
class ProgressResponseBody(
private val responseBody: ResponseBody,
private val progressListener: OnlineLibraryProgressListener
) : ResponseBody() {
private var bufferedSource: BufferedSource? = null
override fun contentType(): MediaType? = responseBody.contentType()
override fun contentLength(): Long = responseBody.contentLength()
@Suppress("UnsafeCallOnNullableType")
override fun source(): BufferedSource {
if (bufferedSource == null) {
bufferedSource = source(responseBody.source()).buffer()
}
return bufferedSource!!
}
private fun source(source: Source): Source {
return object : ForwardingSource(source) {
var totalBytesRead = 0L
override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead = super.read(sink, byteCount)
totalBytesRead += if (bytesRead != -1L) bytesRead else 0
val isDone = bytesRead == -1L
progressListener.onProgress(
totalBytesRead,
responseBody.contentLength(),
isDone
).also {
Log.e(
"PROGRESS",
"onProgress: ${contentLength()} and byteRead = $totalBytesRead\n" +
" sink ${bytesRead == -1L} \n byteRead = $bytesRead " +
"\n bufferedSource = ${bufferedSource?.isOpen}"
)
}
return bytesRead
}
}
}
}

View File

@ -30,11 +30,11 @@ import org.kiwix.kiwixmobile.core.data.remote.UserAgentInterceptor
import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.TimeUnit.SECONDS
import javax.inject.Singleton import javax.inject.Singleton
private const val CONNECTION_TIMEOUT = 10L const val CONNECTION_TIMEOUT = 10L
private const val READ_TIMEOUT = 60L const val READ_TIMEOUT = 60L
private const val CALL_TIMEOUT = 60L const val CALL_TIMEOUT = 60L
private const val USER_AGENT = "kiwix-android-version:${BuildConfig.VERSION_CODE}" const val USER_AGENT = "kiwix-android-version:${BuildConfig.VERSION_CODE}"
private const val KIWIX_DOWNLOAD_URL = "https://mirror.download.kiwix.org/" const val KIWIX_DOWNLOAD_URL = "https://mirror.download.kiwix.org/"
@Module @Module
class NetworkModule { class NetworkModule {

View File

@ -348,6 +348,7 @@
<string name="system_unable_to_grant_permission_message">System unable to grant permission!</string> <string name="system_unable_to_grant_permission_message">System unable to grant permission!</string>
<string name="allow">Allow</string> <string name="allow">Allow</string>
<string name="swipe_down_for_library">Swipe Down for Library</string> <string name="swipe_down_for_library">Swipe Down for Library</string>
<string name="reaching_remote_library">Reaching remote library</string>
<string-array name="pref_dark_modes_entries"> <string-array name="pref_dark_modes_entries">
<item>@string/on</item> <item>@string/on</item>
<item>@string/off</item> <item>@string/off</item>