Browse Source

feat: 悬浮窗(系统悬浮窗不能拖拽)

DoggyZhang 1 month ago
parent
commit
8656978fcb
20 changed files with 439 additions and 91 deletions
  1. 2 0
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/FloatViewFactory.kt
  2. 2 0
      app/src/main/java/com/adealink/weparty/commonui/widget/floatview/data/IFloatData.kt
  3. 10 0
      app/src/main/java/com/adealink/weparty/module/call/CallModule.kt
  4. 3 1
      app/src/main/java/com/adealink/weparty/module/call/ICallService.kt
  5. 2 4
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/hint/HintView.kt
  6. 1 3
      frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/hint/TimerView.kt
  7. 8 34
      module/call/src/main/java/com/adealink/weparty/call/CallActivity.kt
  8. 7 4
      module/call/src/main/java/com/adealink/weparty/call/CallServiceImpl.kt
  9. 6 2
      module/call/src/main/java/com/adealink/weparty/call/comp/CallTopComp.kt
  10. 0 1
      module/call/src/main/java/com/adealink/weparty/call/constant/Tags.kt
  11. 3 1
      module/call/src/main/java/com/adealink/weparty/call/datasource/local/CallLocalService.kt
  12. 0 14
      module/call/src/main/java/com/adealink/weparty/call/widget/BaseCallFragment.kt
  13. 10 0
      module/call/src/main/java/com/adealink/weparty/call/widget/float/calling/CallingFloatData.kt
  14. 133 0
      module/call/src/main/java/com/adealink/weparty/call/widget/float/calling/CallingFloatView.kt
  15. 87 0
      module/call/src/main/java/com/adealink/weparty/call/widget/float/calling/CallingView.kt
  16. 7 0
      module/call/src/main/java/com/adealink/weparty/call/widget/float/calling/ICalling.kt
  17. 90 27
      module/call/src/main/java/com/tencent/qcloud/tuikit/tuicallkit/manager/CallManager.kt
  18. BIN
      module/call/src/main/res/drawable-xhdpi/call_float_ic.png
  19. 66 0
      module/call/src/main/res/layout/call_float_call_view.xml
  20. 2 0
      module/call/src/main/res/values/dimens.xml

+ 2 - 0
app/src/main/java/com/adealink/weparty/commonui/widget/floatview/FloatViewFactory.kt

@@ -6,6 +6,7 @@ import com.adealink.weparty.commonui.widget.floatview.data.IFloatViewFactory
 import com.adealink.weparty.commonui.widget.floatview.view.BaseFloatView
 import com.adealink.weparty.debug.SysMemoryUsageFloatData
 import com.adealink.weparty.debug.SysMemoryUsageFloatView
+import com.adealink.weparty.module.call.CallModule
 
 class FloatViewFactory : IFloatViewFactory {
 
@@ -13,6 +14,7 @@ class FloatViewFactory : IFloatViewFactory {
         val floatView = when (data.windowType()) {
             FloatWindowType.NETWORK_DISCONNECT_TIP -> null//NetworkReconnectFloatView(data as NetworkReconnectFloatData)
             FloatWindowType.MEMORY_USAGE -> SysMemoryUsageFloatView(data as SysMemoryUsageFloatData)
+            FloatWindowType.CALL_1V1_CALLING -> CallModule.getCallingFloatView(data)
         }
         return floatView as? V
     }

+ 2 - 0
app/src/main/java/com/adealink/weparty/commonui/widget/floatview/data/IFloatData.kt

@@ -3,6 +3,8 @@ package com.adealink.weparty.commonui.widget.floatview.data
 enum class FloatWindowType(val type: String) {
     NETWORK_DISCONNECT_TIP("network_disconnect_tip"),
     MEMORY_USAGE("memory_usage"),
+
+    CALL_1V1_CALLING("call_1v1_calling"),
 }
 
 

+ 10 - 0
app/src/main/java/com/adealink/weparty/module/call/CallModule.kt

@@ -8,6 +8,8 @@ import com.adealink.frame.media.IMediaOperator
 import com.adealink.frame.media.MediaConflictConfig
 import com.adealink.frame.media.MediaInfo
 import com.adealink.weparty.R
+import com.adealink.weparty.commonui.widget.floatview.data.IFloatData
+import com.adealink.weparty.commonui.widget.floatview.view.BaseFloatView
 
 object CallModule : BaseDynamicModule<ICallService>(ICallService::class), ICallService {
 
@@ -27,6 +29,10 @@ object CallModule : BaseDynamicModule<ICallService>(ICallService::class), ICallS
             ) {
             }
 
+            override fun getCallingFloatView(data: IFloatData): BaseFloatView<out IFloatData>? {
+                return null
+            }
+
             override fun getService(): ICallService? {
                 return null
             }
@@ -91,6 +97,10 @@ object CallModule : BaseDynamicModule<ICallService>(ICallService::class), ICallS
         getService().call(uid, onSuccess, onFail)
     }
 
