Commit 60857c09 by 刘鹏

Merge branch 'd/4.4.10' into 'release'

D/4.4.10

See merge request app_android_lib/YDL-Component!355
parents c2689a91 4db0d90d
......@@ -22,21 +22,36 @@ interface IAudioImService : IProvider{
/**
* rtm登录返回并登录状态
*/
fun loginRtm(userId:String?,event: (isSuccess: Boolean, msg: String?) -> Unit)
fun loginRtm(userId: String?, event: (isSuccess: Boolean, msg: String?) -> Unit)
/**
* rtm登录
*/
fun loginRtm(userId:String?)
fun loginRtm(userId: String?)
/**
* rtm退出登录
*/
fun logoutRtm()
/**
* 拨打语音电话转axb弹窗
*/
fun openAxbDialog(activity: Activity?, type:Int, phoneNumber: String?)
fun openAxbDialog(activity: Activity?, type: Int, phoneNumber: String?)
fun openPermissionRejectDialog(activity: Activity?, callback: (() -> Unit))
/**
* 事件上报
*/
fun callEventSave(status: String, res: String, session: String?, line: String)
fun initRtcNetQuality()
fun initAgoraRtc(context: Context)
fun reportCallEvent(type: String, name: String, desc: String, retCode: Int?)
fun callCheck(context: Context, calleeUid: String? = null, delay: Long = 0)
}
\ No newline at end of file
......@@ -190,4 +190,6 @@ interface IImService : IProvider {
fun dismissConsultServiceDialog();
/**群聊*/
fun startTeamSession(activity: Activity, tid: String, doctorId: String)
fun getSystemConfig(key:String)
}
\ No newline at end of file
......@@ -37,7 +37,7 @@ android {
multiDexEnabled true
ndk {
abiFilters "arm64-v8a","armeabi-v7a" // 指定要ndk需要兼容的架构(这样其他依赖包里mips,x86,armeabi,arm-v8之类的so会被过滤掉)
abiFilters "arm64-v8a"/*,"armeabi-v7a"*/ // 指定要ndk需要兼容的架构(这样其他依赖包里mips,x86,armeabi,arm-v8之类的so会被过滤掉)
}
}
......@@ -168,6 +168,8 @@ android {
packagingOptions {
exclude 'META-INF/proguard/coroutines.pro'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
}
dataBinding {
enabled true
......@@ -177,6 +179,9 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
debugImplementation('com.ydl.debugkit:debugkit:1.0.0'){
exclude group: "androidx.sqlite"
}
implementation 'com.github.feeeei:CircleSeekbar:v1.1.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation(rootProject.ext.dependencies["appcompat-v7"])
......@@ -213,6 +218,7 @@ dependencies {
implementation project(':m-muse')
implementation project(':m-im')
implementation project(":api:im")
implementation project(":api:audioim")
implementation project(':m-dynamic')
implementation project(':m-course')
implementation project(":api:course")
......
......@@ -27,7 +27,7 @@ import com.ydl.component.mvp.DemoContract
import com.ydl.component.mvp.DemoPresenter
import com.ydl.component.route.PlatformTempCommonRouteImpl
import com.ydl.component.rtc.MDTLoginActivity
import com.ydl.confide.home.ConfideHomeActivity
import com.ydl.confide.api.ConfideRoute
import com.ydl.ydlcommon.data.http.ThrowableConsumer
import com.ydl.ydlcommon.modular.ModularServiceManager
import com.ydl.ydlcommon.mvp.lce.BaseLceActivity
......@@ -179,7 +179,8 @@ class MainActivity : BaseLceActivity<DemoContract.View, DemoContract.Presenter>(
YDLavManager.instances.login(
ModularServiceManager.provide(IUserService::class.java).getUserInfo()?.uid
)
startActivity(Intent(this, ConfideHomeActivity::class.java))
ARouter.getInstance().build(Uri.parse(ConfideRoute.R_CONFIDE_HOME+"?confidedId=299")).navigation()
// startActivity(Intent(this, ConfideHomeActivity::class.java))
}
ModularServiceManager.provide(IConsultantService::class.java).requestGuideData()
......
......@@ -4,9 +4,10 @@ import android.app.ActivityManager;
import android.content.Context;
import android.os.Build;
import android.os.Process;
import androidx.multidex.MultiDex;
import android.webkit.WebView;
import androidx.multidex.MultiDex;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.meituan.android.walle.WalleChannelReader;
import com.tencent.qcloud.tuicore.calling.trtccalling.model.TRTCCalling;
......@@ -14,6 +15,7 @@ import com.umeng.analytics.MobclickAgent;
import com.umeng.commonsdk.UMConfigure;
import com.umeng.socialize.PlatformConfig;
import com.umeng.socialize.UMShareAPI;
import com.ydl.audioim.api.IAudioImService;
import com.ydl.component.route.PlatformTempCommonRouteImpl;
import com.ydl.confide.BuildConfig;
import com.ydl.media.audio.AudioPlayer;
......@@ -21,6 +23,7 @@ import com.ydl.media.audio.manager.MediaSessionManager;
import com.ydl.media.audio.manager.NotifyManager;
import com.ydl.ydl_image.manager.YDLImageCacheManager;
import com.ydl.ydlcommon.base.BaseApp;
import com.ydl.ydlcommon.modular.ModularServiceManagerKt;
import com.ydl.ydlcommon.router.YdlCommonRouterManager;
import com.ydl.ydlcommon.utils.AppProgressUtils;
import com.ydl.ydlcommon.utils.Utils;
......@@ -29,7 +32,6 @@ import com.yidianling.common.tools.ToastUtil;
import com.yidianling.consultant.preview.TestImageLoader;
import com.yidianling.consultant.preview.ZoomMediaLoader;
import com.yidianling.course.lifeCallback.CoursePlayLifecycle;
import com.yidianling.course.widget.AudioPlayView;
/**
......@@ -71,12 +73,14 @@ public class ComponentTestApp extends BaseApp {
registerActivityLifecycleCallbacks(new CoursePlayLifecycle());
ModularServiceManagerKt.findRouteService(IAudioImService.class).initAgoraRtc(this);
Fresco.initialize(this);
TRTCCalling.sharedInstance(this).setMdtCallBack(new TRTCCalling.MdtCallBack() {
@Override
public void onReceiveNewInvitation(String roomId) {
ToastUtil.toastShort("电话邀请"+roomId);
ToastUtil.toastShort("电话邀请" + roomId);
}
@Override
......
......@@ -14,7 +14,7 @@ buildscript {
ydlrouter_version = '1.2.3'
constrait_support_version = '1.0.2'
componentVersion = "0.3.0.44"
componentVersion = "0.3.0.48.4-SNAPSHOT"
}
repositories {
mavenCentral()
......
......@@ -65,7 +65,7 @@ dependencies {
kapt "com.alibaba:arouter-compiler:$arouter_compiler"
api "com.alibaba:arouter-api:$arouter_api"
api "com.ydl:ydl-av:1.4.6"
api "com.ydl:ydl-av:1.4.8"
implementation 'com.volcengine:apm_insight:1.4.6.cn'
api project(':ydl-platform')
......
package com.ydl.audioim
import android.app.Activity
import android.content.Context
import android.text.TextUtils
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.tencent.mmkv.MMKV
import com.ydl.audioim.http.AudioApiRequestUtil
import com.ydl.audioim.http.RtcEvent
import com.ydl.consultantim.ConsultantAudioHomeActivity
import com.ydl.ydl_av.voice.listener.RtcNetInterface
import com.ydl.ydl_av.voice.manager.YDLVoiceManager
import com.ydl.ydlcommon.utils.log.XLog
import io.agora.rtc.Constants
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import java.io.File
import java.io.FileOutputStream
class NetQuality(
val activity: Activity,
val channelId: String?,
val voiceManage: YDLVoiceManager?,
val tvNetUp: TextView,
val ivNetQuality: ImageView,
val tvNetQuality: TextView,
val tvNetDown: TextView,
val tvNetDelay: TextView,
val layoutQualityGroup: View
) : RtcNetInterface {
private var lastOnNetQualityDownReport = 0L
private var lastOnNetQualityUpReport = 0L
private var lastOnRemoteNetworkCongestion = 0L
override fun onLocalAudioStats(bitrate: Int, packetLossRate: Int) {
XLog.i("NetQuality", "bitrate:$bitrate,packetLossRate:$packetLossRate")
activity.runOnUiThread {
tvNetUp.text = "${bitrate}Kbps"
}
}
override fun onNetQuality(uid: Int, tx: Int, rx: Int) {
if (tx in 4..6 || rx in 4..6) {
XLog.i("NetQuality", "$uid,tx:$tx,rx:$rx")
}
activity.runOnUiThread {
if (uid == 0) {
when (rx) {
1 -> {
ivNetQuality.setImageResource(R.drawable.audioim_bg_net_excellent)
tvNetQuality.text = "网络优秀"
}
2, 3 -> {
ivNetQuality.setImageResource(R.drawable.audioim_bg_net_good)
tvNetQuality.text = "网络良好"
}
4, 5, 6 -> {
ivNetQuality.setImageResource(R.drawable.audioim_bg_net_poor)
tvNetQuality.text = "网络极差"
if (System.currentTimeMillis() - lastOnNetQualityDownReport > 10 * 1000) {
lastOnNetQualityDownReport = System.currentTimeMillis()
AudioApiRequestUtil.reportCallEvent(
channelId,
RtcEvent(RtcEvent.Event.networkDownPoor),
retCode = rx
)
}
}
}
if (tx in 4..6) {
if (System.currentTimeMillis() - lastOnNetQualityUpReport > 10 * 1000) {
lastOnNetQualityUpReport = System.currentTimeMillis()
AudioApiRequestUtil.reportCallEvent(
channelId,
RtcEvent(RtcEvent.Event.networkUpPoor),
retCode = rx
)
}
}
}
}
}
override fun onNetworkTypeChanged(type: Int) {
AudioApiRequestUtil.reportCallEvent(channelId, RtcEvent(RtcEvent.Event.networkTypeChanged), retCode = type)
}
override fun onRemoteAudioFeel(frozenRate: Int, qoeQuality: Int, reason: Int, mos: Int) {
XLog.i("NetQuality", "frozenRate:$frozenRate,qoeQuality:$qoeQuality,reason:$reason,mos:$mos")
}
override fun onRemoteAudioStateChanged(uid: Int, state: Int, reason: Int, elapsed: Int) {
XLog.i("NetQuality", "uid:$uid,state:$state,reason:$reason,elapsed:$elapsed")
if (reason == Constants.REMOTE_AUDIO_REASON_NETWORK_CONGESTION && state == Constants.REMOTE_AUDIO_STATE_FROZEN) {
val qualityValue = MMKV.defaultMMKV().getString("network_quality_config", "0")
if (TextUtils.equals(qualityValue, "1")) {
if (System.currentTimeMillis() - lastOnRemoteNetworkCongestion > 30 * 1000) {
lastOnRemoteNetworkCongestion = System.currentTimeMillis()
voiceManage?.getVoiceApi()
?.playEffect(3001, getNetLowEffect(activity))
}
if (activity is AudioHomeActivity) {
activity.showNetStatus("对方的网络状况不佳", 0)
} else if (activity is ConsultantAudioHomeActivity) {
activity.showNetStatus("对方的网络状况不佳", 0)
}
}
}
}
override fun onRemoteAudioStats(quality: Int, delay: Int, bitrate: Int, audioLossRate: Int) {
XLog.i("NetQuality", "quality:$quality,delay:$delay,bitrate:$bitrate,audioLossRate:$audioLossRate")
activity.runOnUiThread {
tvNetDown.text = "${bitrate}Kbps"
tvNetDelay.text = "${delay}ms"
layoutQualityGroup.visibility = View.VISIBLE
}
}
companion object {
fun getHandUpEffect(context: Context): String {
return context.filesDir.absolutePath + "/sounds/effect_hand_up.mp3"
}
fun getNetLowEffect(context: Context): String {
return context.filesDir.absolutePath + "/sounds/effect_net_quality_low.m4a"
}
fun copySoundFile(context: Context?) {
if (context == null) return
Single.create<Int> {
val filesDir = context.filesDir
val soundFile = File(filesDir, "sounds")
if (!soundFile.exists()) {
soundFile.mkdir()
}
val filesName = arrayOf("effect_hand_up.mp3", "effect_net_quality_low.m4a")
filesName.forEach {
val file = File(soundFile, it)
if (!file.exists()) {
file.createNewFile()
val fileOutputStream = FileOutputStream(file)
context.assets.open(it).copyTo(fileOutputStream)
fileOutputStream.close()
}
}
}
.observeOn(Schedulers.io())
.subscribe()
}
}
}
\ No newline at end of file
package com.ydl.audioim.bean
data class CallCheckBean(
var callLongTime: Int = 0,
var callTotalTime: Int = 0,
var remainingTime: Int = 0,
var channelId: String? = null,
var headImage: String? = null,
var relationId: String? = null,
var session: String? = null,
var userId: String? = null,
var userName: String? = null
)
\ No newline at end of file
......@@ -4,9 +4,11 @@ package com.ydl.audioim.bean
* 星链-倾诉日志请求参数
* */
data class CallEventRequestBody(
var session :String?,
var line :String,
var status :String,
var response :String,
var eventTime :String
var session: String?,
var line: String,
var status: String,
var response: String? = null,
var eventTime: String,
var remark: String? = null,
var eventType: String? = null
)
package com.ydl.audioim.http
import androidx.annotation.IntDef
import com.google.gson.Gson
import com.ydl.audioim.bean.AgoraTokenResponse
import com.ydl.audioim.bean.CallEventRequestBody
......@@ -16,8 +17,10 @@ import com.ydl.ydlcommon.utils.NetworkParamsUtils
import com.ydl.ydlcommon.utils.TimeUtil
import com.ydl.ydlnet.YDLHttpUtils
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import okhttp3.MediaType
import okhttp3.RequestBody
import java.util.*
/**
* @author jiucheng
......@@ -106,29 +109,151 @@ class AudioApiRequestUtil {
.listenToken(NetworkParamsUtils.getMaps(cmd))
}
fun callEndStatusUpdate(
channelId: String,
endStatus: Int,
msg: String
): Observable<BaseAPIResponse<Any>> {
return YDLHttpUtils.obtainApi(AudioNetAPi::class.java)
.callEndStatusUpdate(channelId, endStatus, msg)
}
fun callEventSave(
session: String?,
line: String,
status: String,
response: String
response: String?,
remark: String? = null,
eventType: String? = null
): Observable<BaseAPIResponse<Any>> {
var eventTime = TimeUtil.getNowDatetime()
var param = CallEventRequestBody(session, line, status, response, eventTime);
var str = Gson().toJson(param)
val eventTime = TimeUtil.getNowDatetime()
val param = CallEventRequestBody(session, line, status, response, eventTime, remark, eventType);
val str = Gson().toJson(param)
val body = RequestBody.create(
MediaType.parse("application/json; charset=utf-8"),
str
) as RequestBody
return YDLHttpUtils.obtainApi(AudioNetAPi::class.java).callEventSave(body)
}
fun reportCallEvent(
session: String?,
event: CallEvent,
retCode: Int? = null,
errorCode: Int? = null,
errorReason: String? = null,
@ReportLevel reportLevel: Int? = null
) {
if (session == null) return
val type = event.eventType.name.lowercase(Locale.getDefault())
val resp = hashMapOf<String, Any>(
"name" to event.name,
"desc" to event.desc,
"reportLevel" to (reportLevel ?: event.reportLevel),
)
if (retCode != null) {
resp["retCode"] = retCode
}
if (errorCode != null) {
resp["errorCode"] = errorCode
}
if (errorReason != null) {
resp["errorReason"] = errorReason
}
val response = Gson().toJson(resp)
callEventSave(session, "7", "20", response, eventType = type).subscribeOn(Schedulers.io()).subscribe()
}
}
}
const val REPORT_LEVEL_INFO = 1
const val REPORT_LEVEL_WARN = 2
const val REPORT_LEVEL_ERROR = 3
enum class EventType {
RTC, IM, LOGIN, DEVICE
}
@IntDef(REPORT_LEVEL_INFO, REPORT_LEVEL_WARN, REPORT_LEVEL_ERROR)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
annotation class ReportLevel
sealed class CallEvent(val eventType: EventType, val name: String, val desc: String, @ReportLevel val reportLevel: Int)
class RtcEvent(val event: Event) :
CallEvent(EventType.RTC, event.name, event.desc, event.reportLevel) {
@Suppress("EnumEntryName")
enum class Event(val desc: String, @ReportLevel val reportLevel: Int) {
initSdk("初始化SDK", REPORT_LEVEL_WARN),
join("本地加入房间", REPORT_LEVEL_WARN),
joinSuccess("加入房间成功", REPORT_LEVEL_INFO),
joinFail("加入房间失败", REPORT_LEVEL_ERROR),
remoteJoin("远端加入房间", REPORT_LEVEL_WARN),
remoteJoinTimeout("远端加入超时", REPORT_LEVEL_WARN),
leave("本地离开房间", REPORT_LEVEL_WARN),
remoteLeave("远端离开房间", REPORT_LEVEL_WARN),
localFirstFrame("本地音频首帧", REPORT_LEVEL_WARN),
remoteFirstFrame("远端音频首帧", REPORT_LEVEL_WARN),
networkTypeChanged("网络变化", REPORT_LEVEL_WARN),
networkDownPoor("网络下行质量差", REPORT_LEVEL_WARN),
networkUpPoor("网络上行质量差", REPORT_LEVEL_WARN),
connectionChanged("连接状态变化", REPORT_LEVEL_WARN),
errorOccurred("错误发生", REPORT_LEVEL_ERROR),
}
}
class IMEvent(event: Event) : CallEvent(EventType.IM, event.name, event.desc, event.reportLevel) {
@Suppress("EnumEntryName")
enum class Event(val desc: String, @ReportLevel val reportLevel: Int) {
rtmLogin("登录RTM", REPORT_LEVEL_INFO),
rtmLogout("登出RTM", REPORT_LEVEL_INFO),
loginSuccess("登录成功", REPORT_LEVEL_INFO),
loginFail("登录失败", REPORT_LEVEL_ERROR),
onlineStatusChange("在线状态变化", REPORT_LEVEL_WARN),
startCall("开始呼叫", REPORT_LEVEL_INFO),
onPeerReceived("到达对方", REPORT_LEVEL_INFO),
onPeerAccepted("对方已接受", REPORT_LEVEL_INFO),
onPeerRejected("对方已拒绝", REPORT_LEVEL_WARN),
callSuccess("呼叫成功", REPORT_LEVEL_INFO),
callFail("呼叫失败", REPORT_LEVEL_ERROR),
callCancel("呼叫取消", REPORT_LEVEL_WARN),
}
}
class LoginEvent(event: Event) :
CallEvent(EventType.LOGIN, event.name, event.desc, event.reportLevel) {
@Suppress("EnumEntryName")
enum class Event(val desc: String, @ReportLevel val reportLevel: Int) {
userLogin("用户登录", REPORT_LEVEL_INFO),
userLogout("用户登出", REPORT_LEVEL_INFO),
userLoginSuccess("登录成功", REPORT_LEVEL_INFO),
userLoginFail("登录失败", REPORT_LEVEL_ERROR),
}
}
class DeviceEvent(event: Event) :
CallEvent(EventType.DEVICE, event.name, event.desc, event.reportLevel) {
@Suppress("EnumEntryName")
enum class Event(val desc: String, @ReportLevel val reportLevel: Int) {
beForeground("回到前台", REPORT_LEVEL_INFO),
beBackground("进入后台", REPORT_LEVEL_WARN),
onTrimMemory("内存使用等级", REPORT_LEVEL_WARN),
onLowMemory("内存低", REPORT_LEVEL_WARN),
}
}
object LeaveChannelReason {
val LeaveChannelReasonNormal = 0
val LeaveChannelReasonRefuse = 1
val LeaveChannelReasonCancel = 2
val LeaveChannelReasonOfflineWithError = 3
val LeaveChannelReasonOfflineNormal = 4
val LeaveChannelReasonTurnToAXB = 5
val LeaveChannelReasonAnswerTimeOut = 6
val LeaveChannelReasonSystemTimeup = 7
val LeaveChannelReasonJoinChannelTimeOut = 8
val LeaveChannelReasonRtcError = 9
val LeaveChannelReasonKickedByServer = 10
val LeaveChannelReasonPageError = 11
fun getFrom(reason: Int): Int {
return when (reason) {
1, 3, 4, 6, 8 -> 2
7, 10 -> 0
else -> 1
}
}
}
\ No newline at end of file
}
package com.ydl.audioim.http
import com.ydl.audioim.bean.AgoraTokenResponse
import com.ydl.audioim.bean.CallCheckBean
import com.ydl.audioim.bean.ConnectBean
import com.ydl.consultantim.bean.ListenTokenBean
import com.ydl.ydlcommon.base.config.YDL_DOMAIN
......@@ -19,6 +20,10 @@ import retrofit2.http.*
* @date 2018/11/9
*/
interface AudioNetAPi {
@GET("counselor/consultationCall/queryCallInfo")
@Headers(YDL_DOMAIN + YDL_DOMAIN_JAVA)
fun checkCall(@Query("calleeUid") calleeUid: String?): Observable<BaseAPIResponse<CallCheckBean>>
/**
* 通话开始回调
*/
......@@ -33,12 +38,16 @@ interface AudioNetAPi {
// @POST("auth/listen-order/callback/agora/finish")
// fun connectFinish(@Body body:RequestBody): Observable<BaseAPIResponse<Any>>
@Headers(YDL_DOMAIN + YDL_DOMAIN_JAVA, "Content-Type:application/json")
@POST("counselor/device/upload")
fun report(@Body params: Map<String, @JvmSuppressWildcards Any>): Observable<BaseAPIResponse<String>>
/**
* 通话警告与错误回调
*/
@Headers( YDL_DOMAIN + YDL_DOMAIN_JAVA,"Content-Type:application/json")
@Headers(YDL_DOMAIN + YDL_DOMAIN_JAVA, "Content-Type:application/json")
@POST("auth/listen-order/callback/agora")
fun connectException(@Body body:RequestBody): Observable<BaseAPIResponse<Any>>
fun connectException(@Body body: RequestBody): Observable<BaseAPIResponse<Any>>
/**
......@@ -69,9 +78,9 @@ interface AudioNetAPi {
fun listenToken(@FieldMap map: Map<String, String>): Observable<BaseResponse<ListenTokenBean>>
@GET("message/call-end/agora")
@Headers( YDL_DOMAIN + YDL_DOMAIN_JAVA)
fun callEndStatusUpdate(@Query("channelId") channelId:String,@Query("endStatus") endStatus:Int,@Query("msg") msg:String): Observable<BaseAPIResponse<Any>>
// @GET("message/call-end/agora")
// @Headers( YDL_DOMAIN + YDL_DOMAIN_JAVA)
// fun callEndStatusUpdate(@Query("channelId") channelId:String,@Query("endStatus") endStatus:Int,@Query("msg") msg:String): Observable<BaseAPIResponse<Any>>
//倾诉事件上报接口
@Headers(YDL_DOMAIN + YDL_DOMAIN_JAVA, "Content-Type:application/json")
......
......@@ -5,9 +5,21 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import com.alibaba.android.arouter.facade.annotation.Route
import com.google.gson.Gson
import com.ydl.audioim.BuildConfig
import com.ydl.audioim.NetQuality
import com.ydl.audioim.YDLavManager
import com.ydl.audioim.api.IAudioImService
import com.ydl.audioim.http.AudioApiRequestUtil
import com.ydl.audioim.http.REPORT_LEVEL_INFO
import com.ydl.audioim.utils.CallCheck
import com.ydl.audioim.widget.AxbConfirmDialog
import com.ydl.ydl_av.voice.listener.RtcGlobalNet
import com.ydl.ydl_av.voice.manager.YDLVoiceManager
import com.ydl.ydlcommon.modular.findRouteService
import com.ydl.ydlcommon.utils.log.XLog
import com.yidianling.im.api.service.IImService
import io.reactivex.schedulers.Schedulers
/**
* Created by Ykai on 2022/7/26.
......@@ -33,8 +45,13 @@ class AudioImServiceImp : IAudioImService {
YDLavManager.instances.login(userId)
}
override fun logoutRtm() {
YDLavManager.instances.logout()
}
override fun openAxbDialog(activity: Activity?, type: Int, phoneNumber: String?) {
val dialog = AxbConfirmDialog(activity, type, object : AxbConfirmDialog.OnClickEnsureListener {
val dialog =
AxbConfirmDialog(activity, type, object : AxbConfirmDialog.OnClickEnsureListener {
override fun onClickEnsure() {
phoneNumber?.let {
val phoneIntent = Intent(Intent.ACTION_DIAL, Uri.parse("tel:$phoneNumber"))
......@@ -45,7 +62,20 @@ class AudioImServiceImp : IAudioImService {
override fun onClose() {
}
})
})
dialog.show()
}
override fun openPermissionRejectDialog(activity: Activity?, callback: () -> Unit) {
val dialog = AxbConfirmDialog(activity, 3, object : AxbConfirmDialog.OnClickEnsureListener {
override fun onClickEnsure() {
callback.invoke()
}
override fun onClose() {
callback.invoke()
}
})
dialog.show()
}
......@@ -53,7 +83,49 @@ class AudioImServiceImp : IAudioImService {
YDLavManager.instances.callEventSave(status, res, session, line)
}
override fun initRtcNetQuality() {
YDLVoiceManager.enableLastmileTest(true)
YDLVoiceManager.attachGlobalNetListener(RtcGlobalNetQuality(30))
}
override fun initAgoraRtc(context: Context) {
YDLVoiceManager.init(context.applicationContext, BuildConfig.AGORA_APPID)
findRouteService(IImService::class.java).getSystemConfig("network_quality_config")
}
override fun reportCallEvent(type: String, name: String, desc: String, retCode: Int?) {
val resp = hashMapOf<String, Any>(
"name" to name,
"desc" to desc,
"reportLevel" to REPORT_LEVEL_INFO
)
if (retCode != null) {
resp["retCode"] = retCode
}
val toJson = Gson().toJson(resp)
if (YDLVoiceManager.currentChannel.isNullOrEmpty()) return
AudioApiRequestUtil.callEventSave(YDLVoiceManager.currentChannel, "7", "20", toJson, eventType = type)
.subscribeOn(Schedulers.io())
.subscribe()
}
override fun callCheck(context: Context, calleeUid: String?, delay: Long) {
CallCheck.checkCall(context, calleeUid, delay)
}
override fun init(context: Context?) {
NetQuality.copySoundFile(context)
}
}
internal class RtcGlobalNetQuality(private val threshold: Int) : RtcGlobalNet {
private var count = 0
override fun onLastmileQuality(quality: Int) {
if (quality in 4..6) {
count++
if (quality > 3 && count % 10 == 0) {
XLog.i("RtcGlobalNetQuality", quality.toString())
}
}
}
}
\ No newline at end of file
package com.ydl.audioim.utils
import android.content.Context
import android.content.Intent
import com.apm.insight.log.VLog
import com.google.gson.Gson
import com.ydl.audioim.http.AudioNetAPi
import com.ydl.consultantim.ConsultantAudioHomeActivity
import com.ydl.ydl_av.chat.bean.AudioMessageBean
import com.ydl.ydlnet.YDLHttpUtils
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.TimeUnit
object CallCheck {
private var subscribe: Disposable? = null
const val KEY_LATEST_REJECT_CHANNEL = "KeyLatestChannel"
fun checkCall(context: Context, calleeUid: String? = null, delay: Long = 0) {
subscribe?.dispose()
subscribe = Observable.just(1)
.delay(delay, TimeUnit.SECONDS)
.flatMap { YDLHttpUtils.obtainApi(AudioNetAPi::class.java).checkCall(calleeUid) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ resp ->
if (resp.code == "200" && resp.data != null) {
val intent = Intent(context, ConsultantAudioHomeActivity::class.java)
val r = resp.data
val bean = AudioMessageBean(
0, r.channelId, r.userId, r.headImage, r.userName, r.remainingTime, r.callTotalTime,
r.relationId, null, null, null
)
val beanJson = Gson().toJson(bean)
intent.putExtra("param", beanJson)
context.startActivity(intent)
VLog.i("CallCheck", "call check go AudioHomeActivity")
}
}, {
android.util.Log.e("callCheck", it.message)
})
}
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ import android.app.Activity
import android.app.Dialog
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.widget.TextView
import com.ydl.audioim.R
import kotlinx.android.synthetic.main.audioim_dialog_axb_confirm.*
......@@ -17,7 +18,7 @@ import kotlinx.android.synthetic.main.audioim_dialog_axb_confirm.*
class AxbConfirmDialog : Dialog {
private var activity: Activity? = null
private var listener: OnClickEnsureListener? = null
private var type :Int = 1 //弹窗类型,1为手动切换axb布局,2为自动切换axb布局
private var type :Int = 1 //弹窗类型,1为手动切换axb布局,2为自动切换axb布局,3权限拒绝
constructor(activity: Activity?,type:Int, listener: OnClickEnsureListener?) : super(activity) {
this.activity = activity
......@@ -29,7 +30,10 @@ class AxbConfirmDialog : Dialog {
super.onCreate(savedInstanceState)
if (this.type==2){
setContentView(R.layout.audioim_dialog_autoaxb_confirm)
}else{
} else if (this.type == 3) {
setContentView(R.layout.audioim_dialog_autoaxb_confirm)
findViewById<TextView>(R.id.tvTip).text = "您未授权无法使用网络通话\n请点击呼叫使用传统电话"
} else {
setContentView(R.layout.audioim_dialog_axb_confirm)
}
......
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#48CC95" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FF994B" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#E05252" />
</shape>
\ No newline at end of file
......@@ -38,6 +38,85 @@
android:textSize="13sp" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layoutQualityGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="18dp"
android:layout_marginTop="44dp"
android:layout_marginRight="18dp"
android:visibility="gone">
<TextView
android:id="@+id/tvDelay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_net_delay"
android:drawablePadding="4dp"
android:textColor="#99FFFFFF"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tvDown"
app:layout_constraintTop_toTopOf="parent"
tools:text="12ms" />
<TextView
android:id="@+id/tvDown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_net_down"
android:textColor="#99FFFFFF"
android:drawablePadding="4dp"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/tvDelay"
app:layout_constraintRight_toLeftOf="@+id/tvUp"
app:layout_constraintTop_toTopOf="parent"
tools:text="12ms" />
<TextView
android:id="@+id/tvUp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_net_up"
android:textColor="#99FFFFFF"
android:drawablePadding="4dp"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/tvDown"
app:layout_constraintRight_toLeftOf="@+id/layoutQuality"
app:layout_constraintTop_toTopOf="parent"
tools:text="12ms" />
<LinearLayout
android:id="@+id/layoutQuality"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:orientation="horizontal"
android:gravity="center"
app:layout_constraintLeft_toRightOf="@+id/tvUp"
app:layout_constraintRight_toRightOf="parent">
<ImageView
android:id="@+id/ivQuality"
tools:src="@drawable/audioim_bg_net_excellent"
android:layout_width="6dp"
android:layout_height="6dp"/>
<TextView
android:id="@+id/tvQuality"
android:layout_width="wrap_content"
android:layout_marginLeft="4dp"
android:layout_height="wrap_content"
android:textColor="#99FFFFFF"
android:textSize="13sp"
tools:text="12ms" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<RelativeLayout
android:id="@+id/rl_head"
......
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:background="@color/platform_color_80353535">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layoutQualityGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="18dp"
android:layout_marginTop="44dp"
android:layout_marginRight="18dp"
android:visibility="gone">
<TextView
android:id="@+id/tvDelay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_net_delay"
android:drawablePadding="4dp"
android:textColor="#99FFFFFF"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tvDown"
app:layout_constraintTop_toTopOf="parent"
tools:text="12ms" />
<TextView
android:id="@+id/tvDown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_net_down"
android:textColor="#99FFFFFF"
android:drawablePadding="4dp"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/tvDelay"
app:layout_constraintRight_toLeftOf="@+id/tvUp"
app:layout_constraintTop_toTopOf="parent"
tools:text="12ms" />
<TextView
android:id="@+id/tvUp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_net_up"
android:textColor="#99FFFFFF"
android:drawablePadding="4dp"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/tvDown"
app:layout_constraintRight_toLeftOf="@+id/layoutQuality"
app:layout_constraintTop_toTopOf="parent"
tools:text="12ms" />
<LinearLayout
android:id="@+id/layoutQuality"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:orientation="horizontal"
android:gravity="center"
app:layout_constraintLeft_toRightOf="@+id/tvUp"
app:layout_constraintRight_toRightOf="parent">
<ImageView
android:id="@+id/ivQuality"
tools:src="@drawable/audioim_bg_net_excellent"
android:layout_width="6dp"
android:layout_height="6dp"/>
<TextView
android:id="@+id/tvQuality"
android:layout_width="wrap_content"
android:layout_marginLeft="4dp"
android:layout_height="wrap_content"
android:textColor="#99FFFFFF"
android:textSize="13sp"
tools:text="12ms" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<RelativeLayout
android:id="@+id/rl_head"
......
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="315dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
......@@ -34,6 +33,7 @@
android:src="@drawable/audioim_switch_axb" />
<TextView
android:id="@+id/tvTip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
......
......@@ -11,9 +11,10 @@ import androidx.databinding.DataBindingUtil
import androidx.fragment.app.DialogFragment
import com.blankj.utilcode.constant.PermissionConstants
import com.blankj.utilcode.util.PermissionUtils
import com.ydl.audioim.api.IAudioImService
import com.ydl.confide.R
import com.ydl.confide.databinding.DialogConfidePermissionBinding
import com.ydl.ydlcommon.view.dialog.CommonDialog
import com.ydl.ydlcommon.modular.findRouteService
class ConfidePermissionDialog : DialogFragment() {
......@@ -58,13 +59,14 @@ class ConfidePermissionDialog : DialogFragment() {
private fun reject() {
dismissAllowingStateLoss()
CommonDialog(context)
.setMessage("由于您尚未同意获取麦克风权限,本次通话将使用传统电话方式拨打")
.setRightClick("确定") {
callback?.invoke(false)
}
.setCancelAble(false)
.show()
// CommonDialog(context)
// .setMessage("由于您尚未同意获取麦克风权限,本次通话将使用传统电话方式拨打")
// .setRightClick("确定") {
// callback?.invoke(false)
// }
// .setCancelAble(false)
// .show()
findRouteService(IAudioImService::class.java).openPermissionRejectDialog(activity) { callback?.invoke(false) }
}
override fun onStart() {
......
......@@ -98,6 +98,11 @@ class ExpertSearchActivity : BaseMvpActivity<IExpertSearchView, ExpertSearchPres
override fun showPromptPayment(promptPaymentBean: PromptPaymentBean) {
}
override fun consultTopBannerBean(consultantTopData: MutableList<ConsultantTopBannerBean>) {
}
override fun showImage(
url: String?,
imgView: ImageView,
......
......@@ -47,6 +47,7 @@ import com.ydl.ydlcommon.view.listener.EndlessRecyclerViewScrollListener
import com.yidianling.common.tools.LogUtil
import com.yidianling.common.tools.RxImageTool
import com.yidianling.common.tools.ToastUtil
import com.yidianling.consultant.adapter.ConsultantTopAdapter
import com.yidianling.consultant.adapter.ExpertSearchAdapter
import com.yidianling.consultant.bean.ConsultantRecentCity
import com.yidianling.consultant.constants.ConsultBIConstants
......@@ -98,7 +99,7 @@ class ExpertSearchFragment : BaseMvpFragment<IExpertSearchView, ExpertSearchPres
private var locationList: ArrayList<SubItem>? = arrayListOf()
private var locationHistoryList: ArrayList<RegionItem>? = arrayListOf()
private var eightcategoryPopup: EightCategoryPopupWindow? = null
private var mConsultantTopAdapter: ConsultantTopAdapter? = null
override fun layoutResId(): Int {
return R.layout.consultant_activity_expert_search_list
}
......@@ -113,17 +114,9 @@ class ExpertSearchFragment : BaseMvpFragment<IExpertSearchView, ExpertSearchPres
title_layout.setPadding(DisplayUtils.dp2px(context, 15), 0, 0, 0)
initViews()
initData()
val ffrom = PlatformDataManager.getRam().getChannelName()
if (!TextUtils.isEmpty(ffrom) &&
(ffrom.endsWith(ChannelConfig.Xinliceshiyiyu.name))
) {
//抑郁焦虑测试
rl_hot_fix_for_huawei.visibility = View.GONE
} else {
rl_hot_fix_for_huawei.visibility = View.VISIBLE
}
//资源页顶部资源位改版。
rl_hot_fix_for_huawei.visibility = View.GONE
consultant_list.visibility = View.VISIBLE
}
override fun initDataAndEventLazy() {
......@@ -155,6 +148,16 @@ class ExpertSearchFragment : BaseMvpFragment<IExpertSearchView, ExpertSearchPres
}
}
override fun consultTopBannerBean(consultantTopData: MutableList<ConsultantTopBannerBean>) {
//咨询页顶部滑动banner。
consultant_list.visibility = View.VISIBLE
val layoutManager = LinearLayoutManager(requireContext())
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
consultant_list.layoutManager = layoutManager
mConsultantTopAdapter = ConsultantTopAdapter(requireContext(), consultantTopData)
consultant_list.adapter = mConsultantTopAdapter
}
override fun showImage(
url: String?,
imgView: ImageView,
......@@ -386,6 +389,7 @@ class ExpertSearchFragment : BaseMvpFragment<IExpertSearchView, ExpertSearchPres
getPresenter().fetchListHead()
getPresenter().fetchFunctionWord(2)
getPresenter().fetchConsultTopBanner()
recommendListView.requestData("")
v_loading.visibility = View.VISIBLE
......@@ -1015,14 +1019,14 @@ class ExpertSearchFragment : BaseMvpFragment<IExpertSearchView, ExpertSearchPres
tempFilter, this
)
}
pricePopup!!.setOnDismissListener {
pricePopup?.setOnDismissListener {
if (tempFilter.priceRanges != null) {
updateFilterTextViewStatus(tvSort, FILTER_STATUS_FILTERED)
} else {
updateFilterTextViewStatus(tvSort, FILTER_STATUS_NORMAL)
}
}
pricePopup!!.showAsDropDown(viewSep2)
pricePopup?.showAsDropDown(viewSep2)
}
//显示筛选弹窗
......@@ -1600,6 +1604,7 @@ class ExpertSearchFragment : BaseMvpFragment<IExpertSearchView, ExpertSearchPres
onScrollListener.resetState()
getPresenter().mExtras = null
getPresenter().fetchListData(allFilter, getPresenter().mExtras, null)
getPresenter().fetchConsultTopBanner()
rvExperts.scrollToPosition(0)
}
......
......@@ -64,6 +64,21 @@ class ExpertSearchPresenter : SimplePresenter<IExpertSearchView>() {
}
@SuppressLint("CheckResult")
fun fetchConsultTopBanner() {
SearchApi.getSearchApi()
.searchConsultTopicBanner()
.compose(RxLifecycleUtils.bindToLifecycle(mView))
.compose(RxUtils.resultJavaData())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ resp ->
mView.consultTopBannerBean(resp)
}, { t ->
HttpErrorUtils.handleError(BaseApp.getApp(), t)
})
}
@SuppressLint("CheckResult")
fun fetchListData(allFilter: AllFilter, extras: Extras?, mapFilter: Map<String, Any>?) {
//是否亲子教育字段
// ConsultAssistantDialogUtils.REALATION_EDUCATION = allFilter.categories.size == 1 && allFilter.categories[0].cateId == "23"
......
......@@ -28,13 +28,15 @@ interface IExpertSearchView : IView {
/**
* 加载图片
*/
fun showImage(url : String?, imgView : ImageView)
fun showImage(url: String?, imgView: ImageView)
/**
* 加载图片
*/
fun showImage(url : String?, imgView : ImageView, ops : SimpleImageOpConfiger)
fun showImage(url: String?, imgView: ImageView, ops: SimpleImageOpConfiger)
fun showPromptPayment(promptPaymentBean: PromptPaymentBean)
fun consultTopBannerBean(consultantTopData: MutableList<ConsultantTopBannerBean>)
}
\ No newline at end of file
package com.yidianling.consultant.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.ydl.webview.H5Params
import com.ydl.webview.NewH5Activity
import com.ydl.ydl_image.module.GlideApp
import com.yidianling.consultant.R
import com.yidianling.consultant.model.bean.ConsultantTopBannerBean
import kotlinx.android.synthetic.main.consultant_top_banner_item.view.*
/**
* Created by zqk on 17-9-20.
*/
class ConsultantTopAdapter(
private val context: Context,
private val consultList: MutableList<ConsultantTopBannerBean>
) : RecyclerView.Adapter<ConsultantTopAdapter.ViewHolder>() {
override fun getItemCount(): Int = consultList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView =
LayoutInflater.from(context).inflate(R.layout.consultant_top_banner_item, parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val layoutParams = ViewGroup.LayoutParams(
(context.getResources().getDisplayMetrics().widthPixels / 4.5).toInt(),
ViewGroup.LayoutParams.WRAP_CONTENT
)
holder.itemView.layoutParams = layoutParams
if (consultList[position].specialTopicSubhead?.isNotEmpty() == true) {
holder.consult_tag.visibility = View.VISIBLE
holder.consult_tag.text = consultList[position].specialTopicSubhead
if (consultList[position].specialTopicSubhead?.toCharArray()?.size!! <= 2) {
val params = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
params.setMargins(46, 0, 0, 0)
holder.consult_tag.layoutParams = params
}
} else {
holder.consult_tag.visibility = View.GONE
}
GlideApp.with(context)
.load(consultList[position].topicImage)
.into(holder.consult_img)
holder.consult_title.text = consultList[position].specialTopicTitle
holder.itemView.setOnClickListener {
NewH5Activity.start(
context,
H5Params(consultList[position].specialTopicUrl.toString(), "")
)
}
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val consult_tag = itemView.consult_tag
val consult_img = itemView.consult_img
val consult_title = itemView.consult_title
}
}
\ No newline at end of file
......@@ -50,6 +50,11 @@ interface SearchApi {
@Headers(YDL_DOMAIN + YDL_DOMAIN_JAVA)
fun searchFunctionWord(@Body map: Map<String, @JvmSuppressWildcards Any>): Observable<BaseAPIResponse<MutableList<FunctionWordBean>>>
//咨询页顶部滑动banner
@POST("doctor/v1/querySpecialTopic")
@Headers(YDL_DOMAIN + YDL_DOMAIN_JAVA)
fun searchConsultTopicBanner(): Observable<BaseAPIResponse<MutableList<ConsultantTopBannerBean>>>
//查询功能词、底纹词
//(1热门词,2底纹词,3跳转词)
@POST("function/word/queryList")
......
package com.yidianling.consultant.model.bean
data class ConsultantTopBannerBean(
val specialTopicSubhead: String? = "",//右上角标签 有数据则展示
val specialTopicUrl: String? = "",
val topicImage: String? = "",
val specialTopicTitle: String? = ""
)
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingDefaultResource">
<corners
android:bottomRightRadius="@dimen/platform_dp_3"
android:topLeftRadius="@dimen/platform_dp_3"
android:topRightRadius="@dimen/platform_dp_3" />
<solid android:color="#ff7171" />
</shape>
\ No newline at end of file
......@@ -55,6 +55,14 @@
android:layout_height="wrap_content" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/consultant_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:orientation="horizontal"
android:paddingBottom="@dimen/platform_dp_17"
android:visibility="visible" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
......
<?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="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/platform_dp_25"
android:background="@color/white">
<FrameLayout
android:id="@+id/fl_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/consult_img"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginTop="@dimen/platform_dp_10" />
<TextView
android:id="@+id/consult_tag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/platform_dp_12"
android:layout_marginBottom="@dimen/platform_dp_40"
android:background="@drawable/consultant_bg_top_tag"
android:gravity="center"
android:includeFontPadding="false"
android:maxLength="8"
android:minWidth="@dimen/platform_dp_25"
android:paddingStart="@dimen/platform_dp_4"
android:paddingTop="@dimen/platform_dp_2"
android:paddingEnd="@dimen/platform_dp_4"
android:paddingBottom="@dimen/platform_dp_2"
android:textColor="@color/white"
android:textSize="9sp"
tools:text="dd" />
</FrameLayout>
<TextView
android:id="@+id/consult_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/platform_dp_8"
android:includeFontPadding="false"
android:textColor="@color/platform_color_666666"
android:textSize="@dimen/platform_sp_12"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/fl_img"
tools:text="新人专区">
</TextView>
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -60,7 +60,7 @@ dependencies {
api rootProject.ext.dependencies["ydl-user-router"]
implementation rootProject.ext.dependencies["BaseRecyclerViewAdapterHelper"]
implementation('com.netease.yunxin.kit:call-ui:1.5.9-SNAPSHOT') {
implementation('com.netease.yunxin.kit:call-ui:1.6.3') {
exclude group: "com.netease.nimlib", module: "avsignalling"
exclude group: "com.netease.nimlib", module: "basesdk"
}
......@@ -69,6 +69,7 @@ dependencies {
api project(':ydl-webview')
api project(':ydl-platform')
implementation project(":api:im")
implementation project(":api:audioim")
implementation project(":api:user")
implementation project(":api:dynamic")
implementation project(":api:tests")
......
package com.yidianling.im.helper;
import com.yidianling.uikit.api.NimUIKit;
import com.netease.nimlib.sdk.NIMClient;
import com.netease.nimlib.sdk.auth.AuthService;
import com.yidianling.im.preference.IMCache;
import com.yidianling.nimbase.common.http.NimHttpClient;
import com.yidianling.uikit.api.NimUIKit;
/**
* 注销帮助类
......
......@@ -2,18 +2,21 @@ package com.yidianling.im.helper;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Looper;
import android.text.TextUtils;
import com.netease.lava.nertc.sdk.NERtcConstants;
import com.netease.lava.nertc.sdk.NERtcEx;
import com.netease.lava.nertc.sdk.NERtcOption;
import com.netease.lava.nertc.sdk.NERtcParameters;
import com.netease.lava.nertc.sdk.video.NERtcEncodeConfig;
import com.netease.lava.nertc.sdk.video.NERtcVideoConfig;
import com.netease.nimlib.sdk.RequestCallback;
import com.netease.yunxin.nertc.nertcvideocall.bean.InvitedInfo;
import com.netease.yunxin.nertc.nertcvideocall.model.NERtcCallExtension;
import com.netease.yunxin.nertc.ui.CallKitNotificationConfig;
import com.netease.yunxin.nertc.ui.CallKitUI;
import com.netease.yunxin.nertc.ui.CallKitUIOptions;
import com.netease.yunxin.nertc.ui.extension.SelfConfigExtension;
import com.netease.yunxin.nertc.ui.service.DefaultIncomingCallEx;
import com.ydl.ydlcommon.modular.ModularServiceManagerKt;
import com.ydl.ydlcommon.utils.log.XLog;
......@@ -42,8 +45,14 @@ public class NimUICallInit {
UserResponseBean.UserInfo userInfo = ModularServiceManagerKt.findRouteService(IUserService.class).getUserInfo();
if (userInfo == null) return;
String userId = userInfo.getUid();
android.util.Log.e("qwert", userId);
//防止网络波动的情况下多次触发LOGINED状态,导致呼叫组件重新初始化
if (TextUtils.equals(userId, CallKitUI.INSTANCE.getCurrentUserAccId())) return;
android.util.Log.e("qwert", userId + (Looper.myLooper() == Looper.getMainLooper()));
String appKey = NimApplication.getInstance().getAppKey();
NERtcParameters neRtcParameters = new NERtcParameters();
neRtcParameters.set(NERtcParameters.KEY_SERVER_RECORD_AUDIO, true);
neRtcParameters.set(NERtcParameters.KEY_SERVER_RECORD_VIDEO, true);
NERtcEx.getInstance().setParameters(neRtcParameters);
CallKitUIOptions options = new CallKitUIOptions.Builder()
// 必要:音视频通话 sdk appKey,用于通话中使用
.rtcAppKey(appKey)
......@@ -53,15 +62,20 @@ public class NimUICallInit {
.currentUserRtcUId(Long.parseLong(userId))
// 通话接听成功的超时时间单位 毫秒,默认30s
.timeOutMillisecond(30 * 1000L)
.rtcCallExtension(new SelfConfigExtension(){
.rtcCallExtension(new NERtcCallExtension(){
@Override
public void configVideoConfig() {
protected int joinChannel(String token, String channelName, long rtcUid) {
NERtcVideoConfig videoConfig = new NERtcVideoConfig();
videoConfig.frameRate = NERtcEncodeConfig.NERtcVideoFrameRate.FRAME_RATE_FPS_15;
videoConfig.bitrate = 600;
videoConfig.width = 640;
videoConfig.height = 480;
videoConfig.degradationPrefer = NERtcVideoConfig.NERtcDegradationPreference.DEGRADATION_MAINTAIN_FRAMERATE;
NERtcEx.getInstance().setLocalVideoConfig(videoConfig);
// 音频设置 standard + speech
NERtcEx.getInstance().setAudioProfile(NERtcConstants.AudioProfile.STANDARD, NERtcConstants.AudioScenario.SPEECH);
// 设置 channel profile
NERtcEx.getInstance().setChannelProfile(NERtcConstants.RTCChannelProfile.COMMUNICATION);
return NERtcEx.getInstance().joinChannel(token, channelName, rtcUid);
}
})
// 此处为 收到来电时展示的 notification 相关配置,如图标,提示语等。
......@@ -80,9 +94,12 @@ public class NimUICallInit {
// .p2pAudioActivity(TestActivity.class)
// .p2pVideoActivity(TestActivity.class)
// 请求 rtc token 服务,若非安全模式则不需设置
.rtcTokenService((uid, callback) -> requestRtcToken(uid, callback)) // 自己实现的 token 请求方法
.rtcTokenService((uid, channel, callback) -> requestRtcToken(uid, channel, callback)) // 自己实现的 token 请求方法
// 设置初始化 rtc sdk 相关配置,按照所需进行配置
.rtcSdkOption(new NERtcOption())
.enableMsgFilter(false)
.enableCustomParser(false)
.audio2VideoConfirm(true)
// 呼叫组件初始化 rtc 范围,true-全局初始化,false-每次通话进行初始化以及销毁
// 全局初始化有助于更快进入首帧页面,当结合其他组件使用时存在rtc初始化冲突可设置false
.rtcInitScope(true)
......@@ -92,10 +109,10 @@ public class NimUICallInit {
}
@SuppressLint("CheckResult")
private static void requestRtcToken(long uid, RequestCallback<String> callback) {
private static void requestRtcToken(long uid, String channel, RequestCallback<String> callback) {
Map<String, String> body = new HashMap<>();
if (curChannelName != null) {
body.put("channelName", curChannelName);
if (channel != null) {
body.put("channelName", channel);
}
body.put("uid", String.valueOf(uid));
ImHttpImpl.Companion.getInstance().getImApi().nim2Token(body)
......
......@@ -16,6 +16,8 @@ import com.netease.nimlib.sdk.msg.MsgService
import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum
import com.netease.nimlib.sdk.msg.model.CustomNotification
import com.netease.nimlib.sdk.msg.model.CustomNotificationConfig
import com.tencent.mmkv.MMKV
import com.ydl.ydlcommon.data.http.RxUtils
import com.ydl.ydlcommon.router.YdlCommonOut
import com.ydl.ydlcommon.utils.StringUtils
import com.ydl.ydlcommon.utils.remind.HttpErrorUtils
......@@ -440,4 +442,15 @@ class IMServiceImpl : IImService {
P2PCustomActionHandlerImpl(doctorId + "")
)
}
@SuppressLint("CheckResult")
override fun getSystemConfig(key: String) {
ImRetrofitApi.getImRetrofitApi()
.getSystemConfigByKeyword(key)
.compose(RxUtils.resultJavaData())
.subscribeOn(Schedulers.io())
.subscribe {
MMKV.defaultMMKV().putString(key, it.value1)
}
}
}
\ No newline at end of file
......@@ -11,11 +11,13 @@ public class CustomAttachConsultCallStatus extends CustomAttachment{
private final String KEY_CALLEE="callee";
private final String KEY_DURATION="duration";
private final String KEY_IS_CONSULT_ORDER = "isConsultOrder"; //是否是咨询单,true是
private final String KEY_PULL_CALL = "pullCall"; //是否是咨询单,true是
private String status;
private String caller;
private String callee;
private String duration;
private String isConsultOrder;
private Integer pullCall;//标识是可以拉起通话的 0或空 则不拉起
public CustomAttachConsultCallStatus() {
super(CustomAttachmentType.TYPE_CUSTOMER_CONSULT_CALL_STATUS);
......@@ -28,6 +30,7 @@ public class CustomAttachConsultCallStatus extends CustomAttachment{
this.callee=data.getString(KEY_CALLEE);
this.duration=data.getString(KEY_DURATION);
this.isConsultOrder=data.getString(KEY_IS_CONSULT_ORDER);
this.pullCall = data.getInteger(KEY_PULL_CALL);
}
@Override
......@@ -38,6 +41,7 @@ public class CustomAttachConsultCallStatus extends CustomAttachment{
data.put(KEY_CALLEE, callee);
data.put(KEY_DURATION, duration);
data.put(KEY_IS_CONSULT_ORDER, isConsultOrder);
data.put(KEY_PULL_CALL, pullCall);
return data;
}
......@@ -58,4 +62,8 @@ public class CustomAttachConsultCallStatus extends CustomAttachment{
}
public String getIsConsultOrder() {return isConsultOrder;}
public Integer getPullCall() {
return pullCall;
}
}
\ No newline at end of file
......@@ -4,6 +4,8 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.ydl.audioim.api.IAudioImService;
import com.ydl.ydlcommon.modular.ModularServiceManagerKt;
import com.yidianling.im.R;
import com.yidianling.im.session.extension.CustomAttachConsultCallStatus;
import com.yidianling.nimbase.common.ui.recyclerview.adapter.BaseMultiItemFetchLoadAdapter;
......@@ -17,6 +19,8 @@ public class MsgViewHolderConsultCallStatus extends MsgViewHolderBase {
private ImageView typeImageLeft;
private ImageView typeImageRight;
private TextView statusLabel;
private View lineView;
private TextView btnView;
public MsgViewHolderConsultCallStatus(BaseMultiItemFetchLoadAdapter adapter) {
super(adapter);
......@@ -29,9 +33,11 @@ public class MsgViewHolderConsultCallStatus extends MsgViewHolderBase {
@Override
protected void inflateContentView() {
typeImageLeft = findViewById(R.id.type_img_left);
typeImageRight = findViewById(R.id.type_img_right);
statusLabel = findViewById(R.id.tv_state);
typeImageLeft = findViewById(R.id.message_item_type_img_left);
typeImageRight = findViewById(R.id.message_item_type_img_right);
statusLabel = findViewById(R.id.message_item_avchat_state);
lineView = findViewById(R.id.message_item_avchat_line);
btnView = findViewById(R.id.message_item_avchat_btn);
}
@Override
......@@ -57,6 +63,21 @@ public class MsgViewHolderConsultCallStatus extends MsgViewHolderBase {
} else {
statusLabel.setText(customAttachTipMsg.getCaller());
}
long messageTime = message.getTime();
boolean isExpired = System.currentTimeMillis() - messageTime > 60 * 1000;
Integer pullCall = customAttachTipMsg.getPullCall();
if (!isExpired && pullCall != null && pullCall == 1 && isReceivedMessage()) {
lineView.setVisibility(View.VISIBLE);
btnView.setVisibility(View.VISIBLE);
btnView.setOnClickListener(v -> {
String sessionId = message.getSessionId();
ModularServiceManagerKt.findRouteService(IAudioImService.class)
.callCheck(v.getContext(), sessionId, 0);
});
} else {
lineView.setVisibility(View.GONE);
btnView.setVisibility(View.GONE);
}
}
@Override
......@@ -78,5 +99,4 @@ public class MsgViewHolderConsultCallStatus extends MsgViewHolderBase {
return R.drawable.im_message_send_content_bg;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="40dp" />
<gradient
android:endColor="#159CEF"
android:startColor="#22BFFF" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_content"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/message_item_avchat_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="6dp"
android:minHeight="40dp"
android:orientation="horizontal">
android:orientation="vertical"
android:padding="6dp">
<ImageView
android:id="@+id/type_img_left"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_gravity="center_vertical"
android:id="@+id/message_item_type_img_left"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:contentDescription="@string/im_empty"
android:src="@drawable/im_avchat_type_audio"
android:visibility="gone"
android:src="@drawable/im_avchat_type_audio"/>
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="@+id/message_item_avchat_state"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@+id/message_item_avchat_state" />
<TextView
android:id="@+id/tv_state"
android:id="@+id/message_item_avchat_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="5dp"
android:layout_marginTop="4dp"
android:layout_marginRight="5dp"
android:text="无人接听"
android:textColor="@color/platform_color_242424"
android:textSize="14sp"/>
android:text="无人接听111111111"
android:textColor="@color/color_242424"
android:textSize="15sp"
app:layout_constraintLeft_toRightOf="@+id/message_item_type_img_left"
app:layout_constraintRight_toLeftOf="@+id/message_item_type_img_right"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:visibility="gone"
android:id="@+id/type_img_right"
android:layout_width="14dp"
android:layout_height="14dp"
android:id="@+id/message_item_type_img_right"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:contentDescription="@string/im_empty"
android:src="@drawable/im_avchat_type_audio"/>
</LinearLayout>
android:src="@drawable/im_avchat_type_audio"
android:visibility="gone"
android:scaleType="center"
app:layout_constraintBottom_toBottomOf="@+id/message_item_avchat_state"
app:layout_constraintLeft_toRightOf="@+id/message_item_avchat_state"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/message_item_avchat_state" />
<View
android:id="@+id/message_item_avchat_line"
android:layout_width="0dp"
android:layout_height="0.5dp"
android:layout_marginTop="12dp"
android:background="@color/platform_color_EBEBEB"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/message_item_avchat_state" />
<TextView
android:id="@+id/message_item_avchat_btn"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:layout_marginTop="10dp"
android:layout_marginRight="4dp"
android:background="@drawable/bg_btn1"
android:gravity="center"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:text="立即接听"
android:textColor="@color/white"
android:layout_marginBottom="4dp"
android:textSize="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/message_item_avchat_line" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -59,6 +59,7 @@ dependencies {
// implementation project(":ydl-tuicore")
implementation project(":api:user")
implementation project(":api:course")
implementation project(":api:audioim")
implementation project(":api:im")
implementation project(":api:fm")
implementation project(":api:dynamic")
......
......@@ -2,7 +2,9 @@ package com.yidianling.user
import android.app.Activity
import com.tencent.bugly.crashreport.CrashReport
import com.ydl.audioim.api.IAudioImService
import com.ydl.ydlcommon.data.http.RxUtils
import com.ydl.ydlcommon.modular.findRouteService
import com.ydl.ydlcommon.utils.ActivityManager
import com.ydl.ydlcommon.utils.BuryPointUtils
import com.ydl.ydlcommon.utils.log.AliYunLogConfig
......@@ -88,6 +90,7 @@ object LoginUtils {
@JvmStatic
fun logout() {
EventBus.getDefault().post(UserLogoutEvent())
findRouteService(IAudioImService::class.java).reportCallEvent("login", "userLogout", "用户登出", null)
UserHttpImpl.getInstance().logout(Logout())
.compose(RxUtils.resultData())
.observeOn(AndroidSchedulers.mainThread())
......
......@@ -10,14 +10,15 @@ import android.widget.ToggleButton;
import com.umeng.socialize.UMAuthListener;
import com.umeng.socialize.UMShareAPI;
import com.umeng.socialize.bean.SHARE_MEDIA;
import com.ydl.audioim.api.IAudioImService;
import com.ydl.webview.H5Params;
import com.ydl.webview.NewH5Activity;
import com.ydl.ydlcommon.base.BaseActivity;
import com.ydl.ydlcommon.bean.StatusBarOptions;
import com.ydl.ydlcommon.data.http.ThrowableConsumer;
import com.ydl.ydlcommon.modular.ModularServiceManager;
import com.ydl.ydlcommon.modular.ModularServiceManagerKt;
import com.ydl.ydlcommon.router.YdlCommonOut;
import com.ydl.ydlcommon.utils.LogUtil;
import com.ydl.ydlcommon.utils.SharedPreferencesEditor;
import com.ydl.ydlcommon.utils.remind.ToastHelper;
import com.ydl.ydlcommon.view.JumpTextView;
......@@ -299,8 +300,10 @@ public class AccountSettingActivity extends BaseActivity implements View.OnClick
//清除UserInfo
UserHelper.INSTANCE.setUserinfo(null);
UserIn.INSTANCE.clearImData();
ModularServiceManagerKt.findRouteService(IAudioImService.class).logoutRtm();
ModularServiceManagerKt.findRouteService(IImService.class).logoutAll();
LoginUtils.logout();
EventBus.getDefault().post(new RefreshRecentContactListEvent());
finish();
}
......
......@@ -2,7 +2,9 @@ package com.yidianling.user.ui.login.presenter
import android.annotation.SuppressLint
import android.text.TextUtils
import com.ydl.audioim.api.IAudioImService
import com.ydl.ydlcommon.app.Apm
import com.ydl.ydlcommon.modular.findRouteService
import com.ydl.ydlcommon.mvp.base.BasePresenter
import com.ydl.ydlcommon.router.YdlCommonOut
import com.ydl.ydlcommon.utils.StringUtils
......@@ -105,6 +107,7 @@ class InputPassWordPresenterImpl :
)
var param = PhoneLoginPwdParam(StringUtils.md5(inputPassword), phoneCountryCode, phone)
findRouteService(IAudioImService::class.java).reportCallEvent("login", "userLogin", "用户登录", null)
mModel.userLoginByPassword(param)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
......@@ -117,6 +120,7 @@ class InputPassWordPresenterImpl :
ToastUtil.toastShort(it.msg)
Apm.reportEvent("login_android", "password_error", "密码登录失败,$phoneCountryCode-$phone")
mView.startAnim()
findRouteService(IAudioImService::class.java).reportCallEvent("login", "userLoginFail", "用户登录失败", null)
} else {
if (it.data.userInfo?.user_type == 2) {
mView.showNormalDialog()
......@@ -135,6 +139,7 @@ class InputPassWordPresenterImpl :
}
mView.closeActivity()
}
findRouteService(IAudioImService::class.java).reportCallEvent("login", "userLoginSuccess", "用户登录成功", null)
AliYunRichLogsHelper.getInstance().sendRichLog(AliYunLogConfig.LOGIN, "手机号密码 登录成功")
}
......
package com.ydl.ydlcommon.utils.log
import com.apm.insight.log.VLog
import com.ydl.ydlcommon.BuildConfig
object XLog {
fun d(tag: String, msg: String) {
if (BuildConfig.DEBUG) {
android.util.Log.d(tag, msg)
}
VLog.i(tag, msg)
}
fun i(tag: String, msg: String) {
if (BuildConfig.DEBUG) {
android.util.Log.i(tag, msg)
}
VLog.i(tag, msg)
}
fun w(tag: String, msg: String) {
if (BuildConfig.DEBUG) {
android.util.Log.w(tag, msg)
}
VLog.i(tag, msg)
}
fun e(tag: String, msg: String) {
if (BuildConfig.DEBUG) {
android.util.Log.e(tag, msg)
}
VLog.e(tag, msg)
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment