[Chat]车聊聊架构升级
@@ -57,9 +57,6 @@ mogoservice : "com.mogo.service:mogo-service:${MOGO_COMMONS_VER
|
||||
mogoserviceapi : "com.mogo.service:mogo-service-api:${MOGO_COMMONS_VERSION}",
|
||||
moduleapps : "com.mogo.module:module-apps:${MOGO_COMMONS_VERSION}",
|
||||
moduleextensions : "com.mogo.module:module-extensions:${MOGO_COMMONS_VERSION}",
|
||||
chat : "com.mogo.module.carchatout:module-chat:${MOGO_COMMONS_VERSION}",
|
||||
callchat : "com.mogo.module.carchatout:module-carchatting:${MOGO_COMMONS_VERSION}",
|
||||
callchatprovider : "com.mogo.module.carchatout:module-carchatting-provider:${MOGO_COMMONS_VERSION}",
|
||||
|
||||
// V2X
|
||||
moduleV2x : "com.mogo.module:module-v2x:${MOGO_COMMONS_VERSION}",
|
||||
|
||||
@@ -220,7 +220,7 @@ repositories {
|
||||
}
|
||||
|
||||
aspectjx {
|
||||
include "com.mogo.chat"
|
||||
include "com.mogo.eagle.core.function.chat"
|
||||
}
|
||||
|
||||
|
||||
|
||||
17
build.gradle
@@ -115,3 +115,20 @@ afterEvaluate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects.each {
|
||||
Class<?> kotlinCompile = null
|
||||
try {
|
||||
kotlinCompile = Class.forName("org.jetbrains.kotlin.gradle.tasks.KotlinCompile")
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace()
|
||||
}
|
||||
if (kotlinCompile != null) {
|
||||
it.tasks.withType(kotlinCompile).configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,7 @@ ext {
|
||||
|
||||
// androidx-lifecycle-process
|
||||
androidxlifecycleprocess : "androidx.lifecycle:lifecycle-process:2.4.0",
|
||||
androidxlifecycleservice : "androidx.lifecycle:lifecycle-service:2.4.0",
|
||||
// rxjava2 with room
|
||||
roomRxjava : 'androidx.room:room-rxjava2:2.2.3',
|
||||
circleimageview : "de.hdodenhof:circleimageview:3.0.1",
|
||||
|
||||
1
core/function-impl/mogo-core-function-chat/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
82
core/function-impl/mogo-core-function-chat/build.gradle
Normal file
@@ -0,0 +1,82 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-android-extensions'
|
||||
id 'kotlin-kapt'
|
||||
id 'com.alibaba.arouter'
|
||||
}
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
versionCode Integer.valueOf(VERSION_CODE)
|
||||
versionName getValueFromRootProperties("${project.name.replace("-", "_").toUpperCase()}_VERSION")
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles 'consumer-rules.pro'
|
||||
//ARouter apt 参数
|
||||
kapt {
|
||||
useBuildCache = false
|
||||
arguments {
|
||||
arg("AROUTER_MODULE_NAME", project.getName())
|
||||
}
|
||||
}
|
||||
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "arm64-v8a"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation rootProject.ext.dependencies.androidxccorektx
|
||||
implementation rootProject.ext.dependencies.androidxappcompat
|
||||
implementation rootProject.ext.dependencies.arouter
|
||||
implementation rootProject.ext.dependencies.rxandroid
|
||||
compileOnly rootProject.ext.dependencies.aspectj
|
||||
kapt rootProject.ext.dependencies.aroutercompiler
|
||||
implementation rootProject.ext.dependencies.mogowebsocket
|
||||
implementation rootProject.ext.dependencies.circleimageview
|
||||
implementation rootProject.ext.dependencies.androidxconstraintlayout
|
||||
implementation rootProject.ext.dependencies.androidxrecyclerview
|
||||
|
||||
if (Boolean.valueOf(USE_MAVEN_PACKAGE)) {
|
||||
implementation rootProject.ext.dependencies.modulecommon
|
||||
implementation rootProject.ext.dependencies.moduleservice
|
||||
implementation rootProject.ext.dependencies.mogo_core_data
|
||||
implementation rootProject.ext.dependencies.mogo_core_utils
|
||||
implementation rootProject.ext.dependencies.mogo_core_function_api
|
||||
implementation rootProject.ext.dependencies.mogo_core_function_call
|
||||
implementation rootProject.ext.dependencies.mogo_core_res
|
||||
} else {
|
||||
implementation project(':modules:mogo-module-common')
|
||||
implementation project(':modules:mogo-module-service')
|
||||
implementation project(':core:mogo-core-data')
|
||||
implementation project(':core:mogo-core-utils')
|
||||
implementation project(':core:mogo-core-function-api')
|
||||
implementation project(':core:mogo-core-function-call')
|
||||
implementation project(':core:mogo-core-res')
|
||||
}
|
||||
}
|
||||
|
||||
apply from: new File(rootProject.rootDir, "gradle/upload.gradle").toString()
|
||||
@@ -0,0 +1,3 @@
|
||||
GROUP=com.mogo.eagle.core.function.impl
|
||||
POM_ARTIFACT_ID=v2x
|
||||
VERSION_CODE=1
|
||||
BIN
core/function-impl/mogo-core-function-chat/libs/gmesdk.jar
Normal file
21
core/function-impl/mogo-core-function-chat/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.mogo.eagle.core.function.chat">
|
||||
</manifest>
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.mogo.eagle.core.function.chat
|
||||
|
||||
import android.content.Context
|
||||
import com.alibaba.android.arouter.facade.annotation.Route
|
||||
import com.mogo.eagle.core.function.api.chat.IMoGoChatProvider
|
||||
import com.mogo.eagle.core.function.api.chat.biz.ChatConsts
|
||||
import com.mogo.eagle.core.function.api.chat.biz.IMoGoChatFacade
|
||||
import com.mogo.eagle.core.function.chat.facade.MoGoChatFacade
|
||||
import com.mogo.eagle.core.function.chat.facade.ui.CallChatWindowManager
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
||||
@Route(path = ChatConsts.CHAT_PROVIDER_PATH)
|
||||
class MoGoChatProvider: IMoGoChatProvider {
|
||||
|
||||
private val facade by lazy {
|
||||
MoGoChatFacade
|
||||
}
|
||||
|
||||
private val chatWindowManager by lazy {
|
||||
CallChatWindowManager()
|
||||
}
|
||||
|
||||
override val functionName = "MoGoChatProvider"
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun init(context: Context?) {
|
||||
facade.init(context)
|
||||
chatWindowManager.init()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
facade.onDestroy()
|
||||
chatWindowManager.destroy()
|
||||
}
|
||||
|
||||
override fun chat(): IMoGoChatFacade = facade
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.analytics
|
||||
|
||||
|
||||
object ChatAnalyticsEvents {
|
||||
|
||||
const val INVOKE_TRACK_REQUEST_CALL_SHOW = "carchat_requestcall_card_show"
|
||||
const val INVOKE_TRACK_REQUEST_CALL = "carchat_cardetail_click"
|
||||
const val INVOKE_TRACK_REFUSE = "carchat_refuse_card_show"
|
||||
const val INVOKE_TRACK_CHATTING = "carchat_phoneing_card_show"
|
||||
const val INVOKE_TRACK_MATCH_SHOW = "carchat_carphonecall_match_show"
|
||||
const val INVOKE_TRACK_MATCH_FAIL_CLOSE_CLICK = "carchat_match_fail_close_click"
|
||||
|
||||
const val MATCH_TYPE_MANUAL = 1
|
||||
const val MATCH_TYPE_VOICE = 2
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.analytics
|
||||
|
||||
import com.mogo.eagle.core.function.api.chat.biz.IMoGoAnalyticsFacade
|
||||
import com.mogo.eagle.core.function.chat.facade.bridge.BridgeApi
|
||||
|
||||
object ChatAnalyticsFacade: IMoGoAnalyticsFacade {
|
||||
|
||||
override fun track(eventType: String, data: Map<String, Any>?) {
|
||||
BridgeApi.analytics()?.track(eventType, data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.aop
|
||||
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import com.mogo.commons.debug.DebugConfig
|
||||
import org.aspectj.lang.ProceedingJoinPoint
|
||||
import org.aspectj.lang.reflect.CodeSignature
|
||||
import org.aspectj.lang.reflect.MethodSignature
|
||||
|
||||
open class BaseAspectj {
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var enable: Boolean = DebugConfig.isDebug()
|
||||
}
|
||||
|
||||
fun enterMethod(joinPoint: ProceedingJoinPoint) {
|
||||
if (!enable) return
|
||||
|
||||
val signature = joinPoint.signature as CodeSignature
|
||||
val cls = signature.declaringType
|
||||
val methodName = signature.name
|
||||
val parameterNames = signature.parameterNames
|
||||
val parameterValues = joinPoint.args
|
||||
|
||||
val builder = StringBuilder("\u21E2 ")
|
||||
builder.append(methodName).append('(')
|
||||
parameterValues.forEachIndexed { index, _ ->
|
||||
if (index > 0) {
|
||||
builder.append(",")
|
||||
}
|
||||
builder.append(parameterNames[index]).append('=')
|
||||
builder.append(parameterValues[index])
|
||||
}
|
||||
builder.append(')')
|
||||
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
builder.append("[Thread:\"").append(Thread.currentThread().name).append("\"]")
|
||||
}
|
||||
Log.i(asTag(cls), builder.toString())
|
||||
}
|
||||
|
||||
fun exitMethod(joinPoint: ProceedingJoinPoint, result: Any?, lengthMill: Long) {
|
||||
if (!enable) return
|
||||
|
||||
val signature = joinPoint.signature
|
||||
val cls = signature.declaringType
|
||||
val methodName = signature.name
|
||||
val hasReturnType = signature is MethodSignature
|
||||
&& signature.returnType != Void.TYPE
|
||||
|
||||
val builder = StringBuilder("\u21E0 ")
|
||||
.append(methodName)
|
||||
.append("[")
|
||||
.append(lengthMill)
|
||||
.append("ms]")
|
||||
|
||||
if (hasReturnType) {
|
||||
builder.append(" = ")
|
||||
builder.append(result.let {
|
||||
result.toString()
|
||||
})
|
||||
}
|
||||
Log.i(asTag(cls), builder.toString())
|
||||
}
|
||||
|
||||
private fun asTag(cls: Class<*>): String {
|
||||
if (cls.isAnonymousClass) {
|
||||
return asTag(cls.enclosingClass!!)
|
||||
}
|
||||
return cls.simpleName
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.aop
|
||||
|
||||
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.CONSTRUCTOR)
|
||||
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class DebugLog
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.aop
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint
|
||||
import org.aspectj.lang.annotation.Around
|
||||
import org.aspectj.lang.annotation.Aspect
|
||||
import org.aspectj.lang.annotation.Pointcut
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Aspect
|
||||
class LogAspectj : BaseAspectj() {
|
||||
|
||||
|
||||
@Pointcut("within(@com.mogo.eagle.core.function.chat.facade.aop.DebugLog *)")
|
||||
fun withinAnnotatedClass() {
|
||||
}
|
||||
|
||||
@Pointcut("execution(!synthetic * *(..))&& withinAnnotatedClass()")
|
||||
fun methodInsideAnnotatedType() {
|
||||
}
|
||||
|
||||
@Pointcut("execution(!synthetic *.new(..))&& withinAnnotatedClass()")
|
||||
fun constructorInsideAnnotatedType() {
|
||||
}
|
||||
|
||||
@Pointcut("execution(@com.mogo.eagle.core.function.chat.facade.aop.DebugLog * *(..))|| methodInsideAnnotatedType()")
|
||||
fun method() {
|
||||
}
|
||||
|
||||
@Pointcut("execution(@com.mogo.eagle.core.function.chat.facade.aop.DebugLog *.new(..))|| constructorInsideAnnotatedType()")
|
||||
fun constructor() {
|
||||
}
|
||||
|
||||
@Around("method() || constructor()")
|
||||
fun logExecute(joinPoint: ProceedingJoinPoint) {
|
||||
enterMethod(joinPoint)
|
||||
val startNanos = System.nanoTime()
|
||||
val result = joinPoint.proceed()
|
||||
val stopNanos = System.nanoTime()
|
||||
val lengthMill = TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos)
|
||||
exitMethod(joinPoint, result, lengthMill)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.audio
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioFocusRequest
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioManager.OnAudioFocusChangeListener
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.mogo.eagle.core.function.api.chat.biz.ChatConsts
|
||||
import com.mogo.eagle.core.function.api.chat.biz.IMoGoAudioFocusFacade
|
||||
import com.mogo.eagle.core.function.chat.facade.bridge.BridgeApi
|
||||
import com.mogo.eagle.core.function.chat.facade.utils.log
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.android.asCoroutineDispatcher
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
object AudioFocusFacade: IMoGoAudioFocusFacade {
|
||||
|
||||
|
||||
private val audioListener: MyAudioFocusChangeListener by lazy {
|
||||
MyAudioFocusChangeListener()
|
||||
}
|
||||
|
||||
private val audioManager: AudioManager by lazy {
|
||||
BridgeApi.context().getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
}
|
||||
|
||||
private var audioFocusRequest: Any? = null
|
||||
|
||||
private val scope by lazy {
|
||||
CoroutineScope(Handler(Looper.getMainLooper()).asCoroutineDispatcher() + SupervisorJob())
|
||||
}
|
||||
|
||||
private val focusJob by lazy { AtomicReference<Job>() }
|
||||
|
||||
@Volatile
|
||||
private var hasAudioFocus = false
|
||||
|
||||
override fun requireAudioFocus(onGetFocus: () -> Unit) {
|
||||
log(ChatConsts.TAG, "requireDuck===$hasAudioFocus")
|
||||
scope.launch {
|
||||
if (!hasAudioFocus) {
|
||||
while (true) {
|
||||
try {
|
||||
val res = audioManager.let {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
it.requestAudioFocus(audioListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
|
||||
} else {
|
||||
val audioFocusRequest =
|
||||
AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
|
||||
.setAcceptsDelayedFocusGain(true)
|
||||
.setAudioAttributes(
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
|
||||
.build())
|
||||
.setOnAudioFocusChangeListener(audioListener).build()
|
||||
this@AudioFocusFacade.audioFocusRequest = audioFocusRequest
|
||||
it.requestAudioFocus(audioFocusRequest)
|
||||
}
|
||||
}
|
||||
if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||
hasAudioFocus = true
|
||||
onGetFocus.invoke()
|
||||
break
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
delay(SECONDS.toMillis(1))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onGetFocus.invoke()
|
||||
}
|
||||
}.also { focusJob.set(it) }
|
||||
}
|
||||
|
||||
override fun releaseAudioFocus() {
|
||||
log(ChatConsts.TAG, "releaseDuck===$hasAudioFocus")
|
||||
focusJob.get()?.takeIf { it.isActive }?.cancel()
|
||||
scope.launch {
|
||||
if (hasAudioFocus) {
|
||||
hasAudioFocus = false
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
audioManager.abandonAudioFocus(audioListener)
|
||||
} else {
|
||||
(audioFocusRequest as? AudioFocusRequest)?.also {
|
||||
audioManager.abandonAudioFocusRequest(it)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MyAudioFocusChangeListener : OnAudioFocusChangeListener {
|
||||
override fun onAudioFocusChange(focusChange: Int) {
|
||||
log(ChatConsts.TAG, "onAudioFocusChange: $focusChange")
|
||||
when (focusChange) {
|
||||
AudioManager.AUDIOFOCUS_GAIN -> {
|
||||
hasAudioFocus = true
|
||||
}
|
||||
AudioManager.AUDIOFOCUS_LOSS,
|
||||
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT,
|
||||
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> hasAudioFocus = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.bridge
|
||||
|
||||
import android.content.Context
|
||||
import com.mogo.eagle.core.utilcode.util.Utils
|
||||
import com.mogo.module.common.MogoApisHandler
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
internal object BridgeApi {
|
||||
|
||||
private var contextHolder: WeakReference<Context>? = null
|
||||
|
||||
private val apis by lazy {
|
||||
MogoApisHandler.getInstance().apis
|
||||
}
|
||||
|
||||
internal fun init(context: Context?) {
|
||||
context?.let {
|
||||
contextHolder = WeakReference(context)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun context(): Context = contextHolder?.get() ?: Utils.getApp()
|
||||
|
||||
internal fun locationClient() = apis?.mapServiceApi?.getSingletonLocationClient(context())
|
||||
|
||||
internal fun intentManager() = apis?.intentManagerApi
|
||||
|
||||
internal fun statusManager() = apis?.statusManagerApi
|
||||
|
||||
internal fun analytics() = apis?.analyticsApi
|
||||
|
||||
internal fun topViewManager() = apis?.topViewManager
|
||||
|
||||
internal fun floatViewManager() = apis?.windowManagerApi
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.consts
|
||||
|
||||
import com.mogo.commons.debug.DebugConfig
|
||||
|
||||
//WebSocket发送数据相关
|
||||
const val SOCKET_HAND_SHAKE = 0
|
||||
const val SOCKET_HEART_BEAT = 1
|
||||
|
||||
// 与相关接口复用
|
||||
/**
|
||||
* 服务端下发消息状态为0存在三种场景:
|
||||
* 1.语音聊天创建房间成功,给拨打方发送创建房间消息。
|
||||
* 2.有匹配过的车机,表示愿意聊天,如果有人匹配不到,会给有愿意聊天的用户发来打电话邀请
|
||||
* 3.接收车队邀请消息
|
||||
*/
|
||||
const val PUSH_MSG_CREATE_ROOM = 0
|
||||
|
||||
/**
|
||||
* 服务端下发消息状态为1存在三种场景:
|
||||
* 1.语音聊天对方同意,下发同意消息
|
||||
* 2.匹配成功消息
|
||||
* 3.车队邀请对方同意,下发同意消息
|
||||
*/
|
||||
const val PUSH_MSG_AGREE_ENTER = 1
|
||||
|
||||
/**
|
||||
* 服务端下发消息状态为2存在三种场景:
|
||||
* 1.语音邀请被拒绝
|
||||
* 2.被动匹配接收到来电邀请,被拒绝
|
||||
* 3.车队邀请被拒绝
|
||||
*/
|
||||
const val PUSH_MSG_DENY_ENTER = 2
|
||||
|
||||
/**
|
||||
* 服务端下发消息状态为3存在两种场景:
|
||||
* 1.语音和匹配接收到消息下发,挂断电话
|
||||
* 2.车队接收到消息下发,如果消息中有队员,则接收到队员退房消息,若无队员,则挂断车队电话
|
||||
*/
|
||||
const val PUSH_MSG_HANG_UP = 3
|
||||
|
||||
|
||||
class ChatHttp {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val DEV_BASE_URL_OWNER = "http://dzt-show.zhidaohulian.com/"
|
||||
private const val DEV_CONFIG_URL = "http://dzt-test.zhidaohulian.com/"
|
||||
private const val RELEASE_BASE_URL_OWNER = "http://dzt.zhidaohulian.com/"
|
||||
|
||||
private const val SOCKET_SERVER = "ws://62.234.196.121:4001/ws"
|
||||
private const val DEV_SOCKET_SERVER = "ws://dzt-test.zhidaohulian.com/ws"
|
||||
|
||||
fun getBaseUrl(): String {
|
||||
return when (DebugConfig.getNetMode()) {
|
||||
DebugConfig.NET_MODE_DEV, DebugConfig.NET_MODE_QA, DebugConfig.NET_MODE_DEMO -> DEV_BASE_URL_OWNER
|
||||
DebugConfig.NET_MODE_RELEASE -> RELEASE_BASE_URL_OWNER
|
||||
else -> RELEASE_BASE_URL_OWNER
|
||||
}
|
||||
}
|
||||
|
||||
fun getSocketServer(): String {
|
||||
return when (DebugConfig.getNetMode()) {
|
||||
DebugConfig.NET_MODE_DEV, DebugConfig.NET_MODE_QA, DebugConfig.NET_MODE_DEMO -> DEV_SOCKET_SERVER
|
||||
DebugConfig.NET_MODE_RELEASE -> SOCKET_SERVER
|
||||
else -> SOCKET_SERVER
|
||||
}
|
||||
}
|
||||
|
||||
fun getConfig(): String {
|
||||
return when (DebugConfig.getNetMode()) {
|
||||
DebugConfig.NET_MODE_DEV, DebugConfig.NET_MODE_QA, DebugConfig.NET_MODE_DEMO -> DEV_CONFIG_URL
|
||||
DebugConfig.NET_MODE_RELEASE -> RELEASE_BASE_URL_OWNER
|
||||
else -> RELEASE_BASE_URL_OWNER
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.gme
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Handler
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.gme.TMG.ITMGContext
|
||||
import com.gme.av.sdk.AVError
|
||||
import com.gme.av.sig.AuthBuffer
|
||||
import com.mogo.commons.debug.DebugConfig
|
||||
import com.mogo.eagle.core.function.api.chat.biz.ChatConsts
|
||||
import com.mogo.eagle.core.function.chat.facade.utils.log
|
||||
import com.mogo.eagle.core.utilcode.util.BuildConfig
|
||||
import com.mogo.eagle.core.utilcode.util.Utils
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
internal object GMEApi {
|
||||
|
||||
//GME相关信息
|
||||
private const val ROOM_TYPE = 1
|
||||
private const val SDKID = "1400280276"
|
||||
private const val SDKKEY = "I0USylN9YQq0CAiq"
|
||||
|
||||
private var openId: String? = null
|
||||
|
||||
private val hasInit by lazy {
|
||||
AtomicBoolean(false)
|
||||
}
|
||||
|
||||
private val Intent.result: Int
|
||||
get() = getIntExtra("result" , -1)
|
||||
|
||||
private val Intent.errorInfo: String
|
||||
get() = getStringExtra("error_info") ?: ""
|
||||
|
||||
private val Intent.eventId: Int
|
||||
get() = getIntExtra("event_id", 0)
|
||||
|
||||
private val Intent.users: Array<String>
|
||||
get() = getStringArrayExtra("user_list") ?: emptyArray()
|
||||
|
||||
private var cb: ((state: GmeState) -> Unit)? = null
|
||||
|
||||
private var ctx: WeakReference<Context>? = null
|
||||
|
||||
private val tmgCtx by lazy {
|
||||
ITMGContext.GetInstance(ctx?.get()?: Utils.getApp())
|
||||
}
|
||||
|
||||
private val delegate = object : ITMGContext.ITMGDelegate() {
|
||||
|
||||
override fun OnEvent(type: ITMGContext.ITMG_MAIN_EVENT_TYPE?, data: Intent?) {
|
||||
super.OnEvent(type, data)
|
||||
type ?: return
|
||||
data ?: return
|
||||
if (type == ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_ENTER_ROOM) {
|
||||
val code = data.result
|
||||
if (code == AVError.AV_OK) {
|
||||
cb?.invoke(GmeState.EnterRoomSuccess)
|
||||
} else {
|
||||
cb?.invoke(GmeState.EnterRoomFail(code, data.errorInfo))
|
||||
}
|
||||
}
|
||||
if (type == ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVENT_TYPE_EXIT_ROOM) {
|
||||
cb?.invoke(GmeState.ExitRoomSuccess)
|
||||
}
|
||||
if (type == ITMGContext.ITMG_MAIN_EVENT_TYPE.ITMG_MAIN_EVNET_TYPE_USER_UPDATE) {
|
||||
val eventId = data.eventId
|
||||
val users = data.users
|
||||
log(ChatConsts.TAG, "RoomMembersUpdate: ${users.joinToString(",")}")
|
||||
if (users.isEmpty()) {
|
||||
log(ChatConsts.TAG, "没人了,准备退房")
|
||||
cb?.invoke(GmeState.UserChangeInRoom(isEnter = false, emptyArray()))
|
||||
return
|
||||
}
|
||||
val filtered = users.filter {
|
||||
log(ChatConsts.TAG, "成员进房====$it===ownId:$openId")
|
||||
it.toInt() > 99999
|
||||
}.toTypedArray()
|
||||
if (filtered.isEmpty()) {
|
||||
log(ChatConsts.TAG, "成员为空,无操作")
|
||||
return
|
||||
}
|
||||
if (eventId == ITMGContext.ITMG_EVENT_ID_USER_ENTER) {
|
||||
log(ChatConsts.TAG, "成员进房==去掉99999====$filtered")
|
||||
cb?.invoke(GmeState.UserChangeInRoom(isEnter = true, filtered))
|
||||
}
|
||||
if (eventId == ITMGContext.ITMG_EVENT_ID_USER_EXIT) {
|
||||
cb?.invoke(GmeState.UserChangeInRoom(isEnter = false, filtered))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun enableAudio() {
|
||||
Log.d(ChatConsts.TAG, "-- enable audio --- 1 ---")
|
||||
if (!hasInit.get()) {
|
||||
return
|
||||
}
|
||||
Log.d(ChatConsts.TAG, "-- enable audio --- 2 ---")
|
||||
val audioCtrl = tmgCtx.GetAudioCtrl()
|
||||
//开启麦克
|
||||
audioCtrl?.EnableMic(true)
|
||||
//开启扬声器
|
||||
audioCtrl?.EnableSpeaker(true)
|
||||
//设置扬声器音量
|
||||
audioCtrl?.SetSpeakerVolume(200)
|
||||
//解除麦克风静音
|
||||
audioCtrl?.SetMicVolume(100)
|
||||
}
|
||||
|
||||
fun disableAudio() {
|
||||
Log.d(ChatConsts.TAG, "-- disable audio --- 1 ---")
|
||||
if (!hasInit.get()) {
|
||||
return
|
||||
}
|
||||
Log.d(ChatConsts.TAG, "-- disable audio --- 2 ---")
|
||||
val audioCtrl = tmgCtx.GetAudioCtrl()
|
||||
audioCtrl?.EnableSpeaker(false)
|
||||
//开启麦克
|
||||
audioCtrl?.EnableMic(false)
|
||||
}
|
||||
|
||||
fun init(ctx: Context, openId: String, cb: ((s: GmeState) -> Unit)? = null) {
|
||||
if (hasInit.get()) {
|
||||
log(ChatConsts.TAG, "Error: GmeApi has initialized.")
|
||||
return
|
||||
}
|
||||
hasInit.set(true)
|
||||
try {
|
||||
this.ctx = WeakReference(ctx)
|
||||
this.cb = cb
|
||||
tmgCtx.SetTMGDelegate(delegate)
|
||||
|
||||
tmgCtx.SetLogLevel(ITMGContext.ITMG_LOG_LEVEL_INFO, ITMGContext.ITMG_LOG_LEVEL_VERBOSE)
|
||||
|
||||
|
||||
this.openId = openId
|
||||
val ret = tmgCtx.Init(SDKID, openId)
|
||||
if (ret == AVError.AV_OK) {
|
||||
GmePoller.start()
|
||||
cb?.invoke(GmeState.InitSuccess(openId))
|
||||
} else {
|
||||
cb?.invoke(GmeState.InitFail(ret))
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
log(ChatConsts.TAG, "GMEAPI - error: $t")
|
||||
}
|
||||
}
|
||||
|
||||
fun enterRoom(roomId: String) {
|
||||
if (tmgCtx.IsRoomEntered()) {
|
||||
return
|
||||
}
|
||||
tmgCtx.EnterRoom(roomId, ROOM_TYPE, createAuthBuffer(roomId))
|
||||
}
|
||||
|
||||
fun exitRoom() {
|
||||
if (!tmgCtx.IsRoomEntered()) {
|
||||
return
|
||||
}
|
||||
tmgCtx.ExitRoom()
|
||||
}
|
||||
|
||||
fun isRoomEntered() = tmgCtx.IsRoomEntered()
|
||||
|
||||
fun unInit() {
|
||||
if (!hasInit.get()) {
|
||||
return
|
||||
}
|
||||
hasInit.set(false)
|
||||
cb = null
|
||||
tmgCtx.SetTMGDelegate(null)
|
||||
GmePoller.stop()
|
||||
tmgCtx.Uninit()
|
||||
}
|
||||
|
||||
private fun createAuthBuffer(roomId: String?): ByteArray? {
|
||||
return if (TextUtils.isEmpty(roomId)) {
|
||||
AuthBuffer.getInstance().genAuthBuffer(SDKID.toInt(), "0", openId, SDKKEY)
|
||||
} else {
|
||||
AuthBuffer.getInstance().genAuthBuffer(SDKID.toInt(), roomId, openId, SDKKEY)
|
||||
}
|
||||
}
|
||||
|
||||
fun muteVoice(mute: Boolean) {
|
||||
if (!isRoomEntered()) {
|
||||
return
|
||||
}
|
||||
if (mute) {
|
||||
tmgCtx.GetAudioCtrl()?.SetMicVolume(0)
|
||||
} else {
|
||||
tmgCtx.GetAudioCtrl()?.SetMicVolume(100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class GmeState {
|
||||
|
||||
class InitSuccess(val openId: String): GmeState()
|
||||
|
||||
class InitFail(val code: Int): GmeState()
|
||||
|
||||
object EnterRoomSuccess: GmeState()
|
||||
|
||||
class EnterRoomFail(val code: Int, val msg: String): GmeState()
|
||||
|
||||
class UserChangeInRoom(val isEnter: Boolean, val left: Array<String>): GmeState()
|
||||
|
||||
object ExitRoomSuccess: GmeState()
|
||||
}
|
||||
|
||||
|
||||
internal object GmePoller {
|
||||
|
||||
private val handler by lazy { Handler() }
|
||||
|
||||
private val poll: Runnable = object : Runnable {
|
||||
override fun run() {
|
||||
if (ITMGContext.GetInstance(null) != null) {
|
||||
ITMGContext.GetInstance(null).Poll()
|
||||
}
|
||||
handler.postDelayed(this, 30)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun start() {
|
||||
handler.postDelayed(poll, 30)
|
||||
}
|
||||
|
||||
internal fun stop() {
|
||||
handler.removeCallbacks(poll)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.media
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Build
|
||||
import com.mogo.eagle.core.function.api.chat.biz.ChatConsts
|
||||
import com.mogo.eagle.core.function.api.chat.biz.IMoGoMediaFacade
|
||||
import com.mogo.eagle.core.function.chat.facade.utils.log
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
internal object MedialControlFacade : IMoGoMediaFacade {
|
||||
|
||||
private val player by lazy {
|
||||
AtomicReference<WeakReference<MediaPlayer>>()
|
||||
}
|
||||
|
||||
override fun play(context: Context, audioSources: Int, isLoop: Boolean, channel: Int) {
|
||||
val player = player.get()?.get() ?: MediaPlayer().also { player.set(WeakReference(it)) }
|
||||
resetStatus(player)
|
||||
val file = context.resources.openRawResourceFd(audioSources)
|
||||
player.apply {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
setAudioStreamType(channel)
|
||||
} else {
|
||||
setAudioAttributes(AudioAttributes.Builder().setLegacyStreamType(channel).build())
|
||||
}
|
||||
setDataSource(file.fileDescriptor, file.startOffset, file.length)
|
||||
file.close()
|
||||
isLooping = isLoop
|
||||
prepareAsync()
|
||||
setOnPreparedListener { player ->
|
||||
log(ChatConsts.TAG,"real play 准备播放音频====")
|
||||
player.start()
|
||||
}
|
||||
setOnCompletionListener { player ->
|
||||
log(ChatConsts.TAG,"播放完成====")
|
||||
player.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun release() {
|
||||
val player = this.player.get()?.get() ?: return
|
||||
log(ChatConsts.TAG,"release 释放音频播放====")
|
||||
player.run {
|
||||
resetStatus(this)
|
||||
release()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetStatus(player: MediaPlayer) {
|
||||
player.run {
|
||||
if (isPlaying) {
|
||||
stop()
|
||||
}
|
||||
reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.net
|
||||
|
||||
import com.alibaba.android.arouter.launcher.ARouter
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
|
||||
import com.mogo.eagle.core.data.BaseResponse
|
||||
import com.mogo.eagle.core.data.chat.UserInfo
|
||||
import com.mogo.eagle.core.data.constants.MogoServicePaths
|
||||
import com.mogo.eagle.core.function.api.chat.biz.ChatConsts
|
||||
import com.mogo.eagle.core.function.api.chat.biz.IMCallType.CALL_TYPE_VOICE
|
||||
import com.mogo.eagle.core.function.chat.facade.bridge.BridgeApi
|
||||
import com.mogo.eagle.core.function.chat.facade.consts.ChatHttp
|
||||
import com.mogo.eagle.core.function.chat.facade.consts.ChatHttp.Companion.getConfig
|
||||
import com.mogo.eagle.core.function.chat.facade.net.bean.*
|
||||
import com.mogo.eagle.core.function.chat.facade.utils.log
|
||||
import com.mogo.service.IMogoServiceApis
|
||||
import retrofit2.http.*
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
|
||||
internal class ChatServiceModel {
|
||||
|
||||
suspend fun isOnline(sn: String): Boolean {
|
||||
val map = hashMapOf<String, String>()
|
||||
map["sn"] = sn
|
||||
return apiCall {
|
||||
getNetWorkApi()?.isOnLine(map) ?: throw IllegalStateException("apis is null.")
|
||||
}.takeIf { it.code == 0 || it.code == 200 }?.let {
|
||||
val result = it.result
|
||||
return@let result.carOnLine == 1 && result.onLine == 1
|
||||
} ?: false
|
||||
}
|
||||
|
||||
suspend fun queryUserInfo(sn: String): Pair<Error?,UserInfo?>? {
|
||||
val sns = arrayListOf(sn)
|
||||
val requestData = SnArrayRequestBody().also { it.sns = sns }
|
||||
return apiCall {
|
||||
getNetWorkApi(getConfig())?.queryUserInfoBySnS(requestData) ?: throw IllegalStateException("apis is null.")
|
||||
}.let { itx ->
|
||||
if (itx.code != 0 && itx.code != 200) {
|
||||
return@let Pair(Error(itx.code, itx.msg), null)
|
||||
}
|
||||
val result = UserInfo()
|
||||
val json = itx.result
|
||||
val array = json.getAsJsonArray("info")
|
||||
if (array == null || array.size() == 0) {
|
||||
return Pair(null, null)
|
||||
}
|
||||
val first = array[0] as? JsonObject ?: return@let null
|
||||
val userInfo = first.getAsJsonObject("carAndUserInfoRedisVo")
|
||||
val location = first.getAsJsonObject("realTimeLocationVo")
|
||||
if (userInfo == null) {
|
||||
return@let Pair(null, null)
|
||||
}
|
||||
result.sn = userInfo.get("sn")?.takeIf { !it.isJsonNull }?.asString ?: sn
|
||||
result.name = userInfo.get("userNickName")?.takeIf { !it.isJsonNull }?.asString ?: "小蘑菇"
|
||||
result.icon = userInfo.get("headImgUrl")?.takeIf { !it.isJsonNull }?.asString
|
||||
result.sex = userInfo.get("cardIdSex")?.takeIf { !it.isJsonNull }?.asString ?: "未设置"
|
||||
result.age = userInfo.get("cardIdAge")?.takeIf { !it.isJsonNull }?.asString
|
||||
result.brand = userInfo.get("lastBrandName")?.takeIf { !it.isJsonNull }?.asString
|
||||
result.city = userInfo.get("lastActiveCity")?.takeIf { !it.isJsonNull }?.asString
|
||||
if (location != null) {
|
||||
result.lat = location.get("lat")?.takeIf { !it.isJsonNull }?.asDouble ?: 0.0
|
||||
result.lon = location.get("lon")?.takeIf { !it.isJsonNull }?.asDouble ?: 0.0
|
||||
}
|
||||
return@let Pair(null, result)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun requestConnectStatus(params: ConnectStatusParam): BaseResponse<Any> {
|
||||
val map = hashMapOf<String, String>()
|
||||
val sn = MoGoAiCloudClientConfig.getInstance().sn
|
||||
val location = BridgeApi.locationClient()?.lastKnowLocation
|
||||
if (location != null) {
|
||||
params.lon = location.longitude
|
||||
params.lat = location.latitude
|
||||
}
|
||||
log(ChatConsts.TAG, "connectStatusParam:$params")
|
||||
map["sn"] = MoGoAiCloudClientConfig.getInstance().sn
|
||||
map["data"] = Gson().toJson(params)
|
||||
return if (params.type == CALL_TYPE_VOICE.type) {
|
||||
apiCall {
|
||||
getNetWorkApi()?.requestConnectStatus(sn, map) ?: throw IllegalStateException("apis is null.")
|
||||
}
|
||||
} else {
|
||||
apiCall {
|
||||
getNetWorkApi()?.requestVehicleTeamConnectStatus(map) ?: throw IllegalStateException("apis is null.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun inviteJoinVehicleTeam(param: CallRequestParam): BaseResponse<Any> {
|
||||
val map = hashMapOf<String, String>()
|
||||
log(ChatConsts.TAG, "inviteJoinVehicleTeam paras: $param")
|
||||
map["sn"] = MoGoAiCloudClientConfig.getInstance().sn
|
||||
map["data"] = Gson().toJson(param)
|
||||
return apiCall {
|
||||
getNetWorkApi()?.inviteJoinVehicleTeam(map) ?: throw IllegalStateException("apis is null.")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun requestRoomInfo(param: CallRequestParam): BaseResponse<RoomInfo> {
|
||||
val map = hashMapOf<String, String>()
|
||||
val location = BridgeApi.locationClient()?.lastKnowLocation
|
||||
if (location != null) {
|
||||
param.lat = location.latitude
|
||||
param.lon = location.longitude
|
||||
}
|
||||
log(ChatConsts.TAG, "roomParam:$param")
|
||||
map["data"] = Gson().toJson(param)
|
||||
return apiCall {
|
||||
getNetWorkApi()?.requestRoomInfo(map) ?: throw IllegalStateException("apis is null.")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T : Any> apiCall(call: suspend () -> BaseResponse<T>): BaseResponse<T> {
|
||||
return call.invoke()
|
||||
}
|
||||
|
||||
private fun getNetWorkApi(baseUrl: String = ChatHttp.getBaseUrl()): HttpApi? {
|
||||
val serviceApi: IMogoServiceApis? = ARouter.getInstance().build(MogoServicePaths.PATH_SERVICE_APIS).navigation() as? IMogoServiceApis
|
||||
return serviceApi?.networkApi?.createNoCallAdapter(HttpApi::class.java, baseUrl)
|
||||
}
|
||||
}
|
||||
|
||||
internal interface HttpApi {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("/yycp-chat-service/car/voiceRoom/no/operate/v1")
|
||||
suspend fun requestConnectStatus(@Query("sn") sn: String, @FieldMap connectStatus: Map<String, String>): BaseResponse<Any>
|
||||
|
||||
//邀请加入车队
|
||||
@FormUrlEncoded
|
||||
@POST("/yycp-chat-service/car/chat/no/inviteJoinTeam/v1")
|
||||
suspend fun inviteJoinVehicleTeam(@FieldMap inviteVehicleTeam: Map<String, String>): BaseResponse<Any>
|
||||
|
||||
//车队状态同步
|
||||
@FormUrlEncoded
|
||||
@POST("/yycp-chat-service/car/chat/no/operateTeamRoom/v1")
|
||||
suspend fun requestVehicleTeamConnectStatus(@FieldMap connectStatus: Map<String, String>): BaseResponse<Any>
|
||||
|
||||
//查询用户是否在线
|
||||
@FormUrlEncoded
|
||||
@POST("/yycp-chat-service/car/queryOnLineBySn/v1")
|
||||
suspend fun isOnLine(@FieldMap onLine: Map<String, String>): BaseResponse<OnLineStatus>
|
||||
|
||||
@POST("/yycp-realtimeLocations/realTimeLocationServer/queryRsAncCarAndUserInfoBySns")
|
||||
suspend fun queryUserInfoBySnS(@Body body: SnArrayRequestBody): BaseResponse<JsonObject>
|
||||
|
||||
//语音房间信息,原路径dataService
|
||||
@FormUrlEncoded
|
||||
@POST("/yycp-chat-service/car/sender/no/createRoom/v1")
|
||||
suspend fun requestRoomInfo(@FieldMap roomInfo: Map<String, String>): BaseResponse<RoomInfo>
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.net.bean
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.Expose
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.mogo.eagle.core.function.api.chat.biz.IMCallType
|
||||
import com.mogo.eagle.core.function.api.chat.biz.IMCallType.CALL_TYPE_VOICE
|
||||
|
||||
@Keep class CallRequestParam {
|
||||
/**
|
||||
* 发送者sn
|
||||
*/
|
||||
var snSender: String = ""
|
||||
|
||||
/**
|
||||
* 接收者sn
|
||||
*/
|
||||
var snReceiver: String = ""
|
||||
|
||||
/**
|
||||
* 发送者用户名
|
||||
*/
|
||||
var nickName:String? = null
|
||||
|
||||
/**
|
||||
* 发送者头像链接
|
||||
*/
|
||||
var headImgUrl:String? = null
|
||||
|
||||
/**
|
||||
* 发送者车辆信息
|
||||
*/
|
||||
var carInfo:String? = null
|
||||
|
||||
/**
|
||||
* 发送者经度
|
||||
*/
|
||||
var lon:Double = 0.0
|
||||
|
||||
/**
|
||||
* 发送者纬度
|
||||
*/
|
||||
var lat: Double = 0.0
|
||||
|
||||
/**
|
||||
* 呼叫类型
|
||||
*/
|
||||
var type = CALL_TYPE_VOICE.type
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.net.bean
|
||||
|
||||
class ConnectStatusParam (var snSender: String, var snReceiver: String, var roomId: Int, var status: Int, var type: Int) {
|
||||
|
||||
var lat: Double? = null//发送方的纬度
|
||||
|
||||
var lon: Double? = null//发送方的经度
|
||||
|
||||
var nickName: String? = null//发送方的昵称
|
||||
|
||||
var headImgUrl: String? = null//发送方的用户头像
|
||||
|
||||
var carInfo: String? = null//发送方的车辆信息
|
||||
|
||||
var cardIdAge: String? = null //发送方年龄
|
||||
|
||||
var cardIdSex: String? = null//发送发性别
|
||||
|
||||
var cityName: String? = null//发送方城市
|
||||
|
||||
override fun toString(): String {
|
||||
return "ConnectStatusParam(snSender='$snSender', snReceiver='$snReceiver', roomId=$roomId, status=$status, type=$type, lat=$lat, lon=$lon, nickName=$nickName, headImgUrl=$headImgUrl, carInfo=$carInfo, cardIdAge=$cardIdAge, cardIdSex=$cardIdSex, cityName=$cityName)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.net.bean
|
||||
|
||||
data class Error(val code: Int, val msg: String)
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.net.bean
|
||||
|
||||
|
||||
/**
|
||||
* carOnLine:ACC ON状态 1:在线 2:不在线
|
||||
* onLine:是否隐身 1:在线 2:隐身
|
||||
*/
|
||||
internal class OnLineStatus {
|
||||
var carOnLine: Int = -1
|
||||
var onLine: Int = -1
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.net.bean
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep class RoomInfo {
|
||||
|
||||
var roomId:Int = -1
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.net.bean
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
internal class SnArrayRequestBody {
|
||||
var sns: List<String>? = null
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.socket
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.mogo.cloud.passport.MoGoAiCloudClientConfig
|
||||
import com.mogo.eagle.core.data.chat.socket.HeartBeat
|
||||
import com.mogo.eagle.core.function.api.chat.biz.ChatConsts
|
||||
import com.mogo.eagle.core.function.chat.facade.consts.ChatHttp
|
||||
import com.mogo.eagle.core.function.chat.facade.consts.SOCKET_HAND_SHAKE
|
||||
import com.mogo.eagle.core.function.chat.facade.consts.SOCKET_HEART_BEAT
|
||||
import com.mogo.eagle.core.function.chat.facade.utils.log
|
||||
import com.mogo.websocket.ISocketMsgCallBack
|
||||
import com.mogo.websocket.ISocketMsgSetting
|
||||
import com.mogo.websocket.SocketClient
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* 长链接管理类
|
||||
*/
|
||||
internal object SocketConnectManager {
|
||||
|
||||
private val client by lazy {
|
||||
SocketClient()
|
||||
}
|
||||
|
||||
private var msgCallback: ((msg: String) -> Unit)? = null
|
||||
|
||||
private val roomId by lazy {
|
||||
AtomicInteger(0)
|
||||
}
|
||||
|
||||
fun init() {
|
||||
//开启长连服务
|
||||
client.initSocketServer(ChatHttp.getSocketServer())
|
||||
client.getMessageSettings(socketMsgSetting)
|
||||
client.addISocketMsgCallBack(socketMsgCallBack)
|
||||
client.startConnect()
|
||||
}
|
||||
|
||||
fun setOnMsgReceiveCb(cb: ((msg: String) -> Unit)) {
|
||||
msgCallback = cb
|
||||
}
|
||||
|
||||
fun setRoomId(roomId: Int) {
|
||||
this.roomId.set(roomId)
|
||||
}
|
||||
|
||||
private val socketMsgSetting: ISocketMsgSetting = object : ISocketMsgSetting {
|
||||
override fun getHandShakeMsg(): String {
|
||||
log(ChatConsts.TAG, "getHandShakeMsg")
|
||||
val socketMsg = HeartBeat(SOCKET_HAND_SHAKE, MoGoAiCloudClientConfig.getInstance().sn)
|
||||
return Gson().toJson(socketMsg)
|
||||
}
|
||||
|
||||
override fun getHeartBeatMsg(): String {
|
||||
log(ChatConsts.TAG, "getHeartBeatMsg")
|
||||
val socketMsg = HeartBeat(SOCKET_HEART_BEAT, MoGoAiCloudClientConfig.getInstance().sn, roomId.get())
|
||||
return Gson().toJson(socketMsg)
|
||||
}
|
||||
}
|
||||
|
||||
private val socketMsgCallBack: ISocketMsgCallBack = object : ISocketMsgCallBack {
|
||||
override fun onConnectOpen() {
|
||||
log(ChatConsts.TAG, "onConnectOpen ---> ")
|
||||
}
|
||||
|
||||
override fun handleError(e: Exception) {
|
||||
log(ChatConsts.TAG, "handleError ---> msg: ${e.message}")
|
||||
client.stopHeartBeat()
|
||||
}
|
||||
|
||||
override fun onConnectClose() {
|
||||
log(ChatConsts.TAG, "onConnectClose ---> stop web socket thread ,and ready to reconnect")
|
||||
client.stop()
|
||||
log(ChatConsts.TAG, "onConnectClose ---> stop Heart Beat")
|
||||
client.stopHeartBeat()
|
||||
log(ChatConsts.TAG, "ready to reconnect")
|
||||
client.reConnect()
|
||||
}
|
||||
|
||||
override fun handleMessage(message: String) {
|
||||
log(ChatConsts.TAG, "handleMessage ---> $message")
|
||||
msgCallback?.invoke(message)
|
||||
}
|
||||
}
|
||||
|
||||
fun instance() = client
|
||||
|
||||
fun destroy() {
|
||||
try {
|
||||
client.stopHeartBeat()
|
||||
client.disConnect()
|
||||
client.stop()
|
||||
} catch (t: Throwable) {
|
||||
log(ChatConsts.TAG, "IMService -- destroy -- ex: $t")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.media.AudioManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.lifecycle.Lifecycle.Event.ON_CREATE
|
||||
import androidx.lifecycle.Lifecycle.Event.ON_DESTROY
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.mogo.eagle.core.data.chat.UserInfo
|
||||
import com.mogo.eagle.core.function.api.chat.biz.AnswerState
|
||||
import com.mogo.eagle.core.function.api.chat.biz.AnswerState.EnterRoomSuccess
|
||||
import com.mogo.eagle.core.function.api.chat.biz.AnswerState.Error
|
||||
import com.mogo.eagle.core.function.api.chat.biz.AnswerState.ExitRoomSuccess
|
||||
import com.mogo.eagle.core.function.api.chat.biz.AnswerState.RoomMemberUpdate
|
||||
import com.mogo.eagle.core.function.api.chat.biz.ChatConsts
|
||||
import com.mogo.eagle.core.function.api.chat.biz.HangUpState
|
||||
import com.mogo.eagle.core.function.api.chat.biz.IMoGoVoiceControlFacade.IMoGoVoiceCallback
|
||||
import com.mogo.eagle.core.function.api.chat.biz.RefuseState
|
||||
import com.mogo.eagle.core.function.chat.R
|
||||
import com.mogo.eagle.core.function.chat.facade.MoGoChatFacade
|
||||
import com.mogo.eagle.core.function.chat.facade.OnCallingInterrupt
|
||||
import com.mogo.eagle.core.function.chat.facade.OnInComingCallback
|
||||
import com.mogo.eagle.core.function.chat.facade.bridge.BridgeApi
|
||||
import com.mogo.eagle.core.function.chat.facade.utils.log
|
||||
import com.mogo.eagle.core.function.chat.facade.voice.VoiceControlFacade.REQUEST_CLOUD_VOICE_CALL
|
||||
import com.mogo.eagle.core.utilcode.kotlin.lifeCycleScope
|
||||
import com.mogo.eagle.core.utilcode.kotlin.observe
|
||||
import com.mogo.eagle.core.utilcode.kotlin.onClick
|
||||
import com.mogo.eagle.core.utilcode.kotlin.safeCancel
|
||||
import com.mogo.eagle.core.utilcode.mogo.glide.GlideRoundedCornersTransform
|
||||
import com.mogo.eagle.core.utilcode.mogo.glide.GlideRoundedCornersTransform.CornerType.LEFT
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.Channel.Factory.RENDEZVOUS
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.selects.select
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import com.mogo.eagle.core.utilcode.mogo.glide.GlideApp
|
||||
import com.mogo.eagle.core.utilcode.util.ToastUtils
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
internal class CallChatWindowManager {
|
||||
|
||||
companion object {
|
||||
const val TAG = "TeamChatWindowManager"
|
||||
|
||||
const val DEFAULT_MAX_DIALING_TIME = 30 * 1000L
|
||||
}
|
||||
|
||||
private val facade by lazy {
|
||||
MoGoChatFacade
|
||||
}
|
||||
|
||||
private val context by lazy {
|
||||
BridgeApi.context()
|
||||
}
|
||||
|
||||
private val scope by lazy {
|
||||
BridgeApi.context().lifeCycleScope
|
||||
}
|
||||
|
||||
private var hasAnswered = false
|
||||
|
||||
private var hasRefused = false
|
||||
|
||||
private var hasHangUpped = false
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private var interrupt = Channel<Boolean>(RENDEZVOUS)
|
||||
@Synchronized
|
||||
get() = if (field.isClosedForReceive || field.isClosedForSend) {
|
||||
field = Channel(RENDEZVOUS)
|
||||
field
|
||||
} else field
|
||||
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private var exitRoom = Channel<Boolean>(RENDEZVOUS)
|
||||
@Synchronized
|
||||
get() = if (field.isClosedForReceive || field.isClosedForSend) {
|
||||
field = Channel(RENDEZVOUS)
|
||||
field
|
||||
} else field
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private var callerEnter = Channel<Boolean>(RENDEZVOUS)
|
||||
@Synchronized
|
||||
get() = if (field.isClosedForReceive || field.isClosedForSend) {
|
||||
field = Channel(RENDEZVOUS)
|
||||
field
|
||||
} else field
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private var refused = Channel<Boolean>(RENDEZVOUS)
|
||||
@Synchronized
|
||||
get() = if (field.isClosedForReceive || field.isClosedForSend) {
|
||||
field = Channel(RENDEZVOUS)
|
||||
field
|
||||
} else field
|
||||
|
||||
|
||||
private val onInComingCall = object : OnInComingCallback {
|
||||
override fun invoke(isTeam: Boolean, user: UserInfo) {
|
||||
if (isTeam) {
|
||||
log(TAG, "车队邀请,不做处理....")
|
||||
} else {
|
||||
showInComingView(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val onCallInterrupt = object : OnCallingInterrupt {
|
||||
override fun invoke(isTeam: Boolean, user: UserInfo) {
|
||||
if (isTeam) {
|
||||
log(TAG, "车队邀请拒绝,不做处理....")
|
||||
} else {
|
||||
onCallInterrupt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun init() {
|
||||
facade.let {
|
||||
it.onInComingCall(onInComingCall)
|
||||
it.onCallingInterrupt(onCallInterrupt)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun showInComingView(user: UserInfo) {
|
||||
val context = BridgeApi.context()
|
||||
scope.launchWhenResumed {
|
||||
var incomingView = LayoutInflater.from(context)
|
||||
.inflate(R.layout.module_car_chatting_launcher_incoming_hawk_eye_view, null)
|
||||
incomingView.isClickable = true
|
||||
var answer = incomingView.findViewById<View>(R.id.module_carchatting_incoming_answer)
|
||||
var refuse = incomingView.findViewById<View>(R.id.module_carchatting_incoming_hangUp)
|
||||
val dismissJob = AtomicReference<Job>()
|
||||
var timer: Job? = null
|
||||
var ring: Job? = null
|
||||
launch {
|
||||
speak(REQUEST_CLOUD_VOICE_CALL)
|
||||
calling()
|
||||
}.also { ring = it }
|
||||
answer.onClick {
|
||||
timer = resetInComingTimer(timer, user, incomingView)
|
||||
if (hasAnswered) {
|
||||
ToastUtils.showShort("正在处理, 请稍候...")
|
||||
return@onClick
|
||||
}
|
||||
doAnswer(user, incomingView)
|
||||
}
|
||||
refuse.onClick {
|
||||
timer = resetInComingTimer(timer, user, incomingView)
|
||||
if (hasRefused) {
|
||||
ToastUtils.showShort("正在处理, 请稍候...")
|
||||
return@onClick
|
||||
}
|
||||
doRefuse(user)
|
||||
}
|
||||
incomingView.observe(arrayOf(ON_CREATE, ON_DESTROY)) { itx ->
|
||||
if (itx == ON_CREATE) {
|
||||
timer = inComingTimer(user, incomingView)
|
||||
launch(Dispatchers.Main) {
|
||||
val d1 = async {
|
||||
log(TAG, "监听对方呼出,又挂断状态...")
|
||||
var interrupted = interrupt.receive()
|
||||
while (!interrupted) {
|
||||
interrupted = interrupt.receive()
|
||||
}
|
||||
log(TAG, "收到对方呼出又挂断状态...")
|
||||
}
|
||||
val d2 = async {
|
||||
log(TAG, "监听拒绝状态...")
|
||||
var isRefuse = refused.receive()
|
||||
while (!isRefuse) {
|
||||
isRefuse = refused.receive()
|
||||
}
|
||||
log(TAG, "收到拒绝状态, 隐藏来电界面")
|
||||
}
|
||||
val state = select<Int> {
|
||||
d1.onAwait { 1 }
|
||||
d2.onAwait { 2 }
|
||||
}
|
||||
when (state) {
|
||||
1 -> {
|
||||
log(TAG, "由于对方呼出又挂断,导致来电界面隐藏")
|
||||
d2.safeCancel()
|
||||
}
|
||||
2 -> {
|
||||
log(TAG, "由于自己主动拒接,导致来电界面隐藏")
|
||||
d1.safeCancel()
|
||||
}
|
||||
}
|
||||
hide(incomingView)
|
||||
releaseAudioAndVoice()
|
||||
}.also { dismissJob.set(it) }
|
||||
}
|
||||
if (itx == ON_DESTROY) {
|
||||
ring?.safeCancel()
|
||||
timer?.safeCancel()
|
||||
hasAnswered = false
|
||||
hasRefused = false
|
||||
refused.safeCancel()
|
||||
interrupt.safeCancel()
|
||||
dismissJob.get()?.safeCancel()
|
||||
incomingView = null
|
||||
answer = null
|
||||
refuse = null
|
||||
}
|
||||
}
|
||||
show(incomingView, getInComingLayoutParams(context))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun speak(text: String) = suspendCancellableCoroutine<Unit> {
|
||||
val voiceCallback = object : IMoGoVoiceCallback {
|
||||
override fun onSpeakEnd() {
|
||||
it.resumeWith(Result.success(Unit))
|
||||
}
|
||||
}
|
||||
val voiceCtl = facade.voice()
|
||||
it.invokeOnCancellation {
|
||||
voiceCtl.stopTTS(context, text)
|
||||
}
|
||||
voiceCtl.speak(context, text, voiceCallback)
|
||||
}
|
||||
|
||||
private suspend fun calling() = suspendCancellableCoroutine<Unit> {
|
||||
it.invokeOnCancellation {
|
||||
releaseAudioAndVoice()
|
||||
}
|
||||
playAudioCall {
|
||||
it.resumeWith(Result.success(Unit))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getInComingLayoutParams(context: Context): FrameLayout.LayoutParams {
|
||||
return FrameLayout.LayoutParams(
|
||||
context.resources.getDimension(R.dimen.module_call_chat_state_incoming_hawk_eye_width).toInt(),
|
||||
context.resources.getDimension(R.dimen.module_call_chat_state_incoming_hawk_eye_height).toInt())
|
||||
.also {
|
||||
val x = context.resources.getDimension(R.dimen.module_call_chat_state_location_hawk_eye_x)
|
||||
val y = context.resources.getDimension(R.dimen.module_call_chat_state_location_hawk_eye_y)
|
||||
it.leftMargin = x.toInt()
|
||||
it.topMargin = y.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新开始计时
|
||||
*/
|
||||
private fun CoroutineScope.resetInComingTimer(old: Job?, user: UserInfo, incomingView: View): Job {
|
||||
old?.safeCancel()
|
||||
return inComingTimer(user, incomingView)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.inComingTimer(user: UserInfo, incomingView: View) = launch {
|
||||
log(TAG, "延迟30s消失计时开始...")
|
||||
delay(DEFAULT_MAX_DIALING_TIME)
|
||||
log(TAG, "延迟30s消失计时结束...")
|
||||
doRefuse(user, false)
|
||||
hide(incomingView)
|
||||
releaseAudioAndVoice()
|
||||
}
|
||||
|
||||
private fun showCallingView(user: UserInfo) {
|
||||
val context = BridgeApi.context()
|
||||
scope.launchWhenResumed {
|
||||
var callingView = LayoutInflater.from(context)
|
||||
.inflate(R.layout.module_car_chatting_launcher_calling_hawk_eye_view, null)
|
||||
callingView.isClickable = true
|
||||
var calling= callingView.findViewById<View>(R.id.module_carchatting_rl_call_view)
|
||||
var head = callingView.findViewById<ImageView>(R.id.module_carchatting_call_head)
|
||||
var hangUp = callingView.findViewById<View>(R.id.module_carchatting_call_hangUp)
|
||||
var name = callingView.findViewById<TextView>(R.id.module_carchatting_call_nickname)
|
||||
var timer = callingView.findViewById<TextView>(R.id.module_carchatting_call_time)
|
||||
calling.visibility = View.VISIBLE
|
||||
name.text = "云平台"
|
||||
timer.text = context.resources.getString(R.string.module_car_chat_matching_wait)
|
||||
GlideApp.with(context).load(user.icon)
|
||||
.apply(
|
||||
RequestOptions.bitmapTransform(
|
||||
GlideRoundedCornersTransform(
|
||||
20f,
|
||||
LEFT
|
||||
)
|
||||
)
|
||||
)
|
||||
.placeholder(R.mipmap.module_carchatting_hawk_eye_default_head_img)
|
||||
.into(head)
|
||||
hangUp.onClick {
|
||||
if (hasHangUpped) {
|
||||
ToastUtils.showShort("正在处理,请稍候...")
|
||||
return@onClick
|
||||
}
|
||||
doHangUp(user, callingView)
|
||||
}
|
||||
callingView.observe(arrayOf(ON_CREATE, ON_DESTROY)) {
|
||||
var job: Job? = null
|
||||
if (it == ON_CREATE) {
|
||||
launch(Dispatchers.Main) {
|
||||
val d1 = async {
|
||||
log(ChatConsts.TAG, "等着新用户进来...")
|
||||
var isNewUserEnter = callerEnter.receive()
|
||||
while (!isNewUserEnter) {
|
||||
isNewUserEnter = callerEnter.receive()
|
||||
}
|
||||
//新用户进来了
|
||||
log(ChatConsts.TAG, "新用户进来了...")
|
||||
callingTimer()
|
||||
.collect {
|
||||
timer.text = parseTime(it)
|
||||
}
|
||||
}
|
||||
val d2 = async {
|
||||
log(ChatConsts.TAG, "等着退房通知...")
|
||||
var exit = exitRoom.receive()
|
||||
while (!exit) {
|
||||
exit = exitRoom.receive()
|
||||
}
|
||||
}
|
||||
val state = select<Int> {
|
||||
d1.onAwait { 1 }
|
||||
d2.onAwait { 2 }
|
||||
}
|
||||
when (state) {
|
||||
1 -> {
|
||||
log(TAG, "通话中, 计时任务结束了(不般不会发生)...")
|
||||
}
|
||||
2 -> {
|
||||
log(TAG, "收到退房通知,可以移除通话状态的界面了...")
|
||||
d1.safeCancel()
|
||||
}
|
||||
}
|
||||
if (hasHangUpped) {
|
||||
ToastUtils.showShort("已挂断")
|
||||
}
|
||||
hide(callingView)
|
||||
facade.audioFocus().releaseAudioFocus()
|
||||
}.also { job = it }
|
||||
}
|
||||
if (it == ON_DESTROY) {
|
||||
job?.safeCancel()
|
||||
callerEnter.safeCancel()
|
||||
exitRoom.safeCancel()
|
||||
hasHangUpped = false
|
||||
callingView = null
|
||||
calling = null
|
||||
head = null
|
||||
hangUp = null
|
||||
name = null
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
show(callingView, getCallingLayoutParams(context))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCallingLayoutParams(context: Context): FrameLayout.LayoutParams {
|
||||
return FrameLayout.LayoutParams(
|
||||
context.resources.getDimension(R.dimen.module_call_chat_state_hawk_eye_width).toInt(),
|
||||
context.resources.getDimension(R.dimen.module_call_chat_state_hawk_eye_height).toInt()
|
||||
)
|
||||
.also {
|
||||
it.leftMargin = context.resources.getDimension(R.dimen.module_call_chat_state_location_hawk_eye_x).toInt()
|
||||
it.topMargin = context.resources.getDimension(R.dimen.module_call_chat_state_location_hawk_eye_y).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseTime(time: Long): String {
|
||||
val date = Date(time)
|
||||
val formatter = SimpleDateFormat("HH:mm:ss", Locale.CHINA)
|
||||
formatter.timeZone = TimeZone.getTimeZone("GMT+00:00")
|
||||
return formatter.format(date)
|
||||
}
|
||||
|
||||
private fun show(view: View, params: FrameLayout.LayoutParams) {
|
||||
BridgeApi.floatViewManager()?.addView(view, params, true)
|
||||
}
|
||||
|
||||
private fun hide(view: View) {
|
||||
if (!ViewCompat.isAttachedToWindow(view)) {
|
||||
return
|
||||
}
|
||||
BridgeApi.floatViewManager()?.removeView(view)
|
||||
}
|
||||
|
||||
private fun playAudioCall(onPlay:(() -> Unit)? = null) {
|
||||
facade.also {
|
||||
it.audioFocus().requireAudioFocus {
|
||||
it.media().play(context, R.raw.call, true, AudioManager.STREAM_RING)
|
||||
onPlay?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun releaseAudioAndVoice() {
|
||||
facade.also {
|
||||
it.media().release()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onNewUserEnterRoom() {
|
||||
scope.launch {
|
||||
callerEnter.send(true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCallInterrupt() {
|
||||
scope.launch {
|
||||
interrupt.send(true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doAnswer(user: UserInfo, incommingView: View) {
|
||||
answer(
|
||||
user,
|
||||
onEnter = {
|
||||
releaseAudioAndVoice()
|
||||
hide(incommingView)
|
||||
showCallingView(user)
|
||||
},
|
||||
onNewEnter = {
|
||||
onNewUserEnterRoom()
|
||||
},
|
||||
onExit = {
|
||||
exitRoom()
|
||||
},
|
||||
onError = { code, msg, extra ->
|
||||
log(TAG, "-- 应答失败 --: code:: $code; msg:: $msg; extra:: $extra")
|
||||
when (code) {
|
||||
AnswerState.CODE_ANSWER_SOCKET_DISCONNECT -> {
|
||||
ToastUtils.showShort("连接已断开")
|
||||
}
|
||||
else -> {
|
||||
ToastUtils.showShort("应答失败")
|
||||
}
|
||||
}
|
||||
doRefuse(user, false)
|
||||
releaseAudioAndVoice()
|
||||
hide(incommingView)
|
||||
facade.audioFocus().releaseAudioFocus()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param user: 来电用户信息
|
||||
* @param onEnter: 当前用户进房成功通知
|
||||
* @param onExit: 当前用户退房通知(当前用户主动挂断或对方挂断)
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun answer(user: UserInfo, onEnter: () -> Unit, onNewEnter: () -> Unit, onExit: () -> Unit, onError: (code: Int, msg: String, extra: Map<String, String>? ) -> Unit) {
|
||||
facade.also { itx ->
|
||||
if (hasAnswered) {
|
||||
return
|
||||
}
|
||||
hasAnswered = true
|
||||
itx.answer(user.sn)
|
||||
.onEach {
|
||||
when(it) {
|
||||
is EnterRoomSuccess -> {
|
||||
onEnter.invoke()
|
||||
}
|
||||
is RoomMemberUpdate -> {
|
||||
if (!it.isMySelf && it.isEnter) {
|
||||
onNewEnter.invoke()
|
||||
}
|
||||
}
|
||||
is ExitRoomSuccess -> {
|
||||
onExit.invoke()
|
||||
}
|
||||
is Error -> {
|
||||
hasAnswered = false
|
||||
onError.invoke(it.code, it.msg, it.extra)
|
||||
}
|
||||
else -> {
|
||||
log(ChatConsts.TAG, "[Answer][Other]-> $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doRefuse(user: UserInfo, notify: Boolean = true) {
|
||||
refuse(user,
|
||||
onSuccess = {
|
||||
if (notify) {
|
||||
ToastUtils.showShort("已拒接")
|
||||
onRefuseOK()
|
||||
}
|
||||
},
|
||||
onError = { code, msg, extra ->
|
||||
log(TAG, "-- 拒绝失败 --: code:: $code; msg:: $msg; extra:: $extra")
|
||||
ToastUtils.showShort("拒绝异常")
|
||||
})
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun refuse(user: UserInfo, onSuccess: () -> Unit, onError: (code: Int, msg: String, extra: Map<String, String>?) -> Unit){
|
||||
facade.also {
|
||||
if (hasRefused) {
|
||||
return
|
||||
}
|
||||
hasRefused = true
|
||||
it.refuse(user.sn)
|
||||
.onEach { itx ->
|
||||
when(itx) {
|
||||
is RefuseState.Success -> {
|
||||
onSuccess.invoke()
|
||||
}
|
||||
is RefuseState.Error -> {
|
||||
hasRefused = false
|
||||
onError.invoke(itx.code, itx.msg, itx.extra)
|
||||
}
|
||||
else -> {
|
||||
log(ChatConsts.TAG, "[Refuse][Other]-> $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doHangUp(user: UserInfo, callingView: View) {
|
||||
hangUp(
|
||||
user,
|
||||
onSuccess = {
|
||||
log(TAG, "挂断接口请求成功,等着退房...")
|
||||
},
|
||||
onError = { code, msg, extra ->
|
||||
log(TAG, "挂断异常:code: $code; msg: $msg, ; extra: $extra")
|
||||
ToastUtils.showShort("挂断异常")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun exitRoom() {
|
||||
scope.launch {
|
||||
exitRoom.send(true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRefuseOK() {
|
||||
scope.launch {
|
||||
refused.send(true)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
private fun callingTimer(): Flow<Long> {
|
||||
return flow {
|
||||
var counter = 1
|
||||
while (true) {
|
||||
delay(1000)
|
||||
emit(1000L * (counter ++))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun hangUp(user: UserInfo, onSuccess: () -> Unit, onError:(code: Int, msg: String, extra: Map<String, String>?) -> Unit) {
|
||||
facade.also {
|
||||
if (hasHangUpped) {
|
||||
return
|
||||
}
|
||||
hasHangUpped = true
|
||||
it.handUp(user.sn)
|
||||
.onEach { itx ->
|
||||
when(itx) {
|
||||
HangUpState.Success -> {
|
||||
onSuccess.invoke()
|
||||
}
|
||||
is HangUpState.Error -> {
|
||||
hasHangUpped = false
|
||||
log(TAG, "-- 挂断失败 --: code:: ${itx.code}; msg:: ${itx.msg}; extra:: ${itx.extra}")
|
||||
onError.invoke(itx.code, itx.msg, itx.extra)
|
||||
}
|
||||
else -> {
|
||||
log(ChatConsts.TAG, "[HangUp][Other]-> $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
hasAnswered = false
|
||||
hasHangUpped = false
|
||||
hasRefused = false
|
||||
interrupt.safeCancel()
|
||||
refused.safeCancel()
|
||||
exitRoom.safeCancel()
|
||||
callerEnter.safeCancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
@file:JvmName("LogUtil")
|
||||
|
||||
package com.mogo.eagle.core.function.chat.facade.utils
|
||||
|
||||
import com.mogo.eagle.core.function.chat.facade.aop.DebugLog
|
||||
|
||||
|
||||
@DebugLog
|
||||
fun log(tag: String, msg: String) {}
|
||||
@@ -0,0 +1,232 @@
|
||||
package com.mogo.eagle.core.function.chat.facade.voice
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.mogo.commons.AbsMogoApplication
|
||||
import com.mogo.commons.voice.AIAssist
|
||||
import com.mogo.commons.voice.IMogoVoiceCmdCallBack
|
||||
import com.mogo.eagle.core.function.api.chat.biz.IMoGoVoiceControlFacade
|
||||
import com.mogo.eagle.core.function.api.chat.biz.IMoGoVoiceControlFacade.IMoGoVoiceCallback
|
||||
import com.mogo.eagle.core.function.chat.facade.bridge.BridgeApi
|
||||
import com.mogo.eagle.core.function.chat.facade.utils.log
|
||||
import com.mogo.service.intent.IMogoIntentListener
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
object VoiceControlFacade: IMoGoVoiceControlFacade, IMogoVoiceCmdCallBack, IMogoIntentListener {
|
||||
|
||||
private const val TAG = "VoiceControl"
|
||||
|
||||
//WakeUp Command (Intent)
|
||||
private const val VOICE_INTENT_CANCEL_CALL_COMMAND = "com.zhidao.commin.cancel" //取消呼叫,取消通话
|
||||
private const val VOICE_INTENT_REFUSE_CALL = "com.ileja.phone.incoming.reject" //挂断
|
||||
|
||||
//unWakeUp Command
|
||||
private const val VOICE_REGISTER_INVITE_JOIN_TEAM = "CMD_CAR_CHAT_INVITE_JOIN_TEAM"
|
||||
private val customInviteJoinTeamArray: Array<String> = arrayOf("邀请加入车队")
|
||||
private const val VOICE_REGISTER_JOIN_TEAM = "CMD_CAR_CHAT_JOIN_TEAM"
|
||||
private val customJoinTeamArray: Array<String> = arrayOf("加入", "加入车队")
|
||||
private const val VOICE_REGISTER_REFUSE_JOIN_TEAM = "CMD_CAR_CHAT_REFUSE_JOIN_TEAM"
|
||||
private val customRefuseJoinTeamArray: Array<String> = arrayOf("拒绝", "拒绝加入车队")
|
||||
private const val VOICE_REGISTER_CANCEL_CALL = "CMD_CAR_CHAT_CANCEL_CALL"
|
||||
private val customCancelCallArray: Array<String> = arrayOf("取消通话", "取消呼叫", "挂断")
|
||||
|
||||
private const val VOICE_INTENT_ANSWER_CALL = "com.ileja.phone.incoming.accept" //接听
|
||||
|
||||
internal const val REQUEST_CLOUD_VOICE_CALL = "云平台请求跟你进行语音通话"
|
||||
private val requestCallYArray: Array<String> = arrayOf("接听")
|
||||
private val requestCallNArray: Array<String> = arrayOf("挂断")
|
||||
|
||||
private val hasRegister by lazy { AtomicBoolean(false) }
|
||||
|
||||
private val listeners by lazy {
|
||||
CopyOnWriteArrayList<WeakReference<IMoGoVoiceCallback>>()
|
||||
}
|
||||
|
||||
private var onCmdAgree: WeakReference<onCmdAgree>? = null
|
||||
|
||||
private var onSpeechFinish: WeakReference<onSpeedFinish>? = null
|
||||
|
||||
private val intentManager by lazy {
|
||||
BridgeApi.intentManager()
|
||||
}
|
||||
|
||||
override fun speak(context: Context, content: String, listener: IMoGoVoiceCallback) {
|
||||
listeners += WeakReference(listener)
|
||||
AIAssist.getInstance(context).speakTTSVoice(content, this)
|
||||
}
|
||||
|
||||
override fun onCmdSelected(cmd: String?) {
|
||||
super.onCmdSelected(cmd)
|
||||
cmd?.let { itx ->
|
||||
listeners
|
||||
.filter {
|
||||
it.get() != null
|
||||
}
|
||||
.forEach {
|
||||
when (itx) {
|
||||
VOICE_REGISTER_CANCEL_CALL -> {
|
||||
log(TAG, "语音免唤醒 取消打电话")
|
||||
it.get()?.onVoiceCancelCall()
|
||||
}
|
||||
VOICE_REGISTER_INVITE_JOIN_TEAM -> {
|
||||
log(TAG, "语音免唤醒 邀請加入车队")
|
||||
it.get()?.onVoiceInviteJoinTeam()
|
||||
}
|
||||
VOICE_REGISTER_JOIN_TEAM -> {
|
||||
log(TAG, "语音免唤醒 加入车队")
|
||||
it.get()?.onVoiceJoinTeam()
|
||||
}
|
||||
VOICE_REGISTER_REFUSE_JOIN_TEAM -> {
|
||||
log(TAG, "语音免唤醒 拒绝加入车队")
|
||||
it.get()?.onVoiceRefuseJoinTeam()
|
||||
}
|
||||
VOICE_INTENT_ANSWER_CALL -> {
|
||||
log(TAG, "语音免唤醒 接收电话邀请")
|
||||
it.get()?.onVoiceAnswerCall()
|
||||
}
|
||||
VOICE_INTENT_REFUSE_CALL -> {
|
||||
log(TAG, "语音免唤醒 来电拒接")
|
||||
it.get()?.onVoiceRefuseCall()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSpeakEnd(speakText: String?) {
|
||||
super.onSpeakEnd(speakText)
|
||||
listeners
|
||||
.filter {
|
||||
it.get() != null
|
||||
}
|
||||
.forEach {
|
||||
it.get()?.onSpeakEnd()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSpeakError(speakText: String?, errorMsg: String?) {
|
||||
super.onSpeakError(speakText, errorMsg)
|
||||
listeners
|
||||
.filter {
|
||||
it.get() != null
|
||||
}
|
||||
.forEach {
|
||||
it.get()?.onSpeakEnd()
|
||||
}
|
||||
}
|
||||
|
||||
override fun register() {
|
||||
if (hasRegister.get()) {
|
||||
return
|
||||
}
|
||||
hasRegister.set(true)
|
||||
AIAssist.getInstance(BridgeApi.context()).registerUnWakeupCommand(VOICE_REGISTER_CANCEL_CALL, customCancelCallArray, this)
|
||||
intentManager?.also {
|
||||
it.registerIntentListener(VOICE_INTENT_CANCEL_CALL_COMMAND, this)
|
||||
it.registerIntentListener(VOICE_INTENT_REFUSE_CALL, this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun registerInviteJoinTeam(context: Context, listener: IMoGoVoiceCallback) {
|
||||
log(TAG, "registerInviteJoinTeam")
|
||||
listeners += WeakReference(listener)
|
||||
AIAssist.getInstance(context)
|
||||
.registerUnWakeupCommand(VOICE_REGISTER_INVITE_JOIN_TEAM, customInviteJoinTeamArray, this)
|
||||
}
|
||||
|
||||
override fun registerJoinTeam(context: Context, listener: IMoGoVoiceCallback) {
|
||||
log(TAG, "registerJoinTeam")
|
||||
listeners += WeakReference(listener)
|
||||
AIAssist.getInstance(context)
|
||||
.registerUnWakeupCommand(VOICE_REGISTER_JOIN_TEAM, customJoinTeamArray, this)
|
||||
AIAssist.getInstance(context)
|
||||
.registerUnWakeupCommand(VOICE_REGISTER_REFUSE_JOIN_TEAM, customRefuseJoinTeamArray, this)
|
||||
}
|
||||
|
||||
override fun unRegisterInviteJoinTeam(context: Context) {
|
||||
AIAssist.getInstance(context).unregisterUnWakeupCommand(VOICE_REGISTER_INVITE_JOIN_TEAM)
|
||||
}
|
||||
|
||||
override fun unRegisterJoinTeam(context: Context) {
|
||||
AIAssist.getInstance(context).unregisterUnWakeupCommand(VOICE_REGISTER_JOIN_TEAM)
|
||||
AIAssist.getInstance(context).unregisterUnWakeupCommand(VOICE_REGISTER_REFUSE_JOIN_TEAM)
|
||||
}
|
||||
|
||||
override fun registerIntentInComingCall(listener: IMoGoVoiceCallback) {
|
||||
listeners += WeakReference(listener)
|
||||
intentManager?.registerIntentListener(VOICE_INTENT_ANSWER_CALL, this)
|
||||
intentManager?.registerIntentListener(VOICE_INTENT_REFUSE_CALL, this)
|
||||
}
|
||||
|
||||
override fun unRegisterIntentInComingCall(context: Context) {
|
||||
intentManager?.unregisterIntentListener(VOICE_INTENT_ANSWER_CALL, this)
|
||||
intentManager?.unregisterIntentListener(VOICE_INTENT_REFUSE_CALL, this)
|
||||
}
|
||||
|
||||
override fun speakAndRegisterCall(onCmdAgree: (Boolean) -> Unit, onSpeakFinish: () -> Unit, listener: IMoGoVoiceCallback) {
|
||||
this.onCmdAgree = WeakReference(onCmdAgree)
|
||||
this.onSpeechFinish = WeakReference(onSpeakFinish)
|
||||
listeners += WeakReference(listener)
|
||||
|
||||
AIAssist.getInstance(AbsMogoApplication.getApp().applicationContext).speakQAndACmd(REQUEST_CLOUD_VOICE_CALL,
|
||||
requestCallYArray, requestCallNArray, object : IMogoVoiceCmdCallBack {
|
||||
|
||||
override fun onCmdAction(speakText: String?) {
|
||||
super.onCmdAction(speakText)
|
||||
log(TAG, "onCmdAction ---> ")
|
||||
this@VoiceControlFacade.onCmdAgree?.get()?.invoke(true)
|
||||
}
|
||||
|
||||
override fun onCmdCancel(speakText: String?) {
|
||||
super.onCmdCancel(speakText)
|
||||
log(TAG, "onCmdCancel ---> ")
|
||||
this@VoiceControlFacade.onCmdAgree?.get()?.invoke(false)
|
||||
}
|
||||
|
||||
override fun onSpeakEnd(speakText: String?) {
|
||||
super.onSpeakEnd(speakText)
|
||||
log(TAG, "onSpeakEnd ---> ")
|
||||
this@VoiceControlFacade.onSpeechFinish?.get()?.invoke()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun stopTTS(context: Context, text: String) {
|
||||
AIAssist.getInstance(context).stopSpeakTts(text)
|
||||
}
|
||||
|
||||
override fun unRegister() {
|
||||
if (!hasRegister.get()) {
|
||||
return
|
||||
}
|
||||
hasRegister.set(false)
|
||||
listeners.clear()
|
||||
AIAssist.getInstance(BridgeApi.context()).unregisterUnWakeupCommand(VOICE_REGISTER_CANCEL_CALL, this)
|
||||
intentManager?.also {
|
||||
it.unregisterIntentListener(VOICE_INTENT_CANCEL_CALL_COMMAND, this)
|
||||
it.unregisterIntentListener(VOICE_INTENT_REFUSE_CALL, this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIntentReceived(cmd: String?, intent: Intent?) {
|
||||
val command = cmd ?: return
|
||||
log(TAG, "handleOnIntentCmd: cmd -> $command")
|
||||
when (command) {
|
||||
VOICE_INTENT_CANCEL_CALL_COMMAND, VOICE_INTENT_REFUSE_CALL -> {
|
||||
log(TAG, "语音唤醒 取消打电话")
|
||||
listeners
|
||||
.filter {
|
||||
it.get() != null
|
||||
}
|
||||
.forEach {
|
||||
it.get()?.onVoiceCancelCall()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias onCmdAgree = ((Boolean) -> Unit)
|
||||
typealias onSpeedFinish = (() -> Unit)
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/module_callchatting_shape_gradient_blue_normal" android:state_pressed="false" />
|
||||
<item android:drawable="@drawable/module_callchatting_shape_gradient_blue_pressed" android:state_pressed="true" />
|
||||
</selector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_16" />
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:endColor="#1F7EFF"
|
||||
android:startColor="#1E57A4" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_16" />
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:endColor="#1363A4"
|
||||
android:startColor="#164079" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/module_callchatting_user_pop_call_bg_normal" android:state_pressed="false" />
|
||||
<item android:drawable="@drawable/module_callchatting_user_pop_call_bg_pressed" android:state_pressed="true" />
|
||||
</selector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_16" />
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:endColor="#585E8B"
|
||||
android:startColor="#4A4C70" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_16" />
|
||||
<gradient
|
||||
android:angle="0"
|
||||
android:endColor="#383C5E"
|
||||
android:startColor="#373856" />
|
||||
|
||||
</shape>
|
||||
|
After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_30" />
|
||||
<solid android:color="#F710121E" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/module_carchatting_team_fragment_close_normal" android:state_pressed="false" />
|
||||
<item android:drawable="@drawable/module_carchatting_team_fragment_close_pressed" android:state_pressed="true" />
|
||||
</selector>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_30" />
|
||||
<gradient
|
||||
android:angle="180"
|
||||
android:endColor="#3F4057"
|
||||
android:startColor="#5E6079" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:topLeftRadius="@dimen/module_call_chat_team_invitation_hawk_eye_bg_corner"
|
||||
android:bottomLeftRadius="@dimen/module_call_chat_team_invitation_hawk_eye_bg_corner"/>
|
||||
<gradient
|
||||
android:angle="135"
|
||||
android:endColor="#22E2FE"
|
||||
android:startColor="#005CFF" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/module_carchatting_team_invitation_hawk_eye_join_normal" android:state_pressed="false" />
|
||||
<item android:drawable="@drawable/module_carchatting_team_invitation_hawk_eye_join_pressed" android:state_pressed="true" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/module_carchatting_team_invitation_hawk_eye_refuse_normal" android:state_pressed="false" />
|
||||
<item android:drawable="@drawable/module_carchatting_team_invitation_hawk_eye_refuse_pressed" android:state_pressed="true" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/module_carchatting_team_invitation_join_normal" android:state_pressed="false" />
|
||||
<item android:drawable="@drawable/module_carchatting_team_invitation_join_pressed" android:state_pressed="true" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/module_carchatting_team_invitation_refuse_normal" android:state_pressed="false" />
|
||||
<item android:drawable="@drawable/module_carchatting_team_invitation_refuse_pressed" android:state_pressed="true" />
|
||||
</selector>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_11" />
|
||||
<gradient
|
||||
android:angle="180"
|
||||
android:endColor="#1DAAA5"
|
||||
android:startColor="#37DED9" />
|
||||
</shape>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/module_carchatting_team_quit_normal" android:state_pressed="false" />
|
||||
<item android:drawable="@drawable/module_carchatting_team_quit_pressed" android:state_pressed="true" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_23" />
|
||||
<solid android:color="#FF1F2131" />
|
||||
</shape>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_11" />
|
||||
<gradient
|
||||
android:angle="180"
|
||||
android:endColor="#4E5069"
|
||||
android:startColor="#4E5069" />
|
||||
</shape>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="@dimen/dp_11" />
|
||||
<gradient
|
||||
android:angle="180"
|
||||
android:endColor="#256BFF"
|
||||
android:startColor="#5CC1FF" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#3B4577" />
|
||||
<corners android:radius="@dimen/module_call_chat_calling_bg_radius" />
|
||||
</shape>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<gradient
|
||||
|
||||
android:endColor="#595FA2"
|
||||
android:startColor="#43508E"></gradient>
|
||||
<size
|
||||
android:width="@dimen/dp_80"
|
||||
android:height="@dimen/dp_80" />
|
||||
</shape>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/module_call_chat_team_fragment_margin"
|
||||
android:elevation="@dimen/dp_10"
|
||||
android:background="@drawable/module_carchatting_team_fragment_bg"
|
||||
android:clickable="true"
|
||||
android:padding="@dimen/module_call_chat_team_fragment_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_num"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/module_call_chat_team_fragment_close_view_height"
|
||||
android:gravity="center_vertical"
|
||||
android:text="车队人数"
|
||||
android:textColor="@color/module_carchatting_white_color"
|
||||
android:textSize="@dimen/module_call_chat_team_fragment_num_text_size"
|
||||
app:layout_constraintBaseline_toBaselineOf="@+id/iv_close"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_close"
|
||||
android:layout_width="@dimen/module_call_chat_team_fragment_close_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_fragment_close_view_height"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/module_carchatting_team_fragment_close"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_teammates"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/module_call_chat_team_fragment_teammates__margin_top"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/iv_close" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,262 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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="@dimen/module_call_chat_state_hawk_eye_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_hawk_eye_height"
|
||||
android:background="@drawable/module_carchatting_vr_calling_bg"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/module_carchatting_rl_match_init_view"
|
||||
android:layout_width="@dimen/module_call_chat_view_match_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_hawk_eye_height"
|
||||
android:background="@drawable/module_callchatting_shape_gradient_blue"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/module_call_chat_match_iv_width"
|
||||
android:layout_height="@dimen/module_call_chat_match_iv_height"
|
||||
android:layout_marginStart="@dimen/dp_16"
|
||||
android:src="@mipmap/module_callchatting_match"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/module_call_chat_view_margin_end"
|
||||
android:text="@string/module_car_chat_match_tip"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_call_chat_match_text_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/module_carchatting_rl_matching_view"
|
||||
android:layout_width="@dimen/module_call_chat_state_hawk_eye_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_hawk_eye_height"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/dp_30"
|
||||
android:text="@string/module_car_chat_matching"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_call_chat_matching_textsize"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_carchatting_tv_cancel_match"
|
||||
android:layout_width="@dimen/module_call_chat_state_hawk_eye_height"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="@string/module_car_chat_matching_cancel"
|
||||
android:textColor="@color/module_carchatting_red_color"
|
||||
android:textSize="@dimen/module_call_chat_matching_text_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:layout_width="1px"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/module_call_chat_match_view_line_margin_top_bottom"
|
||||
android:layout_marginBottom="@dimen/module_call_chat_match_view_line_margin_top_bottom"
|
||||
android:background="@color/module_carchatting_match_line"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/module_carchatting_tv_cancel_match"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/module_carchatting_rl_call_view"
|
||||
android:layout_width="@dimen/module_call_chat_state_hawk_eye_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_hawk_eye_height"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_carchatting_call_head"
|
||||
android:layout_width="@dimen/module_call_chat_calling_iv_hawk_eye_width_height"
|
||||
android:layout_height="@dimen/module_call_chat_calling_iv_hawk_eye_width_height"
|
||||
android:clickable="true"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@mipmap/module_carchatting_hawk_eye_default_head_img"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_carchatting_call_hangUp"
|
||||
android:layout_width="@dimen/module_call_chat_hawk_eye_circle_btn_size"
|
||||
android:layout_height="@dimen/module_call_chat_hawk_eye_circle_btn_size"
|
||||
android:layout_marginEnd="@dimen/module_call_chat_calling_iv_hawk_eye_margin_left_right"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@mipmap/module_carchatting_launcher_calling_hangup"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_carchatting_call_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/module_call_chat_calling_text_hawk_eye_margin_top_bottom"
|
||||
android:text="@string/module_car_chat_matching_wait"
|
||||
android:textColor="@color/module_carchatting_hawk_eye_status_color"
|
||||
android:textSize="@dimen/module_call_chat_calling_text_hawk_eye_time_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/module_carchatting_call_nickname" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_carchatting_call_nickname"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/dp_22"
|
||||
android:layout_marginTop="@dimen/module_call_chat_calling_text_hawk_eye_margin_top_bottom"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_call_chat_calling_text_hawk_eye_name_size"
|
||||
app:layout_constraintStart_toEndOf="@+id/module_carchatting_call_head"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/module_carchatting_team_rl_view"
|
||||
android:layout_width="@dimen/module_call_chat_state_hawk_eye_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_hawk_eye_height"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_carchatting_iv_team_quit"
|
||||
android:layout_width="@dimen/module_call_chat_hawk_eye_circle_btn_size"
|
||||
android:layout_height="@dimen/module_call_chat_hawk_eye_circle_btn_size"
|
||||
android:layout_marginRight="@dimen/module_call_chat_calling_iv_hawk_eye_margin_left_right"
|
||||
android:src="@drawable/module_carchatting_team_quit"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/module_carchatting_team_ll_head"
|
||||
android:layout_width="@dimen/module_call_chat_team_ll_head_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_hawk_eye_height"
|
||||
android:gravity="center"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_carchatting_team_num"
|
||||
android:layout_width="@dimen/module_call_chat_team_num_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_num_width"
|
||||
android:background="@drawable/module_carchatting_vr_team_num_bg"
|
||||
android:gravity="center"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_call_chat_team_num_text_size"
|
||||
android:visibility="gone" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/module_carchatting_civ_head0"
|
||||
android:layout_width="@dimen/module_call_chat_team_head_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_head_view_height"
|
||||
android:src="@mipmap/module_carchatting_default_head_img"
|
||||
android:translationZ="@dimen/dp_1_5"
|
||||
android:visibility="gone"
|
||||
app:civ_border_color="#FFFFFF"
|
||||
app:civ_border_width="2dp" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/module_carchatting_civ_head1"
|
||||
android:layout_width="@dimen/module_call_chat_team_head_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_head_view_height"
|
||||
android:layout_marginLeft="@dimen/module_call_chat_team_head_view_left_margin"
|
||||
android:src="@mipmap/module_carchatting_default_head_img"
|
||||
android:translationZ="@dimen/dp_1"
|
||||
android:visibility="gone"
|
||||
app:civ_border_color="#FFFFFF"
|
||||
app:civ_border_width="2dp" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/module_carchatting_civ_head2"
|
||||
android:layout_width="@dimen/module_call_chat_team_head_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_head_view_height"
|
||||
android:layout_marginLeft="@dimen/module_call_chat_team_head_view_left_margin"
|
||||
android:src="@mipmap/module_carchatting_default_head_img"
|
||||
android:translationZ="@dimen/dp_0_5"
|
||||
android:visibility="gone"
|
||||
app:civ_border_color="#FFFFFF"
|
||||
app:civ_border_width="2dp" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/module_carchatting_civ_head3"
|
||||
android:layout_width="@dimen/module_call_chat_team_head_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_head_view_height"
|
||||
android:layout_marginLeft="@dimen/module_call_chat_team_head_view_left_margin4"
|
||||
android:layout_marginTop="@dimen/module_call_chat_team_head_view_top_margin"
|
||||
android:src="@mipmap/module_carchatting_default_head_img"
|
||||
android:translationZ="@dimen/dp_0_1"
|
||||
android:visibility="gone"
|
||||
app:civ_border_color="#FFFFFF"
|
||||
app:civ_border_width="2dp" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/module_carchatting_civ_head4"
|
||||
android:layout_width="@dimen/module_call_chat_team_head_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_head_view_height"
|
||||
android:layout_marginLeft="@dimen/module_call_chat_team_head_view_left_margin"
|
||||
android:layout_marginTop="@dimen/module_call_chat_team_head_view_top_margin"
|
||||
android:src="@mipmap/module_carchatting_default_head_img"
|
||||
android:translationZ="@dimen/dp_0"
|
||||
android:visibility="gone"
|
||||
app:civ_border_color="#FFFFFF"
|
||||
app:civ_border_width="2dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_carchatting_team_tv_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/module_call_chat_team_info_margin_bottom"
|
||||
android:text="@string/module_car_chat_team_info"
|
||||
android:textColor="#7FFFFFFF"
|
||||
android:textSize="@dimen/module_call_chat_team_info_text_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/module_carchatting_team_ll_head" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/module_call_chat_team_chatting_margin_top"
|
||||
android:text="@string/module_car_chat_team_calling"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_call_chat_team_chatting_text_size"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toEndOf="@+id/module_carchatting_team_ll_head"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,257 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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="@dimen/module_call_chat_state_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_height"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/module_carchatting_rl_match_init_view"
|
||||
android:layout_width="@dimen/module_call_chat_view_match_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_height"
|
||||
android:background="@drawable/module_callchatting_shape_gradient_blue"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/module_call_chat_match_iv_width"
|
||||
android:layout_height="@dimen/module_call_chat_match_iv_height"
|
||||
android:layout_marginStart="@dimen/module_call_chat_match_iv_left_margin"
|
||||
android:src="@mipmap/module_callchatting_match"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/module_call_chat_view_margin_end"
|
||||
android:text="@string/module_car_chat_match_tip"
|
||||
android:textColor="@color/module_carchatting_white_color"
|
||||
android:textSize="@dimen/module_call_chat_match_text_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/module_carchatting_rl_matching_view"
|
||||
android:layout_width="@dimen/module_call_chat_state_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_height"
|
||||
android:background="@drawable/module_carchatting_launcher_calling_bg"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/module_call_chat_match_view_left_margin"
|
||||
android:text="@string/module_car_chat_matching"
|
||||
android:textColor="@color/module_carchatting_white_color"
|
||||
android:textSize="@dimen/module_call_chat_matching_text_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_carchatting_tv_cancel_match"
|
||||
android:layout_width="90px"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="@dimen/module_call_chat_match_view_right_margin"
|
||||
android:gravity="center"
|
||||
android:text="@string/module_car_chat_matching_cancel"
|
||||
android:textColor="@color/module_carchatting_red_color"
|
||||
android:textSize="@dimen/module_call_chat_matching_text_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:layout_width="1px"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/module_call_chat_match_view_line_margin_top_bottom"
|
||||
android:layout_marginEnd="@dimen/module_call_chat_match_view_right_margin"
|
||||
android:layout_marginBottom="@dimen/module_call_chat_match_view_line_margin_top_bottom"
|
||||
android:background="@color/module_carchatting_match_line"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/module_carchatting_tv_cancel_match"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/module_carchatting_rl_call_view"
|
||||
android:layout_width="@dimen/module_call_chat_state_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_height"
|
||||
android:background="@drawable/module_carchatting_launcher_calling_bg"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_carchatting_call_head"
|
||||
android:layout_width="@dimen/module_call_chat_calling_iv_width_height"
|
||||
android:layout_height="@dimen/module_call_chat_calling_iv_width_height"
|
||||
android:layout_marginStart="@dimen/module_call_chat_calling_iv_margin_left_right"
|
||||
android:clickable="true"
|
||||
android:src="@mipmap/module_carchatting_default_head_img"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_carchatting_call_hangUp"
|
||||
android:layout_width="@dimen/module_call_chat_calling_iv_width_height"
|
||||
android:layout_height="@dimen/module_call_chat_calling_iv_width_height"
|
||||
android:layout_marginEnd="@dimen/module_call_chat_calling_iv_margin_left_right"
|
||||
android:src="@mipmap/module_carchatting_launcher_calling_hangup"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_carchatting_call_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/module_call_chat_calling_text_margin_top_bottom"
|
||||
android:text="@string/module_car_chat_matching_wait"
|
||||
android:textColor="@color/module_carchatting_status_color"
|
||||
android:textSize="@dimen/module_call_chat_calling_text_time_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/module_carchatting_call_nickname" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_carchatting_call_nickname"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/dp_22"
|
||||
android:layout_marginTop="@dimen/module_call_chat_calling_text_margin_top_bottom"
|
||||
android:textColor="@color/module_carchatting_white_color"
|
||||
android:textSize="@dimen/module_call_chat_calling_text_name_size"
|
||||
app:layout_constraintStart_toEndOf="@+id/module_carchatting_call_head"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/module_carchatting_team_rl_view"
|
||||
android:layout_width="@dimen/module_call_chat_state_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_height"
|
||||
android:background="@drawable/module_carchatting_launcher_calling_bg"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/module_carchatting_team_ll_head"
|
||||
android:layout_width="@dimen/dp_160"
|
||||
android:layout_height="@dimen/module_call_chat_state_height"
|
||||
android:gravity="center"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/module_carchatting_civ_head0"
|
||||
android:layout_width="@dimen/module_call_chat_team_head_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_head_view_height"
|
||||
android:src="@mipmap/module_carchatting_default_head_img"
|
||||
android:translationZ="@dimen/dp_1_5"
|
||||
app:civ_border_color="#FFFFFF"
|
||||
app:civ_border_width="2dp" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/module_carchatting_civ_head1"
|
||||
android:layout_width="@dimen/module_call_chat_team_head_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_head_view_height"
|
||||
android:layout_marginLeft="@dimen/module_call_chat_team_head_view_left_margin"
|
||||
android:src="@mipmap/module_carchatting_default_head_img"
|
||||
android:translationZ="@dimen/dp_1"
|
||||
app:civ_border_color="#FFFFFF"
|
||||
app:civ_border_width="2dp" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/module_carchatting_civ_head2"
|
||||
android:layout_width="@dimen/module_call_chat_team_head_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_head_view_height"
|
||||
android:layout_marginLeft="@dimen/module_call_chat_team_head_view_left_margin"
|
||||
android:src="@mipmap/module_carchatting_default_head_img"
|
||||
android:translationZ="@dimen/dp_0_5"
|
||||
android:visibility="gone"
|
||||
app:civ_border_color="#FFFFFF"
|
||||
app:civ_border_width="2dp" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/module_carchatting_civ_head3"
|
||||
android:layout_width="@dimen/module_call_chat_team_head_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_head_view_height"
|
||||
android:layout_marginLeft="@dimen/module_call_chat_team_head_view_left_margin4"
|
||||
android:layout_marginTop="@dimen/module_call_chat_team_head_view_top_margin"
|
||||
android:src="@mipmap/module_carchatting_default_head_img"
|
||||
android:translationZ="@dimen/dp_0_1"
|
||||
android:visibility="gone"
|
||||
app:civ_border_color="#FFFFFF"
|
||||
app:civ_border_width="2dp" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/module_carchatting_civ_head4"
|
||||
android:layout_width="@dimen/module_call_chat_team_head_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_head_view_height"
|
||||
android:layout_marginLeft="@dimen/module_call_chat_team_head_view_left_margin"
|
||||
android:layout_marginTop="@dimen/module_call_chat_team_head_view_top_margin"
|
||||
android:src="@mipmap/module_carchatting_default_head_img"
|
||||
android:translationZ="@dimen/dp_0"
|
||||
android:visibility="gone"
|
||||
app:civ_border_color="#FFFFFF"
|
||||
app:civ_border_width="2dp" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/module_call_chat_team_calling_left_margin"
|
||||
android:layout_marginTop="@dimen/module_call_chat_team_calling_top_margin"
|
||||
android:text="@string/module_car_chat_team_calling"
|
||||
android:textColor="@color/module_carchatting_nick_color"
|
||||
android:textSize="@dimen/module_call_chat_team_calling_size"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@id/module_carchatting_team_tv_info"
|
||||
app:layout_constraintStart_toEndOf="@+id/module_carchatting_team_ll_head" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_carchatting_team_tv_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/module_call_chat_team_info_left_margin"
|
||||
android:layout_marginBottom="@dimen/module_call_chat_team_info_bottom_margin"
|
||||
android:text="@string/module_car_chat_team_info"
|
||||
android:textColor="@color/module_carchatting_status_color"
|
||||
android:textSize="@dimen/module_call_chat_team_info_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/module_carchatting_team_ll_head" />
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_carchatting_iv_team_quit"
|
||||
android:layout_width="@dimen/module_call_chat_team_quit_view_width"
|
||||
android:layout_height="@dimen/module_call_chat_team_quit_view_height"
|
||||
android:layout_marginRight="@dimen/module_call_chat_team_quit_right_margin"
|
||||
android:src="@drawable/module_carchatting_team_quit"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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="@dimen/module_call_chat_state_incoming_hawk_eye_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_incoming_hawk_eye_height"
|
||||
android:background="@drawable/module_carchatting_vr_calling_bg"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/module_carchatting_rl_incoming_view"
|
||||
android:layout_width="@dimen/module_call_chat_state_incoming_hawk_eye_width"
|
||||
android:layout_height="@dimen/module_call_chat_state_incoming_hawk_eye_height"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_carchatting_call_head"
|
||||
android:layout_width="@dimen/module_call_chat_incoming_aisdk_tag_width"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/module_carchatting_aicloud_incoming"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_carchatting_incoming_answer"
|
||||
android:layout_width="@dimen/module_call_chat_hawk_eye_incoming_circle_btn_size"
|
||||
android:layout_height="@dimen/module_call_chat_hawk_eye_incoming_circle_btn_size"
|
||||
android:layout_marginEnd="@dimen/module_call_chat_calling_iv_hawk_eye_margin_left_right"
|
||||
android:layout_marginRight="@dimen/module_call_chat_state_incoming_hawk_eye_call_margin_right"
|
||||
android:clickable="true"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@mipmap/module_callchatting_launcher_incoming_answer"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/module_carchatting_incoming_hangUp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/module_carchatting_incoming_hangUp"
|
||||
android:layout_width="@dimen/module_call_chat_hawk_eye_incoming_circle_btn_size"
|
||||
android:layout_height="@dimen/module_call_chat_hawk_eye_incoming_circle_btn_size"
|
||||
android:layout_marginEnd="@dimen/module_call_chat_calling_iv_hawk_eye_margin_left_right"
|
||||
android:clickable="true"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@mipmap/module_callchatting_launcher_incoming_hangup"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/module_carchatting_call_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/module_call_chat_state_incoming_hawk_eye_margin_left"
|
||||
android:layout_marginBottom="@dimen/module_call_chat_calling_text_hawk_eye_margin_top_bottom"
|
||||
android:text="请求语音通话..."
|
||||
android:textColor="@color/module_carchatting_hawk_eye_status_color"
|
||||
android:textSize="@dimen/module_call_chat_calling_text_hawk_eye_time_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toRightOf="@+id/module_carchatting_call_head" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/module_call_chat_calling_text_hawk_eye_margin_top_bottom"
|
||||
android:text="云平台"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="@dimen/module_call_chat_calling_text_hawk_eye_name_size"
|
||||
app:layout_constraintStart_toStartOf="@+id/module_carchatting_call_time"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||