Skip to content

Commit 3b5afa8

Browse files
dbrantcooltey
andauthored
Wikitext syntax highlighting in Talk input fields. (#4131)
* Begin transition. * Remove username spans. * Come on. * Refactor into helper class. * Correctly integrate wikiSite into username input. * Add button for mentioning user. * Pass invokeSource through. * Correctly highlight selection after inserting. --------- Co-authored-by: Cooltey Feng <cfeng@wikimedia.org>
1 parent 6feda46 commit 3b5afa8

19 files changed

+312
-232
lines changed

app/src/main/java/org/wikipedia/edit/EditSectionActivity.kt

Lines changed: 6 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package org.wikipedia.edit
22

33
import android.content.Context
44
import android.content.Intent
5-
import android.content.res.Configuration
65
import android.graphics.Typeface
76
import android.net.Uri
87
import android.os.Bundle
@@ -51,15 +50,12 @@ import org.wikipedia.edit.preview.EditPreviewFragment
5150
import org.wikipedia.edit.richtext.SyntaxHighlighter
5251
import org.wikipedia.edit.summaries.EditSummaryFragment
5352
import org.wikipedia.extensions.parcelableExtra
54-
import org.wikipedia.history.HistoryEntry
5553
import org.wikipedia.login.LoginActivity
5654
import org.wikipedia.notifications.AnonymousNotificationHelper
5755
import org.wikipedia.page.ExclusiveBottomSheetPresenter
5856
import org.wikipedia.page.LinkMovementMethodExt
5957
import org.wikipedia.page.Namespace
6058
import org.wikipedia.page.PageTitle
61-
import org.wikipedia.page.linkpreview.LinkPreviewDialog
62-
import org.wikipedia.search.SearchActivity
6359
import org.wikipedia.settings.Prefs
6460
import org.wikipedia.suggestededits.SuggestedEditsImageRecsFragment
6561
import org.wikipedia.theme.ThemeChooserDialog
@@ -104,14 +100,6 @@ class EditSectionActivity : BaseActivity(), ThemeChooserDialog.Callback {
104100
private var actionMode: ActionMode? = null
105101
private val disposables = CompositeDisposable()
106102

107-
private val requestLinkFromSearch = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
108-
if (it.resultCode == SearchActivity.RESULT_LINK_SUCCESS) {
109-
it.data?.parcelableExtra<PageTitle>(SearchActivity.EXTRA_RETURN_LINK_TITLE)?.let { title ->
110-
binding.editKeyboardOverlay.insertLink(title, pageTitle.wikiSite.languageCode)
111-
}
112-
}
113-
}
114-
115103
private val requestLogin = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
116104
if (it.resultCode == LoginActivity.RESULT_LOGIN_SUCCESS) {
117105
updateEditLicenseText()
@@ -149,8 +137,8 @@ class EditSectionActivity : BaseActivity(), ThemeChooserDialog.Callback {
149137
binding.editSectionText.setText(newWikiText.first)
150138
intent.putExtra(InsertMediaActivity.EXTRA_INSERTED_INTO_INFOBOX, newWikiText.second)
151139

152-
// TODO: automatically highlight what was added.
153-
// binding.editSectionText.setSelection(cursorPos, cursorPos + newText.length)
140+
val insertPos = newWikiText.third
141+
binding.editSectionText.setSelection(insertPos.first, insertPos.first + insertPos.second)
154142

155143
if (invokeSource == Constants.InvokeSource.EDIT_ADD_IMAGE) {
156144
// If we came from the Image Recommendation workflow, go directly to Preview.
@@ -179,55 +167,6 @@ class EditSectionActivity : BaseActivity(), ThemeChooserDialog.Callback {
179167
UriUtil.visitInExternalBrowser(this, Uri.parse(UriUtil.resolveProtocolRelativeUrl(pageTitle.wikiSite, urlStr)))
180168
}
181169

182-
private val syntaxButtonCallback = object : WikiTextKeyboardView.Callback {
183-
override fun onPreviewLink(title: String) {
184-
val dialog = LinkPreviewDialog.newInstance(HistoryEntry(PageTitle(title, pageTitle.wikiSite), HistoryEntry.SOURCE_INTERNAL_LINK), null)
185-
ExclusiveBottomSheetPresenter.show(supportFragmentManager, dialog)
186-
binding.root.post {
187-
dialog.dialog?.setOnDismissListener {
188-
if (!isDestroyed) {
189-
binding.root.postDelayed({
190-
DeviceUtil.showSoftKeyboard(binding.editSectionText)
191-
}, 200)
192-
}
193-
}
194-
}
195-
}
196-
197-
override fun onRequestInsertMedia() {
198-
requestInsertMedia.launch(InsertMediaActivity.newIntent(this@EditSectionActivity, pageTitle.wikiSite,
199-
pageTitle.displayText, Constants.InvokeSource.EDIT_ACTIVITY))
200-
}
201-
202-
override fun onRequestInsertLink() {
203-
requestLinkFromSearch.launch(SearchActivity.newIntent(this@EditSectionActivity, Constants.InvokeSource.EDIT_ACTIVITY, null, true))
204-
}
205-
206-
override fun onRequestHeading() {
207-
if (binding.editKeyboardOverlayHeadings.isVisible) {
208-
hideAllSyntaxModals()
209-
return
210-
}
211-
hideAllSyntaxModals()
212-
binding.editKeyboardOverlayHeadings.isVisible = true
213-
binding.editKeyboardOverlay.onAfterHeadingsShown()
214-
}
215-
216-
override fun onRequestFormatting() {
217-
if (binding.editKeyboardOverlayFormattingContainer.isVisible) {
218-
hideAllSyntaxModals()
219-
return
220-
}
221-
hideAllSyntaxModals()
222-
binding.editKeyboardOverlayFormattingContainer.isVisible = true
223-
binding.editKeyboardOverlay.onAfterFormattingShown()
224-
}
225-
226-
override fun onSyntaxOverlayCollapse() {
227-
hideAllSyntaxModals()
228-
}
229-
}
230-
231170
public override fun onCreate(savedInstanceState: Bundle?) {
232171
super.onCreate(savedInstanceState)
233172
binding = ActivityEditSectionBinding.inflate(layoutInflater)
@@ -285,29 +224,14 @@ class EditSectionActivity : BaseActivity(), ThemeChooserDialog.Callback {
285224
invalidateOptionsMenu()
286225
}
287226
}
288-
binding.editKeyboardOverlay.editText = binding.editSectionText
289-
binding.editKeyboardOverlay.callback = syntaxButtonCallback
290-
binding.editKeyboardOverlayFormatting.editText = binding.editSectionText
291-
binding.editKeyboardOverlayFormatting.callback = syntaxButtonCallback
292-
binding.editKeyboardOverlayHeadings.editText = binding.editSectionText
293-
binding.editKeyboardOverlayHeadings.callback = syntaxButtonCallback
227+
228+
SyntaxHighlightViewAdapter(this, pageTitle, binding.root, binding.editSectionText,
229+
binding.editKeyboardOverlay, binding.editKeyboardOverlayFormatting, binding.editKeyboardOverlayHeadings,
230+
Constants.InvokeSource.EDIT_ACTIVITY, requestInsertMedia)
294231

295232
binding.editSectionText.setOnClickListener { finishActionMode() }
296233
onEditingPrefsChanged()
297234

298-
binding.editSectionContainer.viewTreeObserver.addOnGlobalLayoutListener {
299-
binding.editSectionContainer.post {
300-
if (!isDestroyed) {
301-
if (isHardKeyboardAttached() || window.decorView.height - binding.editSectionContainer.height > DimenUtil.roundedDpToPx(150f)) {
302-
binding.editKeyboardOverlayContainer.isVisible = true
303-
} else {
304-
hideAllSyntaxModals()
305-
binding.editKeyboardOverlayContainer.isVisible = false
306-
}
307-
}
308-
}
309-
}
310-
311235
if (invokeSource == Constants.InvokeSource.EDIT_ADD_IMAGE) {
312236
// If the intent is to add an image to the article, go directly to the image insertion flow.
313237
startInsertImageFlow()
@@ -316,7 +240,6 @@ class EditSectionActivity : BaseActivity(), ThemeChooserDialog.Callback {
316240
// set focus to the EditText, but keep the keyboard hidden until the user changes the cursor location:
317241
binding.editSectionText.requestFocus()
318242
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
319-
hideAllSyntaxModals()
320243
}
321244

322245
public override fun onStart() {
@@ -332,12 +255,6 @@ class EditSectionActivity : BaseActivity(), ThemeChooserDialog.Callback {
332255
super.onDestroy()
333256
}
334257

335-
private fun isHardKeyboardAttached(): Boolean {
336-
return (resources.configuration.hardKeyboardHidden == Configuration.KEYBOARDHIDDEN_NO &&
337-
resources.configuration.keyboard != Configuration.KEYBOARD_UNDEFINED &&
338-
resources.configuration.keyboard != Configuration.KEYBOARD_NOKEYS)
339-
}
340-
341258
private fun updateEditLicenseText() {
342259
val editLicenseText = ActivityCompat.requireViewById<TextView>(this, R.id.licenseText)
343260
editLicenseText.text = StringUtil.fromHtml(getString(if (isLoggedIn) R.string.edit_save_action_license_logged_in else R.string.edit_save_action_license_anon,
@@ -771,7 +688,6 @@ class EditSectionActivity : BaseActivity(), ThemeChooserDialog.Callback {
771688
scrollToHighlight(textToHighlight)
772689
binding.editSectionText.isEnabled = editingAllowed
773690
binding.editKeyboardOverlay.isVisible = editingAllowed
774-
hideAllSyntaxModals()
775691
}
776692

777693
private fun scrollToHighlight(highlightText: String?) {
@@ -781,12 +697,6 @@ class EditSectionActivity : BaseActivity(), ThemeChooserDialog.Callback {
781697
binding.editSectionText.highlightText(highlightText)
782698
}
783699

784-
private fun hideAllSyntaxModals() {
785-
binding.editKeyboardOverlayHeadings.isVisible = false
786-
binding.editKeyboardOverlayFormattingContainer.isVisible = false
787-
binding.editKeyboardOverlay.onAfterOverlaysHidden()
788-
}
789-
790700
fun showProgressBar(enable: Boolean) {
791701
binding.viewProgressBar.isVisible = enable
792702
invalidateOptionsMenu()
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package org.wikipedia.edit
2+
3+
import android.content.Intent
4+
import android.view.View
5+
import androidx.activity.result.ActivityResultLauncher
6+
import androidx.activity.result.contract.ActivityResultContracts
7+
import androidx.appcompat.app.AppCompatActivity
8+
import androidx.core.view.isVisible
9+
import org.wikipedia.Constants
10+
import org.wikipedia.edit.insertmedia.InsertMediaActivity
11+
import org.wikipedia.extensions.parcelableExtra
12+
import org.wikipedia.history.HistoryEntry
13+
import org.wikipedia.page.ExclusiveBottomSheetPresenter
14+
import org.wikipedia.page.PageTitle
15+
import org.wikipedia.page.linkpreview.LinkPreviewDialog
16+
import org.wikipedia.search.SearchActivity
17+
import org.wikipedia.util.DeviceUtil
18+
import org.wikipedia.util.DimenUtil
19+
20+
class SyntaxHighlightViewAdapter(
21+
val activity: AppCompatActivity,
22+
val pageTitle: PageTitle,
23+
private val rootView: View,
24+
val editText: SyntaxHighlightableEditText,
25+
private val wikiTextKeyboardView: WikiTextKeyboardView,
26+
private val wikiTextKeyboardFormattingView: WikiTextKeyboardFormattingView,
27+
private val wikiTextKeyboardHeadingsView: WikiTextKeyboardHeadingsView,
28+
private val invokeSource: Constants.InvokeSource,
29+
private val requestInsertMedia: ActivityResultLauncher<Intent>,
30+
showUserMention: Boolean = false
31+
) : WikiTextKeyboardView.Callback {
32+
33+
init {
34+
wikiTextKeyboardView.editText = editText
35+
wikiTextKeyboardView.callback = this
36+
wikiTextKeyboardFormattingView.editText = editText
37+
wikiTextKeyboardFormattingView.callback = this
38+
wikiTextKeyboardHeadingsView.editText = editText
39+
wikiTextKeyboardHeadingsView.callback = this
40+
wikiTextKeyboardView.userMentionVisible = showUserMention
41+
hideAllSyntaxModals()
42+
43+
activity.window.decorView.viewTreeObserver.addOnGlobalLayoutListener {
44+
activity.window.decorView.post {
45+
if (!activity.isDestroyed) {
46+
showOrHideSyntax(editText.hasFocus())
47+
}
48+
}
49+
}
50+
51+
editText.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
52+
showOrHideSyntax(hasFocus)
53+
}
54+
}
55+
56+
private val requestLinkFromSearch = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
57+
if (it.resultCode == SearchActivity.RESULT_LINK_SUCCESS) {
58+
it.data?.parcelableExtra<PageTitle>(SearchActivity.EXTRA_RETURN_LINK_TITLE)?.let { title ->
59+
wikiTextKeyboardView.insertLink(title, pageTitle.wikiSite.languageCode)
60+
}
61+
}
62+
}
63+
64+
override fun onPreviewLink(title: String) {
65+
val dialog = LinkPreviewDialog.newInstance(HistoryEntry(PageTitle(title, pageTitle.wikiSite), HistoryEntry.SOURCE_INTERNAL_LINK), null)
66+
ExclusiveBottomSheetPresenter.show(activity.supportFragmentManager, dialog)
67+
editText.post {
68+
dialog.dialog?.setOnDismissListener {
69+
if (!activity.isDestroyed) {
70+
editText.postDelayed({
71+
DeviceUtil.showSoftKeyboard(editText)
72+
}, 200)
73+
}
74+
}
75+
}
76+
}
77+
78+
override fun onRequestInsertMedia() {
79+
requestInsertMedia.launch(InsertMediaActivity.newIntent(activity, pageTitle.wikiSite,
80+
if (invokeSource == Constants.InvokeSource.EDIT_ACTIVITY) pageTitle.displayText else "",
81+
invokeSource))
82+
}
83+
84+
override fun onRequestInsertLink() {
85+
requestLinkFromSearch.launch(SearchActivity.newIntent(activity, invokeSource, null, true))
86+
}
87+
88+
override fun onRequestHeading() {
89+
if (wikiTextKeyboardHeadingsView.isVisible) {
90+
hideAllSyntaxModals()
91+
return
92+
}
93+
hideAllSyntaxModals()
94+
wikiTextKeyboardHeadingsView.isVisible = true
95+
wikiTextKeyboardView.onAfterHeadingsShown()
96+
}
97+
98+
override fun onRequestFormatting() {
99+
if (wikiTextKeyboardFormattingView.isVisible) {
100+
hideAllSyntaxModals()
101+
return
102+
}
103+
hideAllSyntaxModals()
104+
wikiTextKeyboardFormattingView.isVisible = true
105+
wikiTextKeyboardView.onAfterFormattingShown()
106+
}
107+
108+
override fun onSyntaxOverlayCollapse() {
109+
hideAllSyntaxModals()
110+
}
111+
112+
private fun hideAllSyntaxModals() {
113+
wikiTextKeyboardHeadingsView.isVisible = false
114+
wikiTextKeyboardFormattingView.isVisible = false
115+
wikiTextKeyboardView.onAfterOverlaysHidden()
116+
}
117+
118+
private fun showOrHideSyntax(hasFocus: Boolean) {
119+
val hasMinHeight = DeviceUtil.isHardKeyboardAttached(activity.resources) ||
120+
activity.window.decorView.height - rootView.height > DimenUtil.roundedDpToPx(150f)
121+
if (hasFocus && hasMinHeight) {
122+
wikiTextKeyboardView.isVisible = true
123+
} else {
124+
hideAllSyntaxModals()
125+
wikiTextKeyboardView.isVisible = false
126+
}
127+
}
128+
}

app/src/main/java/org/wikipedia/edit/SyntaxHighlightableEditText.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import kotlin.math.min
3434
* case we need to copy over any useful compatibility logic.
3535
*/
3636
@SuppressLint("AppCompatCustomView")
37-
class SyntaxHighlightableEditText : EditText {
37+
open class SyntaxHighlightableEditText : EditText {
3838

3939
private var prevLineCount = -1
4040
private val lineNumberPaint = TextPaint()
@@ -46,7 +46,7 @@ class SyntaxHighlightableEditText : EditText {
4646
private val gutterRect = Rect()
4747
private var allowScrollToCursor = true
4848

49-
lateinit var scrollView: View
49+
var scrollView: View? = null
5050
private lateinit var actualLineFromRenderedLine: IntArray
5151

5252
var inputConnection: InputConnection? = null
@@ -112,8 +112,8 @@ class SyntaxHighlightableEditText : EditText {
112112
if (showLineNumbers && layout != null) {
113113
val wrapContent = true // TODO: make wrap content optional?
114114

115-
val firstLine = layout.getLineForVertical(scrollView.scrollY)
116-
val lastLine = layout.getLineForVertical(scrollView.scrollY + scrollView.height)
115+
val firstLine = if (scrollView != null) layout.getLineForVertical(scrollView!!.scrollY) else 0
116+
val lastLine = if (scrollView != null) layout.getLineForVertical(scrollView!!.scrollY + scrollView!!.height) else layout.lineCount - 1
117117

118118
// paint the gutter area with a slightly different color than text background.
119119
canvas?.drawRect(gutterRect, lineNumberBackgroundPaint)

app/src/main/java/org/wikipedia/edit/WikiTextKeyboardView.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import android.view.LayoutInflater
77
import android.view.inputmethod.InputConnection
88
import android.widget.EditText
99
import android.widget.FrameLayout
10+
import androidx.core.view.isVisible
1011
import org.wikipedia.databinding.ViewWikitextKeyboardBinding
1112
import org.wikipedia.page.PageTitle
1213
import org.wikipedia.util.StringUtil
@@ -25,6 +26,10 @@ class WikiTextKeyboardView : FrameLayout {
2526
var callback: Callback? = null
2627
var editText: SyntaxHighlightableEditText? = null
2728

29+
var userMentionVisible: Boolean
30+
get() { return binding.wikitextButtonUserMention.isVisible }
31+
set(value) { binding.wikitextButtonUserMention.isVisible = value }
32+
2833
constructor(context: Context) : super(context)
2934
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
3035
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle)
@@ -125,6 +130,10 @@ class WikiTextKeyboardView : FrameLayout {
125130
binding.wikitextButtonRedo.setOnClickListener {
126131
editText?.redo()
127132
}
133+
134+
binding.wikitextButtonUserMention.setOnClickListener {
135+
editText?.inputConnection?.commitText("@", 1)
136+
}
128137
}
129138

130139
fun insertLink(title: PageTitle, baseLangCode: String) {

0 commit comments

Comments
 (0)