Skip to content

Commit b19d733

Browse files
authored
Allow multiple selectionChangedListeners to be set in AztecText #1104
2 parents dc5dff3 + 4461ae0 commit b19d733

File tree

3 files changed

+212
-6
lines changed

3 files changed

+212
-6
lines changed

aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
327327

328328
private lateinit var listItemStyle: BlockFormatter.ListItemStyle
329329

330+
private var selectionChangedListeners = CompositeSelectionChangedListener()
331+
330332
override fun onDraw(canvas: Canvas) {
331333
plugins.filterIsInstance<IOnDrawPlugin>().forEach {
332334
it.onDraw(canvas)
@@ -338,6 +340,19 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
338340
fun onSelectionChanged(selStart: Int, selEnd: Int)
339341
}
340342

343+
private class CompositeSelectionChangedListener : OnSelectionChangedListener {
344+
private val listeners = mutableListOf<OnSelectionChangedListener>()
345+
346+
fun addListener(listener: OnSelectionChangedListener) {
347+
if (!listeners.contains(listener)) listeners.add(listener)
348+
}
349+
350+
fun removeListener(listener: OnSelectionChangedListener) = listeners.remove(listener)
351+
352+
override fun onSelectionChanged(selStart: Int, selEnd: Int) =
353+
listeners.forEach { it.onSelectionChanged(selStart, selEnd) }
354+
}
355+
341356
interface OnImeBackListener {
342357
fun onImeBack()
343358
}
@@ -1211,8 +1226,12 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
12111226
return blockFormatter.isOutdentAvailable()
12121227
}
12131228

