Skip to content

Commit

Permalink
New Release Card.
Browse files Browse the repository at this point in the history
  • Loading branch information
oxyroid committed Jun 9, 2024
1 parent f0a8dbf commit f675604
Show file tree
Hide file tree
Showing 14 changed files with 276 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ object Profiles {
val REPOS_PROGRAMME = Profile("repos-programme")
val REPOS_TELEVISION = Profile("repos-television")
val REPOS_MEDIA = Profile("repos-media")
val REPOS_OTHER = Profile("repos-other")

val PARSER_M3U = Profile("parser-m3u")
val PARSER_XTREAM = Profile("parser-xtream")
Expand Down
14 changes: 13 additions & 1 deletion core/src/main/java/com/m3u/core/unit/DataUnit.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ val Double.MB: DataUnit.MB get() = DataUnit.MB(this)
val Double.KB: DataUnit.KB get() = DataUnit.KB(this)

@Immutable
sealed class DataUnit {
sealed class DataUnit : Comparable<DataUnit> {
data class GB(val value: Double) : DataUnit() {
override fun toString(): String = "${value.toInt()} GB"
}
Expand All @@ -22,6 +22,18 @@ sealed class DataUnit {
override fun toString(): String = "${value.toInt()} KB"
}

val length: Double
get() = when (this) {
is GB -> 1024 * 1024 * 1024 * value
is MB -> 1024 * 1024 * value
is KB -> 1024 * value
Unspecified -> 0.0
}

override fun compareTo(other: DataUnit): Int {
return this.length.compareTo(other.length)
}

companion object {
private const val KB: Long = 1024
private const val MB = KB * 1024
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import com.m3u.data.repository.media.MediaRepositoryImpl
import com.m3u.data.repository.playlist.PlaylistRepositoryImpl
import com.m3u.data.repository.programme.ProgrammeRepositoryImpl
import com.m3u.data.repository.channel.ChannelRepositoryImpl
import com.m3u.data.repository.other.OtherRepository
import com.m3u.data.repository.other.OtherRepositoryImpl
import com.m3u.data.repository.television.TelevisionRepositoryImpl
import dagger.Binds
import dagger.Module
Expand Down Expand Up @@ -50,4 +52,10 @@ internal interface RepositoryModule {
fun bindTelevisionRepository(
repository: TelevisionRepositoryImpl
): TelevisionRepository

@Binds
@Singleton
fun bindOtherRepository(
repositoryImpl: OtherRepositoryImpl
): OtherRepository
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import javax.inject.Inject

private const val BITMAP_QUALITY = 100

class MediaRepositoryImpl @Inject constructor(
internal class MediaRepositoryImpl @Inject constructor(
@ApplicationContext private val context: Context,
delegate: Logger,
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.m3u.data.repository.other

import com.m3u.data.api.dto.github.Release

interface OtherRepository {
suspend fun getRelease(): Release?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.m3u.data.repository.other

import com.m3u.core.architecture.Publisher
import com.m3u.core.architecture.logger.Logger
import com.m3u.core.architecture.logger.Profiles
import com.m3u.core.architecture.logger.execute
import com.m3u.core.architecture.logger.install
import com.m3u.core.util.collections.indexOf
import com.m3u.data.api.GithubApi
import com.m3u.data.api.dto.github.Release
import javax.inject.Inject

internal class OtherRepositoryImpl @Inject constructor(
private val githubApi: GithubApi,
private val publisher: Publisher,
delegate: Logger
) : OtherRepository {
private val logger = delegate.install(Profiles.REPOS_OTHER)
override suspend fun getRelease(): Release? {
if (publisher.snapshot) return null
val versionName = publisher.versionName
val releases = logger
.execute { githubApi.releases("oxyroid", "M3UAndroid") }
?: emptyList()
if (releases.isEmpty()) return null
val i = releases.indexOf { it.name == versionName }
// if (i <= 0) return null
return releases.first()
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
package com.m3u.feature.favorite.components

import androidx.compose.animation.animateColor
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
Expand All @@ -25,7 +19,6 @@ import androidx.compose.material3.ListItemDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
Expand All @@ -40,6 +33,7 @@ import com.m3u.i18n.R.string
import com.m3u.material.ktx.isTelevision
import com.m3u.material.ktx.plus
import com.m3u.material.model.LocalSpacing
import com.m3u.ui.createPremiumBrush
import androidx.tv.material3.Card as TvCard
import androidx.tv.material3.CardDefaults as TvCardDefaults
import androidx.tv.material3.Glow as TvGlow
Expand Down Expand Up @@ -131,7 +125,7 @@ private fun RandomTips(
.clip(AbsoluteRoundedCornerShape(spacing.medium))
.clickable(onClick = onClick)
.background(
createPremiumBrush(
Brush.createPremiumBrush(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.tertiary
)
Expand All @@ -156,7 +150,7 @@ private fun RandomTips(
modifier = Modifier
.fillMaxWidth()
.background(
createPremiumBrush(
Brush.createPremiumBrush(
TvMaterialTheme.colorScheme.primary,
TvMaterialTheme.colorScheme.tertiary
)
Expand All @@ -175,33 +169,3 @@ private fun RandomTips(
}
}
}

@Composable
private fun createPremiumBrush(
color1: Color = MaterialTheme.colorScheme.primaryContainer,
color2: Color = MaterialTheme.colorScheme.secondaryContainer
): Brush {
val transition = rememberInfiniteTransition("premium-brush")

val leftColor by transition.animateColor(
initialValue = color1,
targetValue = color2,
animationSpec = infiniteRepeatable(
animation = tween(1600, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
),
label = "left"
)
val rightColor by transition.animateColor(
initialValue = color2,
targetValue = color1,
animationSpec = infiniteRepeatable(
animation = tween(1600, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
),
label = "right"
)
return Brush.linearGradient(
colors = listOf(leftColor, rightColor)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ import androidx.lifecycle.repeatOnLifecycle
import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.title
import com.m3u.core.wrapper.Resource
import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Playlist
import com.m3u.data.database.model.PlaylistWithCount
import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.isSeries
import com.m3u.data.service.MediaCommand
import com.m3u.feature.foryou.components.HeadlineBackground
Expand Down Expand Up @@ -81,7 +81,7 @@ fun ForyouRoute(
val isPageInfoVisible = root == Destination.Root.Foryou

val playlistCountsResource by viewModel.playlistCountsResource.collectAsStateWithLifecycle()
val recommend by viewModel.recommend.collectAsStateWithLifecycle()
val specs by viewModel.specs.collectAsStateWithLifecycle()
val episodes by viewModel.episodes.collectAsStateWithLifecycle()

val series: Channel? by viewModel.series.collectAsStateWithLifecycle()
Expand Down Expand Up @@ -112,7 +112,7 @@ fun ForyouRoute(
playlistCountsResource = playlistCountsResource,
subscribingPlaylistUrls = subscribingPlaylistUrls,
refreshingEpgUrls = refreshingEpgUrls,
recommend = recommend,
specs = specs,
rowCount = preferences.rowCount,
contentPadding = contentPadding,
navigateToPlaylist = navigateToPlaylist,
Expand Down Expand Up @@ -177,7 +177,7 @@ private fun ForyouScreen(
playlistCountsResource: Resource<List<PlaylistWithCount>>,
subscribingPlaylistUrls: List<String>,
refreshingEpgUrls: List<String>,
recommend: Recommend,
specs: List<Recommend.Spec>,
contentPadding: PaddingValues,
navigateToPlaylist: (Playlist) -> Unit,
onClickChannel: (Channel) -> Unit,
Expand All @@ -202,12 +202,13 @@ private fun ForyouScreen(
}

LaunchedEffect(headlineSpec) {
val currentHeadlineSpec = headlineSpec
val spec = headlineSpec
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
delay(400.milliseconds)
Metadata.headlineUrl = when (currentHeadlineSpec) {
is Recommend.UnseenSpec -> currentHeadlineSpec.channel.cover.orEmpty()
Metadata.headlineUrl = when (spec) {
is Recommend.UnseenSpec -> spec.channel.cover.orEmpty()
is Recommend.DiscoverSpec -> ""
is Recommend.NewRelease -> ""
else -> ""
}
}
Expand All @@ -228,7 +229,7 @@ private fun ForyouScreen(
val showPlaylist = playlistCountsResource.data.isNotEmpty()
val header = @Composable {
RecommendGallery(
recommend = recommend,
specs = specs,
navigateToPlaylist = navigateToPlaylist,
onClickChannel = onClickChannel,
onSpecChanged = { spec -> headlineSpec = spec },
Expand All @@ -243,7 +244,7 @@ private fun ForyouScreen(
refreshingEpgUrls = refreshingEpgUrls,
onClick = navigateToPlaylist,
onLongClick = { mediaSheetValue = MediaSheetValue.ForyouScreen(it) },
header = header.takeIf { recommend.isNotEmpty() },
header = header.takeIf { specs.isNotEmpty() },
contentPadding = contentPadding,
modifier = Modifier.fillMaxSize()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,20 @@ import com.m3u.core.architecture.logger.Logger
import com.m3u.core.architecture.logger.Profiles
import com.m3u.core.architecture.logger.install
import com.m3u.core.architecture.preferences.Preferences
import com.m3u.core.unit.DataUnit
import com.m3u.core.wrapper.Resource
import com.m3u.core.wrapper.asResource
import com.m3u.core.wrapper.mapResource
import com.m3u.core.wrapper.resource
import com.m3u.data.api.dto.github.Release
import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Playlist
import com.m3u.data.database.model.PlaylistWithCount
import com.m3u.data.database.model.Channel
import com.m3u.data.parser.xtream.XtreamChannelInfo
import com.m3u.data.repository.channel.ChannelRepository
import com.m3u.data.repository.other.OtherRepository
import com.m3u.data.repository.playlist.PlaylistRepository
import com.m3u.data.repository.programme.ProgrammeRepository
import com.m3u.data.repository.channel.ChannelRepository
import com.m3u.data.worker.SubscriptionWorker
import com.m3u.feature.foryou.components.recommend.Recommend
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -48,6 +51,7 @@ class ForyouViewModel @Inject constructor(
private val playlistRepository: PlaylistRepository,
channelRepository: ChannelRepository,
programmeRepository: ProgrammeRepository,
otherRepository: OtherRepository,
preferences: Preferences,
@Dispatcher(IO) ioDispatcher: CoroutineDispatcher,
workManager: WorkManager,
Expand Down Expand Up @@ -95,14 +99,39 @@ class ForyouViewModel @Inject constructor(
initialValue = Duration.INFINITE
)

internal val recommend: StateFlow<Recommend> = unseensDuration
private val newRelease: StateFlow<Release?> = flow {
emit(otherRepository.getRelease())
}
.stateIn(
scope = viewModelScope,
initialValue = null,
started = SharingStarted.Lazily
)
internal val specs: StateFlow<List<Recommend.Spec>> = unseensDuration
.flatMapLatest { channelRepository.observeAllUnseenFavourites(it) }
.map { prev -> Recommend(prev.map { Recommend.UnseenSpec(it) }) }
.let { flow ->
combine(flow, newRelease) { channels, nr ->
buildList<Recommend.Spec> {
if (nr != null) {
val min = DataUnit.of(nr.assets.minOfOrNull { it.size }?.toLong() ?: 0L)
val max = DataUnit.of(nr.assets.maxOfOrNull { it.size }?.toLong() ?: 0L)
this += Recommend.NewRelease(
name = nr.name,
description = nr.body,
downloadCount = nr.assets.sumOf { it.downloadCount },
size = min..max,
url = nr.htmlUrl
)
}
this += channels.map { channel -> Recommend.UnseenSpec(channel) }
}
}
}
.flowOn(ioDispatcher)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000L),
initialValue = Recommend()
initialValue = emptyList()
)

internal fun onUnsubscribePlaylist(url: String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.m3u.feature.foryou.components.recommend

import androidx.compose.runtime.Immutable
import com.m3u.core.unit.DataUnit
import com.m3u.data.database.model.Playlist
import com.m3u.data.database.model.Channel

Expand All @@ -25,4 +26,13 @@ internal class Recommend(
data class UnseenSpec(
val channel: Channel
) : Spec

@Immutable
data class NewRelease(
val name: String,
val description: String,
val downloadCount: Int,
val size: ClosedRange<DataUnit>,
val url: String,
): Spec
}
Loading

0 comments on commit f675604

Please sign in to comment.