diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/AIMessageManager.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/AIMessageManager.kt new file mode 100644 index 0000000000..c13baa5895 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/AIMessageManager.kt @@ -0,0 +1,64 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind + +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage +import java.util.concurrent.CopyOnWriteArrayList + + +object AIMessageManager { + + // 使用 CopyOnWriteArrayList 来存储消息回调列表,保证线程安全 + private val messageListeners: MutableList = CopyOnWriteArrayList() + + + /** + * 注册一个消息监听器。 + * + * @param listener 要注册的 AiMessageListener 实例。 + */ + fun registerListener(listener: AIMessageListener) { + messageListeners.add(listener) + } + + /** + * 取消注册一个消息监听器。 + * + * @param listener 要取消注册的 AiMessageListener 实例。 + */ + fun unregisterListener(listener: AIMessageListener) { + messageListeners.remove(listener) + } + + /** + * 发布一条消息。 + * + * 这条消息会被发送给所有已注册的监听器。 + * + * @param msg 要发布的消息。 + */ + fun post(msg: AIMessage) { + // 遍历所有已注册的监听器,并调用它们的 onReceive 方法 + messageListeners.forEach { callback -> + callback.onReceive(msg) + } + } + + fun clearData(){ + messageListeners.forEach { callback -> + callback.clear() + } + } + + /** + * 消息监听器接口。 + */ + interface AIMessageListener { + /** + * 当接收到消息时,会调用此方法。 + * + * @param msg 接收到的消息。 + */ + fun onReceive(msg: AIMessage) + + fun clear() + } +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/MindView.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/MindView.kt new file mode 100644 index 0000000000..859d760a1a --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/MindView.kt @@ -0,0 +1,211 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind + +import android.content.Context +import android.util.AttributeSet +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.findViewTreeLifecycleOwner +import androidx.lifecycle.findViewTreeViewModelStoreOwner +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.mogo.eagle.core.data.enums.EventTypeEnumNew +import com.mogo.eagle.core.function.call.hmi.CallerRoadV2NEventWindowListenerManager +import com.mogo.eagle.core.function.call.map.CallerMapRoadListenerManager +import com.mogo.eagle.core.utilcode.kotlin.onClick +import com.mogo.eagle.core.utilcode.util.UriUtils +import com.mogo.och.shuttle.weaknet.passenger.R +import com.mogo.och.shuttle.weaknet.passenger.ui.line.lineinfo.LineViewModel +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.adapter.AIMessageAdapter +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.adapter.OnItemClickListener +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.adapter.PaddingItemDecoration +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.data.AutomaticExplorationViewModel +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.data.NDEViewModel +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.data.RoadCrossRoamViewModel +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.data.RoadV2NEventViewModel +import kotlinx.android.synthetic.main.m2_mind_view.view.rv_mind_list +import kotlinx.android.synthetic.main.m2_mind_view.view.test1 +import kotlinx.android.synthetic.main.m2_mind_view.view.test2 +import kotlinx.android.synthetic.main.m2_mind_view.view.test3 +import kotlinx.coroutines.launch + +class MindView : ConstraintLayout, MindViewModel.AiViewCallback { + + private val TAG = "MindView" + + constructor(context: Context) : super(context) + + constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) + + constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr) + + constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attributeSet, defStyleAttr, defStyleRes) + + private var roadV2NEventModel: RoadV2NEventViewModel?= null + private var roadCrossRoamModel: RoadCrossRoamViewModel?= null + private var automaticExplorationModel: AutomaticExplorationViewModel?= null + private var ndeViewModel: NDEViewModel?= null + + + private var viewModel:MindViewModel?=null + + private var isUserScrollingTime = 0L + + private val SCROLL_THRESHOLD = 2000L + + private val messageAdapter: AIMessageAdapter by lazy { AIMessageAdapter() } + private val messageLayoutManager: LinearLayoutManager by lazy { + LinearLayoutManager(context).apply { + stackFromEnd = true + } + } + + private fun initView() { + LayoutInflater.from(context).inflate(R.layout.m2_mind_view, this, true) + + rv_mind_list.layoutManager = messageLayoutManager + rv_mind_list.adapter = messageAdapter + rv_mind_list.addItemDecoration(PaddingItemDecoration(200, 300)) + messageAdapter.onItemClickListener = OnItemClickListener { item, position -> + if (item is AIMessage.Event) { + + } + } + rv_mind_list.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + if (newState != RecyclerView.SCROLL_STATE_IDLE) { + isUserScrollingTime = System.currentTimeMillis() + } + } + }) + + test1.onClick { + CallerRoadV2NEventWindowListenerManager.showImage( + System.currentTimeMillis().toString(), + System.currentTimeMillis(), + EventTypeEnumNew.getUpdateIconRes(EventTypeEnumNew.TYPE_USECASE_ROAD_BUS_STATION.poiType), + String.format( + EventTypeEnumNew.getAlarmContent(EventTypeEnumNew.TYPE_USECASE_ROAD_BUS_STATION.poiType), + 100 + ), + false, + EventTypeEnumNew.getWarningTts(EventTypeEnumNew.TYPE_USECASE_ROAD_BUS_STATION.poiType), + UriUtils.res2Uri( + EventTypeEnumNew.getPoiTypeBg( + EventTypeEnumNew.TYPE_USECASE_ROAD_BUS_STATION.poiType, + false + ).toString() + ).toString() + ) + } + test2.onClick { + CallerMapRoadListenerManager.invokeCrossDevice(true) + } + test3.onClick { + val one = AIMessage.RoadMsg(201,1,true,false) + val two = AIMessage.RoadMsg(202,2,false,false) + val three = AIMessage.RoadMsg(203,3,false,true) + + val sortedList = ArrayList() + sortedList.add(one) + sortedList.add(two) + sortedList.add(three) + val ndeEvent = AIMessage.NDEData(System.currentTimeMillis().toString(),"路口车龙","前方路口有车龙",sortedList) + AIMessageManager.post(ndeEvent) + +// CallerRoadV2NEventWindowListenerManager.showImage( +// System.currentTimeMillis().toString(), +// System.currentTimeMillis(), +// EventTypeEnumNew.getUpdateIconRes(EventTypeEnumNew.TYPE_USECASE_ROAD_BUS_STATION.poiType), +// String.format( +// EventTypeEnumNew.getAlarmContent(EventTypeEnumNew.TYPE_USECASE_ROAD_BUS_STATION.poiType), +// 100 +// ), +// false, +// String.format( +// EventTypeEnumNew.getWarningTts(EventTypeEnumNew.TYPE_USECASE_ROAD_BUS_STATION.poiType), +// 100 +// ), +// UriUtils.res2Uri( +// EventTypeEnumNew.getPoiTypeBg( +// EventTypeEnumNew.TYPE_USECASE_ROAD_BUS_STATION.poiType, +// false +// ).toString() +// ).toString() +// ) + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + viewModel = findViewTreeViewModelStoreOwner()?.let { + ViewModelProvider(it).get(MindViewModel::class.java) + } + viewModel?.setViewCallback(this) + + roadV2NEventModel = findViewTreeViewModelStoreOwner()?.let{ + ViewModelProvider(it)[RoadV2NEventViewModel::class.java] + } + roadV2NEventModel?.init() + roadCrossRoamModel = findViewTreeViewModelStoreOwner()?.let{ + ViewModelProvider(it)[RoadCrossRoamViewModel::class.java] + } + roadCrossRoamModel?.init(context) + automaticExplorationModel = findViewTreeViewModelStoreOwner()?.let{ + ViewModelProvider(it)[AutomaticExplorationViewModel::class.java] + } + automaticExplorationModel?.init() + ndeViewModel = findViewTreeViewModelStoreOwner()?.let{ + ViewModelProvider(it)[NDEViewModel::class.java] + } + ndeViewModel?.init() + + findViewTreeLifecycleOwner()?.lifecycleScope?.launch { + viewModel?.messagesFlow?.collect { + Log.d(TAG, "${tName()} onMessages update: ${it}") + if(it.isNotEmpty()){ + rv_mind_list.visibility = View.VISIBLE + }else{ + rv_mind_list.visibility = View.VISIBLE + } + messageAdapter.submitList(it) { + Log.d(TAG, "${tName()} adapter submit: ") + scrollToBottom() + } + } + } + } + + // 滚动到RecyclerView底部 + private fun scrollToBottom() { + val delay = System.currentTimeMillis() - isUserScrollingTime + if (delay < SCROLL_THRESHOLD) { + return + } + val layoutManager = rv_mind_list.layoutManager as LinearLayoutManager + layoutManager.scrollToPositionWithOffset(messageAdapter.itemCount - 1, 0) + } + + fun tName(): String { + return "【${Thread.currentThread().name}】" + } + + override fun onVisibilityAggregated(isVisible: Boolean) { + super.onVisibilityAggregated(isVisible) + } + + init { + try { + initView() + } catch (e: Exception) { + e.printStackTrace() + } + } + + +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/MindViewModel.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/MindViewModel.kt new file mode 100644 index 0000000000..ced5337f5f --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/MindViewModel.kt @@ -0,0 +1,205 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.mogo.eagle.core.data.ai.V2XRepository +import com.mogo.eagle.core.data.map.MogoLocation +import com.mogo.eagle.core.function.api.autopilot.IMoGoChassisLocationGCJ02Listener +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage +import com.mogo.och.bridge.autopilot.location.OchLocationManager +import com.mogo.och.common.module.biz.birdge.BridgeListener +import com.mogo.och.common.module.biz.birdge.BridgeManager +import com.mogo.och.common.module.voice.VoiceNotice +import com.mogo.och.shuttle.weaknet.passenger.callback.ICommonCallback +import com.mogo.och.shuttle.weaknet.passenger.model.CommonModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class MindViewModel : ViewModel(), AIMessageManager.AIMessageListener, + BridgeListener { + + private val msgList = mutableListOf() + private var lastTimestamp = System.currentTimeMillis() + + // 记录最后一次事件发生的时间,使用 private set 限制外部修改 + // private val TIMESTAMP_THRESHOLD = 1000 * 60 * 5 // 5分钟 + private val TIMESTAMP_THRESHOLD = 1000 * 30 + + private var llmResultJob: Job? = null + + private var isChecking = false + + private val _messagesFlow = MutableStateFlow>(emptyList()) + val messagesFlow: SharedFlow> get() = _messagesFlow + + private val _asrUIStateFlow = MutableStateFlow(AsrUIState.Idle) + val asrUIStateFlow: StateFlow get() = _asrUIStateFlow + + private val tipsTimeOut = "小智还没反应过来,请稍后再试~" + private val tipsExit = "我先退下了" + val tipsQuestions = listOf( + "可以问我:前方路口有拥堵吗?", + "可以对我说:介绍一下蘑菇车联?", + "想要对话时可以点击:小智智能体", + "可以问我:今天天气怎么样?", + ) + private val tipsWakeUpList = listOf( + "嗯,我在呢", + "在的", + "在呢", + "你说", + ) + + private val commontCallback = object : ICommonCallback{ + override fun showNoTaskView(isTrue: Boolean) { + if(isTrue){ + synchronized(msgList) { + msgList.clear() + _messagesFlow.value = msgList.toList() + } + } + } + } + + private val locationCallback = object : IMoGoChassisLocationGCJ02Listener { + override fun onChassisLocationGCJ02(mogoLocation: MogoLocation?) { + mogoLocation?.let { + V2XRepository.provideLocation(it, 0) + } + + } + } + + init { + + } + + fun setViewCallback(aiView: AiViewCallback) { + CommonModel.setRouteLineInfoCallback(TAG,commontCallback) + OchLocationManager.addGCJ02Listener(TAG, 1, locationCallback) + AIMessageManager.registerListener(this) + + BridgeManager.addBridgeListener(TAG,this) + + } + + + override fun onCleared() { + AIMessageManager.unregisterListener(this) + llmResultJob?.cancel() + CommonModel.setRouteLineInfoCallback(TAG,null) + OchLocationManager.removeGCJ02Listener(TAG) + super.onCleared() + } + + override fun onReceive(msg: AIMessage) { + Log.d(TAG, "onReceive: $msg") + if (isChecking) { + if (msg is AIMessage.Event) { + msg.showScanFlag = true + } + } + + // 获更新消息 + updateMsg(msg) + } + + override fun clear() { + clearMsg() + } + + private fun handleMsg(newMessage: AIMessage) { + val existingIndex = findMessageIndex(newMessage.id) + if (existingIndex != -1) { + handleExistingMessage(existingIndex, newMessage) + } else { + handleNewMessage(newMessage) + } + } + + private fun findMessageIndex(messageId: String): Int { + return msgList.indexOfFirst { it.id == messageId } + } + + private fun handleExistingMessage(index: Int, newMessage: AIMessage) { + val oldMessage = msgList[index] + + newMessage.showTimestamp = oldMessage.showTimestamp + msgList[index] = newMessage + + speakMessageIfNeeded(newMessage, isLastMessage = msgList.last() == newMessage) + } + + private fun handleNewMessage(newMessage: AIMessage) { + updateTimestampIfNeeded(newMessage) + msgList.add(newMessage) + speakMessageIfNeeded(newMessage, isLastMessage = true) + } + + private fun updateTimestampIfNeeded(newMessage: AIMessage) { + val time = newMessage.timestamp - lastTimestamp + if (time >= TIMESTAMP_THRESHOLD) { + newMessage.showTimestamp = true + lastTimestamp = newMessage.timestamp + } + } + + private fun speakMessageIfNeeded(newMessage: AIMessage, isLastMessage: Boolean) { + if (isLastMessage && newMessage.tts.isNotEmpty()) { + VoiceNotice.showNotice(newMessage.tts) + } + } + + companion object { + private const val TAG = "AiViewModel" + } + + private fun updateMsg(msg: AIMessage) { + synchronized(msgList) { + handleMsg(msg) + _messagesFlow.value = msgList.toList() + } + } + + private fun deleteMsg(msgId: String) { + synchronized(msgList) { + val iterator = msgList.iterator() + while (iterator.hasNext()) { + if (iterator.next().id == msgId) { + iterator.remove() + } + } + _messagesFlow.value = msgList.toList() + } + } + + private fun clearMsg() { + synchronized(msgList) { + msgList.clear() + _messagesFlow.value = msgList.toList() + } + } + + + fun getQuestionsRandomIndex(): Int { + return tipsQuestions.indexOf(tipsQuestions.random()) + } + + interface AiViewCallback { + + } +} + +sealed class AsrUIState { + object Idle : AsrUIState() + data class Listening(val partialText: String) : AsrUIState() + data class Recognized(val finalText: String) : AsrUIState() +} + + + diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AIMessageAdapter.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AIMessageAdapter.kt new file mode 100644 index 0000000000..7a4c34d086 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AIMessageAdapter.kt @@ -0,0 +1,51 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import com.mogo.och.shuttle.weaknet.passenger.R +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage + +class AIMessageAdapter : ListAdapter(MessageDiffCallback()) { + + var onItemClickListener: OnItemClickListener? = null + + override fun onBindViewHolder(holder: MessageViewHolder, position: Int) { + getItem(position)?.let { + holder.bind(it, onItemClickListener) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder { + val inflater = LayoutInflater.from(parent.context) + return when (viewType) { + AIMessage.TYPE_PNC_ACTION -> PNCActionViewHolder(inflater.inflate(R.layout.item_ai_pnc_action,parent,false)) + AIMessage.TYPE_ROAD_V2N -> RoadV2NEventViewHolder(inflater.inflate(R.layout.item_ai_road_v2n_event,parent,false)) + AIMessage.TYPE_ROAD_CROSS -> RoadCrossRoamViewHolder(inflater.inflate(R.layout.item_ai_road_cross_roam,parent,false))// 全息路口 + AIMessage.TYPE_AUTOMATIC_EXPLORATION -> AutomaticExplorationViewHolder(inflater.inflate(R.layout.item_ai_automatic_exploration,parent,false))// 探查 + AIMessage.TYPE_NDE -> NDEViewHolder(inflater.inflate(R.layout.item_ai_nde_event,parent,false))// 车龙 + else -> throw IllegalArgumentException("Invalid view type") + } + } + + override fun getItemViewType(position: Int): Int { + return when (getItem(position)) { + is AIMessage.Event -> AIMessage.TYPE_EVENT + is AIMessage.Scan -> AIMessage.TYPE_SCAN + is AIMessage.Light -> AIMessage.TYPE_LIGHT + is AIMessage.Speed -> AIMessage.TYPE_SPEED + is AIMessage.Warning -> AIMessage.TYPE_WARNING + is AIMessage.PNCAction -> AIMessage.TYPE_PNC_ACTION + is AIMessage.RoadV2NEvent -> AIMessage.TYPE_ROAD_V2N + is AIMessage.RoadCrossRoam -> AIMessage.TYPE_ROAD_CROSS + is AIMessage.AutomaticExploration -> AIMessage.TYPE_AUTOMATIC_EXPLORATION + is AIMessage.NDEData -> AIMessage.TYPE_NDE + else -> AIMessage.TYPE_EVENT + } + } + + override fun onViewRecycled(holder: MessageViewHolder) { + super.onViewRecycled(holder) + holder.viewRecycled(holder) + } +} diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AIMessageViewHolder.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AIMessageViewHolder.kt new file mode 100644 index 0000000000..816c674552 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AIMessageViewHolder.kt @@ -0,0 +1,235 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.adapter + +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Rect +import android.util.Log +import android.view.View +import android.view.animation.LinearInterpolator +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.mogo.eagle.core.data.notice.AutoExplorationEntity +import com.mogo.eagle.core.data.v2x.RoadV2NEventType +import com.mogo.eagle.core.function.call.autopilot.CallerChassisLocationWGS84ListenerManager +import com.mogo.eagle.core.function.hmi.ui.notice.exploration.AutomaticExplorationAdapter +import com.mogo.eagle.core.function.hmi.ui.v2n.RoadV2NEventLivePlayView +import com.mogo.eagle.core.function.view.MapRoamView +import com.mogo.eagle.core.function.view.RoadCrossRoamListAdapter +import com.mogo.eagle.core.utilcode.mogo.glide.GlideImageLoader +import com.mogo.eagle.core.utilcode.mogo.imageloader.MogoImageView +import com.mogo.eagle.core.utilcode.util.DateTimeUtils +import com.mogo.och.shuttle.weaknet.passenger.R +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + + +abstract class MessageViewHolder(view: View) : RecyclerView.ViewHolder(view) { + abstract fun bind(item: AIMessage, onItemClickListener: OnItemClickListener? = null) + open fun viewRecycled(holder: MessageViewHolder){} + private val sampleDateFormat = SimpleDateFormat("HH:mm", Locale.CHINA) + protected val TAG = javaClass.simpleName + + fun handleTimestamp(item: AIMessage, tvTimestamp: TextView) { + if (item.showTimestamp) { + tvTimestamp.visibility = View.VISIBLE + tvTimestamp.text = sampleDateFormat.format(Date(item.timestamp)) + } else { + tvTimestamp.visibility = View.GONE + } + } + + fun View.setVisibilityBasedOn(condition: Boolean) { + visibility = if (condition) View.VISIBLE else View.GONE + } + + fun TextView.setTextAndVisibility(text: String) { + if (text.isEmpty()) { + visibility = View.GONE + this.text = "" + } else { + visibility = View.VISIBLE + this.text = text + } + } + + fun ImageView.showOrHideWithUrl(url: String) { + if (url.isEmpty()) { + visibility = View.GONE + + } else { + visibility = View.VISIBLE + Glide.with(this) + .load(url) + .placeholder(R.drawable.icon_pic_holder) + .error(R.drawable.icon_pic_error) +// .error(R.drawable.icon_marker_window_place_holder) +// .placeholder(R.drawable.icon_marker_window_place_holder) + .into(this) + + } + } +} + + +class PNCActionViewHolder(binding: View) : MessageViewHolder(binding){ + + private var tvPncActionDesc: TextView = binding.findViewById(R.id.tvPNCHintContent) + + override fun bind(item: AIMessage, onItemClickListener: OnItemClickListener?) { + if(item is AIMessage.PNCAction){ + tvPncActionDesc.text = item.actionDesc + } + } + +} + +class RoadV2NEventViewHolder(binding: View) : MessageViewHolder(binding){ + + private var tvV2XHintContent: TextView = binding.findViewById(R.id.tvV2XHintContent) + private var containerImageAndLiveVideo: FrameLayout = binding.findViewById(R.id.containerImageAndLiveVideo) + private var livePlayView: RoadV2NEventLivePlayView = binding.findViewById(R.id.livePlayView) + private var contentImageView: MogoImageView = binding.findViewById(R.id.contentImageView) + + override fun bind(item: AIMessage, onItemClickListener: OnItemClickListener?) { + if(item is AIMessage.RoadV2NEvent){ + tvV2XHintContent.text = item.title + when (item.eventType) { + RoadV2NEventType.TEXT -> { + containerImageAndLiveVideo.visibility = View.GONE + contentImageView.visibility = View.GONE + livePlayView.visibility = View.GONE + } + + RoadV2NEventType.IMAGE -> { + containerImageAndLiveVideo.visibility = View.VISIBLE + contentImageView.visibility = View.VISIBLE + livePlayView.visibility = View.GONE + GlideImageLoader.getInstance() + .displayImage(item.contentImageUrl, contentImageView) + } + + RoadV2NEventType.LIVE_VIDEO -> { + containerImageAndLiveVideo.visibility = View.VISIBLE + contentImageView.visibility = View.GONE + livePlayView.visibility = View.VISIBLE + val cityCode = + CallerChassisLocationWGS84ListenerManager.getChassisLocationWGS84().cityCode + livePlayView.startRoadCameraLive( + item.id, + item.cameraIp, item.lon, item.lat, cityCode + ) + } + } + } + + + } + +} + +class RoadCrossRoamViewHolder(binding: View) : MessageViewHolder(binding){ + + private var tvRoadRoamTitle: TextView = binding.findViewById(R.id.tvRoadRoamTitle) + private var lvRoadCrossRoamTip: RecyclerView = binding.findViewById(R.id.lvRoadCrossRoamTip) + + override fun bind(item: AIMessage, onItemClickListener: OnItemClickListener?) { + if(item is AIMessage.RoadCrossRoam){ + lvRoadCrossRoamTip.layoutManager = NoScrollLayoutManager(itemView.context) +// lvRoadCrossRoamTip.addItemDecoration(object : RecyclerView.ItemDecoration() { +// override fun getItemOffsets( +// outRect: Rect, +// view: View, +// parent: RecyclerView, +// state: RecyclerView.State +// ) { +// super.getItemOffsets(outRect, view, parent, state) +// outRect.bottom = 24 +// } +// }) + lvRoadCrossRoamTip.adapter = RoadCrossRoamListB2Adapter(itemView.context) + tvRoadRoamTitle.setTextColor(itemView.context.getColor(R.color.color_131415)) + + } + } + +} + +class AutomaticExplorationViewHolder(binding: View) : MessageViewHolder(binding){ + + private var rvExplorationList: RecyclerView = binding.findViewById(R.id.rvExplorationList) + private lateinit var automaticExplorationAdapter: AutomaticExplorationB2Adapter + + override fun bind(item: AIMessage, onItemClickListener: OnItemClickListener?) { + val linearLayoutManager = LinearLayoutManager(itemView.context) + linearLayoutManager.orientation = LinearLayoutManager.VERTICAL + automaticExplorationAdapter = AutomaticExplorationB2Adapter(itemView.context) + rvExplorationList.adapter = automaticExplorationAdapter + rvExplorationList.layoutManager = linearLayoutManager + initData() + } + + private fun initData() { + val dataList = ArrayList(7) + dataList.add(AutoExplorationEntity("当前道路事件分析",2000L,false)) + dataList.add(AutoExplorationEntity("前方车辆",2000L,false)) + dataList.add(AutoExplorationEntity("两侧车辆",2600L,false)) + dataList.add(AutoExplorationEntity("后方车辆",3000L,false)) + dataList.add(AutoExplorationEntity("前方路口车辆流速分析",4000L,false)) + dataList.add(AutoExplorationEntity("前方路口行人/非机动车分析",4300L,false)) + dataList.add(AutoExplorationEntity("路侧视频分析",5000L,false)) + automaticExplorationAdapter.setListener(object: AutomaticExplorationB2Adapter.CompleteListener{ + override fun onComplete(entity: AutoExplorationEntity) { + dataList.forEach { + if(it.explorationContent == entity.explorationContent){ + it.explorationComplete = true + } + } + } + + }) + automaticExplorationAdapter.setData(dataList) + } + +} + +class NDEViewHolder(binding: View) : MessageViewHolder(binding){ + + private var tvNdeContent: TextView = binding.findViewById(R.id.tvNdeHintContent) + private var rvNdeList: RecyclerView = binding.findViewById(R.id.rvNdeList) + + override fun bind(item: AIMessage, onItemClickListener: OnItemClickListener?) { + if(item is AIMessage.NDEData){ + tvNdeContent.text = item.desc + val linearLayoutManager = LinearLayoutManager(itemView.context) + linearLayoutManager.orientation = LinearLayoutManager.HORIZONTAL + val ndeRoadAdapter = AINDERoadAdapter(itemView.context) + rvNdeList.adapter = ndeRoadAdapter + rvNdeList.layoutManager = linearLayoutManager + ndeRoadAdapter.setData(item.roadList) + } + } + +} + +private class NoScrollLayoutManager(context: Context?) : LinearLayoutManager(context) { + override fun canScrollVertically(): Boolean { + return false + } + + override fun canScrollHorizontally(): Boolean { + return false + } +} + +fun interface OnItemClickListener { + fun onItemClick(item: AIMessage, position: Int) +} diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AINDERoadAdapter.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AINDERoadAdapter.kt new file mode 100644 index 0000000000..51893534b4 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AINDERoadAdapter.kt @@ -0,0 +1,237 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.adapter + +import android.content.Context +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import com.mogo.och.shuttle.weaknet.passenger.R +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage + +class AINDERoadAdapter(private val context: Context): RecyclerView.Adapter() { + + private var roadList: List?= null + + fun setData(list: List){ + roadList = list + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AIRoadHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.b2_item_ai_nde_road, parent, false) + return AIRoadHolder(view) + } + + override fun onBindViewHolder(holder: AIRoadHolder, position: Int) { + roadList?.let{ + val roadMsg = it[position] + if(it.size < 3){ + //设置item宽度为最大宽度180dp + val params = ConstraintLayout.LayoutParams( + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90f, + context.resources.displayMetrics).toInt(), + ConstraintLayout.LayoutParams.WRAP_CONTENT) + holder.clRoadLayout.layoutParams = params + }else if(it.size == 3){ + //设置item宽度为最大宽度180dp + val params = ConstraintLayout.LayoutParams( + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60f, + context.resources.displayMetrics).toInt(), + ConstraintLayout.LayoutParams.WRAP_CONTENT) + holder.clRoadLayout.layoutParams = params + }else if(it.size == 4){ + //设置item宽度为最大宽度180dp + val params = ConstraintLayout.LayoutParams( + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50f, + context.resources.displayMetrics).toInt(), + ConstraintLayout.LayoutParams.WRAP_CONTENT) + holder.clRoadLayout.layoutParams = params + }else{ + val params = ConstraintLayout.LayoutParams( + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 45f, + context.resources.displayMetrics).toInt(), + ConstraintLayout.LayoutParams.WRAP_CONTENT) + holder.clRoadLayout.layoutParams = params + } + when(roadMsg.arrowType){ + //直行 + 201->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_forward + )) + } + //直行或左转 + 202->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_forward_or_turn_left + )) + } + //直行或右转 + 203->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_forward_or_turn_right + )) + } + //直行或掉头 + 204->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_forward_or_reverse + )) + } + //左转 + 205->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_turn_left + )) + } + //左转或掉头 + 206->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_turn_left_or_reverse + )) + } + //左弯或向左合流 + 207->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_turn_or_merge_left + )) + } + //右转 + 208->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_turn_right + )) + } + //右转或向右合流 + 209->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_turn_or_merge_right + )) + } + //左右转弯 + 210->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_turn_left_or_right + )) + } + //掉头 + 211->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_reverse + )) + } + //禁止左转 + 212->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_prohibit_turn_left + )) + } + //禁止右转 + 213->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_prohibit_turn_right + )) + } + //禁止掉头 + 214->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_prohibit_reverse + )) + } + //直行或左转或右转 + 215->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_forward_turn_left_right + )) + } + //直行或掉头或左转 + 216->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_forward_turn_left_reverse + )) + } + //右转或掉头 + 217->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_turn_right_or_reverse + )) + } + //禁止右转或向右合流 + 218->{ + holder.ivRoadType.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_road_prohibit_turn_or_merge_right + )) + } + } + //是否是推荐车道 + if(roadMsg.isRecommend){ + holder.tvRoadStatus.text = context.getString(R.string.nde_road_recommend) + holder.tvRoadStatus.setTextColor(context.getColor(R.color.msg_nde_road_recommend)) + holder.clRoadLayout.background = ContextCompat.getDrawable( + context, + R.drawable.bg_nde_road_recommend + ) + } + //是否有车龙,代表拥堵、行驶缓慢 + if(roadMsg.isCheLong){ + holder.tvRoadStatus.text = context.getString(R.string.nde_road_slow) + holder.tvRoadStatus.setTextColor(context.getColor(R.color.msg_nde_road_slow)) + } + if(position == it.lastIndex){ + holder.viewDivider.visibility = View.INVISIBLE + } + } + } + + override fun getItemCount() = roadList?.size ?: 0 + + class AIRoadHolder(itemView: View) : RecyclerView.ViewHolder(itemView){ + var clRoadLayout: ConstraintLayout = itemView.findViewById(R.id.clRoadLayout) + var ivRoadType: ImageView = itemView.findViewById(R.id.ivRoadType) + var tvRoadStatus: TextView = itemView.findViewById(R.id.tvRoadStatus) + var viewDivider: View = itemView.findViewById(R.id.viewDivider) + } + +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AutomaticExplorationB2Adapter.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AutomaticExplorationB2Adapter.kt new file mode 100644 index 0000000000..6117a085ba --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/AutomaticExplorationB2Adapter.kt @@ -0,0 +1,83 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.adapter + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.animation.LinearInterpolator +import android.widget.ImageView +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import com.mogo.eagle.core.data.notice.AutoExplorationEntity +import com.mogo.och.shuttle.weaknet.passenger.R + +/** + * 自动探查适配器 + * 鹰眼650需求 + */ +class AutomaticExplorationB2Adapter(val context: Context): RecyclerView.Adapter() { + + private var data: List ?= null + private var completeListener: CompleteListener ?= null + + fun setData(data: List){ + this.data = data + notifyDataSetChanged() + } + + fun setListener(listener: CompleteListener){ + completeListener = listener + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExplorationHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_auto_exploration_b2, parent, false) + return ExplorationHolder(view) + } + + override fun getItemCount() = data?.size ?: 0 + + override fun onBindViewHolder(holder: ExplorationHolder, position: Int) { + data?.let { + val entity = it[position] + holder.tvExplorationContent.text = entity.explorationContent + holder.ivExplorationLoading.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_exploration_loading_p + )) + val rotationAnim = ObjectAnimator.ofFloat(holder.ivExplorationLoading, "rotation", 0f, 360f) + rotationAnim.repeatCount = entity.explorationDuration.toInt()/1000 + rotationAnim.repeatMode = ValueAnimator.RESTART + rotationAnim.duration = 1000 + rotationAnim.interpolator = LinearInterpolator() + rotationAnim.addListener(object: AnimatorListenerAdapter(){ + override fun onAnimationEnd(animation: Animator) { + super.onAnimationEnd(animation) + completeListener?.onComplete(entity) + holder.ivExplorationLoading.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.icon_exploration_done_p + )) + } + }) + rotationAnim.start() + } + } + + class ExplorationHolder(itemView: View) : RecyclerView.ViewHolder(itemView){ + var ivExplorationLoading: ImageView = itemView.findViewById(R.id.ivExplorationLoading) + var tvExplorationContent: TextView = itemView.findViewById(R.id.tvExplorationContent) + } + + interface CompleteListener{ + fun onComplete(entity: AutoExplorationEntity) + } + +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/MessageDiffCallback.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/MessageDiffCallback.kt new file mode 100644 index 0000000000..011e06ca1c --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/MessageDiffCallback.kt @@ -0,0 +1,15 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.adapter + +import androidx.recyclerview.widget.DiffUtil +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage + +class MessageDiffCallback: DiffUtil.ItemCallback() { + + override fun areContentsTheSame(oldItem: AIMessage, newItem: AIMessage): Boolean { + return oldItem == newItem + } + + override fun areItemsTheSame(oldItem: AIMessage, newItem: AIMessage): Boolean { + return oldItem.id == newItem.id + } +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/PaddingItemDecoration.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/PaddingItemDecoration.kt new file mode 100644 index 0000000000..058a0a11c9 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/PaddingItemDecoration.kt @@ -0,0 +1,94 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.adapter + +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.view.View +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.StaggeredGridLayoutManager +import com.mogo.commons.AbsMogoApplication +import com.mogo.och.common.module.utils.ResourcesUtils +import com.mogo.och.shuttle.weaknet.passenger.R +import me.jessyan.autosize.utils.AutoSizeUtils + +class PaddingItemDecoration(private val topPadding: Int, private val bottomPadding: Int) : RecyclerView.ItemDecoration() { + + private var divider: Drawable + + private var padding = AutoSizeUtils.dp2px(AbsMogoApplication.getApp(),13f) + + init { + val shapeDrawable = GradientDrawable() + shapeDrawable.setColor(ResourcesUtils.getColor(R.color.b2_BBC9D4)) + shapeDrawable.shape = GradientDrawable.RECTANGLE + val width = AutoSizeUtils.dp2px(AbsMogoApplication.getApp(),343f) + val height = AutoSizeUtils.dp2px(AbsMogoApplication.getApp(),1f) + shapeDrawable.setSize(width, height) + divider = shapeDrawable + } + + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + super.getItemOffsets(outRect, view, parent, state) + + + // 只有第一个 item 的顶部添加空白 + if (parent.getChildAdapterPosition(view) == 0) { + outRect.top = topPadding + } else{ + outRect.top = 0 + } + + // 最后一个 item 的底部添加空白 +// if (parent.getChildAdapterPosition(view) == state.itemCount - 1) { +// outRect.bottom = bottomPadding +// } else{ +// outRect.bottom = 0 +// } + } + + override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { + super.onDraw(c, parent, state) + val childCount = parent.childCount //获取可见item的数量 + val spanCount: Int = getSpanCount(parent) + for (i in 0 until childCount) { + val child = parent.getChildAt(i) + val params = child + .layoutParams as RecyclerView.LayoutParams + val left = child.left - params.leftMargin + padding + val right = child.right - padding //- params.rightMargin - divider.intrinsicWidth + val top = child.bottom + params.bottomMargin + val bottom = top + divider.intrinsicHeight + divider.setBounds(left, top, right, bottom) + divider.draw(c) + if (i < spanCount) { //画第一行顶部的分割线 + drawHorizontalForFirstRow(c, child) + } + } + } + + private fun drawHorizontalForFirstRow(c: Canvas, child: View) { + val params = child + .layoutParams as RecyclerView.LayoutParams + val left = child.left - params.leftMargin - divider.intrinsicWidth + val top = child.top - params.topMargin - divider.intrinsicHeight + val right = child.right + params.rightMargin + divider.intrinsicWidth + val bottom = top + divider.intrinsicHeight + divider.setBounds(left, top, right, bottom) + divider.draw(c) + } + + private fun getSpanCount(parent: RecyclerView): Int { + // 列数 + var spanCount = -1 + val layoutManager = parent.layoutManager + if (layoutManager is GridLayoutManager) { + spanCount = layoutManager.spanCount + } else if (layoutManager is StaggeredGridLayoutManager) { + spanCount = layoutManager + .spanCount + } + return spanCount + } +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/RoadCrossRoamListB2Adapter.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/RoadCrossRoamListB2Adapter.kt new file mode 100644 index 0000000000..162b8516ea --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/adapter/RoadCrossRoamListB2Adapter.kt @@ -0,0 +1,65 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.ProgressBar +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.mogo.och.shuttle.weaknet.passenger.R +import kotlin.random.Random + + +class RoadCrossRoamListB2Adapter(private val mContext: Context) : RecyclerView.Adapter() { + + private val items: MutableList = mutableListOf() + + init { + items.add("前方路况拥堵分析") + items.add("路口危险车辆分析") + items.add("路口交通事故分析") + items.add("路口行人碰撞分析") + items.add("路口非机动车分析") + items.add("路口灯态分析") + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view: View = + LayoutInflater.from(mContext).inflate(R.layout.item_road_cross_ai_roam_tip_b2, parent, false) + return ViewHolder(view) + } + + override fun getItemCount(): Int { + return 6 + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + holder.textView.setTextColor(mContext.getColor(R.color.color_191A1C)) + holder.textView.text = item + // 随机决定是否显示ProgressBar +// if (Random.nextBoolean()) { // 50%的几率显示ProgressBar + holder.progressBar.visibility = View.VISIBLE + holder.checkIcon.visibility = View.INVISIBLE + + val r0 = Random.nextInt(0,3) + val r1 = Random.nextInt(1,9) + // 模拟加载完成 + holder.itemView.postDelayed({ + holder.progressBar.visibility = View.INVISIBLE + holder.checkIcon.visibility = View.VISIBLE + },r0 * 1000L + r1 * 100L) +// } else { +// holder.progressBar.visibility = View.GONE +// holder.checkIcon.visibility = View.VISIBLE +// } + } + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + var textView: TextView = itemView.findViewById(R.id.tvRoadItemTip) + var progressBar: ProgressBar = itemView.findViewById(R.id.pbRoadItemTip) + var checkIcon: ImageView = itemView.findViewById(R.id.ivRoadItemTip) + } +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/bean/AssistantMessage.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/bean/AssistantMessage.kt new file mode 100644 index 0000000000..bdbaaa8155 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/bean/AssistantMessage.kt @@ -0,0 +1,185 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean + +import android.os.CountDownTimer +import android.util.Log +import com.mogo.eagle.core.data.v2x.RoadV2NEventType +import kotlin.math.floor + + +sealed class AIMessage( + open val id: String, + open val title: String, + open val tts: String = "", + val timestamp: Long = System.currentTimeMillis(), + var showTimestamp: Boolean = false +) { + + companion object { + const val TYPE_SCAN = 0 + const val TYPE_EVENT = 1 + const val TYPE_LIGHT = 3 + const val TYPE_SPEED = 4 + const val TYPE_WARNING = 5 + const val TYPE_PNC_ACTION = 6 + const val TYPE_ROAD_V2N = 7 + const val TYPE_ROAD_CROSS = 8 + const val TYPE_AUTOMATIC_EXPLORATION = 9 + const val TYPE_NDE = 11 + } + + data class Scan( + override val id: String, + override val title: String, + val pictureUrl: String = "", + var showScanFlag: Boolean = false + ) : AIMessage(id, title) + + data class Event( + override val id: String, + override val title: String, + override val tts: String = "", + val position: String = "", + val distance: String = "", + val range: String = "", + val time: String = "", + val pictureUrl: String = "", + val videoUrl: String = "", + var showScanFlag: Boolean = false + ) : AIMessage(id, title, tts) + + data class QA( + override val id: String, + override val title: String, + override val tts: String = "", + val question: String, + val answer: String, + var state: QuestionState = QuestionState.UNDERSTAND, + val pictureUrl: String = "", + var pictureUrlList: List = listOf(), + val videoUrl: String = "", + ) : AIMessage(id, title, tts) { + + enum class QuestionState(val code: Int) { + UNDERSTAND(1), + ANALYZE(2), + ANSWER(3), + FINISH(4), + ERROR(-1), + } + } + + data class Light( + override val id: String, + override val title: String, + override val tts: String = "", + var seconds: Int, + val status: Int + ) : AIMessage(id, title, tts) { + private var countDownTimer: CountDownTimer? = null + private var listener: OnCountdownUpdateListener? = null + + fun startCountdown(millisInFuture: Long, countDownInternal: Long) { + countDownTimer?.cancel() + countDownTimer = object : CountDownTimer(millisInFuture, countDownInternal) { + override fun onTick(millisUntilFinished: Long) { + //倒计时开始 + Log.d( + "StartOrSlowDownTip", + "millisUntilFinished = $millisUntilFinished, countDownInternal = $countDownInternal" + ) + val cd = millisUntilFinished / 1000.0 +// val split = String.format("%.2f", cd).split(".") + seconds = floor(cd).toInt() + listener?.onCountdownUpdate() + } + + override fun onFinish() { + //倒计时完成 + seconds = 0 + listener?.onCountdownFinish() + } + } + countDownTimer?.start() + } + + fun stopCountdown() { + countDownTimer?.cancel() + } + + fun setOnCountdownUpdateListener(listener: OnCountdownUpdateListener) { + this.listener = listener + } + + interface OnCountdownUpdateListener { + fun onCountdownUpdate() + fun onCountdownFinish() + } + } + + + data class Speed( + override val id: String, + override val title: String, + override val tts: String = "", + val speedMax: Int, + val speedMin: Int, + ) : AIMessage(id, title, tts) + + data class Warning( + override val id: String, + override val title: String, + override val tts: String = "", + ) : AIMessage(id, title, tts) + + data class PNCAction( + override val id: String, + override val title: String, + var actionDesc: String, + var timeStamp: Long = 0, //事件发生事件戳 + ): AIMessage(id,title) + + data class RoadV2NEvent( + override val id: String, + override val title: String, + override val tts: String = "", //TTS的文案 + var eventType: RoadV2NEventType, //事件弹框类型 + var timeStamp: Long = 0, //事件发生事件戳 + var iconResId: Int, //事件icon res id + var isNeedTTS: Boolean = false, //事件文案是否需要同步tts + var contentImageUrl: String, // Image 类型时图片 url + var cameraIp: String, // 路侧camera ip,用于请求获取拉流地址 + var lon: Double, //事件坐标-经度 + var lat: Double, //事件坐标-纬度 + ): AIMessage(id,title,tts) + + data class RoadCrossRoam( + override val id: String, + override val title: String + ): AIMessage(id,title) + + data class AutomaticExploration( + override val id: String, + override val title: String + ): AIMessage(id,title) + + data class EvaluateData( + override val id: String, + override val title: String, + var isFirst:Boolean = true + ): AIMessage(id,title) + + data class NDEData( + override val id: String, + override val title: String, + var desc: String, + var roadList: List + ): AIMessage(id,title) + + data class RoadMsg( + var arrowType: Int, // 车道类型,如直行201(详情参考文件:message_pad.proto) + var laneNum: Int,// 车道号 + var isRecommend: Boolean,// 是否是推荐车道 + var isCheLong: Boolean// 是否有车龙,代表拥堵、行驶缓慢 + ) + +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/bean/ListenUIState.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/bean/ListenUIState.kt new file mode 100644 index 0000000000..1f98a8b5bf --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/bean/ListenUIState.kt @@ -0,0 +1,3 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean + +data class ListenUIState(val show: Boolean, val text: String, val showTips: Boolean = false) \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/AutomaticExplorationViewModel.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/AutomaticExplorationViewModel.kt new file mode 100644 index 0000000000..e84aea400f --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/AutomaticExplorationViewModel.kt @@ -0,0 +1,118 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.data + +import android.os.CountDownTimer +import androidx.lifecycle.ViewModel +import com.mogo.eagle.core.data.msgbox.MsgBoxBean +import com.mogo.eagle.core.data.msgbox.MsgBoxType +import com.mogo.eagle.core.data.msgbox.MsgCategory +import com.mogo.eagle.core.function.api.datacenter.msgbox.IMsgBoxListener +import com.mogo.eagle.core.function.call.msgbox.CallerMsgBoxListenerManager +import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger +import com.mogo.eagle.core.utilcode.util.ThreadUtils +import com.mogo.och.shuttle.weaknet.passenger.callback.ICommonCallback +import com.mogo.och.shuttle.weaknet.passenger.model.CommonModel +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.AIMessageManager +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage + +/** + * 自动探查 + */ +class AutomaticExplorationViewModel: ViewModel(), IMsgBoxListener, ICommonCallback { + + companion object{ + private const val TAG = "AutomaticExplorationViewModel" + private const val EXPLORATION_SHOW_TIME = 300000L //距离用户在触发上一次事件播报的时间5分钟后,自动触发常规道路情况检测 + } + + private var showViewTimer: CountDownTimer?= null //展示自动探查倒计时 + private var isCountingDown: Boolean = false //是否处于倒计时中 + private var hasOrder: Boolean = false // 车当前是否有订单 + + fun init(){ + CommonModel.setRouteLineInfoCallback(TAG,this) + CallerMsgBoxListenerManager.addListener(TAG,this) + } + + override fun onCleared() { + super.onCleared() + CommonModel.setRouteLineInfoCallback(TAG,null) + CallerMsgBoxListenerManager.removeListener(TAG) + } + + override fun showNoTaskView(isTrue: Boolean) { + super.showNoTaskView(isTrue) + if(isTrue){ + cancelTimer() + currentOrderStatus(false) + }else{ + startShowTimer() + currentOrderStatus(true) + } + } + + + override fun onDataChanged(category: MsgCategory, msgBoxList: MsgBoxBean) { + if(category == MsgCategory.NOTICE){ + if(msgBoxList.type == MsgBoxType.V2X){ + //重置倒计时时长 + cancelTimer() + if(hasOrder){ + startShowTimer() + } + } + } + } + + /** + * 取消倒计时 + */ + private fun cancelTimer(){ + CallerLogger.d(TAG,"cancelTimer") + showViewTimer?.cancel() + showViewTimer = null + isCountingDown = false + } + + /** + * 开始倒计时 + */ + private fun startShowTimer(){ + CallerLogger.d(TAG,"startShowTimer") + if(!isCountingDown){ + ThreadUtils.runOnUiThread { + if(showViewTimer == null){ + showViewTimer = object: CountDownTimer(EXPLORATION_SHOW_TIME,EXPLORATION_SHOW_TIME){ + override fun onTick(millisUntilFinished: Long) { + CallerLogger.d(TAG,"倒计时+:${millisUntilFinished}") + } + + override fun onFinish() { + if(hasOrder){ + showAutoExploration() + } + isCountingDown = false + } + } + } + isCountingDown = true + showViewTimer?.start() + } + } + } + + /** + * 设置当前订单状态 + * @param orderStatus true有订单;false没有订单 + */ + private fun currentOrderStatus(orderStatus: Boolean){ + hasOrder = orderStatus + } + + + + private fun showAutoExploration(){ + val automaticExploration = AIMessage.AutomaticExploration(System.currentTimeMillis().toString(),"") + AIMessageManager.post(automaticExploration) + } + +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/NDEViewModel.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/NDEViewModel.kt new file mode 100644 index 0000000000..a0f18dd3ea --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/NDEViewModel.kt @@ -0,0 +1,87 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.data + +import androidx.lifecycle.ViewModel +import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotIdentifyListener +import com.mogo.eagle.core.function.call.autopilot.CallerAutopilotIdentifyListenerManager +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.AIMessageManager +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage +import mogo.telematics.pad.MessagePad.TrackedObject + +/** + * 车龙信息 + */ +class NDEViewModel: ViewModel(), IMoGoAutopilotIdentifyListener { + + companion object{ + private const val TAG = "NDEViewModel" + } + + private var lastMap2 = HashMap() + private var lastTime = 0L + + fun init(){ + CallerAutopilotIdentifyListenerManager.addListener(TAG, this) + } + + override fun onAutopilotIdentifyDataUpdate(trafficData: List?){ + super.onAutopilotIdentifyDataUpdate(trafficData) + handleCheLong(trafficData) + } + + private fun handleCheLong(trafficData: List?) { + var hasCheLong = false + var isNewData = false + val roadMsgList = ArrayList() + val curMap = HashMap() + if (lastTime > 0 && System.currentTimeMillis() - lastTime > 60000) { + lastMap2.clear()// 清除上次车龙事件的缓存 + } + var lastLocStr: String? = "" + trafficData?.forEach { obj -> + if (obj.type == 707) {// 当前方向所有车道 + if (obj.polygonCount > 1) {// 当次数据有车龙事件 + lastLocStr = lastMap2[obj.laneNum] + // 1. 上次无车龙但此次有车龙,认为是新车龙事件 + // 2. 两次都有车龙,则判断车龙的长度是否相同 + if (lastLocStr == null || lastLocStr == "0" || lastLocStr != "${obj.polygonList[0]}-${obj.polygonList[obj.polygonCount - 1]}") { + isNewData = true + } + hasCheLong = true + + // key: 车道号,value: 非0代表有车龙且首、尾两个点可计算车龙长度 + curMap[obj.laneNum] = + "${obj.polygonList[0]}-${obj.polygonList[obj.polygonCount - 1]}" + } else {// 当次没有车龙 + if (lastMap2[obj.laneNum] != null && lastMap2[obj.laneNum] != "0") {// 3. 上次有车龙,这次无车龙 + isNewData = true + } + // key: 车道号,value: 0代表无车龙 + curMap[obj.laneNum] = "0" + } + // 保存所有车道信息 + roadMsgList.add( + AIMessage.RoadMsg( + obj.arrowType, + laneNum = obj.laneNum, + isRecommend = obj.suggestedLanes, + isCheLong = obj.polygonCount > 1 + ) + ) + } + } + if (isNewData) { + // 清除上次车道信息 + lastMap2.clear() + if (hasCheLong) { + // 缓存当次车龙事件所有车道信息 + lastMap2.putAll(curMap) + lastTime = System.currentTimeMillis() + + val sortedList = roadMsgList.sortedWith(compareByDescending { it.laneNum }) + val ndeEvent = AIMessage.NDEData(System.currentTimeMillis().toString(),"路口车龙","前方路口有车龙",sortedList) + AIMessageManager.post(ndeEvent) + } + } + } + +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/PNCActionsViewModel.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/PNCActionsViewModel.kt new file mode 100644 index 0000000000..74964909d2 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/PNCActionsViewModel.kt @@ -0,0 +1,259 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.data + +import androidx.lifecycle.ViewModel +import com.mogo.eagle.core.data.autopilot.pnc.PncActionsHelper +import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotPlanningActionsListener +import com.mogo.eagle.core.function.api.autopilot.IMoGoAutopilotStatusListener +import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager +import com.mogo.eagle.core.function.call.autopilot.CallerPlanningActionsListenerManager +import com.mogo.och.common.module.manager.logchainanalytic.OchChainLogManager +import com.mogo.och.common.module.manager.loop.BizLoopManager +import com.mogo.och.data.bean.BusStationBean +import com.mogo.och.shuttle.weaknet.passenger.callback.ICommonCallback +import com.mogo.och.shuttle.weaknet.passenger.model.CommonModel +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.AIMessageManager +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage +import mogo.telematics.pad.MessagePad + +class PNCActionsViewModel: ViewModel(), IMoGoAutopilotPlanningActionsListener, ICommonCallback { + + companion object{ + private const val TAG = "PNCActionsViewModel" + } + + private var currentAction = "" + + private var currentStation:BusStationBean?=null + + fun init(){ + CallerPlanningActionsListenerManager.addListener(TAG, this) + CommonModel.setRouteLineInfoCallback(TAG,this) + } + + override fun onCleared() { + super.onCleared() + CallerPlanningActionsListenerManager.removeListener(TAG) + CommonModel.setRouteLineInfoCallback(TAG,null) + } + + override fun updateStationsInfo( + stations: MutableList?, + currentStationIndex: Int, + isArrived: Boolean + ) { + try { + currentStation = stations?.get(currentStationIndex) + }catch (e:Exception){ + OchChainLogManager.writeChainLogError("PNCActionsViewModel 设置错误","${e.message}") + } + } + + override fun showNoTaskView(isTrue: Boolean) { + super.showNoTaskView(isTrue) + if(isTrue){ + currentStation = null + } + } + + + override fun pncActions(planningActionMsg: MessagePad.PlanningActionMsg) { + try { + BizLoopManager.runInMainThread { + if (CallerAutoPilotStatusListenerManager.getState() == IMoGoAutopilotStatusListener.STATUS_AUTOPILOT_RUNNING) { + var actions: String? = null + planningActionMsg.actionMsg?.let { + try { + actions = PncActionsHelper.getAction( + it.drivingState.number, + it.drivingAction.number + ) + } catch (e: Exception) { + e.printStackTrace() + } + } + planningActionMsg.v2NActionMsgList?.forEach { v2nAction -> + actions = PncActionsHelper.getAction( + v2nAction.drivingState.number, + v2nAction.drivingAction.number + ) + } + // update view + actions?.let { + if(it.isNotEmpty() && it != currentAction){ + currentAction = it + val title = getActionTitle(it) + if(title.isNotEmpty()){ + val desc = getActionDesc(title) + val action = AIMessage.PNCAction(it+System.currentTimeMillis(),title,desc,System.currentTimeMillis()) + AIMessageManager.post(action) + } + } + } + + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun getActionTitle(pncAction: String): String{ + return when(pncAction){ + "正在进站"->{ + "车辆进站" + } + "等待进站"->{ + "车辆等待进站" + } + "正在出站"->{ + "车辆出站" + } + "等待出站"->{ + "车辆等待出站" + } + "正在向左变道"->{ + "车辆向左变道" + } + "正在向右变道"->{ + "车辆向右变道" + } + "正在完成变道"->{ + "车辆完成变道" + } + "正在绕过障碍物"->{ + "车辆正在绕过前方障碍物" + } + "正在向左绕行避让前方静止障碍物"->{ + "车辆正在绕过前方障碍物" + } + "正在向右绕行避让前方静止障碍物"->{ + "车辆正在绕过前方障碍物" + } + "正在避让障碍物"->{ + "车辆正在避让前方障碍物" + } + "正在向左变道避让前方静止障碍物"->{ + "车辆正在避让前方障碍物" + } + "正在向右变道避让前方静止障碍物"->{ + "车辆正在避让前方障碍物" + } + "正在等红灯"->{ + "路口等红灯" + } + "正在向左变道避让前方道路施工"->{ + "车辆正在变道避让前方道路施工" + } + "正在向右变道避让前方道路施工"->{ + "车辆正在变道避让前方道路施工" + } + "正在向左绕行避让前方道路施工"->{ + "车辆正在绕行避让前方道路施工" + } + "正在向右绕行避让前方道路施工"->{ + "车辆正在绕行避让前方道路施工" + } + + "正在向左变道避让前方道路事故"->{ + "车辆正在变道避让前方道路事故" + } + "正在向右变道避让前方道路事故"->{ + "车辆正在变道避让前方道路事故" + } + "正在向左绕行避让前方道路事故"->{ + "车辆正在绕行避让前方道路事故" + } + "正在向右绕行避让前方道路事故"->{ + "车辆正在绕行避让前方道路事故" + } + "正在跟随车辆行驶"->{ + "车辆正在跟车通行" + } + "正在跟车行驶"->{ + "车辆正在跟车通行" + } + "正在向左变道避让前方车龙"->{ + "车辆正在绕行前方车龙" + } + "正在向右变道避让前方车龙"->{ + "车辆正在绕行前方车龙" + } + "正在使用云端规划通过路口"->{ + "车辆正在使用云端轨迹通行" + } + "正在避让后方来车"->{ + "车辆正在避让后方来车" + } + else->{ + "" + } + } + } + + private fun getActionDesc(action: String): String{ + return when(action){ + "车辆进站"->{ + "前方即将到达${CommonModel.routesResult}," + + "车辆正在规划减速并进站停靠,请安心等待车辆停稳再下车哦~" + } + "车辆等待进站"->{ + "车辆待环境安全后进站,耐心等几秒,安全比赶路更重要~" + } + "车辆出站"->{ + "欢迎乘坐MOGO RoboTaxi~车辆正在规划出站,坐稳扶好哦,我们出发啦!小智持续守护您的行程。" + } + "车辆等待出站"->{ + "车辆待环境安全后出站,耐心等几秒,安全比赶路更重要~" + } + "车辆向左变道"->{ + "确认环境安全,车辆正在规划平稳向左变道,同时持续监测周边交通参与者动向,放心交给我们吧!" + } + "车辆向右变道"->{ + "确认环境安全,车辆正在规划平稳向右变道,同时持续监测周边交通参与者动向,放心交给我们吧!" + } + "车辆完成变道"->{ + "变道完成啦,继续前进!小智持续守护您的行程。" + } + "车辆正在绕过前方障碍物"->{ + "发现前方障碍物,车辆正在规划平稳变道,即将画出一条完美弧线~" + } + "车辆正在避让前方障碍物"->{ + "发现前方障碍物,车辆正在规划平稳避让,诠释优雅与丝滑~" + } + "车辆完成绕障"->{ + "绕障完成啦,继续前进!小智持续守护您的行程。" + } + "路口等红灯"->{ + "车辆正在路口等红灯,可以安心放空望望窗外~小智一直在您身边哦!" + } + "车辆正在变道避让前方道路施工"->{ + "车辆正在提前规划变道避让前方道路施工,稳稳的很安心~您已体验到车路云一体化协同应用场景,是当之无愧的先锋体验官!" + } + "车辆正在绕行避让前方道路施工"->{ + "车辆正在提前规划绕行避让前方道路施工,稳稳的很安心~您已体验到车路云一体化协同应用场景,是当之无愧的先锋体验官!" + } + "车辆正在变道避让前方道路事故"->{ + "车辆正在提前规划变道避让前方道路事故,放心看我表现吧!您已体验到车路云一体化协同应用场景,小智为您欢呼!" + } + "车辆正在绕行避让前方道路事故"->{ + "车辆正在提前规划绕行避让前方道路事故,放心看我表现吧!您已体验到车路云一体化协同应用场景,小智为您欢呼!" + } + "车辆正在跟车通行"->{ + "车辆正在跟随前车通行,舒适度MAX~您已体验到车路云一体化协同应用场景,超越全国99%的乘客!" + } + "车辆正在绕行前方车龙"->{ + "车辆正在提前规划变道避让路口车龙,舒适度MAX~。您已体验到车路云一体化协同应用场景,超越全国99%的乘客!" + } + "车辆正在使用云端轨迹通行"->{ + "前方智慧路口内有障碍物,车辆正在使用云端规划轨迹通行。您已体验到车路云一体化协同应用场景,超越全国99%的乘客!" + } + "车辆正在避让后方来车"->{ + "车辆正在避让后方来车,耐心等几秒,安全比赶路更重要~" + } + else->{ + "" + } + } + } + +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/RoadCrossRoamViewModel.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/RoadCrossRoamViewModel.kt new file mode 100644 index 0000000000..3b00bc7caf --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/RoadCrossRoamViewModel.kt @@ -0,0 +1,73 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.data + +import android.content.Context +import androidx.lifecycle.ViewModel +import com.mogo.commons.voice.AIAssist +import com.mogo.eagle.core.data.config.FunctionBuildConfig +import com.mogo.eagle.core.function.api.map.road.IMoGoMapRoadListener +import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager +import com.mogo.eagle.core.function.call.autopilot.CallerServicesEventManager +import com.mogo.eagle.core.function.call.hmi.CallerHmiViewControlListenerManager +import com.mogo.eagle.core.function.call.map.CallerMapIdentifyManager +import com.mogo.eagle.core.function.call.map.CallerMapRoadListenerManager +import com.mogo.eagle.core.utilcode.mogo.AppIdentityModeUtils +import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger +import com.mogo.eagle.core.utilcode.util.ToastUtils +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.AIMessageManager +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage + +class RoadCrossRoamViewModel: ViewModel(), IMoGoMapRoadListener { + + companion object{ + const val TAG = "RoadCrossRoamViewModel" + } + + private lateinit var mContext: Context + + fun init(context: Context){ + CallerMapRoadListenerManager.addListener(TAG, this) + mContext = context + } + + override fun onCrossDevice(trigger: Boolean) { + super.onCrossDevice(trigger) + if(trigger){ + show() + } + } + + private fun show(){ + // 没有路线不做提示 + if (CallerAutoPilotStatusListenerManager.getLineId() == 0L) { + CallerLogger.d(TAG, "没有路线不做提示") + return + } + // 处于漫游模式下不做处理 + if (CallerMapIdentifyManager.roam.second) { + if (CallerMapIdentifyManager.roam.first != TAG) { + ToastUtils.showLong("正在漫游中,不展示路口漫游") + } + CallerLogger.d(TAG, "正在漫游中,不展示路口漫游") + return + } + // 首页被遮挡不做提示 + if (!CallerHmiViewControlListenerManager.getMainPageVisible()) { + CallerLogger.d(TAG, "attachView return , mainPageVisible is false") + return + } + // 没有路侧设备,不做处理 + CallerLogger.d(TAG, "命中,attachView") + val cross = CallerMapRoadListenerManager.getCrossEndInfo() + if (cross.isNullOrEmpty()) { + CallerLogger.d(TAG, "未触发,路口ID:$cross") + return + } + if (AppIdentityModeUtils.isDriver(FunctionBuildConfig.appIdentityMode)){ + val disStr = "为您提供路口全息影像,助力出行" + AIAssist.getInstance(mContext).speakTTSVoiceWithLevel(disStr, AIAssist.NEW_LEVEL_2) + } + CallerServicesEventManager.updateServicesNum(CallerServicesEventManager.ServiceType.ROAD) + AIMessageManager.post(AIMessage.RoadCrossRoam(System.currentTimeMillis().toString(),"")) + } + +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/RoadV2NEventViewModel.kt b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/RoadV2NEventViewModel.kt new file mode 100644 index 0000000000..e12074b35b --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/java/b2/com/mogo/och/shuttle/weaknet/passenger/ui/mind/data/RoadV2NEventViewModel.kt @@ -0,0 +1,74 @@ +package com.mogo.och.shuttle.weaknet.passenger.ui.mind.data + +import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.ViewModel +import androidx.lifecycle.lifecycleScope +import com.mogo.commons.utils.MogoAnalyticUtils +import com.mogo.eagle.core.data.v2x.RoadV2NEventWindowBean +import com.mogo.eagle.core.function.api.hmi.v2n.IRoadV2NEventWindowListener +import com.mogo.eagle.core.function.call.autopilot.CallerAutoPilotStatusListenerManager +import com.mogo.eagle.core.function.call.hmi.CallerHmiViewControlListenerManager +import com.mogo.eagle.core.function.call.hmi.CallerRoadV2NEventWindowListenerManager +import com.mogo.eagle.core.function.hmi.ui.utils.HmiActionLog +import com.mogo.eagle.core.utilcode.mogo.logger.CallerLogger +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.AIMessageManager +import com.mogo.och.shuttle.weaknet.passenger.ui.mind.bean.AIMessage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class RoadV2NEventViewModel: ViewModel(), IRoadV2NEventWindowListener { + + companion object{ + const val TAG = "RoadV2NEventViewModel" + const val ANALYTICS_KEY = "hmi_road_event_window_view" + + fun trackEvent(msg: String) { + ProcessLifecycleOwner.get().lifecycleScope.launch(Dispatchers.IO) { + val map: MutableMap = HashMap() + map["msg"] = msg + MogoAnalyticUtils.track( + ANALYTICS_KEY, + map + ) + HmiActionLog.hmiAction(TAG, msg) + } + CallerLogger.i(TAG, msg) + } + } + + fun init(){ + CallerRoadV2NEventWindowListenerManager.addListener(TAG, this) + } + + override fun show(dataBean: RoadV2NEventWindowBean) { + trackEvent("show --> $dataBean") + val canShowV2NEventWindowView = CallerHmiViewControlListenerManager.getMainPageVisible() + if (!canShowV2NEventWindowView) { + trackEvent("show --> 当前不在高精地图页面,跳过") + return + } + val lineId = CallerAutoPilotStatusListenerManager.getLineId() + if (lineId <= 0) { + trackEvent("show --> 当前无订单,跳过") + return + } + val event = AIMessage.RoadV2NEvent( + dataBean.eventId, + dataBean.hintStr, + "", + dataBean.eventType, + dataBean.timestamp, + dataBean.iconResId, + false, + dataBean.contentImageUrl, + dataBean.cameraIp, + dataBean.lon, + dataBean.lat + ) + AIMessageManager.post(event) + } + + override fun dismiss(eventId: String) { + + } +} \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_aip_bg.9.png b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_aip_bg.9.png new file mode 100644 index 0000000000..32f10e1ca5 Binary files /dev/null and b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_aip_bg.9.png differ diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_mind_bg.png b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_mind_bg.png new file mode 100644 index 0000000000..e62e3f8dfd Binary files /dev/null and b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_mind_bg.png differ diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_mind_top_shader.png b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_mind_top_shader.png new file mode 100644 index 0000000000..63634bd8f3 Binary files /dev/null and b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_mind_top_shader.png differ diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_prediction_bg.9.png b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_prediction_bg.9.png new file mode 100644 index 0000000000..6e00fe6f11 Binary files /dev/null and b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/b2_prediction_bg.9.png differ diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/icon_cover_line.png b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/icon_cover_line.png new file mode 100644 index 0000000000..206f9c8d9e Binary files /dev/null and b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/icon_cover_line.png differ diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/icon_image_error.png b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/icon_image_error.png new file mode 100644 index 0000000000..4cadae7774 Binary files /dev/null and b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/icon_image_error.png differ diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/icon_image_holder.png b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/icon_image_holder.png new file mode 100644 index 0000000000..3febfc5587 Binary files /dev/null and b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/icon_image_holder.png differ diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/m2_map_right_top.png b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/m2_map_right_top.png new file mode 100644 index 0000000000..a889bf8503 Binary files /dev/null and b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/m2_map_right_top.png differ diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/m2_map_video_bg.png b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/m2_map_video_bg.png new file mode 100644 index 0000000000..b2ca76b4a0 Binary files /dev/null and b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/m2_map_video_bg.png differ diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/m2_mind_item_icon.png b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/m2_mind_item_icon.png new file mode 100644 index 0000000000..94b54e567d Binary files /dev/null and b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable-nodpi/m2_mind_item_icon.png differ diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable/b2_map_top_shader.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable/b2_map_top_shader.xml new file mode 100644 index 0000000000..2ef485a4c8 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable/b2_map_top_shader.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable/icon_pic_error.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable/icon_pic_error.xml new file mode 100644 index 0000000000..5de9d5afff --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable/icon_pic_error.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable/icon_pic_holder.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable/icon_pic_holder.xml new file mode 100644 index 0000000000..67373cd139 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/drawable/icon_pic_holder.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/b2_item_ai_nde_road.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/b2_item_ai_nde_road.xml new file mode 100644 index 0000000000..f7b6542170 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/b2_item_ai_nde_road.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_automatic_exploration.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_automatic_exploration.xml new file mode 100644 index 0000000000..1fa06b11ab --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_automatic_exploration.xml @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_nde_event.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_nde_event.xml new file mode 100644 index 0000000000..b8a78e6c8e --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_nde_event.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_pnc_action.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_pnc_action.xml new file mode 100644 index 0000000000..564fd242b5 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_pnc_action.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_road_cross_roam.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_road_cross_roam.xml new file mode 100644 index 0000000000..82d7902ee2 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_road_cross_roam.xml @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_road_v2n_event.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_road_v2n_event.xml new file mode 100644 index 0000000000..2e284542e6 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_ai_road_v2n_event.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_auto_exploration_b2.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_auto_exploration_b2.xml new file mode 100644 index 0000000000..9f665788b7 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_auto_exploration_b2.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_road_cross_ai_roam_tip_b2.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_road_cross_ai_roam_tip_b2.xml new file mode 100644 index 0000000000..24398afc97 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/item_road_cross_ai_roam_tip_b2.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/m2_mind_view.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/m2_mind_view.xml new file mode 100644 index 0000000000..e4b78c0498 --- /dev/null +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/m2_mind_view.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/shuttle_p_m2_fragment.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/shuttle_p_m2_fragment.xml index fe413aa398..bd62a8df61 100644 --- a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/shuttle_p_m2_fragment.xml +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/shuttle_p_m2_fragment.xml @@ -62,9 +62,9 @@ + android:layout_height="1290dp"/> diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/shuttle_p_m2_hpmap_fragment.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/shuttle_p_m2_hpmap_fragment.xml index 6076a84f55..fd4eeabd40 100644 --- a/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/shuttle_p_m2_hpmap_fragment.xml +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/layout/shuttle_p_m2_hpmap_fragment.xml @@ -4,40 +4,99 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - + android:layout_height="@dimen/dp_650"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OCH/shuttle/passenger_weaknet/src/main/res/b2/values/colors.xml b/OCH/shuttle/passenger_weaknet/src/main/res/b2/values/colors.xml index 17676ab9d1..47f724a874 100644 --- a/OCH/shuttle/passenger_weaknet/src/main/res/b2/values/colors.xml +++ b/OCH/shuttle/passenger_weaknet/src/main/res/b2/values/colors.xml @@ -27,4 +27,7 @@ #516582 #95B1D6 #1F82FB + #D7F1FF + #394047 + #BBC9D4 \ No newline at end of file