Skip to content

Commit f675604

Browse files
author
oxy
committed
New Release Card.
1 parent f0a8dbf commit f675604

File tree

14 files changed

+276
-79
lines changed

14 files changed

+276
-79
lines changed

core/src/main/java/com/m3u/core/architecture/logger/Profiles.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ object Profiles {
1515
val REPOS_PROGRAMME = Profile("repos-programme")
1616
val REPOS_TELEVISION = Profile("repos-television")
1717
val REPOS_MEDIA = Profile("repos-media")
18+
val REPOS_OTHER = Profile("repos-other")
1819

1920
val PARSER_M3U = Profile("parser-m3u")
2021
val PARSER_XTREAM = Profile("parser-xtream")

core/src/main/java/com/m3u/core/unit/DataUnit.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ val Double.MB: DataUnit.MB get() = DataUnit.MB(this)
99
val Double.KB: DataUnit.KB get() = DataUnit.KB(this)
1010

1111
@Immutable
12-
sealed class DataUnit {
12+
sealed class DataUnit : Comparable<DataUnit> {
1313
data class GB(val value: Double) : DataUnit() {
1414
override fun toString(): String = "${value.toInt()} GB"
1515
}
@@ -22,6 +22,18 @@ sealed class DataUnit {
2222
override fun toString(): String = "${value.toInt()} KB"
2323
}
2424

25+
val length: Double
26+
get() = when (this) {
27+
is GB -> 1024 * 1024 * 1024 * value
28+
is MB -> 1024 * 1024 * value
29+
is KB -> 1024 * value
30+
Unspecified -> 0.0
31+
}
32+
33+
override fun compareTo(other: DataUnit): Int {
34+
return this.length.compareTo(other.length)
35+
}
36+
2537
companion object {
2638
private const val KB: Long = 1024
2739
private const val MB = KB * 1024

data/src/main/java/com/m3u/data/repository/RepositoryModule.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import com.m3u.data.repository.media.MediaRepositoryImpl
1111
import com.m3u.data.repository.playlist.PlaylistRepositoryImpl
1212
import com.m3u.data.repository.programme.ProgrammeRepositoryImpl
1313
import com.m3u.data.repository.channel.ChannelRepositoryImpl
14+
import com.m3u.data.repository.other.OtherRepository
15+
import com.m3u.data.repository.other.OtherRepositoryImpl
1416
import com.m3u.data.repository.television.TelevisionRepositoryImpl
1517
import dagger.Binds
1618
import dagger.Module
@@ -50,4 +52,10 @@ internal interface RepositoryModule {
5052
fun bindTelevisionRepository(
5153
repository: TelevisionRepositoryImpl
5254
): TelevisionRepository
55+
56+
@Binds
57+
@Singleton
58+
fun bindOtherRepository(
59+
repositoryImpl: OtherRepositoryImpl
60+
): OtherRepository
5361
}

data/src/main/java/com/m3u/data/repository/media/MediaRepositoryImpl.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import javax.inject.Inject
3232

3333
private const val BITMAP_QUALITY = 100
3434

35-
class MediaRepositoryImpl @Inject constructor(
35+
internal class MediaRepositoryImpl @Inject constructor(
3636
@ApplicationContext private val context: Context,
3737
delegate: Logger,
3838
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.m3u.data.repository.other
2+
3+
import com.m3u.data.api.dto.github.Release
4+
5+
interface OtherRepository {
6+
suspend fun getRelease(): Release?
7+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.m3u.data.repository.other
2+
3+
import com.m3u.core.architecture.Publisher
4+
import com.m3u.core.architecture.logger.Logger
5+
import com.m3u.core.architecture.logger.Profiles
6+
import com.m3u.core.architecture.logger.execute
7+
import com.m3u.core.architecture.logger.install
8+
import com.m3u.core.util.collections.indexOf
9+
import com.m3u.data.api.GithubApi
10+
import com.m3u.data.api.dto.github.Release
11+
import javax.inject.Inject
12+
13+
internal class OtherRepositoryImpl @Inject constructor(
14+
private val githubApi: GithubApi,
15+
private val publisher: Publisher,
16+
delegate: Logger
17+
) : OtherRepository {
18+
private val logger = delegate.install(Profiles.REPOS_OTHER)
19+
override suspend fun getRelease(): Release? {
20+
if (publisher.snapshot) return null
21+
val versionName = publisher.versionName
22+
val releases = logger
23+
.execute { githubApi.releases("oxyroid", "M3UAndroid") }
24+
?: emptyList()
25+
if (releases.isEmpty()) return null
26+
val i = releases.indexOf { it.name == versionName }
27+
// if (i <= 0) return null
28+
return releases.first()
29+
}
30+
}

feature/favorite/src/main/java/com/m3u/feature/favorite/components/FavoriteGallery.kt

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
package com.m3u.feature.favorite.components
22

3-
import androidx.compose.animation.animateColor
4-
import androidx.compose.animation.core.LinearEasing
5-
import androidx.compose.animation.core.RepeatMode
6-
import androidx.compose.animation.core.infiniteRepeatable
7-
import androidx.compose.animation.core.rememberInfiniteTransition
8-
import androidx.compose.animation.core.tween
93
import androidx.compose.foundation.background
104
import androidx.compose.foundation.clickable
115
import androidx.compose.foundation.layout.Arrangement
@@ -25,7 +19,6 @@ import androidx.compose.material3.ListItemDefaults
2519
import androidx.compose.material3.MaterialTheme
2620
import androidx.compose.material3.Text
2721
import androidx.compose.runtime.Composable
28-
import androidx.compose.runtime.getValue
2922
import androidx.compose.ui.Modifier
3023
import androidx.compose.ui.draw.clip
3124
import androidx.compose.ui.graphics.Brush
@@ -40,6 +33,7 @@ import com.m3u.i18n.R.string
4033
import com.m3u.material.ktx.isTelevision
4134
import com.m3u.material.ktx.plus
4235
import com.m3u.material.model.LocalSpacing
36+
import com.m3u.ui.createPremiumBrush
4337
import androidx.tv.material3.Card as TvCard
4438
import androidx.tv.material3.CardDefaults as TvCardDefaults
4539
import androidx.tv.material3.Glow as TvGlow
@@ -131,7 +125,7 @@ private fun RandomTips(
131125
.clip(AbsoluteRoundedCornerShape(spacing.medium))
132126
.clickable(onClick = onClick)
133127
.background(
134-
createPremiumBrush(
128+
Brush.createPremiumBrush(
135129
MaterialTheme.colorScheme.primary,
136130
MaterialTheme.colorScheme.tertiary
137131
)
@@ -156,7 +150,7 @@ private fun RandomTips(
156150
modifier = Modifier
157151
.fillMaxWidth()
158152
.background(
159-
createPremiumBrush(
153+
Brush.createPremiumBrush(
160154
TvMaterialTheme.colorScheme.primary,
161155
TvMaterialTheme.colorScheme.tertiary
162156
)
@@ -175,33 +169,3 @@ private fun RandomTips(
175169
}
176170
}
177171
}
178-
179-
@Composable
180-
private fun createPremiumBrush(
181-
color1: Color = MaterialTheme.colorScheme.primaryContainer,
182-
color2: Color = MaterialTheme.colorScheme.secondaryContainer
183-
): Brush {
184-
val transition = rememberInfiniteTransition("premium-brush")
185-
186-
val leftColor by transition.animateColor(
187-
initialValue = color1,
188-
targetValue = color2,
189-
animationSpec = infiniteRepeatable(
190-
animation = tween(1600, easing = LinearEasing),
191-
repeatMode = RepeatMode.Reverse
192-
),
193-
label = "left"
194-
)
195-
val rightColor by transition.animateColor(
196-
initialValue = color2,
197-
targetValue = color1,
198-
animationSpec = infiniteRepeatable(
199-
animation = tween(1600, easing = LinearEasing),
200-
repeatMode = RepeatMode.Reverse
201-
),
202-
label = "right"
203-
)
204-
return Brush.linearGradient(
205-
colors = listOf(leftColor, rightColor)
206-
)
207-
}

feature/foryou/src/main/java/com/m3u/feature/foryou/ForyouScreen.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ import androidx.lifecycle.repeatOnLifecycle
3434
import com.m3u.core.architecture.preferences.hiltPreferences
3535
import com.m3u.core.util.basic.title
3636
import com.m3u.core.wrapper.Resource
37+
import com.m3u.data.database.model.Channel
3738
import com.m3u.data.database.model.Playlist
3839
import com.m3u.data.database.model.PlaylistWithCount
39-
import com.m3u.data.database.model.Channel
4040
import com.m3u.data.database.model.isSeries
4141
import com.m3u.data.service.MediaCommand
4242
import com.m3u.feature.foryou.components.HeadlineBackground
@@ -81,7 +81,7 @@ fun ForyouRoute(
8181
val isPageInfoVisible = root == Destination.Root.Foryou
8282

8383
val playlistCountsResource by viewModel.playlistCountsResource.collectAsStateWithLifecycle()
84-
val recommend by viewModel.recommend.collectAsStateWithLifecycle()
84+
val specs by viewModel.specs.collectAsStateWithLifecycle()
8585
val episodes by viewModel.episodes.collectAsStateWithLifecycle()
8686

8787
val series: Channel? by viewModel.series.collectAsStateWithLifecycle()
@@ -112,7 +112,7 @@ fun ForyouRoute(
112112
playlistCountsResource = playlistCountsResource,
113113
subscribingPlaylistUrls = subscribingPlaylistUrls,
114114
refreshingEpgUrls = refreshingEpgUrls,
115-
recommend = recommend,
115+
specs = specs,
116116
rowCount = preferences.rowCount,
117117
contentPadding = contentPadding,
118118
navigateToPlaylist = navigateToPlaylist,
@@ -177,7 +177,7 @@ private fun ForyouScreen(
177177
playlistCountsResource: Resource<List<PlaylistWithCount>>,
178178
subscribingPlaylistUrls: List<String>,
179179
refreshingEpgUrls: List<String>,
180-
recommend: Recommend,
180+
specs: List<Recommend.Spec>,
181181
contentPadding: PaddingValues,
182182
navigateToPlaylist: (Playlist) -> Unit,
183183
onClickChannel: (Channel) -> Unit,
@@ -202,12 +202,13 @@ private fun ForyouScreen(
202202
}
203203

204204
LaunchedEffect(headlineSpec) {
205-
val currentHeadlineSpec = headlineSpec
205+
val spec = headlineSpec
206206
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
207207
delay(400.milliseconds)
208-
Metadata.headlineUrl = when (currentHeadlineSpec) {
209-
is Recommend.UnseenSpec -> currentHeadlineSpec.channel.cover.orEmpty()
208+
Metadata.headlineUrl = when (spec) {
209+
is Recommend.UnseenSpec -> spec.channel.cover.orEmpty()
210210
is Recommend.DiscoverSpec -> ""
211+
is Recommend.NewRelease -> ""
211212
else -> ""
212213
}
213214
}
@@ -228,7 +229,7 @@ private fun ForyouScreen(
228229
val showPlaylist = playlistCountsResource.data.isNotEmpty()
229230
val header = @Composable {
230231
RecommendGallery(
231-
recommend = recommend,
232+
specs = specs,
232233
navigateToPlaylist = navigateToPlaylist,
233234
onClickChannel = onClickChannel,
234235
onSpecChanged = { spec -> headlineSpec = spec },
@@ -243,7 +244,7 @@ private fun ForyouScreen(
243244
refreshingEpgUrls = refreshingEpgUrls,
244245
onClick = navigateToPlaylist,
245246
onLongClick = { mediaSheetValue = MediaSheetValue.ForyouScreen(it) },
246-
header = header.takeIf { recommend.isNotEmpty() },
247+
header = header.takeIf { specs.isNotEmpty() },
247248
contentPadding = contentPadding,
248249
modifier = Modifier.fillMaxSize()
249250
)

feature/foryou/src/main/java/com/m3u/feature/foryou/ForyouViewModel.kt

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ import com.m3u.core.architecture.logger.Logger
1212
import com.m3u.core.architecture.logger.Profiles
1313
import com.m3u.core.architecture.logger.install
1414
import com.m3u.core.architecture.preferences.Preferences
15+
import com.m3u.core.unit.DataUnit
1516
import com.m3u.core.wrapper.Resource
1617
import com.m3u.core.wrapper.asResource
1718
import com.m3u.core.wrapper.mapResource
1819
import com.m3u.core.wrapper.resource
20+
import com.m3u.data.api.dto.github.Release
21+
import com.m3u.data.database.model.Channel
1922
import com.m3u.data.database.model.Playlist
2023
import com.m3u.data.database.model.PlaylistWithCount
21-
import com.m3u.data.database.model.Channel
2224
import com.m3u.data.parser.xtream.XtreamChannelInfo
25+
import com.m3u.data.repository.channel.ChannelRepository
26+
import com.m3u.data.repository.other.OtherRepository
2327
import com.m3u.data.repository.playlist.PlaylistRepository
2428
import com.m3u.data.repository.programme.ProgrammeRepository
25-
import com.m3u.data.repository.channel.ChannelRepository
2629
import com.m3u.data.worker.SubscriptionWorker
2730
import com.m3u.feature.foryou.components.recommend.Recommend
2831
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -48,6 +51,7 @@ class ForyouViewModel @Inject constructor(
4851
private val playlistRepository: PlaylistRepository,
4952
channelRepository: ChannelRepository,
5053
programmeRepository: ProgrammeRepository,
54+
otherRepository: OtherRepository,
5155
preferences: Preferences,
5256
@Dispatcher(IO) ioDispatcher: CoroutineDispatcher,
5357
workManager: WorkManager,
@@ -95,14 +99,39 @@ class ForyouViewModel @Inject constructor(
9599
initialValue = Duration.INFINITE
96100
)
97101

98-
internal val recommend: StateFlow<Recommend> = unseensDuration
102+
private val newRelease: StateFlow<Release?> = flow {
103+
emit(otherRepository.getRelease())
104+
}
105+
.stateIn(
106+
scope = viewModelScope,
107+
initialValue = null,
108+
started = SharingStarted.Lazily
109+
)
110+
internal val specs: StateFlow<List<Recommend.Spec>> = unseensDuration
99111
.flatMapLatest { channelRepository.observeAllUnseenFavourites(it) }
100-
.map { prev -> Recommend(prev.map { Recommend.UnseenSpec(it) }) }
112+
.let { flow ->
113+
combine(flow, newRelease) { channels, nr ->
114+
buildList<Recommend.Spec> {
115+
if (nr != null) {
116+
val min = DataUnit.of(nr.assets.minOfOrNull { it.size }?.toLong() ?: 0L)
117+
val max = DataUnit.of(nr.assets.maxOfOrNull { it.size }?.toLong() ?: 0L)
118+
this += Recommend.NewRelease(
119+
name = nr.name,
120+
description = nr.body,
121+
downloadCount = nr.assets.sumOf { it.downloadCount },
122+
size = min..max,
123+
url = nr.htmlUrl
124+
)
125+
}
126+
this += channels.map { channel -> Recommend.UnseenSpec(channel) }
127+
}
128+
}
129+
}
101130
.flowOn(ioDispatcher)
102131
.stateIn(
103132
scope = viewModelScope,
104133
started = SharingStarted.WhileSubscribed(5_000L),
105-
initialValue = Recommend()
134+
initialValue = emptyList()
106135
)
107136

108137
internal fun onUnsubscribePlaylist(url: String) {

feature/foryou/src/main/java/com/m3u/feature/foryou/components/recommend/Recommend.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.m3u.feature.foryou.components.recommend
22

33
import androidx.compose.runtime.Immutable
4+
import com.m3u.core.unit.DataUnit
45
import com.m3u.data.database.model.Playlist
56
import com.m3u.data.database.model.Channel
67

@@ -25,4 +26,13 @@ internal class Recommend(
2526
data class UnseenSpec(
2627
val channel: Channel
2728
) : Spec
29+
30+
@Immutable
31+
data class NewRelease(
32+
val name: String,
33+
val description: String,
34+
val downloadCount: Int,
35+
val size: ClosedRange<DataUnit>,
36+
val url: String,
37+
): Spec
2838
}

0 commit comments

Comments
 (0)