diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 96e4cc848..ae0dbaa7e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -12,7 +12,7 @@ jobs:
name: Automated tests
strategy:
matrix:
- api-level: [ 25, 30, 33, 34 ]
+ api-level: [ 25, 30, 33, 34, 35 ]
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: 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: 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 ]
+ api-level: [ 25, 30, 33, 34, 35 ]
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: 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: 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 ]
+ api-level: [ 25, 30, 33, 34, 35 ]
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: 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: 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: [ 30, 33, 34 ]
+ api-level: [ 25, 30, 33, 34, 35 ]
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: 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: default
+ target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
arch: x86_64
profile: pixel_c
ram-size: 2048M
diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/localLibrary/OpeningFilesFromStorageTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/localLibrary/OpeningFilesFromStorageTest.kt
index d39ade22e..cc0979e1a 100644
--- a/app/src/androidTest/java/org/kiwix/kiwixmobile/localLibrary/OpeningFilesFromStorageTest.kt
+++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/localLibrary/OpeningFilesFromStorageTest.kt
@@ -133,7 +133,9 @@ class OpeningFilesFromStorageTest : BaseActivityTest() {
@Test
fun testOpeningFileFromFileManager() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
+ Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM
+ ) {
activityScenario.onActivity {
kiwixMainActivity = it
it.navigate(R.id.libraryFragment)
diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/testutils/TestUtils.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/testutils/TestUtils.kt
index 3b7127692..c6c55613c 100644
--- a/app/src/androidTest/java/org/kiwix/kiwixmobile/testutils/TestUtils.kt
+++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/testutils/TestUtils.kt
@@ -25,7 +25,6 @@ import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.os.Build
import android.os.Environment
-import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.test.core.app.canTakeScreenshot
import androidx.test.core.app.takeScreenshot
@@ -91,10 +90,6 @@ object TestUtils {
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
- @RequiresApi(Build.VERSION_CODES.R)
- private fun hasManageExternalStoragePermission(): Boolean =
- Environment.isExternalStorageManager()
-
@JvmStatic fun hasStoragePermission() = Build.VERSION.SDK_INT > Build.VERSION_CODES.M &&
hasReadExternalStoragePermission() && hasWriteExternalStoragePermission()
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt
index 41bc521db..5a3d644a1 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/main/KiwixMainActivity.kt
@@ -51,6 +51,7 @@ import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DOWNLOAD_NOTIFICATION_TITLE
+import org.kiwix.kiwixmobile.core.extensions.applyEdgeToEdgeInsets
import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.main.ACTION_NEW_TAB
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
@@ -122,10 +123,12 @@ class KiwixMainActivity : CoreMainActivity() {
setContentView(activityKiwixMainBinding.root)
navController.addOnDestinationChangedListener(finishActionModeOnDestinationChange)
- activityKiwixMainBinding.drawerNavView.setupWithNavController(navController)
- activityKiwixMainBinding.drawerNavView.setNavigationItemSelectedListener { item ->
- closeNavigationDrawer()
- onNavigationItemSelected(item)
+ activityKiwixMainBinding.drawerNavView.apply {
+ setupWithNavController(navController)
+ setNavigationItemSelectedListener { item ->
+ closeNavigationDrawer()
+ onNavigationItemSelected(item)
+ }
}
activityKiwixMainBinding.bottomNavView.setupWithNavController(navController)
lifecycleScope.launch {
@@ -134,6 +137,7 @@ class KiwixMainActivity : CoreMainActivity() {
handleZimFileIntent(intent)
handleNotificationIntent(intent)
handleGetContentIntent(intent)
+ activityKiwixMainBinding.root.applyEdgeToEdgeInsets()
}
private suspend fun migrateInternalToPublicAppDirectory() {
diff --git a/app/src/main/res/layout/activity_kiwix_main.xml b/app/src/main/res/layout/activity_kiwix_main.xml
index 9679f3981..111f83368 100644
--- a/app/src/main/res/layout/activity_kiwix_main.xml
+++ b/app/src/main/res/layout/activity_kiwix_main.xml
@@ -21,8 +21,7 @@
android:id="@+id/navigation_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:contentDescription="@string/open_drawer"
- android:fitsSystemWindows="true">
+ android:contentDescription="@string/open_drawer">
@@ -62,7 +60,6 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
- android:fitsSystemWindows="true"
app:headerLayout="@layout/drawer_right" />
diff --git a/app/src/main/res/layout/activity_language.xml b/app/src/main/res/layout/activity_language.xml
index 00aed9c19..d1bc2a370 100644
--- a/app/src/main/res/layout/activity_language.xml
+++ b/app/src/main/res/layout/activity_language.xml
@@ -1,24 +1,24 @@
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ app:layout_constraintTop_toBottomOf="@id/app_bar"
+ android:clipToPadding="false"
+ tools:listitem="@layout/item_language" />
diff --git a/app/src/main/res/layout/fragment_destination_download.xml b/app/src/main/res/layout/fragment_destination_download.xml
index f07c90f28..2714feb64 100644
--- a/app/src/main/res/layout/fragment_destination_download.xml
+++ b/app/src/main/res/layout/fragment_destination_download.xml
@@ -49,6 +49,7 @@
android:layout_height="match_parent"
android:contentDescription="@string/library"
android:scrollbars="vertical"
+ android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/item_download" />
diff --git a/app/src/main/res/layout/fragment_destination_library.xml b/app/src/main/res/layout/fragment_destination_library.xml
index 31086b8dd..462076d01 100644
--- a/app/src/main/res/layout/fragment_destination_library.xml
+++ b/app/src/main/res/layout/fragment_destination_library.xml
@@ -21,8 +21,7 @@
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"
- android:fitsSystemWindows="true">
+ android:layout_height="match_parent">
@@ -80,6 +79,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
+ android:clipToPadding="false"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -161,6 +161,7 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@android:color/transparent"
+ android:clipToPadding="false"
android:contentDescription="@string/files_for_transfer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt
index c4d3c09db..333c6b315 100644
--- a/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt
+++ b/app/src/test/java/org/kiwix/kiwixmobile/zimManager/ZimManageViewModelTest.kt
@@ -22,6 +22,7 @@ import android.app.Application
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.os.Build
import com.jraska.livedata.test
import io.mockk.clearAllMocks
import io.mockk.every
@@ -51,6 +52,8 @@ import org.kiwix.kiwixmobile.core.utils.files.ScanningProgressListener
import org.kiwix.kiwixmobile.core.zim_manager.ConnectivityBroadcastReceiver
import org.kiwix.kiwixmobile.core.zim_manager.Language
import org.kiwix.kiwixmobile.core.zim_manager.NetworkState
+import org.kiwix.kiwixmobile.core.zim_manager.NetworkState.CONNECTED
+import org.kiwix.kiwixmobile.core.zim_manager.NetworkState.NOT_CONNECTED
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode.MULTI
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode.NORMAL
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
@@ -58,8 +61,6 @@ import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDis
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CanWrite4GbFile
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CannotWrite4GbFile
-import org.kiwix.kiwixmobile.core.zim_manager.NetworkState.CONNECTED
-import org.kiwix.kiwixmobile.core.zim_manager.NetworkState.NOT_CONNECTED
import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.FileSelectActions.MultiModeFinished
import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.FileSelectActions.RequestDeleteMultiSelection
import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.FileSelectActions.RequestMultiSelection
@@ -139,7 +140,12 @@ class ZimManageViewModelTest {
every { newLanguagesDao.languages() } returns languages
every { fat32Checker.fileSystemStates } returns fileSystemStates
every { connectivityBroadcastReceiver.networkStates } returns networkStates
- every { application.registerReceiver(any(), any()) } returns mockk()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ every { application.registerReceiver(any(), any(), any()) } returns mockk()
+ } else {
+ @Suppress("UnspecifiedRegisterReceiverFlag")
+ every { application.registerReceiver(any(), any()) } returns mockk()
+ }
every { dataSource.booksOnDiskAsListItems() } returns booksOnDiskListItems
every {
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
@@ -167,8 +173,15 @@ class ZimManageViewModelTest {
inner class Context {
@Test
fun `registers broadcastReceiver in init`() {
- verify {
- application.registerReceiver(connectivityBroadcastReceiver, any())
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ verify {
+ application.registerReceiver(connectivityBroadcastReceiver, any(), any())
+ }
+ } else {
+ @Suppress("UnspecifiedRegisterReceiverFlag")
+ verify {
+ application.registerReceiver(connectivityBroadcastReceiver, any())
+ }
}
}
diff --git a/build.gradle.kts b/build.gradle.kts
index 792b02bbe..e5c5dbcd0 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -7,6 +7,7 @@ buildscript {
dependencies {
classpath(Libs.com_android_tools_build_gradle)
classpath(Libs.kotlin_gradle_plugin)
+ classpath(Libs.kotlin_ksp)
classpath(Libs.navigation_safe_args_gradle_plugin)
classpath(Libs.keeper)
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index b174f55a7..bb0074ed9 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -11,8 +11,9 @@ repositories {
}
dependencies {
- implementation("com.android.tools.build:gradle:8.1.3")
+ implementation("com.android.tools.build:gradle:8.7.2")
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")
implementation("org.jlleitschuh.gradle:ktlint-gradle:10.3.0")
implementation("com.google.apis:google-api-services-androidpublisher:v3-rev20230406-2.0.0") {
diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt
index e6f6facfe..75eb961cf 100644
--- a/buildSrc/src/main/kotlin/Config.kt
+++ b/buildSrc/src/main/kotlin/Config.kt
@@ -22,11 +22,11 @@ object Config {
// Here is a list of all Android versions with their corresponding API
// levels: https://apilevels.com/
- const val compileSdk = 34 // SDK version used by Gradle to compile our app.
+ const val compileSdk = 35 // 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 = 34 // Target SDK (Maximum Support Device) is 34 (Android 14).
+ const val targetSdk = 35 // Target SDK (Maximum Support Device) is 34 (Android 14).
- val javaVersion = JavaVersion.VERSION_1_8
+ val javaVersion = JavaVersion.VERSION_17
// Version Information
const val versionMajor = 3 // Major version component of the app's version name and version code.
diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt
index 232bb99d5..8de3a54d4 100644
--- a/buildSrc/src/main/kotlin/Libs.kt
+++ b/buildSrc/src/main/kotlin/Libs.kt
@@ -1,5 +1,3 @@
-import io.opencensus.trace.Tracing
-
/**
* Generated by https://github.com/jmfayard/buildSrcVersions
*
@@ -104,6 +102,9 @@ object Libs {
const val kotlin_stdlib_jdk8: String = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:" +
Versions.org_jetbrains_kotlin
+ const val kotlin_ksp: String =
+ "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:" + Versions.kotlin_ksp
+
/**
* https://developer.android.com/topic/libraries/architecture/index.html
*/
@@ -305,6 +306,8 @@ object Libs {
*/
const val core_ktx: String = "androidx.core:core-ktx:" + Versions.core_ktx
+ const val androidx_activity: String = "androidx.activity:activity:" + Versions.androidx_activity
+
/**
* https://github.com/kiwix/java-libkiwix
*/
diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt
index c23818e29..fa2651dbc 100644
--- a/buildSrc/src/main/kotlin/Versions.kt
+++ b/buildSrc/src/main/kotlin/Versions.kt
@@ -14,13 +14,13 @@ object Versions {
const val document_file_version: String = "1.0.1"
- const val org_jetbrains_kotlinx_kotlinx_coroutines: String = "1.8.1"
+ const val org_jetbrains_kotlinx_kotlinx_coroutines: String = "1.10.1"
const val kotlinx_coroutines_rx3: String = "1.3.9"
- const val androidx_test_espresso: String = "3.5.1"
+ const val androidx_test_espresso: String = "3.6.1"
- const val tracing: String = "1.1.0"
+ const val tracing: String = "1.2.0"
const val com_squareup_retrofit2: String = "2.11.0"
@@ -28,6 +28,8 @@ object Versions {
const val org_jetbrains_kotlin: String = "2.0.0"
+ const val kotlin_ksp: String = "2.0.0-1.0.24"
+
const val androidx_navigation: String = "2.5.3"
const val navigation_ui_ktx: String = "2.4.1"
@@ -46,7 +48,7 @@ object Versions {
const val android_arch_lifecycle_extensions: String = "1.1.1"
- const val com_android_tools_build_gradle: String = "8.1.3"
+ const val com_android_tools_build_gradle: String = "8.7.2"
const val de_fayard_buildsrcversions_gradle_plugin: String = "0.7.0"
@@ -60,9 +62,9 @@ object Versions {
const val swipe_refresh_layout: String = "1.1.0"
- const val collection_ktx: String = "1.1.0"
+ const val collection_ktx: String = "1.4.5"
- const val preference_ktx: String = "1.2.0"
+ const val preference_ktx: String = "1.2.1"
const val junit_jupiter: String = "5.11.0"
@@ -70,7 +72,7 @@ object Versions {
const val core_testing: String = "2.2.0"
- const val fragment_ktx: String = "1.2.5"
+ const val fragment_ktx: String = "1.8.5"
const val testing_ktx: String = "1.3.0"
@@ -86,11 +88,13 @@ object Versions {
const val rxandroid: String = "2.1.1"
- const val core_ktx: String = "1.9.0"
+ const val core_ktx: String = "1.15.0"
+
+ const val androidx_activity: String = "1.9.3"
const val libkiwix: String = "2.2.3"
- const val material: String = "1.8.0"
+ const val material: String = "1.12.0"
const val multidex: String = "2.0.1"
@@ -98,13 +102,13 @@ object Versions {
const val rxjava: String = "2.2.21"
- const val webkit: String = "1.11.0"
+ const val webkit: String = "1.12.1"
const val junit: String = "1.1.5"
const val material_show_case_view: String = "1.3.7"
- const val roomVersion = "2.5.0"
+ const val roomVersion = "2.5.2"
const val zxing = "3.5.3"
diff --git a/buildSrc/src/main/kotlin/plugin/AllProjectConfigurer.kt b/buildSrc/src/main/kotlin/plugin/AllProjectConfigurer.kt
index a87f9f4d3..ed110b738 100644
--- a/buildSrc/src/main/kotlin/plugin/AllProjectConfigurer.kt
+++ b/buildSrc/src/main/kotlin/plugin/AllProjectConfigurer.kt
@@ -37,6 +37,7 @@ class AllProjectConfigurer {
fun applyPlugins(target: Project) {
target.plugins.apply("kotlin-android")
target.plugins.apply("kotlin-kapt")
+ target.plugins.apply("com.google.devtools.ksp")
target.plugins.apply("kotlin-parcelize")
target.plugins.apply("jacoco")
target.plugins.apply("org.jlleitschuh.gradle.ktlint")
@@ -77,7 +78,7 @@ class AllProjectConfigurer {
}
target.tasks.withType(KotlinCompile::class.java) {
compilerOptions {
- jvmTarget.set(JvmTarget.JVM_1_8)
+ jvmTarget.set(JvmTarget.JVM_17)
freeCompilerArgs.add("-Xjvm-default=all-compatibility")
}
}
@@ -137,7 +138,7 @@ class AllProjectConfigurer {
}
fun configureCommonExtension(target: Project) {
- target.configureExtension> {
+ target.configureExtension> {
lint {
abortOnError = true
checkAllWarnings = true
@@ -231,7 +232,9 @@ class AllProjectConfigurer {
implementation(Libs.roomRxjava2)
kapt(Libs.roomCompiler)
implementation(Libs.tracing)
+ implementation(Libs.fetch)
implementation(Libs.fetchOkhttp)
+ implementation(Libs.androidx_activity)
}
}
}
diff --git a/buildSrc/src/main/kotlin/plugin/ConvenienceExtensions.kt b/buildSrc/src/main/kotlin/plugin/ConvenienceExtensions.kt
index 0a397888d..fb6f64721 100644
--- a/buildSrc/src/main/kotlin/plugin/ConvenienceExtensions.kt
+++ b/buildSrc/src/main/kotlin/plugin/ConvenienceExtensions.kt
@@ -48,6 +48,9 @@ internal fun DependencyHandlerScope.compileOnly(dependency: String) =
internal fun DependencyHandlerScope.kapt(dependency: String) =
addDependency("kapt", dependency)
+internal fun DependencyHandlerScope.ksp(dependency: String) =
+ addDependency("ksp", dependency)
+
internal fun DependencyHandlerScope.testImplementation(dependency: String) =
addDependency("testImplementation", dependency)
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index 1d61d4aee..20216805f 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -63,8 +63,4 @@ dependencies {
implementation(Libs.kotlinx_coroutines_android)
implementation(Libs.kotlinx_coroutines_rx3)
implementation(Libs.zxing)
- api(Libs.fetch) {
- // Todo: Will remove this when we add support for Android 15
- exclude("androidx.core", "core-ktx")
- }
}
diff --git a/core/src/main/java/eu/mhutti1/utils/storage/StorageDeviceUtils.kt b/core/src/main/java/eu/mhutti1/utils/storage/StorageDeviceUtils.kt
index e2b56964d..62ccb01af 100644
--- a/core/src/main/java/eu/mhutti1/utils/storage/StorageDeviceUtils.kt
+++ b/core/src/main/java/eu/mhutti1/utils/storage/StorageDeviceUtils.kt
@@ -21,12 +21,10 @@ package eu.mhutti1.utils.storage
import android.content.Context
import android.content.ContextWrapper
import android.os.Environment
-import androidx.core.content.ContextCompat
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import java.io.File
import java.io.FileFilter
import java.io.RandomAccessFile
-import java.util.ArrayList
object StorageDeviceUtils {
@JvmStatic
@@ -63,7 +61,7 @@ object StorageDeviceUtils {
private fun externalFilesDirsDevices(
context: Context,
writable: Boolean
- ) = ContextCompat.getExternalFilesDirs(context, "")
+ ) = context.getExternalFilesDirs("")
.filterNotNull()
.mapIndexed { index, dir -> StorageDevice(generalisePath(dir.path, writable), index == 0) }
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/CoreApp.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/CoreApp.kt
index 3fc9a0330..73c208b35 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/CoreApp.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/CoreApp.kt
@@ -116,6 +116,9 @@ abstract class CoreApp : Application() {
detectLeakedSqlLiteObjects()
penaltyLog()
detectLeakedRegistrationObjects()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ detectUnsafeIntentLaunch()
+ }
}.build()
)
}
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/base/BaseActivity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/base/BaseActivity.kt
index 718f6476c..a6ffee420 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/base/BaseActivity.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/base/BaseActivity.kt
@@ -17,8 +17,13 @@
*/
package org.kiwix.kiwixmobile.core.base
+import android.graphics.Color
+import android.os.Build
import android.os.Bundle
+import androidx.activity.SystemBarStyle
+import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
+import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.setWindowBackgroundColorForAndroid15AndAbove
import org.kiwix.kiwixmobile.core.utils.LanguageUtils
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import javax.inject.Inject
@@ -29,7 +34,14 @@ open class BaseActivity : AppCompatActivity() {
lateinit var sharedPreferenceUtil: SharedPreferenceUtil
override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge(
+ statusBarStyle = SystemBarStyle.dark(Color.BLACK),
+ navigationBarStyle = SystemBarStyle.dark(Color.BLACK)
+ )
super.onCreate(savedInstanceState)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ setWindowBackgroundColorForAndroid15AndAbove()
+ }
LanguageUtils.handleLocaleChange(this, sharedPreferenceUtil)
}
}
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/base/BaseFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/base/BaseFragment.kt
index 77e18b557..c6d4e7e2d 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/base/BaseFragment.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/base/BaseFragment.kt
@@ -19,13 +19,16 @@
package org.kiwix.kiwixmobile.core.base
import android.content.Context
+import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import org.kiwix.kiwixmobile.core.R
+import org.kiwix.kiwixmobile.core.extensions.enableEdgeToEdgeMode
import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon
+import org.kiwix.kiwixmobile.core.extensions.setFragmentBackgroundColorForAndroid15AndAbove
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
/**
@@ -46,7 +49,11 @@ abstract class BaseFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
+ enableEdgeToEdgeMode()
setupToolbar()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ setFragmentBackgroundColorForAndroid15AndAbove()
+ }
}
// Setup toolbar to handle common back pressed event
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/base/adapter/AdapterDelegateManager.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/base/adapter/AdapterDelegateManager.kt
index 82a657fa4..4b6d30687 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/base/adapter/AdapterDelegateManager.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/base/adapter/AdapterDelegateManager.kt
@@ -45,7 +45,7 @@ class AdapterDelegateManager {
private fun getDelegateIndexFor(item: T): Int {
for (index in 0..delegates.size()) {
val valueAt = delegates.valueAt(index)
- if (valueAt?.isFor(item) == true) {
+ if (valueAt.isFor(item) == true) {
return index
}
}
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/error/ErrorActivity.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/error/ErrorActivity.kt
index 2cd0d1f1f..bf504906f 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/error/ErrorActivity.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/error/ErrorActivity.kt
@@ -24,7 +24,6 @@ import android.os.Build
import android.os.Bundle
import android.os.Process
import androidx.activity.result.contract.ActivityResultContracts
-import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
@@ -37,6 +36,7 @@ import org.kiwix.kiwixmobile.core.compat.CompatHelper.Companion.queryIntentActiv
import org.kiwix.kiwixmobile.core.compat.ResolveInfoFlagsCompat
import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.databinding.ActivityKiwixErrorBinding
+import org.kiwix.kiwixmobile.core.extensions.applyEdgeToEdgeInsets
import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
import org.kiwix.kiwixmobile.core.utils.CRASH_AND_FEEDBACK_EMAIL_ADDRESS
@@ -86,6 +86,7 @@ open class ErrorActivity : BaseActivity() {
}
setupReportButton()
activityKiwixErrorBinding?.restartButton?.setOnClickListener { restartApp() }
+ activityKiwixErrorBinding?.root.applyEdgeToEdgeInsets()
}
override fun onDestroy() {
@@ -101,7 +102,7 @@ open class ErrorActivity : BaseActivity() {
val targetedIntents = createEmailIntents(emailIntent, activities)
if (activities.isNotEmpty() && targetedIntents.isNotEmpty()) {
val chooserIntent =
- Intent.createChooser(targetedIntents.removeFirst(), "Send email...")
+ Intent.createChooser(targetedIntents.removeAt(0), "Send email...")
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
sendEmailLauncher.launch(chooserIntent)
} else {
@@ -245,7 +246,7 @@ open class ErrorActivity : BaseActivity() {
""".trimIndent()
private fun externalFileDetails(): String =
- ContextCompat.getExternalFilesDirs(this, null).joinToString("\n") { it?.path ?: "null" }
+ getExternalFilesDirs(null).joinToString("\n") { it?.path ?: "null" }
private fun safeContains(extras: Bundle): Boolean {
return try {
@@ -272,7 +273,7 @@ open class ErrorActivity : BaseActivity() {
private val versionName: String
@SuppressLint("WrongConstant")
get() = packageManager
- .getPackageInformation(packageName, ZERO).versionName
+ .getPackageInformation(packageName, ZERO).versionName.toString()
private fun toStackTraceString(exception: Throwable): String =
try {
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt
index 7d4f2515c..bc8753e0a 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ActivityExtensions.kt
@@ -24,11 +24,13 @@ import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
+import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.view.Menu
import android.view.MenuItem
+import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.Toolbar
@@ -199,4 +201,18 @@ object ActivityExtensions {
val isWideEnough = configuration.smallestScreenWidthDp >= 600
return isLargeOrXLarge && isWideEnough
}
+
+ /**
+ * Sets the window background color to black for Android 15 and above.
+ *
+ * In Android 15, the `setStatusBarColor` method is deprecated and no longer functional.
+ * As a workaround, this method sets the window background color to black because the
+ * status bar and navigation bar now inherit the background color of the window.
+ *
+ * @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ fun Activity.setWindowBackgroundColorForAndroid15AndAbove() {
+ window.decorView.setBackgroundColor(Color.BLACK)
+ }
}
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/FragmentExtensions.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/FragmentExtensions.kt
index 2f7dd640c..0fe2afb55 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/FragmentExtensions.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/FragmentExtensions.kt
@@ -19,15 +19,22 @@
package org.kiwix.kiwixmobile.core.extensions
import android.content.Context
+import android.graphics.Color
+import android.os.Build
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
+import androidx.annotation.RequiresApi
import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
+import com.google.android.material.color.MaterialColors
+import org.kiwix.kiwixmobile.core.CoreApp
+import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.setWindowBackgroundColorForAndroid15AndAbove
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
inline fun Fragment.viewModel(
@@ -55,3 +62,29 @@ fun View.closeKeyboard() {
}
val Fragment.coreMainActivity get() = activity as CoreMainActivity
+
+/**
+ * It enables the edge to edge mode for fragments.
+ */
+fun Fragment.enableEdgeToEdgeMode() {
+ activity?.window?.let {
+ WindowCompat.setDecorFitsSystemWindows(it, false)
+ }
+}
+
+/**
+ * We are changing the fragment's background color for android 15 and above.
+ * @see setWindowBackgroundColorForAndroid15AndAbove for more details.
+ */
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+fun Fragment.setFragmentBackgroundColorForAndroid15AndAbove() {
+ this.view?.let {
+ val darkModeActivity = CoreApp.instance.darkModeConfig.isDarkModeActive()
+ val windowBackGroundColor = if (darkModeActivity) {
+ MaterialColors.getColor(it.context, android.R.attr.windowBackground, Color.BLACK)
+ } else {
+ MaterialColors.getColor(it.context, android.R.attr.windowBackground, Color.WHITE)
+ }
+ it.setBackgroundColor(windowBackGroundColor)
+ }
+}
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ViewExtensions.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ViewExtensions.kt
index 9584c3228..5b5e3255c 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ViewExtensions.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/extensions/ViewExtensions.kt
@@ -21,13 +21,16 @@ package org.kiwix.kiwixmobile.core.extensions
import android.annotation.SuppressLint
import android.os.Build
import android.view.View
+import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import androidx.annotation.ColorInt
import androidx.appcompat.widget.TooltipCompat
+import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
+import androidx.core.view.updateLayoutParams
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
@@ -92,7 +95,7 @@ fun View.showFullScreenMode(window: Window) {
WindowCompat.setDecorFitsSystemWindows(window, false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowInsetsControllerCompat(window, window.decorView).apply {
- hide(WindowInsetsCompat.Type.statusBars())
+ hide(WindowInsetsCompat.Type.systemBars())
hide(WindowInsetsCompat.Type.displayCutout())
systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
@@ -106,10 +109,10 @@ fun View.showFullScreenMode(window: Window) {
}
fun View.closeFullScreenMode(window: Window) {
- WindowCompat.setDecorFitsSystemWindows(window, true)
+ WindowCompat.setDecorFitsSystemWindows(window, false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowInsetsControllerCompat(window, window.decorView).apply {
- show(WindowInsetsCompat.Type.statusBars())
+ show(WindowInsetsCompat.Type.systemBars())
show(WindowInsetsCompat.Type.displayCutout())
}
}
@@ -119,3 +122,31 @@ fun View.closeFullScreenMode(window: Window) {
clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
}
+
+/**
+ * Applies edge-to-edge insets to the current view by adjusting its margins
+ * to account for system bars and display cutouts (e.g., status bar, navigation bar, and notches).
+ *
+ * This method ensures that the view avoids overlapping with system UI components by dynamically
+ * setting margins based on the insets provided by the system.
+ *
+ * Usage: Call this method on any view to apply edge-to-edge handling.
+ */
+fun View?.applyEdgeToEdgeInsets() {
+ this?.let {
+ ViewCompat.setOnApplyWindowInsetsListener(it) { view, windowInsets ->
+ val systemBarsInsets =
+ windowInsets.getInsets(
+ WindowInsetsCompat.Type.displayCutout() or
+ WindowInsetsCompat.Type.systemBars()
+ )
+ view.updateLayoutParams {
+ topMargin = systemBarsInsets.top
+ leftMargin = systemBarsInsets.left
+ bottomMargin = systemBarsInsets.bottom
+ rightMargin = systemBarsInsets.right
+ }
+ WindowInsetsCompat.CONSUMED
+ }
+ }
+}
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt
index 055f1d909..5a353ce2f 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/main/CoreReaderFragment.kt
@@ -1869,9 +1869,9 @@ abstract class CoreReaderFragment :
}
reopenBook()
showTabSwitcher()
- setUpWithTextToSpeech(tempWebViewListForUndo.last())
+ setUpWithTextToSpeech(tempWebViewListForUndo[tempWebViewListForUndo.lastIndex])
updateBottomToolbarVisibility()
- safelyAddWebView(tempWebViewListForUndo.last())
+ safelyAddWebView(tempWebViewListForUndo[tempWebViewListForUndo.lastIndex])
}
}
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.kt
index ac8638fd1..8278b8128 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/settings/CorePrefsFragment.kt
@@ -229,7 +229,7 @@ abstract class CorePrefsFragment :
@Suppress("TooGenericExceptionThrown")
get() = try {
requireActivity().packageManager
- .getPackageInformation(requireActivity().packageName, 0).versionName
+ .getPackageInformation(requireActivity().packageName, 0).versionName.toString()
} catch (e: PackageManager.NameNotFoundException) {
throw RuntimeException(e)
}
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt
index 1e261a2fa..5556425b9 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/SharedPreferenceUtil.kt
@@ -129,7 +129,7 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
get() = sharedPreferences.getInt(STORAGE_POSITION, 0)
fun defaultStorage(): String =
- getExternalFilesDirs(context, null)[0]?.path
+ context.getExternalFilesDirs(null)[0]?.path
?: context.filesDir.path // a workaround for emulators
fun defaultPublicStorage(): String =
diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt
index db3621797..b4d482946 100644
--- a/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt
+++ b/core/src/main/java/org/kiwix/kiwixmobile/core/utils/files/FileUtils.kt
@@ -30,7 +30,6 @@ import android.os.storage.StorageManager
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.webkit.URLUtil
-import androidx.core.content.ContextCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -583,7 +582,7 @@ object FileUtils {
@JvmStatic
fun getDemoFilePathForCustomApp(context: Context) =
- "${ContextCompat.getExternalFilesDirs(context, null)[0]}/demo.zim"
+ "${context.getExternalFilesDirs(null)[0]}/demo.zim"
@SuppressLint("Recycle")
@JvmStatic
diff --git a/core/src/main/res/layout/dialog_navigation_history.xml b/core/src/main/res/layout/dialog_navigation_history.xml
index 715a74e0d..153001b26 100644
--- a/core/src/main/res/layout/dialog_navigation_history.xml
+++ b/core/src/main/res/layout/dialog_navigation_history.xml
@@ -18,10 +18,10 @@
-->
@@ -30,19 +30,20 @@
android:id="@+id/navigationHistoryRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- tools:listitem="@layout/item_bookmark_history"
- app:layout_constraintTop_toBottomOf="@+id/app_bar" />
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/app_bar"
+ tools:listitem="@layout/item_bookmark_history" />
+ app:layout_constraintTop_toTopOf="parent" />
diff --git a/core/src/main/res/layout/drawer_right.xml b/core/src/main/res/layout/drawer_right.xml
index e1170eccb..fc5003d0c 100644
--- a/core/src/main/res/layout/drawer_right.xml
+++ b/core/src/main/res/layout/drawer_right.xml
@@ -4,5 +4,5 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
- android:fitsSystemWindows="true"
+ android:clipToPadding="false"
android:focusable="true" />
diff --git a/core/src/main/res/layout/fragment_help.xml b/core/src/main/res/layout/fragment_help.xml
index 158477da0..e25e528db 100644
--- a/core/src/main/res/layout/fragment_help.xml
+++ b/core/src/main/res/layout/fragment_help.xml
@@ -4,7 +4,6 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:fitsSystemWindows="true"
tools:context=".help.HelpFragment">
@@ -45,6 +44,7 @@
android:id="@+id/activity_help_recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
+ android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/core/src/main/res/layout/fragment_page.xml b/core/src/main/res/layout/fragment_page.xml
index 9a47dc529..d4e449bd2 100644
--- a/core/src/main/res/layout/fragment_page.xml
+++ b/core/src/main/res/layout/fragment_page.xml
@@ -3,8 +3,7 @@
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"
- android:fitsSystemWindows="true">
+ android:layout_height="match_parent">
+ android:layout_height="match_parent">
+ app:layout_constraintStart_toStartOf="parent" />
diff --git a/core/src/main/res/layout/nav_main.xml b/core/src/main/res/layout/nav_main.xml
index a22f55299..0f3fd2a6b 100644
--- a/core/src/main/res/layout/nav_main.xml
+++ b/core/src/main/res/layout/nav_main.xml
@@ -24,7 +24,6 @@
diff --git a/core/src/main/res/layout/reader_fragment_content.xml b/core/src/main/res/layout/reader_fragment_content.xml
index b6a0ed7bc..93053de0b 100644
--- a/core/src/main/res/layout/reader_fragment_content.xml
+++ b/core/src/main/res/layout/reader_fragment_content.xml
@@ -26,8 +26,7 @@
+ android:layout_height="match_parent">
+ android:layout_height="match_parent">
+ android:layout_height="wrap_content"
+ android:clipToPadding="false" />
diff --git a/core/src/main/res/layout/tab_switcher.xml b/core/src/main/res/layout/tab_switcher.xml
index 4ae09a4e5..d242d5acf 100644
--- a/core/src/main/res/layout/tab_switcher.xml
+++ b/core/src/main/res/layout/tab_switcher.xml
@@ -2,13 +2,13 @@
+ android:layout_height="match_parent">
diff --git a/core/src/main/res/values-qq/strings.xml b/core/src/main/res/values-qq/strings.xml
index 81084bde6..d75566233 100644
--- a/core/src/main/res/values-qq/strings.xml
+++ b/core/src/main/res/values-qq/strings.xml
@@ -143,7 +143,7 @@
This is a descriptive message explaining where the Zim files are located after downloading. It is showing on the help screen.
{{Identical|Storage}}
This is showing on the preference settings screen, it shows the currently selected storage in which we are downloading the zim files, whether it is internal or external.
- This refers to free (unused) storage space, ’’not’’ to free as in free of charge.
+ This refers to free (unused) storage space, ’’not’’ to free as in free of charge. Here %s will be replaced by the free space e.g. 20GB.
This message appears in the “Android Toast” as an error message. When there are some files that could not be deleted, due to some reason.
{{identical|pause}}
{{identical|resume}}
diff --git a/core/src/main/res/values/themes.xml b/core/src/main/res/values/themes.xml
index 182d7d8f6..c5869f938 100644
--- a/core/src/main/res/values/themes.xml
+++ b/core/src/main/res/values/themes.xml
@@ -82,7 +82,6 @@
diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/dao/NewRecentSearchDaoTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/dao/NewRecentSearchDaoTest.kt
index 3463ba837..44ddfc7a2 100644
--- a/core/src/test/java/org/kiwix/kiwixmobile/core/dao/NewRecentSearchDaoTest.kt
+++ b/core/src/test/java/org/kiwix/kiwixmobile/core/dao/NewRecentSearchDaoTest.kt
@@ -25,7 +25,7 @@ import io.objectbox.Box
import io.objectbox.query.Query
import io.objectbox.query.QueryBuilder
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity
@@ -43,7 +43,7 @@ internal class NewRecentSearchDaoTest {
@Nested
inner class RecentSearchTests {
@Test
- fun `recentSearches searches by Id passed`() = runBlockingTest {
+ fun `recentSearches searches by Id passed`() = runTest {
val zimId = "id"
val queryResult = listOf(recentSearchEntity())
expectFromRecentSearches(queryResult, zimId)
@@ -56,7 +56,7 @@ internal class NewRecentSearchDaoTest {
}
@Test
- fun `recentSearches searches with blank Id if null passed`() = runBlockingTest {
+ fun `recentSearches searches with blank Id if null passed`() = runTest {
val queryResult = listOf(recentSearchEntity())
expectFromRecentSearches(queryResult, "")
newRecentSearchDao.recentSearches(null)
@@ -68,7 +68,7 @@ internal class NewRecentSearchDaoTest {
}
@Test
- fun `recentSearches searches returns distinct entities by searchTerm`() = runBlockingTest {
+ fun `recentSearches searches returns distinct entities by searchTerm`() = runTest {
val queryResult = listOf(recentSearchEntity(), recentSearchEntity())
expectFromRecentSearches(queryResult, "")
newRecentSearchDao.recentSearches("")
@@ -80,7 +80,7 @@ internal class NewRecentSearchDaoTest {
}
@Test
- fun `recentSearches searches returns a limitedNumber of entities`() = runBlockingTest {
+ fun `recentSearches searches returns a limitedNumber of entities`() = runTest {
val searchResults: List =
(0..200).map { recentSearchEntity(searchTerm = "$it") }
expectFromRecentSearches(searchResults, "")
diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageViewModelTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageViewModelTest.kt
index 4421df77b..44a849e18 100644
--- a/core/src/test/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageViewModelTest.kt
+++ b/core/src/test/java/org/kiwix/kiwixmobile/core/page/viewmodel/PageViewModelTest.kt
@@ -26,7 +26,13 @@ import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
import io.reactivex.schedulers.TestScheduler
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@@ -51,6 +57,7 @@ import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.sharedFunctions.InstantExecutorExtension
import org.kiwix.sharedFunctions.setScheduler
+@OptIn(ExperimentalCoroutinesApi::class)
@ExtendWith(InstantExecutorExtension::class)
internal class PageViewModelTest {
private val pageDao: PageDao = mockk()
@@ -69,6 +76,7 @@ internal class PageViewModelTest {
@BeforeEach
fun init() {
+ Dispatchers.setMain(UnconfinedTestDispatcher())
clearAllMocks()
every { zimReaderContainer.id } returns "id"
every { zimReaderContainer.name } returns "zimName"
@@ -77,6 +85,11 @@ internal class PageViewModelTest {
viewModel = TestablePageViewModel(zimReaderContainer, sharedPreferenceUtil, pageDao)
}
+ @AfterEach
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
@Test
fun `initial state is Initialising`() {
viewModel.state.test().assertValue(pageState())
diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt
index b214e7190..79bdb6ae0 100644
--- a/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt
+++ b/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchViewModelTest.kt
@@ -24,7 +24,6 @@ import io.mockk.clearAllMocks
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
@@ -33,11 +32,10 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.test.TestCoroutineDispatcher
-import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
-import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.assertj.core.api.Assertions.assertThat
@@ -84,7 +82,7 @@ internal class SearchViewModelTest {
private val zimReaderContainer: ZimReaderContainer = mockk()
private val searchResultGenerator: SearchResultGenerator = mockk()
private val zimFileReader: ZimFileReader = mockk()
- private val testDispatcher = TestCoroutineDispatcher()
+ private val testDispatcher = StandardTestDispatcher()
private val searchMutex: Mutex = mockk()
lateinit var viewModel: SearchViewModel
@@ -101,7 +99,7 @@ internal class SearchViewModelTest {
Dispatchers.resetMain()
Dispatchers.setMain(testDispatcher)
clearAllMocks()
- recentsFromDb = Channel(kotlinx.coroutines.channels.Channel.UNLIMITED)
+ recentsFromDb = Channel(Channel.UNLIMITED)
every { zimReaderContainer.zimFileReader } returns zimFileReader
coEvery {
searchResultGenerator.generateSearchResults("", zimFileReader)
@@ -115,7 +113,7 @@ internal class SearchViewModelTest {
@Nested
inner class StateTests {
@Test
- fun `initial state is Initialising`() = runBlockingTest {
+ fun `initial state is Initialising`() = runTest {
viewModel.state.test(this).assertValue(
SearchState("", SearchResultsWithTerm("", null, searchMutex), emptyList(), FromWebView)
).finish()
@@ -152,12 +150,12 @@ internal class SearchViewModelTest {
inner class ActionMapping {
@Test
- fun `ExitedSearch offers PopFragmentBackstack`() = runBlockingTest {
+ fun `ExitedSearch offers PopFragmentBackstack`() = runTest {
actionResultsInEffects(ExitedSearch, PopFragmentBackstack)
}
@Test
- fun `OnItemClick offers Saves and Opens`() = runBlockingTest {
+ fun `OnItemClick offers Saves and Opens`() = runTest {
val searchListItem = RecentSearchListItem("", "")
actionResultsInEffects(
OnItemClick(searchListItem),
@@ -170,7 +168,7 @@ internal class SearchViewModelTest {
}
@Test
- fun `OnOpenInNewTabClick offers Saves and Opens in new tab`() = runBlockingTest {
+ fun `OnOpenInNewTabClick offers Saves and Opens in new tab`() = runTest {
val searchListItem = RecentSearchListItem("", "")
actionResultsInEffects(
OnOpenInNewTabClick(searchListItem),
@@ -183,7 +181,7 @@ internal class SearchViewModelTest {
}
@Test
- fun `OnItemLongClick offers Saves and Opens`() = runBlockingTest {
+ fun `OnItemLongClick offers Saves and Opens`() = runTest {
val searchListItem = RecentSearchListItem("", "")
actionResultsInEffects(
OnItemLongClick(searchListItem),
@@ -192,12 +190,12 @@ internal class SearchViewModelTest {
}
@Test
- fun `ClickedSearchInText offers SearchInPreviousScreen`() = runBlockingTest {
+ fun `ClickedSearchInText offers SearchInPreviousScreen`() = runTest {
actionResultsInEffects(ClickedSearchInText, SearchInPreviousScreen(""))
}
@Test
- fun `ConfirmedDelete offers Delete and Toast`() = runBlockingTest {
+ fun `ConfirmedDelete offers Delete and Toast`() = runTest {
val searchListItem = RecentSearchListItem("", "")
actionResultsInEffects(
ConfirmedDelete(searchListItem),
@@ -207,7 +205,7 @@ internal class SearchViewModelTest {
}
@Test
- fun `CreatedWithArguments offers SearchArgumentProcessing`() = runBlockingTest {
+ fun `CreatedWithArguments offers SearchArgumentProcessing`() = runTest {
val bundle = mockk()
actionResultsInEffects(
CreatedWithArguments(bundle),
@@ -216,7 +214,7 @@ internal class SearchViewModelTest {
}
@Test
- fun `ReceivedPromptForSpeechInput offers StartSpeechInput`() = runBlockingTest {
+ fun `ReceivedPromptForSpeechInput offers StartSpeechInput`() = runTest {
actionResultsInEffects(
ReceivedPromptForSpeechInput,
StartSpeechInput(viewModel.actions)
@@ -224,7 +222,7 @@ internal class SearchViewModelTest {
}
@Test
- fun `StartSpeechInputFailed offers ShowToast`() = runBlockingTest {
+ fun `StartSpeechInputFailed offers ShowToast`() = runTest {
actionResultsInEffects(
StartSpeechInputFailed,
ShowToast(string.speech_not_supported)
@@ -232,22 +230,29 @@ internal class SearchViewModelTest {
}
@Test
- fun `ActivityResultReceived offers ProcessActivityResult`() = runBlockingTest {
+ fun `ActivityResultReceived offers ProcessActivityResult`() = runTest {
actionResultsInEffects(
ActivityResultReceived(0, 1, null),
ProcessActivityResult(0, 1, null, viewModel.actions)
)
}
- private fun TestCoroutineScope.actionResultsInEffects(
+ private fun TestScope.actionResultsInEffects(
action: Action,
vararg effects: SideEffect<*>
) {
- viewModel.effects
- .test(this)
- .also { viewModel.actions.trySend(action).isSuccess }
- .assertValues(*effects)
- .finish()
+ if (effects.size > 1) return
+ val collectedEffects = mutableListOf>()
+ val job = launch {
+ viewModel.effects.collect {
+ collectedEffects.add(it)
+ }
+ }
+
+ viewModel.actions.trySend(action).isSuccess
+ advanceUntilIdle()
+ assertThat(collectedEffects).containsExactlyElementsOf(effects.toList())
+ job.cancel()
}
}
@@ -271,34 +276,51 @@ internal class SearchViewModelTest {
}
}
-fun Flow.test(scope: CoroutineScope) = TestObserver(scope, this)
+fun Flow.test(scope: TestScope): TestObserver {
+ val observer = TestObserver(scope, this)
+ scope.launch { observer.startCollecting() }
+ return observer
+}
class TestObserver(
- scope: CoroutineScope,
- flow: Flow
+ private val scope: TestScope,
+ private val flow: Flow
) {
private val values = mutableListOf()
- private val job: Job = scope.launch {
- flow.collect {
- values.add(it)
+ private val completionChannel = Channel()
+ private var job: Job? = null
+
+ suspend fun startCollecting() {
+ job = scope.launch {
+ flow.collect {
+ values.add(it)
+ }
}
+ completionChannel.send(Unit)
}
- fun assertValues(vararg values: T): TestObserver {
+ private suspend fun awaitCompletion() {
+ completionChannel.receive()
+ }
+
+ suspend fun assertValues(vararg values: T): TestObserver {
+ awaitCompletion()
assertThat(values.toList()).containsExactlyElementsOf(this.values)
return this
}
- fun assertValue(value: T): TestObserver {
+ suspend fun assertValue(value: T): TestObserver {
+ awaitCompletion()
assertThat(values.last()).isEqualTo(value)
return this
}
fun finish() {
- job.cancel()
+ job?.cancel()
}
- fun assertValue(value: (T) -> Boolean): TestObserver {
+ suspend fun assertValue(value: (T) -> Boolean): TestObserver {
+ awaitCompletion()
assertThat(values.last()).satisfies({ value(it) })
return this
}
diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt
index 4fb5b1de8..7a4022594 100644
--- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt
+++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomFileValidator.kt
@@ -22,7 +22,6 @@ import android.content.Context
import android.content.pm.PackageManager
import android.content.res.AssetFileDescriptor
import android.content.res.AssetManager
-import androidx.core.content.ContextCompat
import org.kiwix.kiwixmobile.core.utils.files.Log
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasBothFiles
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasFile
@@ -96,7 +95,7 @@ class CustomFileValidator @Inject constructor(private val context: Context) {
private fun obbFiles() =
scanDirs(
- ContextCompat.getObbDirs(context).filterNotNull().filter(File::exists).toTypedArray(),
+ context.obbDirs.filterNotNull().filter(File::exists).toTypedArray(),
"obb"
)
@@ -105,7 +104,7 @@ class CustomFileValidator @Inject constructor(private val context: Context) {
val directoryList = mutableListOf()
// Get the external files directories for the app
- ContextCompat.getExternalFilesDirs(context, null).filterNotNull()
+ context.getExternalFilesDirs(null).filterNotNull()
.filter(File::exists)
.forEach { dir ->
// Check if the directory's parent is not null
diff --git a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt
index 5721c21e6..f9a41b7aa 100644
--- a/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt
+++ b/custom/src/main/java/org/kiwix/kiwixmobile/custom/main/CustomMainActivity.kt
@@ -38,6 +38,7 @@ import org.kiwix.kiwixmobile.custom.BuildConfig
import org.kiwix.kiwixmobile.custom.R
import org.kiwix.kiwixmobile.core.R.string
import org.kiwix.kiwixmobile.core.R.drawable
+import org.kiwix.kiwixmobile.core.extensions.applyEdgeToEdgeInsets
import org.kiwix.kiwixmobile.custom.customActivityComponent
import org.kiwix.kiwixmobile.custom.databinding.ActivityCustomMainBinding
@@ -84,6 +85,7 @@ class CustomMainActivity : CoreMainActivity() {
super.onCreate(savedInstanceState)
activityCustomMainBinding = ActivityCustomMainBinding.inflate(layoutInflater)
setContentView(activityCustomMainBinding.root)
+ activityCustomMainBinding.root.applyEdgeToEdgeInsets()
if (savedInstanceState != null) {
return
}
diff --git a/custom/src/main/res/layout/activity_custom_main.xml b/custom/src/main/res/layout/activity_custom_main.xml
index 91a95292f..86faa6017 100644
--- a/custom/src/main/res/layout/activity_custom_main.xml
+++ b/custom/src/main/res/layout/activity_custom_main.xml
@@ -22,7 +22,6 @@
android:id="@+id/custom_drawer_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:fitsSystemWindows="true"
tools:ignore="UnusedIds">
@@ -48,6 +46,5 @@
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_marginTop="24dp"
- android:fitsSystemWindows="true"
app:headerLayout="@layout/drawer_right" />
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f1aa9a835..d190455a7 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Mon Dec 19 16:13:45 IST 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/lintConfig.xml b/lintConfig.xml
index 48d9225f6..e043d2bdc 100644
--- a/lintConfig.xml
+++ b/lintConfig.xml
@@ -1,25 +1,25 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -48,4 +48,7 @@
+
+
+