Skip to content

Commit 4df3b6b

Browse files
committed
feat: implement Android native functionality with iOS parity
- Remove triggerMode prop from Android (only long press like iOS) - Remove default emojis - reactions only show when provided from React Native - Implement single-level menu nesting for Android (parent → child only) - Add proper subtitle and icon support for Android menu items - Ensure haptic feedback works consistently - Update CI/CD test workflow to run on feature branches only - Exclude main and development branches (handled by release workflow) - Still runs on PRs targeting main/development BREAKING CHANGE: Android now only supports long press gesture (no tap mode)
1 parent c2ebbe3 commit 4df3b6b

File tree

3 files changed

+81
-43
lines changed

3 files changed

+81
-43
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Test
22

33
on:
44
push:
5-
branches: [main, development]
5+
branches-ignore: [main, development] # Run on all branches except main and development
66
pull_request:
77
branches: [main, development]
88
workflow_call: # Allow this workflow to be called by other workflows

android/src/main/java/expo/modules/focusmenu/ExpoFocusMenuModule.kt

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,7 @@ class ExpoFocusMenuModule : Module() {
5353
view.menuItems = items
5454
}
5555

56-
Prop("triggerMode") { view, mode: String ->
57-
view.triggerMode = mode
58-
view.updateTriggerMode()
59-
}
56+
// Removed triggerMode - always use long press like iOS
6057

