fix: xtream cannot be parse.

This commit is contained in:
oxy
2024-03-15 02:40:59 +08:00
parent 09147be2f6
commit e146d6b9d0
9 changed files with 122 additions and 102 deletions

View File

@ -87,65 +87,63 @@ data class XtreamLive(
@Serializable @Serializable
data class XtreamVod( data class XtreamVod(
@SerialName("added") @SerialName("added")
val added: String?, val added: String? = null,
@SerialName("category_id") @SerialName("category_id")
val categoryId: Int?, val categoryId: Int? = null,
@SerialName("container_extension") @SerialName("container_extension")
val containerExtension: String?, val containerExtension: String? = null,
@SerialName("custom_sid") @SerialName("custom_sid")
val customSid: String?, val customSid: String? = null,
@SerialName("direct_source") @SerialName("direct_source")
val directSource: String?, val directSource: String? = null,
@SerialName("name") @SerialName("name")
val name: String?, val name: String? = null,
@SerialName("num") @SerialName("num")
val num: Int?, val num: String? = null,
@SerialName("rating") @SerialName("rating")
val rating: String?, val rating: String? = null,
@SerialName("rating_5based") @SerialName("rating_5based")
val rating5based: Double?, val rating5based: String? = null,
@SerialName("stream_icon") @SerialName("stream_icon")
val streamIcon: String?, val streamIcon: String? = null,
@SerialName("stream_id") @SerialName("stream_id")
val streamId: Int?, val streamId: Int? = null,
@SerialName("stream_type") @SerialName("stream_type")
val streamType: String? val streamType: String? = null
) )
@Serializable @Serializable
data class XtreamSerial( data class XtreamSerial(
@SerialName("backdrop_path")
val backdropPath: List<String>,
@SerialName("cast") @SerialName("cast")
val cast: String?, val cast: String? = null,
@SerialName("category_id") @SerialName("category_id")
val categoryId: Int?, val categoryId: Int? = null,
@SerialName("cover") @SerialName("cover")
val cover: String?, val cover: String? = null,
@SerialName("director") @SerialName("director")
val director: String?, val director: String? = null,
@SerialName("episode_run_time") @SerialName("episode_run_time")
val episodeRunTime: String?, val episodeRunTime: String? = null,
@SerialName("genre") @SerialName("genre")
val genre: String?, val genre: String? = null,
@SerialName("last_modified") @SerialName("last_modified")
val lastModified: String?, val lastModified: String? = null,
@SerialName("name") @SerialName("name")
val name: String?, val name: String? = null,
@SerialName("num") @SerialName("num")
val num: Int?, val num: String? = null,
@SerialName("plot") @SerialName("plot")
val plot: String?, val plot: String? = null,
@SerialName("rating") @SerialName("rating")
val rating: String?, val rating: String? = null,
@SerialName("rating_5based") @SerialName("rating_5based")
val rating5based: Int?, val rating5based: String? = null,
@SerialName("releaseDate") @SerialName("releaseDate")
val releaseDate: String?, val releaseDate: String? = null,
@SerialName("series_id") @SerialName("series_id")
val seriesId: Int?, val seriesId: Int? = null,
@SerialName("youtube_trailer") @SerialName("youtube_trailer")
val youtubeTrailer: String? val youtubeTrailer: String? = null
) )
fun XtreamLive.toStream( fun XtreamLive.toStream(

View File

@ -6,11 +6,11 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class XtreamStreamInfo( data class XtreamStreamInfo(
@SerialName("episodes") @SerialName("episodes")
val episodes: Map<Int, List<Episode>> = emptyMap(), val episodes: Map<String, List<Episode>> = emptyMap(),
@SerialName("info") @SerialName("info")
val info: Info?, val info: Info?,
@SerialName("seasons") // @SerialName("seasons")
val seasons: List<String> = emptyList() // val seasons: List<String?> = emptyList()
) { ) {
@Serializable @Serializable
data class Episode( data class Episode(
@ -23,13 +23,13 @@ data class XtreamStreamInfo(
@SerialName("direct_source") @SerialName("direct_source")
val directSource: String?, val directSource: String?,
@SerialName("episode_num") @SerialName("episode_num")
val episodeNum: Int?, val episodeNum: String?,
@SerialName("id") @SerialName("id")
val id: String?, val id: String?,
@SerialName("info") @SerialName("info")
val info: Info?, val info: Info?,
@SerialName("season") @SerialName("season")
val season: Int?, val season: String?,
@SerialName("title") @SerialName("title")
val title: String? val title: String?
) { ) {
@ -38,11 +38,11 @@ data class XtreamStreamInfo(
@SerialName("audio") @SerialName("audio")
val audio: Audio?, val audio: Audio?,
@SerialName("bitrate") @SerialName("bitrate")
val bitrate: Int?, val bitrate: String?,
@SerialName("duration") @SerialName("duration")
val duration: String?, val duration: String?,
@SerialName("duration_secs") @SerialName("duration_secs")
val durationSecs: Int?, val durationSecs: String?,
@SerialName("video") @SerialName("video")
val video: Video? val video: Video?
) { ) {
@ -51,9 +51,9 @@ data class XtreamStreamInfo(
@SerialName("avg_frame_rate") @SerialName("avg_frame_rate")
val avgFrameRate: String?, val avgFrameRate: String?,
@SerialName("bits_per_sample") @SerialName("bits_per_sample")
val bitsPerSample: Int?, val bitsPerSample: String?,
@SerialName("channels") @SerialName("channels")
val channels: Int?, val channels: String?,
@SerialName("codec_long_name") @SerialName("codec_long_name")
val codecLongName: String?, val codecLongName: String?,
@SerialName("codec_name") @SerialName("codec_name")
@ -71,7 +71,7 @@ data class XtreamStreamInfo(
@SerialName("dmix_mode") @SerialName("dmix_mode")
val dmixMode: String?, val dmixMode: String?,
@SerialName("index") @SerialName("index")
val index: Int?, val index: String?,
@SerialName("loro_cmixlev") @SerialName("loro_cmixlev")
val loroCmixlev: String?, val loroCmixlev: String?,
@SerialName("loro_surmixlev") @SerialName("loro_surmixlev")
@ -87,7 +87,7 @@ data class XtreamStreamInfo(
@SerialName("sample_rate") @SerialName("sample_rate")
val sampleRate: String?, val sampleRate: String?,
@SerialName("start_pts") @SerialName("start_pts")
val startPts: Int?, val startPts: String?,
@SerialName("start_time") @SerialName("start_time")
val startTime: String?, val startTime: String?,
@SerialName("tags") @SerialName("tags")
@ -117,9 +117,9 @@ data class XtreamStreamInfo(
@SerialName("codec_type") @SerialName("codec_type")
val codecType: String?, val codecType: String?,
@SerialName("coded_height") @SerialName("coded_height")
val codedHeight: Int?, val codedHeight: String?,
@SerialName("coded_width") @SerialName("coded_width")
val codedWidth: Int?, val codedWidth: String?,
@SerialName("display_aspect_ratio") @SerialName("display_aspect_ratio")
val displayAspectRatio: String?, val displayAspectRatio: String?,
@SerialName("disposition") @SerialName("disposition")
@ -127,15 +127,15 @@ data class XtreamStreamInfo(
@SerialName("field_order") @SerialName("field_order")
val fieldOrder: String?, val fieldOrder: String?,
@SerialName("has_b_frames") @SerialName("has_b_frames")
val hasBFrames: Int?, val hasBFrames: String?,
@SerialName("height") @SerialName("height")
val height: Int?, val height: String?,
@SerialName("index") @SerialName("index")
val index: Int?, val index: String?,
@SerialName("is_avc") @SerialName("is_avc")
val isAvc: Boolean = false, val isAvc: Boolean = false,
@SerialName("level") @SerialName("level")
val level: Int?, val level: String?,
@SerialName("nal_length_size") @SerialName("nal_length_size")
val nalLengthSize: String?, val nalLengthSize: String?,
@SerialName("pix_fmt") @SerialName("pix_fmt")
@ -145,11 +145,11 @@ data class XtreamStreamInfo(
@SerialName("r_frame_rate") @SerialName("r_frame_rate")
val rFrameRate: String?, val rFrameRate: String?,
@SerialName("refs") @SerialName("refs")
val refs: Int?, val refs: String?,
@SerialName("sample_aspect_ratio") @SerialName("sample_aspect_ratio")
val sampleAspectRatio: String?, val sampleAspectRatio: String?,
@SerialName("start_pts") @SerialName("start_pts")
val startPts: Int?, val startPts: String?,
@SerialName("start_time") @SerialName("start_time")
val startTime: String?, val startTime: String?,
@SerialName("tags") @SerialName("tags")
@ -157,35 +157,35 @@ data class XtreamStreamInfo(
@SerialName("time_base") @SerialName("time_base")
val timeBase: String?, val timeBase: String?,
@SerialName("width") @SerialName("width")
val width: Int? val width: String?
) )
@Serializable @Serializable
data class Disposition( data class Disposition(
@SerialName("attached_pic") @SerialName("attached_pic")
val attachedPic: Int?, val attachedPic: String?,
@SerialName("clean_effects") @SerialName("clean_effects")
val cleanEffects: Int?, val cleanEffects: String?,
@SerialName("comment") @SerialName("comment")
val comment: Int?, val comment: String?,
@SerialName("default") @SerialName("default")
val default: Int?, val default: String?,
@SerialName("dub") @SerialName("dub")
val dub: Int?, val dub: String?,
@SerialName("forced") @SerialName("forced")
val forced: Int?, val forced: String?,
@SerialName("hearing_impaired") @SerialName("hearing_impaired")
val hearingImpaired: Int?, val hearingImpaired: String?,
@SerialName("karaoke") @SerialName("karaoke")
val karaoke: Int?, val karaoke: String?,
@SerialName("lyrics") @SerialName("lyrics")
val lyrics: Int?, val lyrics: String?,
@SerialName("original") @SerialName("original")
val original: Int?, val original: String?,
@SerialName("timed_thumbnails") @SerialName("timed_thumbnails")
val timedThumbnails: Int?, val timedThumbnails: String?,
@SerialName("visual_impaired") @SerialName("visual_impaired")
val visualImpaired: Int? val visualImpaired: String?
) )
} }
} }
@ -215,7 +215,7 @@ data class XtreamStreamInfo(
@SerialName("rating") @SerialName("rating")
val rating: String?, val rating: String?,
@SerialName("rating_5based") @SerialName("rating_5based")
val rating5based: Int?, val rating5based: String?,
@SerialName("releaseDate") @SerialName("releaseDate")
val releaseDate: String?, val releaseDate: String?,
@SerialName("youtube_trailer") @SerialName("youtube_trailer")

View File

@ -13,6 +13,9 @@ import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface StreamDao { interface StreamDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrReplace(stream: Stream)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrReplaceAll(vararg streams: Stream) suspend fun insertOrReplaceAll(vararg streams: Stream)

View File

@ -32,6 +32,7 @@ internal class XtreamParserImpl @Inject constructor(
private val json = Json { private val json = Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true
explicitNulls = false explicitNulls = false
isLenient = true
} }
override suspend fun execute( override suspend fun execute(
@ -87,19 +88,25 @@ internal class XtreamParserImpl @Inject constructor(
val port = info.serverInfo.port?.toIntOrNull() val port = info.serverInfo.port?.toIntOrNull()
val httpsPort = info.serverInfo.httpsPort?.toIntOrNull() val httpsPort = info.serverInfo.httpsPort?.toIntOrNull()
val lives: List<XtreamLive> = if (requiredLives) newCall(liveStreamsUrl) ?: emptyList() else emptyList() val lives: List<XtreamLive> =
if (requiredLives) newCall(liveStreamsUrl) ?: emptyList() else emptyList()
currentCount += lives.size currentCount += lives.size
callback(currentCount, -1) callback(currentCount, -1)
val vods: List<XtreamVod> = if (requiredVods) newCall(vodStreamsUrl) ?: emptyList() else emptyList() val vods: List<XtreamVod> =
if (requiredVods) newCall(vodStreamsUrl) ?: emptyList() else emptyList()
currentCount += vods.size currentCount += vods.size
callback(currentCount, -1) callback(currentCount, -1)
val series: List<XtreamSerial> = if (requiredSeries) newCall(seriesStreamsUrl) ?: emptyList() else emptyList() val series: List<XtreamSerial> =
if (requiredSeries) newCall(seriesStreamsUrl) ?: emptyList() else emptyList()
currentCount += series.size currentCount += series.size
callback(currentCount, -1) callback(currentCount, -1)
val liveCategories: List<XtreamCategory> = if (requiredLives) newCall(liveCategoriesUrl) ?: emptyList() else emptyList() val liveCategories: List<XtreamCategory> =
val vodCategories: List<XtreamCategory> = if (requiredVods) newCall(vodCategoriesUrl) ?: emptyList() else emptyList() if (requiredLives) newCall(liveCategoriesUrl) ?: emptyList() else emptyList()
val serialCategories: List<XtreamCategory> = if (requiredSeries) newCall(serialCategoriesUrl) ?: emptyList() else emptyList() val vodCategories: List<XtreamCategory> =
if (requiredVods) newCall(vodCategoriesUrl) ?: emptyList() else emptyList()
val serialCategories: List<XtreamCategory> =
if (requiredSeries) newCall(serialCategoriesUrl) ?: emptyList() else emptyList()
return XtreamOutput( return XtreamOutput(
lives = lives, lives = lives,

View File

@ -169,7 +169,7 @@ class PlaylistRepositoryImpl @Inject constructor(
source = DataSource.Xtream source = DataSource.Xtream
) )
playlistDao.insertOrReplace(playlist) playlistDao.insertOrReplace(playlist)
val streams = lives.map { current -> lives.forEach { current ->
current.toStream( current.toStream(
basicUrl = basicUrl, basicUrl = basicUrl,
username = username, username = username,
@ -177,17 +177,13 @@ class PlaylistRepositoryImpl @Inject constructor(
playlistUrl = playlist.url, playlistUrl = playlist.url,
category = liveCategories.find { it.categoryId == current.categoryId }?.categoryName.orEmpty(), category = liveCategories.find { it.categoryId == current.categoryId }?.categoryName.orEmpty(),
containerExtension = allowedOutputFormats.first() containerExtension = allowedOutputFormats.first()
).also { ).also { stream ->
currentCount += 1 currentCount += 1
callback(currentCount, total) callback(currentCount, total)
streamDao.insertOrReplace(stream)
} }
} }
streamDao.compareAndUpdate( logger.log("xtream: lives +[${lives.size}]")
strategy = pref.playlistStrategy,
url = playlist.url,
update = streams
)
logger.log("xtream: lives +[${streams.size}]")
} }
if (requiredVods) { if (requiredVods) {
val playlist = Playlist( val playlist = Playlist(
@ -200,23 +196,19 @@ class PlaylistRepositoryImpl @Inject constructor(
source = DataSource.Xtream source = DataSource.Xtream
) )
playlistDao.insertOrReplace(playlist) playlistDao.insertOrReplace(playlist)
val streams = vods.map { current -> vods.forEach { current ->
current.toStream( current.toStream(
basicUrl = basicUrl, basicUrl = basicUrl,
username = username, username = username,
password = password, password = password,
playlistUrl = playlist.url, playlistUrl = playlist.url,
category = vodCategories.find { it.categoryId == current.categoryId }?.categoryName.orEmpty() category = vodCategories.find { it.categoryId == current.categoryId }?.categoryName.orEmpty()
).also { ).also { stream ->
currentCount += 1 currentCount += 1
callback(currentCount, total) callback(currentCount, total)
streamDao.insertOrReplace(stream)
} }
} }
streamDao.compareAndUpdate(
strategy = pref.playlistStrategy,
url = playlist.url,
update = streams
)
logger.log("xtream: vods +[${vods.size}]") logger.log("xtream: vods +[${vods.size}]")
} }
@ -231,12 +223,12 @@ class PlaylistRepositoryImpl @Inject constructor(
source = DataSource.Xtream source = DataSource.Xtream
) )
playlistDao.insertOrReplace(playlist) playlistDao.insertOrReplace(playlist)
val streams = series.flatMap { current -> series.forEach { current ->
ensureActive() ensureActive()
val seriesInfo = xtreamParser.getSeriesInfo( val seriesInfo = xtreamParser.getSeriesInfo(
input = input.copy(type = DataSource.Xtream.TYPE_SERIES), input = input.copy(type = DataSource.Xtream.TYPE_SERIES),
seriesId = current.seriesId ?: return@flatMap emptyList() seriesId = current.seriesId ?: return@forEach
) ?: return@flatMap emptyList() ) ?: return@forEach
seriesInfo.episodes.flatMap { (_, episodes) -> seriesInfo.episodes.flatMap { (_, episodes) ->
episodes.map { episode -> episodes.map { episode ->
Stream( Stream(
@ -245,19 +237,15 @@ class PlaylistRepositoryImpl @Inject constructor(
title = current.name.orEmpty() + " " + episode.title.orEmpty(), title = current.name.orEmpty() + " " + episode.title.orEmpty(),
cover = current.cover, cover = current.cover,
playlistUrl = playlist.url, playlistUrl = playlist.url,
) ).also { stream ->
currentCount += 1
callback(currentCount, total)
streamDao.insertOrReplace(stream)
}
} }
}.also {
currentCount += 1
callback(currentCount, total)
} }
} }
streamDao.compareAndUpdate( logger.log("xtream: series +[${series.size}]")
strategy = pref.playlistStrategy,
url = playlist.url,
update = streams
)
logger.log("xtream: series +[${streams.size}]")
} }
} }

View File

@ -27,6 +27,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.InternalComposeApi import androidx.compose.runtime.InternalComposeApi
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -45,6 +46,7 @@ import com.google.accompanist.permissions.shouldShowRationale
import com.m3u.core.architecture.pref.LocalPref import com.m3u.core.architecture.pref.LocalPref
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
import com.m3u.core.wrapper.Event import com.m3u.core.wrapper.Event
import com.m3u.data.database.model.DataSource
import com.m3u.data.database.model.Stream import com.m3u.data.database.model.Stream
import com.m3u.features.playlist.internal.PlaylistScreenImpl import com.m3u.features.playlist.internal.PlaylistScreenImpl
import com.m3u.features.playlist.internal.TvPlaylistScreenImpl import com.m3u.features.playlist.internal.TvPlaylistScreenImpl
@ -87,6 +89,14 @@ internal fun PlaylistRoute(
val pinnedCategories by viewModel.pinnedCategories.collectAsStateWithLifecycle() val pinnedCategories by viewModel.pinnedCategories.collectAsStateWithLifecycle()
val refreshing by viewModel.subscribingOrRefreshing.collectAsStateWithLifecycle() val refreshing by viewModel.subscribingOrRefreshing.collectAsStateWithLifecycle()
val singleLineTitle by remember {
derivedStateOf {
when (playlist?.type) {
DataSource.Xtream.TYPE_SERIES -> false
else -> true
}
}
}
val sorts = viewModel.sorts val sorts = viewModel.sorts
val sort by viewModel.sort.collectAsStateWithLifecycle() val sort by viewModel.sort.collectAsStateWithLifecycle()
@ -179,6 +189,7 @@ internal fun PlaylistRoute(
viewModel.onEvent(PlaylistEvent.SavePicture(it)) viewModel.onEvent(PlaylistEvent.SavePicture(it))
}, },
createShortcut = { viewModel.onEvent(PlaylistEvent.CreateShortcut(context, it)) }, createShortcut = { viewModel.onEvent(PlaylistEvent.CreateShortcut(context, it)) },
singleLineTitle = singleLineTitle,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.thenIf(!tv && pref.godMode) { .thenIf(!tv && pref.godMode) {
@ -219,6 +230,7 @@ private fun PlaylistScreen(
savePicture: (streamId: Int) -> Unit, savePicture: (streamId: Int) -> Unit,
createShortcut: (streamId: Int) -> Unit, createShortcut: (streamId: Int) -> Unit,
contentPadding: PaddingValues, contentPadding: PaddingValues,
singleLineTitle: Boolean,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val helper = LocalHelper.current val helper = LocalHelper.current
@ -270,6 +282,7 @@ private fun PlaylistScreen(
hide = hide, hide = hide,
onSavePicture = savePicture, onSavePicture = savePicture,
createShortcut = createShortcut, createShortcut = createShortcut,
singleLineTitle = singleLineTitle,
modifier = modifier modifier = modifier
) )
} else { } else {

View File

@ -25,6 +25,7 @@ internal fun StreamGallery(
streams: ImmutableList<Stream>, streams: ImmutableList<Stream>,
zapping: Stream?, zapping: Stream?,
recently: Boolean, recently: Boolean,
singleLineTitle: Boolean,
onClick: (Stream) -> Unit, onClick: (Stream) -> Unit,
onMenu: (Stream) -> Unit, onMenu: (Stream) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -41,7 +42,8 @@ internal fun StreamGallery(
onClick = onClick, onClick = onClick,
onMenu = onMenu, onMenu = onMenu,
modifier = modifier, modifier = modifier,
contentPadding = contentPadding contentPadding = contentPadding,
singleLineTitle = singleLineTitle
) )
} }
@ -70,6 +72,7 @@ private fun StreamGalleryImpl(
streams: ImmutableList<Stream>, streams: ImmutableList<Stream>,
zapping: Stream?, zapping: Stream?,
recently: Boolean, recently: Boolean,
singleLineTitle: Boolean,
onClick: (Stream) -> Unit, onClick: (Stream) -> Unit,
onMenu: (Stream) -> Unit, onMenu: (Stream) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -97,6 +100,7 @@ private fun StreamGalleryImpl(
onClick = { onClick(stream) }, onClick = { onClick(stream) },
onLongClick = { onMenu(stream) }, onLongClick = { onMenu(stream) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
singleLineTitle = singleLineTitle
) )
} }
} }

View File

@ -55,7 +55,8 @@ internal fun StreamItem(
zapping: Boolean, zapping: Boolean,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
singleLineTitle: Boolean = true
) { ) {
when (currentUiMode()) { when (currentUiMode()) {
UiMode.Default -> { UiMode.Default -> {
@ -65,7 +66,8 @@ internal fun StreamItem(
zapping = zapping, zapping = zapping,
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
modifier = modifier modifier = modifier,
singleLineTitle = singleLineTitle
) )
} }
@ -76,7 +78,8 @@ internal fun StreamItem(
zapping = zapping, zapping = zapping,
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
modifier = modifier modifier = modifier,
singleLineTitle = singleLineTitle
) )
} }
@ -92,6 +95,7 @@ private fun StreamItemImpl(
onLongClick: () -> Unit, onLongClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
zapping: Boolean = false, zapping: Boolean = false,
singleLineTitle: Boolean = true
) { ) {
val context = LocalContext.current val context = LocalContext.current
val spacing = LocalSpacing.current val spacing = LocalSpacing.current
@ -153,7 +157,7 @@ private fun StreamItemImpl(
text = stream.title.trim(), text = stream.title.trim(),
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = if (singleLineTitle) 1 else Int.MAX_VALUE,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
) )
if (recently) { if (recently) {
@ -218,6 +222,7 @@ private fun CompactStreamItem(
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
singleLineTitle: Boolean = true
) { ) {
val spacing = LocalSpacing.current val spacing = LocalSpacing.current
val favourite = stream.favourite val favourite = stream.favourite
@ -242,7 +247,7 @@ private fun CompactStreamItem(
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
fontSize = MaterialTheme.typography.titleSmall.fontSize, fontSize = MaterialTheme.typography.titleSmall.fontSize,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = if (singleLineTitle) 1 else Int.MAX_VALUE,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
}, },

View File

@ -95,6 +95,7 @@ internal fun PlaylistScreenImpl(
onSavePicture: (streamId: Int) -> Unit, onSavePicture: (streamId: Int) -> Unit,
createShortcut: (streamId: Int) -> Unit, createShortcut: (streamId: Int) -> Unit,
isAtTopState: MutableState<Boolean>, isAtTopState: MutableState<Boolean>,
singleLineTitle: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues() contentPadding: PaddingValues = PaddingValues()
) { ) {
@ -219,6 +220,7 @@ internal fun PlaylistScreenImpl(
streams = categories[currentPage].streams, streams = categories[currentPage].streams,
zapping = zapping, zapping = zapping,
recently = sort == Sort.RECENTLY, recently = sort == Sort.RECENTLY,
singleLineTitle = singleLineTitle,
onClick = onStream, onClick = onStream,
contentPadding = inner, contentPadding = inner,
onMenu = { dialogStatus = DialogStatus.Selections(it) }, onMenu = { dialogStatus = DialogStatus.Selections(it) },