feat: 组件解耦

This commit is contained in:
yangxisong 2025-11-27 16:52:43 +08:00
parent 4712855f6c
commit 0c122b9352
14 changed files with 96 additions and 61 deletions

View File

@ -1,25 +1,23 @@
package com.yuanxuan.rokid package com.yuanxuan.rokid
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.KeyEvent import android.view.KeyEvent
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.yuanxuan.rokid.databinding.ActivityMainBinding import com.yuanxuan.rokid.databinding.ActivityMainBinding
import com.yuanxuan.rokid.dependencies.AppDependencies
import com.yuanxuan.rokid.device.DeviceServiceManager
import com.yuanxuan.rokid.extension.fadeIn import com.yuanxuan.rokid.extension.fadeIn
import com.yuanxuan.rokid.extension.fadeOut import com.yuanxuan.rokid.extension.fadeOut
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private val viewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -45,11 +43,9 @@ class MainActivity : AppCompatActivity() {
} }
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
return if (AppDependencies.deviceServiceManager.instructState.value == return if (viewModel.uiState.value.isVoiceInputVisible) {
DeviceServiceManager.InstructState.WaitingInstructState
) {
if (event.keyCode == KeyEvent.KEYCODE_BACK) { if (event.keyCode == KeyEvent.KEYCODE_BACK) {
AppDependencies.deviceServiceManager.quitInstructReceived() viewModel.quitInstructReceived()
} }
true true
} else { } else {
@ -59,37 +55,18 @@ class MainActivity : AppCompatActivity() {
private fun observer() { private fun observer() {
lifecycleScope.launch { lifecycleScope.launch {
/** viewModel.uiState.collect { uiState ->
* 监听电量 binding.batteryLevel.text =
*/ resources.getString(R.string.status_bar_battery, uiState.batteryLevel)
AppDependencies.deviceServiceManager.batteryPercentage.collect { binding.batteryLevelIv.setImageLevel(uiState.batteryLevel)
binding.batteryLevel.text = resources.getString(R.string.status_bar_battery, it) binding.wifiIv.setImageLevel(uiState.wifiLevel)
binding.batteryLevelIv.setImageLevel(it) if (uiState.isVoiceInputVisible) {
} binding.lottieVoiceInput.playAnimation()
} binding.lottieVoiceInput.fadeIn(300)
lifecycleScope.launch { } else {
AppDependencies.deviceServiceManager.wifiState.collect {
when (it) {
is DeviceServiceManager.WifiState.Connected -> binding.wifiIv.setImageLevel(it.level)
DeviceServiceManager.WifiState.Unconnected -> binding.wifiIv.setImageLevel(0)
}
}
}
lifecycleScope.launch {
AppDependencies.deviceServiceManager.instructState.collect {
when (it) {
DeviceServiceManager.InstructState.None -> {
binding.lottieVoiceInput.cancelAnimation() binding.lottieVoiceInput.cancelAnimation()
binding.lottieVoiceInput.fadeOut(300) binding.lottieVoiceInput.fadeOut(300)
} }
DeviceServiceManager.InstructState.WaitingInstructState -> {
binding.lottieVoiceInput.playAnimation()
binding.lottieVoiceInput.fadeIn(300)
}
}
} }
} }
} }

View File

@ -1,10 +1,14 @@
package com.yuanxuan.rokid package com.yuanxuan.rokid
import androidx.activity.result.launch
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.yuanxuan.rokid.dependencies.AppDependencies.deviceServiceManager
import com.yuanxuan.rokid.device.DeviceServiceManager
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MainViewModel : ViewModel() { class MainViewModel : ViewModel() {
@ -12,6 +16,30 @@ class MainViewModel : ViewModel() {
private val _keyEventFlow = MutableSharedFlow<KeyEvent>() private val _keyEventFlow = MutableSharedFlow<KeyEvent>()
val keyEventFlow = _keyEventFlow.asSharedFlow() val keyEventFlow = _keyEventFlow.asSharedFlow()
val uiState = combine(
deviceServiceManager.batteryPercentage,
deviceServiceManager.wifiState,
deviceServiceManager.instructState
) { battery, wifi, instruct ->
MainUiState(
batteryLevel = battery,
wifiLevel = if (wifi is DeviceServiceManager.WifiState.Connected) wifi.level else 0,
isVoiceInputVisible = instruct == DeviceServiceManager.InstructState.WaitingInstructState
)
}.stateIn(
scope = viewModelScope,
initialValue = MainUiState(
batteryLevel = 0,
wifiLevel = 0,
isVoiceInputVisible = false
),
started = SharingStarted.Eagerly,
)
fun quitInstructReceived(){
deviceServiceManager.quitInstructReceived()
}
fun onKeyEventDispatched(event: KeyEvent) { fun onKeyEventDispatched(event: KeyEvent) {
viewModelScope.launch { viewModelScope.launch {
_keyEventFlow.emit(event) _keyEventFlow.emit(event)
@ -24,4 +52,10 @@ class MainViewModel : ViewModel() {
data object Enter : KeyEvent //前滑 data object Enter : KeyEvent //前滑
} }
data class MainUiState(
val batteryLevel: Int,
val wifiLevel: Int,
val isVoiceInputVisible: Boolean,
)
} }

View File

@ -2,10 +2,10 @@ package com.yuanxuan.rokid
import android.app.Application import android.app.Application
import android.content.Intent import android.content.Intent
import android.os.Build
import com.yuanxuan.rokid.dependencies.AppDependencies import com.yuanxuan.rokid.dependencies.AppDependencies
import com.yuanxuan.rokid.dependencies.ApplicationDependencyProvider import com.yuanxuan.rokid.dependencies.ApplicationDependencyProvider
import com.yuanxuan.rokid.keeplive.KeepLiveService import com.yuanxuan.rokid.keeplive.KeepLiveService
import com.yuanxuan.rokid.network.websocket.WebSocketReconnectWorker
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@ -28,7 +28,7 @@ class RokidApplication : Application() {
runCatching { runCatching {
AppDependencies.deviceServiceManager.getSn() AppDependencies.deviceServiceManager.getSn()
}.onSuccess { }.onSuccess {
AppDependencies.webSocketManager.connect() WebSocketReconnectWorker.schedule(this@RokidApplication)
}.onFailure { }.onFailure {
Timber.e(it) Timber.e(it)
} }

View File

@ -57,7 +57,7 @@ class DeviceServiceManager(val context: Application) : ConnectivityManager.Netwo
/** /**
* 系统电量 * 系统电量
*/ */
private val _batteryPercentage = MutableStateFlow(-1) private val _batteryPercentage = MutableStateFlow(getBatteryLevel(context))
val batteryPercentage = _batteryPercentage.asStateFlow() val batteryPercentage = _batteryPercentage.asStateFlow()
/** /**
@ -293,6 +293,11 @@ class DeviceServiceManager(val context: Application) : ConnectivityManager.Netwo
} }
} }
private fun getBatteryLevel(context: Context): Int {
val bm = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
return bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
inner class SysKeyReceiver : BroadcastReceiver() { inner class SysKeyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action ?: return val action = intent?.action ?: return

View File

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

View File

@ -22,13 +22,14 @@ class OkHttpWebSocketConnection() : WebSocketListener(), WebSocketConnection {
private var client: WebSocket? = null private var client: WebSocket? = null
@Synchronized @Synchronized
override fun connect() { override fun connect(sn: String) {
val okHttpClient = OkHttpClient.Builder() val okHttpClient = OkHttpClient.Builder()
.readTimeout(DEFAULT_SEND_TIMEOUT) .readTimeout(DEFAULT_SEND_TIMEOUT)
.pingInterval(PING_INTERVAL_TIME) .pingInterval(PING_INTERVAL_TIME)
.build() .build()
val request = Request.Builder().url(NetUtils.getBaseUrl()).build() val request = Request.Builder().url(NetUtils.getBaseUrl())
.addHeader("sn", sn).build()
_webSocketConnectionStateFlow.update { _webSocketConnectionStateFlow.update {
WebSocketConnectionState.CONNECTING WebSocketConnectionState.CONNECTING
} }
@ -55,6 +56,7 @@ class OkHttpWebSocketConnection() : WebSocketListener(), WebSocketConnection {
} }
override fun onOpen(webSocket: WebSocket, response: Response) { override fun onOpen(webSocket: WebSocket, response: Response) {
_webSocketConnectionStateFlow.update { WebSocketConnectionState.CONNECTED }
Timber.d("websocket onOpen") Timber.d("websocket onOpen")
} }

View File

@ -1,6 +1,5 @@
package com.yuanxuan.rokid.network.websocket package com.yuanxuan.rokid.network.websocket
import kotlinx.coroutines.flow.MutableStateFlow
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
interface WebSocketConnection { interface WebSocketConnection {
@ -10,7 +9,7 @@ interface WebSocketConnection {
val PING_INTERVAL_TIME = 30.seconds val PING_INTERVAL_TIME = 30.seconds
} }
fun connect() fun connect(sn: String)
fun isDead(): Boolean fun isDead(): Boolean

View File

@ -29,8 +29,8 @@ class WebSocketManager(context: Context, scope: CoroutineScope) {
} }
} }
fun connect() { fun connect(sn: String) {
webSocketConnection.connect() webSocketConnection.connect(sn)
} }

View File

@ -19,7 +19,7 @@ class WebSocketReconnectWorker(context: Context, workerParams: WorkerParameters)
override fun doWork(): Result { override fun doWork(): Result {
Timber.d("尝试开始重连") Timber.d("尝试开始重连")
AppDependencies.deviceServiceManager.playTTS("开始重连") AppDependencies.deviceServiceManager.playTTS("开始重连")
AppDependencies.webSocketManager.connect() AppDependencies.webSocketManager.connect(AppDependencies.deviceServiceManager.sn)
return Result.success() return Result.success()
} }

View File

@ -10,7 +10,6 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.yuanxuan.rokid.R import com.yuanxuan.rokid.R
import com.yuanxuan.rokid.databinding.FragmentSettingBinding import com.yuanxuan.rokid.databinding.FragmentSettingBinding
import com.yuanxuan.rokid.dependencies.AppDependencies
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class BrightnessFragment : Fragment() { class BrightnessFragment : Fragment() {
@ -37,7 +36,6 @@ class BrightnessFragment : Fragment() {
binding.tip.text = resources.getString(R.string.setting_brightness_tip) binding.tip.text = resources.getString(R.string.setting_brightness_tip)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewModel.brightnessFlow.collect { data -> viewModel.brightnessFlow.collect { data ->
adapter.submitList(data) adapter.submitList(data)
@ -49,12 +47,12 @@ class BrightnessFragment : Fragment() {
return@setOnKeyListener if (event.action == KeyEvent.ACTION_UP) { return@setOnKeyListener if (event.action == KeyEvent.ACTION_UP) {
when (event.keyCode) { when (event.keyCode) {
KeyEvent.KEYCODE_DPAD_RIGHT -> { KeyEvent.KEYCODE_DPAD_RIGHT -> {
AppDependencies.deviceServiceManager.brightnessAdd() viewModel.brightnessAdd()
true true
} }
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
AppDependencies.deviceServiceManager.brightnessSubtract() viewModel.brightnessSubtract()
true true
} }

View File

@ -43,7 +43,8 @@ class HomeFragment : Fragment() {
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
binding.recyclerView.itemAnimator = null binding.recyclerView.itemAnimator = null
lifecycleScope.launch {
viewLifecycleOwner.lifecycleScope.launch {
viewModel.homeMenuBeans.collect { viewModel.homeMenuBeans.collect {
adapter.submitList(it) adapter.submitList(it)
} }

View File

@ -47,4 +47,22 @@ class SettingViewModel : ViewModel() {
replay = 1 replay = 1
) )
fun brightnessAdd(){
AppDependencies.deviceServiceManager.brightnessAdd()
}
fun brightnessSubtract(){
AppDependencies.deviceServiceManager.brightnessSubtract()
}
fun volumeAdd(){
AppDependencies.deviceServiceManager.volumeAdd()
}
fun volumeSubtract(){
AppDependencies.deviceServiceManager.volumeSubtract()
}
fun deviceSn()= AppDependencies.deviceServiceManager.sn
} }

View File

@ -5,14 +5,16 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.yuanxuan.rokid.BuildConfig import com.yuanxuan.rokid.BuildConfig
import com.yuanxuan.rokid.R import com.yuanxuan.rokid.R
import com.yuanxuan.rokid.databinding.FragmentSnBinding import com.yuanxuan.rokid.databinding.FragmentSnBinding
import com.yuanxuan.rokid.dependencies.AppDependencies
class SnFragment : Fragment() { class SnFragment : Fragment() {
private lateinit var binding: FragmentSnBinding private lateinit var binding: FragmentSnBinding
private val viewModel by viewModels<SettingViewModel>()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -26,7 +28,7 @@ class SnFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.sn.text = binding.sn.text =
resources.getString(R.string.device_info_sn, AppDependencies.deviceServiceManager.sn) resources.getString(R.string.device_info_sn, viewModel.deviceSn())
binding.appVersion.text = binding.appVersion.text =
resources.getString(R.string.device_info_app_version, BuildConfig.VERSION_NAME) resources.getString(R.string.device_info_app_version, BuildConfig.VERSION_NAME)
} }

View File

@ -10,7 +10,6 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.yuanxuan.rokid.R import com.yuanxuan.rokid.R
import com.yuanxuan.rokid.databinding.FragmentSettingBinding import com.yuanxuan.rokid.databinding.FragmentSettingBinding
import com.yuanxuan.rokid.dependencies.AppDependencies
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class VolumeFragment : Fragment() { class VolumeFragment : Fragment() {
@ -49,12 +48,12 @@ class VolumeFragment : Fragment() {
return@setOnKeyListener if (event.action == KeyEvent.ACTION_DOWN) { return@setOnKeyListener if (event.action == KeyEvent.ACTION_DOWN) {
when (event.keyCode) { when (event.keyCode) {
KeyEvent.KEYCODE_DPAD_RIGHT -> { KeyEvent.KEYCODE_DPAD_RIGHT -> {
AppDependencies.deviceServiceManager.volumeAdd() viewModel.volumeAdd()
true true
} }
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
AppDependencies.deviceServiceManager.volumeSubtract() viewModel.volumeSubtract()
true true
} }