Merge pull request #4231 from kiwix/Fixes#4165

Added support for Android 16.
This commit is contained in:
Kelson 2025-04-01 20:17:34 +02:00 committed by GitHub
commit e939ff770d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 258 additions and 457 deletions

View File

@ -12,7 +12,7 @@ jobs:
name: Automated tests name: Automated tests
strategy: strategy:
matrix: matrix:
api-level: [ 25, 30, 33, 34, 35 ] api-level: [ 25, 30, 33, 34, 35, 36 ]
fail-fast: true fail-fast: true
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
@ -57,7 +57,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }} target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64 arch: x86_64
profile: pixel_2 profile: pixel_2
ram-size: 3072M ram-size: 3072M
@ -76,7 +76,7 @@ jobs:
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000" GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }} target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64 arch: x86_64
profile: pixel_2 profile: pixel_2
ram-size: 3072M ram-size: 3072M
@ -118,7 +118,7 @@ jobs:
name: Automated tests for PlayStore variant name: Automated tests for PlayStore variant
strategy: strategy:
matrix: matrix:
api-level: [ 25, 30, 33, 34, 35 ] api-level: [ 25, 30, 33, 34, 35, 36 ]
fail-fast: true fail-fast: true
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
@ -163,7 +163,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }} target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64 arch: x86_64
profile: pixel_2 profile: pixel_2
ram-size: 2048M ram-size: 2048M
@ -182,7 +182,7 @@ jobs:
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000" GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }} target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64 arch: x86_64
profile: pixel_2 profile: pixel_2
ram-size: 2048M ram-size: 2048M
@ -199,7 +199,7 @@ jobs:
name: Automated tests for Custom app name: Automated tests for Custom app
strategy: strategy:
matrix: matrix:
api-level: [ 25, 30, 33, 34, 35 ] api-level: [ 25, 30, 33, 34, 35, 36 ]
fail-fast: true fail-fast: true
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
@ -244,7 +244,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }} target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64 arch: x86_64
profile: pixel_2 profile: pixel_2
ram-size: 2048M ram-size: 2048M
@ -263,7 +263,7 @@ jobs:
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000" GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }} target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64 arch: x86_64
profile: pixel_2 profile: pixel_2
ram-size: 2048M ram-size: 2048M
@ -280,7 +280,7 @@ jobs:
name: Automated tests on Tablet name: Automated tests on Tablet
strategy: strategy:
matrix: matrix:
api-level: [ 25, 30, 33, 34, 35 ] api-level: [ 25, 30, 33, 34, 35, 36 ]
fail-fast: true fail-fast: true
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
@ -325,7 +325,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }} target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64 arch: x86_64
profile: pixel_2 profile: pixel_2
ram-size: 2048M ram-size: 2048M
@ -344,7 +344,7 @@ jobs:
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000" GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }} target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64 arch: x86_64
profile: pixel_c profile: pixel_c
ram-size: 2048M ram-size: 2048M

View File