+    override fun getCallingFloatView(data: IFloatData): BaseFloatView<out IFloatData>? {
+        return getService().getCallingFloatView(data)
+    }
+
     override fun getMediaOperator(): IMediaOperator {
         return getService().getMediaOperator()
     }

+ 3 - 1
app/src/main/java/com/adealink/weparty/module/call/ICallService.kt

@@ -3,6 +3,8 @@ package com.adealink.weparty.module.call
 import com.adealink.frame.aab.IService
 import com.adealink.frame.media.IMediaOperatorGet
 import com.adealink.frame.startup.IAppStartUpTask
+import com.adealink.weparty.commonui.widget.floatview.data.IFloatData
+import com.adealink.weparty.commonui.widget.floatview.view.BaseFloatView
 
 interface ICallService : IService<ICallService>, IMediaOperatorGet, IAppStartUpTask {
 
@@ -14,7 +16,7 @@ interface ICallService : IService<ICallService>, IMediaOperatorGet, IAppStartUpT
 
 //    fun getIncomingFloatView(data: IFloatData): BaseFloatView<out IFloatData>?
 //
-//    fun getCallingFloatView(data: IFloatData): BaseFloatView<out IFloatData>?
+    fun getCallingFloatView(data: IFloatData): BaseFloatView<out IFloatData>?
 //
 //    fun getCallViewModel(owner: ViewModelStoreOwner): ICallViewModel?
 

+ 2 - 4
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/hint/HintView.kt

@@ -2,10 +2,8 @@ package io.trtc.tuikit.atomicx.callview.widget.hint
 
 import android.content.Context
 import android.util.AttributeSet
-import android.view.Gravity
 import android.view.View
 import androidx.appcompat.widget.AppCompatTextView
-import androidx.core.content.ContextCompat
 import io.trtc.tuikit.atomicx.R
 import io.trtc.tuikit.atomicxcore.api.call.CallMediaType
 import io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatus
@@ -73,8 +71,8 @@ class HintView @JvmOverloads constructor(
     }
 
     private fun initView() {
-        setTextColor(ContextCompat.getColor(context, R.color.callview_color_white))
-        gravity = Gravity.CENTER
+        //setTextColor(ContextCompat.getColor(context, R.color.callview_color_white))
+        //gravity = Gravity.CENTER
 
         val activeCall = CallStore.shared.observerState.activeCall.value
         val isGroupCall = activeCall.inviteeIds.size >= 2

+ 1 - 3
frame/atomic_x/src/main/java/io/trtc/tuikit/atomicx/callview/widget/hint/TimerView.kt

@@ -3,9 +3,7 @@ package io.trtc.tuikit.atomicx.callview.widget.hint
 import android.content.Context
 import android.util.AttributeSet
 import androidx.appcompat.widget.AppCompatTextView
-import androidx.core.content.ContextCompat
 import com.tencent.qcloud.tuicore.util.DateTimeUtil
-import io.trtc.tuikit.atomicx.R
 import io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatus
 import io.trtc.tuikit.atomicxcore.api.call.CallStore
 import kotlinx.coroutines.CoroutineScope
@@ -38,7 +36,7 @@ class TimerView @JvmOverloads constructor(
     }
 
     private fun initView() {
-        setTextColor(ContextCompat.getColor(context, R.color.callview_color_white))
+        //setTextColor(ContextCompat.getColor(context, R.color.callview_color_white))
         val duration = CallStore.shared.observerState.activeCall.value.duration
         updateDurationView(duration)
     }

+ 8 - 34
module/call/src/main/java/com/adealink/weparty/call/CallActivity.kt

@@ -3,7 +3,6 @@ package com.adealink.weparty.call
 import android.annotation.SuppressLint
 import android.content.pm.ActivityInfo
 import android.os.Build
-import android.os.Bundle
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.updateLayoutParams
 import com.adealink.frame.mvvm.view.viewBinding
@@ -21,7 +20,6 @@ import com.adealink.weparty.module.call.Call
 import com.qmuiteam.qmui.widget.util.QMUIStatusBarHelper
 import com.tencent.cloud.tuikit.engine.common.TUICommonDefine
 import com.tencent.qcloud.tuicore.permission.PermissionCallback
-import com.tencent.qcloud.tuicore.util.TUIBuild
 import com.tencent.qcloud.tuikit.tuicallkit.common.data.Constants
 import com.tencent.qcloud.tuikit.tuicallkit.common.metrics.KeyMetrics
 import com.tencent.qcloud.tuikit.tuicallkit.common.utils.DeviceUtils
@@ -54,11 +52,6 @@ class CallActivity : BaseActivity() {
 
     private var subscribeStateJob: Job? = null
 
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        activity = this
-    }
-
     @SuppressLint("CommitTransaction")
     override fun initViews() {
         super.initViews()
@@ -92,7 +85,9 @@ class CallActivity : BaseActivity() {
         super.initComponents()
         BgComp(this, binding.vBg).attach()
         CallUserInfoComp(this, binding.vUserInfo).attach()
-        CallTopComp(this, binding.idCallTopBar).attach()
+        CallTopComp(this, binding.idCallTopBar) {
+            finishCallActivity()
+        }.attach()
         CallBottomComp(this, binding.idCallBottomBar).attach()
     }
 
@@ -110,7 +105,7 @@ class CallActivity : BaseActivity() {
                     if (!isCaller(self.id)) {
                         CallManager.instance.reject(null)
                     }
-                    finishCallMainActivity()
+                    finishCallActivity()
                 }
             })
     }
@@ -118,15 +113,11 @@ class CallActivity : BaseActivity() {
     private fun inflateView() {
         val callStatus = CallStore.shared.observerState.selfInfo.value.status
         if (CallParticipantStatus.None == callStatus) {
-            finishCallMainActivity()
+            finishCallActivity()
             return
         }
         val callId = CallStore.shared.observerState.activeCall.value.callId
         KeyMetrics.countUV(KeyMetrics.EventId.WAKEUP, callId)
-//        setBackground()
-//        addCallView()
-//        addFloatButton()
-//        addInviteButton()
         FloatWindowManager.sharedInstance().dismiss()
         CallManager.instance.viewState.router.set(ViewState.ViewRouter.FullView)
     }
@@ -134,7 +125,7 @@ class CallActivity : BaseActivity() {
     private suspend fun observeSelfInfo() {
         CallStore.shared.observerState.selfInfo.collect { selfInfo ->
             if (selfInfo.status == CallParticipantStatus.None) {
-                finishCallMainActivity()
+                finishCallActivity()
                 return@collect
             }
         }
@@ -155,25 +146,8 @@ class CallActivity : BaseActivity() {
         }
     }
 
-    private fun finishCallMainActivity() {
-        //清理视图
-        //callView?.removeAllViews()
-        if (TUIBuild.getVersionInt() >= Build.VERSION_CODES.LOLLIPOP) {
-            finishAndRemoveTask()
-        } else {
-            finish()
-        }
-    }
-
-    companion object {
-        private var activity: CallActivity? = null
-        private const val TAG = "CallActivity"
-
-        fun finishActivity() {
-//            activity?.removeObserver()
-            activity?.finish()
-            activity = null
-        }
+    private fun finishCallActivity() {
+        finishAndRemoveTask()
     }
 
 }

+ 7 - 4
module/call/src/main/java/com/adealink/weparty/call/CallServiceImpl.kt

@@ -10,7 +10,10 @@ import com.adealink.frame.spi.RegisterService
 import com.adealink.frame.util.AppUtil
 import com.adealink.weparty.call.datasource.local.CallLocalService
 import com.adealink.weparty.call.manager.callManager
+import com.adealink.weparty.call.widget.float.calling.CallingFloatData
+import com.adealink.weparty.call.widget.float.calling.CallingFloatView
 import com.adealink.weparty.commonui.BaseActivity
+import com.adealink.weparty.commonui.widget.floatview.data.IFloatData
 import com.adealink.weparty.module.call.ICallService
 import com.tencent.qcloud.tuikit.tuicallkit.manager.bridge.CallServiceInitializer
 import io.trtc.tuikit.atomicxcore.api.CompletionHandler
@@ -49,10 +52,10 @@ class CallServiceImpl : ICallService {
 //    override fun getIncomingFloatView(data: IFloatData): InComingFloatView {
 //        return InComingFloatView(data as InComingFloatData)
 //    }
-//
-//    override fun getCallingFloatView(data: IFloatData): CallingFloatView {
-//        return CallingFloatView(data as CallingFloatData)
-//    }
+
+    override fun getCallingFloatView(data: IFloatData): CallingFloatView {
+        return CallingFloatView(data as CallingFloatData)
+    }
 
 //    override fun getCallViewModel(owner: ViewModelStoreOwner): ICallViewModel {
 //        return ViewModelProvider(owner, CallViewModelFactory())[CallViewModel::class.java]

+ 6 - 2
module/call/src/main/java/com/adealink/weparty/call/comp/CallTopComp.kt

@@ -4,16 +4,20 @@ import androidx.lifecycle.LifecycleOwner
 import com.adealink.frame.util.onClick
 import com.adealink.weparty.call.databinding.LayoutCallTopBarBinding
 import com.adealink.weparty.commonui.BaseViewComponent
+import com.tencent.qcloud.tuikit.tuicallkit.manager.CallManager
 
 class CallTopComp(
     lifecycleOwner: LifecycleOwner,
-    private val binding: LayoutCallTopBarBinding
+    private val binding: LayoutCallTopBarBinding,
+    private val finishActivity: () -> Unit
 ) : BaseViewComponent(lifecycleOwner) {
 
     override fun initViews() {
         super.initViews()
         binding.btnMini.onClick {
-
+            CallManager.instance.showCallingFloatWindow {
+                finishActivity.invoke()
+            }
         }
     }
 

+ 0 - 1
module/call/src/main/java/com/adealink/weparty/call/constant/Tags.kt

@@ -20,7 +20,6 @@ const val TAG_CALL_AUDIO_RECORD = "${TAG_CALL}_audio_record"
 
 const val TAG_CALL_TUI_CALL_KIT_SERVICE = "${TAG_CALL}_tui_call_kit_service"
 
-const val TAG_CALL_VIDEO_VIEW_FACTORY = "${TAG_CALL}_video_view_factory"
 
 const val TAG_CALL_PING = "${TAG_CALL}_ping"
 

+ 3 - 1
module/call/src/main/java/com/adealink/weparty/call/datasource/local/CallLocalService.kt

@@ -10,10 +10,12 @@ object CallLocalService : TypeDelegationPrefs(
         AppUtil.appContext.getSharedPreferences("pref_call", Context.MODE_PRIVATE)
     },
     userId = {
-        AccountModule.uid.toString()
+        AccountModule.uid
     }
 ) {
 
     var mediaConflictRemind: Boolean by PrefUserKey("key_call_media_conflict_remind", true)
 
+    var requestOverPlayPermission: Boolean by PrefUserKey("key_request_overlay_permission", true)
+
 }

+ 0 - 14
module/call/src/main/java/com/adealink/weparty/call/widget/BaseCallFragment.kt

@@ -1,14 +0,0 @@
-package com.adealink.weparty.call.widget
-
-import androidx.annotation.LayoutRes
-import com.adealink.weparty.commonui.BaseFragment
-
-abstract class BaseCallFragment(@LayoutRes contentLayoutId: Int) : BaseFragment(contentLayoutId) {
-
-    override fun onDestroyView() {
-        super.onDestroyView()
-        clear()
-    }
-
-    open fun clear() {}
-}

+ 10 - 0
module/call/src/main/java/com/adealink/weparty/call/widget/float/calling/CallingFloatData.kt

@@ -0,0 +1,10 @@
+package com.adealink.weparty.call.widget.float.calling
+
+import com.adealink.weparty.commonui.widget.floatview.data.FloatWindowType
+import com.adealink.weparty.commonui.widget.floatview.data.IWindowFloatData
+
+class CallingFloatData(private val windowMode: Int) :
+    IWindowFloatData {
+    override fun windowType(): FloatWindowType = FloatWindowType.CALL_1V1_CALLING
+    override fun windowMode() = windowMode
+}

+ 133 - 0
module/call/src/main/java/com/adealink/weparty/call/widget/float/calling/CallingFloatView.kt

@@ -0,0 +1,133 @@
+package com.adealink.weparty.call.widget.float.calling
+
+import android.content.Intent
+import android.view.View
+import android.view.WindowManager
+import com.adealink.frame.log.Log
+import com.adealink.frame.router.Router
+import com.adealink.frame.util.AppUtil
+import com.adealink.frame.util.DisplayUtil
+import com.adealink.weparty.call.constant.TAG_CALL_FLOAT_VIEW
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.widget.floatview.WindowManagerProxy
+import com.adealink.weparty.commonui.widget.floatview.data.FloatWindowType
+import com.adealink.weparty.commonui.widget.floatview.view.BaseDragFloatView
+import com.adealink.weparty.module.call.Call
+import com.tencent.qcloud.tuikit.tuicallkit.manager.CallManager
+import io.trtc.tuikit.atomicx.callview.core.common.utils.CallUtils
+import io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatus
+import io.trtc.tuikit.atomicxcore.api.call.CallStore
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+class CallingFloatView(floatData: CallingFloatData) :
+    BaseDragFloatView(floatData),
+    ICalling {
+
+    companion object {
+        @JvmStatic
+        fun dismiss() {
+            WindowManagerProxy.getWindowManager()
+                .removeFloatViewByType(FloatWindowType.CALL_1V1_CALLING, "")
+        }
+    }
+
+    private var subscribeStateJob: Job? = null
+    private var callStatus = CallParticipantStatus.None
+
+    private var callingView: CallingView =
+        CallingView(context)
+
+    init {
+        setContentView(callingView)
+        callingView.setOnClickListener {
+            cancelCallingView()
+            if (CallUtils.getSelfCallStatus() != CallParticipantStatus.None) {
+                AppUtil.currentActivity?.let {
+                    Router.build(context, Call.Call.PATH)
+                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                        .start()
+                }
+            }
+        }
+    }
+
+    override fun showCallingView() {
+        if (WindowManagerProxy.getWindowManager()
+                .findFloatViewByType(FloatWindowType.CALL_1V1_CALLING) != null
+        ) {
+            Log.d(
+                TAG_CALL_FLOAT_VIEW,
+                "CallingFloatView.showCallingView return, for floatView already added."
+            )
+            return
+        }
+        Log.d(TAG_CALL_FLOAT_VIEW, "CallingFloatView.showCallingView")
+        CallManager.instance.viewState.enterPipMode.set(true)
+        addObserver()
+        WindowManagerProxy.getWindowManager().addView(this)
+    }
+
+    private fun addObserver() {
+        subscribeStateJob?.cancel()
+        subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
+            launch { observeCallStatus() }
+        }
+    }
+
+    private suspend fun observeCallStatus() {
+        CallStore.shared.observerState.selfInfo.collect { selfInfo ->
+            if (callStatus != selfInfo.status) {
+                callStatus = selfInfo.status
+                onCallStatusChanged()
+            }
+        }
+    }
+
+    private fun removeObserver() {
+        subscribeStateJob?.cancel()
+    }
+
+    private fun onCallStatusChanged() {
+        if (callStatus == CallParticipantStatus.None) {
+            cancelCallingView()
+        }
+    }
+
+    override fun getLayoutParamWidth(): Int {
+        return 80.dp()
+    }
+
+    override fun getLayoutParamHeight(): Int {
+        return WindowManager.LayoutParams.WRAP_CONTENT
+    }
+
+    override fun getClickableViews(): List<View> {
+        return listOf(callingView)
+    }
+
+    override fun getLayoutParamX(): Int {
+        return DisplayUtil.getScreenWidth() - getLayoutParamWidth() - 20.dp()
+    }
+
+    override fun getLayoutParamY(): Int {
+        return 100.dp()
+    }
+
+    override fun applySnapToEdge(): Boolean {
+        return true
+    }
+
+    override fun cancelCallingView() {
+        Log.d(TAG_CALL_FLOAT_VIEW, "CallingFloatView.cancelCallingView")
+        CallManager.instance.viewState.enterPipMode.set(false)
+        if (isAttachedToWindow) {
+            WindowManagerProxy.getWindowManager()
+                .removeView(this, reason = "CallingFloatView.cancelCallingView")
+        }
+        removeObserver()
+    }
+
+}

+ 87 - 0
module/call/src/main/java/com/adealink/weparty/call/widget/float/calling/CallingView.kt

@@ -0,0 +1,87 @@
+package com.adealink.weparty.call.widget.float.calling
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.adealink.weparty.call.R
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import io.trtc.tuikit.atomicxcore.api.call.CallParticipantStatus
+import io.trtc.tuikit.atomicxcore.api.call.CallStore
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * 通话浮动视图
+ */
+class CallingView @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+    private var subscribeStateJob: Job? = null
+    private var callStatus = CallParticipantStatus.None
+
+    private var hintView: View? = null
+    private var timerView: View? = null
+
+    init {
+        initView(context)
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        registerObserver()
+    }
+
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+        subscribeStateJob?.cancel()
+    }
+
+    private fun registerObserver() {
+        subscribeStateJob = CoroutineScope(Dispatchers.Main).launch {
+            launch { observeCallStatus() }
+        }
+    }
+
+    private suspend fun observeCallStatus() {
+        CallStore.shared.observerState.selfInfo.collect { selfInfo ->
+            if (callStatus != selfInfo.status) {
+                callStatus = selfInfo.status
+                onCallStatusChanged()
+            }
+        }
+    }
+
+    private fun initView(context: Context) {
+//        val binding = CallFloatCallViewBinding.inflate(LayoutInflater.from(context), this, true)
+        LayoutInflater.from(context).inflate(R.layout.call_float_call_view, this)
+        hintView = findViewById(R.id.tv_hint)
+        timerView = findViewById(R.id.tv_timer)
+    }
+
+    private fun onCallStatusChanged() {
+        when (callStatus) {
+            CallParticipantStatus.None -> {
+
+            }
+
+            CallParticipantStatus.Waiting -> {
+                hintView?.show()
+                timerView?.gone()
+            }
+
+            CallParticipantStatus.Accept -> {
+                hintView?.gone()
+                timerView?.show()
+            }
+        }
+    }
+
+}

