@ -51,9 +51,12 @@ class EnhancedFloatingFAB(
}
private var windowManager : WindowManager ? = null
private var rootView : ViewGroup ? = null
private var mainFAB : ImageButton ? = null
private var menuContainer : LinearLayout ? = null
// Separate window parameters
private var fabParams : WindowManager . LayoutParams ? = null
private var menuParams : WindowManager . LayoutParams ? = null
private var isShowing = false
private var isMenuExpanded = false
private var isDragging = false
@ -87,10 +90,18 @@ class EnhancedFloatingFAB(
if ( !is Showing ) return
try {
rootView ?. let { windowManager ?. removeView ( it ) }
rootView = null
// Remove menu window if showing
if ( isMenuExpanded ) {
menuContainer ?. let { windowManager ?. removeView ( it ) }
}
// Remove main FAB window
mainFAB ?. let { windowManager ?. removeView ( it ) }
mainFAB = null
menuContainer = null
fabParams = null
menuParams = null
windowManager = null
isShowing = false
handler . removeCallbacksAndMessages ( null )
@ -101,39 +112,28 @@ class EnhancedFloatingFAB(
}
private fun createFloatingView ( ) {
// Create root container - sized exactly to content for precise touch handling
rootView = LinearLayout ( context ) . apply {
orientation = LinearLayout . VERTICAL
gravity = Gravity . END or Gravity . BOTTOM
layoutParams = ViewGroup . LayoutParams (
ViewGroup . LayoutParams . WRAP_CONTENT ,
ViewGroup . LayoutParams . WRAP_CONTENT
)
}
// Create main FAB as separate window
mainFAB = createMainFAB ( )
// Create expandable menu container
// Create menu container as separate window
menuContainer = createMenuContainer ( )
rootView ?. addView ( menuContainer )
// Create main FAB
mainFAB = createMainFAB ( )
rootView ?. addView ( mainFAB )
updateMenuLayout ( )
// Set up window parameters for true floating
// Set up initial position
val screenSize = getScreenSize ( )
currentX = screenSize . first - dpToPx ( FAB_SIZE_DP + 16 ) // Start on right edge
currentY = screenSize . second / 2 // Center vertically
val params = WindowManager . LayoutParams (
WindowManager . LayoutParams . WRAP_CONTENT ,
WindowManager . LayoutParams . WRAP_CONTENT ,
// Create window parameters for main FAB
fabParams = WindowManager . LayoutParams (
dpToPx ( FAB_SIZE_DP ) ,
dpToPx ( FAB_SIZE_DP ) ,
if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . O ) {
WindowManager . LayoutParams . TYPE_APPLICATION_OVERLAY
} else {
@Suppress ( " DEPRECATION " )
WindowManager . LayoutParams . TYPE_PHONE
} ,
// Critical flags for touch-through behavior
WindowManager . LayoutParams . FLAG_NOT_FOCUSABLE or
WindowManager . LayoutParams . FLAG_NOT_TOUCH_MODAL or
WindowManager . LayoutParams . FLAG_LAYOUT_NO_LIMITS ,
@ -144,10 +144,33 @@ class EnhancedFloatingFAB(
y = this @EnhancedFloatingFAB . currentY
}
windowManager ?. addView ( rootView , params )
// Create window parameters for menu (initially hidden)
menuParams = WindowManager . LayoutParams (
WindowManager . LayoutParams . WRAP_CONTENT ,
WindowManager . LayoutParams . WRAP_CONTENT ,
if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . O ) {
WindowManager . LayoutParams . TYPE_APPLICATION_OVERLAY
} else {
@Suppress ( " DEPRECATION " )
WindowManager . LayoutParams . TYPE_PHONE
} ,
WindowManager . LayoutParams . FLAG_NOT_FOCUSABLE or
WindowManager . LayoutParams . FLAG_NOT_TOUCH_MODAL or
WindowManager . LayoutParams . FLAG_LAYOUT_NO_LIMITS ,
PixelFormat . TRANSLUCENT
) . apply {
gravity = Gravity . TOP or Gravity . START
// Position menu to the left of FAB with proper spacing
val menuWidth = 200
val padding = dpToPx ( 8 ) // 8dp padding between menu and FAB
// Menu's right edge should align with FAB's left edge minus padding
x = maxOf ( 0 , this @EnhancedFloatingFAB . currentX - menuWidth - padding )
y = this @EnhancedFloatingFAB . currentY // Same vertical position as FAB
}
// Start auto-hide timer
scheduleAutoHide ( )
// Add main FAB window
windowManager ?. addView ( mainFAB , fabParams )
}
private fun createMainFAB ( ) : ImageButton {
@ -176,11 +199,10 @@ class EnhancedFloatingFAB(
private fun createMenuContainer ( ) : LinearLayout {
return LinearLayout ( context ) . apply {
orientation = LinearLayout . VERTICAL
gravity = Gravity . END
visibility = View . GONE
gravity = Gravity . TOP or Gravity . END // Align menu items to top-right of container
visibility = View . VISIBLE // Changed from GONE to VISIBL E
// Add menu items
addMenuItems ( this )
// Initial menu items will be added by updateMenuLayout()
layoutParams = LinearLayout . LayoutParams (
ViewGroup . LayoutParams . WRAP_CONTENT ,
@ -189,32 +211,43 @@ class EnhancedFloatingFAB(
}
}
private fun addMenuItems ( container : LinearLayout ) {
val menuItems = listOf (
MenuItemData ( " DEBUG " , android . R . drawable . ic_menu_info_details , android . R . color . holo_orange_dark ) {
onDebugToggled ( )
onDetectionRequested ( )
} ,
MenuItemData ( " ALL " , android . R . drawable . ic_menu_view , android . R . color . holo_green_dark ) {
onClassFilterRequested ( null )
onDetectionRequested ( )
} ,
MenuItemData ( " POKEBALL " , android . R . drawable . ic_menu_mylocation , android . R . color . holo_red_dark ) {
onClassFilterRequested ( " ball_icon_cherishball " )
onDetectionRequested ( )
} ,
MenuItemData ( " SHINY " , android . R . drawable . btn_star_big_on , android . R . color . holo_purple ) {
onClassFilterRequested ( " shiny_icon " )
onDetectionRequested ( )
} ,
MenuItemData ( " DETECT " , android . R . drawable . ic_menu_search , android . R . color . holo_blue_dark ) {
onDetectionRequested ( )
private fun updateMenuLayout ( ) {
menuContainer ?. let { container ->
// Clear existing items
container . removeAllViews ( )
// Menu items should align to the right edge of the menu container
// so they appear closest to the main FAB
( container as LinearLayout ) . gravity = Gravity . TOP or Gravity . END
// Add menu items with appropriate layout
val menuItems = listOf (
MenuItemData ( " DEBUG " , android . R . drawable . ic_menu_info_details , android . R . color . holo_orange_dark ) {
onDebugToggled ( )
onDetectionRequested ( )
} ,
MenuItemData ( " ALL " , android . R . drawable . ic_menu_view , android . R . color . holo_green_dark ) {
onClassFilterRequested ( null )
onDetectionRequested ( )
} ,
MenuItemData ( " POKEBALL " , android . R . drawable . ic_menu_mylocation , android . R . color . holo_red_dark ) {
onClassFilterRequested ( " ball_icon_cherishball " )
onDetectionRequested ( )
} ,
MenuItemData ( " SHINY " , android . R . drawable . btn_star_big_on , android . R . color . holo_purple ) {
onClassFilterRequested ( " shiny_icon " )
onDetectionRequested ( )
} ,
MenuItemData ( " DETECT " , android . R . drawable . ic_menu_search , android . R . color . holo_blue_dark ) {
onDetectionRequested ( )
}
)
menuItems . forEach { item ->
val menuRow = createMenuRow ( item )
container . addView ( menuRow )
}
)
menuItems . forEach { item ->
val menuRow = createMenuRow ( item )
container . addView ( menuRow )
}
}
@ -234,7 +267,7 @@ class EnhancedFloatingFAB(
ViewGroup . LayoutParams . WRAP_CONTENT ,
ViewGroup . LayoutParams . WRAP_CONTENT
) . apply {
setMargins ( 0 , 0 , dpToPx ( 8 ) , 0 )
setMargins ( 0 , 0 , dpToPx ( 8 ) , 0 ) // 8dp space between label and mini FAB
}
}
@ -249,7 +282,6 @@ class EnhancedFloatingFAB(
performHapticFeedback ( HapticFeedbackConstants . CONTEXT_CLICK )
item . action ( )
hideMenu ( )
scheduleAutoHide ( )
}
layoutParams = LinearLayout . LayoutParams (
@ -258,6 +290,7 @@ class EnhancedFloatingFAB(
)
}
// Always: LABEL → FAB (label on left, mini FAB on right, closest to main FAB)
addView ( label )
addView ( miniFAB )
@ -265,7 +298,7 @@ class EnhancedFloatingFAB(
ViewGroup . LayoutParams . WRAP_CONTENT ,
ViewGroup . LayoutParams . WRAP_CONTENT
) . apply {
setMargins ( 0 , 0 , 0 , dpToPx ( 8 ) )
setMargins ( 0 , 0 , 0 , dpToPx ( 8 ) ) // 8dp space between menu rows
}
}
}
@ -322,7 +355,7 @@ class EnhancedFloatingFAB(
MotionEvent . ACTION_UP -> {
if ( isDragging ) {
snapToEdgeIfNeeded ( )
scheduleAutoHide ( )
// scheduleAutoHide() // Disabled - no auto-hide after drag
} else {
// Handle click
performHapticFeedback ( HapticFeedbackConstants . CONTEXT_CLICK )
@ -331,7 +364,7 @@ class EnhancedFloatingFAB(
} else {
showMenu ( )
}
scheduleAutoHide ( )
// scheduleAutoHide() // Disabled - menu stays open until manually closed
}
isDragging = false
true
@ -343,14 +376,46 @@ class EnhancedFloatingFAB(
}
private fun updateWindowPosition ( ) {
rootView ?. let { view ->
val params = view . layoutParams as WindowManager . LayoutParams
params . x = currentX
params . y = currentY
try {
windowManager ?. updateViewLayout ( view , params )
} catch ( e : Exception ) {
Log . w ( TAG , " Failed to update window position " , e )
// Update main FAB position
mainFAB ?. let { fab ->
fabParams ?. let { params ->
params . x = currentX
params . y = currentY
try {
windowManager ?. updateViewLayout ( fab , params )
} catch ( e : Exception ) {
Log . w ( TAG , " Failed to update FAB position " , e )
}
}
}
// Update menu position if visible
if ( isMenuExpanded ) {
updateMenuPosition ( )
}
}
private fun updateMenuPosition ( ) {
menuContainer ?. let { menu ->
menuParams ?. let { params ->
// Calculate actual menu width by measuring the container
menu . measure (
View . MeasureSpec . makeMeasureSpec ( 0 , View . MeasureSpec . UNSPECIFIED ) ,
View . MeasureSpec . makeMeasureSpec ( 0 , View . MeasureSpec . UNSPECIFIED )
)
val menuWidth = menu . measuredWidth
val padding = dpToPx ( 8 ) // 8dp padding between menu and FAB
// Menu's right edge should align with FAB's left edge minus padding
params . x = maxOf ( 0 , currentX - menuWidth - padding )
params . y = currentY // Same vertical position as FAB (top alignment)
Log . d ( TAG , " Menu position: x= ${params.x} , y= ${params.y} , measured width= ${menuWidth} , FAB at: x= $currentX , y= $currentY " )
try {
windowManager ?. updateViewLayout ( menu , params )
} catch ( e : Exception ) {
Log . w ( TAG , " Failed to update menu position " , e )
}
}
}
}
@ -393,9 +458,21 @@ class EnhancedFloatingFAB(
private fun showMenu ( ) {
if ( isMenuExpanded ) return
Log . d ( TAG , " showMenu() called " )
// Update menu layout for current position
updateMenuLayout ( )
// Update menu position
updateMenuPosition ( )
// Add menu window
menuContainer ?. let { container ->
Log . d ( TAG , " Adding menu container to WindowManager " )
container . visibility = View . VISIBLE
windowManager ?. addView ( container , menuParams )
isMenuExpanded = true
Log . d ( TAG , " Menu window added, isMenuExpanded = $isMenuExpanded " )
// Animate menu items in
for ( i in 0 until container . childCount ) {
@ -426,30 +503,12 @@ class EnhancedFloatingFAB(
if ( !is MenuExpanded ) return
menuContainer ?. let { container ->
// Animate menu items out
for ( i in 0 until container . childCount ) {
val child = container . getChildAt ( i )
ObjectAnimator . ofFloat ( child , " alpha " , 1f , 0f ) . apply {
duration = MENU_ANIMATION_DURATION / 2
start ( )
}
ObjectAnimator . ofFloat ( child , " translationY " , 0f , dpToPx ( 20 ) . toFloat ( ) ) . apply {
duration = MENU_ANIMATION_DURATION / 2
interpolator = AccelerateDecelerateInterpolator ( )
if ( i == container . childCount - 1 ) {
addListener ( object : AnimatorListenerAdapter ( ) {
override fun onAnimationEnd ( animation : Animator ) {
container . visibility = View . GONE
isMenuExpanded = false
}
} )
}
start ( )
}
// Remove menu window
try {
windowManager ?. removeView ( container )
isMenuExpanded = false
} catch ( e : Exception ) {
Log . w ( TAG , " Failed to remove menu window " , e )
}
}