Skip to content

Commit

Permalink
feat: update favourite widget style. (#189)
Browse files Browse the repository at this point in the history
Co-authored-by: oxy-windows <[email protected]>
  • Loading branch information
oxyroid and oxy-windows authored Sep 8, 2024
1 parent af074ea commit 913996c
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 66 deletions.
164 changes: 127 additions & 37 deletions androidApp/src/main/java/com/m3u/androidApp/glance/FavouriteWidget.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ package com.m3u.androidApp.glance
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.action.clickable
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.SizeMode
import androidx.glance.appwidget.action.actionStartActivity
import androidx.glance.appwidget.appWidgetBackground
import androidx.glance.appwidget.components.TitleBar
import androidx.glance.appwidget.cornerRadius
import androidx.glance.appwidget.lazy.LazyColumn
import androidx.glance.appwidget.lazy.itemsIndexed
Expand All @@ -27,10 +33,19 @@ import androidx.glance.layout.fillMaxSize
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.height
import androidx.glance.layout.padding
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import androidx.glance.unit.ColorProvider
import com.m3u.androidApp.R
import com.m3u.core.Contracts
import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Programme
import com.m3u.ui.util.TimeUtils.formatEOrSh
import dagger.hilt.android.EntryPointAccessors
import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime

class FavouriteWidget : GlanceAppWidget() {
override val sizeMode: SizeMode = SizeMode.Exact
Expand All @@ -39,55 +54,130 @@ class FavouriteWidget : GlanceAppWidget() {
EntryPointAccessors.fromApplication(context.applicationContext)
}
val favouriteFlow = accessor.channelRepository.observeAllFavourite()
val programmeRepository = accessor.programmeRepository

provideContent {
val channels by favouriteFlow.collectAsState(initial = emptyList())
GlanceTheme {
Box(
contentAlignment = Alignment.BottomStart,
Column(
modifier = GlanceModifier
.fillMaxSize()
.cornerRadius(16.dp)
.background(GlanceTheme.colors.background)
.background(GlanceTheme.colors.primary)
.appWidgetBackground()
.padding(4.dp)
) {
LazyColumn(
modifier = GlanceModifier.fillMaxWidth()
) {
itemsIndexed(channels) { i, channel ->
Column {
Box(
modifier = GlanceModifier
.fillMaxWidth()
.clickable(
actionStartActivity(
Intent(Intent.ACTION_VIEW).apply {
component = ComponentName.createRelative(
context,
Contracts.PLAYER_ACTIVITY
)
putExtra(Contracts.PLAYER_SHORTCUT_CHANNEL_ID, channel.id)
}
)
)
.padding(16.dp)
.cornerRadius(16.dp)
.background(GlanceTheme.colors.surfaceVariant),
contentAlignment = Alignment.CenterStart
) {
Text(
text = channel.title,
style = TextStyle(GlanceTheme.colors.onSurfaceVariant)
)
}
if (i != channels.lastIndex) {
Spacer(GlanceModifier.height(4.dp))
}
}
val appTitle = LocalContext.current
.getString(com.m3u.i18n.R.string.ui_title_favourite)
TitleBar(
startIcon = ImageProvider(R.drawable.round_calendar_month_24),
title = appTitle,
iconColor = GlanceTheme.colors.onPrimary,
textColor = GlanceTheme.colors.onPrimary
)
FavouriteGallery(
channels = channels,
getProgrammeCurrently = { programmeRepository.getProgrammeCurrently(it) }
)
}
}
}
}
}

@Composable
private fun FavouriteGallery(
channels: List<Channel>,
getProgrammeCurrently: suspend (channelId: Int) -> Programme?,
modifier: GlanceModifier = GlanceModifier
) {
LazyColumn(
modifier = GlanceModifier
.fillMaxWidth()
.cornerRadius(16.dp)
.then(modifier)
) {
itemsIndexed(channels) { i, channel ->
FavouriteGalleryItem(
channel = channel,
shouldShowDivider = i != channels.lastIndex,
getProgrammeCurrently = getProgrammeCurrently
)
}
}
}

@Composable
private fun FavouriteGalleryItem(
channel: Channel,
shouldShowDivider: Boolean,
getProgrammeCurrently: suspend (channelId: Int) -> Programme?,
modifier: GlanceModifier = GlanceModifier
) {
val context = LocalContext.current
Column(modifier) {
Box(
modifier = GlanceModifier
.fillMaxWidth()
.clickable(
actionStartActivity(
Intent(Intent.ACTION_VIEW).apply {
component = ComponentName.createRelative(
context,
Contracts.PLAYER_ACTIVITY
)
putExtra(
Contracts.PLAYER_SHORTCUT_CHANNEL_ID,
channel.id
)
}
}
)
)
.padding(16.dp)
.background(GlanceTheme.colors.surfaceVariant),
contentAlignment = Alignment.CenterStart
) {
Column {
Text(
text = channel.title,
style = TextStyle(
color = GlanceTheme.colors.onSurfaceVariant,
fontWeight = FontWeight.Bold
),
maxLines = 1
)
val programme: Programme? by produceState<Programme?>(
initialValue = null,
key1 = channel.id
) {
value = getProgrammeCurrently(channel.id)
}
programme?.let {
Text(
text = it.readText(),
style = TextStyle(
color = ColorProvider(
GlanceTheme.colors.onSurfaceVariant
.getColor(context)
.copy(alpha = 0.65f)
),
fontWeight = FontWeight.Medium,
fontSize = 12.sp
),
maxLines = 1
)
}
}
}
if (shouldShowDivider) {
Spacer(GlanceModifier.height(2.dp))
}
}
}

private fun Programme.readText(): String = buildString {
val start = Instant.fromEpochMilliseconds(start)
.toLocalDateTime(TimeZone.currentSystemDefault())
.formatEOrSh(true)
append("[$start] $title")
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.m3u.androidApp.glance
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import com.m3u.data.repository.channel.ChannelRepository
import com.m3u.data.repository.programme.ProgrammeRepository
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
Expand All @@ -15,4 +16,5 @@ class GlanceReceiver : GlanceAppWidgetReceiver() {
@InstallIn(SingletonComponent::class)
interface GlanceAccessor {
val channelRepository: ChannelRepository
val programmeRepository: ProgrammeRepository
}
5 changes: 5 additions & 0 deletions androidApp/src/main/res/drawable/round_calendar_month_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="@android:color/white" android:pathData="M17,2c-0.55,0 -1,0.45 -1,1v1H8V3c0,-0.55 -0.45,-1 -1,-1S6,2.45 6,3v1H5C3.89,4 3.01,4.9 3.01,6L3,20c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-1V3C18,2.45 17.55,2 17,2zM19,20H5V10h14V20zM11,13c0,-0.55 0.45,-1 1,-1s1,0.45 1,1s-0.45,1 -1,1S11,13.55 11,13zM7,13c0,-0.55 0.45,-1 1,-1s1,0.45 1,1s-0.45,1 -1,1S7,13.55 7,13zM15,13c0,-0.55 0.45,-1 1,-1s1,0.45 1,1s-0.45,1 -1,1S15,13.55 15,13zM11,17c0,-0.55 0.45,-1 1,-1s1,0.45 1,1s-0.45,1 -1,1S11,17.55 11,17zM7,17c0,-0.55 0.45,-1 1,-1s1,0.45 1,1s-0.45,1 -1,1S7,17.55 7,17zM15,17c0,-0.55 0.45,-1 1,-1s1,0.45 1,1s-0.45,1 -1,1S15,17.55 15,17z"/>

</vector>
5 changes: 5 additions & 0 deletions androidApp/src/main/res/drawable/round_space_dashboard_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="@android:color/white" android:pathData="M9,21H5c-1.1,0 -2,-0.9 -2,-2V5c0,-1.1 0.9,-2 2,-2h4c1.1,0 2,0.9 2,2v14C11,20.1 10.1,21 9,21zM15,21h4c1.1,0 2,-0.9 2,-2v-5c0,-1.1 -0.9,-2 -2,-2h-4c-1.1,0 -2,0.9 -2,2v5C13,20.1 13.9,21 15,21zM21,8V5c0,-1.1 -0.9,-2 -2,-2h-4c-1.1,0 -2,0.9 -2,2v3c0,1.1 0.9,2 2,2h4C20.1,10 21,9.1 21,8z"/>

</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ interface ProgrammeRepository {
): Flow<Int>

suspend fun getById(id: Int): Programme?
suspend fun getProgrammeCurrently(channelId: Int): Programme?
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.m3u.core.architecture.logger.install
import com.m3u.core.architecture.logger.post
import com.m3u.core.util.basic.letIf
import com.m3u.data.api.OkhttpClient
import com.m3u.data.database.dao.ChannelDao
import com.m3u.data.database.dao.PlaylistDao
import com.m3u.data.database.dao.ProgrammeDao
import com.m3u.data.database.model.Programme
Expand Down Expand Up @@ -38,6 +39,7 @@ import javax.inject.Inject

internal class ProgrammeRepositoryImpl @Inject constructor(
private val playlistDao: PlaylistDao,
private val channelDao: ChannelDao,
private val programmeDao: ProgrammeDao,
private val epgParser: EpgParser,
@OkhttpClient(true) private val okHttpClient: OkHttpClient,
Expand Down Expand Up @@ -96,6 +98,22 @@ internal class ProgrammeRepositoryImpl @Inject constructor(
programmeDao.getById(id)
}

override suspend fun getProgrammeCurrently(channelId: Int): Programme? {
val channel = channelDao.get(channelId)?: return null
val relationId = channel.relationId ?: return null
val playlist = playlistDao.get(channel.playlistUrl) ?: return null

val epgUrls = playlist.epgUrlsOrXtreamXmlUrl()
if (epgUrls.isEmpty()) return null

val time = Clock.System.now().toEpochMilliseconds()
return programmeDao.getCurrentByEpgUrlsAndRelationId(
epgUrls = epgUrls,
relationId = relationId,
time = time
)
}

private fun checkOrRefreshProgrammesOrThrowImpl(
epgUrls: List<String>,
ignoreCache: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ class SubscriptionWorker @AssistedInject constructor(

private fun createChannel() {
val channel = NotificationChannel(
CHANNEL_ID, NOTIFICATION_NAME, NotificationManager.IMPORTANCE_DEFAULT
CHANNEL_ID, NOTIFICATION_NAME, NotificationManager.IMPORTANCE_LOW
)
channel.description = "display subscribe task progress"
notificationManager.createNotificationChannel(channel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ private fun PlaylistScreen(
contentPadding: PaddingValues,
isVodPlaylist: Boolean,
isSeriesPlaylist: Boolean,
getProgrammeCurrently: suspend (channelId: String) -> Programme?,
getProgrammeCurrently: suspend (channelId: Int) -> Programme?,
modifier: Modifier = Modifier
) {
val currentOnScrollUp by rememberUpdatedState(onScrollUp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,16 @@ import com.m3u.core.wrapper.Resource
import com.m3u.core.wrapper.handledEvent
import com.m3u.core.wrapper.mapResource
import com.m3u.core.wrapper.resource
import com.m3u.data.database.dao.ProgrammeDao
import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Playlist
import com.m3u.data.database.model.Programme
import com.m3u.data.database.model.epgUrlsOrXtreamXmlUrl
import com.m3u.data.database.model.isSeries
import com.m3u.data.database.model.type
import com.m3u.data.parser.xtream.XtreamChannelInfo
import com.m3u.data.repository.media.MediaRepository
import com.m3u.data.repository.playlist.PlaylistRepository
import com.m3u.data.repository.channel.ChannelRepository
import com.m3u.data.repository.programme.ProgrammeRepository
import com.m3u.data.service.MediaCommand
import com.m3u.data.service.Messager
import com.m3u.data.service.PlayerManager
Expand Down Expand Up @@ -76,7 +75,6 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import androidx.tvprovider.media.tv.Channel as TvProviderChannel
Expand All @@ -89,7 +87,7 @@ class PlaylistViewModel @Inject constructor(
private val channelRepository: ChannelRepository,
private val playlistRepository: PlaylistRepository,
private val mediaRepository: MediaRepository,
private val programmeDao: ProgrammeDao,
private val programmeRepository: ProgrammeRepository,
private val messager: Messager,
playerManager: PlayerManager,
preferences: Preferences,
Expand Down Expand Up @@ -301,16 +299,8 @@ class PlaylistViewModel @Inject constructor(
}
}

internal suspend fun getProgrammeCurrently(channelId: String): Programme? {
val playlist = playlist.value ?: return null
val epgUrls = playlist.epgUrlsOrXtreamXmlUrl()
if (epgUrls.isEmpty()) return null
val time = Clock.System.now().toEpochMilliseconds()
return programmeDao.getCurrentByEpgUrlsAndRelationId(
epgUrls = epgUrls,
relationId = channelId,
time = time
)
internal suspend fun getProgrammeCurrently(channelId: Int): Programme? {
return programmeRepository.getProgrammeCurrently(channelId)
}

private val sortIndex: MutableStateFlow<Int> = MutableStateFlow(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ internal fun ImmersiveBackground(
onRefresh: () -> Unit,
openSearchDrawer: () -> Unit,
openSortDrawer: () -> Unit,
getProgrammeCurrently: suspend (relationId: String) -> Programme?,
getProgrammeCurrently: suspend (channelId: Int) -> Programme?,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
Expand Down Expand Up @@ -102,9 +102,9 @@ internal fun ImmersiveBackground(

val programme: Programme? by produceState<Programme?>(
initialValue = null,
key1 = channel.relationId
key1 = channel.id
) {
value = currentGetProgrammeCurrently(channel.relationId.orEmpty())
value = currentGetProgrammeCurrently(channel.id)
}

programme?.let {
Expand Down
Loading

0 comments on commit 913996c

Please sign in to comment.