Skip to content

Commit

Permalink
fix: programmes won't be cleared if user check from channel which con…
Browse files Browse the repository at this point in the history
…tains programmes to not contains programmes.
  • Loading branch information
oxyroid committed May 5, 2024
1 parent b30aa9a commit cbf4910
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 61 deletions.
44 changes: 42 additions & 2 deletions data/src/main/java/com/m3u/data/database/model/Programme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import androidx.compose.runtime.Immutable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.datetime.Instant
import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.DurationUnit
import kotlin.time.toDuration

@Entity(tableName = "programmes")
@Immutable
Expand Down Expand Up @@ -41,8 +46,43 @@ data class ProgrammeRange(
@ColumnInfo("end_edge")
val end: Long
) {
fun isEmpty(): Boolean = end - start <= 0
fun count(unit: Long = 1L): Int = (end - start).floorDiv(unit).toInt()
fun spread(spread: Spread): ProgrammeRange =
when (spread) {
is Spread.Absolute -> {
val duration = spread.duration - (end - start).toDuration(DurationUnit.MILLISECONDS)
when {
!spread.increaseOnly || duration.isPositive() -> {
ProgrammeRange(
start,
(Instant.fromEpochMilliseconds(end) + duration).toEpochMilliseconds()
)
}

else -> this
}
}

is Spread.Increase -> {
ProgrammeRange(
Instant.fromEpochMilliseconds(start).minus(spread.prev).toEpochMilliseconds(),
Instant.fromEpochMilliseconds(end).plus(spread.future).toEpochMilliseconds()
)
}
}

operator fun plus(duration: Duration): ProgrammeRange = ProgrammeRange(
Instant.fromEpochMilliseconds(start).plus(duration).toEpochMilliseconds(),
Instant.fromEpochMilliseconds(end).plus(duration).toEpochMilliseconds()
)

sealed interface Spread {
data class Absolute(
val duration: Duration = 12.hours,
val increaseOnly: Boolean = true
) : Spread

data class Increase(val prev: Duration = 4.hours, val future: Duration = 8.hours) : Spread
}

companion object {
const val HOUR_LENGTH = 3600000L
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface ProgrammeRepository {
channelId: String
): PagingSource<Int, Programme>

fun observeTimelineRange(
fun observeProgrammeRange(
epgUrls: List<String>,
channelId: String
): Flow<ProgrammeRange>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ internal class ProgrammeRepositoryImpl @Inject constructor(
channelId: String
): PagingSource<Int, Programme> = programmeDao.pagingByEpgUrlsAndChannelId(epgUrls, channelId)

override fun observeTimelineRange(
override fun observeProgrammeRange(
epgUrls: List<String>,
channelId: String
): Flow<ProgrammeRange> = programmeDao
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ fun StreamRoute(

val neighboring = viewModel.neighboring.collectAsLazyPagingItems()
val programmes = viewModel.programmes.collectAsLazyPagingItems()
val timelineRange by viewModel.timelineRange.collectAsStateWithLifecycle()
val programmeRange by viewModel.programmeRange.collectAsStateWithLifecycle()

var brightness by remember { mutableFloatStateOf(helper.brightness) }
var isPipMode by remember { mutableStateOf(false) }
Expand Down Expand Up @@ -198,7 +198,7 @@ fun StreamRoute(
isProgrammesRefreshing = isProgrammesRefreshing,
neighboring = neighboring,
programmes = programmes,
timelineRange = timelineRange,
programmeRange = programmeRange,
onRefreshProgrammesIgnoreCache = {
viewModel.checkOrRefreshProgrammes(true)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,18 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import org.jupnp.model.meta.Device
import javax.inject.Inject
import kotlin.math.roundToInt
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes

@HiltViewModel
class StreamViewModel @Inject constructor(
Expand Down Expand Up @@ -272,22 +271,23 @@ class StreamViewModel @Inject constructor(
}

internal val neighboring: Flow<PagingData<Stream>> = playlist.flatMapLatest { playlist ->
playlist ?: return@flatMapLatest flowOf(PagingData.empty())
Pager(PagingConfig(10)) {
streamRepository.pagingAllByPlaylistUrl(
playlist?.url.orEmpty(),
playlist.url,
"",
StreamRepository.Sort.UNSPECIFIED
)
}
.flow
.cachedIn(viewModelScope)
}
.cachedIn(viewModelScope)

internal val programmes: Flow<PagingData<Programme>> = stream.flatMapLatest { stream ->
stream ?: return@flatMapLatest flow { }
val channelId = stream.channelId ?: return@flatMapLatest flow { }
stream ?: return@flatMapLatest flowOf(PagingData.empty())
val channelId = stream.channelId ?: return@flatMapLatest flowOf(PagingData.empty())
val playlist = stream.playlistUrl.let { playlistRepository.get(it) }
playlist ?: return@flatMapLatest flow { }
playlist ?: return@flatMapLatest flowOf(PagingData.empty())
val epgUrls = playlist.epgUrlsOrXtreamXmlUrl()
Pager(PagingConfig(15)) {
programmeRepository.pagingByEpgUrlsAndChannelId(
Expand All @@ -296,41 +296,34 @@ class StreamViewModel @Inject constructor(
)
}
.flow
.cachedIn(viewModelScope)
}
.cachedIn(viewModelScope)

private val defaultTimelineRange: ProgrammeRange
private val defaultProgrammeRange: ProgrammeRange
get() = with(Clock.System.now()) {
ProgrammeRange(
this.toEpochMilliseconds(),
this.plus(24.hours).toEpochMilliseconds()
this.minus(2.hours).toEpochMilliseconds(),
this.plus(6.hours).toEpochMilliseconds()
)
}

internal val timelineRange: StateFlow<ProgrammeRange> = stream.flatMapLatest { stream ->
stream ?: return@flatMapLatest flowOf(defaultTimelineRange)
val channelId = stream.channelId ?: return@flatMapLatest flowOf(defaultTimelineRange)
internal val programmeRange: StateFlow<ProgrammeRange> = stream.flatMapLatest { stream ->
stream ?: return@flatMapLatest flowOf(defaultProgrammeRange)
val channelId = stream.channelId ?: return@flatMapLatest flowOf(defaultProgrammeRange)
val playlist = stream.playlistUrl.let { playlistRepository.get(it) }
playlist ?: return@flatMapLatest flowOf(defaultTimelineRange)
playlist ?: return@flatMapLatest flowOf(defaultProgrammeRange)
val epgUrls = playlist.epgUrlsOrXtreamXmlUrl()
programmeRepository
.observeTimelineRange(epgUrls, channelId)
.observeProgrammeRange(epgUrls, channelId)
.map {
if (it.isEmpty()) return@map defaultTimelineRange
if (it.count(ProgrammeRange.HOUR_LENGTH) < 12) return@map with(
Instant.fromEpochMilliseconds(it.start)
) {
ProgrammeRange(
this.toEpochMilliseconds(),
this.plus(24.hours).toEpochMilliseconds()
)
}
it
.spread(ProgrammeRange.Spread.Increase(5.minutes, 1.hours + 5.minutes))
.spread(ProgrammeRange.Spread.Absolute(8.hours))
}
}
.stateIn(
scope = viewModelScope,
initialValue = defaultTimelineRange,
initialValue = defaultProgrammeRange,
started = SharingStarted.WhileSubscribed(5_000L)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal fun PlayerPanel(
isProgrammesRefreshing: Boolean,
neighboring: LazyPagingItems<Stream>,
programmes: LazyPagingItems<Programme>,
timelineRange: ProgrammeRange,
programmeRange: ProgrammeRange,
onRefreshProgrammesIgnoreCache: () -> Unit,
modifier: Modifier = Modifier
) {
Expand Down Expand Up @@ -100,7 +100,7 @@ internal fun PlayerPanel(
isProgrammesRefreshing = isProgrammesRefreshing,
programmes = programmes,
onRefreshProgrammesIgnoreCache = onRefreshProgrammesIgnoreCache,
range = timelineRange,
range = programmeRange,
modifier = Modifier
.fillMaxWidth()
.weight(1f)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
Expand Down Expand Up @@ -119,10 +120,16 @@ internal fun ProgramGuide(
label = "minabox-cell-height"
)

val currentTimelineOffset by remember(range.start) {
derivedStateOf {
(currentMilliseconds - range.start).toFloat() / HOUR_LENGTH * currentHeight
}
}

val animateToCurrentTimeline: suspend () -> Unit by rememberUpdatedState {
minaBoxState.animateTo(
0f,
(currentMilliseconds - range.start) / HOUR_LENGTH.toFloat() * currentHeight + scrollOffset
currentTimelineOffset + scrollOffset
)
}

Expand All @@ -145,25 +152,6 @@ internal fun ProgramGuide(
.then(zoomGestureModifier)
.then(modifier)
) {
// timelines
// items(
// count = range.count(HOUR_LENGTH),
// layoutInfo = { index ->
// MinaBoxItem(
// x = 0f,
// y = currentHeight * index + padding * 2.5f,
// width = timelineWidth,
// height = currentHeight
// )
// }
// ) { index ->
// TimelineCell(
// tickMilliseconds = tickMilliseconds,
// startEdge = range.start,
// index = index
// )
// }

// programmes
items(
count = programmes.itemCount,
Expand All @@ -173,13 +161,9 @@ internal fun ProgramGuide(
val start = programme.start
val end = programme.end
MinaBoxItem(
x = padding
// timelineWidth
,
x = padding,
y = currentHeight * (start - range.start) / HOUR_LENGTH + padding * 3,
width = (constraints.maxWidth - padding * 2
// timelineWidth
)
width = (constraints.maxWidth - padding * 2)
.coerceAtLeast(0f),
height = (currentHeight * (end - start) / HOUR_LENGTH - padding)
.coerceAtLeast(0f)
Expand All @@ -203,7 +187,7 @@ internal fun ProgramGuide(
layoutInfo = {
MinaBoxItem(
x = 0f,
y = (currentMilliseconds - range.start) / HOUR_LENGTH.toFloat() * currentHeight + padding * 2,
y = currentTimelineOffset + padding * 2,
width = constraints.maxWidth.toFloat(),
height = currentTimelineHeight
)
Expand All @@ -213,6 +197,23 @@ internal fun ProgramGuide(
milliseconds = currentMilliseconds
)
}

// range
items(
count = 1,
layoutInfo = {
MinaBoxItem(
x = 0f,
y = 0f,
width = 0f,
height = with(range) {
(currentHeight * (end - start) / HOUR_LENGTH - padding)
}
)
}
) {

}
}

Controls(
Expand Down

0 comments on commit cbf4910

Please sign in to comment.