Implemented proper dark mode for ZIM files.

* Removed the custom logic for color inversion of WebView content so that ZIM files can automatically apply the `prefered-color-scheme` setting based on the app theme.
* Removed unused code and test cases from the project.
This commit is contained in:
MohitMaliFtechiz 2025-09-15 19:46:46 +05:30
parent 484c425597
commit 6c0ac69043
9 changed files with 3 additions and 557 deletions

View File

@ -1,79 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.main
import android.view.View
import androidx.compose.ui.test.filter
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.performClick
import applyWithViewHierarchyPrinting
import org.junit.Assert.assertEquals
import org.kiwix.kiwixmobile.BaseRobot
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.main.KiwixWebView
import org.kiwix.kiwixmobile.core.settings.DIALOG_PREFERENCE_ITEM_TESTING_TAG
import org.kiwix.kiwixmobile.testutils.TestUtils.testFlakyView
import org.kiwix.kiwixmobile.utils.StandardActions.enterSettings
import org.kiwix.kiwixmobile.utils.StandardActions.openDrawer
fun darkModeViewPainter(func: DarkModeViewPainterRobot.() -> Unit) =
DarkModeViewPainterRobot().applyWithViewHierarchyPrinting(func)
class DarkModeViewPainterRobot : BaseRobot() {
fun openSettings(coreMainActivity: CoreMainActivity, composeContentTest: ComposeContentTestRule) {
openDrawer(coreMainActivity)
enterSettings(composeContentTest)
}
fun enableTheDarkMode(composeContentTest: ComposeContentTestRule) {
testFlakyView({
composeContentTest.apply {
waitForIdle()
onAllNodesWithTag(DIALOG_PREFERENCE_ITEM_TESTING_TAG)
.filter(hasContentDescription(context.getString(R.string.on)))
.onFirst()
.performClick()
}
})
}
fun enableTheLightMode(composeContentTest: ComposeContentTestRule) {
testFlakyView({
composeContentTest.apply {
waitForIdle()
onAllNodesWithTag(DIALOG_PREFERENCE_ITEM_TESTING_TAG)
.filter(hasContentDescription(context.getString(R.string.off)))
.onFirst()
.performClick()
}
})
}
fun assertNightModeEnabled(kiwixWebView: KiwixWebView) {
assertEquals(kiwixWebView.layerType, View.LAYER_TYPE_HARDWARE)
}
fun assertLightModeEnabled(kiwixWebView: KiwixWebView) {
assertEquals(kiwixWebView.layerType, View.LAYER_TYPE_NONE)
}
}

View File

@ -1,223 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.main
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.navigation.NavOptions
import androidx.preference.PreferenceManager
import androidx.test.espresso.accessibility.AccessibilityChecks
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesCheck
import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesViews
import com.google.android.apps.common.testing.accessibility.framework.checks.SpeakableTextPresentCheck
import com.google.android.apps.common.testing.accessibility.framework.checks.TouchTargetSizeCheck
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.anyOf
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.kiwix.kiwixmobile.BaseActivityTest
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.setNavigationResultOnCurrent
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.main.ZIM_FILE_URI_KEY
import org.kiwix.kiwixmobile.core.ui.components.NAVIGATION_ICON_TESTING_TAG
import org.kiwix.kiwixmobile.core.utils.LanguageUtils
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.core.utils.TestingUtils.COMPOSE_TEST_RULE_ORDER
import org.kiwix.kiwixmobile.core.utils.TestingUtils.RETRY_RULE_ORDER
import org.kiwix.kiwixmobile.nav.destination.reader.KiwixReaderFragment
import org.kiwix.kiwixmobile.settings.settingsRobo
import org.kiwix.kiwixmobile.testutils.RetryRule
import org.kiwix.kiwixmobile.testutils.TestUtils
import org.kiwix.kiwixmobile.testutils.TestUtils.TEST_PAUSE_MS_FOR_DOWNLOAD_TEST
import org.kiwix.kiwixmobile.testutils.TestUtils.waitUntilTimeout
import org.kiwix.kiwixmobile.ui.KiwixDestination
import java.io.File
import java.io.FileOutputStream
import java.io.OutputStream
class DarkModeViewPainterTest : BaseActivityTest() {
@Rule(order = RETRY_RULE_ORDER)
@JvmField
val retryRule = RetryRule()
@get:Rule(order = COMPOSE_TEST_RULE_ORDER)
val composeTestRule = createAndroidComposeRule<KiwixMainActivity>()
private lateinit var kiwixMainActivity: KiwixMainActivity
@Before
override fun waitForIdle() {
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).apply {
if (TestUtils.isSystemUINotRespondingDialogVisible(this)) {
TestUtils.closeSystemDialogs(context, this)
}
waitForIdle()
}
PreferenceManager.getDefaultSharedPreferences(
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext
).edit {
putBoolean(SharedPreferenceUtil.PREF_SHOW_INTRO, false)
putBoolean(SharedPreferenceUtil.PREF_WIFI_ONLY, false)
putBoolean(SharedPreferenceUtil.PREF_IS_TEST, true)
putBoolean(SharedPreferenceUtil.PREF_EXTERNAL_LINK_POPUP, true)
putBoolean(SharedPreferenceUtil.PREF_SHOW_SHOWCASE, false)
putString(SharedPreferenceUtil.PREF_LANG, "en")
}
composeTestRule.apply {
kiwixMainActivity = activity
runOnUiThread {
LanguageUtils.handleLocaleChange(
kiwixMainActivity,
"en",
SharedPreferenceUtil(context)
)
}
waitForIdle()
}
}
init {
AccessibilityChecks.enable().apply {
setRunChecksFromRootView(true)
setSuppressingResultMatcher(
anyOf(
allOf(
matchesCheck(TouchTargetSizeCheck::class.java),
matchesViews(withContentDescription("More options"))
),
matchesCheck(SpeakableTextPresentCheck::class.java)
)
)
}
}
@Test
fun testDarkMode() {
composeTestRule.waitForIdle()
composeTestRule.runOnUiThread {
composeTestRule.activity.navigate(KiwixDestination.Library.route)
}
toggleDarkMode(true)
openZimFileInReader()
verifyDarkMode(true)
toggleDarkMode(false)
openZimFileInReader()
verifyDarkMode(false)
}
private fun openZimFileInReader() {
kiwixMainActivity = composeTestRule.activity
composeTestRule.apply {
waitForIdle()
waitUntilTimeout()
onNodeWithTag(NAVIGATION_ICON_TESTING_TAG).performClick()
waitUntilTimeout()
onNodeWithTag(BOTTOM_NAV_LIBRARY_ITEM_TESTING_TAG).performClick()
waitUntilTimeout()
}
loadZimFileInReader()
}
private fun toggleDarkMode(enable: Boolean) {
composeTestRule.waitForIdle()
darkModeViewPainter { openSettings(kiwixMainActivity as CoreMainActivity, composeTestRule) }
settingsRobo { clickNightModePreference(composeTestRule) }
darkModeViewPainter {
if (enable) {
enableTheDarkMode(composeTestRule)
} else {
enableTheLightMode(composeTestRule)
}
}
}
private fun verifyDarkMode(isEnabled: Boolean) {
var kiwixReaderFragment: KiwixReaderFragment? = null
composeTestRule.waitForIdle()
kiwixMainActivity = composeTestRule.activity
composeTestRule.waitUntil(TEST_PAUSE_MS_FOR_DOWNLOAD_TEST.toLong()) {
kiwixReaderFragment =
kiwixMainActivity.supportFragmentManager.fragments
.filterIsInstance<KiwixReaderFragment>()
.firstOrNull()
kiwixReaderFragment?.getCurrentWebView() != null
}
val currentWebView = kiwixReaderFragment?.getCurrentWebView()
currentWebView?.let {
darkModeViewPainter {
if (isEnabled) {
assertNightModeEnabled(it)
} else {
assertLightModeEnabled(it)
}
}
composeTestRule.waitForIdle()
composeTestRule.waitUntilTimeout()
} ?: run {
throw RuntimeException(
"Could not check the dark mode enable or not because zim file is not loaded in the reader"
)
}
}
private fun loadZimFileInReader() {
composeTestRule.waitForIdle()
val loadFileStream =
DarkModeViewPainterTest::class.java.classLoader.getResourceAsStream("testzim.zim")
val zimFile =
File(
context.getExternalFilesDirs(null)[0],
"testzim.zim"
)
if (zimFile.exists()) zimFile.delete()
zimFile.createNewFile()
loadFileStream.use { inputStream ->
val outputStream: OutputStream = FileOutputStream(zimFile)
outputStream.use { it ->
val buffer = ByteArray(inputStream.available())
var length: Int
while (inputStream.read(buffer).also { length = it } > 0) {
it.write(buffer, 0, length)
}
}
}
composeTestRule.runOnIdle {
val navOptions = NavOptions.Builder()
.setPopUpTo(KiwixDestination.Reader.route, false)
.build()
composeTestRule.activity.apply {
navigate(KiwixDestination.Reader.route, navOptions)
setNavigationResultOnCurrent(zimFile.toUri().toString(), ZIM_FILE_URI_KEY)
}
}
composeTestRule.waitForIdle()
}
@After
fun finish() {
TestUtils.deleteTemporaryFilesOfTestCases(context)
}
}

View File

@ -1,62 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2020 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.main
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.view.View
import org.kiwix.kiwixmobile.core.DarkModeConfig
import javax.inject.Inject
/**
* DarkModeViewPainter class is used to apply respective filters to the views
* depending whether the app is in dark mode or not
* Created by yashk2000 on 24/03/2020.
*/
class DarkModeViewPainter @Inject constructor(
private val darkModeConfig: DarkModeConfig
) {
private val invertedPaint =
Paint().apply { colorFilter = ColorMatrixColorFilter(KiwixWebView.DARK_MODE_COLORS) }
@JvmOverloads
fun <T : View?> update(
view: T,
shouldActivateCriteria: ((T) -> Boolean) = { true },
vararg additionalViews: View? = emptyArray()
) {
if (darkModeConfig.isDarkModeActive()) {
if (shouldActivateCriteria(view)) {
activateDarkMode(view, *additionalViews)
}
} else {
deactivateDarkMode(view, *additionalViews)
}
}
private fun deactivateDarkMode(vararg additionalViews: View?) {
additionalViews.filterNotNull()
.forEach { it.setLayerType(View.LAYER_TYPE_NONE, null) }
}
private fun activateDarkMode(vararg additionalViews: View?) {
additionalViews.filterNotNull()
.forEach { it.setLayerType(View.LAYER_TYPE_HARDWARE, invertedPaint) }
}
}

View File

@ -188,15 +188,4 @@ open class KiwixWebView @SuppressLint("SetJavaScriptEnabled") constructor(
} }
} }
} }
companion object {
val DARK_MODE_COLORS =
floatArrayOf(
-1.0f, 0f, 0f, 0f,
255f, 0f, -1.0f, 0f,
0f, 255f, 0f, 0f,
-1.0f, 0f, 255f, 0f,
0f, 0f, 1.0f, 0f
)
}
} }

View File

@ -108,7 +108,6 @@ import org.kiwix.kiwixmobile.core.main.CompatFindActionModeCallback
import org.kiwix.kiwixmobile.core.main.CoreMainActivity import org.kiwix.kiwixmobile.core.main.CoreMainActivity
import org.kiwix.kiwixmobile.core.main.CoreSearchWidget import org.kiwix.kiwixmobile.core.main.CoreSearchWidget
import org.kiwix.kiwixmobile.core.main.CoreWebViewClient import org.kiwix.kiwixmobile.core.main.CoreWebViewClient
import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter
import org.kiwix.kiwixmobile.core.main.DocumentParser import org.kiwix.kiwixmobile.core.main.DocumentParser
import org.kiwix.kiwixmobile.core.main.DocumentParser.SectionsListener import org.kiwix.kiwixmobile.core.main.DocumentParser.SectionsListener
import org.kiwix.kiwixmobile.core.main.FIND_IN_PAGE_SEARCH_STRING import org.kiwix.kiwixmobile.core.main.FIND_IN_PAGE_SEARCH_STRING
@ -218,10 +217,6 @@ abstract class CoreReaderFragment :
@JvmField @JvmField
@Inject @Inject
var donationDialogHandler: DonationDialogHandler? = null var donationDialogHandler: DonationDialogHandler? = null
@JvmField
@Inject
var painter: DarkModeViewPainter? = null
protected var currentWebViewIndex by mutableStateOf(0) protected var currentWebViewIndex by mutableStateOf(0)
private var currentTtsWebViewIndex = 0 private var currentTtsWebViewIndex = 0
private var isFirstTimeMainPageLoaded = true private var isFirstTimeMainPageLoaded = true
@ -301,7 +296,6 @@ abstract class CoreReaderFragment :
selectedWebView = null, selectedWebView = null,
readerScreenTitle = "", readerScreenTitle = "",
showTabSwitcher = false, showTabSwitcher = false,
darkModeViewPainter = null,
currentWebViewPosition = ZERO, currentWebViewPosition = ZERO,
onTabClickListener = object : TabClickListener { onTabClickListener = object : TabClickListener {
override fun onSelectTab(position: Int) { override fun onSelectTab(position: Int) {
@ -444,7 +438,6 @@ abstract class CoreReaderFragment :
readerScreenState.update { readerScreenState.update {
copy( copy(
readerScreenTitle = context.getString(string.reader), readerScreenTitle = context.getString(string.reader),
darkModeViewPainter = darkModeViewPainter,
tocButtonItem = getTocButtonStateAndAction(), tocButtonItem = getTocButtonStateAndAction(),
appName = (requireActivity() as CoreMainActivity).appName, appName = (requireActivity() as CoreMainActivity).appName,
donateButtonClick = { donateButtonClick = {
@ -1803,7 +1796,6 @@ abstract class CoreReaderFragment :
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
updateBottomToolbarVisibility() updateBottomToolbarVisibility()
updateNightMode()
if (tts == null) { if (tts == null) {
setUpTTS() setUpTTS()
} }
@ -2108,23 +2100,12 @@ abstract class CoreReaderFragment :
getCurrentWebView()?.url?.let { webUrlsFlow.value = it } getCurrentWebView()?.url?.let { webUrlsFlow.value = it }
} }
private fun updateNightMode() {
painter?.update(
getCurrentWebView(),
::shouldActivateNightMode,
readerScreenState.value.fullScreenItem.second
)
}
private fun shouldActivateNightMode(kiwixWebView: KiwixWebView?): Boolean = kiwixWebView != null
private fun loadPrefs() { private fun loadPrefs() {
isBackToTopEnabled = sharedPreferenceUtil?.prefBackToTop == true isBackToTopEnabled = sharedPreferenceUtil?.prefBackToTop == true
isOpenNewTabInBackground = sharedPreferenceUtil?.prefNewTabBackground == true isOpenNewTabInBackground = sharedPreferenceUtil?.prefNewTabBackground == true
if (!isBackToTopEnabled) { if (!isBackToTopEnabled) {
hideBackToTopButton() hideBackToTopButton()
} }
updateNightMode()
} }
private fun showBackToTopButton() { private fun showBackToTopButton() {
@ -2307,7 +2288,6 @@ abstract class CoreReaderFragment :
} }
} }
updateBottomToolbarVisibility() updateBottomToolbarVisibility()
updateNightMode()
if (!isWebViewHistoryRestoring) { if (!isWebViewHistoryRestoring) {
saveTabStates() saveTabStates()
} }

View File

@ -120,7 +120,6 @@ import org.kiwix.kiwixmobile.core.base.FragmentActivityExtensions
import org.kiwix.kiwixmobile.core.downloader.downloadManager.HUNDERED import org.kiwix.kiwixmobile.core.downloader.downloadManager.HUNDERED
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
import org.kiwix.kiwixmobile.core.extensions.update import org.kiwix.kiwixmobile.core.extensions.update
import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter
import org.kiwix.kiwixmobile.core.main.KiwixWebView import org.kiwix.kiwixmobile.core.main.KiwixWebView
import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar import org.kiwix.kiwixmobile.core.ui.components.ContentLoadingProgressBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
@ -458,8 +457,7 @@ private fun TabSwitcherAnimated(state: ReaderScreenState) {
state.kiwixWebViewList, state.kiwixWebViewList,
state.currentWebViewPosition, state.currentWebViewPosition,
state.onTabClickListener, state.onTabClickListener,
state.onCloseAllTabs, state.onCloseAllTabs
state.darkModeViewPainter
) )
} }
} }
@ -776,8 +774,7 @@ fun TabSwitcherView(
webViews: List<KiwixWebView>, webViews: List<KiwixWebView>,
selectedIndex: Int, selectedIndex: Int,
onTabClickListener: TabClickListener, onTabClickListener: TabClickListener,
onCloseAllTabs: () -> Unit, onCloseAllTabs: () -> Unit
painter: DarkModeViewPainter?
) { ) {
val state = rememberLazyListState() val state = rememberLazyListState()
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
@ -797,12 +794,6 @@ fun TabSwitcherView(
?: context.getString(R.string.menu_home) ?: context.getString(R.string.menu_home)
} }
LaunchedEffect(webView) {
if (title != context.getString(R.string.menu_home)) {
painter?.update(webView)
}
}
TabItemView( TabItemView(
index = index, index = index,
title = title, title = title,

View File

@ -20,7 +20,6 @@ package org.kiwix.kiwixmobile.core.main.reader
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import org.kiwix.kiwixmobile.core.main.DarkModeViewPainter
import org.kiwix.kiwixmobile.core.main.KiwixWebView import org.kiwix.kiwixmobile.core.main.KiwixWebView
import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable
@ -139,7 +138,6 @@ data class ReaderScreenState(
*/ */
val shouldShowBottomAppBar: Boolean, val shouldShowBottomAppBar: Boolean,
val readerScreenTitle: String, val readerScreenTitle: String,
val darkModeViewPainter: DarkModeViewPainter?,
/** /**
* Manages the click event on tabs. * Manages the click event on tabs.
*/ */

View File

@ -341,12 +341,7 @@ class ZimFileReader constructor(
val output = ByteArrayOutputStream() val output = ByteArrayOutputStream()
when { when {
uri.endsWith(UNINITIALISER_ADDRESS) -> output.write(UNINITIALISE_HTML.toByteArray()) uri.endsWith(UNINITIALISER_ADDRESS) -> output.write(UNINITIALISE_HTML.toByteArray())
item != null -> { item != null -> output.write(item.data.data)
if ("text/css" == item.mimetype && darkModeConfig.isDarkModeActive()) {
output.write(INVERT_IMAGES_VIDEO.toByteArray())
}
output.write(item.data.data)
}
} }
ByteArrayInputStream(output.toByteArray()) ByteArrayInputStream(output.toByteArray())
@ -415,21 +410,6 @@ class ZimFileReader constructor(
val UI_URI: Uri? = "content://org.kiwix.ui/".toUri() val UI_URI: Uri? = "content://org.kiwix.ui/".toUri()
const val CONTENT_PREFIX = "https://kiwix.app/" const val CONTENT_PREFIX = "https://kiwix.app/"
private val INVERT_IMAGES_VIDEO =
"""
img, video, div[poster] {
-webkit-filter: invert(1);
filter: invert(1);
}
div[poster] img, div[poster] video {
-webkit-filter: invert(0);
filter: invert(0);
}
img {
background-color: white !important;
}
""".trimIndent()
private val assetExtensions = private val assetExtensions =
listOf("3gp", "mp4", "m4a", "webm", "mkv", "ogg", "ogv", "svg", "warc") listOf("3gp", "mp4", "m4a", "webm", "mkv", "ogg", "ogv", "svg", "warc")
private val compressedExtensions = private val compressedExtensions =

View File

@ -1,128 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.core.main
import android.view.View
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.core.DarkModeConfig
class DarkModeViewPainterTest {
private lateinit var darkModeConfig: DarkModeConfig
private lateinit var darkModeViewPainter: DarkModeViewPainter
private lateinit var view: View
@BeforeEach
fun setUp() {
darkModeConfig = mockk()
view = mockk(relaxed = true)
darkModeViewPainter = DarkModeViewPainter(darkModeConfig)
}
@Test
fun `should activate dark mode when dark mode is active and criteria is true`() {
every { darkModeConfig.isDarkModeActive() } returns true
val shouldActivateCriteria: (View) -> Boolean = { true }
darkModeViewPainter.update(view, shouldActivateCriteria)
verify { view.setLayerType(View.LAYER_TYPE_HARDWARE, any()) }
}
@Test
fun `should not activate dark mode when dark mode is active but criteria is false`() {
every { darkModeConfig.isDarkModeActive() } returns true
val shouldActivateCriteria: (View) -> Boolean = { false }
darkModeViewPainter.update(view, shouldActivateCriteria)
verify(exactly = 0) { view.setLayerType(View.LAYER_TYPE_HARDWARE, any()) }
}
@Test
fun `should deactivate dark mode when dark mode is inactive`() {
every { darkModeConfig.isDarkModeActive() } returns false
darkModeViewPainter.update(view)
verify { view.setLayerType(View.LAYER_TYPE_NONE, null) }
}
@Test
fun `should handle null views without crashing`() {
every { darkModeConfig.isDarkModeActive() } returns true
darkModeViewPainter.update(null)
assertTrue(true)
}
@Test
fun `should activate dark mode for multiple additional views when dark mode is active`() {
every { darkModeConfig.isDarkModeActive() } returns true
val additionalView1 = mockk<View>(relaxed = true)
val additionalView2 = mockk<View>(relaxed = true)
darkModeViewPainter.update(view, { true }, additionalView1, additionalView2)
verify { view.setLayerType(View.LAYER_TYPE_HARDWARE, any()) }
verify { additionalView1.setLayerType(View.LAYER_TYPE_HARDWARE, any()) }
verify { additionalView2.setLayerType(View.LAYER_TYPE_HARDWARE, any()) }
}
@Test
fun `should deactivate dark mode for multiple additional views when dark mode is inactive`() {
every { darkModeConfig.isDarkModeActive() } returns false
val additionalView1 = mockk<View>(relaxed = true)
val additionalView2 = mockk<View>(relaxed = true)
darkModeViewPainter.update(view, { true }, additionalView1, additionalView2)
verify { view.setLayerType(View.LAYER_TYPE_NONE, null) }
verify { additionalView1.setLayerType(View.LAYER_TYPE_NONE, null) }
verify { additionalView2.setLayerType(View.LAYER_TYPE_NONE, null) }
}
@Test
fun `should handle null additional views without crashing when dark mode is active`() {
every { darkModeConfig.isDarkModeActive() } returns true
darkModeViewPainter.update(view, { true }, null, null)
verify { view.setLayerType(View.LAYER_TYPE_HARDWARE, any()) }
}
@Test
fun `should handle null additional views without crashing when dark mode is inactive`() {
every { darkModeConfig.isDarkModeActive() } returns false
darkModeViewPainter.update(view, { true }, null, null)
verify { view.setLayerType(View.LAYER_TYPE_NONE, null) }
}
@Test
fun `should only update main view when no additional views are passed and dark mode is active`() {
every { darkModeConfig.isDarkModeActive() } returns true
darkModeViewPainter.update(view)
verify { view.setLayerType(View.LAYER_TYPE_HARDWARE, any()) }
}
@Test
fun shouldOnlyUpdateMainViewWhenNoAdditionalViewsArePassedAndDarkModeIsInactive() {
every { darkModeConfig.isDarkModeActive() } returns false
darkModeViewPainter.update(view)
verify { view.setLayerType(View.LAYER_TYPE_NONE, null) }
}
@Test
fun `should handle empty additional views array without crashing`() {
every { darkModeConfig.isDarkModeActive() } returns true
darkModeViewPainter.update(view, { true }, *arrayOf())
verify { view.setLayerType(View.LAYER_TYPE_HARDWARE, any()) }
}
}