mirror of
https://github.com/kiwix/kiwix-android.git
synced 2025-09-11 16:37:18 -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
|
name: Automated tests
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
api-level: [ 25, 30, 33, 34 ]
|
api-level: [ 25, 30, 33, 34, 35 ]
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
@ -57,7 +57,7 @@ jobs:
|
|||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
target: default
|
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
profile: pixel_2
|
profile: pixel_2
|
||||||
ram-size: 3072M
|
ram-size: 3072M
|
||||||
@ -76,7 +76,7 @@ jobs:
|
|||||||
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
|
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
target: default
|
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
profile: pixel_2
|
profile: pixel_2
|
||||||
ram-size: 3072M
|
ram-size: 3072M
|
||||||
@ -118,7 +118,7 @@ jobs:
|
|||||||
name: Automated tests for PlayStore variant
|
name: Automated tests for PlayStore variant
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
api-level: [ 25, 30, 33, 34 ]
|
api-level: [ 25, 30, 33, 34, 35 ]
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
@ -163,7 +163,7 @@ jobs:
|
|||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
target: default
|
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
profile: pixel_2
|
profile: pixel_2
|
||||||
ram-size: 2048M
|
ram-size: 2048M
|
||||||
@ -182,7 +182,7 @@ jobs:
|
|||||||
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
|
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
target: default
|
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
profile: pixel_2
|
profile: pixel_2
|
||||||
ram-size: 2048M
|
ram-size: 2048M
|
||||||
@ -199,7 +199,7 @@ jobs:
|
|||||||
name: Automated tests for Custom app
|
name: Automated tests for Custom app
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
api-level: [ 25, 30, 33, 34 ]
|
api-level: [ 25, 30, 33, 34, 35 ]
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
@ -244,7 +244,7 @@ jobs:
|
|||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
target: default
|
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
profile: pixel_2
|
profile: pixel_2
|
||||||
ram-size: 2048M
|
ram-size: 2048M
|
||||||
@ -263,7 +263,7 @@ jobs:
|
|||||||
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
|
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
target: default
|
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
profile: pixel_2
|
profile: pixel_2
|
||||||
ram-size: 2048M
|
ram-size: 2048M
|
||||||
@ -280,7 +280,7 @@ jobs:
|
|||||||
name: Automated tests on Tablet
|
name: Automated tests on Tablet
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
api-level: [ 30, 33, 34 ]
|
api-level: [ 25, 30, 33, 34, 35 ]
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
@ -325,7 +325,7 @@ jobs:
|
|||||||
uses: reactivecircus/android-emulator-runner@v2
|
uses: reactivecircus/android-emulator-runner@v2
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
target: default
|
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
profile: pixel_2
|
profile: pixel_2
|
||||||
ram-size: 2048M
|
ram-size: 2048M
|
||||||
@ -344,7 +344,7 @@ jobs:
|
|||||||
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
|
GRADLE_OPTS: "-Dorg.gradle.internal.http.connectionTimeout=60000 -Dorg.gradle.internal.http.socketTimeout=60000 -Dorg.gradle.internal.network.retry.max.attempts=6 -Dorg.gradle.internal.network.retry.initial.backOff=2000"
|
||||||
with:
|
with:
|
||||||
api-level: ${{ matrix.api-level }}
|
api-level: ${{ matrix.api-level }}
|
||||||
target: default
|
target: ${{ (matrix.api-level == 35) && 'google_apis' || 'default' }}
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
profile: pixel_c
|
profile: pixel_c
|
||||||
ram-size: 2048M
|
ram-size: 2048M
|
||||||
|
@ -133,7 +133,9 @@ class OpeningFilesFromStorageTest : BaseActivityTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testOpeningFileFromFileManager() {
|
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 {
|
activityScenario.onActivity {
|
||||||
kiwixMainActivity = it
|
kiwixMainActivity = it
|
||||||
it.navigate(R.id.libraryFragment)
|
it.navigate(R.id.libraryFragment)
|
||||||
|
@ -25,7 +25,6 @@ import android.content.pm.PackageManager
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.test.core.app.canTakeScreenshot
|
import androidx.test.core.app.canTakeScreenshot
|
||||||
import androidx.test.core.app.takeScreenshot
|
import androidx.test.core.app.takeScreenshot
|
||||||
@ -91,10 +90,6 @@ object TestUtils {
|
|||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
) == 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 &&
|
@JvmStatic fun hasStoragePermission() = Build.VERSION.SDK_INT > Build.VERSION_CODES.M &&
|
||||||
hasReadExternalStoragePermission() && hasWriteExternalStoragePermission()
|
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.base.FragmentActivityExtensions
|
||||||
import org.kiwix.kiwixmobile.core.dao.NewBookDao
|
import org.kiwix.kiwixmobile.core.dao.NewBookDao
|
||||||
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DOWNLOAD_NOTIFICATION_TITLE
|
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.extensions.toast
|
||||||
import org.kiwix.kiwixmobile.core.main.ACTION_NEW_TAB
|
import org.kiwix.kiwixmobile.core.main.ACTION_NEW_TAB
|
||||||
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
||||||
@ -122,11 +123,13 @@ class KiwixMainActivity : CoreMainActivity() {
|
|||||||
setContentView(activityKiwixMainBinding.root)
|
setContentView(activityKiwixMainBinding.root)
|
||||||
|
|
||||||
navController.addOnDestinationChangedListener(finishActionModeOnDestinationChange)
|
navController.addOnDestinationChangedListener(finishActionModeOnDestinationChange)
|
||||||
activityKiwixMainBinding.drawerNavView.setupWithNavController(navController)
|
activityKiwixMainBinding.drawerNavView.apply {
|
||||||
activityKiwixMainBinding.drawerNavView.setNavigationItemSelectedListener { item ->
|
setupWithNavController(navController)
|
||||||
|
setNavigationItemSelectedListener { item ->
|
||||||
closeNavigationDrawer()
|
closeNavigationDrawer()
|
||||||
onNavigationItemSelected(item)
|
onNavigationItemSelected(item)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
activityKiwixMainBinding.bottomNavView.setupWithNavController(navController)
|
activityKiwixMainBinding.bottomNavView.setupWithNavController(navController)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
migrateInternalToPublicAppDirectory()
|
migrateInternalToPublicAppDirectory()
|
||||||
@ -134,6 +137,7 @@ class KiwixMainActivity : CoreMainActivity() {
|
|||||||
handleZimFileIntent(intent)
|
handleZimFileIntent(intent)
|
||||||
handleNotificationIntent(intent)
|
handleNotificationIntent(intent)
|
||||||
handleGetContentIntent(intent)
|
handleGetContentIntent(intent)
|
||||||
|
activityKiwixMainBinding.root.applyEdgeToEdgeInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun migrateInternalToPublicAppDirectory() {
|
private suspend fun migrateInternalToPublicAppDirectory() {
|
||||||
|
@ -21,8 +21,7 @@
|
|||||||
android:id="@+id/navigation_container"
|
android:id="@+id/navigation_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:contentDescription="@string/open_drawer"
|
android:contentDescription="@string/open_drawer">
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout
|
<org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -53,7 +52,6 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="start"
|
android:layout_gravity="start"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
app:headerLayout="@layout/nav_main"
|
app:headerLayout="@layout/nav_main"
|
||||||
app:menu="@menu/menu_drawer_main" />
|
app:menu="@menu/menu_drawer_main" />
|
||||||
|
|
||||||
@ -62,7 +60,6 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
app:headerLayout="@layout/drawer_right" />
|
app:headerLayout="@layout/drawer_right" />
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
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"
|
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" />
|
<include layout="@layout/layout_standard_app_bar" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
tools:listitem="@layout/item_language"
|
|
||||||
android:id="@+id/language_recycler_view"
|
android:id="@+id/language_recycler_view"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
android:contentDescription="@string/pref_language_title"
|
android:contentDescription="@string/pref_language_title"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="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
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
android:id="@+id/language_progressbar"
|
android:id="@+id/language_progressbar"
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context="org.kiwix.kiwixmobile.webserver.ZimHostFragment">
|
tools:context="org.kiwix.kiwixmobile.webserver.ZimHostFragment">
|
||||||
|
|
||||||
<include layout="@layout/layout_toolbar" />
|
<include layout="@layout/layout_toolbar" />
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:contentDescription="@string/library"
|
android:contentDescription="@string/library"
|
||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
|
android:clipToPadding="false"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||||
tools:listitem="@layout/item_download" />
|
tools:listitem="@layout/item_download" />
|
||||||
|
|
||||||
|
@ -21,8 +21,7 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout
|
<org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -55,6 +54,7 @@
|
|||||||
android:id="@+id/zimfilelist"
|
android:id="@+id/zimfilelist"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:contentDescription="@string/crash_checkbox_zimfiles"
|
android:contentDescription="@string/crash_checkbox_zimfiles"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context="org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferFragment">
|
tools:context="org.kiwix.kiwixmobile.localFileTransfer.LocalFileTransferFragment">
|
||||||
|
|
||||||
|
|
||||||
@ -80,6 +79,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:visibility="invisible"
|
android:visibility="invisible"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@ -161,6 +161,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:contentDescription="@string/files_for_transfer"
|
android:contentDescription="@string/files_for_transfer"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
@ -22,6 +22,7 @@ import android.app.Application
|
|||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.net.NetworkCapabilities.TRANSPORT_WIFI
|
import android.net.NetworkCapabilities.TRANSPORT_WIFI
|
||||||
|
import android.os.Build
|
||||||
import com.jraska.livedata.test
|
import com.jraska.livedata.test
|
||||||
import io.mockk.clearAllMocks
|
import io.mockk.clearAllMocks
|
||||||
import io.mockk.every
|
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.ConnectivityBroadcastReceiver
|
||||||
import org.kiwix.kiwixmobile.core.zim_manager.Language
|
import org.kiwix.kiwixmobile.core.zim_manager.Language
|
||||||
import org.kiwix.kiwixmobile.core.zim_manager.NetworkState
|
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.MULTI
|
||||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode.NORMAL
|
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.SelectionMode.NORMAL
|
||||||
import org.kiwix.kiwixmobile.core.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
|
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
|
||||||
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CanWrite4GbFile
|
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CanWrite4GbFile
|
||||||
import org.kiwix.kiwixmobile.zimManager.Fat32Checker.FileSystemState.CannotWrite4GbFile
|
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.MultiModeFinished
|
||||||
import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.FileSelectActions.RequestDeleteMultiSelection
|
import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.FileSelectActions.RequestDeleteMultiSelection
|
||||||
import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.FileSelectActions.RequestMultiSelection
|
import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel.FileSelectActions.RequestMultiSelection
|
||||||
@ -139,7 +140,12 @@ class ZimManageViewModelTest {
|
|||||||
every { newLanguagesDao.languages() } returns languages
|
every { newLanguagesDao.languages() } returns languages
|
||||||
every { fat32Checker.fileSystemStates } returns fileSystemStates
|
every { fat32Checker.fileSystemStates } returns fileSystemStates
|
||||||
every { connectivityBroadcastReceiver.networkStates } returns networkStates
|
every { connectivityBroadcastReceiver.networkStates } returns networkStates
|
||||||
|
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 { application.registerReceiver(any(), any()) } returns mockk()
|
||||||
|
}
|
||||||
every { dataSource.booksOnDiskAsListItems() } returns booksOnDiskListItems
|
every { dataSource.booksOnDiskAsListItems() } returns booksOnDiskListItems
|
||||||
every {
|
every {
|
||||||
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||||
@ -167,10 +173,17 @@ class ZimManageViewModelTest {
|
|||||||
inner class Context {
|
inner class Context {
|
||||||
@Test
|
@Test
|
||||||
fun `registers broadcastReceiver in init`() {
|
fun `registers broadcastReceiver in init`() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
verify {
|
||||||
|
application.registerReceiver(connectivityBroadcastReceiver, any(), any())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@Suppress("UnspecifiedRegisterReceiverFlag")
|
||||||
verify {
|
verify {
|
||||||
application.registerReceiver(connectivityBroadcastReceiver, any())
|
application.registerReceiver(connectivityBroadcastReceiver, any())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `unregisters broadcastReceiver in onCleared`() {
|
fun `unregisters broadcastReceiver in onCleared`() {
|
||||||
|
@ -7,6 +7,7 @@ buildscript {
|
|||||||
dependencies {
|
dependencies {
|
||||||
classpath(Libs.com_android_tools_build_gradle)
|
classpath(Libs.com_android_tools_build_gradle)
|
||||||
classpath(Libs.kotlin_gradle_plugin)
|
classpath(Libs.kotlin_gradle_plugin)
|
||||||
|
classpath(Libs.kotlin_ksp)
|
||||||
classpath(Libs.navigation_safe_args_gradle_plugin)
|
classpath(Libs.navigation_safe_args_gradle_plugin)
|
||||||
classpath(Libs.keeper)
|
classpath(Libs.keeper)
|
||||||
|
|
||||||
|
@ -11,8 +11,9 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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("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.jacoco:org.jacoco.core:0.8.12")
|
||||||
implementation("org.jlleitschuh.gradle:ktlint-gradle:10.3.0")
|
implementation("org.jlleitschuh.gradle:ktlint-gradle:10.3.0")
|
||||||
implementation("com.google.apis:google-api-services-androidpublisher:v3-rev20230406-2.0.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
|
// Here is a list of all Android versions with their corresponding API
|
||||||
// levels: https://apilevels.com/
|
// 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 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
|
// Version Information
|
||||||
const val versionMajor = 3 // Major version component of the app's version name and version code.
|
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
|
* 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:" +
|
const val kotlin_stdlib_jdk8: String = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:" +
|
||||||
Versions.org_jetbrains_kotlin
|
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
|
* 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 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
|
* https://github.com/kiwix/java-libkiwix
|
||||||
*/
|
*/
|
||||||
|
@ -14,13 +14,13 @@ object Versions {
|
|||||||
|
|
||||||
const val document_file_version: String = "1.0.1"
|
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 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"
|
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 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 androidx_navigation: String = "2.5.3"
|
||||||
|
|
||||||
const val navigation_ui_ktx: String = "2.4.1"
|
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 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"
|
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 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"
|
const val junit_jupiter: String = "5.11.0"
|
||||||
|
|
||||||
@ -70,7 +72,7 @@ object Versions {
|
|||||||
|
|
||||||
const val core_testing: String = "2.2.0"
|
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"
|
const val testing_ktx: String = "1.3.0"
|
||||||
|
|
||||||
@ -86,11 +88,13 @@ object Versions {
|
|||||||
|
|
||||||
const val rxandroid: String = "2.1.1"
|
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 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"
|
const val multidex: String = "2.0.1"
|
||||||
|
|
||||||
@ -98,13 +102,13 @@ object Versions {
|
|||||||
|
|
||||||
const val rxjava: String = "2.2.21"
|
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 junit: String = "1.1.5"
|
||||||
|
|
||||||
const val material_show_case_view: String = "1.3.7"
|
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"
|
const val zxing = "3.5.3"
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ class AllProjectConfigurer {
|
|||||||
fun applyPlugins(target: Project) {
|
fun applyPlugins(target: Project) {
|
||||||
target.plugins.apply("kotlin-android")
|
target.plugins.apply("kotlin-android")
|
||||||
target.plugins.apply("kotlin-kapt")
|
target.plugins.apply("kotlin-kapt")
|
||||||
|
target.plugins.apply("com.google.devtools.ksp")
|
||||||
target.plugins.apply("kotlin-parcelize")
|
target.plugins.apply("kotlin-parcelize")
|
||||||
target.plugins.apply("jacoco")
|
target.plugins.apply("jacoco")
|
||||||
target.plugins.apply("org.jlleitschuh.gradle.ktlint")
|
target.plugins.apply("org.jlleitschuh.gradle.ktlint")
|
||||||
@ -77,7 +78,7 @@ class AllProjectConfigurer {
|
|||||||
}
|
}
|
||||||
target.tasks.withType(KotlinCompile::class.java) {
|
target.tasks.withType(KotlinCompile::class.java) {
|
||||||
compilerOptions {
|
compilerOptions {
|
||||||
jvmTarget.set(JvmTarget.JVM_1_8)
|
jvmTarget.set(JvmTarget.JVM_17)
|
||||||
freeCompilerArgs.add("-Xjvm-default=all-compatibility")
|
freeCompilerArgs.add("-Xjvm-default=all-compatibility")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,7 +138,7 @@ class AllProjectConfigurer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun configureCommonExtension(target: Project) {
|
fun configureCommonExtension(target: Project) {
|
||||||
target.configureExtension<CommonExtension<*, *, *, *, *>> {
|
target.configureExtension<CommonExtension<*, *, *, *, *, *>> {
|
||||||
lint {
|
lint {
|
||||||
abortOnError = true
|
abortOnError = true
|
||||||
checkAllWarnings = true
|
checkAllWarnings = true
|
||||||
@ -231,7 +232,9 @@ class AllProjectConfigurer {
|
|||||||
implementation(Libs.roomRxjava2)
|
implementation(Libs.roomRxjava2)
|
||||||
kapt(Libs.roomCompiler)
|
kapt(Libs.roomCompiler)
|
||||||
implementation(Libs.tracing)
|
implementation(Libs.tracing)
|
||||||
|
implementation(Libs.fetch)
|
||||||
implementation(Libs.fetchOkhttp)
|
implementation(Libs.fetchOkhttp)
|
||||||
|
implementation(Libs.androidx_activity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,9 @@ internal fun DependencyHandlerScope.compileOnly(dependency: String) =
|
|||||||
internal fun DependencyHandlerScope.kapt(dependency: String) =
|
internal fun DependencyHandlerScope.kapt(dependency: String) =
|
||||||
addDependency("kapt", dependency)
|
addDependency("kapt", dependency)
|
||||||
|
|
||||||
|
internal fun DependencyHandlerScope.ksp(dependency: String) =
|
||||||
|
addDependency("ksp", dependency)
|
||||||
|
|
||||||
internal fun DependencyHandlerScope.testImplementation(dependency: String) =
|
internal fun DependencyHandlerScope.testImplementation(dependency: String) =
|
||||||
addDependency("testImplementation", dependency)
|
addDependency("testImplementation", dependency)
|
||||||
|
|
||||||
|
@ -63,8 +63,4 @@ dependencies {
|
|||||||
implementation(Libs.kotlinx_coroutines_android)
|
implementation(Libs.kotlinx_coroutines_android)
|
||||||
implementation(Libs.kotlinx_coroutines_rx3)
|
implementation(Libs.kotlinx_coroutines_rx3)
|
||||||
implementation(Libs.zxing)
|
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.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileFilter
|
import java.io.FileFilter
|
||||||
import java.io.RandomAccessFile
|
import java.io.RandomAccessFile
|
||||||
import java.util.ArrayList
|
|
||||||
|
|
||||||
object StorageDeviceUtils {
|
object StorageDeviceUtils {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -63,7 +61,7 @@ object StorageDeviceUtils {
|
|||||||
private fun externalFilesDirsDevices(
|
private fun externalFilesDirsDevices(
|
||||||
context: Context,
|
context: Context,
|
||||||
writable: Boolean
|
writable: Boolean
|
||||||
) = ContextCompat.getExternalFilesDirs(context, "")
|
) = context.getExternalFilesDirs("")
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
.mapIndexed { index, dir -> StorageDevice(generalisePath(dir.path, writable), index == 0) }
|
.mapIndexed { index, dir -> StorageDevice(generalisePath(dir.path, writable), index == 0) }
|
||||||
|
|
||||||
|
@ -116,6 +116,9 @@ abstract class CoreApp : Application() {
|
|||||||
detectLeakedSqlLiteObjects()
|
detectLeakedSqlLiteObjects()
|
||||||
penaltyLog()
|
penaltyLog()
|
||||||
detectLeakedRegistrationObjects()
|
detectLeakedRegistrationObjects()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
detectUnsafeIntentLaunch()
|
||||||
|
}
|
||||||
}.build()
|
}.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.kiwix.kiwixmobile.core.base
|
package org.kiwix.kiwixmobile.core.base
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.activity.SystemBarStyle
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
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.LanguageUtils
|
||||||
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -29,7 +34,14 @@ open class BaseActivity : AppCompatActivity() {
|
|||||||
lateinit var sharedPreferenceUtil: SharedPreferenceUtil
|
lateinit var sharedPreferenceUtil: SharedPreferenceUtil
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge(
|
||||||
|
statusBarStyle = SystemBarStyle.dark(Color.BLACK),
|
||||||
|
navigationBarStyle = SystemBarStyle.dark(Color.BLACK)
|
||||||
|
)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||||
|
setWindowBackgroundColorForAndroid15AndAbove()
|
||||||
|
}
|
||||||
LanguageUtils.handleLocaleChange(this, sharedPreferenceUtil)
|
LanguageUtils.handleLocaleChange(this, sharedPreferenceUtil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,16 @@
|
|||||||
package org.kiwix.kiwixmobile.core.base
|
package org.kiwix.kiwixmobile.core.base
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import org.kiwix.kiwixmobile.core.R
|
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.getToolbarNavigationIcon
|
||||||
|
import org.kiwix.kiwixmobile.core.extensions.setFragmentBackgroundColorForAndroid15AndAbove
|
||||||
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
|
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,7 +49,11 @@ abstract class BaseFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
enableEdgeToEdgeMode()
|
||||||
setupToolbar()
|
setupToolbar()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||||
|
setFragmentBackgroundColorForAndroid15AndAbove()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup toolbar to handle common back pressed event
|
// Setup toolbar to handle common back pressed event
|
||||||
|
@ -45,7 +45,7 @@ class AdapterDelegateManager<T> {
|
|||||||
private fun getDelegateIndexFor(item: T): Int {
|
private fun getDelegateIndexFor(item: T): Int {
|
||||||
for (index in 0..delegates.size()) {
|
for (index in 0..delegates.size()) {
|
||||||
val valueAt = delegates.valueAt(index)
|
val valueAt = delegates.valueAt(index)
|
||||||
if (valueAt?.isFor(item) == true) {
|
if (valueAt.isFor(item) == true) {
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ import android.os.Build
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import kotlinx.coroutines.launch
|
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.compat.ResolveInfoFlagsCompat
|
||||||
import org.kiwix.kiwixmobile.core.dao.NewBookDao
|
import org.kiwix.kiwixmobile.core.dao.NewBookDao
|
||||||
import org.kiwix.kiwixmobile.core.databinding.ActivityKiwixErrorBinding
|
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.extensions.toast
|
||||||
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
|
import org.kiwix.kiwixmobile.core.reader.ZimReaderContainer
|
||||||
import org.kiwix.kiwixmobile.core.utils.CRASH_AND_FEEDBACK_EMAIL_ADDRESS
|
import org.kiwix.kiwixmobile.core.utils.CRASH_AND_FEEDBACK_EMAIL_ADDRESS
|
||||||
@ -86,6 +86,7 @@ open class ErrorActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
setupReportButton()
|
setupReportButton()
|
||||||
activityKiwixErrorBinding?.restartButton?.setOnClickListener { restartApp() }
|
activityKiwixErrorBinding?.restartButton?.setOnClickListener { restartApp() }
|
||||||
|
activityKiwixErrorBinding?.root.applyEdgeToEdgeInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@ -101,7 +102,7 @@ open class ErrorActivity : BaseActivity() {
|
|||||||
val targetedIntents = createEmailIntents(emailIntent, activities)
|
val targetedIntents = createEmailIntents(emailIntent, activities)
|
||||||
if (activities.isNotEmpty() && targetedIntents.isNotEmpty()) {
|
if (activities.isNotEmpty() && targetedIntents.isNotEmpty()) {
|
||||||
val chooserIntent =
|
val chooserIntent =
|
||||||
Intent.createChooser(targetedIntents.removeFirst(), "Send email...")
|
Intent.createChooser(targetedIntents.removeAt(0), "Send email...")
|
||||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
|
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedIntents.toTypedArray())
|
||||||
sendEmailLauncher.launch(chooserIntent)
|
sendEmailLauncher.launch(chooserIntent)
|
||||||
} else {
|
} else {
|
||||||
@ -245,7 +246,7 @@ open class ErrorActivity : BaseActivity() {
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
private fun externalFileDetails(): String =
|
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 {
|
private fun safeContains(extras: Bundle): Boolean {
|
||||||
return try {
|
return try {
|
||||||
@ -272,7 +273,7 @@ open class ErrorActivity : BaseActivity() {
|
|||||||
private val versionName: String
|
private val versionName: String
|
||||||
@SuppressLint("WrongConstant")
|
@SuppressLint("WrongConstant")
|
||||||
get() = packageManager
|
get() = packageManager
|
||||||
.getPackageInformation(packageName, ZERO).versionName
|
.getPackageInformation(packageName, ZERO).versionName.toString()
|
||||||
|
|
||||||
private fun toStackTraceString(exception: Throwable): String =
|
private fun toStackTraceString(exception: Throwable): String =
|
||||||
try {
|
try {
|
||||||
|
@ -24,11 +24,13 @@ import android.app.Activity
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
@ -199,4 +201,18 @@ object ActivityExtensions {
|
|||||||
val isWideEnough = configuration.smallestScreenWidthDp >= 600
|
val isWideEnough = configuration.smallestScreenWidthDp >= 600
|
||||||
return isLargeOrXLarge && isWideEnough
|
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
|
package org.kiwix.kiwixmobile.core.extensions
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Build
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.ViewModelProviders
|
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
|
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
|
||||||
|
|
||||||
inline fun <reified T : ViewModel> Fragment.viewModel(
|
inline fun <reified T : ViewModel> Fragment.viewModel(
|
||||||
@ -55,3 +62,29 @@ fun View.closeKeyboard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val Fragment.coreMainActivity get() = activity as CoreMainActivity
|
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.annotation.SuppressLint
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.Window
|
import android.view.Window
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.appcompat.widget.TooltipCompat
|
import androidx.appcompat.widget.TooltipCompat
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
||||||
@ -92,7 +95,7 @@ fun View.showFullScreenMode(window: Window) {
|
|||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
WindowInsetsControllerCompat(window, window.decorView).apply {
|
WindowInsetsControllerCompat(window, window.decorView).apply {
|
||||||
hide(WindowInsetsCompat.Type.statusBars())
|
hide(WindowInsetsCompat.Type.systemBars())
|
||||||
hide(WindowInsetsCompat.Type.displayCutout())
|
hide(WindowInsetsCompat.Type.displayCutout())
|
||||||
systemBarsBehavior =
|
systemBarsBehavior =
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
@ -106,10 +109,10 @@ fun View.showFullScreenMode(window: Window) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun View.closeFullScreenMode(window: Window) {
|
fun View.closeFullScreenMode(window: Window) {
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, true)
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
WindowInsetsControllerCompat(window, window.decorView).apply {
|
WindowInsetsControllerCompat(window, window.decorView).apply {
|
||||||
show(WindowInsetsCompat.Type.statusBars())
|
show(WindowInsetsCompat.Type.systemBars())
|
||||||
show(WindowInsetsCompat.Type.displayCutout())
|
show(WindowInsetsCompat.Type.displayCutout())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,3 +122,31 @@ fun View.closeFullScreenMode(window: Window) {
|
|||||||
clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
|
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()
|
reopenBook()
|
||||||
showTabSwitcher()
|
showTabSwitcher()
|
||||||
setUpWithTextToSpeech(tempWebViewListForUndo.last())
|
setUpWithTextToSpeech(tempWebViewListForUndo[tempWebViewListForUndo.lastIndex])
|
||||||
updateBottomToolbarVisibility()
|
updateBottomToolbarVisibility()
|
||||||
safelyAddWebView(tempWebViewListForUndo.last())
|
safelyAddWebView(tempWebViewListForUndo[tempWebViewListForUndo.lastIndex])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +229,7 @@ abstract class CorePrefsFragment :
|
|||||||
@Suppress("TooGenericExceptionThrown")
|
@Suppress("TooGenericExceptionThrown")
|
||||||
get() = try {
|
get() = try {
|
||||||
requireActivity().packageManager
|
requireActivity().packageManager
|
||||||
.getPackageInformation(requireActivity().packageName, 0).versionName
|
.getPackageInformation(requireActivity().packageName, 0).versionName.toString()
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
throw RuntimeException(e)
|
throw RuntimeException(e)
|
||||||
}
|
}
|
||||||
|
@ -129,7 +129,7 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
|
|||||||
get() = sharedPreferences.getInt(STORAGE_POSITION, 0)
|
get() = sharedPreferences.getInt(STORAGE_POSITION, 0)
|
||||||
|
|
||||||
fun defaultStorage(): String =
|
fun defaultStorage(): String =
|
||||||
getExternalFilesDirs(context, null)[0]?.path
|
context.getExternalFilesDirs(null)[0]?.path
|
||||||
?: context.filesDir.path // a workaround for emulators
|
?: context.filesDir.path // a workaround for emulators
|
||||||
|
|
||||||
fun defaultPublicStorage(): String =
|
fun defaultPublicStorage(): String =
|
||||||
|
@ -30,7 +30,6 @@ import android.os.storage.StorageManager
|
|||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -583,7 +582,7 @@ object FileUtils {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getDemoFilePathForCustomApp(context: Context) =
|
fun getDemoFilePathForCustomApp(context: Context) =
|
||||||
"${ContextCompat.getExternalFilesDirs(context, null)[0]}/demo.zim"
|
"${context.getExternalFilesDirs(null)[0]}/demo.zim"
|
||||||
|
|
||||||
@SuppressLint("Recycle")
|
@SuppressLint("Recycle")
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<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:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
android:background="@android:color/transparent">
|
android:background="@android:color/transparent">
|
||||||
|
|
||||||
<include layout="@layout/layout_standard_app_bar" />
|
<include layout="@layout/layout_standard_app_bar" />
|
||||||
@ -30,19 +30,20 @@
|
|||||||
android:id="@+id/navigationHistoryRecyclerView"
|
android:id="@+id/navigationHistoryRecyclerView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
tools:listitem="@layout/item_bookmark_history"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/app_bar" />
|
app:layout_constraintTop_toBottomOf="@+id/app_bar"
|
||||||
|
tools:listitem="@layout/item_bookmark_history" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/searchNoResults"
|
android:id="@+id/searchNoResults"
|
||||||
style="@style/no_content"
|
style="@style/no_content"
|
||||||
android:text="@string/no_history"
|
android:text="@string/no_history"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
@ -4,5 +4,5 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:fitsSystemWindows="true"
|
android:clipToPadding="false"
|
||||||
android:focusable="true" />
|
android:focusable="true" />
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:context=".help.HelpFragment">
|
tools:context=".help.HelpFragment">
|
||||||
|
|
||||||
<include layout="@layout/layout_standard_app_bar" />
|
<include layout="@layout/layout_standard_app_bar" />
|
||||||
@ -45,6 +44,7 @@
|
|||||||
android:id="@+id/activity_help_recycler_view"
|
android:id="@+id/activity_help_recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/app_bar"
|
android:id="@+id/app_bar"
|
||||||
@ -17,8 +16,8 @@
|
|||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:popupTheme="@style/KiwixTheme"
|
|
||||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar"
|
||||||
|
app:popupTheme="@style/KiwixTheme"
|
||||||
tools:showIn="@layout/fragment_search" />
|
tools:showIn="@layout/fragment_search" />
|
||||||
|
|
||||||
<androidx.appcompat.widget.SwitchCompat
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
@ -42,6 +41,7 @@
|
|||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recycler_view"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
@ -20,8 +20,7 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/navigation_fragment_main_drawer_layout"
|
android:id="@+id/navigation_fragment_main_drawer_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:animateLayoutChanges="true"
|
android:animateLayoutChanges="true"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
@ -18,12 +17,13 @@
|
|||||||
android:id="@+id/search_list"
|
android:id="@+id/search_list"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="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_constraintBottom_toTopOf="@+id/loadingMoreDataIndicator"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
android:contentDescription="@string/searched_list"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:layout_marginTop="?actionBarSize"
|
|
||||||
tools:listitem="@layout/list_item_search" />
|
tools:listitem="@layout/list_item_search" />
|
||||||
|
|
||||||
<androidx.core.widget.ContentLoadingProgressBar
|
<androidx.core.widget.ContentLoadingProgressBar
|
||||||
@ -32,10 +32,10 @@
|
|||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
android:src="@drawable/ic_home_kiwix_banner" />
|
android:src="@drawable/ic_home_kiwix_banner" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -26,8 +26,7 @@
|
|||||||
|
|
||||||
<org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout
|
<org.kiwix.kiwixmobile.core.utils.NestedCoordinatorLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/activity_main_content_frame"
|
android:id="@+id/activity_main_content_frame"
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -16,5 +16,6 @@
|
|||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/device_list"
|
android:id="@+id/device_list"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:fitsSystemWindows="true">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/tab_switcher_recycler_view"
|
android:id="@+id/tab_switcher_recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
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="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_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_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="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_pause">{{identical|pause}}</string>
|
||||||
<string name="tts_resume">{{identical|resume}}</string>
|
<string name="tts_resume">{{identical|resume}}</string>
|
||||||
|
@ -82,7 +82,6 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Base.MaterialThemeBuilder" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
<style name="Base.MaterialThemeBuilder" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||||
|
|
||||||
<item name="android:statusBarColor" tools:ignore="NewApi">@color/black</item>
|
<item name="android:statusBarColor" tools:ignore="NewApi">@color/black</item>
|
||||||
<item name="actionModeBackground">@color/cornflower_blue</item>
|
<item name="actionModeBackground">@color/cornflower_blue</item>
|
||||||
<item name="windowActionModeOverlay">true</item>
|
<item name="windowActionModeOverlay">true</item>
|
||||||
@ -92,6 +91,8 @@
|
|||||||
<item name="colorAccent">?colorSecondary</item>
|
<item name="colorAccent">?colorSecondary</item>
|
||||||
<item name="android:navigationBarColor">@color/black</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>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -25,7 +25,7 @@ import io.objectbox.Box
|
|||||||
import io.objectbox.query.Query
|
import io.objectbox.query.Query
|
||||||
import io.objectbox.query.QueryBuilder
|
import io.objectbox.query.QueryBuilder
|
||||||
import kotlinx.coroutines.flow.flowOf
|
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.Nested
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity
|
import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity
|
||||||
@ -43,7 +43,7 @@ internal class NewRecentSearchDaoTest {
|
|||||||
@Nested
|
@Nested
|
||||||
inner class RecentSearchTests {
|
inner class RecentSearchTests {
|
||||||
@Test
|
@Test
|
||||||
fun `recentSearches searches by Id passed`() = runBlockingTest {
|
fun `recentSearches searches by Id passed`() = runTest {
|
||||||
val zimId = "id"
|
val zimId = "id"
|
||||||
val queryResult = listOf<RecentSearchEntity>(recentSearchEntity())
|
val queryResult = listOf<RecentSearchEntity>(recentSearchEntity())
|
||||||
expectFromRecentSearches(queryResult, zimId)
|
expectFromRecentSearches(queryResult, zimId)
|
||||||
@ -56,7 +56,7 @@ internal class NewRecentSearchDaoTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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())
|
val queryResult = listOf<RecentSearchEntity>(recentSearchEntity())
|
||||||
expectFromRecentSearches(queryResult, "")
|
expectFromRecentSearches(queryResult, "")
|
||||||
newRecentSearchDao.recentSearches(null)
|
newRecentSearchDao.recentSearches(null)
|
||||||
@ -68,7 +68,7 @@ internal class NewRecentSearchDaoTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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())
|
val queryResult = listOf<RecentSearchEntity>(recentSearchEntity(), recentSearchEntity())
|
||||||
expectFromRecentSearches(queryResult, "")
|
expectFromRecentSearches(queryResult, "")
|
||||||
newRecentSearchDao.recentSearches("")
|
newRecentSearchDao.recentSearches("")
|
||||||
@ -80,7 +80,7 @@ internal class NewRecentSearchDaoTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `recentSearches searches returns a limitedNumber of entities`() = runBlockingTest {
|
fun `recentSearches searches returns a limitedNumber of entities`() = runTest {
|
||||||
val searchResults: List<RecentSearchEntity> =
|
val searchResults: List<RecentSearchEntity> =
|
||||||
(0..200).map { recentSearchEntity(searchTerm = "$it") }
|
(0..200).map { recentSearchEntity(searchTerm = "$it") }
|
||||||
expectFromRecentSearches(searchResults, "")
|
expectFromRecentSearches(searchResults, "")
|
||||||
|
@ -26,7 +26,13 @@ import io.reactivex.plugins.RxJavaPlugins
|
|||||||
import io.reactivex.processors.PublishProcessor
|
import io.reactivex.processors.PublishProcessor
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import io.reactivex.schedulers.TestScheduler
|
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.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.extension.ExtendWith
|
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.InstantExecutorExtension
|
||||||
import org.kiwix.sharedFunctions.setScheduler
|
import org.kiwix.sharedFunctions.setScheduler
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@ExtendWith(InstantExecutorExtension::class)
|
@ExtendWith(InstantExecutorExtension::class)
|
||||||
internal class PageViewModelTest {
|
internal class PageViewModelTest {
|
||||||
private val pageDao: PageDao = mockk()
|
private val pageDao: PageDao = mockk()
|
||||||
@ -69,6 +76,7 @@ internal class PageViewModelTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun init() {
|
fun init() {
|
||||||
|
Dispatchers.setMain(UnconfinedTestDispatcher())
|
||||||
clearAllMocks()
|
clearAllMocks()
|
||||||
every { zimReaderContainer.id } returns "id"
|
every { zimReaderContainer.id } returns "id"
|
||||||
every { zimReaderContainer.name } returns "zimName"
|
every { zimReaderContainer.name } returns "zimName"
|
||||||
@ -77,6 +85,11 @@ internal class PageViewModelTest {
|
|||||||
viewModel = TestablePageViewModel(zimReaderContainer, sharedPreferenceUtil, pageDao)
|
viewModel = TestablePageViewModel(zimReaderContainer, sharedPreferenceUtil, pageDao)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun tearDown() {
|
||||||
|
Dispatchers.resetMain()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `initial state is Initialising`() {
|
fun `initial state is Initialising`() {
|
||||||
viewModel.state.test().assertValue(pageState())
|
viewModel.state.test().assertValue(pageState())
|
||||||
|
@ -24,7 +24,6 @@ import io.mockk.clearAllMocks
|
|||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@ -33,11 +32,10 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.consumeAsFlow
|
import kotlinx.coroutines.flow.consumeAsFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||||
import kotlinx.coroutines.test.TestCoroutineScope
|
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.advanceUntilIdle
|
||||||
import kotlinx.coroutines.test.resetMain
|
import kotlinx.coroutines.test.resetMain
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.coroutines.test.setMain
|
import kotlinx.coroutines.test.setMain
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
@ -84,7 +82,7 @@ internal class SearchViewModelTest {
|
|||||||
private val zimReaderContainer: ZimReaderContainer = mockk()
|
private val zimReaderContainer: ZimReaderContainer = mockk()
|
||||||
private val searchResultGenerator: SearchResultGenerator = mockk()
|
private val searchResultGenerator: SearchResultGenerator = mockk()
|
||||||
private val zimFileReader: ZimFileReader = mockk()
|
private val zimFileReader: ZimFileReader = mockk()
|
||||||
private val testDispatcher = TestCoroutineDispatcher()
|
private val testDispatcher = StandardTestDispatcher()
|
||||||
private val searchMutex: Mutex = mockk()
|
private val searchMutex: Mutex = mockk()
|
||||||
|
|
||||||
lateinit var viewModel: SearchViewModel
|
lateinit var viewModel: SearchViewModel
|
||||||
@ -101,7 +99,7 @@ internal class SearchViewModelTest {
|
|||||||
Dispatchers.resetMain()
|
Dispatchers.resetMain()
|
||||||
Dispatchers.setMain(testDispatcher)
|
Dispatchers.setMain(testDispatcher)
|
||||||
clearAllMocks()
|
clearAllMocks()
|
||||||
recentsFromDb = Channel(kotlinx.coroutines.channels.Channel.UNLIMITED)
|
recentsFromDb = Channel(Channel.UNLIMITED)
|
||||||
every { zimReaderContainer.zimFileReader } returns zimFileReader
|
every { zimReaderContainer.zimFileReader } returns zimFileReader
|
||||||
coEvery {
|
coEvery {
|
||||||
searchResultGenerator.generateSearchResults("", zimFileReader)
|
searchResultGenerator.generateSearchResults("", zimFileReader)
|
||||||
@ -115,7 +113,7 @@ internal class SearchViewModelTest {
|
|||||||
@Nested
|
@Nested
|
||||||
inner class StateTests {
|
inner class StateTests {
|
||||||
@Test
|
@Test
|
||||||
fun `initial state is Initialising`() = runBlockingTest {
|
fun `initial state is Initialising`() = runTest {
|
||||||
viewModel.state.test(this).assertValue(
|
viewModel.state.test(this).assertValue(
|
||||||
SearchState("", SearchResultsWithTerm("", null, searchMutex), emptyList(), FromWebView)
|
SearchState("", SearchResultsWithTerm("", null, searchMutex), emptyList(), FromWebView)
|
||||||
).finish()
|
).finish()
|
||||||
@ -152,12 +150,12 @@ internal class SearchViewModelTest {
|
|||||||
inner class ActionMapping {
|
inner class ActionMapping {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ExitedSearch offers PopFragmentBackstack`() = runBlockingTest {
|
fun `ExitedSearch offers PopFragmentBackstack`() = runTest {
|
||||||
actionResultsInEffects(ExitedSearch, PopFragmentBackstack)
|
actionResultsInEffects(ExitedSearch, PopFragmentBackstack)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `OnItemClick offers Saves and Opens`() = runBlockingTest {
|
fun `OnItemClick offers Saves and Opens`() = runTest {
|
||||||
val searchListItem = RecentSearchListItem("", "")
|
val searchListItem = RecentSearchListItem("", "")
|
||||||
actionResultsInEffects(
|
actionResultsInEffects(
|
||||||
OnItemClick(searchListItem),
|
OnItemClick(searchListItem),
|
||||||
@ -170,7 +168,7 @@ internal class SearchViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `OnOpenInNewTabClick offers Saves and Opens in new tab`() = runBlockingTest {
|
fun `OnOpenInNewTabClick offers Saves and Opens in new tab`() = runTest {
|
||||||
val searchListItem = RecentSearchListItem("", "")
|
val searchListItem = RecentSearchListItem("", "")
|
||||||
actionResultsInEffects(
|
actionResultsInEffects(
|
||||||
OnOpenInNewTabClick(searchListItem),
|
OnOpenInNewTabClick(searchListItem),
|
||||||
@ -183,7 +181,7 @@ internal class SearchViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `OnItemLongClick offers Saves and Opens`() = runBlockingTest {
|
fun `OnItemLongClick offers Saves and Opens`() = runTest {
|
||||||
val searchListItem = RecentSearchListItem("", "")
|
val searchListItem = RecentSearchListItem("", "")
|
||||||
actionResultsInEffects(
|
actionResultsInEffects(
|
||||||
OnItemLongClick(searchListItem),
|
OnItemLongClick(searchListItem),
|
||||||
@ -192,12 +190,12 @@ internal class SearchViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ClickedSearchInText offers SearchInPreviousScreen`() = runBlockingTest {
|
fun `ClickedSearchInText offers SearchInPreviousScreen`() = runTest {
|
||||||
actionResultsInEffects(ClickedSearchInText, SearchInPreviousScreen(""))
|
actionResultsInEffects(ClickedSearchInText, SearchInPreviousScreen(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ConfirmedDelete offers Delete and Toast`() = runBlockingTest {
|
fun `ConfirmedDelete offers Delete and Toast`() = runTest {
|
||||||
val searchListItem = RecentSearchListItem("", "")
|
val searchListItem = RecentSearchListItem("", "")
|
||||||
actionResultsInEffects(
|
actionResultsInEffects(
|
||||||
ConfirmedDelete(searchListItem),
|
ConfirmedDelete(searchListItem),
|
||||||
@ -207,7 +205,7 @@ internal class SearchViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `CreatedWithArguments offers SearchArgumentProcessing`() = runBlockingTest {
|
fun `CreatedWithArguments offers SearchArgumentProcessing`() = runTest {
|
||||||
val bundle = mockk<Bundle>()
|
val bundle = mockk<Bundle>()
|
||||||
actionResultsInEffects(
|
actionResultsInEffects(
|
||||||
CreatedWithArguments(bundle),
|
CreatedWithArguments(bundle),
|
||||||
@ -216,7 +214,7 @@ internal class SearchViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ReceivedPromptForSpeechInput offers StartSpeechInput`() = runBlockingTest {
|
fun `ReceivedPromptForSpeechInput offers StartSpeechInput`() = runTest {
|
||||||
actionResultsInEffects(
|
actionResultsInEffects(
|
||||||
ReceivedPromptForSpeechInput,
|
ReceivedPromptForSpeechInput,
|
||||||
StartSpeechInput(viewModel.actions)
|
StartSpeechInput(viewModel.actions)
|
||||||
@ -224,7 +222,7 @@ internal class SearchViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `StartSpeechInputFailed offers ShowToast`() = runBlockingTest {
|
fun `StartSpeechInputFailed offers ShowToast`() = runTest {
|
||||||
actionResultsInEffects(
|
actionResultsInEffects(
|
||||||
StartSpeechInputFailed,
|
StartSpeechInputFailed,
|
||||||
ShowToast(string.speech_not_supported)
|
ShowToast(string.speech_not_supported)
|
||||||
@ -232,22 +230,29 @@ internal class SearchViewModelTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ActivityResultReceived offers ProcessActivityResult`() = runBlockingTest {
|
fun `ActivityResultReceived offers ProcessActivityResult`() = runTest {
|
||||||
actionResultsInEffects(
|
actionResultsInEffects(
|
||||||
ActivityResultReceived(0, 1, null),
|
ActivityResultReceived(0, 1, null),
|
||||||
ProcessActivityResult(0, 1, null, viewModel.actions)
|
ProcessActivityResult(0, 1, null, viewModel.actions)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TestCoroutineScope.actionResultsInEffects(
|
private fun TestScope.actionResultsInEffects(
|
||||||
action: Action,
|
action: Action,
|
||||||
vararg effects: SideEffect<*>
|
vararg effects: SideEffect<*>
|
||||||
) {
|
) {
|
||||||
viewModel.effects
|
if (effects.size > 1) return
|
||||||
.test(this)
|
val collectedEffects = mutableListOf<SideEffect<*>>()
|
||||||
.also { viewModel.actions.trySend(action).isSuccess }
|
val job = launch {
|
||||||
.assertValues(*effects)
|
viewModel.effects.collect {
|
||||||
.finish()
|
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>(
|
class TestObserver<T>(
|
||||||
scope: CoroutineScope,
|
private val scope: TestScope,
|
||||||
flow: Flow<T>
|
private val flow: Flow<T>
|
||||||
) {
|
) {
|
||||||
private val values = mutableListOf<T>()
|
private val values = mutableListOf<T>()
|
||||||
private val job: Job = scope.launch {
|
private val completionChannel = Channel<Unit>()
|
||||||
|
private var job: Job? = null
|
||||||
|
|
||||||
|
suspend fun startCollecting() {
|
||||||
|
job = scope.launch {
|
||||||
flow.collect {
|
flow.collect {
|
||||||
values.add(it)
|
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)
|
assertThat(values.toList()).containsExactlyElementsOf(this.values)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertValue(value: T): TestObserver<T> {
|
suspend fun assertValue(value: T): TestObserver<T> {
|
||||||
|
awaitCompletion()
|
||||||
assertThat(values.last()).isEqualTo(value)
|
assertThat(values.last()).isEqualTo(value)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun finish() {
|
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) })
|
assertThat(values.last()).satisfies({ value(it) })
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ import android.content.Context
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.AssetFileDescriptor
|
import android.content.res.AssetFileDescriptor
|
||||||
import android.content.res.AssetManager
|
import android.content.res.AssetManager
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import org.kiwix.kiwixmobile.core.utils.files.Log
|
import org.kiwix.kiwixmobile.core.utils.files.Log
|
||||||
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasBothFiles
|
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasBothFiles
|
||||||
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasFile
|
import org.kiwix.kiwixmobile.custom.main.ValidationState.HasFile
|
||||||
@ -96,7 +95,7 @@ class CustomFileValidator @Inject constructor(private val context: Context) {
|
|||||||
|
|
||||||
private fun obbFiles() =
|
private fun obbFiles() =
|
||||||
scanDirs(
|
scanDirs(
|
||||||
ContextCompat.getObbDirs(context).filterNotNull().filter(File::exists).toTypedArray(),
|
context.obbDirs.filterNotNull().filter(File::exists).toTypedArray(),
|
||||||
"obb"
|
"obb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -105,7 +104,7 @@ class CustomFileValidator @Inject constructor(private val context: Context) {
|
|||||||
val directoryList = mutableListOf<File>()
|
val directoryList = mutableListOf<File>()
|
||||||
|
|
||||||
// Get the external files directories for the app
|
// Get the external files directories for the app
|
||||||
ContextCompat.getExternalFilesDirs(context, null).filterNotNull()
|
context.getExternalFilesDirs(null).filterNotNull()
|
||||||
.filter(File::exists)
|
.filter(File::exists)
|
||||||
.forEach { dir ->
|
.forEach { dir ->
|
||||||
// Check if the directory's parent is not null
|
// 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.custom.R
|
||||||
import org.kiwix.kiwixmobile.core.R.string
|
import org.kiwix.kiwixmobile.core.R.string
|
||||||
import org.kiwix.kiwixmobile.core.R.drawable
|
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.customActivityComponent
|
||||||
import org.kiwix.kiwixmobile.custom.databinding.ActivityCustomMainBinding
|
import org.kiwix.kiwixmobile.custom.databinding.ActivityCustomMainBinding
|
||||||
|
|
||||||
@ -84,6 +85,7 @@ class CustomMainActivity : CoreMainActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
activityCustomMainBinding = ActivityCustomMainBinding.inflate(layoutInflater)
|
activityCustomMainBinding = ActivityCustomMainBinding.inflate(layoutInflater)
|
||||||
setContentView(activityCustomMainBinding.root)
|
setContentView(activityCustomMainBinding.root)
|
||||||
|
activityCustomMainBinding.root.applyEdgeToEdgeInsets()
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
android:id="@+id/custom_drawer_container"
|
android:id="@+id/custom_drawer_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
tools:ignore="UnusedIds">
|
tools:ignore="UnusedIds">
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
@ -38,7 +37,6 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="start"
|
android:layout_gravity="start"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
app:headerLayout="@layout/nav_main"
|
app:headerLayout="@layout/nav_main"
|
||||||
app:menu="@menu/menu_drawer_main" />
|
app:menu="@menu/menu_drawer_main" />
|
||||||
|
|
||||||
@ -48,6 +46,5 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
android:fitsSystemWindows="true"
|
|
||||||
app:headerLayout="@layout/drawer_right" />
|
app:headerLayout="@layout/drawer_right" />
|
||||||
</androidx.drawerlayout.widget.DrawerLayout>
|
</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
|
#Mon Dec 19 16:13:45 IST 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
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
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<lint>
|
<lint>
|
||||||
<issue id="TypographyQuotes">
|
<issue id="TypographyQuotes" severity="warning">
|
||||||
<ignore path="**-qq/**.xml" />
|
<ignore path="**-qq/**.xml" />
|
||||||
<ignore path="**-iw/**.xml" />
|
<ignore path="**-iw/**.xml" />
|
||||||
</issue>
|
</issue>
|
||||||
@ -48,4 +48,7 @@
|
|||||||
<issue id="DataExtractionRules" severity="warning" />
|
<issue id="DataExtractionRules" severity="warning" />
|
||||||
<issue id="ObsoleteSdkInt" severity="warning" />
|
<issue id="ObsoleteSdkInt" severity="warning" />
|
||||||
<issue id="AppLinksAutoVerify" severity="warning" />
|
<issue id="AppLinksAutoVerify" severity="warning" />
|
||||||
|
<issue id="CheckResult">
|
||||||
|
<ignore path="**/androidTest/**.kt" />
|
||||||
|
</issue>
|
||||||
</lint>
|
</lint>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user