Fixed: Downloading was not working.

* Fixed: `Online Books` were showing incorrect book sizes. The OPDS stream now provides sizes in bytes instead of kilobytes, so the code has been updated accordingly.
* Added the `io.coil-kt.coil3:coil-compose` library to load favicons for online books, as OPDS now returns favicon URLs instead of Base64-encoded strings.
* Since favicons are no longer provided in Base64 format when downloading ZIM files, we now extract the favicon from the Archive using libkiwix after the download completes. This allows us to display it locally on various screens such as the library, Wi-Fi hotspot, notes, history, and more.
* Cached the `LibkiwixBook` instance to avoid recreating it multiple times when adding or removing bookmarks.
This commit is contained in:
MohitMaliFtechiz 2025-06-05 23:49:02 +05:30
parent 7a10528d19
commit ebbe1b9889
20 changed files with 129 additions and 69 deletions

View File

@ -44,7 +44,6 @@ import androidx.compose.ui.semantics.testTag
import com.tonyodev.fetch2.Status import com.tonyodev.fetch2.Status
import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.R.string import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.downloader.model.toPainter
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
import org.kiwix.kiwixmobile.core.ui.components.ProgressBarStyle import org.kiwix.kiwixmobile.core.ui.components.ProgressBarStyle
import org.kiwix.kiwixmobile.core.ui.models.IconItem import org.kiwix.kiwixmobile.core.ui.models.IconItem
@ -104,7 +103,7 @@ private fun DownloadBookContent(
.fillMaxWidth(), .fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
BookIcon(item.favIcon.toPainter()) BookIcon(item.favIconUrl, isOnlineLibrary = true)
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)

View File

@ -48,8 +48,6 @@ import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import org.kiwix.kiwixmobile.R import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
import org.kiwix.kiwixmobile.core.downloader.model.toPainter
import org.kiwix.kiwixmobile.core.extensions.toast import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
import org.kiwix.kiwixmobile.core.ui.theme.PureGrey import org.kiwix.kiwixmobile.core.ui.theme.PureGrey
@ -58,7 +56,7 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FIVE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONLINE_BOOK_DISABLED_COLOR_ALPHA import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONLINE_BOOK_DISABLED_COLOR_ALPHA
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP
import org.kiwix.kiwixmobile.core.zim_manager.KiloByte import org.kiwix.kiwixmobile.core.zim_manager.Byte
import org.kiwix.kiwixmobile.ui.BookDate import org.kiwix.kiwixmobile.ui.BookDate
import org.kiwix.kiwixmobile.ui.BookDescription import org.kiwix.kiwixmobile.ui.BookDescription
import org.kiwix.kiwixmobile.ui.BookIcon import org.kiwix.kiwixmobile.ui.BookIcon
@ -167,7 +165,7 @@ private fun OnlineBookContent(item: BookItem, bookUtils: BookUtils) {
.fillMaxWidth(), .fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
BookIcon(Base64String(item.book.favicon).toPainter()) BookIcon(item.book.favicon, isOnlineLibrary = true)
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
@ -207,7 +205,7 @@ private fun BookSizeAndDateRow(item: BookItem) {
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
BookSize( BookSize(
KiloByte(item.book.size).humanReadable, Byte(item.book.size).humanReadable,
modifier = Modifier.weight(1f).testTag(ONLINE_BOOK_SIZE_TEXT_TESTING_TAG) modifier = Modifier.weight(1f).testTag(ONLINE_BOOK_SIZE_TEXT_TESTING_TAG)
) )
BookDate(item.book.date) BookDate(item.book.date)

View File

@ -19,6 +19,7 @@
package org.kiwix.kiwixmobile.ui package org.kiwix.kiwixmobile.ui
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -31,18 +32,17 @@ import androidx.compose.foundation.layout.width
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import coil3.compose.AsyncImage
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.downloader.model.Base64String import org.kiwix.kiwixmobile.core.downloader.model.Base64String
import org.kiwix.kiwixmobile.core.downloader.model.toPainter import org.kiwix.kiwixmobile.core.downloader.model.toPainter
@ -53,7 +53,7 @@ import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FIVE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOUR_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP import org.kiwix.kiwixmobile.core.utils.ComposeDimens.TWO_DP
import org.kiwix.kiwixmobile.core.zim_manager.KiloByte import org.kiwix.kiwixmobile.core.zim_manager.Byte
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.ArticleCount import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.ArticleCount
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode
@ -116,7 +116,7 @@ private fun BookContent(
if (selectionMode == SelectionMode.MULTI) { if (selectionMode == SelectionMode.MULTI) {
BookCheckbox(bookOnDisk, selectionMode, onMultiSelect, onClick, index) BookCheckbox(bookOnDisk, selectionMode, onMultiSelect, onClick, index)
} }
BookIcon(Base64String(bookOnDisk.book.favicon).toPainter()) BookIcon(bookOnDisk.book.favicon, isOnlineLibrary = false)
BookDetails(Modifier.weight(1f), bookOnDisk) BookDetails(Modifier.weight(1f), bookOnDisk)
} }
} }
@ -142,14 +142,23 @@ private fun BookCheckbox(
} }
@Composable @Composable
fun BookIcon(painter: Painter) { fun BookIcon(iconSource: String, isOnlineLibrary: Boolean) {
Icon( val modifier = Modifier.size(BOOK_ICON_SIZE)
painter = painter, if (isOnlineLibrary) {
contentDescription = stringResource(R.string.fav_icon), AsyncImage(
modifier = Modifier model = iconSource,
.size(BOOK_ICON_SIZE), contentDescription = stringResource(R.string.fav_icon),
tint = Color.Unspecified modifier = modifier,
) placeholder = painterResource(R.drawable.default_zim_file_icon),
error = painterResource(R.drawable.default_zim_file_icon),
)
} else {
Image(
painter = Base64String(iconSource).toPainter(),
contentDescription = stringResource(R.string.fav_icon),
modifier = modifier
)
}
} }
@Composable @Composable
@ -164,7 +173,7 @@ private fun BookDetails(modifier: Modifier, bookOnDisk: BookOnDisk) {
) { ) {
BookDate(bookOnDisk.book.date) BookDate(bookOnDisk.book.date)
Spacer(modifier = Modifier.width(EIGHT_DP)) Spacer(modifier = Modifier.width(EIGHT_DP))
BookSize(KiloByte(bookOnDisk.book.size).humanReadable) BookSize(Byte(bookOnDisk.book.size).humanReadable)
Spacer(modifier = Modifier.width(EIGHT_DP)) Spacer(modifier = Modifier.width(EIGHT_DP))
BookArticleCount( BookArticleCount(
ArticleCount(bookOnDisk.book.articleCount.orEmpty()) ArticleCount(bookOnDisk.book.articleCount.orEmpty())

View File

@ -446,13 +446,19 @@ class ZimManageViewModel @Inject constructor(
): Flow<List<LibkiwixBook>> = flow { ): Flow<List<LibkiwixBook>> = flow {
downloadProgress.postValue(context.getString(R.string.starting_downloading_remote_library)) downloadProgress.postValue(context.getString(R.string.starting_downloading_remote_library))
val response = kiwixService.getLibrary() val response = kiwixService.getLibrary()
val resolvedUrl = response.raw().networkResponse?.request?.url
?: response.raw().request.url
val baseHostUrl = "${resolvedUrl.scheme}://${resolvedUrl.host}"
downloadProgress.postValue(context.getString(R.string.parsing_remote_library)) downloadProgress.postValue(context.getString(R.string.parsing_remote_library))
val isLibraryParsed = onlineLibraryManager.parseOPDSStream(response, KIWIX_OPDS_LIBRARY_URL) val libraryXml = response.body()
if (isLibraryParsed) { val isLibraryParsed = onlineLibraryManager.parseOPDSStream(libraryXml, baseHostUrl)
emit(onlineLibraryManager.getOnlineBooks()) emit(
} else { if (isLibraryParsed) {
emit(emptyList()) onlineLibraryManager.getOnlineBooks()
} } else {
emptyList()
}
)
} }
.retry(5) .retry(5)
.catch { e -> .catch { e ->

View File

@ -19,7 +19,6 @@
package org.kiwix.kiwixmobile.zimManager.libraryView package org.kiwix.kiwixmobile.zimManager.libraryView
import eu.mhutti1.utils.storage.Bytes import eu.mhutti1.utils.storage.Bytes
import eu.mhutti1.utils.storage.KB
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
@ -42,7 +41,7 @@ class AvailableSpaceCalculator @Inject constructor(
.map { downloads -> downloads.sumOf(DownloadModel::bytesRemaining) } .map { downloads -> downloads.sumOf(DownloadModel::bytesRemaining) }
.map { bytesToBeDownloaded -> storageCalculator.availableBytes() - bytesToBeDownloaded } .map { bytesToBeDownloaded -> storageCalculator.availableBytes() - bytesToBeDownloaded }
.first() .first()
if (bookItem.book.size.toLong() * KB < trueAvailableBytes) { if (bookItem.book.size.toLong() < trueAvailableBytes) {
successAction.invoke(bookItem) successAction.invoke(bookItem)
} else { } else {
failureAction.invoke(Bytes(trueAvailableBytes).humanReadable) failureAction.invoke(Bytes(trueAvailableBytes).humanReadable)
@ -50,5 +49,5 @@ class AvailableSpaceCalculator @Inject constructor(
} }
suspend fun hasAvailableSpaceForBook(book: LibkiwixBook) = suspend fun hasAvailableSpaceForBook(book: LibkiwixBook) =
book.size.toLong() * KB < storageCalculator.availableBytes() book.size.toLong() < storageCalculator.availableBytes()
} }

View File

@ -20,7 +20,6 @@ package org.kiwix.kiwixmobile.zimManager.libraryView.adapter
import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.tonyodev.fetch2.Status import com.tonyodev.fetch2.Status
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.downloader.model.DownloadState import org.kiwix.kiwixmobile.core.downloader.model.DownloadState
import org.kiwix.kiwixmobile.core.downloader.model.Seconds import org.kiwix.kiwixmobile.core.downloader.model.Seconds
@ -61,7 +60,7 @@ sealed class LibraryListItem {
data class LibraryDownloadItem( data class LibraryDownloadItem(
val downloadId: Long, val downloadId: Long,
val favIcon: Base64String, val favIconUrl: String,
val title: String, val title: String,
val description: String?, val description: String?,
val bytesDownloaded: Long, val bytesDownloaded: Long,
@ -76,7 +75,7 @@ sealed class LibraryListItem {
constructor(downloadModel: DownloadModel) : this( constructor(downloadModel: DownloadModel) : this(
downloadModel.downloadId, downloadModel.downloadId,
Base64String(downloadModel.book.favicon), downloadModel.book.favicon,
downloadModel.book.title, downloadModel.book.title,
downloadModel.book.description, downloadModel.book.description,
downloadModel.bytesDownloaded, downloadModel.bytesDownloaded,

View File

@ -369,4 +369,7 @@ object Libs {
const val COMPOSE_LIVE_DATA = const val COMPOSE_LIVE_DATA =
"androidx.compose.runtime:runtime-livedata:${Versions.COMPOSE_VERSION}" "androidx.compose.runtime:runtime-livedata:${Versions.COMPOSE_VERSION}"
const val COIL3_COMPOSE = "io.coil-kt.coil3:coil-compose:${Versions.COIL_COMPOSE}"
const val COIL3_OKHTTP_COMPOSE = "io.coil-kt.coil3:coil-network-okhttp:${Versions.COIL_COMPOSE}"
} }

View File

@ -115,6 +115,8 @@ object Versions {
const val COMPOSE_MATERIAL3 = "1.3.1" const val COMPOSE_MATERIAL3 = "1.3.1"
const val TURBINE_FLOW_TEST = "1.2.0" const val TURBINE_FLOW_TEST = "1.2.0"
const val COIL_COMPOSE = "3.2.0"
} }
/** /**

View File

@ -243,6 +243,8 @@ class AllProjectConfigurer {
implementation(Libs.ANDROIDX_ACTIVITY_COMPOSE) implementation(Libs.ANDROIDX_ACTIVITY_COMPOSE)
implementation(Libs.COMPOSE_TOOLING_PREVIEW) implementation(Libs.COMPOSE_TOOLING_PREVIEW)
implementation(Libs.COMPOSE_LIVE_DATA) implementation(Libs.COMPOSE_LIVE_DATA)
implementation(Libs.COIL3_COMPOSE)
implementation(Libs.COIL3_OKHTTP_COMPOSE)
// Compose UI test implementation // Compose UI test implementation
androidTestImplementation(Libs.COMPOSE_UI_TEST_JUNIT) androidTestImplementation(Libs.COMPOSE_UI_TEST_JUNIT)

View File

@ -21,7 +21,7 @@
<ID>MagicNumber:DownloaderModule.kt$DownloaderModule$5</ID> <ID>MagicNumber:DownloaderModule.kt$DownloaderModule$5</ID>
<ID>MagicNumber:FileUtils.kt$FileUtils$3</ID> <ID>MagicNumber:FileUtils.kt$FileUtils$3</ID>
<ID>MagicNumber:JNIInitialiser.kt$JNIInitialiser$1024</ID> <ID>MagicNumber:JNIInitialiser.kt$JNIInitialiser$1024</ID>
<ID>MagicNumber:KiloByte.kt$KiloByte$1024.0</ID> <ID>MagicNumber:Byte.kt$Byte$1024.0</ID>
<ID>MagicNumber:MainMenu.kt$MainMenu$99</ID> <ID>MagicNumber:MainMenu.kt$MainMenu$99</ID>
<ID>MagicNumber:OnSwipeTouchListener.kt$OnSwipeTouchListener.GestureListener$100</ID> <ID>MagicNumber:OnSwipeTouchListener.kt$OnSwipeTouchListener.GestureListener$100</ID>
<ID>MagicNumber:SearchResultGenerator.kt$ZimSearchResultGenerator$200</ID> <ID>MagicNumber:SearchResultGenerator.kt$ZimSearchResultGenerator$200</ID>
@ -42,7 +42,7 @@
<ID>NestedBlockDepth:StorageDeviceUtils.kt$StorageDeviceUtils$// Amazingly file.canWrite() does not always return the correct value private fun canWrite(file: File): Boolean</ID> <ID>NestedBlockDepth:StorageDeviceUtils.kt$StorageDeviceUtils$// Amazingly file.canWrite() does not always return the correct value private fun canWrite(file: File): Boolean</ID>
<ID>PackageNaming:ArticleCount.kt$package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view</ID> <ID>PackageNaming:ArticleCount.kt$package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view</ID>
<ID>PackageNaming:BooksOnDiskListItem.kt$package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view</ID> <ID>PackageNaming:BooksOnDiskListItem.kt$package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view</ID>
<ID>PackageNaming:KiloByte.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID> <ID>PackageNaming:Byte.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
<ID>PackageNaming:KiwixTag.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID> <ID>PackageNaming:KiwixTag.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
<ID>PackageNaming:Language.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID> <ID>PackageNaming:Language.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>
<ID>PackageNaming:MountPointProducer.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID> <ID>PackageNaming:MountPointProducer.kt$package org.kiwix.kiwixmobile.core.zim_manager</ID>

View File

@ -18,6 +18,7 @@
package org.kiwix.kiwixmobile.core.dao package org.kiwix.kiwixmobile.core.dao
import android.util.Base64
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
import androidx.room.Insert import androidx.room.Insert
@ -25,16 +26,20 @@ import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import com.tonyodev.fetch2.Download import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.Status.COMPLETED import com.tonyodev.fetch2.Status.COMPLETED
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity
import org.kiwix.kiwixmobile.core.downloader.DownloadRequester import org.kiwix.kiwixmobile.core.downloader.DownloadRequester
import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel import org.kiwix.kiwixmobile.core.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest import org.kiwix.kiwixmobile.core.downloader.model.DownloadRequest
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.reader.ILLUSTRATION_SIZE
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.BooksOnDiskListItem
import org.kiwix.libzim.Archive
import javax.inject.Inject import javax.inject.Inject
@Dao @Dao
@ -53,15 +58,37 @@ abstract class DownloadRoomDao {
fun allDownloads() = getAllDownloads().map { it.map(::DownloadModel) } fun allDownloads() = getAllDownloads().map { it.map(::DownloadModel) }
private fun moveCompletedToBooksOnDiskDao(downloadEntities: List<DownloadRoomEntity>) { @Suppress("InjectDispatcher")
private suspend fun moveCompletedToBooksOnDiskDao(downloadEntities: List<DownloadRoomEntity>) {
downloadEntities.filter { it.status == COMPLETED } downloadEntities.filter { it.status == COMPLETED }
.takeIf(List<DownloadRoomEntity>::isNotEmpty) .takeIf(List<DownloadRoomEntity>::isNotEmpty)
?.let { ?.let { completedDownloads ->
deleteDownloadsList(it) deleteDownloadsList(completedDownloads)
newBookDao.insert(it.map(BooksOnDiskListItem::BookOnDisk)) // We now use the OPDS stream instead of the custom library.xml handling.
// In the OPDS stream, the favicon is a URL instead of a Base64 string.
// So when a download is completed, we extract the illustration directly from the archive.
val booksOnDisk = completedDownloads.map { download ->
val archive = withContext(Dispatchers.IO) {
Archive(download.file)
}
val favicon = getOnlineBookFaviconForOfflineUsages(archive).orEmpty()
val updatedEntity = download.copy(favIcon = favicon)
BooksOnDiskListItem.BookOnDisk(updatedEntity)
}
newBookDao.insert(booksOnDisk)
} }
} }
private fun getOnlineBookFaviconForOfflineUsages(archive: Archive): String? =
if (archive.hasIllustration(ILLUSTRATION_SIZE)) {
Base64.encodeToString(
archive.getIllustrationItem(ILLUSTRATION_SIZE).data.data,
Base64.DEFAULT
)
} else {
null
}
fun update(download: Download) { fun update(download: Download) {
getEntityForDownloadId(download.id.toLong())?.let { downloadRoomEntity -> getEntityForDownloadId(download.id.toLong())?.let { downloadRoomEntity ->
downloadRoomEntity.updateWith(download) downloadRoomEntity.updateWith(download)

View File

@ -285,6 +285,7 @@ class LibkiwixBookmarks @Inject constructor(
// Check if the book has an illustration of the specified size and encode it to Base64. // Check if the book has an illustration of the specified size and encode it to Base64.
val favicon = book?.getFavicon() val favicon = book?.getFavicon()
Log.e(TAG, "getBookmarksList: $favicon")
val zimReaderSource = book?.path?.let { ZimReaderSource(File(it)) } val zimReaderSource = book?.path?.let { ZimReaderSource(File(it)) }
// Return the LibkiwixBookmarkItem, filtering out null results. // Return the LibkiwixBookmarkItem, filtering out null results.

View File

@ -21,14 +21,16 @@ package org.kiwix.kiwixmobile.core.data.remote
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.kiwix.kiwixmobile.core.entity.MetaLinkNetworkEntity import org.kiwix.kiwixmobile.core.entity.MetaLinkNetworkEntity
import retrofit2.Response
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory import retrofit2.converter.scalars.ScalarsConverterFactory
import retrofit2.converter.simplexml.SimpleXmlConverterFactory
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Url import retrofit2.http.Url
interface KiwixService { interface KiwixService {
@GET(OPDS_LIBRARY_NETWORK_PATH) @GET(OPDS_LIBRARY_NETWORK_PATH)
suspend fun getLibrary(): String? suspend fun getLibrary(): Response<String>
@GET @GET
suspend fun getMetaLinks( suspend fun getMetaLinks(
@ -43,7 +45,7 @@ interface KiwixService {
.baseUrl(baseUrl) .baseUrl(baseUrl)
.client(okHttpClient) .client(okHttpClient)
.addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(ScalarsConverterFactory.create())
// .addConverterFactory(SimpleXmlConverterFactory.create()) .addConverterFactory(SimpleXmlConverterFactory.create())
.build() .build()
return retrofit.create(KiwixService::class.java) return retrofit.create(KiwixService::class.java)
} }
@ -52,6 +54,6 @@ interface KiwixService {
companion object { companion object {
// To fetch the full OPDS catalog. // To fetch the full OPDS catalog.
// TODO we will change this to pagination later once we migrate to OPDS properly. // TODO we will change this to pagination later once we migrate to OPDS properly.
const val OPDS_LIBRARY_NETWORK_PATH = "entries?count=-1" const val OPDS_LIBRARY_NETWORK_PATH = "v2/entries?count=-1"
} }
} }

View File

@ -38,7 +38,7 @@ const val CONNECTION_TIMEOUT = 10L
const val READ_TIMEOUT = 300L const val READ_TIMEOUT = 300L
const val CALL_TIMEOUT = 300L const val CALL_TIMEOUT = 300L
const val USER_AGENT = "kiwix-android-version:${BuildConfig.VERSION_CODE}" const val USER_AGENT = "kiwix-android-version:${BuildConfig.VERSION_CODE}"
const val KIWIX_OPDS_LIBRARY_URL = "https://opds.library.kiwix.org/v2/" const val KIWIX_OPDS_LIBRARY_URL = "https://opds.library.kiwix.org/"
@Module @Module
class NetworkModule { class NetworkModule {

View File

@ -38,7 +38,7 @@ value class Base64String(private val encodedString: String?) {
BitmapFactory.decodeByteArray(it, 0, it.size) BitmapFactory.decodeByteArray(it, 0, it.size)
} }
} }
} catch (illegalArgumentException: IllegalArgumentException) { } catch (_: IllegalArgumentException) {
null null
} }
} }

View File

@ -34,7 +34,7 @@ import org.kiwix.kiwixmobile.core.R
data class DownloadItem( data class DownloadItem(
val downloadId: Long, val downloadId: Long,
val favIcon: Base64String, val favIconUrl: String,
val title: String, val title: String,
val description: String?, val description: String?,
val bytesDownloaded: Long, val bytesDownloaded: Long,
@ -47,7 +47,7 @@ data class DownloadItem(
constructor(downloadModel: DownloadModel) : this( constructor(downloadModel: DownloadModel) : this(
downloadModel.downloadId, downloadModel.downloadId,
Base64String(downloadModel.book.favicon), downloadModel.book.favicon,
downloadModel.book.title, downloadModel.book.title,
downloadModel.book.description, downloadModel.book.description,
downloadModel.bytesDownloaded, downloadModel.bytesDownloaded,

View File

@ -19,6 +19,7 @@
package org.kiwix.kiwixmobile.core.extensions package org.kiwix.kiwixmobile.core.extensions
import android.util.Base64 import android.util.Base64
import android.util.Log
import org.kiwix.kiwixmobile.core.CoreApp import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.entity.LibkiwixBook import org.kiwix.kiwixmobile.core.entity.LibkiwixBook
import org.kiwix.kiwixmobile.core.reader.ILLUSTRATION_SIZE import org.kiwix.kiwixmobile.core.reader.ILLUSTRATION_SIZE
@ -57,6 +58,20 @@ fun LibkiwixBook.buildSearchableText(bookUtils: BookUtils): String =
} }
}.toString() }.toString()
fun Book.getFavicon(): String? = getIllustration(ILLUSTRATION_SIZE)?.data?.let { fun Book?.getFavicon(): String? =
Base64.encodeToString(it, Base64.DEFAULT) runCatching {
} val illustration = this?.getIllustration(ILLUSTRATION_SIZE)
illustration?.url()?.ifBlank {
illustration.data?.let {
Base64.encodeToString(it, Base64.DEFAULT)
}
}
}.getOrElse {
it.printStackTrace().also {
this?.illustrations?.forEach { illustration ->
Log.e("BOOK", "getFavicon: ${illustration.data} and ${illustration.url()}")
}
Log.e("BOOK", "getFavicon: ${this?.title}")
}
""
}

View File

@ -24,20 +24,6 @@ import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.widget.ImageViewCompat import androidx.core.widget.ImageViewCompat
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.downloader.model.Base64String
fun ImageView.setBitmap(base64String: Base64String) {
base64String.toBitmap()
?.let(::setImageBitmap)
?: kotlin.run { setImageDrawableCompat(R.drawable.default_zim_file_icon) }
}
// methods that accept inline classes as parameters are not allowed to be called from java
// hence this facade
fun ImageView.setBitmapFromString(string: String?) {
setBitmap(Base64String(string))
}
fun ImageView.setImageDrawableCompat( fun ImageView.setImageDrawableCompat(
@DrawableRes id: Int @DrawableRes id: Int

View File

@ -340,6 +340,7 @@ abstract class CoreReaderFragment :
private val navigationHistoryList: MutableList<NavigationHistoryListItem> = ArrayList() private val navigationHistoryList: MutableList<NavigationHistoryListItem> = ArrayList()
private var isReadSelection = false private var isReadSelection = false
private var isReadAloudServiceRunning = false private var isReadAloudServiceRunning = false
private var libkiwixBook: Book? = null
private var readerLifeCycleScope: CoroutineScope? = null private var readerLifeCycleScope: CoroutineScope? = null
val coreReaderLifeCycleScope: CoroutineScope? val coreReaderLifeCycleScope: CoroutineScope?
@ -2038,9 +2039,7 @@ abstract class CoreReaderFragment :
lifecycleScope.launch { lifecycleScope.launch {
getCurrentWebView()?.url?.let { articleUrl -> getCurrentWebView()?.url?.let { articleUrl ->
zimReaderContainer?.zimFileReader?.let { zimFileReader -> zimReaderContainer?.zimFileReader?.let { zimFileReader ->
val libKiwixBook = Book().apply { val libKiwixBook = getLibkiwixBook(zimFileReader)
update(zimFileReader.jniKiwixReader)
}
if (isBookmarked) { if (isBookmarked) {
repositoryActions?.deleteBookmark(libKiwixBook.id, articleUrl) repositoryActions?.deleteBookmark(libKiwixBook.id, articleUrl)
snackBarRoot?.snack(R.string.bookmark_removed) snackBarRoot?.snack(R.string.bookmark_removed)
@ -2071,6 +2070,19 @@ abstract class CoreReaderFragment :
} }
} }
/**
* Returns the libkiwix book evertime when user saves or remove the bookmark.
* the object will be created once to avoid creating it multiple times.
*/
private fun getLibkiwixBook(zimFileReader: ZimFileReader): Book {
libkiwixBook?.let { return it }
val book = Book().apply {
update(zimFileReader.jniKiwixReader)
}
libkiwixBook = book
return book
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
updateBottomToolbarVisibility() updateBottomToolbarVisibility()

View File

@ -23,10 +23,10 @@ import kotlin.math.log10
import kotlin.math.pow import kotlin.math.pow
@JvmInline @JvmInline
value class KiloByte(private val kilobyteString: String?) { value class Byte(private val byteString: String?) {
val humanReadable val humanReadable
get() = kilobyteString?.toLongOrNull()?.let { get() = byteString?.toLongOrNull()?.let {
val units = arrayOf("KB", "MB", "GB", "TB") val units = arrayOf("B", "KB", "MB", "GB", "TB")
val conversion = (log10(it.toDouble()) / log10(1024.0)).toInt() val conversion = (log10(it.toDouble()) / log10(1024.0)).toInt()
DecimalFormat("#,##0.#") DecimalFormat("#,##0.#")
.format(it / 1024.0.pow(conversion.toDouble())) + .format(it / 1024.0.pow(conversion.toDouble())) +