expo-native-sheet-emojis 2.1.0 → 2.1.1
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/android/src/main/java/expo/community/modules/emojisheet/EmojiSheetContentView.kt +29 -1
- package/android/src/main/java/expo/community/modules/emojisheet/EmojiSheetModule.kt +18 -1
- package/android/src/main/java/expo/community/modules/emojisheet/EmojiSheetUIView.kt +140 -64
- package/ios/EmojiGridView.swift +20 -16
- package/ios/EmojiSheetContentView.swift +16 -1
- package/ios/EmojiSheetModule.podspec +1 -1
- package/ios/EmojiSheetUIView.swift +17 -17
- package/lib/commonjs/EmojiSheetView.js +1 -7
- package/lib/commonjs/EmojiSheetView.js.map +1 -1
- package/lib/module/EmojiSheetView.js +1 -7
- package/lib/module/EmojiSheetView.js.map +1 -1
- package/lib/typescript/EmojiSheetModule.types.d.ts +1 -1
- package/lib/typescript/EmojiSheetModule.types.d.ts.map +1 -1
- package/lib/typescript/EmojiSheetView.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EmojiSheetModule.types.ts +1 -1
- package/src/EmojiSheetView.tsx +1 -7
|
@@ -2,6 +2,8 @@ package expo.community.modules.emojisheet
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
+
import android.os.Handler
|
|
6
|
+
import android.os.Looper
|
|
5
7
|
import expo.modules.kotlin.AppContext
|
|
6
8
|
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
7
9
|
import expo.modules.kotlin.views.ExpoView
|
|
@@ -27,9 +29,30 @@ class EmojiSheetContentView(
|
|
|
27
29
|
pickerView.loadDataAsync()
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
private var hasOpened = false
|
|
33
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
34
|
+
// A transient detach (layout reparenting, brief clipping) is followed by a
|
|
35
|
+
// reattach on the same or next loop. Defer the dismiss one loop and skip it if
|
|
36
|
+
// the view came back, so onDismiss fires only when the view stays detached.
|
|
37
|
+
private val emitDismissIfStillDetached = Runnable {
|
|
38
|
+
if (!isAttachedToWindow) {
|
|
39
|
+
hasOpened = false
|
|
40
|
+
onDismiss(mapOf<String, Any>())
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
override fun onAttachedToWindow() {
|
|
31
45
|
super.onAttachedToWindow()
|
|
32
|
-
|
|
46
|
+
mainHandler.removeCallbacks(emitDismissIfStillDetached)
|
|
47
|
+
if (!hasOpened) {
|
|
48
|
+
hasOpened = true
|
|
49
|
+
onOpen(mapOf<String, Any>())
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun onDetachedFromWindow() {
|
|
54
|
+
super.onDetachedFromWindow()
|
|
55
|
+
mainHandler.post(emitDismissIfStillDetached)
|
|
33
56
|
}
|
|
34
57
|
|
|
35
58
|
fun applyConfiguration() {
|
|
@@ -61,14 +84,17 @@ class EmojiSheetContentView(
|
|
|
61
84
|
|
|
62
85
|
fun updateRecentLimit(limit: Int) {
|
|
63
86
|
pickerView.recentLimit = limit
|
|
87
|
+
pickerView.rebuildAfterPropChange()
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
fun updateShowSearch(show: Boolean) {
|
|
67
91
|
pickerView.showSearch = show
|
|
92
|
+
pickerView.applyConfiguration()
|
|
68
93
|
}
|
|
69
94
|
|
|
70
95
|
fun updateShowRecents(show: Boolean) {
|
|
71
96
|
pickerView.showRecents = show
|
|
97
|
+
pickerView.rebuildAfterPropChange()
|
|
72
98
|
}
|
|
73
99
|
|
|
74
100
|
fun updateEnableSkinTones(enable: Boolean) {
|
|
@@ -93,9 +119,11 @@ class EmojiSheetContentView(
|
|
|
93
119
|
|
|
94
120
|
fun updateCategoryNames(names: Map<String, String>) {
|
|
95
121
|
pickerView.categoryNames = names
|
|
122
|
+
pickerView.rebuildAfterPropChange()
|
|
96
123
|
}
|
|
97
124
|
|
|
98
125
|
fun updateExcludeEmojis(ids: List<String>) {
|
|
99
126
|
pickerView.excludeEmojis = ids.toSet()
|
|
127
|
+
pickerView.rebuildAfterPropChange()
|
|
100
128
|
}
|
|
101
129
|
}
|
|
@@ -127,6 +127,12 @@ class EmojiSheetModule : Module() {
|
|
|
127
127
|
|
|
128
128
|
private fun presentSheet(options: Map<String, Any>, promise: Promise) {
|
|
129
129
|
val activity = appContext.currentActivity ?: return
|
|
130
|
+
// Single-flight guard (mirrors iOS): ignore a second present() while a sheet
|
|
131
|
+
// is already open so the first promise is never orphaned and dialogs cannot stack.
|
|
132
|
+
if (currentPromise != null || dialog != null) {
|
|
133
|
+
promise.resolve(Bundle().apply { putBoolean("cancelled", true) })
|
|
134
|
+
return
|
|
135
|
+
}
|
|
130
136
|
val themeString = options["theme"] as? String ?: "light"
|
|
131
137
|
val isDark = when (themeString) {
|
|
132
138
|
"dark" -> true
|
|
@@ -560,8 +566,19 @@ class EmojiSheetModule : Module() {
|
|
|
560
566
|
|
|
561
567
|
private fun parseColor(hex: String?): Int? {
|
|
562
568
|
if (hex == null) return null
|
|
569
|
+
val trimmed = hex.trim()
|
|
570
|
+
// 8-digit hex is #RRGGBBAA (CSS / iOS order). Android's Color.parseColor reads
|
|
571
|
+
// 8-digit as #AARRGGBB, so parse it manually to keep both platforms in sync.
|
|
572
|
+
if (trimmed.startsWith("#") && trimmed.length == 9) {
|
|
573
|
+
val value = trimmed.substring(1).toLongOrNull(16) ?: return null
|
|
574
|
+
val r = ((value shr 24) and 0xFF).toInt()
|
|
575
|
+
val g = ((value shr 16) and 0xFF).toInt()
|
|
576
|
+
val b = ((value shr 8) and 0xFF).toInt()
|
|
577
|
+
val a = (value and 0xFF).toInt()
|
|
578
|
+
return Color.argb(a, r, g, b)
|
|
579
|
+
}
|
|
563
580
|
return try {
|
|
564
|
-
Color.parseColor(
|
|
581
|
+
Color.parseColor(trimmed)
|
|
565
582
|
} catch (e: IllegalArgumentException) {
|
|
566
583
|
null
|
|
567
584
|
}
|
|
@@ -21,6 +21,7 @@ import kotlinx.coroutines.SupervisorJob
|
|
|
21
21
|
import kotlinx.coroutines.cancel
|
|
22
22
|
import kotlinx.coroutines.currentCoroutineContext
|
|
23
23
|
import kotlinx.coroutines.ensureActive
|
|
24
|
+
import kotlinx.coroutines.isActive
|
|
24
25
|
import kotlinx.coroutines.launch
|
|
25
26
|
import kotlinx.coroutines.sync.Mutex
|
|
26
27
|
import kotlinx.coroutines.sync.withLock
|
|
@@ -39,8 +40,11 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
39
40
|
private var cachedData: Pair<List<EmojiCategory>, Map<String, List<String>>>? = null
|
|
40
41
|
|
|
41
42
|
fun warmCache(context: Context) {
|
|
42
|
-
if (cachedData != null) return
|
|
43
43
|
val appContext = context.applicationContext
|
|
44
|
+
cacheScope.launch {
|
|
45
|
+
appContext.getSharedPreferences(FREQ_PREFS, Context.MODE_PRIVATE).all
|
|
46
|
+
}
|
|
47
|
+
if (cachedData != null) return
|
|
44
48
|
cacheScope.launch {
|
|
45
49
|
loadCachedData(appContext)
|
|
46
50
|
}
|
|
@@ -57,8 +61,11 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
57
61
|
val keywords = loadAllKeywords(context)
|
|
58
62
|
Pair(categories, keywords).also { cachedData = it }
|
|
59
63
|
} catch (e: Exception) {
|
|
64
|
+
// Degrade to an empty data set instead of throwing an uncaught
|
|
65
|
+
// coroutine exception (which would crash the app). Not cached, so a
|
|
66
|
+
// later attempt can still succeed. Mirrors iOS, which returns [].
|
|
60
67
|
android.util.Log.e("EmojiSheet", "Failed to load emoji data", e)
|
|
61
|
-
|
|
68
|
+
Pair(emptyList(), emptyMap())
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
}
|
|
@@ -98,6 +105,13 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
98
105
|
var columns: Int = 7
|
|
99
106
|
var emojiSize: Float = 32f
|
|
100
107
|
var showSearch: Boolean = true
|
|
108
|
+
set(value) {
|
|
109
|
+
field = value
|
|
110
|
+
if (!value && currentSearchQuery.isNotBlank()) {
|
|
111
|
+
searchBar.clearSearch()
|
|
112
|
+
onSearch("")
|
|
113
|
+
}
|
|
114
|
+
}
|
|
101
115
|
var showRecents: Boolean = true
|
|
102
116
|
var enableSkinTones: Boolean = true
|
|
103
117
|
var enableHaptics: Boolean = true
|
|
@@ -130,6 +144,7 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
130
144
|
|
|
131
145
|
private var isSearchActive = false
|
|
132
146
|
private var bottomPillContainer: View? = null
|
|
147
|
+
private var bottomBarWrapper: FrameLayout? = null
|
|
133
148
|
private var suppressCategorySync = false
|
|
134
149
|
private var currentSearchQuery = ""
|
|
135
150
|
private var didTriggerExpandForCurrentDrag = false
|
|
@@ -146,7 +161,7 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
146
161
|
private var baseRecyclerBottomPadding = 0
|
|
147
162
|
private var keyboardRecyclerBottomPadding = 0
|
|
148
163
|
private var usesDockedSheetKeyboardInsets = false
|
|
149
|
-
private
|
|
164
|
+
private var viewScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
|
150
165
|
private var loadJob: Job? = null
|
|
151
166
|
private var searchJob: Job? = null
|
|
152
167
|
|
|
@@ -212,9 +227,12 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
212
227
|
lastTopPullDragDistance = 0f
|
|
213
228
|
isHandlingTopPullDrag = false
|
|
214
229
|
topPullStartY = null
|
|
230
|
+
// While the sheet is expanding, halt any momentum scroll but do
|
|
231
|
+
// NOT swallow the gesture: intercepting DOWN/UP would cancel an
|
|
232
|
+
// emoji tap. Only a drag (ACTION_MOVE below) is intercepted, so a
|
|
233
|
+
// plain tap still reaches the cell even mid-expansion.
|
|
215
234
|
if (isSheetExpansionInProgress) {
|
|
216
235
|
rv.stopScroll()
|
|
217
|
-
return true
|
|
218
236
|
}
|
|
219
237
|
}
|
|
220
238
|
android.view.MotionEvent.ACTION_UP,
|
|
@@ -232,15 +250,22 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
232
250
|
velocityTracker = null
|
|
233
251
|
if (isSheetExpansionInProgress) {
|
|
234
252
|
rv.stopScroll()
|
|
235
|
-
return true
|
|
236
253
|
}
|
|
237
254
|
}
|
|
238
255
|
}
|
|
239
256
|
|
|
240
257
|
if (e.actionMasked == android.view.MotionEvent.ACTION_MOVE) {
|
|
241
258
|
if (isSheetExpansionInProgress) {
|
|
242
|
-
|
|
243
|
-
|
|
259
|
+
// Only steal the gesture once it becomes a real drag (past touch
|
|
260
|
+
// slop). A finger tap jitters a pixel or two; intercepting that
|
|
261
|
+
// here cancels the emoji tap whenever this flag is set while the
|
|
262
|
+
// keyboard is up — which is exactly the "can't tap while searching"
|
|
263
|
+
// bug. Sub-slop movement falls through so the cell click fires.
|
|
264
|
+
if (kotlin.math.abs(e.y - initialTouchY) > touchSlop) {
|
|
265
|
+
rv.stopScroll()
|
|
266
|
+
return true
|
|
267
|
+
}
|
|
268
|
+
return false
|
|
244
269
|
}
|
|
245
270
|
|
|
246
271
|
val deltaY = e.y - initialTouchY
|
|
@@ -390,9 +415,13 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
390
415
|
updateRecyclerBottomPadding()
|
|
391
416
|
}
|
|
392
417
|
|
|
393
|
-
/**
|
|
418
|
+
/**
|
|
419
|
+
* Call after setting configurable properties to apply grid + layout changes.
|
|
420
|
+
* Safe to call repeatedly: grid settings are always refreshed, and the
|
|
421
|
+
* category-bar hierarchy is only moved when the target position actually changes.
|
|
422
|
+
*/
|
|
394
423
|
fun applyConfiguration() {
|
|
395
|
-
// Update grid adapter settings
|
|
424
|
+
// Update grid adapter settings (idempotent — safe on every call)
|
|
396
425
|
gridAdapter.spanCount = columns
|
|
397
426
|
gridAdapter.emojiTextSize = emojiSize
|
|
398
427
|
gridAdapter.enableSkinTones = enableSkinTones
|
|
@@ -403,72 +432,105 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
403
432
|
// Show/hide search
|
|
404
433
|
searchBar.visibility = if (showSearch) View.VISIBLE else View.GONE
|
|
405
434
|
|
|
406
|
-
|
|
435
|
+
applyCategoryBarPosition()
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private fun applyCategoryBarPosition() {
|
|
407
439
|
if (categoryBarPosition == "bottom") {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
440
|
+
if (bottomBarWrapper != null) return // already wrapped; do not re-move the hierarchy
|
|
441
|
+
moveCategoryBarToBottom()
|
|
442
|
+
} else {
|
|
443
|
+
if (bottomBarWrapper == null) {
|
|
444
|
+
// Already in the default top layout.
|
|
445
|
+
baseRecyclerBottomPadding = 0
|
|
446
|
+
updateRecyclerBottomPadding()
|
|
447
|
+
return
|
|
412
448
|
}
|
|
413
|
-
|
|
414
|
-
|
|
449
|
+
restoreCategoryBarToTop()
|
|
450
|
+
}
|
|
451
|
+
}
|
|
415
452
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
val cornerRadius = 22 * density
|
|
453
|
+
private fun moveCategoryBarToBottom() {
|
|
454
|
+
// Detach the strip and content frame from their current parents (whatever they are).
|
|
455
|
+
(categoryStrip.parent as? android.view.ViewGroup)?.removeView(categoryStrip)
|
|
456
|
+
(contentFrame.parent as? android.view.ViewGroup)?.removeView(contentFrame)
|
|
421
457
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
428
|
-
FrameLayout.LayoutParams.MATCH_PARENT
|
|
429
|
-
)
|
|
430
|
-
wrapperFrame.addView(contentFrame)
|
|
458
|
+
val density = context.resources.displayMetrics.density
|
|
459
|
+
val horizontalInset = (16 * density).toInt()
|
|
460
|
+
val bottomInset = (8 * density).toInt()
|
|
461
|
+
val stripHeight = (44 * density).toInt()
|
|
462
|
+
val cornerRadius = 22 * density
|
|
431
463
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
this.cornerRadius = cornerRadius
|
|
436
|
-
}
|
|
464
|
+
val wrapperFrame = FrameLayout(context).apply {
|
|
465
|
+
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f)
|
|
466
|
+
}
|
|
437
467
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
468
|
+
contentFrame.layoutParams = FrameLayout.LayoutParams(
|
|
469
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
470
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
471
|
+
)
|
|
472
|
+
wrapperFrame.addView(contentFrame)
|
|
444
473
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
pillContainer.addView(categoryStrip)
|
|
474
|
+
// Floating pill container with rounded background
|
|
475
|
+
val pillBackground = android.graphics.drawable.GradientDrawable().apply {
|
|
476
|
+
setColor(currentTheme.categoryBarBackgroundColor)
|
|
477
|
+
this.cornerRadius = cornerRadius
|
|
478
|
+
}
|
|
451
479
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
bottomPillContainer = pillContainer
|
|
460
|
-
wrapperFrame.addView(pillContainer)
|
|
480
|
+
val pillContainer = FrameLayout(context).apply {
|
|
481
|
+
background = pillBackground
|
|
482
|
+
elevation = 8 * density
|
|
483
|
+
clipToOutline = true
|
|
484
|
+
outlineProvider = android.view.ViewOutlineProvider.BACKGROUND
|
|
485
|
+
}
|
|
461
486
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
487
|
+
categoryStrip.setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
488
|
+
categoryStrip.layoutParams = FrameLayout.LayoutParams(
|
|
489
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
490
|
+
stripHeight
|
|
491
|
+
)
|
|
492
|
+
pillContainer.addView(categoryStrip)
|
|
466
493
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
494
|
+
pillContainer.layoutParams = FrameLayout.LayoutParams(
|
|
495
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
496
|
+
stripHeight
|
|
497
|
+
).apply {
|
|
498
|
+
gravity = Gravity.BOTTOM
|
|
499
|
+
setMargins(horizontalInset, 0, horizontalInset, bottomInset)
|
|
471
500
|
}
|
|
501
|
+
bottomPillContainer = pillContainer
|
|
502
|
+
wrapperFrame.addView(pillContainer)
|
|
503
|
+
|
|
504
|
+
// Bottom padding so grid content scrolls above the floating bar
|
|
505
|
+
val totalBarSpace = stripHeight + bottomInset * 2
|
|
506
|
+
baseRecyclerBottomPadding = totalBarSpace
|
|
507
|
+
updateRecyclerBottomPadding()
|
|
508
|
+
|
|
509
|
+
bottomBarWrapper = wrapperFrame
|
|
510
|
+
addView(wrapperFrame)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private fun restoreCategoryBarToTop() {
|
|
514
|
+
val wrapper = bottomBarWrapper ?: return
|
|
515
|
+
// Detach views from the wrapper, then drop the wrapper itself.
|
|
516
|
+
(categoryStrip.parent as? android.view.ViewGroup)?.removeView(categoryStrip)
|
|
517
|
+
(contentFrame.parent as? android.view.ViewGroup)?.removeView(contentFrame)
|
|
518
|
+
removeView(wrapper)
|
|
519
|
+
bottomBarWrapper = null
|
|
520
|
+
bottomPillContainer = null
|
|
521
|
+
|
|
522
|
+
// Restore the default top order: searchBar, categoryStrip, contentFrame.
|
|
523
|
+
categoryStrip.setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
524
|
+
categoryStrip.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
525
|
+
addView(categoryStrip, indexOfChild(searchBar) + 1)
|
|
526
|
+
contentFrame.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f)
|
|
527
|
+
addView(contentFrame)
|
|
528
|
+
|
|
529
|
+
categoryStrip.applyTheme(currentTheme)
|
|
530
|
+
applyLayoutDirection()
|
|
531
|
+
|
|
532
|
+
baseRecyclerBottomPadding = 0
|
|
533
|
+
updateRecyclerBottomPadding()
|
|
472
534
|
}
|
|
473
535
|
|
|
474
536
|
private fun setGridItems(items: List<EmojiGridAdapter.ListItem>, sectionPositions: List<Int>) {
|
|
@@ -509,6 +571,13 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
509
571
|
}
|
|
510
572
|
}
|
|
511
573
|
|
|
574
|
+
fun rebuildAfterPropChange() {
|
|
575
|
+
if (allCategories.isEmpty()) return
|
|
576
|
+
allCategoryKeys = buildCategoryKeys()
|
|
577
|
+
rebuildCategoryStrip()
|
|
578
|
+
if (currentSearchQuery.isBlank()) buildAndSetItems() else onSearch(currentSearchQuery)
|
|
579
|
+
}
|
|
580
|
+
|
|
512
581
|
fun updateTheme(theme: String) {
|
|
513
582
|
currentTheme = EmojiSheetTheme.fromName(theme)
|
|
514
583
|
applyTheme(currentTheme)
|
|
@@ -906,6 +975,13 @@ class EmojiSheetUIView(context: Context) : LinearLayout(context) {
|
|
|
906
975
|
|
|
907
976
|
override fun onAttachedToWindow() {
|
|
908
977
|
super.onAttachedToWindow()
|
|
978
|
+
if (!viewScope.isActive) {
|
|
979
|
+
viewScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
|
980
|
+
// Only reload when data hasn't loaded yet; otherwise the populated grid
|
|
981
|
+
// and category strip survive the detach, so re-running loadDataAsync would
|
|
982
|
+
// rebuild the strip and reset scroll to 0 on every reattach.
|
|
983
|
+
if (allCategories.isEmpty()) loadDataAsync()
|
|
984
|
+
}
|
|
909
985
|
ViewCompat.requestApplyInsets(this)
|
|
910
986
|
}
|
|
911
987
|
}
|
package/ios/EmojiGridView.swift
CHANGED
|
@@ -149,15 +149,26 @@ class EmojiGridView: UIView, UICollectionViewDataSource, UICollectionViewDelegat
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
func scrollToSection(_ index: Int) {
|
|
152
|
-
guard index < sections.count else { return }
|
|
152
|
+
guard index < sections.count, !sections[index].data.isEmpty else { return }
|
|
153
153
|
isScrollingProgrammatically = true
|
|
154
154
|
let indexPath = IndexPath(item: 0, section: index)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
) {
|
|
159
|
-
let
|
|
160
|
-
|
|
155
|
+
// The header pins to the visible bounds, so its queried origin.y can be the
|
|
156
|
+
// sticky position rather than the section's natural top. Items never pin, so
|
|
157
|
+
// derive the section top from the first item and the (stable) header height.
|
|
158
|
+
if let itemAttributes = collectionView.layoutAttributesForItem(at: indexPath) {
|
|
159
|
+
let headerHeight = collectionView.layoutAttributesForSupplementaryElement(
|
|
160
|
+
ofKind: UICollectionView.elementKindSectionHeader,
|
|
161
|
+
at: indexPath
|
|
162
|
+
)?.frame.height ?? 32
|
|
163
|
+
let sectionTopInset: CGFloat = 4
|
|
164
|
+
let sectionTop = itemAttributes.frame.origin.y - headerHeight - sectionTopInset
|
|
165
|
+
let minOffset = -collectionView.adjustedContentInset.top
|
|
166
|
+
let maxOffset = max(
|
|
167
|
+
minOffset,
|
|
168
|
+
collectionView.contentSize.height - collectionView.bounds.height + collectionView.adjustedContentInset.bottom
|
|
169
|
+
)
|
|
170
|
+
let target = min(max(sectionTop - collectionView.adjustedContentInset.top, minOffset), maxOffset)
|
|
171
|
+
collectionView.setContentOffset(CGPoint(x: 0, y: target), animated: true)
|
|
161
172
|
}
|
|
162
173
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in
|
|
163
174
|
self?.isScrollingProgrammatically = false
|
|
@@ -178,15 +189,8 @@ class EmojiGridView: UIView, UICollectionViewDataSource, UICollectionViewDelegat
|
|
|
178
189
|
|
|
179
190
|
static func applyingSkinTone(_ modifier: String, to emoji: String) -> String {
|
|
180
191
|
var scalars = Array(emoji.unicodeScalars)
|
|
181
|
-
if scalars.count >= 1 {
|
|
182
|
-
|
|
183
|
-
// Insert skin tone modifier after the first scalar
|
|
184
|
-
if scalars.count > 1 && scalars[1] == "\u{200D}" {
|
|
185
|
-
// ZWJ sequence: insert before ZWJ
|
|
186
|
-
scalars.insert(modifierScalar, at: 1)
|
|
187
|
-
} else {
|
|
188
|
-
scalars.insert(modifierScalar, at: 1)
|
|
189
|
-
}
|
|
192
|
+
if scalars.count >= 1, let modifierScalar = modifier.unicodeScalars.first {
|
|
193
|
+
scalars.insert(modifierScalar, at: 1)
|
|
190
194
|
}
|
|
191
195
|
return String(String.UnicodeScalarView(scalars))
|
|
192
196
|
}
|
|
@@ -27,6 +27,7 @@ class EmojiSheetContentView: ExpoView, EmojiSheetUIViewDelegate {
|
|
|
27
27
|
let onDismiss = EventDispatcher()
|
|
28
28
|
let onOpen = EventDispatcher()
|
|
29
29
|
private let pickerView = EmojiSheetUIView()
|
|
30
|
+
private var hasOpened = false
|
|
30
31
|
|
|
31
32
|
required init(appContext: AppContext? = nil) {
|
|
32
33
|
super.init(appContext: appContext)
|
|
@@ -49,7 +50,21 @@ class EmojiSheetContentView: ExpoView, EmojiSheetUIViewDelegate {
|
|
|
49
50
|
override func didMoveToWindow() {
|
|
50
51
|
super.didMoveToWindow()
|
|
51
52
|
if window != nil {
|
|
52
|
-
|
|
53
|
+
if !hasOpened {
|
|
54
|
+
hasOpened = true
|
|
55
|
+
onOpen([:])
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
// A transient detach (reparenting, brief clipping) reattaches on the next
|
|
59
|
+
// runloop. Defer the dismiss and skip it if the view came back, so onDismiss
|
|
60
|
+
// fires only when the view stays detached (e.g. real unmount).
|
|
61
|
+
DispatchQueue.main.async { [weak self, dismiss = onDismiss] in
|
|
62
|
+
if let self {
|
|
63
|
+
if self.window != nil { return }
|
|
64
|
+
self.hasOpened = false
|
|
65
|
+
}
|
|
66
|
+
dismiss([:])
|
|
67
|
+
}
|
|
53
68
|
}
|
|
54
69
|
}
|
|
55
70
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Pod::Spec.new do |s|
|
|
2
2
|
s.name = 'EmojiSheetModule'
|
|
3
|
-
s.version = '2.1.
|
|
3
|
+
s.version = '2.1.1'
|
|
4
4
|
s.summary = 'Native emoji picker bottom sheet for React Native'
|
|
5
5
|
s.description = 'A fully native iOS/Android emoji picker presented in a bottom sheet with search, skin tones, and theming support.'
|
|
6
6
|
s.author = ''
|
|
@@ -118,7 +118,9 @@ class EmojiSheetUIView: UIView,
|
|
|
118
118
|
var showSearch: Bool = true {
|
|
119
119
|
didSet { if showSearch != oldValue { configureLayout() } }
|
|
120
120
|
}
|
|
121
|
-
var showRecents: Bool = true
|
|
121
|
+
var showRecents: Bool = true {
|
|
122
|
+
didSet { if showRecents != oldValue { rebuildLoadedSections() } }
|
|
123
|
+
}
|
|
122
124
|
var enableSkinTones: Bool = true {
|
|
123
125
|
didSet { gridView.enableSkinTones = enableSkinTones }
|
|
124
126
|
}
|
|
@@ -128,15 +130,21 @@ class EmojiSheetUIView: UIView,
|
|
|
128
130
|
var enableAnimations: Bool = false {
|
|
129
131
|
didSet { gridView.enableAnimations = enableAnimations }
|
|
130
132
|
}
|
|
131
|
-
var recentLimit: Int = 30
|
|
133
|
+
var recentLimit: Int = 30 {
|
|
134
|
+
didSet { if recentLimit != oldValue { rebuildLoadedSections() } }
|
|
135
|
+
}
|
|
132
136
|
var categoryBarPosition: String = "top" {
|
|
133
137
|
didSet { if categoryBarPosition != oldValue { configureLayout() } }
|
|
134
138
|
}
|
|
135
139
|
var layoutDirection: String = "auto" {
|
|
136
140
|
didSet { applyLayoutDirection() }
|
|
137
141
|
}
|
|
138
|
-
var categoryNames: [String: String]?
|
|
139
|
-
|
|
142
|
+
var categoryNames: [String: String]? {
|
|
143
|
+
didSet { if categoryNames != oldValue { rebuildLoadedSections() } }
|
|
144
|
+
}
|
|
145
|
+
var excludeEmojis: Set<String> = [] {
|
|
146
|
+
didSet { if excludeEmojis != oldValue { rebuildLoadedSections() } }
|
|
147
|
+
}
|
|
140
148
|
|
|
141
149
|
private var currentTheme: EmojiSheetTheme = .light
|
|
142
150
|
private var allSections: [EmojiSection] = []
|
|
@@ -544,6 +552,11 @@ class EmojiSheetUIView: UIView,
|
|
|
544
552
|
}
|
|
545
553
|
}
|
|
546
554
|
|
|
555
|
+
private func rebuildLoadedSections() {
|
|
556
|
+
guard !allSections.isEmpty else { return }
|
|
557
|
+
rebuildSections(searchText: currentSearchText)
|
|
558
|
+
}
|
|
559
|
+
|
|
547
560
|
// iOS version → max Unicode emoji version. Source: https://emojipedia.org/apple
|
|
548
561
|
// Cases MUST remain ordered most-specific first (Swift evaluates top-to-bottom).
|
|
549
562
|
nonisolated private static func maxSupportedEmojiVersion() -> Double {
|
|
@@ -755,19 +768,6 @@ class EmojiSheetUIView: UIView,
|
|
|
755
768
|
return 0
|
|
756
769
|
}
|
|
757
770
|
|
|
758
|
-
/// Fast path: checks pre-normalized text first, then falls back to transliteration-aware variants.
|
|
759
|
-
private func matchesNormalized(_ normalizedText: String, originalText: String, searchVariants: [String]) -> Bool {
|
|
760
|
-
if searchVariants.contains(where: normalizedText.contains) {
|
|
761
|
-
return true
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
return Self.normalizedSearchVariants(originalText)
|
|
765
|
-
.contains { candidateVariant in
|
|
766
|
-
candidateVariant != normalizedText &&
|
|
767
|
-
searchVariants.contains(where: { candidateVariant.contains($0) })
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
771
|
// MARK: - Frequently Used
|
|
772
772
|
|
|
773
773
|
private func loadFrequentlyUsed() -> [EmojiItem] {
|
|
@@ -26,15 +26,9 @@ function EmojiSheetView({
|
|
|
26
26
|
excludeEmojis,
|
|
27
27
|
...rest
|
|
28
28
|
}) {
|
|
29
|
-
const resolvedTheme = typeof theme === 'object' ? 'custom' : theme;
|
|
30
|
-
|
|
31
|
-
// For custom themes, we pass individual color props through the native view
|
|
32
|
-
// For now, the declarative view supports 'dark' | 'light' string themes
|
|
33
|
-
// Custom theme object support would require additional native view props
|
|
34
|
-
|
|
35
29
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(NativeView, {
|
|
36
30
|
...rest,
|
|
37
|
-
theme:
|
|
31
|
+
theme: typeof theme === 'string' ? theme : undefined,
|
|
38
32
|
layoutDirection: layoutDirection,
|
|
39
33
|
categoryBarPosition: categoryBarPosition,
|
|
40
34
|
columns: columns,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_expo","require","_jsxRuntime","NativeView","requireNativeView","EmojiSheetView","onEmojiSelected","onDismiss","onOpen","theme","translations","layoutDirection","categoryBarPosition","columns","emojiSize","recentLimit","showSearch","showRecents","enableSkinTones","enableHaptics","enableAnimations","excludeEmojis","rest","
|
|
1
|
+
{"version":3,"names":["_expo","require","_jsxRuntime","NativeView","requireNativeView","EmojiSheetView","onEmojiSelected","onDismiss","onOpen","theme","translations","layoutDirection","categoryBarPosition","columns","emojiSize","recentLimit","showSearch","showRecents","enableSkinTones","enableHaptics","enableAnimations","excludeEmojis","rest","jsx","undefined","searchPlaceholder","noResultsText","categoryNames","nativeEvent","emoji","name","id"],"sourceRoot":"../../src","sources":["EmojiSheetView.tsx"],"mappings":";;;;;;AAAA,IAAAA,KAAA,GAAAC,OAAA;AAAyC,IAAAC,WAAA,GAAAD,OAAA;AAOzC,MAAME,UAA0D,GAC9D,IAAAC,uBAAiB,EAAC,YAAY,CAAC;AAElB,SAASC,cAAcA,CAAC;EACrCC,eAAe;EACfC,SAAS;EACTC,MAAM;EACNC,KAAK;EACLC,YAAY;EACZC,eAAe;EACfC,mBAAmB;EACnBC,OAAO;EACPC,SAAS;EACTC,WAAW;EACXC,UAAU;EACVC,WAAW;EACXC,eAAe;EACfC,aAAa;EACbC,gBAAgB;EAChBC,aAAa;EACb,GAAGC;AACgB,CAAC,EAAE;EACtB,oBACE,IAAApB,WAAA,CAAAqB,GAAA,EAACpB,UAAU;IAAA,GACLmB,IAAI;IACRb,KAAK,EAAE,OAAOA,KAAK,KAAK,QAAQ,GAAGA,KAAK,GAAGe,SAAU;IACrDb,eAAe,EAAEA,eAAgB;IACjCC,mBAAmB,EAAEA,mBAAoB;IACzCC,OAAO,EAAEA,OAAQ;IACjBC,SAAS,EAAEA,SAAU;IACrBC,WAAW,EAAEA,WAAY;IACzBC,UAAU,EAAEA,UAAW;IACvBC,WAAW,EAAEA,WAAY;IACzBC,eAAe,EAAEA,eAAgB;IACjCC,aAAa,EAAEA,aAAc;IAC7BC,gBAAgB,EAAEA,gBAAiB;IACnCC,aAAa,EAAEA,aAAc;IAC7BI,iBAAiB,EAAEf,YAAY,EAAEe,iBAAkB;IACnDC,aAAa,EAAEhB,YAAY,EAAEgB,aAAc;IAC3CC,aAAa,EAAEjB,YAAY,EAAEiB,aAAc;IAC3CrB,eAAe,EAAEA,CAAC;MAAEsB;IAAY,CAAC,KAAK;MACpCtB,eAAe,CAACsB,WAAW,CAACC,KAAK,EAAED,WAAW,CAACE,IAAI,EAAEF,WAAW,CAACG,EAAE,CAAC;IACtE,CAAE;IACFxB,SAAS,EAAEA,SAAS,GAAG,MAAMA,SAAS,CAAC,CAAC,GAAGiB,SAAU;IACrDhB,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGgB;EAAU,CAC7C,CAAC;AAEN","ignoreList":[]}
|
|
@@ -22,15 +22,9 @@ export default function EmojiSheetView({
|
|
|
22
22
|
excludeEmojis,
|
|
23
23
|
...rest
|
|
24
24
|
}) {
|
|
25
|
-
const resolvedTheme = typeof theme === 'object' ? 'custom' : theme;
|
|
26
|
-
|
|
27
|
-
// For custom themes, we pass individual color props through the native view
|
|
28
|
-
// For now, the declarative view supports 'dark' | 'light' string themes
|
|
29
|
-
// Custom theme object support would require additional native view props
|
|
30
|
-
|
|
31
25
|
return /*#__PURE__*/_jsx(NativeView, {
|
|
32
26
|
...rest,
|
|
33
|
-
theme:
|
|
27
|
+
theme: typeof theme === 'string' ? theme : undefined,
|
|
34
28
|
layoutDirection: layoutDirection,
|
|
35
29
|
categoryBarPosition: categoryBarPosition,
|
|
36
30
|
columns: columns,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["requireNativeView","jsx","_jsx","NativeView","EmojiSheetView","onEmojiSelected","onDismiss","onOpen","theme","translations","layoutDirection","categoryBarPosition","columns","emojiSize","recentLimit","showSearch","showRecents","enableSkinTones","enableHaptics","enableAnimations","excludeEmojis","rest","
|
|
1
|
+
{"version":3,"names":["requireNativeView","jsx","_jsx","NativeView","EmojiSheetView","onEmojiSelected","onDismiss","onOpen","theme","translations","layoutDirection","categoryBarPosition","columns","emojiSize","recentLimit","showSearch","showRecents","enableSkinTones","enableHaptics","enableAnimations","excludeEmojis","rest","undefined","searchPlaceholder","noResultsText","categoryNames","nativeEvent","emoji","name","id"],"sourceRoot":"../../src","sources":["EmojiSheetView.tsx"],"mappings":";;AAAA,SAASA,iBAAiB,QAAQ,MAAM;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAOzC,MAAMC,UAA0D,GAC9DH,iBAAiB,CAAC,YAAY,CAAC;AAEjC,eAAe,SAASI,cAAcA,CAAC;EACrCC,eAAe;EACfC,SAAS;EACTC,MAAM;EACNC,KAAK;EACLC,YAAY;EACZC,eAAe;EACfC,mBAAmB;EACnBC,OAAO;EACPC,SAAS;EACTC,WAAW;EACXC,UAAU;EACVC,WAAW;EACXC,eAAe;EACfC,aAAa;EACbC,gBAAgB;EAChBC,aAAa;EACb,GAAGC;AACgB,CAAC,EAAE;EACtB,oBACEnB,IAAA,CAACC,UAAU;IAAA,GACLkB,IAAI;IACRb,KAAK,EAAE,OAAOA,KAAK,KAAK,QAAQ,GAAGA,KAAK,GAAGc,SAAU;IACrDZ,eAAe,EAAEA,eAAgB;IACjCC,mBAAmB,EAAEA,mBAAoB;IACzCC,OAAO,EAAEA,OAAQ;IACjBC,SAAS,EAAEA,SAAU;IACrBC,WAAW,EAAEA,WAAY;IACzBC,UAAU,EAAEA,UAAW;IACvBC,WAAW,EAAEA,WAAY;IACzBC,eAAe,EAAEA,eAAgB;IACjCC,aAAa,EAAEA,aAAc;IAC7BC,gBAAgB,EAAEA,gBAAiB;IACnCC,aAAa,EAAEA,aAAc;IAC7BG,iBAAiB,EAAEd,YAAY,EAAEc,iBAAkB;IACnDC,aAAa,EAAEf,YAAY,EAAEe,aAAc;IAC3CC,aAAa,EAAEhB,YAAY,EAAEgB,aAAc;IAC3CpB,eAAe,EAAEA,CAAC;MAAEqB;IAAY,CAAC,KAAK;MACpCrB,eAAe,CAACqB,WAAW,CAACC,KAAK,EAAED,WAAW,CAACE,IAAI,EAAEF,WAAW,CAACG,EAAE,CAAC;IACtE,CAAE;IACFvB,SAAS,EAAEA,SAAS,GAAG,MAAMA,SAAS,CAAC,CAAC,GAAGgB,SAAU;IACrDf,MAAM,EAAEA,MAAM,GAAG,MAAMA,MAAM,CAAC,CAAC,GAAGe;EAAU,CAC7C,CAAC;AAEN","ignoreList":[]}
|
|
@@ -62,7 +62,7 @@ export type EmojiSheetViewProps = ViewProps & {
|
|
|
62
62
|
onEmojiSelected: (emoji: string, name: string, id: string) => void;
|
|
63
63
|
onDismiss?: () => void;
|
|
64
64
|
onOpen?: () => void;
|
|
65
|
-
theme?:
|
|
65
|
+
theme?: 'dark' | 'light' | 'system';
|
|
66
66
|
translations?: EmojiSheetTranslations;
|
|
67
67
|
layoutDirection?: 'ltr' | 'rtl' | 'auto';
|
|
68
68
|
categoryBarPosition?: 'top' | 'bottom';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmojiSheetModule.types.d.ts","sourceRoot":"","sources":["../../src/EmojiSheetModule.types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"EmojiSheetModule.types.d.ts","sourceRoot":"","sources":["../../src/EmojiSheetModule.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmojiSheetView.d.ts","sourceRoot":"","sources":["../../src/EmojiSheetView.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAKlC,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,eAAe,EACf,SAAS,EACT,MAAM,EACN,KAAK,EACL,YAAY,EACZ,eAAe,EACf,mBAAmB,EACnB,OAAO,EACP,SAAS,EACT,WAAW,EACX,UAAU,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,GAAG,IAAI,EACR,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"EmojiSheetView.d.ts","sourceRoot":"","sources":["../../src/EmojiSheetView.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAKlC,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,eAAe,EACf,SAAS,EACT,MAAM,EACN,KAAK,EACL,YAAY,EACZ,eAAe,EACf,mBAAmB,EACnB,OAAO,EACP,SAAS,EACT,WAAW,EACX,UAAU,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,GAAG,IAAI,EACR,EAAE,mBAAmB,2CA0BrB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-native-sheet-emojis",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "A fully native emoji picker bottom sheet for React Native. Built with Swift and Kotlin for maximum performance. Features search with multilingual keywords, skin tones, frequently used tracking, theming, and configurable layout.",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -65,7 +65,7 @@ export type EmojiSheetViewProps = ViewProps & {
|
|
|
65
65
|
onEmojiSelected: (emoji: string, name: string, id: string) => void;
|
|
66
66
|
onDismiss?: () => void;
|
|
67
67
|
onOpen?: () => void;
|
|
68
|
-
theme?:
|
|
68
|
+
theme?: 'dark' | 'light' | 'system';
|
|
69
69
|
translations?: EmojiSheetTranslations;
|
|
70
70
|
layoutDirection?: 'ltr' | 'rtl' | 'auto';
|
|
71
71
|
categoryBarPosition?: 'top' | 'bottom';
|
package/src/EmojiSheetView.tsx
CHANGED
|
@@ -27,16 +27,10 @@ export default function EmojiSheetView({
|
|
|
27
27
|
excludeEmojis,
|
|
28
28
|
...rest
|
|
29
29
|
}: EmojiSheetViewProps) {
|
|
30
|
-
const resolvedTheme = typeof theme === 'object' ? 'custom' : theme;
|
|
31
|
-
|
|
32
|
-
// For custom themes, we pass individual color props through the native view
|
|
33
|
-
// For now, the declarative view supports 'dark' | 'light' string themes
|
|
34
|
-
// Custom theme object support would require additional native view props
|
|
35
|
-
|
|
36
30
|
return (
|
|
37
31
|
<NativeView
|
|
38
32
|
{...rest}
|
|
39
|
-
theme={
|
|
33
|
+
theme={typeof theme === 'string' ? theme : undefined}
|
|
40
34
|
layoutDirection={layoutDirection}
|
|
41
35
|
categoryBarPosition={categoryBarPosition}
|
|
42
36
|
columns={columns}
|