You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于NowInAndroid架构:如何从AppState向ModalBottomSheet传语音文本?

解决方案(遵循Clean Architecture与Compose规范)

1. 调整ViewModel,管理朗读文本状态

在UniversalisViewModel中添加状态流保存当前朗读文本,并提供更新方法,符合UI层状态管理的职责:

@HiltViewModel
class UniversalisViewModel @Inject constructor(
    private val savedStateHandle: SavedStateHandle,
    val userDataRepository: UserDataRepository,
    getTopicWithDate: GetUniversalisUseCase,
) : ViewModel() {

    // 保存当前需朗读的文本,对外暴露只读流
    private val _currentVoiceText = MutableStateFlow("")
    val currentVoiceText: StateFlow<String> = _currentVoiceText.asStateFlow()

    // 更新朗读文本的方法
    fun updateCurrentVoiceText(text: String) {
        _currentVoiceText.value = text
    }

    // 原有uiState等代码保持不变
}

2. 修改Universalis可组合项,同步文本到ViewModel

在Universalis可组合项中,利用LaunchedEffect监听data变化,将getAllForRead()的结果同步到ViewModel:

@Composable
fun Universalis(
    data: Universalis,
    topicId: String?,
    userData: UserDataDynamic,
    viewModel: UniversalisViewModel // 传入ViewModel
) {
    val itemId = topicId!!.toInt()
    val onTap = { point: Offset -> }
    
    if (data.liturgia!!.liturgiaTypus is Sortable) {
        data.liturgia!!.liturgiaTypus?.sort()
    }

    // 当data实例变化时,自动更新ViewModel中的朗读文本
    LaunchedEffect(data) {
        viewModel.updateCurrentVoiceText(data.getAllForRead())
    }

    // 原有UI渲染代码保持不变
    when (itemId) {
        1 -> MixtusScreen(
            data = data.liturgia!!.liturgiaTypus as LHMixtus,
            calendarTime = data.timeFK,
            userData = userData,
            onTap = onTap
        )
        2 -> OfficiumScreen(
            data = data.liturgia!!.liturgiaTypus as LHOfficium,
            calendarTime = data.timeFK,
            userData = userData,
            onTap = onTap
        )
    }
}

3. 连接AppState与ViewModel状态,控制BottomSheet

在顶层组合中,订阅ViewModel的朗读文本状态,同步到AppState,并处理顶部栏点击逻辑:

@Composable
fun UniversalisScreen(
    viewModel: UniversalisViewModel = hiltViewModel(),
    appState: AppState = rememberAppState()
) {
    // 订阅ViewModel中的朗读文本
    val currentVoiceText by viewModel.currentVoiceText.collectAsState()

    // 同步文本到AppState
    LaunchedEffect(currentVoiceText) {
        appState.textToRead = currentVoiceText
    }

    // 顶部栏按钮逻辑
    NiaTopAppBar(
        // ... 其他配置
        readerIcon = NiaIcons.Reader,
        onReaderClick = { 
            appState.showBottomSheet = true
            // 此时appState.textToRead已同步为当前页面的朗读文本
        },
    )

    // 展示ModalBottomSheet
    if (appState.showBottomSheet) {
        ModalBottomSheet(onDismissRequest = { appState.showBottomSheet = false }) {
            TextToSpeechScreen(appState.textToRead)
        }
    }

    // 渲染Universalis内容
    val uiState by viewModel.uiState.collectAsState()
    when (uiState) {
        is UniversalisUiState.UniversalisData -> {
            val uiData = uiState as UniversalisUiState.UniversalisData
            // 根据selectedTopicId筛选当前显示的Universalis实例
            val currentUniversalis = uiData.topics.firstOrNull { 
                it.id == uiData.selectedTopicId?.toInt() 
            }
            currentUniversalis?.let {
                Universalis(
                    data = it,
                    topicId = uiData.selectedTopicId,
                    userData = // 从userDataRepository获取用户数据
                    viewModel = viewModel
                )
            }
        }
        UniversalisUiState.Loading -> LoadingScreen()
        UniversalisUiState.Error -> ErrorScreen()
        UniversalisUiState.Empty -> EmptyScreen()
    }
}

规范说明

  • Clean Architecture:ViewModel作为UI层状态中枢,负责接收UI层的文本更新请求并维护状态,业务方法getAllForRead()属于领域层逻辑,仅在可组合项中调用后传递给ViewModel,符合单向数据流原则。
  • Compose规范:使用LaunchedEffect处理副作用(同步文本到ViewModel),通过StateFlow和collectAsState实现状态订阅,保持可组合项的无状态性,仅依赖参数传递数据。

内容的提问来源于stack exchange,提问作者A. Cedano

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 07:59:51