@ -1,3 +1,4 @@
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.slack.keeper.optInToKeeper import com.slack.keeper.optInToKeeper
import org.w3c.dom.Element import org.w3c.dom.Element
import plugin.KiwixConfigurationPlugin import plugin.KiwixConfigurationPlugin
@ -29,12 +30,12 @@ android {
// it directly in the AndroidManifest file. // it directly in the AndroidManifest file.
namespace = "org.kiwix.kiwixmobile" namespace = "org.kiwix.kiwixmobile"
defaultConfig { defaultConfig {
base.archivesName.set(apkPrefix)
resValue("string", "app_name", "Kiwix") resValue("string", "app_name", "Kiwix")
resValue("string", "app_search_string", "Search Kiwix") resValue("string", "app_search_string", "Search Kiwix")
versionCode = "".getVersionCode() versionCode = "".getVersionCode()
versionName = generateVersionName() versionName = generateVersionName()
manifestPlaceholders["permission"] = "android.permission.MANAGE_EXTERNAL_STORAGE" manifestPlaceholders["permission"] = "android.permission.MANAGE_EXTERNAL_STORAGE"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
lint { lint {
checkDependencies = true checkDependencies = true
@ -130,7 +131,7 @@ dependencies {
androidTestImplementation(Libs.leakcanary_android_instrumentation) androidTestImplementation(Libs.leakcanary_android_instrumentation)
testImplementation(Libs.kotlinx_coroutines_test) testImplementation(Libs.kotlinx_coroutines_test)
} }
task("generateVersionCodeAndName") { tasks.register("generateVersionCodeAndName") {
val file = File("VERSION_INFO") val file = File("VERSION_INFO")
if (!file.exists()) file.createNewFile() if (!file.exists()) file.createNewFile()
file.printWriter().use { file.printWriter().use {
@ -138,7 +139,7 @@ task("generateVersionCodeAndName") {
} }
} }
task("renameTarakFile") { tasks.register("renameTarakFile") {
val taraskFile = File("$rootDir/core/src/main/res/values-b+be+tarask/strings.xml") val taraskFile = File("$rootDir/core/src/main/res/values-b+be+tarask/strings.xml")
val mainStringsFile = File("$rootDir/core/src/main/res/values/strings.xml") val mainStringsFile = File("$rootDir/core/src/main/res/values/strings.xml")
@ -214,6 +215,10 @@ fun elementToString(element: Element): String {
return result.writer.toString() return result.writer.toString()
} }
tasks.build { gradle.projectsEvaluated {
dependsOn("renameTarakFile") tasks.forEach { task ->
if (task.name != "renameTarakFile") {
task.dependsOn("renameTarakFile")
}
}
} }

View File

@ -119,8 +119,11 @@ class ObjectBoxToRoomMigratorTest {
val expectedSearchTerm = "test search" val expectedSearchTerm = "test search"
val expectedZimId = "8812214350305159407L" val expectedZimId = "8812214350305159407L"
val expectedUrl = "http://kiwix.app/mainPage" val expectedUrl = "http://kiwix.app/mainPage"
val recentSearchEntity = val recentSearchEntity = RecentSearchEntity(
RecentSearchEntity(searchTerm = expectedSearchTerm, zimId = expectedZimId, url = expectedUrl) searchTerm = expectedSearchTerm,
zimId = expectedZimId,
url = expectedUrl
)
// insert into object box // insert into object box
box.put(recentSearchEntity) box.put(recentSearchEntity)
// migrate data into room database // migrate data into room database

View File

@ -68,7 +68,9 @@ class DownloadRobot : BaseRobot() {
clickOn(ViewId(R.id.downloadsFragment)) clickOn(ViewId(R.id.downloadsFragment))
} }
fun waitForDataToLoad(retryCountForDataToLoad: Int = 10) { // Increasing the default timeout for data loading because, on the Android 16 Emulator,
// the internet connection is slow, and the library download takes longer.
fun waitForDataToLoad(retryCountForDataToLoad: Int = 20) {
try { try {
isVisible(TextId(string.your_languages)) isVisible(TextId(string.your_languages))
} catch (e: RuntimeException) { } catch (e: RuntimeException) {

View File

@ -56,7 +56,9 @@ class InitialDownloadRobot : BaseRobot() {
refresh(R.id.librarySwipeRefresh) refresh(R.id.librarySwipeRefresh)
} }
fun waitForDataToLoad(retryCountForDataToLoad: Int = 10) { // Increasing the default timeout for data loading because, on the Android 16 Emulator,
// the internet connection is slow, and the library download takes longer.
fun waitForDataToLoad(retryCountForDataToLoad: Int = 20) {
try { try {
isVisible(TextId(string.your_languages)) isVisible(TextId(string.your_languages))
} catch (e: RuntimeException) { } catch (e: RuntimeException) {

View File

@ -21,6 +21,7 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet import android.animation.AnimatorSet
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.database.DataSetObserver import android.database.DataSetObserver
import android.graphics.Canvas import android.graphics.Canvas
@ -776,13 +777,17 @@ class CustomPageIndicator @JvmOverloads constructor(
val density = context.resources.displayMetrics.density.toInt() val density = context.resources.displayMetrics.density.toInt()
// Load attributes // Load attributes
val a = @SuppressLint("UseKtx")
getContext().obtainStyledAttributes(attrs, R.styleable.CustomPageIndicator, defStyle, 0) val a = getContext().obtainStyledAttributes(
dotDiameter = attrs,
a.getDimensionPixelSize( R.styleable.CustomPageIndicator,
R.styleable.CustomPageIndicator_ipi_dotDiameter, defStyle,
DEFAULT_DOT_SIZE * density 0
) )
dotDiameter = a.getDimensionPixelSize(
R.styleable.CustomPageIndicator_ipi_dotDiameter,
DEFAULT_DOT_SIZE * density
)
dotRadius = (dotDiameter / 2).toFloat() dotRadius = (dotDiameter / 2).toFloat()
halfDotRadius = dotRadius / 2 halfDotRadius = dotRadius / 2
gap = gap =

View File

@ -366,18 +366,17 @@ class LocalLibraryFragment : BaseFragment(), CopyMoveFileHandler.FileCopyMoveCal
} }
private fun showFileChooser() { private fun showFileChooser() {
val intent = val intent = Intent().apply {
Intent().apply { action = Intent.ACTION_OPEN_DOCUMENT
action = Intent.ACTION_OPEN_DOCUMENT type = "*/*"
type = "*/*" addCategory(Intent.CATEGORY_OPENABLE)
addCategory(Intent.CATEGORY_OPENABLE) if (sharedPreferenceUtil.prefIsTest) {
if (sharedPreferenceUtil.prefIsTest) { putExtra(
putExtra( "android.provider.extra.INITIAL_URI",
"android.provider.extra.INITIAL_URI", "content://com.android.externalstorage.documents/document/primary:Download".toUri()
"content://com.android.externalstorage.documents/document/primary:Download".toUri() )
)
}
} }
}
try { try {
fileSelectLauncher.launch(Intent.createChooser(intent, "Select a zim file")) fileSelectLauncher.launch(Intent.createChooser(intent, "Select a zim file"))
} catch (_: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {

View File

@ -135,11 +135,10 @@ class KiwixReaderFragment : CoreReaderFragment() {
// Update the reader screen title to prevent showing the previously set title // Update the reader screen title to prevent showing the previously set title
// when creating the new archive object. // when creating the new archive object.
updateTitle() updateTitle()
val filePath = val filePath = FileUtils.getLocalFilePathByUri(
FileUtils.getLocalFilePathByUri( requireActivity().applicationContext,
requireActivity().applicationContext, zimFileUri.toUri()
zimFileUri.toUri() )
)
if (filePath == null || !File(filePath).isFileExist()) { if (filePath == null || !File(filePath).isFileExist()) {
// Close the previously opened book in the reader. Since this file is not found, // Close the previously opened book in the reader. Since this file is not found,
// it will not be set in the zimFileReader. The previously opened ZIM file // it will not be set in the zimFileReader. The previously opened ZIM file

View File

@ -42,6 +42,7 @@ 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
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.isWifi import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.isWifi
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.NewBookDao import org.kiwix.kiwixmobile.core.dao.NewBookDao
@ -475,7 +476,7 @@ class ZimManageViewModel @Inject constructor(
networkLanguageCounts: MutableMap<String, Int>, networkLanguageCounts: MutableMap<String, Int>,
listToActivateBy: List<Language> listToActivateBy: List<Language>
) = Locale.getISOLanguages() ) = Locale.getISOLanguages()
.map(::Locale) .map { it.convertToLocal() }
.filter { networkLanguageCounts.containsKey(it.isO3Language) } .filter { networkLanguageCounts.containsKey(it.isO3Language) }
.map { locale -> .map { locale ->
Language( Language(

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Kiwix Android
~ Copyright (c) 2020 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/>.
~
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="300"
android:fromAlpha="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="1.0" />
</set>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Kiwix Android
~ Copyright (c) 2020 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/>.
~
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="300"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="0.0" />
</set>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Kiwix Android
~ Copyright (c) 2020 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/>.
~
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="300"
android:fromAlpha="0.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="1.0" />
</set>

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Kiwix Android
~ Copyright (c) 2020 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/>.
~
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="300"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="0.0" />
</set>

View File

@ -1,3 +1,5 @@
import org.gradle.kotlin.dsl.register
buildscript { buildscript {
repositories { repositories {
google() google()
@ -28,6 +30,6 @@ allprojects {
} }
} }
tasks.create<Delete>("clean") { tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory) delete(rootProject.layout.buildDirectory)
} }

View File

@ -11,7 +11,7 @@ repositories {
} }
dependencies { dependencies {
implementation("com.android.tools.build:gradle:8.7.2") implementation("com.android.tools.build:gradle:8.11.0-alpha03")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0") implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0")
implementation("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.0.0-1.0.24") implementation("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.0.0-1.0.24")
implementation("org.jacoco:org.jacoco.core:0.8.12") implementation("org.jacoco:org.jacoco.core:0.8.12")
@ -23,8 +23,8 @@ dependencies {
exclude(group = "com.google.guava", module = "guava") exclude(group = "com.google.guava", module = "guava")
} }
implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.8") implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.8")
implementation("com.googlecode.json-simple:json-simple:1.1") implementation("com.googlecode.json-simple:json-simple:1.1.1")
implementation("com.squareup.okhttp3:okhttp:4.10.0") implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation(gradleApi()) implementation(gradleApi())
implementation(localGroovy()) implementation(localGroovy())

View File

@ -22,9 +22,9 @@ object Config {
// Here is a list of all Android versions with their corresponding API // Here is a list of all Android versions with their corresponding API
// levels: https://apilevels.com/ // levels: https://apilevels.com/
const val compileSdk = 35 // SDK version used by Gradle to compile our app. const val compileSdk = 36 // SDK version used by Gradle to compile our app.
const val minSdk = 25 // Minimum SDK (Minimum Support Device) is 25 (Android 7.1 Nougat). const val minSdk = 25 // Minimum SDK (Minimum Support Device) is 25 (Android 7.1 Nougat).
const val targetSdk = 35 // Target SDK (Maximum Support Device) is 34 (Android 14). const val targetSdk = 36 // Target SDK (Maximum Support Device) is 36 (Android 16).
val javaVersion = JavaVersion.VERSION_17 val javaVersion = JavaVersion.VERSION_17

View File

@ -16,7 +16,7 @@ object Versions {
const val org_jetbrains_kotlinx_kotlinx_coroutines: String = "1.10.1" const val org_jetbrains_kotlinx_kotlinx_coroutines: String = "1.10.1"
const val kotlinx_coroutines_rx3: String = "1.3.9" const val kotlinx_coroutines_rx3: String = "1.10.1"
const val androidx_test_espresso: String = "3.6.1" const val androidx_test_espresso: String = "3.6.1"
@ -40,7 +40,7 @@ object Versions {
const val androidx_test_core: String = "1.6.1" const val androidx_test_core: String = "1.6.1"
const val androidx_test_orchestrator: String = "1.5.0" const val androidx_test_orchestrator: String = "1.5.1"
const val io_objectbox: String = "4.0.3" const val io_objectbox: String = "4.0.3"
@ -48,7 +48,7 @@ object Versions {
const val android_arch_lifecycle_extensions: String = "1.1.1" const val android_arch_lifecycle_extensions: String = "1.1.1"
const val com_android_tools_build_gradle: String = "8.7.2" const val com_android_tools_build_gradle: String = "8.11.0-alpha03"
const val de_fayard_buildsrcversions_gradle_plugin: String = "0.7.0" const val de_fayard_buildsrcversions_gradle_plugin: String = "0.7.0"
@ -58,7 +58,7 @@ object Versions {
const val leakcanary_android: String = "2.14" const val leakcanary_android: String = "2.14"
const val constraintlayout: String = "2.1.4" const val constraintlayout: String = "2.2.0"
const val swipe_refresh_layout: String = "1.1.0" const val swipe_refresh_layout: String = "1.1.0"

View File

@ -24,7 +24,7 @@ import org.json.simple.JSONObject
import org.json.simple.parser.JSONParser import org.json.simple.parser.JSONParser
import java.io.File import java.io.File
typealias ProductFlavors = NamedDomainObjectContainer<ProductFlavor> typealias ProductFlavors = NamedDomainObjectContainer<out ProductFlavor>
object CustomApps { object CustomApps {

View File

@ -58,6 +58,7 @@ class AllProjectConfigurer {
if (isLibrary) { if (isLibrary) {
namespace = "org.kiwix.kiwixmobile.core" namespace = "org.kiwix.kiwixmobile.core"
} }
setCompileSdkVersion(Config.compileSdk) setCompileSdkVersion(Config.compileSdk)
defaultConfig { defaultConfig {
minSdk = Config.minSdk minSdk = Config.minSdk
@ -161,6 +162,9 @@ class AllProjectConfigurer {
add("UnusedResources") add("UnusedResources")
add("NonConstantResourceId") add("NonConstantResourceId")
add("NotifyDataSetChanged") add("NotifyDataSetChanged")
add("Aligned16KB") // TODO Remove when properly migrated to Android 16.
add("AndroidGradlePluginVersion")
add("MemberExtensionConflict")
} }
lintConfig = target.rootProject.file("lintConfig.xml") lintConfig = target.rootProject.file("lintConfig.xml")
} }

View File

@ -21,6 +21,10 @@
# Enable Wi-Fi on the emulator # Enable Wi-Fi on the emulator
adb shell svc wifi enable adb shell svc wifi enable
adb logcat -c adb logcat -c
# Check if the stylus_handwriting_enabled setting exists before disabling
if adb shell settings list secure | grep -q "stylus_handwriting_enabled"; then
adb shell settings put secure stylus_handwriting_enabled 0
fi
# shellcheck disable=SC2035 # shellcheck disable=SC2035
adb logcat *:E -v color & adb logcat *:E -v color &
@ -61,6 +65,10 @@ while [ $retry -le 3 ]; do
# Enable Wi-Fi on the emulator # Enable Wi-Fi on the emulator
adb shell svc wifi enable adb shell svc wifi enable
adb logcat -c adb logcat -c
# Check if the stylus_handwriting_enabled setting exists before disabling
if adb shell settings list secure | grep -q "stylus_handwriting_enabled"; then
adb shell settings put secure stylus_handwriting_enabled 0
fi
# shellcheck disable=SC2035 # shellcheck disable=SC2035
adb logcat *:E -v color & adb logcat *:E -v color &

View File

@ -3,6 +3,10 @@
# Enable Wi-Fi on the emulator # Enable Wi-Fi on the emulator
adb shell svc wifi enable adb shell svc wifi enable
adb logcat -c adb logcat -c
# Check if the stylus_handwriting_enabled setting exists before disabling
if adb shell settings list secure | grep -q "stylus_handwriting_enabled"; then
adb shell settings put secure stylus_handwriting_enabled 0
fi
# shellcheck disable=SC2035 # shellcheck disable=SC2035
adb logcat *:E -v color & adb logcat *:E -v color &
@ -42,6 +46,10 @@ while [ $retry -le 3 ]; do
# Enable Wi-Fi on the emulator # Enable Wi-Fi on the emulator
adb shell svc wifi enable adb shell svc wifi enable
adb logcat -c adb logcat -c
# Check if the stylus_handwriting_enabled setting exists before disabling
if adb shell settings list secure | grep -q "stylus_handwriting_enabled"; then
adb shell settings put secure stylus_handwriting_enabled 0
fi
# shellcheck disable=SC2035 # shellcheck disable=SC2035
adb logcat *:E -v color & adb logcat *:E -v color &

View File

@ -3,6 +3,10 @@
# Enable Wi-Fi on the emulator # Enable Wi-Fi on the emulator
adb shell svc wifi enable adb shell svc wifi enable
adb logcat -c adb logcat -c
# Check if the stylus_handwriting_enabled setting exists before disabling
if adb shell settings list secure | grep -q "stylus_handwriting_enabled"; then
adb shell settings put secure stylus_handwriting_enabled 0
fi
# shellcheck disable=SC2035 # shellcheck disable=SC2035
adb logcat *:E -v color & adb logcat *:E -v color &
@ -42,6 +46,10 @@ while [ $retry -le 3 ]; do
# Enable Wi-Fi on the emulator # Enable Wi-Fi on the emulator
adb shell svc wifi enable adb shell svc wifi enable
adb logcat -c adb logcat -c
# Check if the stylus_handwriting_enabled setting exists before disabling
if adb shell settings list secure | grep -q "stylus_handwriting_enabled"; then
adb shell settings put secure stylus_handwriting_enabled 0
fi
# shellcheck disable=SC2035 # shellcheck disable=SC2035
adb logcat *:E -v color & adb logcat *:E -v color &

View File

@ -3,6 +3,10 @@
# Enable Wi-Fi on the emulator # Enable Wi-Fi on the emulator
adb shell svc wifi enable adb shell svc wifi enable
adb logcat -c adb logcat -c
# Check if the stylus_handwriting_enabled setting exists before disabling
if adb shell settings list secure | grep -q "stylus_handwriting_enabled"; then
adb shell settings put secure stylus_handwriting_enabled 0
fi
# shellcheck disable=SC2035 # shellcheck disable=SC2035
adb logcat *:E -v color & adb logcat *:E -v color &
@ -43,6 +47,10 @@ while [ $retry -le 3 ]; do
# Enable Wi-Fi on the emulator # Enable Wi-Fi on the emulator
adb shell svc wifi enable adb shell svc wifi enable
adb logcat -c adb logcat -c
# Check if the stylus_handwriting_enabled setting exists before disabling
if adb shell settings list secure | grep -q "stylus_handwriting_enabled"; then
adb shell settings put secure stylus_handwriting_enabled 0
fi
# shellcheck disable=SC2035 # shellcheck disable=SC2035
adb logcat *:E -v color & adb logcat *:E -v color &

View File

@ -20,6 +20,7 @@ apply(plugin = "io.objectbox")
android { android {
defaultConfig { defaultConfig {
buildConfigField("long", "VERSION_CODE", "".getVersionCode().toString()) buildConfigField("long", "VERSION_CODE", "".getVersionCode().toString())
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
getByName("release") { getByName("release") {

View File

@ -52,12 +52,13 @@
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="false"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:hasFragileUserData="true"
android:largeHeap="true" android:largeHeap="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:resizeableActivity="true" android:resizeableActivity="true"
android:hasFragileUserData="true"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/KiwixTheme" android:theme="@style/KiwixTheme"
tools:targetApi="tiramisu"> tools:targetApi="tiramisu">
@ -91,6 +92,7 @@
<activity <activity
android:name=".error.DiagnosticReportActivity" android:name=".error.DiagnosticReportActivity"
android:exported="false" /> android:exported="false" />
<service android:name=".read_aloud.ReadAloudService" /> <service android:name=".read_aloud.ReadAloudService" />
<service <service
android:name=".downloader.downloadManager.DownloadMonitorService" android:name=".downloader.downloadManager.DownloadMonitorService"

View File

@ -23,6 +23,7 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import android.net.ConnectivityManager import android.net.ConnectivityManager
import java.util.Locale
/** /**
* This interface defines a set of functions that are not available on all platforms. * This interface defines a set of functions that are not available on all platforms.
@ -76,4 +77,6 @@ interface Compat {
fun isNetworkAvailable(connectivity: ConnectivityManager): Boolean fun isNetworkAvailable(connectivity: ConnectivityManager): Boolean
fun isWifi(connectivity: ConnectivityManager): Boolean fun isWifi(connectivity: ConnectivityManager): Boolean
fun convertToLocal(language: String): Locale
} }

View File

@ -28,7 +28,8 @@ import android.os.Build
class CompatHelper private constructor() { class CompatHelper private constructor() {
// Note: Needs ": Compat" or the type system assumes `Compat21` // Note: Needs ": Compat" or the type system assumes `Compat21`
private val compatValue: Compat = when { private val compatValue: Compat = when {
sdkVersion >= Build.VERSION_CODES.TIRAMISU -> CompatV33() Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA -> CompatV36()
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> CompatV33()
else -> CompatV25() else -> CompatV25()
} }
@ -36,10 +37,6 @@ class CompatHelper private constructor() {
/** Singleton instance of [CompatHelper] */ /** Singleton instance of [CompatHelper] */
private val instance by lazy(::CompatHelper) private val instance by lazy(::CompatHelper)
/** Get the current Android API level. */
val sdkVersion: Int
get() = Build.VERSION.SDK_INT
val compat get() = instance.compatValue val compat get() = instance.compatValue
/** /**
@ -79,5 +76,7 @@ class CompatHelper private constructor() {
fun ConnectivityManager.isWifi(): Boolean = fun ConnectivityManager.isWifi(): Boolean =
compat.isWifi(this) compat.isWifi(this)
fun String.convertToLocal() = compat.convertToLocal(this)
} }
} }

View File

@ -26,6 +26,7 @@ import android.content.pm.ResolveInfo
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.NetworkCapabilities.TRANSPORT_WIFI
import java.util.Locale
open class CompatV25 : Compat { open class CompatV25 : Compat {
override fun queryIntentActivities( override fun queryIntentActivities(
@ -61,4 +62,6 @@ open class CompatV25 : Compat {
return connectivity.getNetworkCapabilities(connectivity.activeNetwork) return connectivity.getNetworkCapabilities(connectivity.activeNetwork)
?.hasTransport(TRANSPORT_WIFI) == true ?.hasTransport(TRANSPORT_WIFI) == true
} }
override fun convertToLocal(language: String): Locale = Locale(language)
} }

View File

@ -18,17 +18,17 @@
package org.kiwix.kiwixmobile.core.compat package org.kiwix.kiwixmobile.core.compat
import android.annotation.TargetApi
import android.content.Intent import android.content.Intent
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.PackageInfoFlags import android.content.pm.PackageManager.PackageInfoFlags
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.Build.VERSION_CODES.TIRAMISU
import androidx.annotation.RequiresApi
import java.util.Locale
const val API_33 = 33 @RequiresApi(TIRAMISU)
@TargetApi(API_33)
open class CompatV33 : Compat { open class CompatV33 : Compat {
private val compatV25 = CompatV25() private val compatV25 = CompatV25()
override fun queryIntentActivities( override fun queryIntentActivities(
@ -52,4 +52,6 @@ open class CompatV33 : Compat {
override fun isWifi(connectivity: ConnectivityManager): Boolean = override fun isWifi(connectivity: ConnectivityManager): Boolean =
compatV25.isWifi(connectivity) compatV25.isWifi(connectivity)
override fun convertToLocal(language: String): Locale = compatV25.convertToLocal(language)
} }

View File

@ -0,0 +1,53 @@
/*
* Kiwix Android
* Copyright (c) 2025 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.compat
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.ConnectivityManager
import android.os.Build.VERSION_CODES.BAKLAVA
import androidx.annotation.RequiresApi
import java.util.Locale
@RequiresApi(BAKLAVA)
open class CompatV36 : Compat {
private val compatV33 = CompatV33()
override fun queryIntentActivities(
packageManager: PackageManager,
intent: Intent,
flags: ResolveInfoFlagsCompat
): List<ResolveInfo> = compatV33.queryIntentActivities(packageManager, intent, flags)
override fun getPackageInformation(
packageName: String,
packageManager: PackageManager,
flag: Int
): PackageInfo =
compatV33.getPackageInformation(packageName, packageManager, flag)
override fun isNetworkAvailable(connectivity: ConnectivityManager): Boolean =
compatV33.isNetworkAvailable(connectivity)
override fun isWifi(connectivity: ConnectivityManager): Boolean =
compatV33.isWifi(connectivity)
override fun convertToLocal(language: String): Locale = Locale.of(language)
}

View File

@ -22,6 +22,7 @@ import io.objectbox.annotation.Convert
import io.objectbox.annotation.Entity import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id import io.objectbox.annotation.Id
import io.objectbox.converter.PropertyConverter import io.objectbox.converter.PropertyConverter
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import org.kiwix.kiwixmobile.core.zim_manager.Language import org.kiwix.kiwixmobile.core.zim_manager.Language
import java.util.Locale import java.util.Locale
@ -35,7 +36,7 @@ data class LanguageEntity(
) { ) {
constructor(language: Language) : this( constructor(language: Language) : this(
0, 0,
Locale(language.languageCode), language.languageCode.convertToLocal(),
language.active, language.active,
language.occurencesOfLanguage language.occurencesOfLanguage
) )
@ -49,5 +50,5 @@ class StringToLocaleConverter : PropertyConverter<Locale, String> {
entityProperty?.isO3Language ?: Locale.ENGLISH.isO3Language entityProperty?.isO3Language ?: Locale.ENGLISH.isO3Language
override fun convertToEntityProperty(databaseValue: String?): Locale = override fun convertToEntityProperty(databaseValue: String?): Locale =
databaseValue?.let(::Locale) ?: Locale.ENGLISH databaseValue?.convertToLocal() ?: Locale.ENGLISH
} }

View File

@ -33,6 +33,9 @@ import android.util.TypedValue
import android.widget.Toast import android.widget.Toast
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.createBitmap
import androidx.core.graphics.drawable.toDrawable
import androidx.core.graphics.scale
import org.kiwix.kiwixmobile.core.base.BaseBroadcastReceiver import org.kiwix.kiwixmobile.core.base.BaseBroadcastReceiver
import java.util.Locale import java.util.Locale
@ -87,14 +90,9 @@ fun Context.getResizedDrawable(resourceId: Int, width: Int, height: Int): Drawab
val drawable = ContextCompat.getDrawable(this, resourceId) val drawable = ContextCompat.getDrawable(this, resourceId)
return if (drawable != null) { return if (drawable != null) {
val bitmap = Bitmap.createScaledBitmap( val bitmap = getBitmapFromDrawable(drawable).scale(width, height, false)
getBitmapFromDrawable(drawable),
width,
height,
false
)
BitmapDrawable(resources, bitmap).apply { bitmap.toDrawable(resources).apply {
bounds = drawable.bounds bounds = drawable.bounds
} }
} else { } else {
@ -107,11 +105,7 @@ fun Context.getBitmapFromDrawable(drawable: Drawable): Bitmap {
return drawable.bitmap return drawable.bitmap
} }
val bitmap = Bitmap.createBitmap( val bitmap = createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight)
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap) val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height) drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas) drawable.draw(canvas)

View File

@ -18,6 +18,7 @@
package org.kiwix.kiwixmobile.core.extensions package org.kiwix.kiwixmobile.core.extensions
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams
import android.widget.EditText import android.widget.EditText
@ -29,6 +30,7 @@ import org.kiwix.kiwixmobile.core.R
const val CLOSE_ICON_PADDING = 30 const val CLOSE_ICON_PADDING = 30
@SuppressLint("PrivateResource")
fun SearchView.setUpSearchView(context: Context) { fun SearchView.setUpSearchView(context: Context) {
val heightAndWidth = context.resources.getDimensionPixelSize( val heightAndWidth = context.resources.getDimensionPixelSize(
R.dimen.material_minimum_height_and_width R.dimen.material_minimum_height_and_width

View File

@ -72,6 +72,7 @@ import androidx.constraintlayout.widget.Group
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.core.view.MenuHost import androidx.core.view.MenuHost
@ -2516,7 +2517,6 @@ abstract class CoreReaderFragment :
SharedPreferenceUtil.PREF_KIWIX_MOBILE, SharedPreferenceUtil.PREF_KIWIX_MOBILE,
0 0
) )
val editor = settings.edit()
val webViewHistoryEntityList = arrayListOf<WebViewHistoryEntity>() val webViewHistoryEntityList = arrayListOf<WebViewHistoryEntity>()
webViewList.forEachIndexed { index, view -> webViewList.forEachIndexed { index, view ->
if (view.url == null) return@forEachIndexed if (view.url == null) return@forEachIndexed
@ -2525,9 +2525,10 @@ abstract class CoreReaderFragment :
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
repositoryActions?.saveWebViewPageHistory(webViewHistoryEntityList) repositoryActions?.saveWebViewPageHistory(webViewHistoryEntityList)
} }
editor.putString(TAG_CURRENT_FILE, zimReaderContainer?.zimReaderSource?.toDatabase()) settings.edit {
editor.putInt(TAG_CURRENT_TAB, currentWebViewIndex) putString(TAG_CURRENT_FILE, zimReaderContainer?.zimReaderSource?.toDatabase())
editor.apply() putInt(TAG_CURRENT_TAB, currentWebViewIndex)
}
Log.d( Log.d(
TAG_KIWIX, TAG_KIWIX,
"Save current zim file to preferences: " + "Save current zim file to preferences: " +

View File

@ -18,6 +18,7 @@
package org.kiwix.kiwixmobile.core.qr package org.kiwix.kiwixmobile.core.qr
import android.annotation.SuppressLint
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import com.google.zxing.BarcodeFormat import com.google.zxing.BarcodeFormat
@ -39,6 +40,7 @@ class GenerateQR @Inject constructor() {
* @param foregroundColor The color of the QR code. * @param foregroundColor The color of the QR code.
* @param backgroundColor The background color of the QR code. * @param backgroundColor The background color of the QR code.
*/ */
@SuppressLint("UseKtx")
fun createQR( fun createQR(
code: String, code: String,
size: Int = 512, size: Int = 512,

View File

@ -46,6 +46,7 @@ import org.kiwix.kiwixmobile.core.CoreApp.Companion.coreComponent
import org.kiwix.kiwixmobile.core.CoreApp.Companion.instance import org.kiwix.kiwixmobile.core.CoreApp.Companion.instance
import org.kiwix.kiwixmobile.core.DarkModeConfig import org.kiwix.kiwixmobile.core.DarkModeConfig
import org.kiwix.kiwixmobile.core.R import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getPackageInformation import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getPackageInformation
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getVersionCode import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.getVersionCode
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
@ -169,9 +170,7 @@ abstract class CorePrefsFragment :
if (selectedLang == Locale.ROOT.toString()) { if (selectedLang == Locale.ROOT.toString()) {
getString(R.string.device_default) getString(R.string.device_default)
} else { } else {
Locale( selectedLang.convertToLocal().displayLanguage
selectedLang
).displayLanguage
} }
languagePref.onPreferenceChangeListener = languagePref.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue -> Preference.OnPreferenceChangeListener { _, newValue ->
@ -206,7 +205,7 @@ abstract class CorePrefsFragment :
val entries = arrayOfNulls<String>(languageCodeList.size) val entries = arrayOfNulls<String>(languageCodeList.size)
entries[0] = getString(R.string.device_default) entries[0] = getString(R.string.device_default)
for (i in 1 until languageCodeList.size) { for (i in 1 until languageCodeList.size) {
val locale = Locale(languageCodeList[i]) val locale = languageCodeList[i].convertToLocal()
entries[i] = locale.displayLanguage + " (" + locale.getDisplayLanguage(locale) + ") " entries[i] = locale.displayLanguage + " (" + locale.getDisplayLanguage(locale) + ") "
} }
return entries return entries
@ -389,7 +388,7 @@ abstract class CorePrefsFragment :
} }
try { try {
fileSelectLauncher.launch(Intent.createChooser(intent, "Select a bookmark file")) fileSelectLauncher.launch(Intent.createChooser(intent, "Select a bookmark file"))
} catch (ex: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
activity.toast( activity.toast(
resources.getString(R.string.no_app_found_to_select_bookmark_file), resources.getString(R.string.no_app_found_to_select_bookmark_file),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT

View File

@ -17,13 +17,15 @@
*/ */
package org.kiwix.kiwixmobile.core.utils package org.kiwix.kiwixmobile.core.utils
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import java.util.Locale import java.util.Locale
/** /**
* Created by mhutti1 on 19/04/17. * Created by mhutti1 on 19/04/17.
*/ */
class BookUtils { class BookUtils {
val localeMap = Locale.getISOLanguages().map(::Locale).associateBy { it.isO3Language } val localeMap =
Locale.getISOLanguages().map { it.convertToLocal() }.associateBy { it.isO3Language }
// Get the language from the language codes of the parsed xml stream // Get the language from the language codes of the parsed xml stream
@Suppress("MagicNumber") @Suppress("MagicNumber")

View File

@ -18,6 +18,7 @@
package org.kiwix.kiwixmobile.core.utils package org.kiwix.kiwixmobile.core.utils
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import java.util.Locale import java.util.Locale
class LanguageContainer private constructor(val languageCode: String, val languageName: String) { class LanguageContainer private constructor(val languageCode: String, val languageName: String) {
@ -25,7 +26,7 @@ class LanguageContainer private constructor(val languageCode: String, val langua
companion object { companion object {
private fun chooseLanguageName(languageCode: String): String { private fun chooseLanguageName(languageCode: String): String {
val displayLanguage = Locale(languageCode).displayLanguage val displayLanguage = languageCode.convertToLocal().displayLanguage
return if (displayLanguage.length == 2 || displayLanguage.isEmpty()) { return if (displayLanguage.length == 2 || displayLanguage.isEmpty()) {
Locale.ENGLISH.displayLanguage Locale.ENGLISH.displayLanguage
} else { } else {

View File

@ -21,6 +21,7 @@ import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -136,9 +137,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
ContextWrapper(context).externalMediaDirs[0]?.path ContextWrapper(context).externalMediaDirs[0]?.path
?: context.filesDir.path // a workaround for emulators ?: context.filesDir.path // a workaround for emulators
fun getPrefStorageTitle(defaultTitle: String): String =
sharedPreferences.getString(PREF_STORAGE_TITLE, defaultTitle) ?: defaultTitle
fun putPrefBookMarkMigrated(isMigrated: Boolean) = fun putPrefBookMarkMigrated(isMigrated: Boolean) =
sharedPreferences.edit { putBoolean(PREF_BOOKMARKS_MIGRATED, isMigrated) } sharedPreferences.edit { putBoolean(PREF_BOOKMARKS_MIGRATED, isMigrated) }
@ -168,9 +166,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
_prefWifiOnlys.onNext(wifiOnly) _prefWifiOnlys.onNext(wifiOnly)
} }
fun putPrefStorageTitle(storageTitle: String) =
sharedPreferences.edit { putString(PREF_STORAGE_TITLE, storageTitle) }
fun putPrefStorage(storage: String) { fun putPrefStorage(storage: String) {
sharedPreferences.edit { putString(PREF_STORAGE, storage) } sharedPreferences.edit { putString(PREF_STORAGE, storage) }
_prefStorages.onNext(storage) _prefStorages.onNext(storage)
@ -294,9 +289,11 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
path.substringBefore(context.getString(R.string.android_directory_seperator)) path.substringBefore(context.getString(R.string.android_directory_seperator))
} }
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
fun isPlayStoreBuildWithAndroid11OrAbove(): Boolean = fun isPlayStoreBuildWithAndroid11OrAbove(): Boolean =
isPlayStoreBuild && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R isPlayStoreBuild && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
fun isNotPlayStoreBuildWithAndroid11OrAbove(): Boolean = fun isNotPlayStoreBuildWithAndroid11OrAbove(): Boolean =
!isPlayStoreBuild && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R !isPlayStoreBuild && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
@ -316,7 +313,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
private const val PREF_BACK_TO_TOP = "pref_backtotop" private const val PREF_BACK_TO_TOP = "pref_backtotop"
private const val PREF_FULLSCREEN = "pref_fullscreen" private const val PREF_FULLSCREEN = "pref_fullscreen"
private const val PREF_NEW_TAB_BACKGROUND = "pref_newtab_background" private const val PREF_NEW_TAB_BACKGROUND = "pref_newtab_background"
private const val PREF_STORAGE_TITLE = "pref_selected_title"
const val PREF_EXTERNAL_LINK_POPUP = "pref_external_link_popup" const val PREF_EXTERNAL_LINK_POPUP = "pref_external_link_popup"
const val PREF_SHOW_STORAGE_OPTION = "show_storgae_option" const val PREF_SHOW_STORAGE_OPTION = "show_storgae_option"
private const val PREF_IS_FIRST_RUN = "isFirstRun" private const val PREF_IS_FIRST_RUN = "isFirstRun"

View File

@ -1,38 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2020 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.utils
import android.text.Editable
import android.text.TextWatcher
class SimpleTextWatcher(
private val onTextWatcherChangeAction: (CharSequence?, Int, Int, Int) -> Unit
) : TextWatcher {
@SuppressWarnings("EmptyFunctionBlock")
override fun afterTextChanged(p0: Editable?) {
}
@SuppressWarnings("EmptyFunctionBlock")
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
onTextWatcherChangeAction.invoke(s, start, before, count)
}
}

View File

@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.core.zim_manager
import android.os.Parcelable import android.os.Parcelable
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import java.util.Locale import java.util.Locale
@Parcelize @Parcelize
@ -51,7 +52,7 @@ data class Language constructor(
languageCode: String, languageCode: String,
active: Boolean, active: Boolean,
occurrencesOfLanguage: Int occurrencesOfLanguage: Int
) : this(Locale(languageCode), active, occurrencesOfLanguage) ) : this(languageCode.convertToLocal(), active, occurrencesOfLanguage)
override fun equals(other: Any?): Boolean = override fun equals(other: Any?): Boolean =
(other as Language).language == language && other.active == active (other as Language).language == language && other.active == active

View File

@ -18,6 +18,7 @@
package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view package org.kiwix.kiwixmobile.core.zim_manager.fileselect_view
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity import org.kiwix.kiwixmobile.core.dao.entities.BookOnDiskEntity
import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
@ -50,7 +51,7 @@ sealed class BooksOnDiskListItem {
override val id: Long = databaseId override val id: Long = databaseId
) : BooksOnDiskListItem() { ) : BooksOnDiskListItem() {
val locale: Locale by lazy { val locale: Locale by lazy {
Locale(book.language) book.language.convertToLocal()
} }
constructor(bookOnDiskEntity: BookOnDiskEntity) : this( constructor(bookOnDiskEntity: BookOnDiskEntity) : this(

View File

@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="48dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="48dp">
<path android:fillColor="@android:color/white" android:pathData="M15,21h-2v-2h2V21zM13,14h-2v5h2V14zM21,12h-2v4h2V12zM19,10h-2v2h2V10zM7,12H5v2h2V12zM5,10H3v2h2V10zM12,5h2V3h-2V5zM4.5,4.5v3h3v-3H4.5zM9,9H3V3h6V9zM4.5,16.5v3h3v-3H4.5zM9,21H3v-6h6V21zM16.5,4.5v3h3v-3H16.5zM21,9h-6V3h6V9zM19,19v-3l-4,0v2h2v3h4v-2H19zM17,12l-4,0v2h4V12zM13,10H7v2h2v2h2v-2h2V10zM14,9V7h-2V5h-2v4L14,9zM6.75,5.25h-1.5v1.5h1.5V5.25zM6.75,17.25h-1.5v1.5h1.5V17.25zM18.75,5.25h-1.5v1.5h1.5V5.25z"/>
</vector>

View File

@ -1,17 +0,0 @@
<?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="wrap_content">
<TextView
android:id="@+id/header_language"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_horizontal_margin"
android:textAppearance="?textAppearanceSubtitle1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="English" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,130 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/card_margin">
<androidx.constraintlayout.widget.ConstraintLayout 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">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/horizontal_padding"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="@dimen/activity_horizontal_margin" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/vertical_padding"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="@dimen/activity_vertical_margin" />
<CheckBox
android:id="@+id/itemBookCheckbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/horizontal_padding"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlSymmetry" />
<ImageView
android:id="@+id/item_book_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/fav_icon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/itemBookCheckbox"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/ic_launcher_round" />
<TextView
android:id="@+id/item_book_title"
style="@style/list_item_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/item_book_icon"
app:layout_constraintTop_toBottomOf="@+id/vertical_padding"
tools:text="Wikipedia" />
<TextView
android:id="@+id/item_book_description"
style="@style/list_item_body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="2"
android:textColor="?textSecondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/item_book_title"
app:layout_constraintTop_toBottomOf="@id/item_book_title"
tools:text="All wikipedia articles" />
<TextView
android:id="@+id/item_book_date"
style="@style/list_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?textTertiary"
app:layout_constraintStart_toStartOf="@id/item_book_title"
app:layout_constraintTop_toBottomOf="@id/item_book_description"
tools:text="1 Jan 2018" />
<TextView
android:id="@+id/item_book_size"
style="@style/list_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:textColor="?textTertiary"
app:layout_constraintStart_toEndOf="@id/item_book_date"
app:layout_constraintTop_toTopOf="@id/item_book_date"
tools:text="20 GB" />
<TextView
android:id="@+id/item_book_article_count"
style="@style/list_item_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:textColor="?textTertiary"
app:layout_constraintStart_toEndOf="@id/item_book_size"
app:layout_constraintTop_toTopOf="@id/item_book_size"
tools:text="10.1 K articles" />
<org.kiwix.kiwixmobile.core.zim_manager.TagsView
android:id="@+id/tags"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/item_book_description"
app:layout_constraintTop_toBottomOf="@id/item_book_date" />
<View
android:id="@+id/item_book_clickable_area"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
app:layout_constraintBottom_toBottomOf="parent"
android:contentDescription="@string/zim_file_content_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/delete_note"
android:icon="@drawable/ic_delete_white_24dp"
android:title="@string/delete"
app:showAsAction="ifRoom" />
<item
android:id="@+id/share_note"
android:icon="@drawable/baseline_share_24"
android:title="@string/share"
app:showAsAction="ifRoom" />
<item
android:id="@+id/save_note"
android:icon="@drawable/ic_save"
android:title="@string/save"
app:showAsAction="ifRoom" />
</menu>

View File

@ -91,7 +91,9 @@ internal class NewBookDaoTest {
} }
} }
private fun expectEmissionOfExistingAndNotExistingBook(isInTrashFolder: Boolean = false): Pair<BookOnDiskEntity, BookOnDiskEntity> { private fun expectEmissionOfExistingAndNotExistingBook(
isInTrashFolder: Boolean = false
): Pair<BookOnDiskEntity, BookOnDiskEntity> {
val query: Query<BookOnDiskEntity> = mockk() val query: Query<BookOnDiskEntity> = mockk()
every { box.query().build() } returns query every { box.query().build() } returns query
val zimReaderSourceThatExists = mockk<ZimReaderSource>() val zimReaderSourceThatExists = mockk<ZimReaderSource>()

View File

@ -18,17 +18,17 @@
package org.kiwix.kiwixmobile.core.dao package org.kiwix.kiwixmobile.core.dao
import org.junit.jupiter.api.Test
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import io.objectbox.Box import io.objectbox.Box
import io.objectbox.query.Query import io.objectbox.query.Query
import io.objectbox.query.QueryBuilder import io.objectbox.query.QueryBuilder
import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity_ import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity
import org.kiwix.kiwixmobile.core.page.bookmark import org.kiwix.kiwixmobile.core.dao.entities.BookmarkEntity_
import org.kiwix.kiwixmobile.core.page.adapter.Page import org.kiwix.kiwixmobile.core.page.adapter.Page
import org.kiwix.kiwixmobile.core.page.bookmark
import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem import org.kiwix.kiwixmobile.core.page.bookmark.adapter.BookmarkItem
import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.reader.ZimFileReader
@ -69,7 +69,6 @@ internal class NewBookmarksDaoTest {
every { bookmarkItem.zimName } returns "" every { bookmarkItem.zimName } returns ""
every { bookmarkItem.databaseId } returns 0L every { bookmarkItem.databaseId } returns 0L
newBookmarksDao.getCurrentZimBookmarksUrl(zimFileReader) newBookmarksDao.getCurrentZimBookmarksUrl(zimFileReader)
val bookmarkEntity: BookmarkEntity = mockk()
every { every {
query.property(BookmarkEntity_.bookmarkUrl).findStrings().toList().distinct() query.property(BookmarkEntity_.bookmarkUrl).findStrings().toList().distinct()
} returns listOf("") } returns listOf("")

View File

@ -27,13 +27,12 @@ class MetaLinkNetworkEntityTest {
@Throws(Exception::class) @Throws(Exception::class)
fun testDeserialize() { fun testDeserialize() {
val serializer = Persister() val serializer = Persister()
val result = val result = serializer.read(
serializer.read( MetaLinkNetworkEntity::class.java,
MetaLinkNetworkEntity::class.java, MetaLinkNetworkEntityTest::class.java.classLoader!!.getResourceAsStream(
MetaLinkNetworkEntityTest::class.java.classLoader!!.getResourceAsStream( "wikipedia_af_all_nopic_2016-05.zim.meta4"
"wikipedia_af_all_nopic_2016-05.zim.meta4"
)
) )
)
result?.urls?.let { result?.urls?.let {
MetaLinkNetworkEntityUrlAssert(it).hasItems( MetaLinkNetworkEntityUrlAssert(it).hasItems(
listOf( listOf(
@ -68,9 +67,7 @@ class MetaLinkNetworkEntityTest {
} }
// Basic file attributes // Basic file attributes
assertThat(result.file?.name).isEqualTo("wikipedia_af_all_nopic_2016-05.zim") assertThat(result.file?.name).isEqualTo("wikipedia_af_all_nopic_2016-05.zim")
assertThat(result.file?.size).isEqualTo(63973123L) assertThat(result.file?.size).isEqualTo(63973123L)
// File hashes // File hashes
assertThat(result.file?.getHash("md5")).isEqualTo("6f06866b61c4a921b57f28cfd4307220") assertThat(result.file?.getHash("md5")).isEqualTo("6f06866b61c4a921b57f28cfd4307220")
assertThat( assertThat(
@ -103,21 +100,17 @@ class MetaLinkNetworkEntityTest {
*/ */
class MetaLinkNetworkEntityUrlAssert( class MetaLinkNetworkEntityUrlAssert(
actual: List<MetaLinkNetworkEntity.Url> actual: List<MetaLinkNetworkEntity.Url>
) : ) : AbstractAssert<MetaLinkNetworkEntityUrlAssert, List<MetaLinkNetworkEntity.Url>>(
AbstractAssert<MetaLinkNetworkEntityUrlAssert, List<MetaLinkNetworkEntity.Url>>( actual,
actual, MetaLinkNetworkEntityUrlAssert::class.java
MetaLinkNetworkEntityUrlAssert::class.java ) {
) {
private fun <S, T> intersectionWith( private fun <S, T> intersectionWith(
first: List<S>, first: List<S>,
second: List<T>, second: List<T>,
function: (S, T) -> Boolean function: (S, T) -> Boolean
): Boolean { ): Boolean {
val filtered = first.filter { a -> second.any { b -> function(a, b) } } val filtered = first.filter { a -> second.any { b -> function(a, b) } }
if (filtered.isNotEmpty()) { return filtered.isNotEmpty()
return true
}
return false
} }
fun hasItems(items: List<DummyUrl>): Boolean { fun hasItems(items: List<DummyUrl>): Boolean {

View File

@ -43,8 +43,9 @@ internal class SearchStateTest {
val estimatedMatches = 100 val estimatedMatches = 100
every { suggestionSearchWrapper.estimatedMatches } returns estimatedMatches.toLong() every { suggestionSearchWrapper.estimatedMatches } returns estimatedMatches.toLong()
// Settings list to hasNext() to ensure it returns true only for the first call. // Settings list to hasNext() to ensure it returns true only for the first call.
// Otherwise, if we do not set this, the method will always return true when checking if the iterator has a next value, // Otherwise, if we do not set this, the method will always return true when
// causing our test case to get stuck in an infinite loop due to this explicit setting. // checking if the iterator has a next value, causing our test case to get stuck
// in an infinite loop due to this explicit setting.
every { searchIteratorWrapper.hasNext() } returnsMany listOf(true, false) every { searchIteratorWrapper.hasNext() } returnsMany listOf(true, false)
every { searchIteratorWrapper.next() } returns entryWrapper every { searchIteratorWrapper.next() } returns entryWrapper
every { entryWrapper.title } returns searchTerm every { entryWrapper.title } returns searchTerm
@ -119,7 +120,8 @@ internal class SearchStateTest {
every { entryWrapper.path } returns "path" every { entryWrapper.path } returns "path"
every { suggestionSearchWrapper.getResults(any(), any()) } returns searchIteratorWrapper every { suggestionSearchWrapper.getResults(any(), any()) } returns searchIteratorWrapper
val searchResultsWithTerm = SearchResultsWithTerm(searchTerm, suggestionSearchWrapper, mockk()) val searchResultsWithTerm =
SearchResultsWithTerm(searchTerm, suggestionSearchWrapper, mockk())
val searchState = SearchState(searchTerm, searchResultsWithTerm, emptyList(), FromWebView) val searchState = SearchState(searchTerm, searchResultsWithTerm, emptyList(), FromWebView)
var list: List<SearchListItem.RecentSearchListItem>? = emptyList() var list: List<SearchListItem.RecentSearchListItem>? = emptyList()
var list1: List<SearchListItem.RecentSearchListItem>? = emptyList() var list1: List<SearchListItem.RecentSearchListItem>? = emptyList()

View File

@ -50,7 +50,6 @@ import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao
import org.kiwix.kiwixmobile.core.reader.ZimFileReader import org.kiwix.kiwixmobile.core.reader.ZimFileReader
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.RecentSearchListItem import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.RecentSearchListItem
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem.ZimSearchResultListItem
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ActivityResultReceived import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ActivityResultReceived
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ClickedSearchInText import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ClickedSearchInText
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ConfirmedDelete import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ConfirmedDelete
@ -123,7 +122,6 @@ internal class SearchViewModelTest {
@Test @Test
fun `SearchState combines sources from inputs`() = fun `SearchState combines sources from inputs`() =
runTest { runTest {
val item = ZimSearchResultListItem("", "")
val searchTerm = "searchTerm" val searchTerm = "searchTerm"
val searchOrigin = FromWebView val searchOrigin = FromWebView
val suggestionSearch: SuggestionSearch = mockk() val suggestionSearch: SuggestionSearch = mockk()

View File

@ -21,7 +21,9 @@ package org.kiwix.kiwixmobile.core.search.viewmodel
import org.kiwix.libzim.SuggestionIterator import org.kiwix.libzim.SuggestionIterator
class SuggestionIteratorWrapper : SuggestionIterator() { class SuggestionIteratorWrapper : SuggestionIterator() {
override fun remove() {} override fun remove() {
// Do nothing just to ignore the EmptyFunctionBlock detekt error.
}
override fun hasNext(): Boolean = super.hasNext() override fun hasNext(): Boolean = super.hasNext()
override fun next(): SuggestionItemWrapper = super.next() as SuggestionItemWrapper override fun next(): SuggestionItemWrapper = super.next() as SuggestionItemWrapper

View File

@ -4,16 +4,16 @@ import com.android.build.gradle.internal.dsl.ProductFlavor
import custom.CustomApps import custom.CustomApps
import custom.createPublisher import custom.createPublisher
import custom.transactionWithCommit import custom.transactionWithCommit
import plugin.KiwixConfigurationPlugin
import java.net.URI
import java.net.URLDecoder
import java.util.Locale
import java.util.Base64
import java.io.FileOutputStream
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.ResponseBody import okhttp3.ResponseBody
import plugin.KiwixConfigurationPlugin
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.net.URI
import java.net.URLDecoder
import java.util.Base64
import java.util.Locale
plugins { plugins {
android android
@ -24,6 +24,7 @@ plugins.apply(KiwixConfigurationPlugin::class)
android { android {
defaultConfig { defaultConfig {
applicationId = "org.kiwix" applicationId = "org.kiwix"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
flavorDimensions += "default" flavorDimensions += "default"
@ -59,8 +60,8 @@ android {
} }
} }
fun ProductFlavor.createDownloadTask(file: File): Task { fun ProductFlavor.createDownloadTask(file: File): TaskProvider<Task> {
return tasks.create( return tasks.register(
"download${ "download${
name.replaceFirstChar { name.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else "$it" if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else "$it"
@ -156,8 +157,8 @@ fun writeZimFileDataInChunk(
outputStream?.close() outputStream?.close()
} }
fun ProductFlavor.createDownloadTaskForPlayAssetDelivery(file: File): Task { fun ProductFlavor.createDownloadTaskForPlayAssetDelivery(file: File): TaskProvider<Task> {
return tasks.create( return tasks.register(
"download${ "download${
name.replaceFirstChar { name.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else "$it" if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else "$it"
@ -205,10 +206,10 @@ val String.removeAuthenticationFromUrl: String
fun ProductFlavor.createPublishApkWithExpansionTask( fun ProductFlavor.createPublishApkWithExpansionTask(
file: File, file: File,
applicationVariants: DomainObjectSet<ApplicationVariant> applicationVariants: DomainObjectSet<ApplicationVariant>
): Task { ): TaskProvider<Task> {
val capitalizedName = val capitalizedName =
name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else "$it" } name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else "$it" }
return tasks.create("publish${capitalizedName}ReleaseApkWithExpansionFile") { return tasks.register("publish${capitalizedName}ReleaseApkWithExpansionFile") {
group = "publishing" group = "publishing"
description = "Uploads $capitalizedName to the Play Console with an Expansion file" description = "Uploads $capitalizedName to the Play Console with an Expansion file"
doLast { doLast {
@ -236,10 +237,10 @@ fun DomainObjectSet<ApplicationVariant>.releaseVariantsFor(productFlavor: Produc
.filter { !it.outputFileName.contains("universal") } .filter { !it.outputFileName.contains("universal") }
.sortedBy { it.versionCodeOverride } .sortedBy { it.versionCodeOverride }
fun ProductFlavor.createPublishBundleWithAssetPlayDelivery(): Task { fun ProductFlavor.createPublishBundleWithAssetPlayDelivery(): TaskProvider<Task> {
val capitalizedName = val capitalizedName =
name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else "$it" } name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else "$it" }
return tasks.create("publish${capitalizedName}ReleaseBundleWithPlayAssetDelivery") { return tasks.register("publish${capitalizedName}ReleaseBundleWithPlayAssetDelivery") {
group = "publishing" group = "publishing"
description = "Uploads $capitalizedName to the Play Console with an Play Asset delivery mode" description = "Uploads $capitalizedName to the Play Console with an Play Asset delivery mode"
doLast { doLast {
@ -249,7 +250,7 @@ fun ProductFlavor.createPublishBundleWithAssetPlayDelivery(): Task {
.transactionWithCommit(packageName) { .transactionWithCommit(packageName) {
val generatedBundleFile = val generatedBundleFile =
File( File(
"$buildDir/outputs/bundle/${capitalizedName.lowercase(Locale.getDefault())}" + "${layout.buildDirectory}/outputs/bundle/${capitalizedName.lowercase(Locale.getDefault())}" +
"Release/custom-${capitalizedName.lowercase(Locale.getDefault())}-release.aab" "Release/custom-${capitalizedName.lowercase(Locale.getDefault())}-release.aab"
) )
if (generatedBundleFile.exists()) { if (generatedBundleFile.exists()) {

View File

@ -1,6 +1,6 @@
#Mon Dec 19 16:13:45 IST 2022 #Mon Dec 19 16:13:45 IST 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME