Skip to content

Commit

Permalink
Extract SelectorTestState to separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
Him188 committed Sep 22, 2024
1 parent a3a1a6f commit 7a33999
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 231 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,250 +23,19 @@ import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CoroutineScope
import me.him188.ani.app.data.models.ApiResponse
import me.him188.ani.app.data.models.fold
import me.him188.ani.app.data.source.media.source.web.SelectorMediaSourceEngine
import me.him188.ani.app.data.source.media.source.web.SelectorSearchConfig
import me.him188.ani.app.data.source.media.source.web.SelectorSearchQuery
import me.him188.ani.app.ui.foundation.interaction.nestedScrollWorkaround
import me.him188.ani.app.ui.foundation.layout.cardVerticalPadding
import me.him188.ani.app.ui.foundation.layout.connectedScroll
import me.him188.ani.app.ui.foundation.layout.rememberConnectedScrollState
import me.him188.ani.app.ui.foundation.theme.AniThemeDefaults
import me.him188.ani.app.ui.foundation.widgets.FastLinearProgressIndicator
import me.him188.ani.app.ui.settings.mediasource.AbstractMediaSourceTestState
import me.him188.ani.app.ui.settings.mediasource.BackgroundSearcher
import me.him188.ani.app.ui.settings.mediasource.EditMediaSourceTestDataCardDefaults
import me.him188.ani.app.ui.settings.mediasource.RefreshIndicatedHeadlineRow
import me.him188.ani.app.ui.settings.mediasource.selector.edit.SelectorConfigurationDefaults
import me.him188.ani.datasources.api.EpisodeSort
import me.him188.ani.utils.xml.Document
import kotlin.coroutines.cancellation.CancellationException

@Stable
class SelectorTestState(
searchConfigState: State<SelectorSearchConfig?>,
private val engine: SelectorMediaSourceEngine,
backgroundScope: CoroutineScope,
) : AbstractMediaSourceTestState() {
// null for invalid config
private val queryState = derivedStateOf {
val searchKeyword = searchKeyword.ifEmpty { searchKeywordPlaceholder }
val sort = sort
if (searchKeyword.isBlank() || sort.isBlank()) {
null
} else {
SelectorSearchQuery(subjectName = searchKeyword, episodeSort = EpisodeSort(sort))
}
}

var selectedSubjectIndex by mutableIntStateOf(-1)
val selectedSubjectState = derivedStateOf {
val success = subjectSearchSelectResult as? SelectorTestSearchSubjectResult.Success
?: return@derivedStateOf null
success.subjects.getOrNull(selectedSubjectIndex)
}
val selectedSubject by selectedSubjectState
private val searchUrl by derivedStateOf {
searchConfigState.value?.searchUrl
}

/**
* 用于查询条目列表, 每当编辑请求和 `searchUrl`, 会重新搜索, 但不会筛选.
* 筛选在 [subjectSearchSelectResult].
*/
val subjectSearcher = BackgroundSearcher(
backgroundScope,
derivedStateOf {
val url = searchUrl
url to searchKeyword
},
search = { (url, searchKeyword) ->
// 不清除 selectedSubjectIndex

launchRequestInBackground {
if (url.isNullOrBlank() || searchKeyword.isBlank()) {
null
} else {
try {
val res = engine.searchSubjects(
url,
searchKeyword,
)
Result.success(res)
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
Result.failure(e)
}
}
}
},
)

val subjectSearchSelectResult by derivedStateOf {
val res = subjectSearcher.searchResult
val config = searchConfigState.value
val query = queryState.value
when {
res == null -> {
null
}

config == null || query == null -> {
SelectorTestSearchSubjectResult.InvalidConfig
}

else -> {
res.fold(
onSuccess = {
selectSubjectResult(it, config, query)
},
onFailure = {
SelectorTestSearchSubjectResult.UnknownError(it)
},
)
}
}
}

/**
* 用于查询条目的剧集列表, 每当选择新的条目时, 会重新搜索. 但不会筛选. 筛选在 [episodeListSearchSelectResult].
*/
val episodeListSearcher = BackgroundSearcher(
backgroundScope,
selectedSubjectState,
search = { selectedSubject ->
launchRequestInBackground {
if (selectedSubject == null) {
null
} else {
try {
engine.searchEpisodes(
selectedSubject.subjectDetailsPageUrl,
)
} catch (e: CancellationException) {
throw e
}
}
}
},
)

/**
* 经过筛选的条目的剧集列表
*/
val episodeListSearchSelectResult by derivedStateOf {
val subjectDetailsPageDocument = episodeListSearcher.searchResult
val searchConfig = searchConfigState.value
val queryState = queryState.value

when {
queryState == null || searchConfig == null -> {
SelectorTestEpisodeListResult.InvalidConfig
}

subjectDetailsPageDocument == null -> {
SelectorTestEpisodeListResult.Success(null, emptyList())
}

else -> {
convertEpisodeResult(
subjectDetailsPageDocument,
searchConfig,
queryState,
)
}
}
}

// lateinit var episodeNavController: NavHostController
var viewingItem by mutableStateOf<SelectorTestEpisodePresentation?>(null)
private set

fun viewEpisode(
episode: SelectorTestEpisodePresentation,
) {
this.viewingItem = episode
// episodeNavController.navigate("details")
}

fun stopViewing() {
this.viewingItem = null
// episodeNavController.navigate("list")
}

private fun convertEpisodeResult(
res: ApiResponse<Document>,
config: SelectorSearchConfig,
query: SelectorSearchQuery,
): SelectorTestEpisodeListResult {
return res.fold(
onSuccess = { document ->
try {
val episodeList = engine.selectEpisodes(document, config)
?: return SelectorTestEpisodeListResult.InvalidConfig
SelectorTestEpisodeListResult.Success(
episodeList.channels,
episodeList.episodes.map {
SelectorTestEpisodePresentation.compute(it, query, document, config)
},
)
} catch (e: Throwable) {
SelectorTestEpisodeListResult.UnknownError(e)
}
},
onKnownFailure = { reason ->
SelectorTestEpisodeListResult.ApiError(reason)
},
)
}

private fun selectSubjectResult(
res: ApiResponse<SelectorMediaSourceEngine.SearchSubjectResult>,
searchConfig: SelectorSearchConfig,
query: SelectorSearchQuery,
): SelectorTestSearchSubjectResult {
return res.fold(
onSuccess = { data ->
val document = data.document

val originalList = if (document == null) {
emptyList()
} else {
engine.selectSubjects(document, searchConfig).let {
if (it == null) {
return SelectorTestSearchSubjectResult.InvalidConfig
}
it
}
}

SelectorTestSearchSubjectResult.Success(
data.url.toString(),
originalList.map {
SelectorTestSubjectPresentation.compute(it, query, document, searchConfig)
},
)
},
onKnownFailure = { reason ->
SelectorTestSearchSubjectResult.ApiError(reason)
},
)
}
}

/**
* 测试数据源. 编辑
Expand Down
Loading

0 comments on commit 7a33999

Please sign in to comment.