feat: home
This commit is contained in:
parent
e5d17bc970
commit
699bc11c25
|
|
@ -44,6 +44,7 @@ dependencies {
|
||||||
implementation(fileTree("libs") { include("*.aar", "*.jar") })
|
implementation(fileTree("libs") { include("*.aar", "*.jar") })
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
|
implementation(libs.androidx.leanback)
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
implementation(libs.androidx.activity)
|
implementation(libs.androidx.activity)
|
||||||
implementation(libs.androidx.constraintlayout)
|
implementation(libs.androidx.constraintlayout)
|
||||||
|
|
@ -52,7 +53,9 @@ dependencies {
|
||||||
implementation(libs.androidx.work.ktx)
|
implementation(libs.androidx.work.ktx)
|
||||||
implementation(libs.retrofit)
|
implementation(libs.retrofit)
|
||||||
implementation(libs.retrofit.converter.gson)
|
implementation(libs.retrofit.converter.gson)
|
||||||
// implementation(libs.gson)
|
implementation(libs.lottie)
|
||||||
|
implementation(libs.androidx.navigation.fragment)
|
||||||
|
implementation(libs.androidx.navigation.ui)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.yuanxuan.rokid
|
package com.yuanxuan.rokid
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.KeyEvent
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
@ -10,6 +11,8 @@ 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.dependencies.AppDependencies
|
||||||
import com.yuanxuan.rokid.device.DeviceServiceManager
|
import com.yuanxuan.rokid.device.DeviceServiceManager
|
||||||
|
import com.yuanxuan.rokid.extension.fadeIn
|
||||||
|
import com.yuanxuan.rokid.extension.fadeOut
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
@ -29,6 +32,46 @@ class MainActivity : AppCompatActivity() {
|
||||||
insets
|
insets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
observer()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截返回键事件,防止返回到桌面
|
||||||
|
*/
|
||||||
|
onBackPressedDispatcher.addCallback {
|
||||||
|
if (AppDependencies.deviceServiceManager.instructState.value
|
||||||
|
== DeviceServiceManager.InstructState.WaitingInstructState
|
||||||
|
) {
|
||||||
|
AppDependencies.deviceServiceManager.quitInstructReceived()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触摸板事件
|
||||||
|
* [KeyEvent.KEYCODE_DPAD_DOWN] 和 [KeyEvent.KEYCODE_DPAD_RIGHT] 同时响应
|
||||||
|
* 所以直接消费掉一个
|
||||||
|
*/
|
||||||
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
|
return if (event.action == KeyEvent.ACTION_DOWN) {
|
||||||
|
when (event.keyCode) {
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN -> {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP -> {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> super.dispatchKeyEvent(event)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.dispatchKeyEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observer() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
/**
|
/**
|
||||||
* 监听电量
|
* 监听电量
|
||||||
|
|
@ -48,10 +91,20 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
lifecycleScope.launch {
|
||||||
* 拦截返回键事件,防止返回到桌面
|
AppDependencies.deviceServiceManager.instructState.collect {
|
||||||
*/
|
when (it) {
|
||||||
onBackPressedDispatcher.addCallback {
|
DeviceServiceManager.InstructState.None -> {
|
||||||
|
binding.lottieVoiceInput.cancelAnimation()
|
||||||
|
binding.lottieVoiceInput.fadeOut(300)
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceServiceManager.InstructState.WaitingInstructState -> {
|
||||||
|
binding.lottieVoiceInput.playAnimation()
|
||||||
|
binding.lottieVoiceInput.fadeIn(300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.yuanxuan.rokid
|
||||||
|
|
||||||
|
import androidx.activity.result.launch
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class MainViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private val _keyEventFlow = MutableSharedFlow<KeyEvent>()
|
||||||
|
val keyEventFlow = _keyEventFlow.asSharedFlow()
|
||||||
|
|
||||||
|
fun onKeyEventDispatched(event: KeyEvent) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_keyEventFlow.emit(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface KeyEvent {
|
||||||
|
data object DpadRight : KeyEvent //前滑
|
||||||
|
data object DpadLeft : KeyEvent //后滑
|
||||||
|
data object Enter : KeyEvent //前滑
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -67,6 +67,19 @@ class DeviceServiceManager(val context: Application) : ConnectivityManager.Netwo
|
||||||
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 音量
|
||||||
|
*/
|
||||||
|
private val _volume: MutableStateFlow<Int> = MutableStateFlow(0)
|
||||||
|
val volume = _volume.asStateFlow()
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 亮度
|
||||||
|
*/
|
||||||
|
private val _brightness: MutableStateFlow<Int> = MutableStateFlow(0)
|
||||||
|
val brightness = _brightness.asStateFlow()
|
||||||
|
|
||||||
lateinit var sn: String
|
lateinit var sn: String
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
@ -124,27 +137,19 @@ class DeviceServiceManager(val context: Application) : ConnectivityManager.Netwo
|
||||||
}
|
}
|
||||||
|
|
||||||
IInstructService.INSTRUCT_VOLUME_UP -> {
|
IInstructService.INSTRUCT_VOLUME_UP -> {
|
||||||
systemFuncServiceTodo { service ->
|
volumeAdd()
|
||||||
setVolumeSpecified(service.volumeSpecified + 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IInstructService.INSTRUCT_VOLUME_DOWN -> {
|
IInstructService.INSTRUCT_VOLUME_DOWN -> {
|
||||||
systemFuncServiceTodo { service ->
|
volumeSubtract()
|
||||||
setVolumeSpecified(service.volumeSpecified - 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IInstructService.INSTRUCT_LIGHT_UP -> {
|
IInstructService.INSTRUCT_LIGHT_UP -> {
|
||||||
systemFuncServiceTodo { service ->
|
brightnessAdd()
|
||||||
setBrightnessSpecified(service.brightnessSpecified + 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IInstructService.INSTRUCT_LIGHT_DOWN -> {
|
IInstructService.INSTRUCT_LIGHT_DOWN -> {
|
||||||
systemFuncServiceTodo { service ->
|
brightnessSubtract()
|
||||||
setBrightnessSpecified(service.brightnessSpecified - 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IInstructService.INSTRUCT_TAKE_PHOTO -> {
|
IInstructService.INSTRUCT_TAKE_PHOTO -> {
|
||||||
|
|
@ -185,12 +190,48 @@ class DeviceServiceManager(val context: Application) : ConnectivityManager.Netwo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun quitInstructReceived() {
|
||||||
|
_instructState.update { InstructState.None }
|
||||||
|
}
|
||||||
|
|
||||||
fun playTTS(msg: String) {
|
fun playTTS(msg: String) {
|
||||||
ttsServiceTodo {
|
ttsServiceTodo {
|
||||||
ttsService?.playTtsMsg(msg)
|
ttsService?.playTtsMsg(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun volumeAdd() {
|
||||||
|
systemFuncServiceTodo { service ->
|
||||||
|
val volume = (service.volumeSpecified + 1).coerceIn(0, 15)
|
||||||
|
service.volumeSpecified = volume
|
||||||
|
_volume.update { volume }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun volumeSubtract() {
|
||||||
|
systemFuncServiceTodo { service ->
|
||||||
|
val volume = (service.volumeSpecified - 1).coerceIn(0, 15)
|
||||||
|
service.volumeSpecified = volume
|
||||||
|
_volume.update { volume }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun brightnessAdd() {
|
||||||
|
systemFuncServiceTodo { service ->
|
||||||
|
val brightness = (service.brightnessSpecified + 1).coerceIn(0, 15)
|
||||||
|
service.brightnessSpecified = brightness
|
||||||
|
_brightness.update { brightness }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun brightnessSubtract() {
|
||||||
|
systemFuncServiceTodo { service ->
|
||||||
|
val brightness = (service.brightnessSpecified - 1).coerceIn(0, 15)
|
||||||
|
service.brightnessSpecified = brightness
|
||||||
|
_brightness.update { brightness }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getSn() = suspendCancellableCoroutine { continuation ->
|
suspend fun getSn() = suspendCancellableCoroutine { continuation ->
|
||||||
ServiceManager.getSystemFuncService(context) { service ->
|
ServiceManager.getSystemFuncService(context) { service ->
|
||||||
if (service == null) {
|
if (service == null) {
|
||||||
|
|
@ -199,6 +240,8 @@ class DeviceServiceManager(val context: Application) : ConnectivityManager.Netwo
|
||||||
}
|
}
|
||||||
systemFuncService = service
|
systemFuncService = service
|
||||||
sn = service.sn
|
sn = service.sn
|
||||||
|
_volume.update { service.volumeSpecified }
|
||||||
|
_brightness.update { service.brightnessSpecified }
|
||||||
continuation.resume(sn)
|
continuation.resume(sn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -248,14 +291,6 @@ class DeviceServiceManager(val context: Application) : ConnectivityManager.Netwo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setVolumeSpecified(volume: Int) {
|
|
||||||
systemFuncService?.volumeSpecified = volume.coerceIn(0, 15)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setBrightnessSpecified(brightness: Int) {
|
|
||||||
systemFuncService?.brightnessSpecified = brightness.coerceIn(0, 15)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.yuanxuan.rokid.extension
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.util.TypedValue
|
||||||
|
|
||||||
|
fun Number.dpToPx(): Float {
|
||||||
|
return TypedValue.applyDimension(
|
||||||
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
this.toFloat(),
|
||||||
|
Resources.getSystem().displayMetrics
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.yuanxuan.rokid.extension
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.animation.AnimatorListenerAdapter
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
|
||||||
|
|
||||||
|
fun View.animateTranslationY(translationY: Float, duration: Int) {
|
||||||
|
animate()
|
||||||
|
.translationY(translationY)
|
||||||
|
.setDuration(250)
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.fadeIn(duration: Long) {
|
||||||
|
if (this.isVisible) return
|
||||||
|
this.alpha = 0f
|
||||||
|
this.isVisible = true
|
||||||
|
this.animate()
|
||||||
|
.alpha(1f)
|
||||||
|
.setDuration(duration)
|
||||||
|
.setListener(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.fadeOut(duration: Long) {
|
||||||
|
if (this.isVisible.not()) return
|
||||||
|
this.animate()
|
||||||
|
.alpha(0f)
|
||||||
|
.setDuration(duration)
|
||||||
|
.setListener(object : AnimatorListenerAdapter() {
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
this@fadeOut.isGone = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.yuanxuan.rokid.network.http
|
package com.yuanxuan.rokid.network.bean
|
||||||
|
|
||||||
data class ApiResponse<T>(
|
data class ApiResponse<T>(
|
||||||
val code: String,
|
val code: String,
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.yuanxuan.rokid.network.http
|
package com.yuanxuan.rokid.network.http
|
||||||
|
|
||||||
|
import com.yuanxuan.rokid.network.bean.Test
|
||||||
|
|
||||||
class ApiRepository(private val apiService: ApiService) {
|
class ApiRepository(private val apiService: ApiService) {
|
||||||
|
|
||||||
suspend fun test(): Test? {
|
suspend fun test(): Test? {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.yuanxuan.rokid.network.http
|
package com.yuanxuan.rokid.network.http
|
||||||
|
|
||||||
import com.yuanxuan.rokid.network.NetUtils
|
import com.yuanxuan.rokid.network.NetUtils
|
||||||
|
import com.yuanxuan.rokid.network.bean.ApiResponse
|
||||||
|
import com.yuanxuan.rokid.network.bean.Test
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Url
|
import retrofit2.http.Url
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,8 @@ package com.yuanxuan.rokid.network.websocket
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.Date
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
class WebSocketManager(context: Context, scope: CoroutineScope) {
|
class WebSocketManager(context: Context, scope: CoroutineScope) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
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.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import com.yuanxuan.rokid.R
|
||||||
|
import com.yuanxuan.rokid.databinding.FragmentHomeBinding
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class HomeFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentHomeBinding
|
||||||
|
private val viewModel by viewModels<HomeViewModel>()
|
||||||
|
|
||||||
|
private val adapter by lazy {
|
||||||
|
HomeMenuAdapter { item ->
|
||||||
|
when (item.type) {
|
||||||
|
is HomeMenuAdapter.HomeMenuBean.Type.Brightness -> {}
|
||||||
|
is HomeMenuAdapter.HomeMenuBean.Type.Sn ->
|
||||||
|
findNavController().navigate(R.id.home_to_sn)
|
||||||
|
is HomeMenuAdapter.HomeMenuBean.Type.Volume -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentHomeBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
binding.recyclerView.adapter = adapter
|
||||||
|
binding.recyclerView.itemAnimator = null
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.homeMenuBeans.collect {
|
||||||
|
Timber.d(it.toString())
|
||||||
|
adapter.submitList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
package com.yuanxuan.rokid.ui
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.yuanxuan.rokid.databinding.ItemHomeBinding
|
||||||
|
|
||||||
|
class HomeMenuAdapter(val itemClick: (HomeMenuBean) -> Unit) :
|
||||||
|
ListAdapter<HomeMenuAdapter.HomeMenuBean, HomeMenuAdapter.ViewHolder>(DiffCallback()) {
|
||||||
|
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(
|
||||||
|
parent: ViewGroup,
|
||||||
|
viewType: Int
|
||||||
|
): ViewHolder {
|
||||||
|
val binding = ItemHomeBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return ViewHolder(
|
||||||
|
binding = binding,
|
||||||
|
itemClick = itemClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(
|
||||||
|
holder: ViewHolder,
|
||||||
|
position: Int
|
||||||
|
) {
|
||||||
|
val item = getItem(position)
|
||||||
|
bindTitle(
|
||||||
|
holder = holder,
|
||||||
|
item = item,
|
||||||
|
)
|
||||||
|
holder.bindClick(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: List<Any?>) {
|
||||||
|
if (payloads.isEmpty()) {
|
||||||
|
super.onBindViewHolder(holder, position, payloads)
|
||||||
|
} else {
|
||||||
|
payloads.forEach {
|
||||||
|
val payload = it as Int
|
||||||
|
if ((DiffCallback.FLAG_TYPE_VALUE_CHANGED and payload) != 0) {
|
||||||
|
bindTitle(holder, getItem(position))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindTitle(holder: ViewHolder, item: HomeMenuBean) {
|
||||||
|
val displayTitle = when (val type = item.type) {
|
||||||
|
is HomeMenuBean.Type.Sn -> item.title
|
||||||
|
is HomeMenuBean.Type.Brightness,
|
||||||
|
is HomeMenuBean.Type.Volume -> "${item.title}: ${type.value}"
|
||||||
|
}
|
||||||
|
holder.binding.title.text = displayTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ViewHolder(val binding: ItemHomeBinding, val itemClick: (HomeMenuBean) -> Unit) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
fun bindClick(item: HomeMenuBean) {
|
||||||
|
binding.parent.setOnClickListener {
|
||||||
|
itemClick(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class HomeMenuBean(
|
||||||
|
val title: String,
|
||||||
|
@param:DrawableRes val icon: Int,
|
||||||
|
val type: Type,
|
||||||
|
) {
|
||||||
|
sealed interface Type {
|
||||||
|
val value: String
|
||||||
|
|
||||||
|
data class Brightness(override val value: String) : Type
|
||||||
|
data class Volume(override val value: String) : Type
|
||||||
|
data class Sn(override val value: String) : Type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DiffCallback : DiffUtil.ItemCallback<HomeMenuBean>() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val FLAG_TYPE_VALUE_CHANGED = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areItemsTheSame(
|
||||||
|
oldItem: HomeMenuBean,
|
||||||
|
newItem: HomeMenuBean
|
||||||
|
): Boolean {
|
||||||
|
return oldItem.title == newItem.title
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(
|
||||||
|
oldItem: HomeMenuBean,
|
||||||
|
newItem: HomeMenuBean
|
||||||
|
): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getChangePayload(oldItem: HomeMenuBean, newItem: HomeMenuBean): Any? {
|
||||||
|
var payload = 0
|
||||||
|
val oldType = oldItem.type
|
||||||
|
val newType = newItem.type
|
||||||
|
// 不会出现类型变化 只有value 变更
|
||||||
|
when (oldType) {
|
||||||
|
is HomeMenuBean.Type.Brightness,
|
||||||
|
is HomeMenuBean.Type.Volume -> oldType.value == newType.value
|
||||||
|
|
||||||
|
is HomeMenuBean.Type.Sn -> true
|
||||||
|
}.let {
|
||||||
|
if (it.not()) {
|
||||||
|
payload = 0 or FLAG_TYPE_VALUE_CHANGED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (payload == 0) null else payload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package com.yuanxuan.rokid.ui
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.yuanxuan.rokid.R
|
||||||
|
import com.yuanxuan.rokid.dependencies.AppDependencies
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class HomeViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private val _homeMenuBeans = MutableStateFlow(
|
||||||
|
listOf(
|
||||||
|
HomeMenuAdapter.HomeMenuBean(
|
||||||
|
title = "音量",
|
||||||
|
icon = R.drawable.icon_battery_100,
|
||||||
|
type = HomeMenuAdapter.HomeMenuBean.Type.Volume("0"),
|
||||||
|
),
|
||||||
|
HomeMenuAdapter.HomeMenuBean(
|
||||||
|
title = "亮度",
|
||||||
|
icon = R.drawable.icon_battery_100,
|
||||||
|
type = HomeMenuAdapter.HomeMenuBean.Type.Brightness("0"),
|
||||||
|
),
|
||||||
|
HomeMenuAdapter.HomeMenuBean(
|
||||||
|
title = "SN",
|
||||||
|
icon = R.drawable.icon_battery_100,
|
||||||
|
type = HomeMenuAdapter.HomeMenuBean.Type.Sn("0"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val homeMenuBeans = _homeMenuBeans.asStateFlow()
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
AppDependencies.deviceServiceManager.brightness.collect { brightness ->
|
||||||
|
_homeMenuBeans.update { oldBeans ->
|
||||||
|
oldBeans.map { data ->
|
||||||
|
when (data.type) {
|
||||||
|
is HomeMenuAdapter.HomeMenuBean.Type.Brightness -> data.copy(
|
||||||
|
type = data.type.copy(
|
||||||
|
value = "$brightness"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
AppDependencies.deviceServiceManager.volume.collect { volume ->
|
||||||
|
_homeMenuBeans.update { oldBeans ->
|
||||||
|
oldBeans.map { data ->
|
||||||
|
when (data.type) {
|
||||||
|
is HomeMenuAdapter.HomeMenuBean.Type.Volume -> data.copy(
|
||||||
|
type = data.type.copy(
|
||||||
|
value = "$volume"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.yuanxuan.rokid.ui
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
|
||||||
|
class SettingFragment: Fragment() {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
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 com.yuanxuan.rokid.databinding.FragmentSnBinding
|
||||||
|
import com.yuanxuan.rokid.dependencies.AppDependencies
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class SnFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var binding: FragmentSnBinding
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
binding = FragmentSnBinding.inflate(inflater, container, false)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
try {
|
||||||
|
binding.sn.text = "SN:${AppDependencies.deviceServiceManager.sn}"
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
Timber.d("onDestroy")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_focused="true">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="200"
|
||||||
|
android:propertyName="translationY"
|
||||||
|
android:valueTo="-10dp"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item android:state_focused="false">
|
||||||
|
<set>
|
||||||
|
<objectAnimator
|
||||||
|
android:duration="200"
|
||||||
|
android:propertyName="translationY"
|
||||||
|
android:valueTo="0dp"
|
||||||
|
android:valueType="floatType" />
|
||||||
|
</set>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<corners android:radius="10.dp" />
|
||||||
|
<stroke
|
||||||
|
android:width="1.dp"
|
||||||
|
android:color="@color/white" />
|
||||||
|
</shape>
|
||||||
|
|
@ -7,6 +7,14 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".MainActivity">
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/nav_host_fragment"
|
||||||
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:defaultNavHost="true"
|
||||||
|
app:navGraph="@navigation/app_navigation" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/battery_level"
|
android:id="@+id/battery_level"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
@ -47,6 +55,18 @@
|
||||||
app:layout_constraintBottom_toTopOf="@id/bottom"
|
app:layout_constraintBottom_toTopOf="@id/bottom"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<com.airbnb.lottie.LottieAnimationView
|
||||||
|
android:id="@+id/lottie_voice_input"
|
||||||
|
android:layout_width="200.dp"
|
||||||
|
android:layout_height="100.dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/bottom"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:lottie_loop="true"
|
||||||
|
app:lottie_rawRes="@raw/lottie_input_voice"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
android:id="@+id/bottom"
|
android:id="@+id/bottom"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?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">
|
||||||
|
|
||||||
|
<androidx.leanback.widget.HorizontalGridView
|
||||||
|
android:id="@+id/recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="85.dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:paddingStart="10.dp"
|
||||||
|
android:paddingTop="10.dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:listitem="@layout/item_home" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?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"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="setting"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?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:focusable="true"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/sn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:focusedByDefault="true"
|
||||||
|
android:nextFocusLeft="@id/sn2"
|
||||||
|
android:stateListAnimator="@animator/focus_animator"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:text="askjdhkjashdkjhsad" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/sn2"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:stateListAnimator="@animator/focus_animator"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/sn"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:text="askjdhkjashdkjhsad" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?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:id="@+id/parent"
|
||||||
|
android:layout_width="50.dp"
|
||||||
|
android:layout_height="75.dp"
|
||||||
|
android:layout_marginEnd="10.dp"
|
||||||
|
android:background="@drawable/bg_home_menu"
|
||||||
|
android:focusable="true"
|
||||||
|
android:stateListAnimator="@animator/focus_animator">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
android:textSize="10.sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="@string/home_menu_setting" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<navigation 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:id="@+id/app_navigation"
|
||||||
|
app:startDestination="@id/home_fragment">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/home_fragment"
|
||||||
|
android:name="com.yuanxuan.rokid.ui.HomeFragment"
|
||||||
|
android:label="home"
|
||||||
|
tools:layout="@layout/fragment_home">
|
||||||
|
<action
|
||||||
|
android:id="@+id/home_to_sn"
|
||||||
|
app:destination="@id/sn_fragment" />
|
||||||
|
</fragment>
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/setting_fragment"
|
||||||
|
android:name="com.yuanxuan.rokid.ui.SettingFragment"
|
||||||
|
android:label="setting"
|
||||||
|
tools:layout="@layout/fragment_setting" />
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/sn_fragment"
|
||||||
|
android:name="com.yuanxuan.rokid.ui.SnFragment"
|
||||||
|
android:label="sn"
|
||||||
|
tools:layout="@layout/fragment_sn" />
|
||||||
|
|
||||||
|
</navigation>
|
||||||
Binary file not shown.
|
|
@ -3,5 +3,6 @@
|
||||||
<style name="Base.Theme.Rokid" parent="Theme.Material3.DayNight.NoActionBar">
|
<style name="Base.Theme.Rokid" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
<!-- Customize your dark theme here. -->
|
<!-- Customize your dark theme here. -->
|
||||||
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
|
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
|
||||||
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Rokid</string>
|
<string name="app_name">Rokid</string>
|
||||||
|
<string name="home_menu_setting">设置</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -14,6 +14,9 @@ okhttp = "5.3.0"
|
||||||
worker = "2.11.0"
|
worker = "2.11.0"
|
||||||
retrofit = "3.0.0"
|
retrofit = "3.0.0"
|
||||||
gson = "2.13.2"
|
gson = "2.13.2"
|
||||||
|
lottie = "6.6.6"
|
||||||
|
navigation = "2.9.6"
|
||||||
|
leanback = "1.2.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
|
@ -31,7 +34,10 @@ androidx-work-ktx = { group = "androidx.work", name = "work-runtime-ktx", versio
|
||||||
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
|
||||||
retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
|
retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
|
||||||
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||||
|
lottie = { group = "com.airbnb.android", name = "lottie", version.ref = "lottie" }
|
||||||
|
androidx-navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigation" }
|
||||||
|
androidx-navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "navigation" }
|
||||||
|
androidx-leanback = { group = "androidx.leanback", name = "leanback", version.ref = "leanback" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue