mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-08-03 10:46:53 -04:00
Merge pull request #4147 from kiwix/Fixes#3990
Added support for Android 15.
This commit is contained in:
commit
359a1fc4bc
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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">
|
||||
|
||||
<org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -53,7 +52,6 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
app:headerLayout="@layout/nav_main"
|
||||
app:menu="@menu/menu_drawer_main" />
|
||||
|
||||
@ -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" />
|
||||
|
||||
|
||||
|
@ -1,24 +1,24 @@
|
||||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/layout_standard_app_bar" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
tools:listitem="@layout/item_language"
|
||||
android:id="@+id/language_recycler_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
android:contentDescription="@string/pref_language_title"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/app_bar" />
|
||||
app:layout_constraintTop_toBottomOf="@id/app_bar"
|
||||
android:clipToPadding="false"
|
||||
tools:listitem="@layout/item_language" />
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/language_progressbar"
|
||||
|
@ -22,7 +22,6 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="org.kiwix.kiwixmobile.webserver.ZimHostFragment">
|
||||
|
||||
<include layout="@layout/layout_toolbar" />
|
||||
|
@ -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" />
|
||||
|
||||
|
@ -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">
|
||||
|
||||
<org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
@ -55,6 +54,7 @@
|
||||
android:id="@+id/zimfilelist"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:contentDescription="@string/crash_checkbox_zimfiles"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -5,7 +5,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/transparent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferFragment">
|
||||
|
||||
|
||||
@ -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"
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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") {
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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<CommonExtension<*, *, *, *, *>> {
|
||||
target.configureExtension<CommonExtension<*, *, *, *, *, *>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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) }
|
||||
|
||||
|
@ -116,6 +116,9 @@ abstract class CoreApp : Application() {
|
||||
detectLeakedSqlLiteObjects()
|
||||
penaltyLog()
|
||||
detectLeakedRegistrationObjects()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
detectUnsafeIntentLaunch()
|
||||
}
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -45,7 +45,7 @@ class AdapterDelegateManager<T> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 <reified T : ViewModel> 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)
|
||||
}
|
||||
}
|
||||
|
@ -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<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = systemBarsInsets.top
|
||||
leftMargin = systemBarsInsets.left
|
||||
bottomMargin = systemBarsInsets.bottom
|
||||
rightMargin = systemBarsInsets.right
|
||||
}
|
||||
WindowInsetsCompat.CONSUMED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1869,9 +1869,9 @@ abstract class CoreReaderFragment :
|
||||
}
|
||||
reopenBook()
|
||||
showTabSwitcher()
|
||||
setUpWithTextToSpeech(tempWebViewListForUndo.last())
|
||||
setUpWithTextToSpeech(tempWebViewListForUndo[tempWebViewListForUndo.lastIndex])
|
||||
updateBottomToolbarVisibility()
|
||||
safelyAddWebView(tempWebViewListForUndo.last())
|
||||
safelyAddWebView(tempWebViewListForUndo[tempWebViewListForUndo.lastIndex])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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
|
||||
|
@ -18,10 +18,10 @@
|
||||
-->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
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:background="@android:color/transparent">
|
||||
|
||||
<include layout="@layout/layout_standard_app_bar" />
|
||||
@ -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" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/searchNoResults"
|
||||
style="@style/no_content"
|
||||
android:text="@string/no_history"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
@ -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" />
|
||||
|
@ -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">
|
||||
|
||||
<include layout="@layout/layout_standard_app_bar" />
|
||||
@ -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"
|
||||
|
@ -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">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar"
|
||||
@ -17,8 +16,8 @@
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:popupTheme="@style/KiwixTheme"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
||||
app:popupTheme="@style/KiwixTheme"
|
||||
tools:showIn="@layout/fragment_search" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
@ -42,6 +41,7 @@
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -20,8 +20,7 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/navigation_fragment_main_drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -4,7 +4,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
android:fitsSystemWindows="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
@ -18,12 +17,13 @@
|
||||
android:id="@+id/search_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="?actionBarSize"
|
||||
android:clipToPadding="false"
|
||||
android:contentDescription="@string/searched_list"
|
||||
app:layout_constraintBottom_toTopOf="@+id/loadingMoreDataIndicator"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:contentDescription="@string/searched_list"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_marginTop="?actionBarSize"
|
||||
tools:listitem="@layout/list_item_search" />
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
@ -32,10 +32,10 @@
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:visibility="gone" />
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
@ -24,7 +24,6 @@
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:ignore="ContentDescription"
|
||||
android:src="@drawable/ic_home_kiwix_banner" />
|
||||
</LinearLayout>
|
||||
|
@ -26,8 +26,7 @@
|
||||
|
||||
<org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/activity_main_content_frame"
|
||||
|
@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -16,5 +16,6 @@
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/device_list"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false" />
|
||||
</LinearLayout>
|
||||
|
@ -2,13 +2,13 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/tab_switcher_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
|
@ -143,7 +143,7 @@
|
||||
<string name="help_11">This is a descriptive message explaining where the Zim files are located after downloading. It is showing on the help screen.</string>
|
||||
<string name="pref_storage">{{Identical|Storage}}</string>
|
||||
<string name="pref_current_folder">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.</string>
|
||||
<string name="pref_free_storage">This refers to free (unused) storage space, ’’not’’ to free as in free of charge.</string>
|
||||
<string name="pref_free_storage">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.</string>
|
||||
<string name="delete_zim_failed">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.</string>
|
||||
<string name="tts_pause">{{identical|pause}}</string>
|
||||
<string name="tts_resume">{{identical|resume}}</string>
|
||||
|
@ -82,7 +82,6 @@
|
||||
</style>
|
||||
|
||||
<style name="Base.MaterialThemeBuilder" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
|
||||
<item name="android:statusBarColor" tools:ignore="NewApi">@color/black</item>
|
||||
<item name="actionModeBackground">@color/cornflower_blue</item>
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
@ -92,6 +91,8 @@
|
||||
<item name="colorAccent">?colorSecondary</item>
|
||||
<item name="android:navigationBarColor">@color/black</item>
|
||||
|
||||
<!-- To prevent showing content behind the cutouts -->
|
||||
<item name="android:windowLayoutInDisplayCutoutMode" tools:ignore="NewApi">never</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -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>(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>(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(), 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<RecentSearchEntity> =
|
||||
(0..200).map { recentSearchEntity(searchTerm = "$it") }
|
||||
expectFromRecentSearches(searchResults, "")
|
||||
|
@ -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())
|
||||
|
@ -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<Bundle>()
|
||||
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<SideEffect<*>>()
|
||||
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 <T> Flow<T>.test(scope: CoroutineScope) = TestObserver(scope, this)
|
||||
fun <T> Flow<T>.test(scope: TestScope): TestObserver<T> {
|
||||
val observer = TestObserver(scope, this)
|
||||
scope.launch { observer.startCollecting() }
|
||||
return observer
|
||||
}
|
||||
|
||||
class TestObserver<T>(
|
||||
scope: CoroutineScope,
|
||||
flow: Flow<T>
|
||||
private val scope: TestScope,
|
||||
private val flow: Flow<T>
|
||||
) {
|
||||
private val values = mutableListOf<T>()
|
||||
private val job: Job = scope.launch {
|
||||
flow.collect {
|
||||
values.add(it)
|
||||
private val completionChannel = Channel<Unit>()
|
||||
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<T> {
|
||||
private suspend fun awaitCompletion() {
|
||||
completionChannel.receive()
|
||||
}
|
||||
|
||||
suspend fun assertValues(vararg values: T): TestObserver<T> {
|
||||
awaitCompletion()
|
||||
assertThat(values.toList()).containsExactlyElementsOf(this.values)
|
||||
return this
|
||||
}
|
||||
|
||||
fun assertValue(value: T): TestObserver<T> {
|
||||
suspend fun assertValue(value: T): TestObserver<T> {
|
||||
awaitCompletion()
|
||||
assertThat(values.last()).isEqualTo(value)
|
||||
return this
|
||||
}
|
||||
|
||||
fun finish() {
|
||||
job.cancel()
|
||||
job?.cancel()
|
||||
}
|
||||
|
||||
fun assertValue(value: (T) -> Boolean): TestObserver<T> {
|
||||
suspend fun assertValue(value: (T) -> Boolean): TestObserver<T> {
|
||||
awaitCompletion()
|
||||
assertThat(values.last()).satisfies({ value(it) })
|
||||
return this
|
||||
}
|
||||
|
@ -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<File>()
|
||||
|
||||
// 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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
@ -38,7 +37,6 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
app:headerLayout="@layout/nav_main"
|
||||
app:menu="@menu/menu_drawer_main" />
|
||||
|
||||
@ -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" />
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
@ -1,25 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<lint>
|
||||
<issue id="TypographyQuotes">
|
||||
<issue id="TypographyQuotes" severity="warning">
|
||||
<ignore path="**-qq/**.xml" />
|
||||
<ignore path="**-iw/**.xml" />
|
||||
</issue>
|
||||
<issue id="LintError">
|
||||
<ignore regexp=".*BookmarksRobot.kt.*"/>
|
||||
<ignore regexp=".*DebugFunctions.kt.*"/>
|
||||
<ignore regexp=".*HistoryRobot.kt.*"/>
|
||||
<ignore regexp=".*IntroRobot.kt.*"/>
|
||||
<ignore regexp=".*LanguageRobot.kt.*"/>
|
||||
<ignore regexp=".*LibraryRobot.kt.*"/>
|
||||
<ignore regexp=".*LocalFileTransferRobot.kt.*"/>
|
||||
<ignore regexp=".*OnlineLibraryRobot.kt.*"/>
|
||||
<ignore regexp=".*ReaderRobot.kt.*"/>
|
||||
<ignore regexp=".*SettingsRobot.kt.*"/>
|
||||
<ignore regexp=".*TopLevelDestinationRobot.kt.*"/>
|
||||
<ignore regexp=".*ZimHostRobot.kt.*"/>
|
||||
<ignore regexp=".*SearchRobot.kt.*"/>
|
||||
<ignore regexp=".*InitialDownloadRobot.kt.*"/>
|
||||
<ignore regexp=".*DownloadRobot.kt.*"/>
|
||||
<ignore regexp=".*BookmarksRobot.kt.*" />
|
||||
<ignore regexp=".*DebugFunctions.kt.*" />
|
||||
<ignore regexp=".*HistoryRobot.kt.*" />
|
||||
<ignore regexp=".*IntroRobot.kt.*" />
|
||||
<ignore regexp=".*LanguageRobot.kt.*" />
|
||||
<ignore regexp=".*LibraryRobot.kt.*" />
|
||||
<ignore regexp=".*LocalFileTransferRobot.kt.*" />
|
||||
<ignore regexp=".*OnlineLibraryRobot.kt.*" />
|
||||
<ignore regexp=".*ReaderRobot.kt.*" />
|
||||
<ignore regexp=".*SettingsRobot.kt.*" />
|
||||
<ignore regexp=".*TopLevelDestinationRobot.kt.*" />
|
||||
<ignore regexp=".*ZimHostRobot.kt.*" />
|
||||
<ignore regexp=".*SearchRobot.kt.*" />
|
||||
<ignore regexp=".*InitialDownloadRobot.kt.*" />
|
||||
<ignore regexp=".*DownloadRobot.kt.*" />
|
||||
</issue>
|
||||
<issue id="TypographyEllipsis">
|
||||
<ignore path="**-iw/**.xml" />
|
||||
@ -48,4 +48,7 @@
|
||||
<issue id="DataExtractionRules" severity="warning" />
|
||||
<issue id="ObsoleteSdkInt" severity="warning" />
|
||||
<issue id="AppLinksAutoVerify" severity="warning" />
|
||||
<issue id="CheckResult">
|
||||
<ignore path="**/androidTest/**.kt" />
|
||||
</issue>
|
||||
</lint>
|
||||
|
Loading…
x
Reference in New Issue
Block a user