+ 7 - 0
module/call/src/main/java/com/adealink/weparty/call/widget/float/calling/ICalling.kt

@@ -0,0 +1,7 @@
+package com.adealink.weparty.call.widget.float.calling
+
+interface ICalling {
+    fun showCallingView()
+
+    fun cancelCallingView()
+}

+ 90 - 27
module/call/src/main/java/com/tencent/qcloud/tuikit/tuicallkit/manager/CallManager.kt

@@ -1,12 +1,22 @@
 package com.tencent.qcloud.tuikit.tuicallkit.manager
 
 import android.content.Context
+import com.adealink.frame.log.Log
 import com.adealink.weparty.call.R
+import com.adealink.weparty.call.constant.TAG_CALL_ENGINE
+import com.adealink.weparty.call.datasource.local.CallLocalService
+import com.adealink.weparty.call.util.PermissionRequest
+import com.adealink.weparty.call.widget.float.calling.CallingFloatData
+import com.adealink.weparty.call.widget.float.calling.CallingFloatView
+import com.adealink.weparty.commonui.widget.floatview.data.MODE_APPLICATION
+import com.adealink.weparty.commonui.widget.floatview.data.MODE_SYSTEM
 import com.tencent.cloud.tuikit.engine.call.TUICallDefine
 import com.tencent.cloud.tuikit.engine.call.TUICallEngine
 import com.tencent.cloud.tuikit.engine.common.TUICommonDefine
 import com.tencent.imsdk.BaseConstants
 import com.tencent.qcloud.tuicore.TUIConfig
