@ -21,6 +21,8 @@ import android.view.Gravity
import android.widget.Button
import android.widget.LinearLayout
import androidx.core.app.NotificationCompat
import com.quillstudios.pokegoalshelper.controllers.DetectionController
import com.quillstudios.pokegoalshelper.ui.FloatingOrbUI
import org.opencv.android.Utils
import org.opencv.core.*
import org.opencv.imgproc.Imgproc
@ -98,9 +100,9 @@ class ScreenCaptureService : Service() {
private var screenDensity = 0
private var detectionOverlay : DetectionOverlay ? = null
// Floating button overlay
private var overlayButton : View ? = null
private var windowManager : WindowManager ? = null
// MVC Components
private lateinit var detectionController : DetectionController
private var floatingOrbUI : FloatingOrbUI ? = null
private val handler = Handler ( Looper . getMainLooper ( ) )
private var captureInterval = 2000L // Capture every 2 seconds
@ -148,6 +150,14 @@ class ScreenCaptureService : Service() {
} else {
Log . i ( TAG , " ✅ ONNX YOLO detector initialized for screen capture " )
}
// Initialize MVC components
detectionController = DetectionController ( yoloDetector !! )
floatingOrbUI = FloatingOrbUI ( this , detectionController )
detectionController . setUICallbacks ( floatingOrbUI !! )
detectionController . setDetectionRequestCallback { triggerManualDetection ( ) }
Log . d ( TAG , " ✅ MVC architecture initialized " )
}
override fun onStartCommand ( intent : Intent ? , flags : Int , startId : Int ) : Int {
@ -268,9 +278,9 @@ class ScreenCaptureService : Service() {
return
}
Log . d ( TAG , " Screen capture setup complete, creating manual trigger button " )
// Create floating detection button instead of auto-capture
createFloatingButton ( )
Log . d ( TAG , " Screen capture setup complete, showing floating orb UI " )
// Show the floating orb UI
floatingOrbUI ?. show ( )
} catch ( e : Exception ) {
Log . e ( TAG , " Error starting screen capture " , e )
@ -283,7 +293,7 @@ class ScreenCaptureService : Service() {
handler . removeCallbacks ( captureRunnable )
hideDetectionOverlay ( )
removeFloatingButton ( )
floatingOrbUI ?. hide ( )
latestImage ?. close ( )
latestImage = null
virtualDisplay ?. release ( )
@ -1058,12 +1068,7 @@ class ScreenCaptureService : Service() {
windowManager = getSystemService ( Context . WINDOW_SERVICE ) as WindowManager
// Create a container for multiple buttons
val buttonContainer = LinearLayout ( this ) . apply {
orientation = LinearLayout . VERTICAL
setBackgroundColor ( 0x80000000 . toInt ( ) ) // Semi-transparent black background
setPadding ( 8 , 8 , 8 , 8 )
}
createFloatingOrb ( )
// Main detect button
val detectButton = Button ( this ) . apply {
@ -1196,9 +1201,137 @@ class ScreenCaptureService : Service() {
Log . e ( TAG , " ❌ Error creating floating button " , e )
}
}
private fun createFloatingOrb ( ) {
// Create the main floating orb button
val orbButton = Button ( this ) . apply {
text = " 🎯 "
textSize = 20f
setBackgroundResource ( android . R . drawable . btn_default )
background . setTint ( 0xFF4CAF50 . toInt ( ) ) // Green
setTextColor ( 0xFFFFFFFF . toInt ( ) )
// Make it circular
width = 120
height = 120
layoutParams = ViewGroup . LayoutParams ( 120 , 120 )
setOnClickListener {
if ( isMenuExpanded ) {
collapseMenu ( )
} else {
expandMenu ( )
}
}
}
overlayButton = orbButton
val params = WindowManager . LayoutParams (
120 , 120 ,
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 ,
PixelFormat . TRANSLUCENT
) . apply {
gravity = Gravity . TOP or Gravity . START
x = 50
y = 200
}
windowManager ?. addView ( overlayButton , params )
}
private fun expandMenu ( ) {
if ( isMenuExpanded ) return
// Create the expanded menu
val menuContainer = LinearLayout ( this ) . apply {
orientation = LinearLayout . VERTICAL
setBackgroundColor ( 0xE0000000 . toInt ( ) ) // Semi-transparent black
setPadding ( 16 , 16 , 16 , 16 )
}
// Add menu buttons
val buttons = listOf (
Triple ( " 🔍 DETECT " , 0xFF4CAF50 . toInt ( ) ) { triggerManualDetection ( ) } ,
Triple ( " SHINY " , 0xFFFFD700 . toInt ( ) ) { YOLOOnnxDetector . setClassFilter ( " shiny_icon " ) ; triggerManualDetection ( ) } ,
Triple ( " POKEBALL " , 0xFFE91E63 . toInt ( ) ) { YOLOOnnxDetector . setClassFilter ( " ball_icon_cherishball " ) ; triggerManualDetection ( ) } ,
Triple ( " ALL " , 0xFF607D8B . toInt ( ) ) { YOLOOnnxDetector . setClassFilter ( null ) ; triggerManualDetection ( ) } ,
Triple ( " DEBUG " , 0xFFFF5722 . toInt ( ) ) { YOLOOnnxDetector . toggleShowAllConfidences ( ) ; triggerManualDetection ( ) }
)
buttons . forEach { ( text , color , action ) ->
val button = Button ( this ) . apply {
this . text = text
textSize = 12f
setBackgroundColor ( color )
setTextColor ( 0xFFFFFFFF . toInt ( ) )
layoutParams = LinearLayout . LayoutParams ( 160 , 60 ) . apply {
setMargins ( 0 , 0 , 0 , 8 )
}
setOnClickListener {
action ( )
collapseMenu ( )
}
}
menuContainer . addView ( button )
}
expandedMenu = menuContainer
val params = 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 ,
PixelFormat . TRANSLUCENT
) . apply {
gravity = Gravity . TOP or Gravity . START
x = 180 // Position next to the orb
y = 200
}
windowManager ?. addView ( expandedMenu , params )
isMenuExpanded = true
// Change orb appearance
( overlayButton as ? Button ) ?. apply {
text = " ✖ "
background . setTint ( 0xFFFF5722 . toInt ( ) ) // Orange-red
}
}
private fun collapseMenu ( ) {
if ( !is MenuExpanded ) return
expandedMenu ?. let { windowManager ?. removeView ( it ) }
expandedMenu = null
isMenuExpanded = false
// Reset orb appearance
( overlayButton as ? Button ) ?. apply {
text = " 🎯 "
background . setTint ( 0xFF4CAF50 . toInt ( ) ) // Green
}
}
private fun removeFloatingButton ( ) {
try {
// Collapse menu first if expanded
if ( isMenuExpanded ) {
collapseMenu ( )
}
overlayButton ?. let { button ->
windowManager ?. removeView ( button )
overlayButton = null
@ -1210,35 +1343,63 @@ class ScreenCaptureService : Service() {
}
}
private fun convertImageToMat ( image : Image ) : Mat ? {
return try {
val planes = image . planes
val buffer = planes [ 0 ] . buffer
val pixelStride = planes [ 0 ] . pixelStride
val rowStride = planes [ 0 ] . rowStride
val rowPadding = rowStride - pixelStride * screenWidth
// Create bitmap from image
val bitmap = Bitmap . createBitmap (
screenWidth + rowPadding / pixelStride ,
screenHeight ,
Bitmap . Config . ARGB_8888
)
bitmap . copyPixelsFromBuffer ( buffer )
// Convert bitmap to Mat
val mat = Mat ( )
Utils . bitmapToMat ( bitmap , mat )
// Convert from RGBA to RGB (YOLO expects RGB)
val rgbMat = Mat ( )
Imgproc . cvtColor ( mat , rgbMat , Imgproc . COLOR_RGBA2RGB )
// Clean up
mat . release ( )
bitmap . recycle ( )
rgbMat
} catch ( e : Exception ) {
Log . e ( TAG , " ❌ Error converting image to Mat " , e )
null
}
}
private fun triggerManualDetection ( ) {
Log . d ( TAG , " 🔍 Manual detection triggered! " )
Log . d ( TAG , " 🔍 Manual detection triggered via MVC ! " )
latestImage ?. let { image ->
try {
// Update main button to show processing (find the first button in the LinearLayout)
val mainButton = ( overlayButton as ? LinearLayout ) ?. getChildAt ( 0 ) as ? Button
mainButton ?. text = " ⏳ PROCESSING... "
mainButton ?. isEnabled = false
// Convert image to Mat for processing
val mat = convertImageToMat ( image )
// Process the image
processImage ( image )
if ( mat != null ) {
// Use controller to process detection (this will notify UI via callbacks)
detectionController . processDetection ( mat )
mat . release ( )
} else {
Log . e ( TAG , " ❌ Failed to convert image to Mat " )
}
// Close the image after processing to free the buffer
image . close ( )
latestImage = null
// Reset button after processing
handler . postDelayed ( {
val resetButton = ( overlayButton as ? LinearLayout ) ?. getChildAt ( 0 ) as ? Button
resetButton ?. text = " 🔍 DETECT "
resetButton ?. isEnabled = true
} , 2000 )
} catch ( e : Exception ) {
Log . e ( TAG , " ❌ Error in manual detection " , e )
val errorButton = ( overlayButton as ? LinearLayout ) ?. getChildAt ( 0 ) as ? Button
errorButton ?. text = " 🔍 DETECT "
errorButton ?. isEnabled = true
}
} ?: run {
Log . w ( TAG , " ⚠️ No image available for detection " )
@ -1248,7 +1409,8 @@ class ScreenCaptureService : Service() {
override fun onDestroy ( ) {
super . onDestroy ( )
hideDetectionOverlay ( )
removeFloatingButton ( )
floatingOrbUI ?. hide ( )
detectionController . clearUICallbacks ( )
yoloDetector ?. release ( )
ocrExecutor . shutdown ( )
stopScreenCapture ( )