@apollohg/react-native-prose-editor 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -7
- package/android/build.gradle +7 -2
- package/android/src/main/java/com/apollohg/editor/EditorEditText.kt +289 -2
- package/android/src/main/java/com/apollohg/editor/EditorTheme.kt +51 -1
- package/android/src/main/java/com/apollohg/editor/ImageResizeOverlayView.kt +199 -0
- package/android/src/main/java/com/apollohg/editor/NativeEditorExpoView.kt +16 -3
- package/android/src/main/java/com/apollohg/editor/NativeEditorModule.kt +82 -1
- package/android/src/main/java/com/apollohg/editor/NativeToolbar.kt +403 -45
- package/android/src/main/java/com/apollohg/editor/RemoteSelectionOverlayView.kt +246 -0
- package/android/src/main/java/com/apollohg/editor/RenderBridge.kt +841 -155
- package/android/src/main/java/com/apollohg/editor/RichTextEditorView.kt +125 -8
- package/{src/EditorTheme.ts → dist/EditorTheme.d.ts} +12 -52
- package/dist/EditorTheme.js +29 -0
- package/dist/EditorToolbar.d.ts +129 -0
- package/dist/EditorToolbar.js +394 -0
- package/dist/NativeEditorBridge.d.ts +242 -0
- package/dist/NativeEditorBridge.js +647 -0
- package/dist/NativeRichTextEditor.d.ts +142 -0
- package/dist/NativeRichTextEditor.js +649 -0
- package/dist/YjsCollaboration.d.ts +83 -0
- package/dist/YjsCollaboration.js +585 -0
- package/dist/addons.d.ts +70 -0
- package/dist/addons.js +77 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +26 -0
- package/dist/schemas.d.ts +35 -0
- package/{src/schemas.ts → dist/schemas.js} +62 -27
- package/dist/useNativeEditor.d.ts +40 -0
- package/dist/useNativeEditor.js +117 -0
- package/ios/EditorAddons.swift +26 -3
- package/ios/EditorCore.xcframework/Info.plist +5 -5
- package/ios/EditorCore.xcframework/ios-arm64/libeditor_core.a +0 -0
- package/ios/EditorCore.xcframework/ios-arm64_x86_64-simulator/libeditor_core.a +0 -0
- package/ios/EditorLayoutManager.swift +236 -0
- package/ios/EditorTheme.swift +51 -1
- package/ios/Generated_editor_core.swift +270 -2
- package/ios/NativeEditorExpoView.swift +612 -45
- package/ios/NativeEditorModule.swift +81 -0
- package/ios/PositionBridge.swift +22 -0
- package/ios/RenderBridge.swift +427 -39
- package/ios/RichTextEditorView.swift +1342 -18
- package/ios/editor_coreFFI/editor_coreFFI.h +209 -0
- package/package.json +80 -64
- package/rust/android/arm64-v8a/libeditor_core.so +0 -0
- package/rust/android/armeabi-v7a/libeditor_core.so +0 -0
- package/rust/android/x86_64/libeditor_core.so +0 -0
- package/rust/bindings/kotlin/uniffi/editor_core/editor_core.kt +404 -4
- package/src/EditorToolbar.tsx +0 -620
- package/src/NativeEditorBridge.ts +0 -607
- package/src/NativeRichTextEditor.tsx +0 -951
- package/src/addons.ts +0 -158
- package/src/index.ts +0 -63
- package/src/useNativeEditor.ts +0 -173
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
package com.apollohg.editor
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.os.Build
|
|
4
5
|
import android.graphics.Color
|
|
5
6
|
import android.graphics.Typeface
|
|
6
7
|
import android.graphics.drawable.GradientDrawable
|
|
8
|
+
import android.util.TypedValue
|
|
7
9
|
import android.view.Gravity
|
|
8
10
|
import android.view.View
|
|
11
|
+
import android.view.ViewOutlineProvider
|
|
9
12
|
import android.widget.HorizontalScrollView
|
|
10
13
|
import android.widget.LinearLayout
|
|
11
14
|
import androidx.appcompat.widget.AppCompatButton
|
|
12
15
|
import androidx.appcompat.widget.AppCompatTextView
|
|
16
|
+
import androidx.appcompat.content.res.AppCompatResources
|
|
13
17
|
import androidx.core.view.setPadding
|
|
18
|
+
import com.google.android.material.R as MaterialR
|
|
19
|
+
import com.google.android.material.color.DynamicColors
|
|
14
20
|
import org.json.JSONObject
|
|
21
|
+
import kotlin.math.roundToInt
|
|
15
22
|
|
|
16
23
|
internal data class NativeToolbarState(
|
|
17
24
|
val marks: Map<String, Boolean>,
|
|
@@ -91,6 +98,9 @@ internal enum class ToolbarDefaultIconId {
|
|
|
91
98
|
italic,
|
|
92
99
|
underline,
|
|
93
100
|
strike,
|
|
101
|
+
link,
|
|
102
|
+
image,
|
|
103
|
+
blockquote,
|
|
94
104
|
bulletList,
|
|
95
105
|
orderedList,
|
|
96
106
|
indentList,
|
|
@@ -103,6 +113,7 @@ internal enum class ToolbarDefaultIconId {
|
|
|
103
113
|
|
|
104
114
|
internal enum class ToolbarItemKind {
|
|
105
115
|
mark,
|
|
116
|
+
blockquote,
|
|
106
117
|
list,
|
|
107
118
|
command,
|
|
108
119
|
node,
|
|
@@ -122,6 +133,9 @@ internal data class NativeToolbarIcon(
|
|
|
122
133
|
ToolbarDefaultIconId.italic to "I",
|
|
123
134
|
ToolbarDefaultIconId.underline to "U",
|
|
124
135
|
ToolbarDefaultIconId.strike to "S",
|
|
136
|
+
ToolbarDefaultIconId.link to "🔗",
|
|
137
|
+
ToolbarDefaultIconId.image to "🖼",
|
|
138
|
+
ToolbarDefaultIconId.blockquote to "❝",
|
|
125
139
|
ToolbarDefaultIconId.bulletList to "•≡",
|
|
126
140
|
ToolbarDefaultIconId.orderedList to "1.",
|
|
127
141
|
ToolbarDefaultIconId.indentList to "→",
|
|
@@ -136,6 +150,9 @@ internal data class NativeToolbarIcon(
|
|
|
136
150
|
ToolbarDefaultIconId.italic to "format-italic",
|
|
137
151
|
ToolbarDefaultIconId.underline to "format-underlined",
|
|
138
152
|
ToolbarDefaultIconId.strike to "strikethrough-s",
|
|
153
|
+
ToolbarDefaultIconId.link to "link",
|
|
154
|
+
ToolbarDefaultIconId.image to "image",
|
|
155
|
+
ToolbarDefaultIconId.blockquote to "format-quote",
|
|
139
156
|
ToolbarDefaultIconId.bulletList to "format-list-bulleted",
|
|
140
157
|
ToolbarDefaultIconId.orderedList to "format-list-numbered",
|
|
141
158
|
ToolbarDefaultIconId.indentList to "format-indent-increase",
|
|
@@ -279,6 +296,7 @@ internal data class NativeToolbarItem(
|
|
|
279
296
|
NativeToolbarItem(ToolbarItemKind.mark, label = "Italic", icon = NativeToolbarIcon(defaultId = ToolbarDefaultIconId.italic), mark = "italic"),
|
|
280
297
|
NativeToolbarItem(ToolbarItemKind.mark, label = "Underline", icon = NativeToolbarIcon(defaultId = ToolbarDefaultIconId.underline), mark = "underline"),
|
|
281
298
|
NativeToolbarItem(ToolbarItemKind.mark, label = "Strikethrough", icon = NativeToolbarIcon(defaultId = ToolbarDefaultIconId.strike), mark = "strike"),
|
|
299
|
+
NativeToolbarItem(ToolbarItemKind.blockquote, label = "Blockquote", icon = NativeToolbarIcon(defaultId = ToolbarDefaultIconId.blockquote)),
|
|
282
300
|
NativeToolbarItem(ToolbarItemKind.separator),
|
|
283
301
|
NativeToolbarItem(ToolbarItemKind.list, label = "Bullet List", icon = NativeToolbarIcon(defaultId = ToolbarDefaultIconId.bulletList), listType = ToolbarListType.bulletList),
|
|
284
302
|
NativeToolbarItem(ToolbarItemKind.list, label = "Ordered List", icon = NativeToolbarIcon(defaultId = ToolbarDefaultIconId.orderedList), listType = ToolbarListType.orderedList),
|
|
@@ -313,6 +331,11 @@ internal data class NativeToolbarItem(
|
|
|
313
331
|
val label = rawItem.optNullableString("label") ?: continue
|
|
314
332
|
parsed.add(NativeToolbarItem(type, key, label, icon, mark = mark))
|
|
315
333
|
}
|
|
334
|
+
ToolbarItemKind.blockquote -> {
|
|
335
|
+
val icon = NativeToolbarIcon.fromJson(rawItem.optJSONObject("icon")) ?: continue
|
|
336
|
+
val label = rawItem.optNullableString("label") ?: continue
|
|
337
|
+
parsed.add(NativeToolbarItem(type, key, label, icon))
|
|
338
|
+
}
|
|
316
339
|
ToolbarItemKind.list -> {
|
|
317
340
|
val icon = NativeToolbarIcon.fromJson(rawItem.optJSONObject("icon")) ?: continue
|
|
318
341
|
val listType = runCatching {
|
|
@@ -358,6 +381,16 @@ internal data class NativeToolbarItem(
|
|
|
358
381
|
}
|
|
359
382
|
|
|
360
383
|
internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollView(context) {
|
|
384
|
+
private companion object {
|
|
385
|
+
private const val NATIVE_CONTAINER_HEIGHT_DP = 64
|
|
386
|
+
private const val NATIVE_CONTAINER_HORIZONTAL_PADDING_DP = 16
|
|
387
|
+
private const val NATIVE_CONTAINER_VERTICAL_PADDING_DP = 12
|
|
388
|
+
private const val NATIVE_BUTTON_SIZE_DP = 40
|
|
389
|
+
private const val NATIVE_BUTTON_ICON_SIZE_SP = 24f
|
|
390
|
+
private const val NATIVE_ITEM_SPACING_DP = 8
|
|
391
|
+
private const val NATIVE_GROUP_SPACING_DP = 12
|
|
392
|
+
}
|
|
393
|
+
|
|
361
394
|
private data class ButtonBinding(
|
|
362
395
|
val item: NativeToolbarItem,
|
|
363
396
|
val button: AppCompatButton
|
|
@@ -366,6 +399,7 @@ internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollVie
|
|
|
366
399
|
var onPressItem: ((NativeToolbarItem) -> Unit)? = null
|
|
367
400
|
var onSelectMentionSuggestion: ((NativeMentionSuggestion) -> Unit)? = null
|
|
368
401
|
|
|
402
|
+
private val themedContext: Context = DynamicColors.wrapContextIfAvailable(context)
|
|
369
403
|
private val contentRow = LinearLayout(context)
|
|
370
404
|
private var theme: EditorToolbarTheme? = null
|
|
371
405
|
private var mentionTheme: EditorMentionTheme? = null
|
|
@@ -375,11 +409,18 @@ internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollVie
|
|
|
375
409
|
private val bindings = mutableListOf<ButtonBinding>()
|
|
376
410
|
private val separators = mutableListOf<View>()
|
|
377
411
|
private val mentionChips = mutableListOf<MentionSuggestionChipView>()
|
|
412
|
+
private val buttonBackgroundColors = mutableMapOf<AppCompatButton, Int>()
|
|
378
413
|
private val density = resources.displayMetrics.density
|
|
414
|
+
internal var appliedAppearance: EditorToolbarAppearance = EditorToolbarAppearance.CUSTOM
|
|
415
|
+
private set
|
|
379
416
|
internal var appliedChromeCornerRadiusPx: Float = 0f
|
|
380
417
|
private set
|
|
381
418
|
internal var appliedChromeStrokeWidthPx: Int = 0
|
|
382
419
|
private set
|
|
420
|
+
internal var appliedChromeElevationPx: Float = 0f
|
|
421
|
+
private set
|
|
422
|
+
internal var appliedChromeColor: Int = Color.TRANSPARENT
|
|
423
|
+
private set
|
|
383
424
|
internal var appliedButtonCornerRadiusPx: Float = 0f
|
|
384
425
|
private set
|
|
385
426
|
val isShowingMentionSuggestions: Boolean
|
|
@@ -390,13 +431,17 @@ internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollVie
|
|
|
390
431
|
overScrollMode = OVER_SCROLL_NEVER
|
|
391
432
|
setBackgroundColor(Color.TRANSPARENT)
|
|
392
433
|
clipToPadding = false
|
|
434
|
+
clipChildren = false
|
|
435
|
+
isFillViewport = true
|
|
393
436
|
|
|
394
437
|
contentRow.orientation = LinearLayout.HORIZONTAL
|
|
395
|
-
contentRow.gravity = Gravity.CENTER_VERTICAL
|
|
438
|
+
contentRow.gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
|
396
439
|
contentRow.setPadding(dp(12))
|
|
440
|
+
contentRow.clipToPadding = false
|
|
441
|
+
contentRow.clipChildren = false
|
|
397
442
|
addView(
|
|
398
443
|
contentRow,
|
|
399
|
-
LayoutParams(LayoutParams.
|
|
444
|
+
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
400
445
|
)
|
|
401
446
|
rebuildContent()
|
|
402
447
|
}
|
|
@@ -412,7 +457,7 @@ internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollVie
|
|
|
412
457
|
this.theme = theme
|
|
413
458
|
updateChrome()
|
|
414
459
|
separators.forEach { separator ->
|
|
415
|
-
separator.setBackgroundColor(
|
|
460
|
+
separator.setBackgroundColor(resolveSeparatorColor())
|
|
416
461
|
}
|
|
417
462
|
bindings.forEach { binding ->
|
|
418
463
|
updateButtonAppearance(
|
|
@@ -422,14 +467,14 @@ internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollVie
|
|
|
422
467
|
)
|
|
423
468
|
}
|
|
424
469
|
mentionChips.forEach { chip ->
|
|
425
|
-
chip.applyTheme(mentionTheme)
|
|
470
|
+
chip.applyTheme(mentionTheme, theme?.appearance ?: EditorToolbarAppearance.CUSTOM)
|
|
426
471
|
}
|
|
427
472
|
}
|
|
428
473
|
|
|
429
474
|
fun applyMentionTheme(theme: EditorMentionTheme?) {
|
|
430
475
|
mentionTheme = theme
|
|
431
476
|
mentionChips.forEach { chip ->
|
|
432
|
-
chip.applyTheme(theme)
|
|
477
|
+
chip.applyTheme(theme, this.theme?.appearance ?: EditorToolbarAppearance.CUSTOM)
|
|
433
478
|
}
|
|
434
479
|
}
|
|
435
480
|
|
|
@@ -454,6 +499,18 @@ internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollVie
|
|
|
454
499
|
mentionChips.getOrNull(index)?.performClick()
|
|
455
500
|
}
|
|
456
501
|
|
|
502
|
+
internal fun buttonAtForTesting(index: Int): AppCompatButton? =
|
|
503
|
+
bindings.getOrNull(index)?.button
|
|
504
|
+
|
|
505
|
+
internal fun buttonBackgroundColorAtForTesting(index: Int): Int? =
|
|
506
|
+
bindings.getOrNull(index)?.button?.let { buttonBackgroundColors[it] }
|
|
507
|
+
|
|
508
|
+
internal fun mentionChipAtForTesting(index: Int): MentionSuggestionChipView? =
|
|
509
|
+
mentionChips.getOrNull(index)
|
|
510
|
+
|
|
511
|
+
internal fun separatorAtForTesting(index: Int): View? =
|
|
512
|
+
separators.getOrNull(index)
|
|
513
|
+
|
|
457
514
|
private fun rebuildContent() {
|
|
458
515
|
bindings.clear()
|
|
459
516
|
separators.clear()
|
|
@@ -475,40 +532,36 @@ internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollVie
|
|
|
475
532
|
for (item in compactItems(items)) {
|
|
476
533
|
if (item.type == ToolbarItemKind.separator) {
|
|
477
534
|
val separator = View(context)
|
|
478
|
-
|
|
479
|
-
params.marginStart = dp(6)
|
|
480
|
-
params.marginEnd = dp(6)
|
|
481
|
-
separator.layoutParams = params
|
|
482
|
-
separator.setBackgroundColor(theme?.separatorColor ?: Color.parseColor("#E5E5EA"))
|
|
535
|
+
configureSeparator(separator)
|
|
483
536
|
separators.add(separator)
|
|
484
537
|
contentRow.addView(separator)
|
|
485
538
|
continue
|
|
486
539
|
}
|
|
487
540
|
|
|
488
|
-
val button = AppCompatButton(
|
|
489
|
-
val resolvedIcon = item.icon?.resolveForAndroid(
|
|
541
|
+
val button = AppCompatButton(themedContext).apply {
|
|
542
|
+
val resolvedIcon = item.icon?.resolveForAndroid(themedContext)
|
|
490
543
|
?: NativeToolbarResolvedIcon("?")
|
|
491
544
|
text = resolvedIcon.text
|
|
492
545
|
typeface = resolvedIcon.typeface ?: Typeface.DEFAULT
|
|
493
|
-
textSize = 16f
|
|
494
|
-
minWidth = dp(36)
|
|
495
|
-
minimumWidth = dp(36)
|
|
496
|
-
minHeight = dp(36)
|
|
497
|
-
minimumHeight = dp(36)
|
|
498
546
|
gravity = Gravity.CENTER
|
|
499
|
-
setPadding(dp(10), dp(8), dp(10), dp(8))
|
|
500
547
|
background = GradientDrawable()
|
|
501
548
|
isAllCaps = false
|
|
502
549
|
includeFontPadding = false
|
|
503
550
|
contentDescription = item.label
|
|
504
551
|
setOnClickListener { onPressItem?.invoke(item) }
|
|
552
|
+
elevation = 0f
|
|
553
|
+
translationZ = 0f
|
|
554
|
+
stateListAnimator = null
|
|
555
|
+
}
|
|
556
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
557
|
+
button.foreground = resolveDrawableAttr(android.R.attr.selectableItemBackgroundBorderless)
|
|
505
558
|
}
|
|
506
559
|
val params = LinearLayout.LayoutParams(
|
|
507
560
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
|
508
561
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
|
509
562
|
)
|
|
510
|
-
params.marginEnd = dp(6)
|
|
511
563
|
button.layoutParams = params
|
|
564
|
+
applyButtonLayout(button, appearance = theme?.appearance ?: EditorToolbarAppearance.CUSTOM)
|
|
512
565
|
bindings.add(ButtonBinding(item, button))
|
|
513
566
|
contentRow.addView(button)
|
|
514
567
|
}
|
|
@@ -517,7 +570,7 @@ internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollVie
|
|
|
517
570
|
private fun rebuildMentionSuggestions() {
|
|
518
571
|
for (suggestion in mentionSuggestions) {
|
|
519
572
|
val chip = MentionSuggestionChipView(context, suggestion).apply {
|
|
520
|
-
applyTheme(mentionTheme)
|
|
573
|
+
applyTheme(mentionTheme, theme?.appearance ?: EditorToolbarAppearance.CUSTOM)
|
|
521
574
|
setOnClickListener { onSelectMentionSuggestion?.invoke(suggestion) }
|
|
522
575
|
}
|
|
523
576
|
val params = LinearLayout.LayoutParams(
|
|
@@ -542,41 +595,135 @@ internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollVie
|
|
|
542
595
|
}
|
|
543
596
|
|
|
544
597
|
private fun updateChrome() {
|
|
545
|
-
val
|
|
546
|
-
val
|
|
598
|
+
val appearance = theme?.appearance ?: EditorToolbarAppearance.CUSTOM
|
|
599
|
+
val cornerRadiusPx = (theme?.resolvedBorderRadius() ?: 0f) * density
|
|
600
|
+
val strokeWidthPx = if (appearance == EditorToolbarAppearance.NATIVE) {
|
|
601
|
+
0
|
|
602
|
+
} else {
|
|
603
|
+
((theme?.resolvedBorderWidth() ?: 1f) * density).roundToInt().coerceAtLeast(1)
|
|
604
|
+
}
|
|
547
605
|
val drawable = GradientDrawable().apply {
|
|
548
606
|
shape = GradientDrawable.RECTANGLE
|
|
549
607
|
cornerRadius = cornerRadiusPx
|
|
550
|
-
setColor(
|
|
551
|
-
|
|
608
|
+
setColor(
|
|
609
|
+
if (appearance == EditorToolbarAppearance.NATIVE) {
|
|
610
|
+
resolveColorAttr(
|
|
611
|
+
MaterialR.attr.colorSurfaceContainer,
|
|
612
|
+
MaterialR.attr.colorSurfaceContainerLow,
|
|
613
|
+
MaterialR.attr.colorSurface,
|
|
614
|
+
android.R.attr.colorBackground
|
|
615
|
+
)
|
|
616
|
+
} else {
|
|
617
|
+
theme?.backgroundColor ?: resolveColorAttr(
|
|
618
|
+
MaterialR.attr.colorSurface,
|
|
619
|
+
android.R.attr.colorBackground
|
|
620
|
+
)
|
|
621
|
+
}
|
|
622
|
+
)
|
|
623
|
+
if (strokeWidthPx > 0) {
|
|
624
|
+
setStroke(strokeWidthPx, theme?.borderColor ?: resolveSeparatorColor())
|
|
625
|
+
}
|
|
552
626
|
}
|
|
627
|
+
appliedAppearance = appearance
|
|
553
628
|
appliedChromeCornerRadiusPx = cornerRadiusPx
|
|
554
629
|
appliedChromeStrokeWidthPx = strokeWidthPx
|
|
630
|
+
appliedChromeElevationPx = 0f
|
|
631
|
+
appliedChromeColor = if (appearance == EditorToolbarAppearance.NATIVE) {
|
|
632
|
+
resolveColorAttr(
|
|
633
|
+
MaterialR.attr.colorSurfaceContainer,
|
|
634
|
+
MaterialR.attr.colorSurfaceContainerLow,
|
|
635
|
+
MaterialR.attr.colorSurface,
|
|
636
|
+
android.R.attr.colorBackground
|
|
637
|
+
)
|
|
638
|
+
} else {
|
|
639
|
+
theme?.backgroundColor ?: resolveColorAttr(
|
|
640
|
+
MaterialR.attr.colorSurface,
|
|
641
|
+
android.R.attr.colorBackground
|
|
642
|
+
)
|
|
643
|
+
}
|
|
555
644
|
background = drawable
|
|
556
|
-
|
|
645
|
+
outlineProvider = ViewOutlineProvider.BACKGROUND
|
|
646
|
+
clipToOutline = cornerRadiusPx > 0f
|
|
647
|
+
elevation = appliedChromeElevationPx
|
|
648
|
+
updateContainerLayout(appearance)
|
|
649
|
+
separators.forEach(::configureSeparator)
|
|
557
650
|
}
|
|
558
651
|
|
|
559
652
|
private fun updateButtonAppearance(button: AppCompatButton, enabled: Boolean, active: Boolean) {
|
|
560
|
-
val
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
653
|
+
val appearance = theme?.appearance ?: EditorToolbarAppearance.CUSTOM
|
|
654
|
+
applyButtonLayout(button, appearance)
|
|
655
|
+
val textColor = if (appearance == EditorToolbarAppearance.NATIVE) {
|
|
656
|
+
when {
|
|
657
|
+
!enabled -> withAlpha(
|
|
658
|
+
resolveColorAttr(
|
|
659
|
+
MaterialR.attr.colorOnSurface,
|
|
660
|
+
android.R.attr.textColorPrimary
|
|
661
|
+
),
|
|
662
|
+
0.38f
|
|
663
|
+
)
|
|
664
|
+
active -> resolveColorAttr(
|
|
665
|
+
MaterialR.attr.colorOnSecondaryContainer,
|
|
666
|
+
MaterialR.attr.colorOnPrimaryContainer,
|
|
667
|
+
MaterialR.attr.colorOnSurface,
|
|
668
|
+
android.R.attr.textColorPrimary
|
|
669
|
+
)
|
|
670
|
+
else -> resolveColorAttr(
|
|
671
|
+
MaterialR.attr.colorOnSurfaceVariant,
|
|
672
|
+
MaterialR.attr.colorOnSurface,
|
|
673
|
+
android.R.attr.textColorSecondary
|
|
674
|
+
)
|
|
675
|
+
}
|
|
676
|
+
} else {
|
|
677
|
+
when {
|
|
678
|
+
!enabled -> theme?.buttonDisabledColor ?: withAlpha(
|
|
679
|
+
resolveColorAttr(MaterialR.attr.colorOnSurface, android.R.attr.textColorPrimary),
|
|
680
|
+
0.38f
|
|
681
|
+
)
|
|
682
|
+
active -> theme?.buttonActiveColor ?: resolveColorAttr(
|
|
683
|
+
MaterialR.attr.colorPrimary,
|
|
684
|
+
android.R.attr.textColorPrimary
|
|
685
|
+
)
|
|
686
|
+
else -> theme?.buttonColor ?: resolveColorAttr(
|
|
687
|
+
MaterialR.attr.colorOnSurfaceVariant,
|
|
688
|
+
MaterialR.attr.colorOnSurface,
|
|
689
|
+
android.R.attr.textColorSecondary
|
|
690
|
+
)
|
|
691
|
+
}
|
|
564
692
|
}
|
|
565
|
-
val backgroundColor = if (
|
|
566
|
-
|
|
693
|
+
val backgroundColor = if (appearance == EditorToolbarAppearance.NATIVE) {
|
|
694
|
+
if (active) {
|
|
695
|
+
resolveColorAttr(
|
|
696
|
+
MaterialR.attr.colorSecondaryContainer,
|
|
697
|
+
MaterialR.attr.colorPrimaryContainer,
|
|
698
|
+
MaterialR.attr.colorSurfaceVariant,
|
|
699
|
+
android.R.attr.colorAccent
|
|
700
|
+
)
|
|
701
|
+
} else {
|
|
702
|
+
Color.TRANSPARENT
|
|
703
|
+
}
|
|
704
|
+
} else if (active) {
|
|
705
|
+
theme?.buttonActiveBackgroundColor ?: resolveColorAttr(
|
|
706
|
+
MaterialR.attr.colorPrimaryContainer,
|
|
707
|
+
MaterialR.attr.colorSecondaryContainer,
|
|
708
|
+
MaterialR.attr.colorSurfaceVariant,
|
|
709
|
+
android.R.attr.colorAccent
|
|
710
|
+
)
|
|
567
711
|
} else {
|
|
568
712
|
Color.TRANSPARENT
|
|
569
713
|
}
|
|
570
|
-
val buttonCornerRadiusPx = (theme?.
|
|
714
|
+
val buttonCornerRadiusPx = (theme?.resolvedButtonBorderRadius() ?: 6f) * density
|
|
571
715
|
val drawable = GradientDrawable().apply {
|
|
572
716
|
shape = GradientDrawable.RECTANGLE
|
|
573
717
|
cornerRadius = buttonCornerRadiusPx
|
|
574
718
|
setColor(backgroundColor)
|
|
575
719
|
}
|
|
576
720
|
appliedButtonCornerRadiusPx = buttonCornerRadiusPx
|
|
721
|
+
buttonBackgroundColors[button] = backgroundColor
|
|
577
722
|
button.background = drawable
|
|
578
723
|
button.setTextColor(textColor)
|
|
579
|
-
button.alpha = if (enabled) 1f else 0.7f
|
|
724
|
+
button.alpha = if (enabled || appearance == EditorToolbarAppearance.NATIVE) 1f else 0.7f
|
|
725
|
+
button.refreshDrawableState()
|
|
726
|
+
button.invalidate()
|
|
580
727
|
}
|
|
581
728
|
|
|
582
729
|
private fun buttonState(
|
|
@@ -589,6 +736,10 @@ internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollVie
|
|
|
589
736
|
val mark = item.mark.orEmpty()
|
|
590
737
|
Pair(state.allowedMarks.contains(mark), state.marks[mark] == true)
|
|
591
738
|
}
|
|
739
|
+
ToolbarItemKind.blockquote -> Pair(
|
|
740
|
+
state.commands["toggleBlockquote"] == true,
|
|
741
|
+
state.nodes["blockquote"] == true
|
|
742
|
+
)
|
|
592
743
|
ToolbarItemKind.list -> when (item.listType) {
|
|
593
744
|
ToolbarListType.bulletList -> Pair(
|
|
594
745
|
state.commands["wrapBulletList"] == true,
|
|
@@ -617,15 +768,133 @@ internal class EditorKeyboardToolbarView(context: Context) : HorizontalScrollVie
|
|
|
617
768
|
}
|
|
618
769
|
|
|
619
770
|
private fun dp(value: Int): Int = (value * density).toInt()
|
|
771
|
+
|
|
772
|
+
private fun resolveColorAttr(vararg attrs: Int): Int =
|
|
773
|
+
resolveColorAttrOrNull(*attrs) ?: Color.TRANSPARENT
|
|
774
|
+
|
|
775
|
+
private fun resolveColorAttrOrNull(vararg attrs: Int): Int? {
|
|
776
|
+
val typedValue = TypedValue()
|
|
777
|
+
for (attr in attrs) {
|
|
778
|
+
if (!themedContext.theme.resolveAttribute(attr, typedValue, true)) {
|
|
779
|
+
continue
|
|
780
|
+
}
|
|
781
|
+
if (typedValue.resourceId != 0) {
|
|
782
|
+
AppCompatResources.getColorStateList(themedContext, typedValue.resourceId)
|
|
783
|
+
?.defaultColor
|
|
784
|
+
?.let { return it }
|
|
785
|
+
} else if (typedValue.type in TypedValue.TYPE_FIRST_COLOR_INT..TypedValue.TYPE_LAST_COLOR_INT) {
|
|
786
|
+
return typedValue.data
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return null
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
private fun resolveDrawableAttr(attr: Int) =
|
|
793
|
+
TypedValue().let { typedValue ->
|
|
794
|
+
if (!themedContext.theme.resolveAttribute(attr, typedValue, true) || typedValue.resourceId == 0) {
|
|
795
|
+
null
|
|
796
|
+
} else {
|
|
797
|
+
AppCompatResources.getDrawable(themedContext, typedValue.resourceId)
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
private fun resolveSeparatorColor(): Int =
|
|
802
|
+
theme?.separatorColor
|
|
803
|
+
?: theme?.borderColor
|
|
804
|
+
?: resolveColorAttr(
|
|
805
|
+
MaterialR.attr.colorOutlineVariant,
|
|
806
|
+
MaterialR.attr.colorOutline,
|
|
807
|
+
android.R.attr.textColorHint
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
private fun updateContainerLayout(appearance: EditorToolbarAppearance) {
|
|
811
|
+
val isNative = appearance == EditorToolbarAppearance.NATIVE
|
|
812
|
+
val horizontalPadding = dp(
|
|
813
|
+
if (isNative) {
|
|
814
|
+
NATIVE_CONTAINER_HORIZONTAL_PADDING_DP
|
|
815
|
+
} else {
|
|
816
|
+
12
|
|
817
|
+
}
|
|
818
|
+
)
|
|
819
|
+
val verticalPadding = dp(
|
|
820
|
+
if (isNative) {
|
|
821
|
+
NATIVE_CONTAINER_VERTICAL_PADDING_DP
|
|
822
|
+
} else {
|
|
823
|
+
12
|
|
824
|
+
}
|
|
825
|
+
)
|
|
826
|
+
contentRow.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding)
|
|
827
|
+
contentRow.gravity = if (isNative) {
|
|
828
|
+
Gravity.START or Gravity.CENTER_VERTICAL
|
|
829
|
+
} else {
|
|
830
|
+
Gravity.CENTER_VERTICAL
|
|
831
|
+
}
|
|
832
|
+
contentRow.minimumHeight = dp(
|
|
833
|
+
if (isNative) {
|
|
834
|
+
NATIVE_CONTAINER_HEIGHT_DP
|
|
835
|
+
} else {
|
|
836
|
+
0
|
|
837
|
+
}
|
|
838
|
+
)
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
private fun applyButtonLayout(button: AppCompatButton, appearance: EditorToolbarAppearance) {
|
|
842
|
+
val isNative = appearance == EditorToolbarAppearance.NATIVE
|
|
843
|
+
val sizePx = dp(if (isNative) NATIVE_BUTTON_SIZE_DP else 36)
|
|
844
|
+
button.textSize = if (isNative) NATIVE_BUTTON_ICON_SIZE_SP else 16f
|
|
845
|
+
button.minWidth = sizePx
|
|
846
|
+
button.minimumWidth = sizePx
|
|
847
|
+
button.minHeight = sizePx
|
|
848
|
+
button.minimumHeight = sizePx
|
|
849
|
+
button.setPadding(
|
|
850
|
+
if (isNative) 0 else dp(10),
|
|
851
|
+
if (isNative) 0 else dp(8),
|
|
852
|
+
if (isNative) 0 else dp(10),
|
|
853
|
+
if (isNative) 0 else dp(8)
|
|
854
|
+
)
|
|
855
|
+
(button.layoutParams as? LinearLayout.LayoutParams)?.let { params ->
|
|
856
|
+
params.marginEnd = dp(if (isNative) NATIVE_ITEM_SPACING_DP else 6)
|
|
857
|
+
button.layoutParams = params
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
private fun configureSeparator(separator: View) {
|
|
862
|
+
val appearance = theme?.appearance ?: EditorToolbarAppearance.CUSTOM
|
|
863
|
+
val params = if (appearance == EditorToolbarAppearance.NATIVE) {
|
|
864
|
+
LinearLayout.LayoutParams(dp(1), dp(24)).apply {
|
|
865
|
+
marginStart = dp(NATIVE_GROUP_SPACING_DP / 2)
|
|
866
|
+
marginEnd = dp(NATIVE_GROUP_SPACING_DP / 2)
|
|
867
|
+
}
|
|
868
|
+
} else {
|
|
869
|
+
LinearLayout.LayoutParams(dp(1), dp(22)).apply {
|
|
870
|
+
marginStart = dp(6)
|
|
871
|
+
marginEnd = dp(6)
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
separator.layoutParams = params
|
|
875
|
+
separator.setBackgroundColor(
|
|
876
|
+
if (appearance == EditorToolbarAppearance.NATIVE) {
|
|
877
|
+
withAlpha(resolveSeparatorColor(), 0.6f)
|
|
878
|
+
} else {
|
|
879
|
+
resolveSeparatorColor()
|
|
880
|
+
}
|
|
881
|
+
)
|
|
882
|
+
}
|
|
620
883
|
}
|
|
621
884
|
|
|
622
|
-
private
|
|
885
|
+
private fun withAlpha(color: Int, alphaFraction: Float): Int {
|
|
886
|
+
val alpha = (alphaFraction.coerceIn(0f, 1f) * 255).roundToInt()
|
|
887
|
+
return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color))
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
internal class MentionSuggestionChipView(
|
|
623
891
|
context: Context,
|
|
624
892
|
val suggestion: NativeMentionSuggestion
|
|
625
893
|
) : LinearLayout(context) {
|
|
626
894
|
private val titleView = AppCompatTextView(context)
|
|
627
895
|
private val subtitleView = AppCompatTextView(context)
|
|
628
896
|
private var theme: EditorMentionTheme? = null
|
|
897
|
+
private var toolbarAppearance: EditorToolbarAppearance = EditorToolbarAppearance.CUSTOM
|
|
629
898
|
private val density = resources.displayMetrics.density
|
|
630
899
|
|
|
631
900
|
init {
|
|
@@ -671,15 +940,34 @@ private class MentionSuggestionChipView(
|
|
|
671
940
|
applyTheme(null)
|
|
672
941
|
}
|
|
673
942
|
|
|
674
|
-
fun applyTheme(
|
|
943
|
+
fun applyTheme(
|
|
944
|
+
theme: EditorMentionTheme?,
|
|
945
|
+
toolbarAppearance: EditorToolbarAppearance = EditorToolbarAppearance.CUSTOM
|
|
946
|
+
) {
|
|
675
947
|
this.theme = theme
|
|
948
|
+
this.toolbarAppearance = toolbarAppearance
|
|
676
949
|
val hasSubtitle = !suggestion.subtitle.isNullOrBlank()
|
|
677
950
|
subtitleView.visibility = if (hasSubtitle) View.VISIBLE else View.GONE
|
|
678
951
|
background = GradientDrawable().apply {
|
|
679
952
|
shape = GradientDrawable.RECTANGLE
|
|
680
|
-
cornerRadius = (theme?.borderRadius ?: 12f) * density
|
|
681
|
-
setColor(
|
|
682
|
-
|
|
953
|
+
cornerRadius = (if (toolbarAppearance == EditorToolbarAppearance.NATIVE) 20f else (theme?.borderRadius ?: 12f)) * density
|
|
954
|
+
setColor(
|
|
955
|
+
if (toolbarAppearance == EditorToolbarAppearance.NATIVE) {
|
|
956
|
+
Color.TRANSPARENT
|
|
957
|
+
} else {
|
|
958
|
+
theme?.backgroundColor ?: resolveColorAttr(
|
|
959
|
+
MaterialR.attr.colorSurfaceContainerLow,
|
|
960
|
+
MaterialR.attr.colorSurfaceVariant,
|
|
961
|
+
MaterialR.attr.colorSurface,
|
|
962
|
+
android.R.attr.colorBackground
|
|
963
|
+
)
|
|
964
|
+
}
|
|
965
|
+
)
|
|
966
|
+
val strokeWidth = if (toolbarAppearance == EditorToolbarAppearance.NATIVE) {
|
|
967
|
+
0
|
|
968
|
+
} else {
|
|
969
|
+
((theme?.borderWidth ?: 0f) * density).toInt()
|
|
970
|
+
}
|
|
683
971
|
if (strokeWidth > 0) {
|
|
684
972
|
setStroke(strokeWidth, theme?.borderColor ?: Color.TRANSPARENT)
|
|
685
973
|
}
|
|
@@ -689,23 +977,93 @@ private class MentionSuggestionChipView(
|
|
|
689
977
|
|
|
690
978
|
private fun updateAppearance(highlighted: Boolean) {
|
|
691
979
|
val backgroundDrawable = background as? GradientDrawable
|
|
692
|
-
val backgroundColor = if (
|
|
693
|
-
|
|
980
|
+
val backgroundColor = if (toolbarAppearance == EditorToolbarAppearance.NATIVE) {
|
|
981
|
+
if (highlighted) {
|
|
982
|
+
resolveColorAttr(
|
|
983
|
+
MaterialR.attr.colorSecondaryContainer,
|
|
984
|
+
MaterialR.attr.colorPrimaryContainer,
|
|
985
|
+
MaterialR.attr.colorSurfaceVariant,
|
|
986
|
+
android.R.attr.colorAccent
|
|
987
|
+
)
|
|
988
|
+
} else {
|
|
989
|
+
Color.TRANSPARENT
|
|
990
|
+
}
|
|
991
|
+
} else if (highlighted) {
|
|
992
|
+
theme?.optionHighlightedBackgroundColor ?: resolveColorAttr(
|
|
993
|
+
MaterialR.attr.colorSecondaryContainer,
|
|
994
|
+
MaterialR.attr.colorPrimaryContainer,
|
|
995
|
+
MaterialR.attr.colorSurfaceVariant,
|
|
996
|
+
android.R.attr.colorAccent
|
|
997
|
+
)
|
|
694
998
|
} else {
|
|
695
|
-
theme?.backgroundColor ?:
|
|
999
|
+
theme?.backgroundColor ?: resolveColorAttr(
|
|
1000
|
+
MaterialR.attr.colorSurfaceContainerLow,
|
|
1001
|
+
MaterialR.attr.colorSurfaceVariant,
|
|
1002
|
+
MaterialR.attr.colorSurface,
|
|
1003
|
+
android.R.attr.colorBackground
|
|
1004
|
+
)
|
|
696
1005
|
}
|
|
697
1006
|
backgroundDrawable?.setColor(backgroundColor)
|
|
698
1007
|
titleView.setTextColor(
|
|
699
|
-
if (highlighted) {
|
|
700
|
-
|
|
1008
|
+
if (toolbarAppearance == EditorToolbarAppearance.NATIVE && !highlighted) {
|
|
1009
|
+
resolveColorAttr(
|
|
1010
|
+
MaterialR.attr.colorOnSurface,
|
|
1011
|
+
android.R.attr.textColorPrimary
|
|
1012
|
+
)
|
|
1013
|
+
} else if (highlighted) {
|
|
1014
|
+
theme?.optionHighlightedTextColor
|
|
1015
|
+
?: theme?.optionTextColor
|
|
1016
|
+
?: resolveColorAttr(
|
|
1017
|
+
MaterialR.attr.colorOnSecondaryContainer,
|
|
1018
|
+
MaterialR.attr.colorOnPrimaryContainer,
|
|
1019
|
+
MaterialR.attr.colorOnSurface,
|
|
1020
|
+
android.R.attr.textColorPrimary
|
|
1021
|
+
)
|
|
701
1022
|
} else {
|
|
702
|
-
theme?.optionTextColor
|
|
1023
|
+
theme?.optionTextColor
|
|
1024
|
+
?: theme?.textColor
|
|
1025
|
+
?: resolveColorAttr(
|
|
1026
|
+
MaterialR.attr.colorOnSurface,
|
|
1027
|
+
android.R.attr.textColorPrimary
|
|
1028
|
+
)
|
|
1029
|
+
}
|
|
1030
|
+
)
|
|
1031
|
+
subtitleView.setTextColor(
|
|
1032
|
+
if (toolbarAppearance == EditorToolbarAppearance.NATIVE) {
|
|
1033
|
+
resolveColorAttr(
|
|
1034
|
+
MaterialR.attr.colorOnSurfaceVariant,
|
|
1035
|
+
android.R.attr.textColorSecondary
|
|
1036
|
+
)
|
|
1037
|
+
} else {
|
|
1038
|
+
theme?.optionSecondaryTextColor ?: resolveColorAttr(
|
|
1039
|
+
MaterialR.attr.colorOnSurfaceVariant,
|
|
1040
|
+
android.R.attr.textColorSecondary
|
|
1041
|
+
)
|
|
703
1042
|
}
|
|
704
1043
|
)
|
|
705
|
-
subtitleView.setTextColor(theme?.optionSecondaryTextColor ?: Color.DKGRAY)
|
|
706
1044
|
}
|
|
707
1045
|
|
|
1046
|
+
fun usesNativeAppearanceForTesting(): Boolean =
|
|
1047
|
+
toolbarAppearance == EditorToolbarAppearance.NATIVE
|
|
1048
|
+
|
|
708
1049
|
private fun dp(value: Int): Int = (value * density).toInt()
|
|
1050
|
+
|
|
1051
|
+
private fun resolveColorAttr(vararg attrs: Int): Int {
|
|
1052
|
+
val typedValue = TypedValue()
|
|
1053
|
+
for (attr in attrs) {
|
|
1054
|
+
if (!context.theme.resolveAttribute(attr, typedValue, true)) {
|
|
1055
|
+
continue
|
|
1056
|
+
}
|
|
1057
|
+
if (typedValue.resourceId != 0) {
|
|
1058
|
+
AppCompatResources.getColorStateList(context, typedValue.resourceId)
|
|
1059
|
+
?.defaultColor
|
|
1060
|
+
?.let { return it }
|
|
1061
|
+
} else if (typedValue.type in TypedValue.TYPE_FIRST_COLOR_INT..TypedValue.TYPE_LAST_COLOR_INT) {
|
|
1062
|
+
return typedValue.data
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return Color.TRANSPARENT
|
|
1066
|
+
}
|
|
709
1067
|
}
|
|
710
1068
|
|
|
711
1069
|
private fun JSONObject.optNullableString(key: String): String? {
|