1214-
fun setOnSelectionChangedListener(onSelectionChangedListener: OnSelectionChangedListener) {
1215-
this.onSelectionChangedListener = onSelectionChangedListener
1229+
fun setOnSelectionChangedListener(listener: OnSelectionChangedListener) {
1230+
selectionChangedListeners.addListener(listener)
1231+
}
1232+
1233+
fun removeOnSelectionChangedListener(listener: OnSelectionChangedListener) {
1234+
selectionChangedListeners.removeListener(listener)
12161235
}
12171236

12181237
fun getAztecKeyListener(): OnAztecKeyListener? {
@@ -1324,7 +1343,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
13241343
return
13251344
}
13261345

1327-
onSelectionChangedListener?.onSelectionChanged(selStart, selEnd)
1346+
selectionChangedListeners.onSelectionChanged(selStart, selEnd)
13281347
// Gutenberg controls the selected styles so we need to prevent Aztec to modify them
13291348
if (!isInGutenbergMode) {
13301349
setSelectedStyles(getAppliedStyles(selStart, selEnd))

aztec/src/main/kotlin/org/wordpress/aztec/toolbar/AztecToolbar.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ class AztecToolbar : FrameLayout, IAztecToolbar, OnMenuItemClickListener {
9494
private var toolbarItems: ToolbarItems? = null
9595
private var tasklistEnabled: Boolean = false
9696

97+
private var editorSelectionListener: AztecText.OnSelectionChangedListener? = null
98+
9799
constructor(context: Context) : super(context) {
98100
initView(null)
99101
}
@@ -408,12 +410,14 @@ class AztecToolbar : FrameLayout, IAztecToolbar, OnMenuItemClickListener {
408410
this.sourceEditor = sourceEditor
409411
this.editor = editor
410412

411-
// highlight toolbar buttons based on what styles are applied to the text beneath cursor
412-
this.editor!!.setOnSelectionChangedListener(object : AztecText.OnSelectionChangedListener {
413+
editorSelectionListener?.let { this.editor?.removeOnSelectionChangedListener(it) }
414+
editorSelectionListener = object : AztecText.OnSelectionChangedListener {
413415
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
414416
highlightAppliedStyles(selStart, selEnd)
415417
}
416-
})
418+
}
419+
this.editor?.setOnSelectionChangedListener(editorSelectionListener!!)
420+
417421
setupToolbarItems()
418422

419423
if (sourceEditor == null) {
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package org.wordpress.aztec
2+
3+
import android.app.Activity
4+
import android.widget.ToggleButton
5+
import org.junit.Assert
6+
import org.junit.Before
7+
import org.junit.Test
8+
import org.junit.runner.RunWith
9+
import org.robolectric.Robolectric
10+
import org.robolectric.RobolectricTestRunner
11+
import org.wordpress.aztec.toolbar.AztecToolbar
12+
import org.wordpress.aztec.toolbar.ToolbarAction
13+
14+
@RunWith(RobolectricTestRunner::class)
15+
class AztecTextSelectionListenerTest {
16+
private lateinit var editText: AztecText
17+
private lateinit var toolbar: AztecToolbar
18+
private var clientListenerCallCount = 0
19+
private var lastClientSelectionStart = -1
20+
private var lastClientSelectionEnd = -1
21+
22+
@Before
23+
fun init() {
24+
val activity = Robolectric.buildActivity(Activity::class.java).create().visible().get()
25+
editText = AztecText(activity)
26+
editText.setCalypsoMode(false)
27+
toolbar = AztecToolbar(activity)
28+
toolbar.setEditor(editText, null)
29+
30+
// Reset counters
31+
clientListenerCallCount = 0
32+
lastClientSelectionStart = -1
33+
lastClientSelectionEnd = -1
34+
}
35+
36+
@Test
37+
fun `test toolbar highlights styles when selection changes`() {
38+
// Add a client listener
39+
editText.setOnSelectionChangedListener(object : AztecText.OnSelectionChangedListener {
40+
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
41+
clientListenerCallCount++
42+
lastClientSelectionStart = selStart
43+
lastClientSelectionEnd = selEnd
44+
}
45+
})
46+
47+
// Add some text and make a selection
48+
editText.setText("Hello World")
49+
// setText triggers a selection change event, so we need to reset the counters
50+
clientListenerCallCount = 0
51+
editText.setSelection(0, 5)
52+
53+
// Verify client listener was called
54+
Assert.assertEquals(1, clientListenerCallCount)
55+
Assert.assertEquals(0, lastClientSelectionStart)
56+
Assert.assertEquals(5, lastClientSelectionEnd)
57+
58+
// Apply bold formatting
59+
val boldButton = toolbar.findViewById<ToggleButton>(ToolbarAction.BOLD.buttonId)
60+
boldButton.performClick()
61+
62+
// Verify bold button is checked
63+
Assert.assertTrue("Bold button should be checked after applying bold formatting", boldButton.isChecked)
64+
65+
// Make another selection
66+
editText.setSelection(6, 11)
67+
68+
// Verify client listener was called again
69+
Assert.assertEquals(2, clientListenerCallCount)
70+
Assert.assertEquals(6, lastClientSelectionStart)
71+
Assert.assertEquals(11, lastClientSelectionEnd)
72+
73+
// Verify bold button is unchecked for non-bold text
74+
Assert.assertFalse("Bold button should be unchecked for non-bold text", boldButton.isChecked)
75+
76+
// Apply italic formatting
77+
val italicButton = toolbar.findViewById<ToggleButton>(ToolbarAction.ITALIC.buttonId)
78+
italicButton.performClick()
79+
80+
// Verify italic button is checked
81+
Assert.assertTrue("Italic button should be checked after applying italic formatting", italicButton.isChecked)
82+
83+
// Select the bold text again
84+
editText.setSelection(0, 5)
85+
86+
// Verify bold button is checked again
87+
Assert.assertTrue("Bold button should be checked when selecting bold text", boldButton.isChecked)
88+
// Verify italic button is unchecked for non-italic text
89+
Assert.assertFalse("Italic button should be unchecked for non-italic text", italicButton.isChecked)
90+
}
91+
92+
@Test
93+
fun `test client listener works before toolbar setup`() {
94+
// Add a client listener before setting up the toolbar
95+
editText.setOnSelectionChangedListener(object : AztecText.OnSelectionChangedListener {
96+
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
97+
clientListenerCallCount++
98+
lastClientSelectionStart = selStart
99+
lastClientSelectionEnd = selEnd
100+
}
101+
})
102+
103+
// Set up the toolbar after adding the listener
104+
toolbar.setEditor(editText, null)
105+
106+
// Add some text and make a selection
107+
editText.setText("Hello World")
108+
// setText triggers a selection change event, so we need to reset the counters
109+
clientListenerCallCount = 0
110+
editText.setSelection(0, 5)
111+
112+
// Verify client listener was called
113+
Assert.assertEquals(1, clientListenerCallCount)
114+
Assert.assertEquals(0, lastClientSelectionStart)
115+
Assert.assertEquals(5, lastClientSelectionEnd)
116+
}
117+
118+
@Test
119+
fun `test client listener works after toolbar setup`() {
120+
// Set up the toolbar first
121+
toolbar.setEditor(editText, null)
122+
123+
// Add a client listener after setting up the toolbar
124+
editText.setOnSelectionChangedListener(object : AztecText.OnSelectionChangedListener {
125+
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
126+
clientListenerCallCount++
127+
lastClientSelectionStart = selStart
128+
lastClientSelectionEnd = selEnd
129+
}
130+
})
131+
132+
// Add some text and make a selection
133+
editText.setText("Hello World")
134+
// setText triggers a selection change event, so we need to reset the counters
135+
clientListenerCallCount = 0
136+
editText.setSelection(0, 5)
137+
138+
// Verify client listener was called
139+
Assert.assertEquals(1, clientListenerCallCount)
140+
Assert.assertEquals(0, lastClientSelectionStart)
141+
Assert.assertEquals(5, lastClientSelectionEnd)
142+
}
143+
144+
@Test
145+
fun `test multiple client listeners work together`() {
146+
var secondClientListenerCallCount = 0
147+
var lastSecondClientSelectionStart = -1
148+
var lastSecondClientSelectionEnd = -1
149+
150+
// Add first client listener
151+
editText.setOnSelectionChangedListener(object : AztecText.OnSelectionChangedListener {
152+
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
153+
clientListenerCallCount++
154+
lastClientSelectionStart = selStart
155+
lastClientSelectionEnd = selEnd
156+
}
157+
})
158+
159+
// Add second client listener
160+
editText.setOnSelectionChangedListener(object : AztecText.OnSelectionChangedListener {
161+
override fun onSelectionChanged(selStart: Int, selEnd: Int) {
162+
secondClientListenerCallCount++
163+
lastSecondClientSelectionStart = selStart
164+
lastSecondClientSelectionEnd = selEnd
165+
}
166+
})
167+
168+
// Add some text and make a selection
169+
editText.setText("Hello World")
170+
// setText triggers a selection change event, so we need to reset the counters
171+
clientListenerCallCount = 0
172+
secondClientListenerCallCount = 0
173+
editText.setSelection(0, 5)
174+
175+
// Verify both client listeners were called
176+
Assert.assertEquals(1, clientListenerCallCount)
177+
Assert.assertEquals(1, secondClientListenerCallCount)
178+
Assert.assertEquals(0, lastClientSelectionStart)
179+
Assert.assertEquals(5, lastClientSelectionEnd)
180+
Assert.assertEquals(0, lastSecondClientSelectionStart)
181+
Assert.assertEquals(5, lastSecondClientSelectionEnd)
182+
}
183+
}

0 commit comments

Comments
 (0)