Skip to content
Open
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
2 changes: 1 addition & 1 deletion AnkiDroid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ dependencies {
implementation libs.jsoup
implementation libs.java.semver // For AnkiDroid JS API Versioning
implementation libs.drakeet.drawer
implementation libs.colorpicker
implementation libs.skydoves.colorpickerview
implementation libs.kotlin.reflect
implementation libs.kotlin.test
implementation libs.search.preference
Expand Down
19 changes: 2 additions & 17 deletions AnkiDroid/src/main/java/com/ichi2/anki/Whiteboard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ 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.ui.windows.reviewer.whiteboard.showColorPickerDialog
import com.ichi2.compat.CompatHelper
import com.ichi2.themes.Themes.currentTheme
import com.ichi2.utils.DisplayUtils.getDisplayDimensions
import com.mrudultora.colorpicker.ColorPickerPopUp
import timber.log.Timber
import java.io.FileNotFoundException
import kotlin.math.abs
Expand Down Expand Up @@ -395,22 +395,7 @@ class Whiteboard(
penColor = yellowPenColor
}
R.id.pen_color_custom -> {
ColorPickerPopUp(context).run {
setShowAlpha(true)
setDefaultColor(penColor)
setOnPickColorListener(
object : ColorPickerPopUp.OnPickColorListener {
override fun onColorPicked(color: Int) {
penColor = color
}

override fun onCancel() {
// unused
}
},
)
show()
}
context.showColorPickerDialog(penColor) { penColor = it }
}
R.id.stroke_width -> {
handleWidthChangeDialog()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2025 Divyansh Kushwaha <[email protected]>
*
* 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.ui.windows.reviewer.whiteboard

import android.content.Context
import com.ichi2.anki.R
import com.ichi2.utils.Dp
import com.ichi2.utils.dp
import com.ichi2.utils.negativeButton
import com.ichi2.utils.show
import com.skydoves.colorpickerview.AlphaTileView
import com.skydoves.colorpickerview.ColorEnvelope
import com.skydoves.colorpickerview.ColorPickerDialog
import com.skydoves.colorpickerview.flag.FlagView
import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener
import com.skydoves.colorpickerview.sliders.AlphaSlideBar
import com.skydoves.colorpickerview.sliders.BrightnessSlideBar

/**
* Shows a customizable color picker dialog with alpha and brightness controls.
*
* @receiver The Android context used to create the dialog.
* @param initialColor The initial color to display in the picker as an `AARRGGBB` integer.
* @param onColorSelected Callback invoked when the user confirms their color selection.
* Receives the selected color as an `AARRGGBB` integer.
*/
fun Context.showColorPickerDialog(
initialColor: Int,
onColorSelected: (Int) -> Unit,
) {
ColorPickerDialog
.Builder(this)
.show {
// Use post() so setInitialColor() runs after the view is laid out.
// This ensures the BrightnessSlideBar is fully initialized before applying
// the initial color. Calling it too early causes the slider to use its
// default position, resulting in an incorrect displayed color.
colorPickerView.post { colorPickerView.setInitialColor(initialColor) }
// Bubble showing the selected color
colorPickerView.flagView = BubbleFlag(this@showColorPickerDialog)
setPositiveButton(
R.string.dialog_ok,
ColorEnvelopeListener { envelope, _ ->
envelope?.color?.let(onColorSelected)
},
)
setTitle(R.string.choose_color)
negativeButton(R.string.dialog_cancel)
setBottomSpace(12.dp)
}
}

private class BubbleFlag(
context: Context?,
) : FlagView(context, R.layout.colorpicker_flag_bubble) {
val alphaTileView: AlphaTileView = findViewById(R.id.flag_color_layout)

override fun onRefresh(colorEnvelope: ColorEnvelope) {
alphaTileView.setPaintColor(colorEnvelope.color)
}

override fun onFlipped(isFlipped: Boolean?) {
}
}

/**
* Sets the margin of the bottom. this space is visible when [AlphaSlideBar] or
* [BrightnessSlideBar] is attached.
*
* @param bottomSpace the bottom margin in display pixels.
*/
private fun ColorPickerDialog.Builder.setBottomSpace(bottomSpace: Dp) = this.setBottomSpace(bottomSpace.dp.toInt())
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import com.ichi2.themes.Themes
import com.ichi2.utils.dp
import com.ichi2.utils.increaseHorizontalPaddingOfMenuIcons
import com.ichi2.utils.toRGBAHex
import com.mrudultora.colorpicker.ColorPickerPopUp
import dev.androidbroadcast.vbpd.viewBinding
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
Expand Down Expand Up @@ -273,21 +272,11 @@ class WhiteboardFragment :
* Shows a popup for adding a new brush color.
*/
private fun showAddColorDialog() {
ColorPickerPopUp(context).run {
setShowAlpha(true)
setDefaultColor(viewModel.brushColor.value)
setOnPickColorListener(
object : ColorPickerPopUp.OnPickColorListener {
override fun onColorPicked(color: Int) {
Timber.i("Added brush with color ${color.toRGBAHex()}")
viewModel.addBrush(color)
}

override fun onCancel() {}
},
)
show()
}
requireContext()
.showColorPickerDialog(viewModel.brushColor.value) { color ->
Timber.i("Added brush with color ${color.toRGBAHex()}")
viewModel.addBrush(color)
}
}

/**
Expand Down Expand Up @@ -366,19 +355,11 @@ class WhiteboardFragment :
* Shows a color picker popup to change the active brush's color.
*/
private fun showChangeColorDialog() {
ColorPickerPopUp(requireContext())
.setShowAlpha(true)
.setDefaultColor(viewModel.brushColor.value)
.setOnPickColorListener(
object : ColorPickerPopUp.OnPickColorListener {
override fun onColorPicked(color: Int) {
viewModel.updateBrushColor(color)
brushConfigPopup?.dismiss()
}

override fun onCancel() {}
},
).show()
requireContext()
.showColorPickerDialog(viewModel.brushColor.value) { color ->
viewModel.updateBrushColor(color)
brushConfigPopup?.dismiss()
}
}

/**
Expand Down
6 changes: 3 additions & 3 deletions AnkiDroid/src/main/java/com/ichi2/utils/AlertDialogFacade.kt
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ fun AlertDialog.Builder.cancelable(cancelable: Boolean): AlertDialog.Builder = t
* Executes the provided block, then creates an [AlertDialog] with the arguments supplied
* and immediately displays the dialog
*/
inline fun AlertDialog.Builder.show(
inline fun <T : AlertDialog.Builder> T.show(
enableEnterKeyHandler: Boolean = false, // Make it opt-in
block: AlertDialog.Builder.() -> Unit,
block: T.() -> Unit,
): AlertDialog {
this.apply { block() }
val dialog = this.show()
Expand Down Expand Up @@ -187,7 +187,7 @@ fun AlertDialog.Builder.createAndApply(block: AlertDialog.() -> Unit): AlertDial
/**
* Executes [block] on the [AlertDialog.Builder] instance and returns the initialized [AlertDialog].
*/
fun AlertDialog.Builder.create(block: AlertDialog.Builder.() -> Unit): AlertDialog {
fun <T : AlertDialog.Builder> T.create(block: T.() -> Unit): AlertDialog {
block()
return create()
}
Expand Down
29 changes: 29 additions & 0 deletions AnkiDroid/src/main/res/drawable/pin_shape.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!--
~ Copyright (c) 2025 Divyansh Kushwaha <[email protected]>
~
~ 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/>.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:pathData="M16,32C11.64,28.347 8.383,24.953 6.23,21.82C4.077,18.687 3,15.787 3,13.12C3,9.12 4.307,5.933 6.92,3.56C9.534,1.187 12.56,0 16,0C19.44,0 22.466,1.187 25.08,3.56C27.693,5.933 29,9.12 29,13.12C29,15.787 27.923,18.687 25.77,21.82C23.617,24.953 20.36,28.347 16,32Z"
android:fillColor="#000000"/>
<path
android:strokeWidth="1"
android:pathData="M16,22C20.971,22 25,17.971 25,13C25,8.029 20.971,4 16,4C11.029,4 7,8.029 7,13C7,17.971 11.029,22 16,22Z"
android:fillColor="#ffffff"
android:strokeColor="#ffffff"/>
</vector>
31 changes: 31 additions & 0 deletions AnkiDroid/src/main/res/layout/colorpicker_flag_bubble.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2025 Divyansh Kushwaha <[email protected]>
~
~ 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/>.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="8dp"
android:paddingTop="5dp"
android:paddingBottom="11dp"
android:background="@drawable/pin_shape">

<com.skydoves.colorpickerview.AlphaTileView
android:id="@+id/flag_color_layout"
android:layout_width="16dp"
android:layout_height="16dp"
android:background="@drawable/circle_background"
android:clipToOutline="true" />
</FrameLayout>
2 changes: 2 additions & 0 deletions AnkiDroid/src/main/res/values/03-dialogs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,6 @@ also changes the interval of the card"
<string name="tts_error_dialog_reason_text">The text to speech engine <b>%1$s</b> does not support the following language: <b>%2$s</b></string>
<string name="tts_error_dialog_change_button_text">Change engine</string>
<string name="tts_error_dialog_supported_voices_button_text">Voice options</string>

<string name="choose_color">Choose color</string>
</resources>
5 changes: 3 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ androidxWork = "2.11.0"
ankiBackend = '0.1.62-anki25.09.2'
autoService = "1.1.1"
autoServiceAnnotations = "1.1.1"
colorpicker = "1.2.0"
# https://github.com/skydoves/ColorPickerView/releases
colorPickerView = "2.3.0"
# https://commons.apache.org/proper/commons-collections/changes.html
commonsCollections4 = "4.5.0"
# https://commons.apache.org/proper/commons-compress/changes-report.html
Expand Down Expand Up @@ -143,7 +144,7 @@ androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version
auto-service = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
auto-service-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoServiceAnnotations" }
jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
colorpicker = { module = "com.github.mrudultora:Colorpicker", version.ref = "colorpicker" }
skydoves-colorpickerview = { module = "com.github.skydoves:colorpickerview", version.ref = "colorPickerView" }
commons-io = { module = "commons-io:commons-io", version.ref = "commonsIo" }
commons-collections4 = { module = "org.apache.commons:commons-collections4", version.ref = "commonsCollections4" }
commons-compress = { module = "org.apache.commons:commons-compress", version.ref = "commonsCompress" }
Expand Down