Skip to content

Commit f4a9ec4

Browse files
committed
Animate size changes of placeholders
1 parent 8ce8c78 commit f4a9ec4

File tree

5 files changed

+164
-97
lines changed

5 files changed

+164
-97
lines changed

app/src/main/kotlin/org/wordpress/aztec/demo/MainActivity.kt

Lines changed: 17 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ import androidx.appcompat.app.AppCompatActivity
3535
import androidx.appcompat.content.res.AppCompatResources
3636
import androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback
3737
import androidx.core.content.FileProvider
38+
import kotlinx.coroutines.Dispatchers
39+
import kotlinx.coroutines.GlobalScope
40+
import kotlinx.coroutines.launch
3841
import org.wordpress.android.util.AppLog
3942
import org.wordpress.android.util.ImageUtils
4043
import org.wordpress.android.util.PermissionUtils
@@ -48,6 +51,8 @@ import org.wordpress.aztec.IHistoryListener
4851
import org.wordpress.aztec.ITextFormat
4952
import org.wordpress.aztec.glideloader.GlideImageLoader
5053
import org.wordpress.aztec.glideloader.GlideVideoThumbnailLoader
54+
import org.wordpress.aztec.placeholders.ImageWithCaptionAdapter
55+
import org.wordpress.aztec.placeholders.PlaceholderManager
5156
import org.wordpress.aztec.plugins.CssUnderlinePlugin
5257
import org.wordpress.aztec.plugins.IMediaToolbarButton
5358
import org.wordpress.aztec.plugins.shortcodes.AudioShortcodePlugin
@@ -188,40 +193,10 @@ open class MainActivity : AppCompatActivity(),
188193
private val MARK = "<p>Donec ipsum dolor, <mark style=\"color:#ff0000\">tempor sed</mark> bibendum <mark style=\"color:#1100ff\">vita</mark>.</p>"
189194

190195
private val EXAMPLE =
191-
IMG +
192-
HEADING +
193-
BOLD +
194-
ITALIC +
195-
UNDERLINE +
196-
STRIKETHROUGH +
197-
TASK_LIST +
198-
ORDERED +
199-
ORDERED_WITH_START +
200-
ORDERED_REVERSED +
201-
ORDERED_REVERSED_WITH_START +
202-
ORDERED_REVERSED_NEGATIVE_WITH_START +
203-
ORDERED_REVERSED_WITH_START_IDENT +
204-
LINE +
205-
UNORDERED +
206-
QUOTE +
207-
PREFORMAT +
208-
LINK +
209-
HIDDEN +
210-
COMMENT +
211-
COMMENT_MORE +
212-
COMMENT_PAGE +
213-
CODE +
214-
UNKNOWN +
215-
EMOJI +
216-
NON_LATIN_TEXT +
217-
LONG_TEXT +
218-
VIDEO +
219-
VIDEOPRESS +
220-
VIDEOPRESS_2 +
221-
AUDIO +
222-
GUTENBERG_CODE_BLOCK +
223-
QUOTE_RTL +
224-
MARK
196+
"""<p>Line 1</p>
197+
<placeholder height="0.5f" type="image_with_caption" caption="Image 1" src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/26/Six_eggs_views_from_the_top_on_a_white_background.jpg/440px-Six_eggs_views_from_the_top_on_a_white_background.jpg" />
198+
<p>Line 2</p>
199+
""".trimIndent()
225200

226201
private val isRunningTest: Boolean by lazy {
227202
try {
@@ -254,6 +229,7 @@ open class MainActivity : AppCompatActivity(),
254229

255230
private var mIsKeyboardOpen = false
256231
private var mHideActionBarOnSoftKeyboardUp = false
232+
private lateinit var placeholderManager: PlaceholderManager
257233

258234
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
259235
if (resultCode == Activity.RESULT_OK) {
@@ -427,15 +403,14 @@ open class MainActivity : AppCompatActivity(),
427403
}
428404
}
429405

406+
placeholderManager = PlaceholderManager(visualEditor, findViewById(R.id.container_frame_layout))
407+
placeholderManager.registerAdapter(ImageWithCaptionAdapter())
408+
430409
val galleryButton = MediaToolbarGalleryButton(toolbar)
431410
galleryButton.setMediaToolbarButtonClickListener(object : IMediaToolbarButton.IMediaToolbarClickListener {
432411
override fun onClick(view: View) {
433-
mediaMenu = PopupMenu(this@MainActivity, view)
434-
mediaMenu?.setOnMenuItemClickListener(this@MainActivity)
435-
mediaMenu?.inflate(R.menu.menu_gallery)
436-
mediaMenu?.show()
437-
if (view is ToggleButton) {
438-
view.isChecked = false
412+
GlobalScope.launch(Dispatchers.Main) {
413+
ImageWithCaptionAdapter.insertImageWithCaption(placeholderManager, "https://sample-videos.com/img/Sample-png-image-100kb.png", "Image 2")
439414
}
440415
}
441416
})
@@ -463,6 +438,7 @@ open class MainActivity : AppCompatActivity(),
463438
.setOnVideoTappedListener(this)
464439
.setOnAudioTappedListener(this)
465440
.addOnMediaDeletedListener(this)
441+
.addOnMediaDeletedListener(placeholderManager)
466442
.setOnVideoInfoRequestedListener(this)
467443
.addPlugin(WordPressCommentsPlugin(visualEditor))
468444
.addPlugin(MoreToolbarButton(visualEditor))
@@ -473,6 +449,7 @@ open class MainActivity : AppCompatActivity(),
473449
.addPlugin(HiddenGutenbergPlugin(visualEditor))
474450
.addPlugin(galleryButton)
475451
.addPlugin(cameraButton)
452+
.addPlugin(placeholderManager)
476453

477454
// initialize the plugins, text & HTML
478455
if (!isRunningTest) {

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2267,15 +2267,13 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
22672267
text.getSpans(0, text.length, AztecMediaSpan::class.java).firstOrNull {
22682268
attributePredicate.matches(it.attributes)
22692269
}?.let { mediaSpan ->
2270-
mediaSpan.beforeMediaDeleted()
22712270
val start = text.getSpanStart(mediaSpan)
22722271
val end = text.getSpanEnd(mediaSpan)
22732272

22742273
val clickableSpan = text.getSpans(start, end, AztecMediaClickableSpan::class.java).firstOrNull()
22752274

22762275
text.removeSpan(clickableSpan)
22772276
text.removeSpan(mediaSpan)
2278-
mediaSpan.onMediaDeleted()
22792277
aztecMediaSpan.onMediaDeletedListener = onMediaDeletedListener
22802278
lineBlockFormatter.insertMediaSpanOverCurrentChar(aztecMediaSpan, start)
22812279
contentChangeWatcher.notifyContentChanged()

media-placeholders/src/main/java/org/wordpress/aztec/placeholders/ImageWithCaptionAdapter.kt

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,18 @@ class ImageWithCaptionAdapter(
1919
) : PlaceholderManager.PlaceholderAdapter {
2020
private val media = mutableMapOf<String, ImageWithCaptionObject>()
2121
suspend override fun getHeight(attrs: AztecAttributes): Proportion {
22-
return Proportion.Ratio(0.5f)
22+
return Proportion.Ratio(attrs.getValue(HEIGHT).toFloatOrNull() ?: 0.5f)
2323
}
2424

2525
suspend override fun createView(context: Context, placeholderUuid: String, attrs: AztecAttributes): View {
26-
val imageWithCaptionObject = media[placeholderUuid]
27-
?: ImageWithCaptionObject(placeholderUuid, attrs.getValue(SRC_ATTRIBUTE), View.generateViewId()).apply {
26+
val imageWithCaptionObject = ImageWithCaptionObject(placeholderUuid, attrs.getValue(SRC_ATTRIBUTE), View.generateViewId()).apply {
2827
media[placeholderUuid] = this
2928
}
3029
val captionLayoutId = View.generateViewId()
3130
val imageLayoutId = imageWithCaptionObject.layoutId
3231
val linearLayout = LinearLayout(context)
3332
linearLayout.orientation = LinearLayout.VERTICAL
3433

35-
val layoutParams = LinearLayout.LayoutParams(
36-
LinearLayout.LayoutParams.MATCH_PARENT,
37-
LinearLayout.LayoutParams.MATCH_PARENT)
38-
linearLayout.layoutParams = layoutParams
3934
val image = ImageView(context)
4035
image.id = imageLayoutId
4136
val imageParams = LinearLayout.LayoutParams(
@@ -80,19 +75,22 @@ class ImageWithCaptionAdapter(
8075
private const val ADAPTER_TYPE = "image_with_caption"
8176
private const val CAPTION_ATTRIBUTE = "caption"
8277
private const val SRC_ATTRIBUTE = "src"
78+
private const val HEIGHT = "height"
8379

84-
suspend fun insertImageWithCaption(placeholderManager: PlaceholderManager, src: String, caption: String) {
85-
placeholderManager.insertOrUpdateItem(ADAPTER_TYPE) { currentAttributes, type, placeAtStart ->
80+
suspend fun insertImageWithCaption(placeholderManager: PlaceholderManager, src: String, caption: String, height: Float = 0.5f, shouldMergePlaceholders: Boolean = true) {
81+
placeholderManager.insertOrUpdateItem(ADAPTER_TYPE, {
82+
shouldMergePlaceholders
83+
}) { currentAttributes, type, placeAtStart ->
8684
if (currentAttributes == null || type != ADAPTER_TYPE) {
87-
mapOf(SRC_ATTRIBUTE to src, CAPTION_ATTRIBUTE to caption)
85+
mapOf(SRC_ATTRIBUTE to src, CAPTION_ATTRIBUTE to caption, HEIGHT to height.toString())
8886
} else {
8987
val currentCaption = currentAttributes[CAPTION_ATTRIBUTE]
9088
val newCaption = if (placeAtStart) {
9189
"$caption - $currentCaption"
9290
} else {
9391
"$currentCaption - $caption"
9492
}
95-
mapOf(SRC_ATTRIBUTE to src, CAPTION_ATTRIBUTE to newCaption)
93+
mapOf(SRC_ATTRIBUTE to src, CAPTION_ATTRIBUTE to newCaption, HEIGHT to height.toString())
9694
}
9795
}
9896
}

media-placeholders/src/main/java/org/wordpress/aztec/placeholders/PlaceholderManager.kt

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import android.graphics.drawable.Drawable
77
import android.text.Editable
88
import android.text.Layout
99
import android.text.Spanned
10+
import android.transition.TransitionManager
11+
import android.util.Log
1012
import android.view.MotionEvent
1113
import android.view.View
1214
import android.view.ViewTreeObserver
@@ -123,9 +125,23 @@ class PlaceholderManager(
123125
val targetItem = getTargetItem()
124126
val targetSpan = targetItem?.span
125127
val currentType = targetSpan?.attributes?.getValue(TYPE_ATTRIBUTE)
126-
if (currentType != null && shouldMergeItem(currentType)) {
127-
updateSpan(type, targetItem.span, targetItem.placeAtStart, updateItem, currentType)
128+
if (currentType != null) {
129+
Log.d("vojta", "Item found: $currentType")
130+
if (shouldMergeItem(currentType)) {
131+
Log.d("vojta", "Updating item: $currentType")
132+
updateSpan(type, targetItem.span, targetItem.placeAtStart, updateItem, currentType)
133+
} else {
134+
val (newLinePosition, targetSelection) = if (targetItem.placeAtStart) {
135+
targetItem.spanStart to targetItem.spanStart
136+
} else {
137+
targetItem.spanEnd to targetItem.spanEnd + 2
138+
}
139+
aztecText.text.insert(newLinePosition, Constants.NEWLINE_STRING)
140+
aztecText.setSelection(targetSelection)
141+
insertItem(type, *updateItem(null, null, false).toList().toTypedArray())
142+
}
128143
} else {
144+
Log.d("vojta", "Inserting item: $type")
129145
insertItem(type, *updateItem(null, null, false).toList().toTypedArray())
130146
}
131147
}
@@ -192,7 +208,7 @@ class PlaceholderManager(
192208
return true
193209
}
194210

195-
private data class TargetItem(val span: AztecPlaceholderSpan, val placeAtStart: Boolean)
211+
private data class TargetItem(val span: AztecPlaceholderSpan, val placeAtStart: Boolean, val spanStart: Int, val spanEnd: Int)
196212

197213
private fun getTargetItem(): TargetItem? {
198214
if (aztecText.length() == 0) {
@@ -224,7 +240,7 @@ class PlaceholderManager(
224240
from,
225241
to,
226242
AztecPlaceholderSpan::class.java
227-
).map { TargetItem(it, placeAtStart) }.lastOrNull()
243+
).map { TargetItem(it, placeAtStart, editableText.getSpanStart(it), editableText.getSpanEnd(it)) }.lastOrNull()
228244
}
229245

230246
/**
@@ -258,10 +274,12 @@ class PlaceholderManager(
258274
suspend fun reloadAllPlaceholders() {
259275
val tempPositionToId = positionToId.toList()
260276
tempPositionToId.forEach { placeholder ->
277+
Log.d("vojta", "Looking up position to ID")
261278
val isValid = positionToIdMutex.withLock {
262279
positionToId.contains(placeholder)
263280
}
264281
if (isValid) {
282+
Log.d("vojta", "Reloading all placeholders")
265283
insertContentOverSpanWithId(placeholder.uuid)
266284
}
267285
}
@@ -286,7 +304,7 @@ class PlaceholderManager(
286304
}
287305
}
288306
val targetPosition = aztecText.getElementPosition(predicate) ?: return
289-
307+
Log.d("vojta", "Inserting in position")
290308
insertInPosition(aztecAttributes ?: return, targetPosition)
291309
}
292310

@@ -318,39 +336,63 @@ class PlaceholderManager(
318336
parentTextViewRect.top += parentTextViewTopAndBottomOffset
319337
parentTextViewRect.bottom = parentTextViewRect.top + height
320338

321-
positionToIdMutex.withLock {
322-
positionToId.removeAll {
323-
it.uuid == uuid
339+
Log.d("vojta", "Looking for a view with tag $uuid")
340+
var box = container.findViewWithTag<View>(uuid)?.apply {
341+
id = uuid.hashCode()
342+
}
343+
val newWidth = adapter.calculateWidth(attrs, windowWidth) - EDITOR_INNER_PADDING
344+
val newHeight = height - EDITOR_INNER_PADDING
345+
val padding = 10
346+
val newLeftPadding = parentTextViewRect.left + padding + aztecText.paddingStart
347+
val newTopPadding = parentTextViewRect.top + padding
348+
Log.d("vojta", "Redrawing: top padding $newTopPadding, left padding $newLeftPadding, width $newWidth, height $newHeight")
349+
box?.let { existingView ->
350+
val currentParams = existingView.layoutParams as FrameLayout.LayoutParams
351+
val widthSame = currentParams.width == newWidth
352+
val heightSame = currentParams.height == newHeight
353+
val topMarginSame = currentParams.topMargin == newTopPadding
354+
val leftMarginSame = currentParams.leftMargin == newLeftPadding
355+
Log.d("vojta", "Same: $widthSame, $heightSame, $topMarginSame, $leftMarginSame")
356+
if (widthSame && heightSame && topMarginSame && leftMarginSame) {
357+
Log.d("vojta", "Not redrawing")
358+
return
359+
}
360+
Log.d("vojta", "Redrawing")
361+
if (!widthSame || !heightSame) {
362+
TransitionManager.beginDelayedTransition(container)
363+
}
364+
365+
container.removeView(box)
366+
positionToIdMutex.withLock {
367+
positionToId.removeAll {
368+
it.uuid == uuid
369+
}
324370
}
325371
}
326372

327-
var box = container.findViewWithTag<View>(uuid)
328-
val exists = box != null
329-
if (!exists) {
330-
box = adapter.createView(container.context, uuid, attrs)
331-
}
373+
box = adapter.createView(container.context, uuid, attrs)
374+
box.id = uuid.hashCode()
375+
Log.d("vojta", "Creating a new view with id: ${box.id}")
376+
box.setBackgroundColor(Color.TRANSPARENT)
377+
box.setOnTouchListener(adapter)
378+
box.tag = uuid
332379
val params = FrameLayout.LayoutParams(
333-
adapter.calculateWidth(attrs, windowWidth) - EDITOR_INNER_PADDING,
334-
height - EDITOR_INNER_PADDING
380+
newWidth,
381+
newHeight
335382
)
336-
val padding = 10
337383
params.setMargins(
338-
parentTextViewRect.left + padding + aztecText.paddingStart,
339-
parentTextViewRect.top + padding,
384+
newLeftPadding,
385+
newTopPadding,
340386
0,
341387
0
342388
)
343389
box.layoutParams = params
344-
box.tag = uuid
345-
box.setBackgroundColor(Color.TRANSPARENT)
346-
box.setOnTouchListener(adapter)
390+
347391
positionToIdMutex.withLock {
348392
positionToId.add(Placeholder(targetPosition, uuid))
349393
}
350-
if (!exists && box.parent == null) {
351-
container.addView(box)
352-
adapter.onViewCreated(box, uuid)
353-
}
394+
container.addView(box)
395+
adapter.onViewCreated(box, uuid)
354396
}
355397

356398
private fun validateAttributes(attributes: AztecAttributes): Boolean {
@@ -485,6 +527,7 @@ class PlaceholderManager(
485527
val adapter = adapters[type] ?: return@forEach
486528
it.drawable = buildPlaceholderDrawable(adapter, it.attributes)
487529
aztecText.refreshText(false)
530+
Log.d("vojta", "Building view on global layout")
488531
insertInPosition(it.attributes, aztecText.editableText.getSpanStart(it))
489532
}
490533
}

0 commit comments

Comments
 (0)