diff --git a/app/src/main/java/com/quillstudios/pokegoalshelper/ui/EnhancedFloatingFAB.kt b/app/src/main/java/com/quillstudios/pokegoalshelper/ui/EnhancedFloatingFAB.kt index bc7dec8..ac49cd0 100644 --- a/app/src/main/java/com/quillstudios/pokegoalshelper/ui/EnhancedFloatingFAB.kt +++ b/app/src/main/java/com/quillstudios/pokegoalshelper/ui/EnhancedFloatingFAB.kt @@ -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 (!isShowing) 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 VISIBLE - // 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 (!isMenuExpanded) 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) } }