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
strategy:
matrix:
api-level: [ 25, 30, 33, 34, 35 ]
api-level: [ 25, 30, 33, 34, 35, 36 ]
fail-fast: true
runs-on: ubuntu-22.04
steps:
@ -57,7 +57,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64
profile: pixel_2
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"
with:
api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64
profile: pixel_2
ram-size: 3072M
@ -118,7 +118,7 @@ jobs:
name: Automated tests for PlayStore variant
strategy:
matrix:
api-level: [ 25, 30, 33, 34, 35 ]
api-level: [ 25, 30, 33, 34, 35, 36 ]
fail-fast: true
runs-on: ubuntu-22.04
steps:
@ -163,7 +163,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64
profile: pixel_2
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"
with:
api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64
profile: pixel_2
ram-size: 2048M
@ -199,7 +199,7 @@ jobs:
name: Automated tests for Custom app
strategy:
matrix:
api-level: [ 25, 30, 33, 34, 35 ]
api-level: [ 25, 30, 33, 34, 35, 36 ]
fail-fast: true
runs-on: ubuntu-22.04
steps:
@ -244,7 +244,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64
profile: pixel_2
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"
with:
api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64
profile: pixel_2
ram-size: 2048M
@ -280,7 +280,7 @@ jobs:
name: Automated tests on Tablet
strategy:
matrix:
api-level: [ 25, 30, 33, 34, 35 ]
api-level: [ 25, 30, 33, 34, 35, 36 ]
fail-fast: true
runs-on: ubuntu-22.04
steps:
@ -325,7 +325,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64
profile: pixel_2
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"
with:
api-level: ${{ matrix.api-level }}
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
target: ${{ (matrix.api-level >= 35) && 'google_apis' || 'default' }}
arch: x86_64
profile: pixel_c
ram-size: 2048M

View File

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

View File

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

View File

@ -68,7 +68,9 @@ class DownloadRobot : BaseRobot() {
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 {
isVisible(TextId(string.your_languages))
} catch (e: RuntimeException) {

View File

@ -56,7 +56,9 @@ class InitialDownloadRobot : BaseRobot() {
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 {
isVisible(TextId(string.your_languages))
} catch (e: RuntimeException) {

View File

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

View File

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

View File

@ -135,11 +135,10 @@ class KiwixReaderFragment : CoreReaderFragment() {
// Update the reader screen title to prevent showing the previously set title
// when creating the new archive object.
updateTitle()
val filePath =
FileUtils.getLocalFilePathByUri(
requireActivity().applicationContext,
zimFileUri.toUri()
)
val filePath = FileUtils.getLocalFilePathByUri(
requireActivity().applicationContext,
zimFileUri.toUri()
)
if (filePath == null || !File(filePath).isFileExist()) {
// 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

View File

@ -42,6 +42,7 @@ import org.kiwix.kiwixmobile.BuildConfig.DEBUG
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.StorageObserver
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.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.NewBookDao
@ -475,7 +476,7 @@ class ZimManageViewModel @Inject constructor(
networkLanguageCounts: MutableMap<String, Int>,
listToActivateBy: List<Language>
) = Locale.getISOLanguages()
.map(::Locale)
.map { it.convertToLocal() }
.filter { networkLanguageCounts.containsKey(it.isO3Language) }
.map { locale ->
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 {
repositories {
google()
@ -28,6 +30,6 @@ allprojects {
}
}
tasks.create<Delete>("clean") {
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@ -11,7 +11,7 @@ repositories {
}
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("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")
@ -23,8 +23,8 @@ dependencies {
exclude(group = "com.google.guava", module = "guava")
}
implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.8")
implementation("com.googlecode.json-simple:json-simple:1.1")
implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("com.googlecode.json-simple:json-simple:1.1.1")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation(gradleApi())
implementation(localGroovy())

View File

@ -22,9 +22,9 @@ object Config {
// Here is a list of all Android versions with their corresponding API
// 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 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

View File

@ -16,7 +16,7 @@ object Versions {
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"
@ -40,7 +40,7 @@ object Versions {
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"
@ -48,7 +48,7 @@ object Versions {
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"
@ -58,7 +58,7 @@ object Versions {
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"

View File

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

View File

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

View File

@ -21,6 +21,10 @@
# Enable Wi-Fi on the emulator
adb shell svc wifi enable
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
adb logcat *:E -v color &
@ -61,6 +65,10 @@ while [ $retry -le 3 ]; do
# Enable Wi-Fi on the emulator
adb shell svc wifi enable
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
adb logcat *:E -v color &

View File

@ -3,6 +3,10 @@
# Enable Wi-Fi on the emulator
adb shell svc wifi enable
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
adb logcat *:E -v color &
@ -42,6 +46,10 @@ while [ $retry -le 3 ]; do
# Enable Wi-Fi on the emulator
adb shell svc wifi enable
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
adb logcat *:E -v color &

View File

@ -3,6 +3,10 @@
# Enable Wi-Fi on the emulator
adb shell svc wifi enable
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
adb logcat *:E -v color &
@ -42,6 +46,10 @@ while [ $retry -le 3 ]; do
# Enable Wi-Fi on the emulator
adb shell svc wifi enable
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
adb logcat *:E -v color &

View File

@ -3,6 +3,10 @@
# Enable Wi-Fi on the emulator
adb shell svc wifi enable
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
adb logcat *:E -v color &
@ -43,6 +47,10 @@ while [ $retry -le 3 ]; do
# Enable Wi-Fi on the emulator
adb shell svc wifi enable
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
adb logcat *:E -v color &

View File

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

View File

@ -52,12 +52,13 @@
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="false"
android:fullBackupContent="@xml/backup_rules"
android:hardwareAccelerated="true"
android:hasFragileUserData="true"
android:largeHeap="true"
android:requestLegacyExternalStorage="true"
android:resizeableActivity="true"
android:hasFragileUserData="true"
android:supportsRtl="true"
android:theme="@style/KiwixTheme"
tools:targetApi="tiramisu">
@ -91,6 +92,7 @@
<activity
android:name=".error.DiagnosticReportActivity"
android:exported="false" />
<service android:name=".read_aloud.ReadAloudService" />
<service
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.ResolveInfo
import android.net.ConnectivityManager
import java.util.Locale
/**
* 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 isWifi(connectivity: ConnectivityManager): Boolean
fun convertToLocal(language: String): Locale
}

View File

@ -28,7 +28,8 @@ import android.os.Build
class CompatHelper private constructor() {
// Note: Needs ": Compat" or the type system assumes `Compat21`
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()
}
@ -36,10 +37,6 @@ class CompatHelper private constructor() {
/** Singleton instance of [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
/**
@ -79,5 +76,7 @@ class CompatHelper private constructor() {
fun ConnectivityManager.isWifi(): Boolean =
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.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import java.util.Locale
open class CompatV25 : Compat {
override fun queryIntentActivities(
@ -61,4 +62,6 @@ open class CompatV25 : Compat {
return connectivity.getNetworkCapabilities(connectivity.activeNetwork)
?.hasTransport(TRANSPORT_WIFI) == true
}
override fun convertToLocal(language: String): Locale = Locale(language)
}

View File

@ -18,17 +18,17 @@
package org.kiwix.kiwixmobile.core.compat
import android.annotation.TargetApi
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PackageInfoFlags
import android.content.pm.ResolveInfo
import android.net.ConnectivityManager
import android.os.Build.VERSION_CODES.TIRAMISU
import androidx.annotation.RequiresApi
import java.util.Locale
const val API_33 = 33
@TargetApi(API_33)
@RequiresApi(TIRAMISU)
open class CompatV33 : Compat {
private val compatV25 = CompatV25()
override fun queryIntentActivities(
@ -52,4 +52,6 @@ open class CompatV33 : Compat {
override fun isWifi(connectivity: ConnectivityManager): Boolean =
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.Id
import io.objectbox.converter.PropertyConverter
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import org.kiwix.kiwixmobile.core.zim_manager.Language
import java.util.Locale
@ -35,7 +36,7 @@ data class LanguageEntity(
) {
constructor(language: Language) : this(
0,
Locale(language.languageCode),
language.languageCode.convertToLocal(),
language.active,
language.occurencesOfLanguage
)
@ -49,5 +50,5 @@ class StringToLocaleConverter : PropertyConverter<Locale, String> {
entityProperty?.isO3Language ?: Locale.ENGLISH.isO3Language
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 androidx.annotation.AttrRes
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 java.util.Locale
@ -87,14 +90,9 @@ fun Context.getResizedDrawable(resourceId: Int, width: Int, height: Int): Drawab
val drawable = ContextCompat.getDrawable(this, resourceId)
return if (drawable != null) {
val bitmap = Bitmap.createScaledBitmap(
getBitmapFromDrawable(drawable),
width,
height,
false
)
val bitmap = getBitmapFromDrawable(drawable).scale(width, height, false)
BitmapDrawable(resources, bitmap).apply {
bitmap.toDrawable(resources).apply {
bounds = drawable.bounds
}
} else {
@ -107,11 +105,7 @@ fun Context.getBitmapFromDrawable(drawable: Drawable): Bitmap {
return drawable.bitmap
}
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val bitmap = createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)

View File

@ -18,6 +18,7 @@
package org.kiwix.kiwixmobile.core.extensions
import android.annotation.SuppressLint
import android.content.Context
import android.view.ViewGroup.LayoutParams
import android.widget.EditText
@ -29,6 +30,7 @@ import org.kiwix.kiwixmobile.core.R
const val CLOSE_ICON_PADDING = 30
@SuppressLint("PrivateResource")
fun SearchView.setUpSearchView(context: Context) {
val heightAndWidth = context.resources.getDimensionPixelSize(
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.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.core.view.GravityCompat
import androidx.core.view.MenuHost
@ -2516,7 +2517,6 @@ abstract class CoreReaderFragment :
SharedPreferenceUtil.PREF_KIWIX_MOBILE,
0
)
val editor = settings.edit()
val webViewHistoryEntityList = arrayListOf<WebViewHistoryEntity>()
webViewList.forEachIndexed { index, view ->
if (view.url == null) return@forEachIndexed
@ -2525,9 +2525,10 @@ abstract class CoreReaderFragment :
withContext(Dispatchers.IO) {
repositoryActions?.saveWebViewPageHistory(webViewHistoryEntityList)
}
editor.putString(TAG_CURRENT_FILE, zimReaderContainer?.zimReaderSource?.toDatabase())
editor.putInt(TAG_CURRENT_TAB, currentWebViewIndex)
editor.apply()
settings.edit {
putString(TAG_CURRENT_FILE, zimReaderContainer?.zimReaderSource?.toDatabase())
putInt(TAG_CURRENT_TAB, currentWebViewIndex)
}
Log.d(
TAG_KIWIX,
"Save current zim file to preferences: " +

View File

@ -18,6 +18,7 @@
package org.kiwix.kiwixmobile.core.qr
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.Color
import com.google.zxing.BarcodeFormat
@ -39,6 +40,7 @@ class GenerateQR @Inject constructor() {
* @param foregroundColor The color of the QR code.
* @param backgroundColor The background color of the QR code.
*/
@SuppressLint("UseKtx")
fun createQR(
code: String,
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.DarkModeConfig
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.getVersionCode
import org.kiwix.kiwixmobile.core.dao.LibkiwixBookmarks
@ -169,9 +170,7 @@ abstract class CorePrefsFragment :
if (selectedLang == Locale.ROOT.toString()) {
getString(R.string.device_default)
} else {
Locale(
selectedLang
).displayLanguage
selectedLang.convertToLocal().displayLanguage
}
languagePref.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue ->
@ -206,7 +205,7 @@ abstract class CorePrefsFragment :
val entries = arrayOfNulls<String>(languageCodeList.size)
entries[0] = getString(R.string.device_default)
for (i in 1 until languageCodeList.size) {
val locale = Locale(languageCodeList[i])
val locale = languageCodeList[i].convertToLocal()
entries[i] = locale.displayLanguage + " (" + locale.getDisplayLanguage(locale) + ") "
}
return entries
@ -389,7 +388,7 @@ abstract class CorePrefsFragment :
}
try {
fileSelectLauncher.launch(Intent.createChooser(intent, "Select a bookmark file"))
} catch (ex: ActivityNotFoundException) {
} catch (_: ActivityNotFoundException) {
activity.toast(
resources.getString(R.string.no_app_found_to_select_bookmark_file),
Toast.LENGTH_SHORT

View File

@ -17,13 +17,15 @@
*/
package org.kiwix.kiwixmobile.core.utils
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import java.util.Locale
/**
* Created by mhutti1 on 19/04/17.
*/
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
@Suppress("MagicNumber")

View File

@ -18,6 +18,7 @@
package org.kiwix.kiwixmobile.core.utils
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import java.util.Locale
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 {
private fun chooseLanguageName(languageCode: String): String {
val displayLanguage = Locale(languageCode).displayLanguage
val displayLanguage = languageCode.convertToLocal().displayLanguage
return if (displayLanguage.length == 2 || displayLanguage.isEmpty()) {
Locale.ENGLISH.displayLanguage
} else {

View File

@ -21,6 +21,7 @@ import android.content.Context
import android.content.ContextWrapper
import android.content.SharedPreferences
import android.os.Build
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import androidx.preference.PreferenceManager
@ -136,9 +137,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
ContextWrapper(context).externalMediaDirs[0]?.path
?: context.filesDir.path // a workaround for emulators
fun getPrefStorageTitle(defaultTitle: String): String =
sharedPreferences.getString(PREF_STORAGE_TITLE, defaultTitle) ?: defaultTitle
fun putPrefBookMarkMigrated(isMigrated: Boolean) =
sharedPreferences.edit { putBoolean(PREF_BOOKMARKS_MIGRATED, isMigrated) }
@ -168,9 +166,6 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
_prefWifiOnlys.onNext(wifiOnly)
}
fun putPrefStorageTitle(storageTitle: String) =
sharedPreferences.edit { putString(PREF_STORAGE_TITLE, storageTitle) }
fun putPrefStorage(storage: String) {
sharedPreferences.edit { putString(PREF_STORAGE, storage) }
_prefStorages.onNext(storage)
@ -294,9 +289,11 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
path.substringBefore(context.getString(R.string.android_directory_seperator))
}
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
fun isPlayStoreBuildWithAndroid11OrAbove(): Boolean =
isPlayStoreBuild && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
fun isNotPlayStoreBuildWithAndroid11OrAbove(): Boolean =
!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_FULLSCREEN = "pref_fullscreen"
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_SHOW_STORAGE_OPTION = "show_storgae_option"
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 kotlinx.parcelize.Parcelize
import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.convertToLocal
import java.util.Locale
@Parcelize
@ -51,7 +52,7 @@ data class Language constructor(
languageCode: String,
active: Boolean,
occurrencesOfLanguage: Int
) : this(Locale(languageCode), active, occurrencesOfLanguage)
) : this(languageCode.convertToLocal(), active, occurrencesOfLanguage)
override fun equals(other: Any?): Boolean =
(other as Language).language == language && other.active == active

View File

@ -18,6 +18,7 @@
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.DownloadRoomEntity
import org.kiwix.kiwixmobile.core.entity.LibraryNetworkEntity
@ -50,7 +51,7 @@ sealed class BooksOnDiskListItem {
override val id: Long = databaseId
) : BooksOnDiskListItem() {
val locale: Locale by lazy {
Locale(book.language)
book.language.convertToLocal()
}
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()
every { box.query().build() } returns query
val zimReaderSourceThatExists = mockk<ZimReaderSource>()

View File

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

View File

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

View File

@ -43,8 +43,9 @@ internal class SearchStateTest {
val estimatedMatches = 100
every { suggestionSearchWrapper.estimatedMatches } returns estimatedMatches.toLong()
// 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,
// causing our test case to get stuck in an infinite loop due to this explicit setting.
// Otherwise, if we do not set this, the method will always return true when
// 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.next() } returns entryWrapper
every { entryWrapper.title } returns searchTerm
@ -119,7 +120,8 @@ internal class SearchStateTest {
every { entryWrapper.path } returns "path"
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)
var list: 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.ZimReaderContainer
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.ClickedSearchInText
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ConfirmedDelete
@ -123,7 +122,6 @@ internal class SearchViewModelTest {
@Test
fun `SearchState combines sources from inputs`() =
runTest {
val item = ZimSearchResultListItem("", "")
val searchTerm = "searchTerm"
val searchOrigin = FromWebView
val suggestionSearch: SuggestionSearch = mockk()

View File

@ -21,7 +21,9 @@ package org.kiwix.kiwixmobile.core.search.viewmodel
import org.kiwix.libzim.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 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.createPublisher
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.Request
import okhttp3.ResponseBody
import plugin.KiwixConfigurationPlugin
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 {
android
@ -24,6 +24,7 @@ plugins.apply(KiwixConfigurationPlugin::class)
android {
defaultConfig {
applicationId = "org.kiwix"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
flavorDimensions += "default"
@ -59,8 +60,8 @@ android {
}
}
fun ProductFlavor.createDownloadTask(file: File): Task {
return tasks.create(
fun ProductFlavor.createDownloadTask(file: File): TaskProvider<Task> {
return tasks.register(
"download${
name.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else "$it"
@ -156,8 +157,8 @@ fun writeZimFileDataInChunk(
outputStream?.close()
}
fun ProductFlavor.createDownloadTaskForPlayAssetDelivery(file: File): Task {
return tasks.create(
fun ProductFlavor.createDownloadTaskForPlayAssetDelivery(file: File): TaskProvider<Task> {
return tasks.register(
"download${
name.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else "$it"
@ -205,10 +206,10 @@ val String.removeAuthenticationFromUrl: String
fun ProductFlavor.createPublishApkWithExpansionTask(
file: File,
applicationVariants: DomainObjectSet<ApplicationVariant>
): Task {
): TaskProvider<Task> {
val capitalizedName =
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"
description = "Uploads $capitalizedName to the Play Console with an Expansion file"
doLast {
@ -236,10 +237,10 @@ fun DomainObjectSet<ApplicationVariant>.releaseVariantsFor(productFlavor: Produc
.filter { !it.outputFileName.contains("universal") }
.sortedBy { it.versionCodeOverride }
fun ProductFlavor.createPublishBundleWithAssetPlayDelivery(): Task {
fun ProductFlavor.createPublishBundleWithAssetPlayDelivery(): TaskProvider<Task> {
val capitalizedName =
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"
description = "Uploads $capitalizedName to the Play Console with an Play Asset delivery mode"
doLast {
@ -249,7 +250,7 @@ fun ProductFlavor.createPublishBundleWithAssetPlayDelivery(): Task {
.transactionWithCommit(packageName) {
val generatedBundleFile =
File(
"$buildDir/outputs/bundle/${capitalizedName.lowercase(Locale.getDefault())}" +
"${layout.buildDirectory}/outputs/bundle/${capitalizedName.lowercase(Locale.getDefault())}" +
"Release/custom-${capitalizedName.lowercase(Locale.getDefault())}-release.aab"
)
if (generatedBundleFile.exists()) {

View File

@ -1,6 +1,6 @@
#Mon Dec 19 16:13:45 IST 2022
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
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME