Skip to content

Commit

Permalink
Fix bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
Him188 committed Sep 22, 2024
1 parent a40a297 commit 0ac6707
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ class SelectorMediaSource(
SelectorSearchQuery(
subjectName = name,
episodeSort = query.episodeSort,
allSubjectNames = query.subjectNames,
),
mediaSourceId,
).getOrThrow().asFlow()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ import me.him188.ani.utils.xml.Xml

data class SelectorSearchQuery(
val subjectName: String,
val allSubjectNames: Set<String>,
val episodeSort: EpisodeSort,
)

fun SelectorSearchQuery.toFilterContext() = MediaListFilterContext(
subjectNames = setOf(subjectName),
subjectNames = allSubjectNames,
episodeSort = episodeSort,
)

Expand Down Expand Up @@ -117,7 +118,16 @@ abstract class SelectorMediaSourceEngine {

suspend fun searchEpisodes(
subjectDetailsPageUrl: String,
): ApiResponse<Document> = doHttpGet(subjectDetailsPageUrl)
): ApiResponse<Document?> = try {
doHttpGet(subjectDetailsPageUrl)
} catch (e: ClientRequestException) {
e.response.status.let {
if (it == HttpStatusCode.NotFound) {
return ApiResponse.success(null)
}
throw e
}
}

/**
* @return `null` if config is invalid
Expand Down Expand Up @@ -231,7 +241,7 @@ internal fun SelectorSearchConfig.createFiltersForSubject() = buildList {
}

internal fun SelectorSearchConfig.createFiltersForEpisode() = buildList {
addAll(createFiltersForSubject())
// 不使用 filterBySubjectName, 因为 web 的剧集名称通常为 "第x集", 不包含 subject
if (filterByEpisodeSort) add(MediaListFilters.ContainsEpisodeSort)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package me.him188.ani.app.data.source.media.source.web

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import io.ktor.http.URLBuilder
import kotlinx.serialization.Serializable
import me.him188.ani.app.data.source.media.source.web.format.SelectorChannelFormat
import me.him188.ani.app.data.source.media.source.web.format.SelectorChannelFormatFlattened
Expand Down Expand Up @@ -59,7 +60,24 @@ data class SelectorSearchConfig(
val matchVideo: MatchVideoConfig = MatchVideoConfig(),
) { // TODO: add Engine version capabilities
val baseUrl by lazy(LazyThreadSafetyMode.PUBLICATION) {
searchUrl.substringBeforeLast("/")
kotlin.runCatching {
URLBuilder(searchUrl).apply {
pathSegments = emptyList()
parameters.clear()
}.toString()
}.getOrElse {
val schemaIndex = searchUrl.indexOf("//")
if (schemaIndex == -1) {
searchUrl.removeSuffix("/")
} else {
val slashIndex = searchUrl.indexOf('/', startIndex = schemaIndex + 2)
if (slashIndex == -1) {
searchUrl.removeSuffix("/")
} else {
searchUrl.substring(0, slashIndex)
}
}
}
}

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import me.him188.ani.app.data.source.media.resolver.WebViewVideoExtractor
Expand All @@ -47,13 +46,13 @@ import me.him188.ani.app.ui.foundation.layout.isWidthCompact
import me.him188.ani.app.ui.foundation.layout.materialWindowMarginPadding
import me.him188.ani.app.ui.foundation.layout.rememberConnectedScrollState
import me.him188.ani.app.ui.foundation.navigation.BackHandler
import me.him188.ani.app.ui.foundation.theme.AniThemeDefaults
import me.him188.ani.app.ui.foundation.widgets.TopAppBarGoBackButton
import me.him188.ani.app.ui.settings.mediasource.rss.SaveableStorage
import me.him188.ani.app.ui.settings.mediasource.selector.edit.SelectorConfigState
import me.him188.ani.app.ui.settings.mediasource.selector.edit.SelectorConfigurationPane
import me.him188.ani.app.ui.settings.mediasource.selector.episode.SelectorEpisodePaneDefaults
import me.him188.ani.app.ui.settings.mediasource.selector.episode.SelectorEpisodePaneLayout
import me.him188.ani.app.ui.settings.mediasource.selector.episode.SelectorEpisodePaneRoutes
import me.him188.ani.app.ui.settings.mediasource.selector.episode.SelectorEpisodeState
import me.him188.ani.app.ui.settings.mediasource.selector.episode.SelectorTestAndEpisodePane
import me.him188.ani.app.ui.settings.mediasource.selector.test.SelectorTestEpisodePresentation
Expand Down Expand Up @@ -123,20 +122,21 @@ fun EditSelectorMediaSourcePage(
navigator: ThreePaneScaffoldNavigator<Nothing> = rememberListDetailPaneScaffoldNavigator(),
windowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
) {
val nestedNav = rememberNavController()
val episodePaneLayout = SelectorEpisodePaneLayout.calculate(navigator.scaffoldValue)
val testConnectedScrollState = rememberConnectedScrollState()
Scaffold(
modifier,
topBar = {
WindowDragArea {
if (episodePaneLayout.showTopBarInScaffold) {
SelectorEpisodePaneDefaults.TopAppBar(state.episodeState)
val viewingItem = state.viewingItem
if (viewingItem != null && episodePaneLayout.showTopBarInScaffold) {
SelectorEpisodePaneDefaults.TopAppBar(
state.episodeState,
windowInsets = windowInsets.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top),
)
} else {
TopAppBar(
title = {
nestedNav.navigate(SelectorEpisodePaneRoutes.EPISODE)
val viewingItem = state.viewingItem
if (viewingItem != null) {
Text(viewingItem.name)
} else {
Expand All @@ -152,6 +152,7 @@ fun EditSelectorMediaSourcePage(
}
},
windowInsets = windowInsets.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top),
colors = AniThemeDefaults.topAppBarColors(),
)
}
}
Expand Down Expand Up @@ -188,12 +189,11 @@ fun EditSelectorMediaSourcePage(
detailPane = {
AnimatedPane1 {
SelectorTestAndEpisodePane(
state,
episodePaneLayout,
Modifier.consumeWindowInsets(paddingValues),
nestedNav,
paddingValues,
testConnectedScrollState,
state = state,
layout = episodePaneLayout,
modifier = Modifier.consumeWindowInsets(paddingValues),
contentPadding = paddingValues,
testConnectedScrollState = testConnectedScrollState,
)
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ private fun SubjectChannelSelectionButtonRow(
@Composable
fun Btn(
id: SelectorFormatId, index: Int,
enabled: Boolean = true,
label: @Composable () -> Unit,
) {
SegmentedButton(
Expand All @@ -267,11 +268,15 @@ private fun SubjectChannelSelectionButtonRow(
SegmentedButtonDefaults.itemShape(index, state.allChannelFormats.size),
icon = { SegmentedButtonDefaults.Icon(state.channelFormatId == id) },
label = label,
enabled = enabled,
)
}

for ((index, selectorChannelFormat) in state.allChannelFormats.withIndex()) {
Btn(selectorChannelFormat.id, index) {
Btn(
selectorChannelFormat.id, index,
enabled = selectorChannelFormat == SelectorChannelFormatNoChannel,
) {
Text(
when (selectorChannelFormat) { // type-safe to handle all formats
SelectorChannelFormatNoChannel -> "不区分线路"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,13 @@ fun SelectorTestAndEpisodePane(
LaunchedEffect(state) {
snapshotFlow { state.viewingItem }.collect { value ->
if (value == null) {
nestedNav.navigate(SelectorEpisodePaneRoutes.TEST)
nestedNav.navigate(SelectorEpisodePaneRoutes.TEST) {
launchSingleTop = true
}
} else {
nestedNav.navigate(SelectorEpisodePaneRoutes.EPISODE)
nestedNav.navigate(SelectorEpisodePaneRoutes.EPISODE) {
launchSingleTop = true
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,30 @@

package me.him188.ani.app.ui.settings.mediasource.selector.episode

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.ArrowOutward
import androidx.compose.material3.*
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import me.him188.ani.app.ui.foundation.theme.AniThemeDefaults
import me.him188.ani.app.ui.foundation.widgets.LocalToaster
import me.him188.ani.app.ui.foundation.widgets.TopAppBarGoBackButton
import me.him188.ani.app.ui.settings.mediasource.RefreshIndicationDefaults
import me.him188.ani.app.ui.settings.mediasource.selector.edit.MatchVideoSection
Expand All @@ -41,7 +54,7 @@ object SelectorEpisodePaneDefaults {
title = {
Row(
verticalAlignment = Alignment.Companion.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(state.episodeName, style = LocalTextStyle.current)
RefreshIndicationDefaults.RefreshIconButton(
Expand All @@ -55,11 +68,22 @@ object SelectorEpisodePaneDefaults {
},
actions = {
val uriHandler = LocalUriHandler.current
IconButton({ uriHandler.openUri(state.episodeUrl) }) {
Icon(Icons.Rounded.ArrowOutward, "打开原始链接 ${state.episodeName}")
val toaster = LocalToaster.current
if (state.episodeUrl.isNotBlank() && state.episodeUrl.startsWith("http")) {
IconButton(
{
try {
uriHandler.openUri(state.episodeUrl)
} catch (e: Throwable) {
toaster.toast("无法打开链接")
}
},
) {
Icon(Icons.Rounded.ArrowOutward, "打开原始链接 ${state.episodeName}")
}
}
},
colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
colors = AniThemeDefaults.topAppBarColors(),
modifier = modifier,
windowInsets = windowInsets,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ class SelectorTestState(
if (searchKeyword.isBlank() || sort.isBlank()) {
null
} else {
SelectorSearchQuery(subjectName = searchKeyword, episodeSort = EpisodeSort(sort))
SelectorSearchQuery(
subjectName = searchKeyword,
episodeSort = EpisodeSort(sort),
allSubjectNames = setOf(searchKeyword),
)
}
}

Expand Down Expand Up @@ -126,11 +130,15 @@ class SelectorTestState(
null
} else {
try {
engine.searchEpisodes(
selectedSubject.subjectDetailsPageUrl,
Result.success(
engine.searchEpisodes(
selectedSubject.subjectDetailsPageUrl,
),
)
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
Result.failure(e)
}
}
}
Expand All @@ -155,23 +163,28 @@ class SelectorTestState(
}

else -> {
convertEpisodeResult(
subjectDetailsPageDocument,
searchConfig,
queryState,
subjectDetailsPageDocument.fold(
onSuccess = { document ->
convertEpisodeResult(document, searchConfig, queryState)
},
onFailure = {
SelectorTestEpisodeListResult.UnknownError(it)
},
)
}
}
}

private fun convertEpisodeResult(
res: ApiResponse<Document>,
res: ApiResponse<Document?>,
config: SelectorSearchConfig,
query: SelectorSearchQuery,
): SelectorTestEpisodeListResult {
return res.fold(
onSuccess = { document ->
try {
document ?: return SelectorTestEpisodeListResult.Success(null, emptyList())

val episodeList = engine.selectEpisodes(document, config)
?: return SelectorTestEpisodeListResult.InvalidConfig
SelectorTestEpisodeListResult.Success(
Expand Down

0 comments on commit 0ac6707

Please sign in to comment.