Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1443,7 +1443,7 @@ abstract class AbstractFlashcardViewer :
}

internal val isInNightMode: Boolean
get() = Themes.currentTheme.isNightMode
get() = Themes.isNightTheme

private fun updateCard(content: RenderedCard) {
Timber.d("updateCard()")
Expand Down
2 changes: 1 addition & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ open class AnkiActivity(
get() =
if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
COLOR_SCHEME_SYSTEM
} else if (Themes.currentTheme.isNightMode) {
} else if (Themes.isNightTheme) {
COLOR_SCHEME_DARK
} else {
COLOR_SCHEME_LIGHT
Expand Down
2 changes: 1 addition & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/DrawingFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class DrawingFragment : Fragment(R.layout.drawing_fragment) {
val canvas = Canvas(bitmap)

val backgroundColor =
if (Themes.currentTheme.isNightMode) {
if (Themes.isNightTheme) {
Color.BLACK
} else {
Color.WHITE
Expand Down
3 changes: 2 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ import com.ichi2.anki.scheduling.registerOnForgetHandler
import com.ichi2.anki.servicelayer.NoteService.isMarked
import com.ichi2.anki.servicelayer.NoteService.toggleMark
import com.ichi2.anki.settings.Prefs
import com.ichi2.anki.settings.enums.DayTheme
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.ui.internationalization.toSentenceCase
import com.ichi2.anki.ui.windows.reviewer.ReviewerFragment
Expand Down Expand Up @@ -1684,7 +1685,7 @@ open class Reviewer :
}
whiteboard.onPaintColorChangeListener =
OnPaintColorChangeListener { color ->
MetaDB.storeWhiteboardPenColor(this@Reviewer, parentDid, !currentTheme.isNightMode, color)
MetaDB.storeWhiteboardPenColor(this@Reviewer, parentDid, currentTheme is DayTheme, color)
}
whiteboard.setOnTouchListener { v: View, event: MotionEvent? ->
if (event == null) return@setOnTouchListener false
Expand Down
3 changes: 2 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/Whiteboard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import com.ichi2.anki.common.time.Time
import com.ichi2.anki.common.time.getTimestamp
import com.ichi2.anki.dialogs.WhiteBoardWidthDialog
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.settings.enums.NightTheme
import com.ichi2.compat.CompatHelper
import com.ichi2.themes.Themes.currentTheme
import com.ichi2.utils.DisplayUtils.getDisplayDimensions
Expand Down Expand Up @@ -584,7 +585,7 @@ class Whiteboard(
handleMultiTouch: Boolean,
whiteboardMultiTouchMethods: WhiteboardMultiTouchMethods?,
): Whiteboard {
val whiteboard = Whiteboard(context, handleMultiTouch, currentTheme.isNightMode)
val whiteboard = Whiteboard(context, handleMultiTouch, currentTheme is NightTheme)
Companion.whiteboardMultiTouchMethods = whiteboardMultiTouchMethods
val lp2 =
FrameLayout.LayoutParams(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ class BrowserMultiColumnAdapter(
fun setColor(
@ColorInt color: Int,
) {
val nightMode = Themes.currentTheme.isNightMode
val nightMode = Themes.isNightTheme
val pressedColor: Int
val focusedColor: Int

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ package com.ichi2.anki.cardviewer
import android.content.SharedPreferences
import androidx.annotation.CheckResult
import com.ichi2.anki.reviewer.ReviewerCustomFonts
import com.ichi2.themes.Theme
import com.ichi2.anki.settings.enums.DayTheme
import com.ichi2.anki.settings.enums.NightTheme
import com.ichi2.themes.Themes.currentTheme

/** Responsible for calculating CSS and element styles and modifying content on a flashcard */
Expand Down Expand Up @@ -47,17 +48,17 @@ class CardAppearance(
if (centerVertically) {
cardClass.append(" vertically_centered")
}
if (currentTheme.isNightMode) {
if (currentTheme is NightTheme) {
// Enable the night-mode class
cardClass.append(" night_mode nightMode")

// Emit the dark_mode selector to allow dark theme overrides
if (currentTheme == Theme.DARK) {
if (currentTheme == NightTheme.DARK) {
cardClass.append(" ankidroid_dark_mode")
}
} else {
// Emit the plain_mode selector to allow plain theme overrides
if (currentTheme == Theme.PLAIN) {
if (currentTheme == DayTheme.PLAIN) {
cardClass.append(" ankidroid_plain_mode")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
package com.ichi2.anki.model

import androidx.annotation.CheckResult
import com.ichi2.themes.Themes.currentTheme
import com.ichi2.themes.Themes

class WhiteboardPenColor(
val lightPenColor: Int?,
val darkPenColor: Int?,
) {
fun fromPreferences(): Int? =
if (currentTheme.isNightMode) {
if (Themes.isNightTheme) {
darkPenColor
} else {
lightPenColor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ abstract class PageFragment(
setupBridgeCommand(pageWebViewClient)
onWebViewCreated()
}
val nightMode = if (Themes.currentTheme.isNightMode) "#night" else ""
val nightMode = if (Themes.isNightTheme) "#night" else ""
val url = "${server.baseUrl()}$pagePath$nightMode".toUri()
Timber.i("Loading $url")
webViewLayout.loadUrl(url.toString())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ import com.ichi2.anki.deckpicker.BackgroundImage
import com.ichi2.anki.deckpicker.BackgroundImage.FileSizeResult
import com.ichi2.anki.launchCatchingTask
import com.ichi2.anki.settings.Prefs
import com.ichi2.anki.settings.enums.AppTheme
import com.ichi2.anki.showThemedToast
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.utils.CollectionPreferences
import com.ichi2.themes.Theme
import com.ichi2.themes.Themes
import com.ichi2.themes.Themes.systemIsInNightMode
import com.ichi2.themes.Themes.updateCurrentTheme
Expand Down Expand Up @@ -72,56 +72,6 @@ class AppearanceSettingsFragment : SettingsFragment() {
// Initially update visibility based on whether a background exists
updateRemoveBackgroundVisibility()

val appThemePref = requirePreference<ListPreference>(R.string.app_theme_key)
val dayThemePref = requirePreference<ListPreference>(R.string.day_theme_key)
val nightThemePref = requirePreference<ListPreference>(R.string.night_theme_key)
val themeIsFollowSystem = appThemePref.value == Themes.FOLLOW_SYSTEM_MODE

// Remove follow system options in android versions which do not have system dark mode
// When minSdk reaches 29, the only necessary change is to remove this if-block
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
dayThemePref.isVisible = false
nightThemePref.isVisible = false

// Drop "Follow system" option (the first one)
appThemePref.entries = resources.getStringArray(R.array.app_theme_labels).drop(1).toTypedArray()
appThemePref.entryValues = resources.getStringArray(R.array.app_theme_values).drop(1).toTypedArray()
if (themeIsFollowSystem) {
appThemePref.value = Theme.fallback.id
}
}
dayThemePref.isEnabled = themeIsFollowSystem
nightThemePref.isEnabled = themeIsFollowSystem

appThemePref.setOnPreferenceChangeListener { newValue ->
val selectedThemeIsFollowSystem = newValue == Themes.FOLLOW_SYSTEM_MODE
dayThemePref.isEnabled = selectedThemeIsFollowSystem
nightThemePref.isEnabled = selectedThemeIsFollowSystem

// Only restart if theme has changed
if (newValue != appThemePref.value) {
val previousThemeId = Themes.currentTheme.id
appThemePref.value = newValue
updateCurrentTheme(requireContext())

if (previousThemeId != Themes.currentTheme.id) {
ActivityCompat.recreate(requireActivity())
}
}
}

dayThemePref.setOnPreferenceChangeListener { newValue ->
if (newValue != dayThemePref.value && !systemIsInNightMode(requireContext()) && newValue != Themes.currentTheme.id) {
ActivityCompat.recreate(requireActivity())
}
}

nightThemePref.setOnPreferenceChangeListener { newValue ->
if (newValue != nightThemePref.value && systemIsInNightMode(requireContext()) && newValue != Themes.currentTheme.id) {
ActivityCompat.recreate(requireActivity())
}
}

// Show estimate time
// Represents the collection pref "estTime": i.e.
// whether the buttons should indicate the duration of the interval if we click on them.
Expand Down Expand Up @@ -151,6 +101,7 @@ class AppearanceSettingsFragment : SettingsFragment() {
}
}

setupThemePreferences()
setupNewStudyScreenSettings()
}

Expand All @@ -173,6 +124,54 @@ class AppearanceSettingsFragment : SettingsFragment() {
}
}

private fun setupThemePreferences() {
val appTheme = Prefs.appTheme
val appThemePref = requirePreference<ListPreference>(R.string.app_theme_key)
val dayThemePref = requirePreference<ListPreference>(R.string.day_theme_key)
val nightThemePref = requirePreference<ListPreference>(R.string.night_theme_key)

// Remove follow system options in android versions which do not have system dark mode
// When minSdk reaches 29, the only necessary change is to remove this if-block
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// Drop "Follow system" option (the first one)
appThemePref.entries = resources.getStringArray(R.array.app_theme_labels).drop(1).toTypedArray()
appThemePref.entryValues = resources.getStringArray(R.array.app_theme_values).drop(1).toTypedArray()
if (appTheme == AppTheme.FOLLOW_SYSTEM) {
appThemePref.value = getString(Themes.currentTheme.entryResId)
}
}

appThemePref.setOnPreferenceChangeListener { newValue ->
if (newValue != appThemePref.value) {
val previousThemeId = Themes.currentTheme.styleResId
appThemePref.value = newValue
updateCurrentTheme(requireContext())

if (previousThemeId != Themes.currentTheme.styleResId) {
ActivityCompat.recreate(requireActivity())
}
}
}

dayThemePref.setOnPreferenceChangeListener { newValue ->
if (
newValue != dayThemePref.value &&
(appTheme == AppTheme.DAY || (appTheme == AppTheme.FOLLOW_SYSTEM && !systemIsInNightMode(requireContext())))
) {
ActivityCompat.recreate(requireActivity())
}
}

nightThemePref.setOnPreferenceChangeListener { newValue ->
if (
newValue != nightThemePref.value &&
(appTheme == AppTheme.NIGHT || (appTheme == AppTheme.FOLLOW_SYSTEM && systemIsInNightMode(requireContext())))
) {
ActivityCompat.recreate(requireActivity())
}
}
}

private val backgroundImageResultLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { selectedImage ->
if (selectedImage == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ abstract class CardViewerFragment(
protected open fun onLoadInitialHtml(): String =
stdHtml(
context = requireContext(),
nightMode = Themes.currentTheme.isNightMode,
nightMode = Themes.isNightTheme,
)

private fun setupWebView(savedInstanceState: Bundle?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ fun stdHtml(
*/
fun bodyClassForCardOrd(
cardOrd: Int,
nightMode: Boolean = Themes.currentTheme.isNightMode,
nightMode: Boolean = Themes.isNightTheme,
): String = "card card${cardOrd + 1} ${bodyClass(nightMode)} mathjax-rendered"

private fun bodyClass(nightMode: Boolean = Themes.currentTheme.isNightMode): String = if (nightMode) "nightMode night_mode" else ""
private fun bodyClass(nightMode: Boolean = Themes.isNightTheme): String = if (nightMode) "nightMode night_mode" else ""
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ object PreferenceUpgradeService {
yield(RemoveHostNum())
yield(UpgradeHideAnswerButtons())
yield(UpgradeToggleBacksideOnlyControl())
yield(UpgradeThemes())
}

/** Returns a list of preference upgrade classes which have not been applied */
Expand Down Expand Up @@ -750,6 +751,44 @@ object PreferenceUpgradeService {
}
}
}

internal class UpgradeThemes : PreferenceUpgrade(26) {
companion object {
const val KEY_APP_THEME = "appTheme"
const val KEY_DAY_THEME = "dayTheme"
const val KEY_NIGHT_THEME = "nightTheme"

const val THEME_FOLLOW_SYSTEM = "0"
const val THEME_LIGHT = "1"
const val THEME_PLAIN = "2"
const val THEME_BLACK = "3"
const val THEME_DARK = "4"

const val THEME_DAY = "1"
const val THEME_NIGHT = "2"
}

@Suppress("MoveVariableDeclarationIntoWhen")
override fun upgrade(preferences: SharedPreferences) {
val appTheme = preferences.getString(KEY_APP_THEME, THEME_FOLLOW_SYSTEM)

when (appTheme) {
THEME_FOLLOW_SYSTEM -> return
THEME_LIGHT, THEME_PLAIN -> {
preferences.edit {
putString(KEY_APP_THEME, THEME_DAY)
putString(KEY_DAY_THEME, appTheme)
}
}
THEME_BLACK, THEME_DARK -> {
preferences.edit {
putString(KEY_APP_THEME, THEME_NIGHT)
putString(KEY_NIGHT_THEME, appTheme)
}
}
}
}
}
}
}

Expand Down
13 changes: 13 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ import com.ichi2.anki.R
import com.ichi2.anki.cardviewer.TapGestureMode
import com.ichi2.anki.common.utils.isRunningAsUnitTest
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.settings.enums.AppTheme
import com.ichi2.anki.settings.enums.DayTheme
import com.ichi2.anki.settings.enums.FrameStyle
import com.ichi2.anki.settings.enums.HideSystemBars
import com.ichi2.anki.settings.enums.NightTheme
import com.ichi2.anki.settings.enums.PrefEnum
import com.ichi2.anki.settings.enums.ShouldFetchMedia
import com.ichi2.anki.settings.enums.ToolbarPosition
Expand All @@ -43,6 +46,8 @@ open class PrefsRepository(
val sharedPrefs: SharedPreferences,
private val resources: Resources,
) {
constructor(context: Context) : this(context.sharedPrefs(), context.resources)

@VisibleForTesting
fun key(
@StringRes resId: Int,
Expand Down Expand Up @@ -289,6 +294,14 @@ open class PrefsRepository(
val hideSystemBars: HideSystemBars by enumPref(R.string.hide_system_bars_key, HideSystemBars.NONE)
val toolbarPosition: ToolbarPosition by enumPref(R.string.reviewer_toolbar_position_key, ToolbarPosition.TOP)

//region Appearance

val appTheme: AppTheme by enumPref(R.string.app_theme_key, AppTheme.FOLLOW_SYSTEM)
val dayTheme: DayTheme by enumPref(R.string.day_theme_key, DayTheme.LIGHT)
val nightTheme: NightTheme by enumPref(R.string.night_theme_key, NightTheme.DARK)

//endregion

// **************************************** Controls **************************************** //
//region Controls

Expand Down
27 changes: 27 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/settings/enums/AppTheme.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 Brayan Oliveira <69634269+brayandso@users.noreply.github.com>
*
* 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 com.ichi2.anki.settings.enums

import com.ichi2.anki.R

/** [R.array.app_theme_values] */
enum class AppTheme(
override val entryResId: Int,
) : PrefEnum {
FOLLOW_SYSTEM(R.string.theme_follow_system_value),
DAY(R.string.theme_day_scheme_value),
NIGHT(R.string.theme_night_scheme_value),
}
Loading