6158
Prop("hapticFeedback") { view, enabled: Boolean ->
6259
view.hapticFeedback = enabled
@@ -163,7 +160,7 @@ class ExpoFocusMenuModule : Module() {
163160
}
164161

165162
if (children != null && children.isNotEmpty()) {
166-
// Create submenu
163+
// Create submenu (limited to 1 level deep like iOS)
167164
val subMenu = menu.addSubMenu(displayTitle)
168165

169166
// Add icon if available (only works on some Android versions)
@@ -174,8 +171,33 @@ class ExpoFocusMenuModule : Module() {
174171
}
175172
}
176173

177-
// Recursively add children to submenu
178-
currentId = addItemsToMenu(subMenu, children, menuItemIdMap, currentId)
174+
// Add children to submenu (no further nesting allowed)
175+
for (child in children) {
176+
val childId = child["id"] as? String ?: continue
177+
val childTitle = child["title"] as? String ?: continue
178+
val childSubtitle = child["subtitle"] as? String
179+
val childDisabled = child["disabled"] as? Boolean ?: false
180+
181+
val childDisplayTitle = if (childSubtitle != null) {
182+
"$childTitle\n$childSubtitle"
183+
} else {
184+
childTitle
185+
}
186+
187+
val childMenuItem = subMenu.add(Menu.NONE, currentId, Menu.NONE, childDisplayTitle)
188+
menuItemIdMap[currentId] = childId
189+
currentId++
190+
childMenuItem.isEnabled = !childDisabled
191+
192+
// Add icon for child if available
193+
val childIcon = child["icon"] as? String
194+
if (childIcon != null) {
195+
val childIconResource = getIconResource(childIcon)
196+
if (childIconResource != 0) {
197+
childMenuItem.setIcon(childIconResource)
198+
}
199+
}
200+
}
179201
} else {
180202
// Create regular menu item
181203
val menuItem = menu.add(Menu.NONE, currentId, Menu.NONE, displayTitle)

android/src/main/java/expo/modules/focusmenu/ExpoFocusMenuView.kt

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class ExpoFocusMenuView(context: Context, appContext: AppContext) : ExpoView(con
3535

3636
// Properties from JS
3737
var menuItems: List<Map<String, Any>> = emptyList()
38-
var triggerMode: String = "longPress"
38+
// Removed triggerMode - always use long press like iOS
3939
var hapticFeedback: Boolean = false
4040
var reactions: List<String> = emptyList()
4141
set(value) {
@@ -62,57 +62,73 @@ class ExpoFocusMenuView(context: Context, appContext: AppContext) : ExpoView(con
6262
createContextMenu(menu)
6363
}
6464

65-
// Set up click/long click based on trigger mode
66-
updateTriggerMode()
67-
}
68-
69-
fun updateTriggerMode() {
70-
when (triggerMode) {
71-
"tap" -> {
72-
setOnClickListener {
73-
if (hapticFeedback) provideHapticFeedback()
74-
showContextMenu()
75-
if (reactions.isNotEmpty()) showEmojiPicker()
76-
}
77-
setOnLongClickListener(null)
78-
}
79-
else -> { // "longPress"
80-
setOnClickListener(null)
81-
setOnLongClickListener {
82-
if (hapticFeedback) provideHapticFeedback()
83-
showContextMenu()
84-
if (reactions.isNotEmpty()) showEmojiPicker()
85-
true
86-
}
65+
// Only long press triggers the menu (like iOS)
66+
setOnLongClickListener {
67+
if (hapticFeedback) provideHapticFeedback()
68+
showContextMenu()
69+
// Show emoji picker if reactions are provided
70+
if (reactions.isNotEmpty()) {
71+
showEmojiPicker()
8772
}
73+
true
8874
}
8975
}
9076

77+
// Removed updateTriggerMode - always use long press
78+
9179
private fun createContextMenu(menu: ContextMenu) {
9280
menu.clear()
9381
menuItemIdMap.clear()
9482
menuItemCounter = 1
9583

84+
// Add menu items with support for single-level nesting
9685
for (item in menuItems) {
9786
val id = item["id"] as? String ?: continue
9887
val title = item["title"] as? String ?: continue
9988
val subtitle = item["subtitle"] as? String
10089
val disabled = item["disabled"] as? Boolean ?: false
10190
val destructive = item["destructive"] as? Boolean ?: false
10291
val icon = item["icon"] as? String
92+
val children = item["children"] as? List<Map<String, Any>>
10393

104-
val menuItem = menu.add(0, menuItemCounter, 0, title)
105-
menuItemIdMap[menuItemCounter] = id
106-
menuItemCounter++
107-
108-
// Set enabled state
109-
menuItem.isEnabled = !disabled
94+
// Combine title and subtitle if present
95+
val displayTitle = if (subtitle != null) {
96+
"$title\n$subtitle"
97+
} else {
98+
title
99+
}
110100

111-
// Add icon if available
112-
if (icon != null) {
113-
val iconResource = getIconResource(icon)
114-
if (iconResource != 0) {
115-
menuItem.setIcon(iconResource)
101+
if (!children.isNullOrEmpty()) {
102+
// Create submenu for nested items (only 1 level deep like iOS)
103+
val subMenu = menu.addSubMenu(0, Menu.NONE, menuItemCounter, title)
104+
menuItemCounter++
105+
106+
// Add children to submenu
107+
for (child in children) {
108+
val childId = child["id"] as? String ?: continue
109+
val childTitle = child["title"] as? String ?: continue
110+
val childDisabled = child["disabled"] as? Boolean ?: false
111+
112+
val childMenuItem = subMenu.add(0, menuItemCounter, 0, childTitle)
113+
menuItemIdMap[menuItemCounter] = childId
114+
menuItemCounter++
115+
childMenuItem.isEnabled = !childDisabled
116+
}
117+
} else {
118+
// Regular menu item
119+
val menuItem = menu.add(0, menuItemCounter, 0, displayTitle)
120+
menuItemIdMap[menuItemCounter] = id
121+
menuItemCounter++
122+
123+
// Set enabled state
124+
menuItem.isEnabled = !disabled
125+
126+
// Add icon if available
127+
if (icon != null) {
128+
val iconResource = getIconResource(icon)
129+
if (iconResource != 0) {
130+
menuItem.setIcon(iconResource)
131+
}
116132
}
117133
}
118134
}
@@ -160,7 +176,7 @@ class ExpoFocusMenuView(context: Context, appContext: AppContext) : ExpoView(con
160176
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
161177

162178
val adapter = EmojiPickerAdapter(
163-
emojis = reactions.ifEmpty { DEFAULT_EMOJIS },
179+
emojis = reactions, // Use only the reactions provided from React Native
164180
selectedEmoji = selectedEmoji,
165181
onEmojiClick = { emoji ->
166182
handleEmojiSelection(emoji)

0 commit comments

Comments
 (0)