Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update favourite widget style. #189

Merged
merged 1 commit into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading