Added pure white theme for light theme.

* Refactored the all UI to support pure white theme.
* Enhanced the `EdgeToEdge` mode for pure white theme.
This commit is contained in:
MohitMaliFtechiz 2025-08-28 20:24:06 +05:30 committed by Kelson
parent c25e98b043
commit badd101177
12 changed files with 71 additions and 144 deletions

View File

@ -30,6 +30,7 @@ import androidx.compose.material3.BottomAppBarScrollBehavior
import androidx.compose.material3.DrawerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.NavigationBarItemDefaults
@ -52,7 +53,6 @@ import org.kiwix.kiwixmobile.R.drawable
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.main.DrawerMenuGroup
import org.kiwix.kiwixmobile.core.main.LeftDrawerMenu
import org.kiwix.kiwixmobile.core.ui.theme.Black
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
import org.kiwix.kiwixmobile.core.ui.theme.White
import org.kiwix.kiwixmobile.ui.KiwixDestination
@ -182,7 +182,7 @@ fun BottomNavigationBar(
)
val currentDestinationRoute = navBackStackEntry?.destination?.route
BottomAppBar(
containerColor = Black,
containerColor = MaterialTheme.colorScheme.onPrimary,
contentColor = White.copy(alpha = 0.5f),
scrollBehavior = bottomAppBarScrollBehaviour
) {
@ -211,13 +211,13 @@ fun BottomNavigationBar(
Icon(
painter = painterResource(id = item.iconRes),
contentDescription = item.title,
tint = White
tint = MaterialTheme.colorScheme.onBackground
)
},
label = { Text(item.title, color = White) },
label = { Text(item.title, color = MaterialTheme.colorScheme.onBackground) },
modifier = Modifier.semantics { testTag = item.testingTag },
colors = NavigationBarItemDefaults.colors()
.copy(selectedIndicatorColor = White.copy(alpha = 0.3f))
.copy(selectedIndicatorColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.3f))
)
}
}

View File

@ -18,12 +18,10 @@
package org.kiwix.kiwixmobile.core.base
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.setWindowBackgroundColorForAndroid15AndAbove
import org.kiwix.kiwixmobile.core.utils.LanguageUtils
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import javax.inject.Inject
@ -34,13 +32,9 @@ open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.dark(Color.BLACK),
navigationBarStyle = SystemBarStyle.dark(Color.BLACK)
statusBarStyle = SystemBarStyle.auto(Color.WHITE, Color.BLACK)
)
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
setWindowBackgroundColorForAndroid15AndAbove()
}
LanguageUtils.handleLocaleChange(this, sharedPreferenceUtil)
}
}

View File

@ -19,26 +19,16 @@
package org.kiwix.kiwixmobile.core.base
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.extensions.enableEdgeToEdgeMode
import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon
import org.kiwix.kiwixmobile.core.extensions.setFragmentBackgroundColorForAndroid15AndAbove
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
import org.kiwix.kiwixmobile.core.extensions.setStatusBarColor
/**
* All fragments should inherit from this fragment.
*/
abstract class BaseFragment : Fragment() {
open val fragmentToolbar: Toolbar? = null
open val fragmentTitle: String? = null
override fun onAttach(context: Context) {
super.onAttach(context)
inject(activity as BaseActivity)
@ -47,32 +37,7 @@ abstract class BaseFragment : Fragment() {
abstract fun inject(baseActivity: BaseActivity)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setStatusBarColor()
super.onViewCreated(view, savedInstanceState)
enableEdgeToEdgeMode()
setupToolbar()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
setFragmentBackgroundColorForAndroid15AndAbove()
}
}
// Setup toolbar to handle common back pressed event
private fun setupToolbar() {
val activity = activity as AppCompatActivity?
fragmentToolbar?.apply {
activity?.let {
it.setSupportActionBar(this)
it.supportActionBar?.let { actionBar ->
actionBar.setDisplayHomeAsUpEnabled(true)
actionBar.title = fragmentTitle
// set the navigation back button contentDescription
getToolbarNavigationIcon()?.setToolTipWithContentDescription(
getString(R.string.toolbar_back_button_content_description)
)
}
}
setNavigationOnClickListener {
activity?.onBackPressedDispatcher?.onBackPressed()
}
}
}
}

View File

@ -24,12 +24,10 @@ import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Color
import android.os.Build
import android.os.Environment
import android.view.Menu
import android.view.MenuItem
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.app.ActivityCompat
@ -192,18 +190,4 @@ object ActivityExtensions {
fun Activity.isLandScapeMode(): Boolean =
resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
/**
* 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)
}
}

View File

@ -20,21 +20,16 @@ package org.kiwix.kiwixmobile.core.extensions
import android.content.Context
import android.graphics.Color
import android.os.Build
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.google.android.material.color.MaterialColors
import org.kiwix.kiwixmobile.core.CoreApp
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.setWindowBackgroundColorForAndroid15AndAbove
import org.kiwix.kiwixmobile.core.main.CoreMainActivity
inline fun <reified T : ViewModel> Fragment.viewModel(
@ -64,27 +59,18 @@ fun View.closeKeyboard() {
val Fragment.coreMainActivity get() = activity as CoreMainActivity
/**
* It enables the edge to edge mode for fragments.
* Updates the Activity windows background color to match the current theme (light or dark).
*
* Since the status bar is drawn over the window background in edge-to-edge mode,
* this effectively controls the visible status bar color and ensures proper
* contrast with dark mode or light mode.
*/
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)
fun Fragment.setStatusBarColor() {
val darkModeActivity = CoreApp.instance.darkModeConfig.isDarkModeActive()
val windowBackGroundColor = if (darkModeActivity) {
Color.BLACK
} else {
Color.WHITE
}
activity?.window?.decorView?.setBackgroundColor(windowBackGroundColor)
}

View File

@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
@ -42,8 +43,6 @@ import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.page.SEARCH_ICON_TESTING_TAG
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.ui.theme.Black
import org.kiwix.kiwixmobile.core.ui.theme.White
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.MATERIAL_MINIMUM_HEIGHT_AND_WIDTH
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.ONE_DP
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIX_DP
@ -217,15 +216,19 @@ class ReaderMenuState(
Box(
modifier = modifier
.clip(RoundedCornerShape(TAB_SWITCHER_ICON_CORNER_RADIUS))
.background(Black)
.border(ONE_DP, White, RoundedCornerShape(TAB_SWITCHER_ICON_CORNER_RADIUS))
.background(MaterialTheme.colorScheme.onPrimary)
.border(
ONE_DP,
MaterialTheme.colorScheme.onBackground,
RoundedCornerShape(TAB_SWITCHER_ICON_CORNER_RADIUS)
)
.padding(horizontal = SIX_DP, vertical = TWO_DP)
.defaultMinSize(minWidth = TWENTY_DP, minHeight = TWENTY_DP),
contentAlignment = Alignment.Center
) {
Text(
text = tabLabel,
color = White,
color = MaterialTheme.colorScheme.onBackground,
fontWeight = FontWeight.Bold,
fontSize = TAB_SWITCHER_TEXT_SIZE,
maxLines = 1,

View File

@ -134,9 +134,8 @@ import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.ui.models.IconItem
import org.kiwix.kiwixmobile.core.ui.models.IconItem.Drawable
import org.kiwix.kiwixmobile.core.ui.models.toPainter
import org.kiwix.kiwixmobile.core.ui.theme.Black
import org.kiwix.kiwixmobile.core.ui.theme.DenimBlue800
import org.kiwix.kiwixmobile.core.ui.theme.KiwixDialogTheme
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
import org.kiwix.kiwixmobile.core.ui.theme.MineShaftGray700
import org.kiwix.kiwixmobile.core.ui.theme.White
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.BACK_TO_TOP_BUTTON_BOTTOM_MARGIN
@ -198,7 +197,7 @@ fun ReaderScreen(
mainActivityBottomAppBarScrollBehaviour?.state?.heightOffset =
bottomAppBarScrollBehavior.state.heightOffset
}
KiwixDialogTheme {
KiwixTheme {
Box(Modifier.fillMaxSize()) {
Scaffold(
snackbarHost = { KiwixSnackbarHost(snackbarHostState = state.snackBarHostState) },
@ -698,8 +697,8 @@ private fun BottomAppBarOfReaderScreen(
) {
if (!shouldShowBottomAppBar) return
BottomAppBar(
containerColor = Black,
contentColor = White,
containerColor = MaterialTheme.colorScheme.onPrimary,
contentColor = MaterialTheme.colorScheme.onBackground,
scrollBehavior = bottomAppBarScrollBehavior,
) {
Row(

View File

@ -20,7 +20,6 @@ package org.kiwix.kiwixmobile.core.page
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -58,8 +57,6 @@ import org.kiwix.kiwixmobile.core.page.history.adapter.HistoryListItem.DateItem
import org.kiwix.kiwixmobile.core.ui.components.KiwixAppBar
import org.kiwix.kiwixmobile.core.ui.components.KiwixSearchView
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.ui.theme.AlabasterWhite
import org.kiwix.kiwixmobile.core.ui.theme.Black
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
import org.kiwix.kiwixmobile.core.ui.theme.White
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.FOURTEEN_SP
@ -172,22 +169,17 @@ fun PageSwitchRow(
val context = LocalActivity.current as CoreMainActivity
// hide switches for custom apps, see more info here https://github.com/kiwix/kiwix-android/issues/3523
if (!context.isCustomApp()) {
val switchTextColor = if (isSystemInDarkTheme()) {
AlabasterWhite
} else {
White
}
Row(
modifier = Modifier
.fillMaxWidth()
.background(Black)
.background(MaterialTheme.colorScheme.onPrimary)
.padding(bottom = PAGE_SWITCH_ROW_BOTTOM_MARGIN),
horizontalArrangement = Arrangement.Absolute.Right,
verticalAlignment = Alignment.CenterVertically
) {
Text(
state.switchString,
color = switchTextColor,
color = MaterialTheme.colorScheme.onBackground,
style = TextStyle(fontSize = FOURTEEN_SP),
modifier = Modifier.testTag(SWITCH_TEXT_TESTING_TAG)
)

View File

@ -19,7 +19,6 @@
package org.kiwix.kiwixmobile.core.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -55,10 +54,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import org.kiwix.kiwixmobile.core.ui.models.ActionMenuItem
import org.kiwix.kiwixmobile.core.ui.models.toPainter
import org.kiwix.kiwixmobile.core.ui.theme.Black
import org.kiwix.kiwixmobile.core.ui.theme.KiwixTheme
import org.kiwix.kiwixmobile.core.ui.theme.MineShaftGray350
import org.kiwix.kiwixmobile.core.ui.theme.White
import org.kiwix.kiwixmobile.core.utils.ComposeDimens.SIXTEEN_DP
const val TOOLBAR_TITLE_TESTING_TAG = "toolbarTitle"
@ -82,8 +78,8 @@ fun KiwixAppBar(
actions = { ActionMenu(actionMenuItems) },
scrollBehavior = topAppBarScrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Black,
scrolledContainerColor = Black
containerColor = MaterialTheme.colorScheme.onPrimary,
scrolledContainerColor = MaterialTheme.colorScheme.onPrimary
),
// Edge-to-Edge mode is already enabled in our application,
// so we don't need to apply additional top insets.
@ -118,14 +114,9 @@ private fun AppBarTitleSection(
private fun AppBarTitle(
title: String
) {
val appBarTitleColor = if (isSystemInDarkTheme()) {
MineShaftGray350
} else {
White
}
Text(
text = title,
color = appBarTitleColor,
color = MaterialTheme.colorScheme.onBackground,
style = MaterialTheme.typography.titleMedium,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
@ -149,7 +140,7 @@ private fun ActionMenu(actionMenuItems: List<ActionMenuItem>) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = null,
tint = White
tint = MaterialTheme.colorScheme.onBackground
)
}
}
@ -161,7 +152,6 @@ private fun ActionMenu(actionMenuItems: List<ActionMenuItem>) {
private fun MainMenuItems(mainActions: List<ActionMenuItem>) {
mainActions.forEach { menuItem ->
val modifier = menuItem.modifier.testTag(menuItem.testingTag)
menuItem.customView?.let { customComposable ->
Box(modifier = modifier.clickable(enabled = menuItem.isEnabled) { menuItem.onClick() }) {
customComposable()
@ -176,7 +166,7 @@ private fun MainMenuItems(mainActions: List<ActionMenuItem>) {
Icon(
painter = iconItem.toPainter(),
contentDescription = stringResource(menuItem.contentDescription),
tint = if (menuItem.isEnabled) menuItem.iconTint else Color.Gray
tint = if (menuItem.isEnabled) MaterialTheme.colorScheme.onBackground else Color.Gray
)
}
} ?: run {
@ -187,7 +177,7 @@ private fun MainMenuItems(mainActions: List<ActionMenuItem>) {
) {
Text(
text = menuItem.iconButtonText.uppercase(),
color = if (menuItem.isEnabled) Color.White else Color.Gray,
color = if (menuItem.isEnabled) MaterialTheme.colorScheme.onBackground else Color.Gray,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)

View File

@ -18,10 +18,12 @@
package org.kiwix.kiwixmobile.core.ui.components
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
@ -53,6 +55,11 @@ fun KiwixSearchView(
onClearClick: () -> Unit,
onKeyboardSubmitButtonClick: (String) -> Unit = {}
) {
val hintColor = if (isSystemInDarkTheme()) {
Color.LightGray
} else {
Color.Gray
}
val keyboardController = LocalSoftwareKeyboardController.current
val colors = TextFieldDefaults.colors(
focusedIndicatorColor = Color.Transparent,
@ -60,11 +67,10 @@ fun KiwixSearchView(
focusedContainerColor = Color.Transparent,
disabledContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
focusedTextColor = Color.White
focusedTextColor = MaterialTheme.colorScheme.onBackground
)
val focusRequester = FocusRequester()
SideEffect(focusRequester::requestFocus)
TextField(
modifier = modifier
.testTag(searchViewTextFiledTestTag)
@ -75,33 +81,27 @@ fun KiwixSearchView(
placeholder = {
Text(
text = placeholder,
color = Color.LightGray,
color = hintColor,
fontSize = ComposeDimens.EIGHTEEN_SP,
maxLines = ONE,
overflow = Ellipsis
)
},
colors = colors,
textStyle = TextStyle.Default.copy(
fontSize = ComposeDimens.EIGHTEEN_SP
),
onValueChange = {
onValueChange(it.replace("\n", ""))
},
textStyle = TextStyle.Default.copy(fontSize = ComposeDimens.EIGHTEEN_SP),
onValueChange = { onValueChange(it.replace("\n", "")) },
trailingIcon = {
if (value.isNotEmpty()) {
IconButton(onClick = onClearClick, modifier = Modifier.testTag(clearButtonTestTag)) {
Icon(
painter = painterResource(R.drawable.ic_clear_white_24dp),
tint = Color.White,
tint = MaterialTheme.colorScheme.onBackground,
contentDescription = stringResource(R.string.searchview_description_clear)
)
}
}
},
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Done
),
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = {
keyboardController?.hide()

View File

@ -23,6 +23,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -59,7 +60,23 @@ fun NavigationIcon(
Icon(
painter = iconItem.toPainter(),
contentDescription = stringResource(contentDescription),
tint = iconTint
tint = getNavigationIconTintColor(iconTint)
)
}
}
/**
* Returns the navigationIcon color.
*
* If iconTint is set as [Color.Unspecified] then return that color. Because it is set to show
* the actual color of image or icon set on navigation icon.
*
* Otherwise: return the navigationIcon color according to theme.
*/
@Composable
private fun getNavigationIconTintColor(iconTint: Color): Color =
if (iconTint == Color.Unspecified) {
iconTint
} else {
MaterialTheme.colorScheme.onBackground
}

View File

@ -21,14 +21,11 @@ package org.kiwix.kiwixmobile.core.ui.models
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import org.kiwix.kiwixmobile.core.ui.theme.White
data class ActionMenuItem(
val icon: IconItem? = null,
@StringRes val contentDescription: Int,
val onClick: () -> Unit,
val iconTint: Color = White,
val isEnabled: Boolean = true,
val iconButtonText: String = "",
val testingTag: String,