feat: 服务消息UI交互

This commit is contained in:
yangxisong 2025-12-02 15:21:29 +08:00
parent 84961a367c
commit 9c29fee24e
15 changed files with 202 additions and 28 deletions

View File

@ -1,5 +1,6 @@
package com.yuanxuan.rokid
import android.content.Intent
import android.os.Bundle
import android.view.KeyEvent
import androidx.activity.addCallback
@ -11,9 +12,15 @@ import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavOptions
import androidx.navigation.fragment.NavHostFragment
import com.yuanxuan.rokid.databinding.ActivityMainBinding
import com.yuanxuan.rokid.extension.fadeIn
import com.yuanxuan.rokid.extension.fadeOut
import com.yuanxuan.rokid.keeplive.KeepLiveService
import com.yuanxuan.rokid.network.websocket.WebSocketEvent
import com.yuanxuan.rokid.ui.NoticeFragment
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
@ -39,8 +46,33 @@ class MainActivity : AppCompatActivity() {
/**
* 拦截返回键事件防止返回到桌面
*/
onBackPressedDispatcher.addCallback {}
onBackPressedDispatcher.addCallback {
viewModel.wakeUp()
navigationToast("TestMessage")
}
/**
* 这个服务保证心跳包准时发
*/
val serviceIntent = Intent(this, KeepLiveService::class.java)
startForegroundService(serviceIntent)
/**
* 出路服务端事件
*/
lifecycleScope.launch {
viewModel.socketEvent.collect { event ->
when (event) {
is WebSocketEvent.Notice -> {
viewModel.wakeUp()
delay(500)
navigationToast(event.msg)
}
WebSocketEvent.Unknow -> {}
}
}
}
}
@ -55,6 +87,23 @@ class MainActivity : AppCompatActivity() {
}
}
private fun navigationToast(toast: String) {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val options = NavOptions.Builder()
.setEnterAnim(android.R.anim.fade_in)
.setExitAnim(android.R.anim.fade_out)
.setPopEnterAnim(android.R.anim.fade_in)
.setPopExitAnim(android.R.anim.fade_out)
.build()
val bundle = Bundle().apply {
putString(NoticeFragment.TOAST_MESSAGE, toast)
}
val navController = navHostFragment.navController
navController.navigate(R.id.notice_fragment, bundle, options)
}
private fun observer() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {

View File

@ -2,6 +2,7 @@ package com.yuanxuan.rokid
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.yuanxuan.rokid.dependencies.AppDependencies
import com.yuanxuan.rokid.dependencies.AppDependencies.deviceServiceManager
import com.yuanxuan.rokid.device.DeviceServiceManager
import kotlinx.coroutines.flow.MutableSharedFlow
@ -37,6 +38,8 @@ class MainViewModel : ViewModel() {
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds),
)
val socketEvent = AppDependencies.webSocketManager.socketEventFlow
fun quitInstructReceived() {
deviceServiceManager.quitInstructReceived()
}
@ -47,6 +50,14 @@ class MainViewModel : ViewModel() {
}
}
fun wakeUp() {
deviceServiceManager.wakeup()
}
fun playTTS(tts: String) {
deviceServiceManager.playTTS(tts)
}
sealed interface KeyEvent {
data object DpadRight : KeyEvent //前滑
data object DpadLeft : KeyEvent //后滑

View File

@ -1,10 +1,8 @@
package com.yuanxuan.rokid
import android.app.Application
import android.content.Intent
import com.yuanxuan.rokid.dependencies.AppDependencies
import com.yuanxuan.rokid.dependencies.ApplicationDependencyProvider
import com.yuanxuan.rokid.keeplive.KeepLiveService
import com.yuanxuan.rokid.network.websocket.WebSocketReconnectWorker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -33,11 +31,6 @@ class RokidApplication : Application() {
Timber.e(it)
}
}
/**
* 这个服务保证心跳包准时发
*/
val serviceIntent = Intent(this, KeepLiveService::class.java)
startForegroundService(serviceIntent)
}
}

View File

