Skip to content

Commit 75a3a16

Browse files
feat: move AI tasks into a dedicated AI submenu (#2288) (#2289)
Collapses the two top-level main-menu entries (Reply Drafter and Screen Highlights) into a single AI submenu link, with both features living inside the submenu. User-visible behaviour is unchanged — HUD copy, Compose activities, screenshot capture, and the per-action IAP gates are all preserved. Mirrors the existing system/ submenu pattern: AiMenu wraps BaseMenu, AiMenuStructure builds the items via the registry, MenuStructureHolder exposes buildAiMenuObject(), MenuManager exposes openAiMenu(). Closes #2288 Co-authored-by: Owen McGirr <o.a.mcgirr@gmail.com>
1 parent 096cd08 commit 75a3a16

9 files changed

Lines changed: 131 additions & 22 deletions

File tree

app/src/main/java/com/enaboapps/switchify/service/menu/MenuManager.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.enaboapps.switchify.service.menu
22

33
import com.enaboapps.switchify.service.core.SwitchifyAccessibilityService
44
import com.enaboapps.switchify.service.gestures.visuals.GestureVisualManager
5+
import com.enaboapps.switchify.service.menu.menus.ai.AiMenu
56
import com.enaboapps.switchify.service.menu.menus.edit.EditMenu
67
import com.enaboapps.switchify.service.menu.menus.gestures.CustomGestureConfirmationMenu
78
import com.enaboapps.switchify.service.menu.menus.gestures.FingerModeMenu
@@ -114,6 +115,14 @@ class MenuManager {
114115
openMenu(deviceMenu.build())
115116
}
116117

118+
/**
119+
* This function opens the AI menu
120+
*/
121+
fun openAiMenu() {
122+
val aiMenu = AiMenu(accessibilityService!!)
123+
openMenu(aiMenu.build())
124+
}
125+
117126
/**
118127
* This function opens the edit menu
119128
*/
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.enaboapps.switchify.service.menu.menus.ai
2+
3+
import com.enaboapps.switchify.service.core.SwitchifyAccessibilityService
4+
import com.enaboapps.switchify.service.menu.MenuItem
5+
import com.enaboapps.switchify.service.menu.menus.BaseMenu
6+
import com.enaboapps.switchify.service.menu.structure.MenuConstants
7+
import com.enaboapps.switchify.service.menu.structure.MenuStructureHolder
8+
9+
class AiMenu(
10+
accessibilityService: SwitchifyAccessibilityService
11+
) : BaseMenu(
12+
accessibilityService,
13+
buildAiItems(accessibilityService),
14+
MenuConstants.MenuIds.AI_MENU
15+
) {
16+
17+
companion object {
18+
private fun buildAiItems(accessibilityService: SwitchifyAccessibilityService): List<MenuItem> {
19+
return MenuStructureHolder(accessibilityService).buildAiMenuObject().getMenuItems()
20+
}
21+
}
22+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.enaboapps.switchify.service.menu.menus.ai
2+
3+
import com.enaboapps.switchify.backend.iap.IAPHandler
4+
import com.enaboapps.switchify.service.core.SwitchifyAccessibilityService
5+
import com.enaboapps.switchify.service.llm.ReplyDrafterManager
6+
import com.enaboapps.switchify.service.llm.ScreenHighlightsManager
7+
import com.enaboapps.switchify.service.menu.MenuItem
8+
import com.enaboapps.switchify.service.menu.structure.MenuConstants
9+
import com.enaboapps.switchify.service.menu.structure.MenuItemRegistry
10+
import com.enaboapps.switchify.service.menu.structure.MenuStructure
11+
import kotlinx.coroutines.CoroutineScope
12+
13+
class AiMenuStructure(
14+
private val accessibilityService: SwitchifyAccessibilityService,
15+
private val coroutineScope: CoroutineScope
16+
) {
17+
18+
/**
19+
* Build the AI submenu containing the on-device AI features (Reply Drafter
20+
* and Screen Highlights). Each item keeps the per-action Pro gate it had
21+
* on the main menu — the submenu link itself opens freely so users can
22+
* discover the features.
23+
*/
24+
fun buildAiMenuObject(): MenuStructure {
25+
return MenuStructure(
26+
id = MenuConstants.MenuIds.AI_MENU,
27+
items = listOfNotNull(
28+
MenuItemRegistry.getDefinition(MenuConstants.MenuIds.AI_MENU, MenuConstants.ItemIds.Ai.REPLY_DRAFTER)?.let { def ->
29+
MenuItem(
30+
definition = def,
31+
action = {
32+
IAPHandler.runIfProPurchased(accessibilityService) {
33+
ReplyDrafterManager.startDrafting(accessibilityService)
34+
}
35+
}
36+
)
37+
},
38+
MenuItemRegistry.getDefinition(MenuConstants.MenuIds.AI_MENU, MenuConstants.ItemIds.Ai.SCREEN_HIGHLIGHTS)?.let { def ->
39+
MenuItem(
40+
definition = def,
41+
action = {
42+
IAPHandler.runIfProPurchased(accessibilityService) {
43+
ScreenHighlightsManager.startExtracting(accessibilityService)
44+
}
45+
}
46+
)
47+
}
48+
),
49+
context = accessibilityService,
50+
coroutineScope = coroutineScope
51+
)
52+
}
53+
}

app/src/main/java/com/enaboapps/switchify/service/menu/menus/main/MainMenuStructure.kt

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import com.enaboapps.switchify.service.core.ServiceCore
88
import com.enaboapps.switchify.service.core.SwitchifyAccessibilityService
99
import com.enaboapps.switchify.service.gestures.GesturePoint
1010
import com.enaboapps.switchify.service.keyboard.KeyboardManager
11-
import com.enaboapps.switchify.service.llm.ReplyDrafterManager
12-
import com.enaboapps.switchify.service.llm.ScreenHighlightsManager
1311
import com.enaboapps.switchify.service.menu.MenuItem
1412
import com.enaboapps.switchify.service.menu.MenuManager
1513
import com.enaboapps.switchify.service.menu.database.MenuConfigurationRepository
@@ -145,24 +143,11 @@ class MainMenuStructure(
145143
)
146144
}
147145
} else null,
148-
MenuItemRegistry.getMainMenuDefinition("reply_drafter")?.let { def ->
146+
MenuItemRegistry.getMainMenuDefinition("ai")?.let { def ->
149147
MenuItem(
150148
definition = def,
151-
action = {
152-
IAPHandler.runIfProPurchased(accessibilityService) {
153-
ReplyDrafterManager.startDrafting(accessibilityService)
154-
}
155-
}
156-
)
157-
},
158-
MenuItemRegistry.getMainMenuDefinition("screen_highlights")?.let { def ->
159-
MenuItem(
160-
definition = def,
161-
action = {
162-
IAPHandler.runIfProPurchased(accessibilityService) {
163-
ScreenHighlightsManager.startExtracting(accessibilityService)
164-
}
165-
}
149+
isLinkToMenu = true,
150+
action = { MenuManager.getInstance().openAiMenu() }
166151
)
167152
},
168153
MenuItemRegistry.getMainMenuDefinition("pause")?.let { def ->

app/src/main/java/com/enaboapps/switchify/service/menu/structure/MenuConstants.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ object MenuConstants {
2626
const val FAVOURITE_APPS_MENU = "favourite_apps_menu"
2727
const val GESTURE_PATTERNS_MENU = "gesture_patterns_menu"
2828
const val FINGER_MODE_MENU = "finger_mode_menu"
29+
const val AI_MENU = "ai_menu"
2930
}
3031

3132
fun getTitleResource(menuId: String?): Int? = when (menuId) {
3233
MenuIds.MAIN_MENU -> R.string.menu_title_main
34+
MenuIds.AI_MENU -> R.string.menu_title_ai
3335
MenuIds.DEVICE_MENU -> R.string.menu_title_device
3436
MenuIds.VOLUME_CONTROL_MENU -> R.string.menu_title_volume_control
3537
MenuIds.GESTURES_MENU -> R.string.menu_title_gestures
@@ -77,9 +79,16 @@ object MenuConstants {
7779
const val SETTINGS = "settings"
7880
const val MEDIA_CONTROL = "media_control"
7981
const val EDIT = "edit"
82+
const val AI = "ai"
83+
const val PAUSE = "pause"
84+
}
85+
86+
/**
87+
* AI submenu items
88+
*/
89+
object Ai {
8090
const val REPLY_DRAFTER = "reply_drafter"
8191
const val SCREEN_HIGHLIGHTS = "screen_highlights"
82-
const val PAUSE = "pause"
8392
}
8493

8594
/**

app/src/main/java/com/enaboapps/switchify/service/menu/structure/MenuItemRegistry.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ object MenuItemRegistry {
2828
MenuItemDefinition(MenuConstants.ItemIds.Main.SETTINGS, labelResource = R.string.menu_item_settings, descriptionResource = R.string.menu_item_settings_description, drawableId = R.drawable.ic_settings),
2929
MenuItemDefinition(MenuConstants.ItemIds.Main.MEDIA_CONTROL, labelResource = R.string.menu_title_media_control, descriptionResource = R.string.menu_item_media_control_description, drawableId = R.drawable.ic_media_control),
3030
MenuItemDefinition(MenuConstants.ItemIds.Main.EDIT, labelResource = R.string.menu_title_edit, descriptionResource = R.string.menu_item_edit_description, drawableId = R.drawable.ic_edit),
31-
MenuItemDefinition(MenuConstants.ItemIds.Main.REPLY_DRAFTER, labelResource = R.string.menu_item_reply_drafter, descriptionResource = R.string.menu_item_reply_drafter_description, drawableId = R.drawable.ic_reply_drafter),
32-
MenuItemDefinition(MenuConstants.ItemIds.Main.SCREEN_HIGHLIGHTS, labelResource = R.string.menu_item_screen_highlights, descriptionResource = R.string.menu_item_screen_highlights_description, drawableId = R.drawable.ic_screen_highlights),
31+
MenuItemDefinition(MenuConstants.ItemIds.Main.AI, labelResource = R.string.menu_title_ai, descriptionResource = R.string.menu_item_ai_description, drawableId = R.drawable.ic_ai),
3332
MenuItemDefinition(MenuConstants.ItemIds.Main.PAUSE, labelResource = R.string.menu_item_pause, descriptionResource = R.string.menu_item_pause_description, drawableId = R.drawable.ic_pause)
3433
)
3534
}
@@ -204,17 +203,30 @@ object MenuItemRegistry {
204203
)
205204
}
206205

206+
/**
207+
* Provides menu item definitions for the AI submenu.
208+
*
209+
* @return A list of MenuItemDefinition for Reply Drafter and Screen Highlights.
210+
*/
211+
fun getAiMenuDefinitions(): List<MenuItemDefinition> {
212+
return listOf(
213+
MenuItemDefinition(MenuConstants.ItemIds.Ai.REPLY_DRAFTER, labelResource = R.string.menu_item_reply_drafter, descriptionResource = R.string.menu_item_reply_drafter_description, drawableId = R.drawable.ic_reply_drafter),
214+
MenuItemDefinition(MenuConstants.ItemIds.Ai.SCREEN_HIGHLIGHTS, labelResource = R.string.menu_item_screen_highlights, descriptionResource = R.string.menu_item_screen_highlights_description, drawableId = R.drawable.ic_screen_highlights)
215+
)
216+
}
217+
207218
/**
208219
* Retrieve the menu item definitions for a given menu ID.
209220
*
210-
* @param menuId Identifier of the menu. Recognized values: "main_menu", "device_menu", "volume_control_menu",
221+
* @param menuId Identifier of the menu. Recognized values: "main_menu", "ai_menu", "device_menu", "volume_control_menu",
211222
* "gestures_menu", "tap_gestures_menu", "tap_and_hold_menu", "swipe_gestures_menu", "pinch_gestures_menu",
212223
* "scroll_menu", "media_control_menu", "edit_menu", "settings_menu".
213224
* @return A list of MenuItemDefinition for the specified menu, or an empty list if the menuId is not recognized.
214225
*/
215226
fun getDefinitionsForMenu(menuId: String): List<MenuItemDefinition> {
216227
return when (menuId) {
217228
MenuConstants.MenuIds.MAIN_MENU -> getMainMenuDefinitions()
229+
MenuConstants.MenuIds.AI_MENU -> getAiMenuDefinitions()
218230
MenuConstants.MenuIds.DEVICE_MENU -> getDeviceMenuDefinitions()
219231
MenuConstants.MenuIds.VOLUME_CONTROL_MENU -> getVolumeControlMenuDefinitions()
220232
MenuConstants.MenuIds.GESTURES_MENU -> getGesturesMenuDefinitions()

app/src/main/java/com/enaboapps/switchify/service/menu/structure/MenuStructureHolder.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.enaboapps.switchify.service.menu.structure
22

33
import com.enaboapps.switchify.service.core.SwitchifyAccessibilityService
4+
import com.enaboapps.switchify.service.menu.menus.ai.AiMenuStructure
45
import com.enaboapps.switchify.service.menu.menus.edit.EditMenuStructure
56
import com.enaboapps.switchify.service.menu.menus.gestures.GestureMenuStructure
67
import com.enaboapps.switchify.service.menu.menus.main.MainMenuStructure
@@ -26,11 +27,15 @@ class MenuStructureHolder(accessibilityService: SwitchifyAccessibilityService) {
2627
private val editMenuStructure = EditMenuStructure(accessibilityService, serviceScope)
2728
private val favouriteAppsMenuStructure = FavouriteAppsMenuStructure(accessibilityService, serviceScope)
2829
private val settingsMenuStructure = SettingsMenuStructure(accessibilityService, serviceScope)
30+
private val aiMenuStructure = AiMenuStructure(accessibilityService, serviceScope)
2931

3032
// Main Menu
3133
fun buildMainMenuObject() = mainMenuStructure.buildMainMenuObject()
3234
val menuManipulatorItems = mainMenuStructure.menuManipulatorItems
3335

36+
// AI Menu
37+
fun buildAiMenuObject() = aiMenuStructure.buildAiMenuObject()
38+
3439
// Gesture Menus
3540
val gesturesMenuObject = gestureMenuStructure.gesturesMenuObject
3641
val pinchGesturesMenuObject = gestureMenuStructure.pinchGesturesMenuObject
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
2+
android:width="32dp"
3+
android:height="32dp"
4+
android:viewportWidth="32"
5+
android:viewportHeight="32"
6+
android:tint="?attr/colorControlNormal">
7+
<path
8+
android:fillColor="@android:color/white"
9+
android:pathData="M16,3 L19,13 L29,16 L19,19 L16,29 L13,19 L3,16 L13,13 Z" />
10+
</vector>

app/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,10 @@
10371037
<string name="menu_item_next_page_description">Show the next page of menu items</string>
10381038
<string name="menu_item_loading_description">Please wait while items load</string>
10391039

1040+
<!-- AI submenu -->
1041+
<string name="menu_title_ai">AI</string>
1042+
<string name="menu_item_ai_description">Draft replies and pull out highlights from the screen</string>
1043+
10401044
<!-- Reply Drafter -->
10411045
<string name="menu_item_reply_drafter">Reply Drafter</string>
10421046
<string name="menu_item_reply_drafter_description">Capture the screen and suggest replies</string>

0 commit comments

Comments
 (0)