+import com.tencent.qcloud.tuicore.permission.PermissionCallback
+import com.tencent.qcloud.tuicore.permission.PermissionRequester
 import com.tencent.qcloud.tuicore.util.ErrorMessageConverter
 import com.tencent.qcloud.tuicore.util.SPUtils
 import com.tencent.qcloud.tuicore.util.ToastUtil
@@ -43,13 +53,19 @@ class CallManager private constructor(context: Context) {
         Logger.i(TAG, "calls, userIdList: $userIdList, mediaType: $mediaType, params: $params")
         if (userIdList.isEmpty()) {
             Logger.e(TAG, "calls failed, userIdList is empty")
-            completion?.onFailure(TUICallDefine.ERROR_PARAM_INVALID, "calls failed, userIdList is empty")
+            completion?.onFailure(
+                TUICallDefine.ERROR_PARAM_INVALID,
+                "calls failed, userIdList is empty"
+            )
             return
         }
         if (userIdList.size >= Constants.MAX_USER) {
             ToastUtil.toastLongMessage(context.getString(R.string.callkit_user_exceed_limit))
             Logger.e(TAG, "calls failed, exceeding max user number: 9")
-            completion?.onFailure(TUICallDefine.ERROR_PARAM_INVALID, "calls failed, exceeding max user number")
+            completion?.onFailure(
+                TUICallDefine.ERROR_PARAM_INVALID,
+                "calls failed, exceeding max user number"
+            )
             return
         }
 
@@ -150,40 +166,44 @@ class CallManager private constructor(context: Context) {
 
     fun enableMultiDeviceAbility(enable: Boolean, completion: CompletionHandler?) {
         Logger.i(TAG, "enableMultiDeviceAbility, enable: $enable")
-        TUICallEngine.createInstance(context).enableMultiDeviceAbility(enable, object : TUICommonDefine.Callback {
-            override fun onSuccess() {
-                completion?.onSuccess()
-            }
+        TUICallEngine.createInstance(context)
+            .enableMultiDeviceAbility(enable, object : TUICommonDefine.Callback {
+                override fun onSuccess() {
+                    completion?.onSuccess()
+                }
 
-            override fun onError(errCode: Int, errMsg: String?) {
-                completion?.onFailure(errCode, errMsg ?: "")
-            }
+                override fun onError(errCode: Int, errMsg: String?) {
+                    completion?.onFailure(errCode, errMsg ?: "")
+                }
 
-        })
+            })
     }
 
     fun setSelfInfo(nickname: String?, avatar: String?, completion: CompletionHandler?) {
         Logger.i(TAG, "setSelfInfo, nickname: $nickname, avatar: $avatar")
-        TUICallEngine.createInstance(context).setSelfInfo(nickname, avatar, object : TUICommonDefine.Callback {
-            override fun onSuccess() {
-                completion?.onSuccess()
-            }
+        TUICallEngine.createInstance(context)
+            .setSelfInfo(nickname, avatar, object : TUICommonDefine.Callback {
+                override fun onSuccess() {
+                    completion?.onSuccess()
+                }
 
-            override fun onError(errCode: Int, errMsg: String?) {
-                completion?.onFailure(errCode, errMsg ?: "")
-            }
-        })
+                override fun onError(errCode: Int, errMsg: String?) {
+                    completion?.onFailure(errCode, errMsg ?: "")
+                }
+            })
     }
 
     fun setCallingBell(filePath: String?) {
         Logger.i(TAG, "setCallingBell, filePath: $filePath")
-        SPUtils.getInstance(CallingBellFeature.PROFILE_TUICALLKIT).put(CallingBellFeature.PROFILE_CALL_BELL, filePath)
+        SPUtils.getInstance(CallingBellFeature.PROFILE_TUICALLKIT)
+            .put(CallingBellFeature.PROFILE_CALL_BELL, filePath)
     }
 
     fun enableMuteMode(enable: Boolean) {
         Logger.i(TAG, "enableMuteMode, enable: $enable")
         GlobalState.instance.enableMuteMode = enable
-        SPUtils.getInstance(CallingBellFeature.PROFILE_TUICALLKIT).put(CallingBellFeature.PROFILE_MUTE_MODE, enable)
+        SPUtils.getInstance(CallingBellFeature.PROFILE_TUICALLKIT)
+            .put(CallingBellFeature.PROFILE_MUTE_MODE, enable)
     }
 
     fun enableFloatWindow(enable: Boolean) {
@@ -212,7 +232,8 @@ class CallManager private constructor(context: Context) {
 
         if (orientation == Constants.Orientation.LandScape.ordinal) {
             val videoEncoderParams = TUICommonDefine.VideoEncoderParams()
-            videoEncoderParams.resolutionMode = TUICommonDefine.VideoEncoderParams.ResolutionMode.Landscape
+            videoEncoderParams.resolutionMode =
+                TUICommonDefine.VideoEncoderParams.ResolutionMode.Landscape
             TUICallEngine.createInstance(context).setVideoEncoderParams(videoEncoderParams, null)
         }
     }
@@ -280,13 +301,19 @@ class CallManager private constructor(context: Context) {
 
     private fun getCommonErrorMap(): Map<Int, String> {
         val map = HashMap<Int, String>()
-        map[TUICallDefine.ERROR_PACKAGE_NOT_PURCHASED] = context.getString(R.string.callkit_package_not_purchased)
-        map[TUICallDefine.ERROR_PACKAGE_NOT_SUPPORTED] = context.getString(R.string.callkit_package_not_support)
+        map[TUICallDefine.ERROR_PACKAGE_NOT_PURCHASED] =
+            context.getString(R.string.callkit_package_not_purchased)
+        map[TUICallDefine.ERROR_PACKAGE_NOT_SUPPORTED] =
+            context.getString(R.string.callkit_package_not_support)
         map[TUICallDefine.ERROR_INIT_FAIL] = context.getString(R.string.callkit_error_invalid_login)
-        map[TUICallDefine.ERROR_PARAM_INVALID] = context.getString(R.string.callkit_error_parameter_invalid)
-        map[TUICallDefine.ERROR_REQUEST_REFUSED] = context.getString(R.string.callkit_error_request_refused)
-        map[TUICallDefine.ERROR_REQUEST_REPEATED] = context.getString(R.string.callkit_error_request_repeated)
-        map[TUICallDefine.ERROR_SCENE_NOT_SUPPORTED] = context.getString(R.string.callkit_error_scene_not_support)
+        map[TUICallDefine.ERROR_PARAM_INVALID] =
+            context.getString(R.string.callkit_error_parameter_invalid)
+        map[TUICallDefine.ERROR_REQUEST_REFUSED] =
+            context.getString(R.string.callkit_error_request_refused)
+        map[TUICallDefine.ERROR_REQUEST_REPEATED] =
+            context.getString(R.string.callkit_error_request_repeated)
+        map[TUICallDefine.ERROR_SCENE_NOT_SUPPORTED] =
+            context.getString(R.string.callkit_error_scene_not_support)
         return map
     }
 
@@ -337,6 +364,42 @@ class CallManager private constructor(context: Context) {
         }
     }
 
+    fun showCallingFloatWindow(finishActivity: (() -> Unit)? = null) {
+        Log.d(TAG_CALL_ENGINE, "showCallingFloatWindow")
+        if (PermissionRequester.newInstance(
+                PermissionRequester.FLOAT_PERMISSION,
+                PermissionRequester.BG_START_PERMISSION
+            ).has()
+        ) {
+            CallingFloatView(CallingFloatData(MODE_SYSTEM)).showCallingView()
+            finishActivity?.invoke()
+            return
+        }
+        if (!CallLocalService.requestOverPlayPermission) {
+            CallingFloatView(CallingFloatData(MODE_APPLICATION)).showCallingView()
+            finishActivity?.invoke()
+            return
+        }
+        CallLocalService.requestOverPlayPermission = false
+        PermissionRequest.requestFloatPermission(context, object : PermissionCallback() {
+            override fun onGranted() {
+                CallingFloatView(CallingFloatData(MODE_SYSTEM)).showCallingView()
+                finishActivity?.invoke()
+            }
+
+            override fun onDenied() {
+                super.onDenied()
+                CallingFloatView(CallingFloatData(MODE_APPLICATION)).showCallingView()
+                finishActivity?.invoke()
+            }
+        })
+    }
+
+    fun dismissCallingFloatWindow() {
+        Log.d(TAG_CALL_ENGINE, "dismissCallingFloatWindow")
+        CallingFloatView.dismiss()
+    }
+
     companion object {
         private const val TAG = "CallManager"
         val instance: CallManager = CallManager(TUIConfig.getAppContext())

BIN
module/call/src/main/res/drawable-xhdpi/call_float_ic.png


+ 66 - 0
module/call/src/main/res/layout/call_float_call_view.xml

@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.adealink.weparty.commonui.widget.RoundShadowLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="@dimen/call_calling_float_view_width"
+        android:layout_height="@dimen/call_calling_float_view_width"
+        android:background="@color/white">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_icon"
+            android:layout_width="26dp"
+            android:layout_height="26dp"
+            android:layout_marginTop="8dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/call_float_ic" />
+
+        <io.trtc.tuikit.atomicx.callview.widget.hint.HintView
+            android:id="@+id/tv_hint"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="8dp"
+            android:ellipsize="end"
+            android:includeFontPadding="false"
+            android:singleLine="true"
+            android:textColor="@color/color_FF3FBFBD"
+            android:textSize="12sp"
+            android:visibility="gone"
+            app:fontFamily="@font/poppins_semibold"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/iv_icon"
+            tools:text="00:30"
+            tools:visibility="visible" />
+
+        <io.trtc.tuikit.atomicx.callview.widget.hint.TimerView
+            android:id="@+id/tv_timer"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="8dp"
+            android:ellipsize="end"
+            android:includeFontPadding="false"
+            android:singleLine="true"
+            android:textColor="@color/color_FF3FBFBD"
+            android:textSize="12sp"
+            android:visibility="gone"
+            app:fontFamily="@font/poppins_semibold"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/iv_icon"
+            tools:text="00:30"
+            tools:visibility="visible" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.adealink.weparty.commonui.widget.RoundShadowLayout>
+

+ 2 - 0
module/call/src/main/res/values/dimens.xml

@@ -5,4 +5,6 @@
     <dimen name="callkit_text_size_hint">12sp</dimen>
     <dimen name="callkit_video_small_view_width">120dp</dimen>
     <dimen name="callkit_video_small_view_height">180dp</dimen>
+
+    <dimen name="call_calling_float_view_width">67dp</dimen>
 </resources>