Skip to content

Commit f14d31b

Browse files
committed
fix(Whiteboard): broken color picker in RTL layout
Fixes: #19237 replace existing color picker from another library Replaced With: https://github.com/skydoves/ColorPickerView/releases # Conflicts: # AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/whiteboard/WhiteboardFragment.kt
1 parent bab0fb4 commit f14d31b

File tree

9 files changed

+167
-52
lines changed

9 files changed

+167
-52
lines changed

AnkiDroid/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ dependencies {
421421
implementation libs.jsoup
422422
implementation libs.java.semver // For AnkiDroid JS API Versioning
423423
implementation libs.drakeet.drawer
424-
implementation libs.colorpicker
424+
implementation libs.skydoves.colorpickerview
425425
implementation libs.kotlin.reflect
426426
implementation libs.kotlin.test
427427
implementation libs.search.preference

AnkiDroid/src/main/java/com/ichi2/anki/Whiteboard.kt

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ import com.ichi2.anki.common.time.Time
4545
import com.ichi2.anki.common.time.getTimestamp
4646
import com.ichi2.anki.dialogs.WhiteBoardWidthDialog
4747
import com.ichi2.anki.preferences.sharedPrefs
48+
import com.ichi2.anki.ui.windows.reviewer.whiteboard.showColorPickerDialog
4849
import com.ichi2.compat.CompatHelper
4950
import com.ichi2.themes.Themes.currentTheme
5051
import com.ichi2.utils.DisplayUtils.getDisplayDimensions
51-
import com.mrudultora.colorpicker.ColorPickerPopUp
5252
import timber.log.Timber
5353
import java.io.FileNotFoundException
5454
import kotlin.math.abs
@@ -395,22 +395,7 @@ class Whiteboard(
395395
penColor = yellowPenColor
396396
}
397397
R.id.pen_color_custom -> {
398-
ColorPickerPopUp(context).run {
399-
setShowAlpha(true)
400-
setDefaultColor(penColor)
401-
setOnPickColorListener(
402-
object : ColorPickerPopUp.OnPickColorListener {
403-
override fun onColorPicked(color: Int) {
404-
penColor = color
405-
}
406-
407-
override fun onCancel() {
408-
// unused
409-
}
410-
},
411-
)
412-
show()
413-
}
398+
context.showColorPickerDialog(penColor) { penColor = it }
414399
}
415400
R.id.stroke_width -> {
416401
handleWidthChangeDialog()
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright (c) 2025 Divyansh Kushwaha <[email protected]>
3+
*
4+
* This program is free software; you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free Software
6+
* Foundation; either version 3 of the License, or (at your option) any later
7+
* version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
10+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11+
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License along with
14+
* this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
package com.ichi2.anki.ui.windows.reviewer.whiteboard
18+
19+
import android.content.Context
20+
import com.ichi2.anki.R
21+
import com.ichi2.utils.Dp
22+
import com.ichi2.utils.dp
23+
import com.ichi2.utils.negativeButton
24+
import com.ichi2.utils.show
25+
import com.skydoves.colorpickerview.AlphaTileView
26+
import com.skydoves.colorpickerview.ColorEnvelope
27+
import com.skydoves.colorpickerview.ColorPickerDialog
28+
import com.skydoves.colorpickerview.flag.FlagView
29+
import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener
30+
import com.skydoves.colorpickerview.sliders.AlphaSlideBar
31+
import com.skydoves.colorpickerview.sliders.BrightnessSlideBar
32+
33+
/**
34+
* Shows a customizable color picker dialog with alpha and brightness controls.
35+
*
36+
* @receiver The Android context used to create the dialog.
37+
* @param initialColor The initial color to display in the picker as an `AARRGGBB` integer.
38+
* @param onColorSelected Callback invoked when the user confirms their color selection.
39+
* Receives the selected color as an `AARRGGBB` integer.
40+
*/
41+
fun Context.showColorPickerDialog(
42+
initialColor: Int,
43+
onColorSelected: (Int) -> Unit,
44+
) {
45+
ColorPickerDialog
46+
.Builder(this)
47+
.show {
48+
// Use post() so setInitialColor() runs after the view is laid out.
49+
// This ensures the BrightnessSlideBar is fully initialized before applying
50+
// the initial color. Calling it too early causes the slider to use its
51+
// default position, resulting in an incorrect displayed color.
52+
colorPickerView.post { colorPickerView.setInitialColor(initialColor) }
53+
// Bubble showing the selected color
54+
colorPickerView.flagView = BubbleFlag(this@showColorPickerDialog)
55+
setPositiveButton(
56+
R.string.dialog_ok,
57+
ColorEnvelopeListener { envelope, _ ->
58+
envelope?.color?.let(onColorSelected)
59+
},
60+
)
61+
setTitle(R.string.choose_color)
62+
negativeButton(R.string.dialog_cancel)
63+
setBottomSpace(12.dp)
64+
}
65+
}
66+
67+
private class BubbleFlag(
68+
context: Context?,
69+
) : FlagView(context, R.layout.colorpicker_flag_bubble) {
70+
val alphaTileView: AlphaTileView = findViewById(R.id.flag_color_layout)
71+
72+
override fun onRefresh(colorEnvelope: ColorEnvelope) {
73+
alphaTileView.setPaintColor(colorEnvelope.color)
74+
}
75+
76+
override fun onFlipped(isFlipped: Boolean?) {
77+
}
78+
}
79+
80+
/**
81+
* Sets the margin of the bottom. this space is visible when [AlphaSlideBar] or
82+
* [BrightnessSlideBar] is attached.
83+
*
84+
* @param bottomSpace the bottom margin in display pixels.
85+
*/
86+
private fun ColorPickerDialog.Builder.setBottomSpace(bottomSpace: Dp) = this.setBottomSpace(bottomSpace.dp.toInt())

AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/whiteboard/WhiteboardFragment.kt

Lines changed: 10 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ import com.ichi2.themes.Themes
4646
import com.ichi2.utils.dp
4747
import com.ichi2.utils.increaseHorizontalPaddingOfMenuIcons
4848
import com.ichi2.utils.toRGBAHex
49-
import com.mrudultora.colorpicker.ColorPickerPopUp
5049
import dev.androidbroadcast.vbpd.viewBinding
5150
import kotlinx.coroutines.flow.combine
5251
import kotlinx.coroutines.flow.launchIn
@@ -273,21 +272,11 @@ class WhiteboardFragment :
273272
* Shows a popup for adding a new brush color.
274273
*/
275274
private fun showAddColorDialog() {
276-
ColorPickerPopUp(context).run {
277-
setShowAlpha(true)
278-
setDefaultColor(viewModel.brushColor.value)
279-
setOnPickColorListener(
280-
object : ColorPickerPopUp.OnPickColorListener {
281-
override fun onColorPicked(color: Int) {
282-
Timber.i("Added brush with color ${color.toRGBAHex()}")
283-
viewModel.addBrush(color)
284-
}
285-
286-
override fun onCancel() {}
287-
},
288-
)
289-
show()
290-
}
275+
requireContext()
276+
.showColorPickerDialog(viewModel.brushColor.value) { color ->
277+
Timber.i("Added brush with color ${color.toRGBAHex()}")
278+
viewModel.addBrush(color)
279+
}
291280
}
292281

293282
/**
@@ -366,19 +355,11 @@ class WhiteboardFragment :
366355
* Shows a color picker popup to change the active brush's color.
367356
*/
368357
private fun showChangeColorDialog() {
369-
ColorPickerPopUp(requireContext())
370-
.setShowAlpha(true)
371-
.setDefaultColor(viewModel.brushColor.value)
372-
.setOnPickColorListener(
373-
object : ColorPickerPopUp.OnPickColorListener {
374-
override fun onColorPicked(color: Int) {
375-
viewModel.updateBrushColor(color)
376-
brushConfigPopup?.dismiss()
377-
}
378-
379-
override fun onCancel() {}
380-
},
381-
).show()
358+
requireContext()
359+
.showColorPickerDialog(viewModel.brushColor.value) { color ->
360+
viewModel.updateBrushColor(color)
361+
brushConfigPopup?.dismiss()
362+
}
382363
}
383364

384365
/**

AnkiDroid/src/main/java/com/ichi2/utils/AlertDialogFacade.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,9 @@ fun AlertDialog.Builder.cancelable(cancelable: Boolean): AlertDialog.Builder = t
144144
* Executes the provided block, then creates an [AlertDialog] with the arguments supplied
145145
* and immediately displays the dialog
146146
*/
147-
inline fun AlertDialog.Builder.show(
147+
inline fun <T : AlertDialog.Builder> T.show(
148148
enableEnterKeyHandler: Boolean = false, // Make it opt-in
149-
block: AlertDialog.Builder.() -> Unit,
149+
block: T.() -> Unit,
150150
): AlertDialog {
151151
this.apply { block() }
152152
val dialog = this.show()
@@ -187,7 +187,7 @@ fun AlertDialog.Builder.createAndApply(block: AlertDialog.() -> Unit): AlertDial
187187
/**
188188
* Executes [block] on the [AlertDialog.Builder] instance and returns the initialized [AlertDialog].
189189
*/
190-
fun AlertDialog.Builder.create(block: AlertDialog.Builder.() -> Unit): AlertDialog {
190+
fun <T : AlertDialog.Builder> T.create(block: T.() -> Unit): AlertDialog {
191191
block()
192192
return create()
193193
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!--
2+
~ Copyright (c) 2025 Divyansh Kushwaha <[email protected]>
3+
~
4+
~ This program is free software; you can redistribute it and/or modify it under
5+
~ the terms of the GNU General Public License as published by the Free Software
6+
~ Foundation; either version 3 of the License, or (at your option) any later
7+
~ version.
8+
~
9+
~ This program is distributed in the hope that it will be useful, but WITHOUT ANY
10+
~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11+
~ PARTICULAR PURPOSE. See the GNU General Public License for more details.
12+
~
13+
~ You should have received a copy of the GNU General Public License along with
14+
~ this program. If not, see <http://www.gnu.org/licenses/>.
15+
-->
16+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
17+
android:width="32dp"
18+
android:height="32dp"
19+
android:viewportWidth="32"
20+
android:viewportHeight="32">
21+
<path
22+
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"
23+
android:fillColor="#000000"/>
24+
<path
25+
android:strokeWidth="1"
26+
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"
27+
android:fillColor="#ffffff"
28+
android:strokeColor="#ffffff"/>
29+
</vector>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright (c) 2025 Divyansh Kushwaha <[email protected]>
4+
~
5+
~ This program is free software; you can redistribute it and/or modify it under
6+
~ the terms of the GNU General Public License as published by the Free Software
7+
~ Foundation; either version 3 of the License, or (at your option) any later
8+
~ version.
9+
~
10+
~ This program is distributed in the hope that it will be useful, but WITHOUT ANY
11+
~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12+
~ PARTICULAR PURPOSE. See the GNU General Public License for more details.
13+
~
14+
~ You should have received a copy of the GNU General Public License along with
15+
~ this program. If not, see <http://www.gnu.org/licenses/>.
16+
-->
17+
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
18+
android:layout_width="wrap_content"
19+
android:layout_height="wrap_content"
20+
android:paddingHorizontal="8dp"
21+
android:paddingTop="5dp"
22+
android:paddingBottom="11dp"
23+
android:background="@drawable/pin_shape">
24+
25+
<com.skydoves.colorpickerview.AlphaTileView
26+
android:id="@+id/flag_color_layout"
27+
android:layout_width="16dp"
28+
android:layout_height="16dp"
29+
android:background="@drawable/circle_background"
30+
android:clipToOutline="true" />
31+
</FrameLayout>

AnkiDroid/src/main/res/values/03-dialogs.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,6 @@ also changes the interval of the card"
268268
<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>
269269
<string name="tts_error_dialog_change_button_text">Change engine</string>
270270
<string name="tts_error_dialog_supported_voices_button_text">Voice options</string>
271+
272+
<string name="choose_color">Choose color</string>
271273
</resources>

gradle/libs.versions.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ androidxWork = "2.11.0"
6262
ankiBackend = '0.1.62-anki25.09.2'
6363
autoService = "1.1.1"
6464
autoServiceAnnotations = "1.1.1"
65-
colorpicker = "1.2.0"
65+
# https://github.com/skydoves/ColorPickerView/releases
66+
colorPickerView = "2.3.0"
6667
# https://commons.apache.org/proper/commons-collections/changes.html
6768
commonsCollections4 = "4.5.0"
6869
# https://commons.apache.org/proper/commons-compress/changes-report.html
@@ -143,7 +144,7 @@ androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version
143144
auto-service = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }
144145
auto-service-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoServiceAnnotations" }
145146
jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrainsAnnotations" }
146-
colorpicker = { module = "com.github.mrudultora:Colorpicker", version.ref = "colorpicker" }
147+
skydoves-colorpickerview = { module = "com.github.skydoves:colorpickerview", version.ref = "colorPickerView" }
147148
commons-io = { module = "commons-io:commons-io", version.ref = "commonsIo" }
148149
commons-collections4 = { module = "org.apache.commons:commons-collections4", version.ref = "commonsCollections4" }
149150
commons-compress = { module = "org.apache.commons:commons-compress", version.ref = "commonsCompress" }

0 commit comments

Comments
 (0)