@ -196,6 +196,13 @@ class DeviceServiceManager(val context: Application) : ConnectivityManager.Netwo
_instructState.update { InstructState.None }
}
fun wakeup() {
systemFuncServiceTodo { service ->
service.wakeUp()
}
}
fun playTTS(msg: String) {
ttsServiceTodo {
ttsService?.playTtsMsg(msg)

View File

@ -6,8 +6,8 @@ import java.net.NetworkInterface
object NetUtils {
// fun getBaseUrl() = "http://${getLocalIPV4address()}.70:7070"
fun getBaseUrl() = "http://${getLocalIPV4address()}.52:8765"
fun getBaseUrl() = "http://${getLocalIPV4address()}.70:7070"
// fun getBaseUrl() = "http://${getLocalIPV4address()}.52:8765"
/**

View File

@ -0,0 +1,22 @@
package com.yuanxuan.rokid.network.bean
import com.google.gson.JsonElement
import com.google.gson.annotations.SerializedName
data class WebSocketResponse(
@SerializedName("MsgType")
val msgType: Int,
@SerializedName("Msg")
val msg: JsonElement
) {
enum class MsgType(val value: Int) {
Notice(0);
companion object {
fun fromValue(value: Int) = entries.firstOrNull { it.value == value }
}
}
}

View File

@ -1,11 +1,17 @@
package com.yuanxuan.rokid.network.websocket
import com.google.gson.Gson
import com.yuanxuan.rokid.network.NetUtils
import com.yuanxuan.rokid.network.bean.WebSocketResponse
import com.yuanxuan.rokid.network.websocket.WebSocketConnection.Companion.DEFAULT_SEND_TIMEOUT
import com.yuanxuan.rokid.network.websocket.WebSocketConnection.Companion.PING_INTERVAL_TIME
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@ -13,12 +19,16 @@ import okhttp3.WebSocket
import okhttp3.WebSocketListener
import timber.log.Timber
class OkHttpWebSocketConnection() : WebSocketListener(), WebSocketConnection {
class OkHttpWebSocketConnection(val scope: CoroutineScope) : WebSocketListener(),
WebSocketConnection {
private val _webSocketConnectionStateFlow: MutableStateFlow<WebSocketConnectionState> =
MutableStateFlow(WebSocketConnectionState.DISCONNECTED)
val webSocketConnectionStateFlow = _webSocketConnectionStateFlow.asStateFlow()
private val _eventFlow = MutableSharedFlow<WebSocketEvent>()
val eventFlow = _eventFlow.asSharedFlow()
private var client: WebSocket? = null
@Synchronized
@ -38,6 +48,20 @@ class OkHttpWebSocketConnection() : WebSocketListener(), WebSocketConnection {
override fun onMessage(webSocket: WebSocket, text: String) {
Timber.d(text)
val response = Gson().fromJson(text, WebSocketResponse::class.java)
val msgType = WebSocketResponse.MsgType.fromValue(response.msgType)
val event = when (msgType) {
WebSocketResponse.MsgType.Notice -> {
WebSocketEvent.Notice(response.msg.asString)
}
null -> {
WebSocketEvent.Unknow
}
}
scope.launch {
_eventFlow.emit(event)
}
}
override fun onOpen(webSocket: WebSocket, response: Response) {

View File

@ -0,0 +1,7 @@
package com.yuanxuan.rokid.network.websocket
sealed interface WebSocketEvent {
data class Notice(val msg: String) : WebSocketEvent
data object Unknow : WebSocketEvent
}

View File

@ -7,7 +7,9 @@ import kotlinx.coroutines.launch
class WebSocketManager(context: Context, scope: CoroutineScope) {
private val webSocketConnection = OkHttpWebSocketConnection()
private val webSocketConnection = OkHttpWebSocketConnection(scope = scope)
val socketEventFlow = webSocketConnection.eventFlow
init {
scope.launch {

View File

@ -2,23 +2,27 @@ package com.yuanxuan.rokid.network.websocket
import android.content.Context
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.OutOfQuotaPolicy
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.yuanxuan.rokid.dependencies.AppDependencies
import kotlinx.coroutines.delay
import timber.log.Timber
class WebSocketReconnectWorker(context: Context, workerParams: WorkerParameters) : Worker(
class WebSocketReconnectWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(
context,
workerParams
) {
override fun doWork(): Result {
override suspend fun doWork(): Result {
/**
* 第一次启动给点时间连wifi重连等5s
*/
delay(5000)
Timber.d("尝试开始重连")
AppDependencies.deviceServiceManager.playTTS("开始重连")
AppDependencies.webSocketManager.connect(AppDependencies.deviceServiceManager.sn)
return Result.success()
}

View File

@ -1,10 +0,0 @@
package com.yuanxuan.rokid.network.websocket
import com.google.gson.annotations.SerializedName
data class WebSocketResponseMessage<T>(
@SerializedName("MsgType")
val msgType: Int,
@SerializedName("Msg")
val msg: T
)

View File

@ -0,0 +1,38 @@
package com.yuanxuan.rokid.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.yuanxuan.rokid.databinding.FragmentNoticeBinding
class NoticeFragment : Fragment() {
companion object {
const val TOAST_MESSAGE = "toast_message"
}
private lateinit var binding: FragmentNoticeBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentNoticeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val toast = arguments?.getString(TOAST_MESSAGE)
binding.toast.text = toast
}
override fun onPause() {
super.onPause()
findNavController().navigateUp()
}
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/toast"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="30.dp"
android:paddingEnd="30.dp"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据测试数据" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -14,7 +14,7 @@
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#ffffff"
android:textColor="@color/white"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@ -39,4 +39,10 @@
android:label="sn"
tools:layout="@layout/fragment_sn" />
<fragment
android:id="@+id/notice_fragment"
android:name="com.yuanxuan.rokid.ui.NoticeFragment"
android:label="notice"
tools:layout="@layout/fragment_notice" />
</navigation>