|
|
|
|
@ -6,6 +6,7 @@ import android.app.Service
|
|
|
|
|
import android.content.Context |
|
|
|
|
import android.content.Intent |
|
|
|
|
import android.graphics.PixelFormat |
|
|
|
|
import android.graphics.drawable.GradientDrawable |
|
|
|
|
import android.os.Build |
|
|
|
|
import android.os.IBinder |
|
|
|
|
import android.provider.Settings |
|
|
|
|
@ -13,10 +14,13 @@ import android.view.Gravity
|
|
|
|
|
import android.view.LayoutInflater |
|
|
|
|
import android.view.MotionEvent |
|
|
|
|
import android.view.View |
|
|
|
|
import android.view.ViewConfiguration |
|
|
|
|
import android.view.WindowManager |
|
|
|
|
import android.widget.TextView |
|
|
|
|
import androidx.core.app.NotificationCompat |
|
|
|
|
import kotlin.math.abs |
|
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow |
|
|
|
|
import kotlinx.coroutines.flow.StateFlow |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Оверлей PTT поверх других приложений: плавающая круглая кнопка, drag-and-drop. |
|
|
|
|
@ -29,6 +33,12 @@ class RadioOverlayService : Service() {
|
|
|
|
|
companion object { |
|
|
|
|
const val CHANNEL_ID = "radio_overlay" |
|
|
|
|
const val NOTIF_ID = 1002 |
|
|
|
|
const val COLOR_IDLE = 0xFF81C784.toInt() |
|
|
|
|
const val COLOR_TALKING = 0xFFE53935.toInt() |
|
|
|
|
const val DRAG_LOCK_MS = 300L |
|
|
|
|
|
|
|
|
|
private val _isShown = MutableStateFlow(false) |
|
|
|
|
val isShown: StateFlow<Boolean> = _isShown |
|
|
|
|
|
|
|
|
|
fun canDrawOverlays(ctx: Context): Boolean = |
|
|
|
|
Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.canDrawOverlays(ctx) |
|
|
|
|
@ -89,8 +99,12 @@ class RadioOverlayService : Service() {
|
|
|
|
|
text = "\uD83C\uDF99" |
|
|
|
|
textSize = 30f |
|
|
|
|
gravity = android.view.Gravity.CENTER |
|
|
|
|
setBackgroundColor(0xFFE74C3C.toInt()) |
|
|
|
|
background = GradientDrawable().apply { |
|
|
|
|
shape = GradientDrawable.OVAL |
|
|
|
|
setColor(COLOR_IDLE) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
val touchSlop = (ViewConfiguration.get(this).scaledTouchSlop * 2.5f).toInt() |
|
|
|
|
val lp = WindowManager.LayoutParams( |
|
|
|
|
WindowManager.LayoutParams.WRAP_CONTENT, |
|
|
|
|
WindowManager.LayoutParams.WRAP_CONTENT, |
|
|
|
|
@ -109,6 +123,7 @@ class RadioOverlayService : Service() {
|
|
|
|
|
private var startX = 0; private var startY = 0 |
|
|
|
|
private var touchX = 0f; private var touchY = 0f |
|
|
|
|
private var dragging = false |
|
|
|
|
private var dragAllowed = true |
|
|
|
|
|
|
|
|
|
override fun onTouch(view: View, event: MotionEvent): Boolean { |
|
|
|
|
when (event.action) { |
|
|
|
|
@ -118,16 +133,25 @@ class RadioOverlayService : Service() {
|
|
|
|
|
touchX = event.rawX |
|
|
|
|
touchY = event.rawY |
|
|
|
|
dragging = false |
|
|
|
|
dragAllowed = true |
|
|
|
|
beginTalk() |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
MotionEvent.ACTION_MOVE -> { |
|
|
|
|
if (abs(event.rawX - touchX) > 8 || abs(event.rawY - touchY) > 8) { |
|
|
|
|
dragging = true |
|
|
|
|
endTalk() |
|
|
|
|
if (dragging) { |
|
|
|
|
lp.x = startX + (event.rawX - touchX).toInt() |
|
|
|
|
lp.y = startY + (event.rawY - touchY).toInt() |
|
|
|
|
wm.updateViewLayout(v, lp) |
|
|
|
|
} else if (dragAllowed) { |
|
|
|
|
if (event.eventTime - event.downTime > DRAG_LOCK_MS) { |
|
|
|
|
dragAllowed = false |
|
|
|
|
} else if (abs(event.rawX - touchX) > touchSlop || abs(event.rawY - touchY) > touchSlop) { |
|
|
|
|
dragging = true |
|
|
|
|
endTalk() |
|
|
|
|
lp.x = startX + (event.rawX - touchX).toInt() |
|
|
|
|
lp.y = startY + (event.rawY - touchY).toInt() |
|
|
|
|
wm.updateViewLayout(v, lp) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
@ -142,27 +166,37 @@ class RadioOverlayService : Service() {
|
|
|
|
|
wm.addView(v, lp) |
|
|
|
|
overlayView = v |
|
|
|
|
params = lp |
|
|
|
|
_isShown.value = true |
|
|
|
|
LogManager.addLog("INFO", "RadioOverlay", "overlay shown") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun beginTalk() { |
|
|
|
|
if (talking) return |
|
|
|
|
talking = true |
|
|
|
|
updateButtonColor() |
|
|
|
|
RadioAudioEngine.instance().talkBegin() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun endTalk() { |
|
|
|
|
if (!talking) return |
|
|
|
|
talking = false |
|
|
|
|
updateButtonColor() |
|
|
|
|
RadioAudioEngine.instance().talkEnd() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun updateButtonColor() { |
|
|
|
|
(overlayView?.background as? GradientDrawable)?.setColor( |
|
|
|
|
if (talking) COLOR_TALKING else COLOR_IDLE |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun dp(v: Int): Int = (v * resources.displayMetrics.density).toInt() |
|
|
|
|
|
|
|
|
|
override fun onDestroy() { |
|
|
|
|
endTalk() |
|
|
|
|
overlayView?.let { windowManager?.removeView(it) } |
|
|
|
|
overlayView = null |
|
|
|
|
_isShown.value = false |
|
|
|
|
LogManager.addLog("INFO", "RadioOverlay", "overlay destroyed") |
|
|
|
|
super.onDestroy() |
|
|
|
|
} |
|
|
|
|
|