refactor: move ui logic to smartphone module.

This commit is contained in:
oxy-macmini
2025-03-09 23:53:14 +08:00
parent 2c9d321a0b
commit 7f69735527
203 changed files with 779 additions and 1171 deletions

View File

@ -22,7 +22,7 @@ android {
dependencies { dependencies {
implementation(project(":core")) implementation(project(":core"))
implementation(project(":ui")) implementation(project(":data"))
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
@ -39,6 +39,5 @@ dependencies {
ksp(libs.androidx.hilt.compiler) ksp(libs.androidx.hilt.compiler)
implementation(libs.androidx.hilt.work) implementation(libs.androidx.hilt.work)
implementation(libs.minabox)
implementation(libs.net.mm2d.mmupnp.mmupnp) implementation(libs.net.mm2d.mmupnp.mmupnp)
} }

View File

@ -20,6 +20,7 @@ import com.m3u.core.architecture.logger.Logger
import com.m3u.core.architecture.logger.Profiles import com.m3u.core.architecture.logger.Profiles
import com.m3u.core.architecture.logger.install import com.m3u.core.architecture.logger.install
import com.m3u.core.util.coroutine.flatmapCombined import com.m3u.core.util.coroutine.flatmapCombined
import com.m3u.core.wrapper.Sort
import com.m3u.data.database.model.AdjacentChannels import com.m3u.data.database.model.AdjacentChannels
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.DataSource import com.m3u.data.database.model.DataSource
@ -74,15 +75,15 @@ class ChannelViewModel @Inject constructor(
private val logger = delegate.install(Profiles.VIEWMODEL_CHANNEL) private val logger = delegate.install(Profiles.VIEWMODEL_CHANNEL)
// searched screencast devices // searched screencast devices
internal var devices by mutableStateOf(emptyList<Device>()) var devices by mutableStateOf(emptyList<Device>())
private val _volume: MutableStateFlow<Float> by lazy { private val _volume: MutableStateFlow<Float> by lazy {
MutableStateFlow(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) / 100f) MutableStateFlow(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) / 100f)
} }
internal val volume = _volume.asStateFlow() val volume = _volume.asStateFlow()
internal val channel: StateFlow<Channel?> = playerManager.channel val channel: StateFlow<Channel?> = playerManager.channel
internal val playlist: StateFlow<Playlist?> = playerManager.playlist val playlist: StateFlow<Playlist?> = playerManager.playlist
val adjacentChannels: StateFlow<AdjacentChannels?> = flatmapCombined( val adjacentChannels: StateFlow<AdjacentChannels?> = flatmapCombined(
playlist, playlist,
@ -103,9 +104,9 @@ class ChannelViewModel @Inject constructor(
) )
internal val isSeriesPlaylist: Flow<Boolean> = playlist.map { it?.isSeries ?: false } val isSeriesPlaylist: Flow<Boolean> = playlist.map { it?.isSeries ?: false }
internal val isProgrammeSupported: Flow<Boolean> = playlist.map { val isProgrammeSupported: Flow<Boolean> = playlist.map {
it ?: return@map false it ?: return@map false
if (it.isSeries || it.isVod) return@map false if (it.isSeries || it.isVod) return@map false
when (it.source) { when (it.source) {
@ -115,16 +116,16 @@ class ChannelViewModel @Inject constructor(
} }
} }
internal val tracks: Flow<Map<Int, List<Format>>> = playerManager.tracks val tracks: Flow<Map<Int, List<Format>>> = playerManager.tracks
.map { all -> .map { all ->
all all
.mapValues { (_, formats) -> formats } .mapValues { (_, formats) -> formats }
.toMap() .toMap()
} }
internal val currentTracks: Flow<Map<@C.TrackType Int, Format?>> = playerManager.currentTracks val currentTracks: Flow<Map<@C.TrackType Int, Format?>> = playerManager.currentTracks
internal fun chooseTrack(type: @C.TrackType Int, format: Format) { fun chooseTrack(type: @C.TrackType Int, format: Format) {
val groups = playerManager.tracksGroups.value val groups = playerManager.tracksGroups.value
val group = groups.find { it.type == type } ?: return val group = groups.find { it.type == type } ?: return
val trackGroup = group.mediaTrackGroup val trackGroup = group.mediaTrackGroup
@ -139,12 +140,12 @@ class ChannelViewModel @Inject constructor(
} }
} }
internal fun clearTrack(type: @C.TrackType Int) { fun clearTrack(type: @C.TrackType Int) {
playerManager.clearTrack(type) playerManager.clearTrack(type)
} }
// channel playing state // channel playing state
internal val playerState: StateFlow<PlayerState> = combine( val playerState: StateFlow<PlayerState> = combine(
playerManager.player, playerManager.player,
playerManager.playbackState, playerManager.playbackState,
playerManager.size, playerManager.size,
@ -168,14 +169,14 @@ class ChannelViewModel @Inject constructor(
private val _isDevicesVisible: MutableStateFlow<Boolean> = MutableStateFlow(false) private val _isDevicesVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
// show searching devices dialog or not // show searching devices dialog or not
internal val isDevicesVisible = _isDevicesVisible.asStateFlow() val isDevicesVisible = _isDevicesVisible.asStateFlow()
private val _searching: MutableStateFlow<Boolean> = MutableStateFlow(false) private val _searching: MutableStateFlow<Boolean> = MutableStateFlow(false)
// searching or not // searching or not
internal val searching = _searching.asStateFlow() val searching = _searching.asStateFlow()
internal fun openDlnaDevices() { fun openDlnaDevices() {
viewModelScope.launch { viewModelScope.launch {
delay(800.milliseconds) delay(800.milliseconds)
_searching.value = true _searching.value = true
@ -189,7 +190,7 @@ class ChannelViewModel @Inject constructor(
_isDevicesVisible.value = true _isDevicesVisible.value = true
} }
internal fun closeDlnaDevices() { fun closeDlnaDevices() {
runCatching { runCatching {
_searching.value = false _searching.value = false
_isDevicesVisible.value = false _isDevicesVisible.value = false
@ -213,7 +214,7 @@ class ChannelViewModel @Inject constructor(
private var controlPoint: ControlPoint? = null private var controlPoint: ControlPoint? = null
internal fun connectDlnaDevice(device: Device) { fun connectDlnaDevice(device: Device) {
val url = channel.value?.url ?: return val url = channel.value?.url ?: return
device.findAction(ACTION_SET_AV_TRANSPORT_URI)?.invoke( device.findAction(ACTION_SET_AV_TRANSPORT_URI)?.invoke(
argumentValues = mapOf( argumentValues = mapOf(
@ -223,18 +224,18 @@ class ChannelViewModel @Inject constructor(
) )
} }
internal fun disconnectDlnaDevice(device: Device) { fun disconnectDlnaDevice(device: Device) {
} }
internal fun onFavourite() { fun onFavourite() {
viewModelScope.launch { viewModelScope.launch {
val id = channel.value?.id ?: return@launch val id = channel.value?.id ?: return@launch
channelRepository.favouriteOrUnfavourite(id) channelRepository.favouriteOrUnfavourite(id)
} }
} }
internal fun onVolume(target: Float) { fun onVolume(target: Float) {
_volume.update { target } _volume.update { target }
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
audioManager.setStreamVolume( audioManager.setStreamVolume(
@ -246,7 +247,7 @@ class ChannelViewModel @Inject constructor(
// controlPoint?.setVolume((target * 100).roundToInt(), null) // controlPoint?.setVolume((target * 100).roundToInt(), null)
} }
internal fun getPreviousChannel() { fun getPreviousChannel() {
viewModelScope.launch { viewModelScope.launch {
val previousChannelId = adjacentChannels.value?.prevId val previousChannelId = adjacentChannels.value?.prevId
if (adjacentChannels.value != null && previousChannelId != null) { if (adjacentChannels.value != null && previousChannelId != null) {
@ -255,7 +256,7 @@ class ChannelViewModel @Inject constructor(
} }
} }
internal fun getNextChannel() { fun getNextChannel() {
viewModelScope.launch { viewModelScope.launch {
val nextChannelId = adjacentChannels.value?.nextId val nextChannelId = adjacentChannels.value?.nextId
if (adjacentChannels.value != null && nextChannelId != null) { if (adjacentChannels.value != null && nextChannelId != null) {
@ -279,7 +280,7 @@ class ChannelViewModel @Inject constructor(
playerManager.pauseOrContinue(isContinued) playerManager.pauseOrContinue(isContinued)
} }
internal val programmeReminderIds: StateFlow<List<Int>> = workManager.getWorkInfosFlow( val programmeReminderIds: StateFlow<List<Int>> = workManager.getWorkInfosFlow(
WorkQuery.fromStates( WorkQuery.fromStates(
WorkInfo.State.ENQUEUED WorkInfo.State.ENQUEUED
) )
@ -319,14 +320,14 @@ class ChannelViewModel @Inject constructor(
// the channels which is in the same category with the current channel // the channels which is in the same category with the current channel
// or the episodes which is in the same series. // or the episodes which is in the same series.
internal val pagingChannels: Flow<PagingData<Channel>> = playlist.flatMapLatest { playlist -> val pagingChannels: Flow<PagingData<Channel>> = playlist.flatMapLatest { playlist ->
playlist ?: return@flatMapLatest flowOf(PagingData.empty()) playlist ?: return@flatMapLatest flowOf(PagingData.empty())
Pager(PagingConfig(10)) { Pager(PagingConfig(10)) {
channelRepository.pagingAllByPlaylistUrl( channelRepository.pagingAllByPlaylistUrl(
playlist.url, playlist.url,
channel.value?.category.orEmpty(), channel.value?.category.orEmpty(),
"", "",
ChannelRepository.Sort.UNSPECIFIED Sort.UNSPECIFIED
) )
} }
.flow .flow
@ -340,7 +341,7 @@ class ChannelViewModel @Inject constructor(
) )
} }
internal val programmes: Flow<PagingData<Programme>> = channel.flatMapLatest { channel -> val programmes: Flow<PagingData<Programme>> = channel.flatMapLatest { channel ->
channel ?: return@flatMapLatest flowOf(PagingData.empty()) channel ?: return@flatMapLatest flowOf(PagingData.empty())
val relationId = channel.relationId ?: return@flatMapLatest flowOf(PagingData.empty()) val relationId = channel.relationId ?: return@flatMapLatest flowOf(PagingData.empty())
val playlist = channel.playlistUrl.let { playlistRepository.get(it) } val playlist = channel.playlistUrl.let { playlistRepository.get(it) }
@ -360,7 +361,7 @@ class ChannelViewModel @Inject constructor(
) )
} }
internal val programmeRange: StateFlow<ProgrammeRange> = channel.flatMapLatest { channel -> val programmeRange: StateFlow<ProgrammeRange> = channel.flatMapLatest { channel ->
channel ?: return@flatMapLatest flowOf(defaultProgrammeRange) channel ?: return@flatMapLatest flowOf(defaultProgrammeRange)
val relationId = channel.relationId ?: return@flatMapLatest flowOf(defaultProgrammeRange) val relationId = channel.relationId ?: return@flatMapLatest flowOf(defaultProgrammeRange)
programmeRepository programmeRepository
@ -377,7 +378,7 @@ class ChannelViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L) started = SharingStarted.WhileSubscribed(5_000L)
) )
internal fun onSpeedUpdated(race: Float) { fun onSpeedUpdated(race: Float) {
playerManager.updateSpeed(race) playerManager.updateSpeed(race)
} }

View File

@ -6,7 +6,7 @@ import androidx.media3.common.PlaybackException
import androidx.media3.common.Player import androidx.media3.common.Player
@Immutable @Immutable
internal data class PlayerState( data class PlayerState(
val playState: @Player.State Int = Player.STATE_IDLE, val playState: @Player.State Int = Player.STATE_IDLE,
val videoSize: Rect = Rect(), val videoSize: Rect = Rect(),
val playerError: PlaybackException? = null, val playerError: PlaybackException? = null,

View File

@ -1 +0,0 @@
/build

View File

@ -1,35 +0,0 @@
plugins {
alias(libs.plugins.com.android.library)
alias(libs.plugins.org.jetbrains.kotlin.android)
alias(libs.plugins.com.google.devtools.ksp)
alias(libs.plugins.com.google.dagger.hilt.android)
alias(libs.plugins.compose.compiler)
}
android {
namespace = "com.m3u.business.crash"
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
}
packaging {
resources.excludes += "META-INF/**"
}
}
dependencies {
implementation(project(":core"))
implementation(project(":ui"))
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.google.dagger.hilt)
implementation(libs.androidx.hilt.navigation.compose)
ksp(libs.google.dagger.hilt.compiler)
}

View File

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
</manifest>

View File

@ -1,3 +0,0 @@
package com.m3u.business.crash.screen.list.navigation
typealias NavigateToDetail = (path: String) -> Unit

View File

@ -21,7 +21,7 @@ android {
dependencies { dependencies {
implementation(project(":core")) implementation(project(":core"))
implementation(project(":ui")) implementation(project(":data"))
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)

View File

@ -18,6 +18,7 @@ import com.m3u.core.architecture.logger.Profiles
import com.m3u.core.architecture.logger.install import com.m3u.core.architecture.logger.install
import com.m3u.core.architecture.preferences.Preferences import com.m3u.core.architecture.preferences.Preferences
import com.m3u.core.wrapper.Resource import com.m3u.core.wrapper.Resource
import com.m3u.core.wrapper.Sort
import com.m3u.core.wrapper.asResource import com.m3u.core.wrapper.asResource
import com.m3u.core.wrapper.mapResource import com.m3u.core.wrapper.mapResource
import com.m3u.core.wrapper.resource import com.m3u.core.wrapper.resource
@ -29,7 +30,6 @@ import com.m3u.data.repository.playlist.PlaylistRepository
import com.m3u.data.repository.channel.ChannelRepository import com.m3u.data.repository.channel.ChannelRepository
import com.m3u.data.service.MediaCommand import com.m3u.data.service.MediaCommand
import com.m3u.data.service.PlayerManager import com.m3u.data.service.PlayerManager
import com.m3u.ui.Sort
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -161,9 +161,9 @@ class FavouriteViewModel @Inject constructor(
} }
} }
internal val series = MutableStateFlow<Channel?>(null) val series = MutableStateFlow<Channel?>(null)
internal val seriesReplay = MutableStateFlow(0) val seriesReplay = MutableStateFlow(0)
internal val episodes: StateFlow<Resource<List<XtreamChannelInfo.Episode>>> = series val episodes: StateFlow<Resource<List<XtreamChannelInfo.Episode>>> = series
.combine(seriesReplay) { series, _ -> series } .combine(seriesReplay) { series, _ -> series }
.flatMapLatest { series -> .flatMapLatest { series ->
if (series == null) flow { } if (series == null) flow { }
@ -177,10 +177,10 @@ class FavouriteViewModel @Inject constructor(
started = SharingStarted.Lazily started = SharingStarted.Lazily
) )
internal suspend fun getPlaylist(playlistUrl: String): Playlist? = suspend fun getPlaylist(playlistUrl: String): Playlist? =
playlistRepository.get(playlistUrl) playlistRepository.get(playlistUrl)
internal fun playRandomly() { fun playRandomly() {
viewModelScope.launch { viewModelScope.launch {
val channel = channelRepository.getRandomIgnoreSeriesAndHidden() ?: return@launch val channel = channelRepository.getRandomIgnoreSeriesAndHidden() ?: return@launch
playerManager.play( playerManager.play(

View File

@ -21,15 +21,13 @@ android {
dependencies { dependencies {
implementation(project(":core")) implementation(project(":core"))
implementation(project(":ui")) implementation(project(":data"))
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.airbnb.lottie.compose)
implementation(libs.google.dagger.hilt) implementation(libs.google.dagger.hilt)
implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.hilt.navigation.compose)
ksp(libs.google.dagger.hilt.compiler) ksp(libs.google.dagger.hilt.compiler)

View File

@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkQuery import androidx.work.WorkQuery
import com.m3u.business.foryou.Recommend
import com.m3u.core.architecture.dispatcher.Dispatcher import com.m3u.core.architecture.dispatcher.Dispatcher
import com.m3u.core.architecture.dispatcher.M3uDispatchers.IO import com.m3u.core.architecture.dispatcher.M3uDispatchers.IO
import com.m3u.core.architecture.logger.Logger import com.m3u.core.architecture.logger.Logger
@ -26,7 +27,6 @@ import com.m3u.data.repository.channel.ChannelRepository
import com.m3u.data.repository.playlist.PlaylistRepository import com.m3u.data.repository.playlist.PlaylistRepository
import com.m3u.data.repository.programme.ProgrammeRepository import com.m3u.data.repository.programme.ProgrammeRepository
import com.m3u.data.worker.SubscriptionWorker import com.m3u.data.worker.SubscriptionWorker
import com.m3u.business.foryou.components.recommend.Recommend
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -58,7 +58,7 @@ class ForyouViewModel @Inject constructor(
) : ViewModel() { ) : ViewModel() {
private val logger = delegate.install(Profiles.VIEWMODEL_FORYOU) private val logger = delegate.install(Profiles.VIEWMODEL_FORYOU)
internal val playlistCounts: StateFlow<Resource<List<PlaylistWithCount>>> = val playlistCounts: StateFlow<Resource<List<PlaylistWithCount>>> =
playlistRepository playlistRepository
.observeAllCounts() .observeAllCounts()
.asResource() .asResource()
@ -68,7 +68,7 @@ class ForyouViewModel @Inject constructor(
initialValue = Resource.Loading initialValue = Resource.Loading
) )
internal val subscribingPlaylistUrls: StateFlow<List<String>> = val subscribingPlaylistUrls: StateFlow<List<String>> =
workManager.getWorkInfosFlow( workManager.getWorkInfosFlow(
WorkQuery.fromStates( WorkQuery.fromStates(
WorkInfo.State.RUNNING, WorkInfo.State.RUNNING,
@ -86,7 +86,7 @@ class ForyouViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L) started = SharingStarted.WhileSubscribed(5_000L)
) )
internal val refreshingEpgUrls: Flow<List<String>> = programmeRepository.refreshingEpgUrls val refreshingEpgUrls: Flow<List<String>> = programmeRepository.refreshingEpgUrls
private val unseensDuration = snapshotFlow { preferences.unseensMilliseconds } private val unseensDuration = snapshotFlow { preferences.unseensMilliseconds }
.map { it.toDuration(DurationUnit.MILLISECONDS) } .map { it.toDuration(DurationUnit.MILLISECONDS) }
@ -105,7 +105,7 @@ class ForyouViewModel @Inject constructor(
initialValue = null, initialValue = null,
started = SharingStarted.Lazily started = SharingStarted.Lazily
) )
internal val specs: StateFlow<List<Recommend.Spec>> = unseensDuration val specs: StateFlow<List<Recommend.Spec>> = unseensDuration
.flatMapLatest { channelRepository.observeAllUnseenFavourites(it) } .flatMapLatest { channelRepository.observeAllUnseenFavourites(it) }
.let { flow -> .let { flow ->
combine(flow, newRelease) { channels, nr -> combine(flow, newRelease) { channels, nr ->
@ -132,15 +132,15 @@ class ForyouViewModel @Inject constructor(
initialValue = emptyList() initialValue = emptyList()
) )
internal fun onUnsubscribePlaylist(url: String) { fun onUnsubscribePlaylist(url: String) {
viewModelScope.launch { viewModelScope.launch {
playlistRepository.unsubscribe(url) playlistRepository.unsubscribe(url)
} }
} }
internal val series = MutableStateFlow<Channel?>(null) val series = MutableStateFlow<Channel?>(null)
internal val seriesReplay = MutableStateFlow(0) val seriesReplay = MutableStateFlow(0)
internal val episodes: StateFlow<Resource<List<XtreamChannelInfo.Episode>>> = series val episodes: StateFlow<Resource<List<XtreamChannelInfo.Episode>>> = series
.combine(seriesReplay) { series, _ -> series } .combine(seriesReplay) { series, _ -> series }
.flatMapLatest { series -> .flatMapLatest { series ->
if (series == null) flow { } if (series == null) flow { }
@ -154,6 +154,6 @@ class ForyouViewModel @Inject constructor(
started = SharingStarted.Lazily started = SharingStarted.Lazily
) )
internal suspend fun getPlaylist(playlistUrl: String): Playlist? = suspend fun getPlaylist(playlistUrl: String): Playlist? =
playlistRepository.get(playlistUrl) playlistRepository.get(playlistUrl)
} }

View File

@ -1,9 +1,9 @@
package com.m3u.business.foryou.components.recommend package com.m3u.business.foryou
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import com.m3u.core.unit.DataUnit import com.m3u.core.unit.DataUnit
import com.m3u.data.database.model.Playlist
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Playlist
@Immutable @Immutable
class Recommend( class Recommend(

View File

@ -1,38 +0,0 @@
package com.m3u.business.foryou.internal
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalContext
@Composable
internal fun produceSensorOffset(
context: Context = LocalContext.current
): State<Offset> = produceState(
initialValue = Offset.Zero
) {
val manager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
val (x, y, _) = event.values
value = Offset(x, y)
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
val sensor = manager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
manager.registerListener(
listener,
sensor,
SensorManager.SENSOR_DELAY_NORMAL
)
awaitDispose {
manager.unregisterListener(listener)
}
}

View File

@ -21,7 +21,7 @@ android {
dependencies { dependencies {
implementation(project(":core")) implementation(project(":core"))
implementation(project(":ui")) implementation(project(":data"))
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)

View File

@ -16,7 +16,7 @@ import androidx.navigation.navArgument
private const val PLAYLIST_CONFIGURATION_ROUTE_PATH = "playlist_configuration_route" private const val PLAYLIST_CONFIGURATION_ROUTE_PATH = "playlist_configuration_route"
object PlaylistConfigurationNavigation { object PlaylistConfigurationNavigation {
internal const val TYPE_PLAYLIST_URL = "playlist_url" const val TYPE_PLAYLIST_URL = "playlist_url"
const val PLAYLIST_CONFIGURATION_ROUTE = const val PLAYLIST_CONFIGURATION_ROUTE =
"$PLAYLIST_CONFIGURATION_ROUTE_PATH?$TYPE_PLAYLIST_URL={$TYPE_PLAYLIST_URL}" "$PLAYLIST_CONFIGURATION_ROUTE_PATH?$TYPE_PLAYLIST_URL={$TYPE_PLAYLIST_URL}"
@ -34,25 +34,3 @@ fun NavController.navigateToPlaylistConfiguration(
val route = PlaylistConfigurationNavigation.createPlaylistConfigurationRoute(encodedUrl) val route = PlaylistConfigurationNavigation.createPlaylistConfigurationRoute(encodedUrl)
this.navigate(route, navOptions) this.navigate(route, navOptions)
} }
fun NavGraphBuilder.playlistConfigurationScreen(
contentPadding: PaddingValues = PaddingValues(),
) {
composable(
route = PlaylistConfigurationNavigation.PLAYLIST_CONFIGURATION_ROUTE,
arguments = listOf(
navArgument(PlaylistConfigurationNavigation.TYPE_PLAYLIST_URL) {
type = NavType.StringType
}
),
enterTransition = { slideInVertically { it } },
exitTransition = { fadeOut() },
popEnterTransition = { fadeIn() },
popExitTransition = { slideOutVertically { it } }
) {
PlaylistConfigurationRoute(
contentPadding = contentPadding
)
}
}

View File

@ -38,7 +38,7 @@ import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import javax.inject.Inject import javax.inject.Inject
internal typealias EpgManifest = Map<Playlist, Boolean> typealias EpgManifest = Map<Playlist, Boolean>
@HiltViewModel @HiltViewModel
class PlaylistConfigurationViewModel @Inject constructor( class PlaylistConfigurationViewModel @Inject constructor(
@ -53,7 +53,7 @@ class PlaylistConfigurationViewModel @Inject constructor(
private val logger = delegate.install(Profiles.VIEWMODEL_PLAYLIST_CONFIGURATION) private val logger = delegate.install(Profiles.VIEWMODEL_PLAYLIST_CONFIGURATION)
private val playlistUrl: StateFlow<String> = savedStateHandle private val playlistUrl: StateFlow<String> = savedStateHandle
.getStateFlow(PlaylistConfigurationNavigation.TYPE_PLAYLIST_URL, "") .getStateFlow(PlaylistConfigurationNavigation.TYPE_PLAYLIST_URL, "")
internal val playlist: StateFlow<Playlist?> = playlistUrl.flatMapLatest { val playlist: StateFlow<Playlist?> = playlistUrl.flatMapLatest {
playlistRepository.observe(it) playlistRepository.observe(it)
} }
.stateIn( .stateIn(
@ -62,7 +62,7 @@ class PlaylistConfigurationViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L) started = SharingStarted.WhileSubscribed(5_000L)
) )
internal val xtreamUserInfo: StateFlow<Resource<XtreamInfo.UserInfo>> = val xtreamUserInfo: StateFlow<Resource<XtreamInfo.UserInfo>> =
playlist.map { playlist -> playlist.map { playlist ->
playlist ?: return@map null playlist ?: return@map null
if (playlist.source != DataSource.Xtream) return@map null if (playlist.source != DataSource.Xtream) return@map null
@ -81,7 +81,7 @@ class PlaylistConfigurationViewModel @Inject constructor(
started = SharingStarted.Lazily started = SharingStarted.Lazily
) )
internal val manifest: StateFlow<EpgManifest> = combine( val manifest: StateFlow<EpgManifest> = combine(
playlistRepository.observeAllEpgs(), playlistRepository.observeAllEpgs(),
playlist playlist
) { epgs, playlist -> ) { epgs, playlist ->
@ -93,7 +93,7 @@ class PlaylistConfigurationViewModel @Inject constructor(
started = SharingStarted.Lazily, started = SharingStarted.Lazily,
initialValue = emptyMap() initialValue = emptyMap()
) )
internal val subscribingOrRefreshingWorkInfo: StateFlow<WorkInfo?> = workManager val subscribingOrRefreshingWorkInfo: StateFlow<WorkInfo?> = workManager
.getWorkInfosFlow( .getWorkInfosFlow(
WorkQuery.fromStates( WorkQuery.fromStates(
WorkInfo.State.RUNNING, WorkInfo.State.RUNNING,
@ -114,7 +114,7 @@ class PlaylistConfigurationViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L) started = SharingStarted.WhileSubscribed(5_000L)
) )
internal val expired: StateFlow<LocalDateTime?> = playlistUrl val expired: StateFlow<LocalDateTime?> = playlistUrl
.flatMapLatest { playlistUrl -> .flatMapLatest { playlistUrl ->
programmeRepository.observeProgrammeRange(playlistUrl) programmeRepository.observeProgrammeRange(playlistUrl)
} }
@ -131,39 +131,39 @@ class PlaylistConfigurationViewModel @Inject constructor(
) )
internal fun onUpdatePlaylistTitle(title: String) { fun onUpdatePlaylistTitle(title: String) {
val playlistUrl = playlistUrl.value val playlistUrl = playlistUrl.value
viewModelScope.launch { viewModelScope.launch {
playlistRepository.onUpdatePlaylistTitle(playlistUrl, title) playlistRepository.onUpdatePlaylistTitle(playlistUrl, title)
} }
} }
internal fun onUpdatePlaylistUserAgent(userAgent: String?) { fun onUpdatePlaylistUserAgent(userAgent: String?) {
val playlistUrl = playlistUrl.value val playlistUrl = playlistUrl.value
viewModelScope.launch { viewModelScope.launch {
playlistRepository.onUpdatePlaylistUserAgent(playlistUrl, userAgent) playlistRepository.onUpdatePlaylistUserAgent(playlistUrl, userAgent)
} }
} }
internal fun onUpdateEpgPlaylist(usecase: PlaylistRepository.EpgPlaylistUseCase) { fun onUpdateEpgPlaylist(usecase: PlaylistRepository.EpgPlaylistUseCase) {
viewModelScope.launch { viewModelScope.launch {
playlistRepository.onUpdateEpgPlaylist(usecase) playlistRepository.onUpdateEpgPlaylist(usecase)
} }
} }
internal fun onUpdatePlaylistAutoRefreshProgrammes() { fun onUpdatePlaylistAutoRefreshProgrammes() {
val playlistUrl = playlistUrl.value val playlistUrl = playlistUrl.value
viewModelScope.launch { viewModelScope.launch {
playlistRepository.onUpdatePlaylistAutoRefreshProgrammes(playlistUrl) playlistRepository.onUpdatePlaylistAutoRefreshProgrammes(playlistUrl)
} }
} }
internal fun onSyncProgrammes() { fun onSyncProgrammes() {
val playlistUrl = playlistUrl.value val playlistUrl = playlistUrl.value
SubscriptionWorker.epg(workManager, playlistUrl, true) SubscriptionWorker.epg(workManager, playlistUrl, true)
} }
internal fun onCancelSyncProgrammes() { fun onCancelSyncProgrammes() {
val workInfo = subscribingOrRefreshingWorkInfo.value val workInfo = subscribingOrRefreshingWorkInfo.value
workInfo?.id?.let { workManager.cancelWorkById(it) } workInfo?.id?.let { workManager.cancelWorkById(it) }
} }

View File

@ -23,13 +23,10 @@ android {
dependencies { dependencies {
implementation(project(":core")) implementation(project(":core"))
implementation(project(":ui")) implementation(project(":data"))
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
// for m2 BackdropScaffold only
implementation("androidx.compose.material:material")
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.lifecycle.runtime.compose)

View File

@ -0,0 +1,27 @@
package com.m3u.business.playlist
import android.net.Uri
import androidx.navigation.NavController
import androidx.navigation.NavOptions
const val PLAYLIST_ROUTE_PATH = "playlist_route"
object PlaylistNavigation {
const val TYPE_URL = "url"
const val PLAYLIST_ROUTE =
"$PLAYLIST_ROUTE_PATH?$TYPE_URL={$TYPE_URL}"
internal fun createPlaylistRoute(url: String): String {
return "$PLAYLIST_ROUTE_PATH?$TYPE_URL=$url"
}
}
fun NavController.navigateToPlaylist(
playlistUrl: String,
navOptions: NavOptions? = null,
) {
val encodedUrl = Uri.encode(playlistUrl)
val route = PlaylistNavigation.createPlaylistRoute(encodedUrl)
this.navigate(route, navOptions)
}

View File

@ -52,9 +52,7 @@ import com.m3u.data.service.Messager
import com.m3u.data.service.PlayerManager import com.m3u.data.service.PlayerManager
import com.m3u.data.worker.SubscriptionWorker import com.m3u.data.worker.SubscriptionWorker
import com.m3u.business.playlist.PlaylistMessage.ChannelCoverSaved import com.m3u.business.playlist.PlaylistMessage.ChannelCoverSaved
import com.m3u.business.playlist.navigation.PlaylistNavigation import com.m3u.core.wrapper.Sort
import com.m3u.ui.Sort
import com.m3u.ui.toCommonSort
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
@ -99,10 +97,10 @@ class PlaylistViewModel @Inject constructor(
) : ViewModel() { ) : ViewModel() {
private val logger = delegate.install(Profiles.VIEWMODEL_PLAYLIST) private val logger = delegate.install(Profiles.VIEWMODEL_PLAYLIST)
internal val playlistUrl: StateFlow<String> = savedStateHandle val playlistUrl: StateFlow<String> = savedStateHandle
.getStateFlow(PlaylistNavigation.TYPE_URL, "") .getStateFlow(PlaylistNavigation.TYPE_URL, "")
internal val playlist: StateFlow<Playlist?> = playlistUrl.flatMapLatest { val playlist: StateFlow<Playlist?> = playlistUrl.flatMapLatest {
playlistRepository.observe(it) playlistRepository.observe(it)
} }
.stateIn( .stateIn(
@ -111,7 +109,7 @@ class PlaylistViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L) started = SharingStarted.WhileSubscribed(5_000L)
) )
internal val zapping: StateFlow<Channel?> = combine( val zapping: StateFlow<Channel?> = combine(
snapshotFlow { preferences.zappingMode }, snapshotFlow { preferences.zappingMode },
playerManager.channel, playerManager.channel,
playlistUrl.flatMapLatest { channelRepository.observeAllByPlaylistUrl(it) } playlistUrl.flatMapLatest { channelRepository.observeAllByPlaylistUrl(it) }
@ -125,7 +123,7 @@ class PlaylistViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000) started = SharingStarted.WhileSubscribed(5_000)
) )
internal val subscribingOrRefreshing: StateFlow<Boolean> = workManager val subscribingOrRefreshing: StateFlow<Boolean> = workManager
.getWorkInfosFlow( .getWorkInfosFlow(
WorkQuery.fromStates( WorkQuery.fromStates(
WorkInfo.State.RUNNING, WorkInfo.State.RUNNING,
@ -146,20 +144,20 @@ class PlaylistViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5000) started = SharingStarted.WhileSubscribed(5000)
) )
internal fun refresh() { fun refresh() {
val url = playlistUrl.value val url = playlistUrl.value
viewModelScope.launch { viewModelScope.launch {
playlistRepository.refresh(url) playlistRepository.refresh(url)
} }
} }
internal fun favourite(id: Int) { fun favourite(id: Int) {
viewModelScope.launch { viewModelScope.launch {
channelRepository.favouriteOrUnfavourite(id) channelRepository.favouriteOrUnfavourite(id)
} }
} }
internal fun savePicture(id: Int) { fun savePicture(id: Int) {
viewModelScope.launch { viewModelScope.launch {
val channel = channelRepository.get(id) val channel = channelRepository.get(id)
if (channel == null) { if (channel == null) {
@ -188,7 +186,7 @@ class PlaylistViewModel @Inject constructor(
} }
} }
internal fun hide(id: Int) { fun hide(id: Int) {
viewModelScope.launch { viewModelScope.launch {
val channel = channelRepository.get(id) val channel = channelRepository.get(id)
if (channel == null) { if (channel == null) {
@ -199,7 +197,7 @@ class PlaylistViewModel @Inject constructor(
} }
} }
internal fun createShortcut(context: Context, id: Int) { fun createShortcut(context: Context, id: Int) {
val shortcutId = "channel_$id" val shortcutId = "channel_$id"
viewModelScope.launch { viewModelScope.launch {
val channel = channelRepository.get(id) ?: return@launch val channel = channelRepository.get(id) ?: return@launch
@ -227,7 +225,7 @@ class PlaylistViewModel @Inject constructor(
} }
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")
internal fun createTvRecommend(activityContext: Context, id: Int) { fun createTvRecommend(activityContext: Context, id: Int) {
val channelInternalProviderId = "M3U" val channelInternalProviderId = "M3U"
val programInternalProviderId = "Program_$id" val programInternalProviderId = "Program_$id"
val contentResolver = activityContext.contentResolver val contentResolver = activityContext.contentResolver
@ -301,13 +299,13 @@ class PlaylistViewModel @Inject constructor(
} }
} }
internal suspend fun getProgrammeCurrently(channelId: Int): Programme? { suspend fun getProgrammeCurrently(channelId: Int): Programme? {
return programmeRepository.getProgrammeCurrently(channelId) return programmeRepository.getProgrammeCurrently(channelId)
} }
private val sortIndex: MutableStateFlow<Int> = MutableStateFlow(0) private val sortIndex: MutableStateFlow<Int> = MutableStateFlow(0)
internal val sort: StateFlow<Sort> = sortIndex val sort: StateFlow<Sort> = sortIndex
.map { Sort.entries[it] } .map { Sort.entries[it] }
.stateIn( .stateIn(
scope = viewModelScope, scope = viewModelScope,
@ -315,12 +313,12 @@ class PlaylistViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L) started = SharingStarted.WhileSubscribed(5_000L)
) )
internal fun sort(sort: Sort) { fun sort(sort: Sort) {
sortIndex.value = Sort.entries.indexOf(sort).coerceAtLeast(0) sortIndex.value = Sort.entries.indexOf(sort).coerceAtLeast(0)
} }
internal val query = MutableStateFlow("") val query = MutableStateFlow("")
internal val scrollUp: MutableStateFlow<Event<Unit>> = MutableStateFlow(handledEvent()) val scrollUp: MutableStateFlow<Event<Unit>> = MutableStateFlow(handledEvent())
@Immutable @Immutable
data class ChannelParameters( data class ChannelParameters(
@ -354,7 +352,7 @@ class PlaylistViewModel @Inject constructor(
started = SharingStarted.Lazily started = SharingStarted.Lazily
) )
internal val channels: StateFlow<List<CategoryWithChannels>> = combine( val channels: StateFlow<List<CategoryWithChannels>> = combine(
playlistUrl, playlistUrl,
categories, categories,
query, sort query, sort
@ -376,7 +374,7 @@ class PlaylistViewModel @Inject constructor(
playlistUrl, playlistUrl,
"", "",
query, query,
sort.toCommonSort() sort
) )
} }
.flow .flow
@ -392,7 +390,7 @@ class PlaylistViewModel @Inject constructor(
playlistUrl, playlistUrl,
category, category,
query, query,
sort.toCommonSort() sort
) )
} }
.flow .flow
@ -407,7 +405,7 @@ class PlaylistViewModel @Inject constructor(
started = SharingStarted.Lazily started = SharingStarted.Lazily
) )
internal val pinnedCategories: StateFlow<List<String>> = playlist val pinnedCategories: StateFlow<List<String>> = playlist
.map { it?.pinnedCategories ?: emptyList() } .map { it?.pinnedCategories ?: emptyList() }
.flowOn(ioDispatcher) .flowOn(ioDispatcher)
@ -417,21 +415,21 @@ class PlaylistViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L) started = SharingStarted.WhileSubscribed(5_000L)
) )
internal fun pinOrUnpinCategory(category: String) { fun pinOrUnpinCategory(category: String) {
val currentPlaylistUrl = playlistUrl.value val currentPlaylistUrl = playlistUrl.value
viewModelScope.launch { viewModelScope.launch {
playlistRepository.pinOrUnpinCategory(currentPlaylistUrl, category) playlistRepository.pinOrUnpinCategory(currentPlaylistUrl, category)
} }
} }
internal fun hideCategory(category: String) { fun hideCategory(category: String) {
val currentPlaylistUrl = playlistUrl.value val currentPlaylistUrl = playlistUrl.value
viewModelScope.launch { viewModelScope.launch {
playlistRepository.hideOrUnhideCategory(currentPlaylistUrl, category) playlistRepository.hideOrUnhideCategory(currentPlaylistUrl, category)
} }
} }
internal fun setup( fun setup(
channelId: Int, channelId: Int,
onPlayMediaCommand: (MediaCommand) -> Unit onPlayMediaCommand: (MediaCommand) -> Unit
) { ) {
@ -448,10 +446,10 @@ class PlaylistViewModel @Inject constructor(
} }
} }
internal val series = MutableStateFlow<Channel?>(null) val series = MutableStateFlow<Channel?>(null)
internal val seriesReplay = MutableStateFlow(0) val seriesReplay = MutableStateFlow(0)
internal val episodes: StateFlow<Resource<List<XtreamChannelInfo.Episode>>> = series val episodes: StateFlow<Resource<List<XtreamChannelInfo.Episode>>> = series
.combine(seriesReplay) { series, _ -> series } .combine(seriesReplay) { series, _ -> series }
.flatMapLatest { series -> .flatMapLatest { series ->
if (series == null) flow {} if (series == null) flow {}

View File

@ -21,7 +21,7 @@ android {
dependencies { dependencies {
implementation(project(":core")) implementation(project(":core"))
implementation(project(":ui")) implementation(project(":data"))
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
@ -35,8 +35,4 @@ dependencies {
implementation(libs.androidx.work.runtime.ktx) implementation(libs.androidx.work.runtime.ktx)
ksp(libs.androidx.hilt.compiler) ksp(libs.androidx.hilt.compiler)
implementation(libs.androidx.hilt.work) implementation(libs.androidx.hilt.work)
implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.material3.adaptive.navigation)
implementation(libs.androidx.compose.material3.adaptive.layout)
} }

View File

@ -1,6 +1,6 @@
package com.m3u.business.setting package com.m3u.business.setting
internal enum class BackingUpAndRestoringState { enum class BackingUpAndRestoringState {
NONE, BACKING_UP, RESTORING, BOTH; NONE, BACKING_UP, RESTORING, BOTH;
companion object { companion object {

View File

@ -68,7 +68,7 @@ class SettingViewModel @Inject constructor(
) : ViewModel() { ) : ViewModel() {
private val logger = delegate.install(Profiles.VIEWMODEL_SETTING) private val logger = delegate.install(Profiles.VIEWMODEL_SETTING)
internal val epgs: StateFlow<List<Playlist>> = playlistRepository val epgs: StateFlow<List<Playlist>> = playlistRepository
.observeAllEpgs() .observeAllEpgs()
.stateIn( .stateIn(
scope = viewModelScope, scope = viewModelScope,
@ -76,7 +76,7 @@ class SettingViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L) started = SharingStarted.WhileSubscribed(5_000L)
) )
internal val hiddenChannels: StateFlow<List<Channel>> = channelRepository val hiddenChannels: StateFlow<List<Channel>> = channelRepository
.observeAllHidden() .observeAllHidden()
.stateIn( .stateIn(
scope = viewModelScope, scope = viewModelScope,
@ -84,7 +84,7 @@ class SettingViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L) started = SharingStarted.WhileSubscribed(5_000L)
) )
internal val hiddenCategoriesWithPlaylists: StateFlow<List<Pair<Playlist, String>>> = val hiddenCategoriesWithPlaylists: StateFlow<List<Pair<Playlist, String>>> =
playlistRepository playlistRepository
.observeAll() .observeAll()
.map { playlists -> .map { playlists ->
@ -99,13 +99,13 @@ class SettingViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L) started = SharingStarted.WhileSubscribed(5_000L)
) )
internal fun onUnhidePlaylistCategory(playlistUrl: String, group: String) { fun onUnhidePlaylistCategory(playlistUrl: String, group: String) {
viewModelScope.launch { viewModelScope.launch {
playlistRepository.hideOrUnhideCategory(playlistUrl, group) playlistRepository.hideOrUnhideCategory(playlistUrl, group)
} }
} }
internal val colorSchemes: StateFlow<List<ColorScheme>> = combine( val colorSchemes: StateFlow<List<ColorScheme>> = combine(
colorSchemeDao.observeAll().catch { emit(emptyList()) }, colorSchemeDao.observeAll().catch { emit(emptyList()) },
snapshotFlow { preferences.followSystemTheme } snapshotFlow { preferences.followSystemTheme }
) { all, followSystemTheme -> if (followSystemTheme) all.filter { !it.isDark } else all } ) { all, followSystemTheme -> if (followSystemTheme) all.filter { !it.isDark } else all }
@ -116,7 +116,7 @@ class SettingViewModel @Inject constructor(
initialValue = emptyList() initialValue = emptyList()
) )
internal fun onClipboard(url: String) { fun onClipboard(url: String) {
val title = run { val title = run {
val filePath = url.split("/") val filePath = url.split("/")
val fileSplit = filePath.lastOrNull()?.split(".") ?: emptyList() val fileSplit = filePath.lastOrNull()?.split(".") ?: emptyList()
@ -137,7 +137,7 @@ class SettingViewModel @Inject constructor(
} }
} }
internal fun onUnhideChannel(channelId: Int) { fun onUnhideChannel(channelId: Int) {
val hidden = hiddenChannels.value.find { it.id == channelId } val hidden = hiddenChannels.value.find { it.id == channelId }
if (hidden != null) { if (hidden != null) {
viewModelScope.launch { viewModelScope.launch {
@ -146,7 +146,7 @@ class SettingViewModel @Inject constructor(
} }
} }
internal fun subscribe() { fun subscribe() {
val title = titleState.value val title = titleState.value
val url = urlState.value val url = urlState.value
val uri = uriState.value val uri = uriState.value
@ -237,7 +237,7 @@ class SettingViewModel @Inject constructor(
resetAllInputs() resetAllInputs()
} }
internal val backingUpOrRestoring: StateFlow<BackingUpAndRestoringState> = workManager val backingUpOrRestoring: StateFlow<BackingUpAndRestoringState> = workManager
.getWorkInfosFlow( .getWorkInfosFlow(
WorkQuery.fromStates( WorkQuery.fromStates(
WorkInfo.State.RUNNING, WorkInfo.State.RUNNING,
@ -266,7 +266,7 @@ class SettingViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5000) started = SharingStarted.WhileSubscribed(5000)
) )
internal fun backup(uri: Uri) { fun backup(uri: Uri) {
workManager.cancelAllWorkByTag(BackupWorker.TAG) workManager.cancelAllWorkByTag(BackupWorker.TAG)
val request = OneTimeWorkRequestBuilder<BackupWorker>() val request = OneTimeWorkRequestBuilder<BackupWorker>()
.setInputData( .setInputData(
@ -281,7 +281,7 @@ class SettingViewModel @Inject constructor(
messager.emit(SettingMessage.BackingUp) messager.emit(SettingMessage.BackingUp)
} }
internal fun restore(uri: Uri) { fun restore(uri: Uri) {
workManager.cancelAllWorkByTag(RestoreWorker.TAG) workManager.cancelAllWorkByTag(RestoreWorker.TAG)
val request = OneTimeWorkRequestBuilder<RestoreWorker>() val request = OneTimeWorkRequestBuilder<RestoreWorker>()
.setInputData( .setInputData(
@ -296,7 +296,7 @@ class SettingViewModel @Inject constructor(
messager.emit(SettingMessage.Restoring) messager.emit(SettingMessage.Restoring)
} }
internal val cacheSpace: StateFlow<DataUnit> = playerManager val cacheSpace: StateFlow<DataUnit> = playerManager
.cacheSpace .cacheSpace
.map { DataUnit.of(it) } .map { DataUnit.of(it) }
.stateIn( .stateIn(
@ -315,18 +315,18 @@ class SettingViewModel @Inject constructor(
epgState.value = "" epgState.value = ""
} }
internal fun clearCache() { fun clearCache() {
playerManager.clearCache() playerManager.clearCache()
} }
internal fun deleteEpgPlaylist(epgUrl: String) { fun deleteEpgPlaylist(epgUrl: String) {
viewModelScope.launch { viewModelScope.launch {
playlistRepository.deleteEpgPlaylistAndProgrammes(epgUrl) playlistRepository.deleteEpgPlaylistAndProgrammes(epgUrl)
} }
} }
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
internal fun applyColor( fun applyColor(
prev: ColorScheme?, prev: ColorScheme?,
argb: Int, argb: Int,
isDark: Boolean isDark: Boolean
@ -347,24 +347,24 @@ class SettingViewModel @Inject constructor(
} }
} }
internal fun restoreSchemes() { fun restoreSchemes() {
val schemes = ColorSchemeExample.schemes val schemes = ColorSchemeExample.schemes
viewModelScope.launch { viewModelScope.launch {
colorSchemeDao.insertAll(*schemes.toTypedArray()) colorSchemeDao.insertAll(*schemes.toTypedArray())
} }
} }
internal val versionName: String = publisher.versionName val versionName: String = publisher.versionName
internal val versionCode: Int = publisher.versionCode val versionCode: Int = publisher.versionCode
internal val titleState = mutableStateOf("") val titleState = mutableStateOf("")
internal val urlState = mutableStateOf("") val urlState = mutableStateOf("")
internal val uriState = mutableStateOf(Uri.EMPTY) val uriState = mutableStateOf(Uri.EMPTY)
internal val localStorageState = mutableStateOf(false) val localStorageState = mutableStateOf(false)
internal val forTvState = mutableStateOf(false) val forTvState = mutableStateOf(false)
internal val basicUrlState = mutableStateOf("") val basicUrlState = mutableStateOf("")
internal val usernameState = mutableStateOf("") val usernameState = mutableStateOf("")
internal val passwordState = mutableStateOf("") val passwordState = mutableStateOf("")
internal val epgState = mutableStateOf("") val epgState = mutableStateOf("")
internal val selectedState: MutableState<DataSource> = mutableStateOf(DataSource.M3U) val selectedState: MutableState<DataSource> = mutableStateOf(DataSource.M3U)
} }

View File

@ -13,7 +13,6 @@ android {
dependencies { dependencies {
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.google.material)
implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.exoplayer)
} }

View File

@ -13,7 +13,6 @@ android {
dependencies { dependencies {
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.google.material)
implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.exoplayer)
implementation(libs.nextlib.media3.ext) implementation(libs.nextlib.media3.ext)

View File

@ -0,0 +1,14 @@
package com.m3u.core.wrapper
import com.m3u.i18n.R.string
import androidx.annotation.StringRes
import androidx.compose.runtime.Immutable
@Immutable
enum class Sort(@StringRes val resId: Int) {
UNSPECIFIED(string.ui_sort_unspecified),
ASC(string.ui_sort_asc),
DESC(string.ui_sort_desc),
RECENTLY(string.ui_sort_recently),
MIXED(string.ui_sort_mixed)
}

View File

@ -1,6 +1,7 @@
package com.m3u.data.repository.channel package com.m3u.data.repository.channel
import androidx.paging.PagingSource import androidx.paging.PagingSource
import com.m3u.core.wrapper.Sort
import com.m3u.data.database.model.AdjacentChannels import com.m3u.data.database.model.AdjacentChannels
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -35,12 +36,4 @@ interface ChannelRepository {
fun observeAllUnseenFavourites(limit: Duration): Flow<List<Channel>> fun observeAllUnseenFavourites(limit: Duration): Flow<List<Channel>>
fun observeAllFavourite(): Flow<List<Channel>> fun observeAllFavourite(): Flow<List<Channel>>
fun observeAllHidden(): Flow<List<Channel>> fun observeAllHidden(): Flow<List<Channel>>
enum class Sort {
UNSPECIFIED,
ASC,
DESC,
RECENTLY,
MIXED
}
} }

View File

@ -12,7 +12,7 @@ import com.m3u.data.database.dao.PlaylistDao
import com.m3u.data.database.model.AdjacentChannels import com.m3u.data.database.model.AdjacentChannels
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.isSeries import com.m3u.data.database.model.isSeries
import com.m3u.data.repository.channel.ChannelRepository.Sort import com.m3u.core.wrapper.Sort
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.datetime.Clock import kotlinx.datetime.Clock

View File

@ -55,7 +55,6 @@ ktor-server = "3.0.0-beta-1"
mm2d-mmupnp = "3.1.6" mm2d-mmupnp = "3.1.6"
symbolProcessingApi = "2.0.0-1.0.22" symbolProcessingApi = "2.0.0-1.0.22"
profileinstaller = "1.4.1" profileinstaller = "1.4.1"
tvFoundation = "1.0.0-alpha12"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core" }
@ -123,6 +122,7 @@ androidx-tvprovider = { group = "androidx.tvprovider", name = "tvprovider", vers
auto-service-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoServiceAnnotations" } auto-service-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoServiceAnnotations" }
auto-service-ksp = { module = "dev.zacsweers.autoservice:auto-service-ksp", version.ref = "autoServiceKsp" } auto-service-ksp = { module = "dev.zacsweers.autoservice:auto-service-ksp", version.ref = "autoServiceKsp" }
google-accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "google-accompanist" } google-accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "google-accompanist" }
google-dagger-hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "google-dagger" } google-dagger-hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "google-dagger" }
@ -166,14 +166,9 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-api" }
minabox = { group = "io.github.oleksandrbalan", name = "minabox", version.ref = "minabox" } minabox = { group = "io.github.oleksandrbalan", name = "minabox", version.ref = "minabox" }
net-mm2d-mmupnp-mmupnp = { group = "net.mm2d.mmupnp", name = "mmupnp", version.ref = "mm2d-mmupnp" } net-mm2d-mmupnp-mmupnp = { group = "net.mm2d.mmupnp", name = "mmupnp", version.ref = "mm2d-mmupnp" }
androidx-graphics-shapes-android = { group = "androidx.graphics", name = "graphics-shapes-android", version.ref = "androidx-graphics-shapes" } androidx-graphics-shapes = { group = "androidx.graphics", name = "graphics-shapes-android", version.ref = "androidx-graphics-shapes" }
symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "symbolProcessingApi" } symbol-processing-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "symbolProcessingApi" }
androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" } androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "profileinstaller" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-tv-foundation = { group = "androidx.tv", name = "tv-foundation", version.ref = "tvFoundation" }
[plugins] [plugins]
com-android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } com-android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }

3
material/.gitignore vendored
View File

@ -1,3 +0,0 @@
/build
/src/test
/src/androidTest

View File

@ -1,50 +0,0 @@
plugins {
alias(libs.plugins.com.android.library)
alias(libs.plugins.org.jetbrains.kotlin.android)
alias(libs.plugins.compose.compiler)
}
android {
namespace = "com.m3u.material"
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.lifecycle.runtime.ktx)
api(platform(libs.androidx.compose.bom))
api(libs.androidx.compose.foundation)
api(libs.androidx.compose.foundation.layout)
api(libs.androidx.compose.material.icons.extended)
api(libs.androidx.compose.material3)
api(libs.androidx.compose.runtime)
api(libs.androidx.compose.ui.util)
androidTestImplementation(platform(libs.androidx.compose.bom))
api(libs.androidx.compose.ui.tooling.preview)
debugApi(libs.androidx.compose.ui.tooling)
api(libs.androidx.compose.material3.window.size.clazz)
api(libs.androidx.constraintlayout.compose)
api(libs.androidx.navigation.compose)
api(libs.io.coil.kt)
api(libs.io.coil.kt.compose)
implementation(libs.airbnb.lottie.compose)
api(libs.androidx.graphics.shapes.android)
api(libs.google.material)
api(libs.haze)
api(libs.google.accompanist.permissions)
}

View File

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
</manifest>

View File

@ -1,3 +0,0 @@
package com.m3u.material.components.mask
typealias MaskInterceptor = (Boolean) -> Boolean

View File

@ -1,6 +0,0 @@
package com.m3u.material.model
import androidx.compose.runtime.staticCompositionLocalOf
import dev.chrisbanes.haze.HazeState
val LocalHazeState = staticCompositionLocalOf { HazeState() }

View File

@ -18,16 +18,13 @@ rootProject.name = "M3U"
include(":smartphone", ":tv") include(":smartphone", ":tv")
include(":core") include(":core")
include(":data") include(":data")
include(":material")
include(":ui")
include( include(
":business:foryou", ":business:foryou",
":business:favorite", ":business:favorite",
":business:setting", ":business:setting",
":business:playlist", ":business:playlist",
":business:playlist-configuration", ":business:playlist-configuration",
":business:channel", ":business:channel"
":business:crash"
) )
include(":baselineprofile") include(":baselineprofile")
include(":i18n") include(":i18n")

View File

@ -10,6 +10,7 @@ plugins {
alias(libs.plugins.androidx.baselineprofile) alias(libs.plugins.androidx.baselineprofile)
id("kotlin-parcelize") id("kotlin-parcelize")
} }
android { android {
namespace = "com.m3u.smartphone" namespace = "com.m3u.smartphone"
compileSdk = 35 compileSdk = 35
@ -120,38 +121,68 @@ baselineProfile {
dependencies { dependencies {
implementation(project(":core")) implementation(project(":core"))
implementation(project(":ui")) implementation(project(":data"))
// business
implementation(project(":business:foryou")) implementation(project(":business:foryou"))
implementation(project(":business:favorite")) implementation(project(":business:favorite"))
implementation(project(":business:setting")) implementation(project(":business:setting"))
implementation(project(":business:playlist")) implementation(project(":business:playlist"))
implementation(project(":business:channel")) implementation(project(":business:channel"))
implementation(project(":business:playlist-configuration")) implementation(project(":business:playlist-configuration"))
implementation(project(":business:crash")) // baselineprofile
implementation(libs.androidx.profileinstaller) implementation(libs.androidx.profileinstaller)
"baselineProfile"(project(":baselineprofile")) "baselineProfile"(project(":baselineprofile"))
// base
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.startup.runtime) implementation(libs.androidx.startup.runtime)
implementation(libs.androidx.core.splashscreen)
implementation(libs.google.material)
// lifecycle
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.process) implementation(libs.androidx.lifecycle.process)
// work
implementation(libs.androidx.core.splashscreen) implementation(libs.androidx.work.runtime.ktx)
// dagger
implementation(libs.google.dagger.hilt) implementation(libs.google.dagger.hilt)
ksp(libs.google.dagger.hilt.compiler) ksp(libs.google.dagger.hilt.compiler)
implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.work.runtime.ktx)
ksp(libs.androidx.hilt.compiler) ksp(libs.androidx.hilt.compiler)
implementation(libs.androidx.hilt.work) implementation(libs.androidx.hilt.work)
// compose
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.foundation.layout)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.ui.util)
implementation(libs.androidx.navigation.compose)
// compose-material3
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material3.window.size.clazz)
implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.material3.adaptive.navigation)
implementation(libs.androidx.compose.material3.adaptive.layout)
// glance
implementation(libs.androidx.glance.appwidget) implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.glance.material3) implementation(libs.androidx.glance.material3)
// accompanist
implementation(libs.google.accompanist.permissions)
// performance
debugImplementation(libs.squareup.leakcanary) debugImplementation(libs.squareup.leakcanary)
// other
implementation(libs.androidx.graphics.shapes)
implementation(libs.androidx.constraintlayout.compose)
implementation(libs.io.coil.kt)
implementation(libs.io.coil.kt.compose)
implementation(libs.androidx.media3.ui)
implementation(libs.androidx.media3.exoplayer)
implementation(libs.airbnb.lottie.compose)
implementation(libs.minabox)
implementation(libs.net.mm2d.mmupnp.mmupnp)
implementation(libs.haze)
// FIXME: for m2 BackdropScaffold only
implementation("androidx.compose.material:material")
} }

View File

@ -5,7 +5,7 @@ import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration import androidx.work.Configuration
import com.m3u.core.architecture.logger.Logger import com.m3u.core.architecture.logger.Logger
import com.m3u.core.architecture.preferences.Preferences import com.m3u.core.architecture.preferences.Preferences
import com.m3u.business.crash.CrashHandler import com.m3u.smartphone.ui.business.crash.CrashHandler
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject import javax.inject.Inject

View File

@ -9,9 +9,9 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.m3u.smartphone.ui.App import com.m3u.smartphone.ui.App
import com.m3u.smartphone.ui.AppViewModel import com.m3u.smartphone.ui.AppViewModel
import com.m3u.ui.Events.enableDPadReaction import com.m3u.smartphone.ui.common.helper.Helper
import com.m3u.ui.Toolkit import com.m3u.smartphone.ui.common.internal.Events.enableDPadReaction
import com.m3u.ui.helper.Helper import com.m3u.smartphone.ui.common.internal.Toolkit
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint

View File

@ -1,4 +1,4 @@
package com.m3u.ui.util package com.m3u.smartphone
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime

View File

@ -41,7 +41,7 @@ import com.m3u.smartphone.R
import com.m3u.core.Contracts import com.m3u.core.Contracts
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Programme import com.m3u.data.database.model.Programme
import com.m3u.ui.util.TimeUtils.formatEOrSh import com.m3u.smartphone.TimeUtils.formatEOrSh
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone

View File

@ -35,12 +35,12 @@ import com.m3u.smartphone.ui.common.connect.RemoteControlSheetValue
import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.data.tv.model.RemoteDirection import com.m3u.data.tv.model.RemoteDirection
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.common.AppNavHost import com.m3u.smartphone.ui.common.AppNavHost
import com.m3u.smartphone.ui.common.Scaffold import com.m3u.smartphone.ui.common.Scaffold
import com.m3u.ui.Destination import com.m3u.smartphone.ui.material.components.Destination
import com.m3u.ui.FontFamilies import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.ui.SnackHost import com.m3u.smartphone.ui.material.components.SnackHost
@Composable @Composable
fun App( fun App(

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel package com.m3u.smartphone.ui.business.channel
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
@ -71,25 +71,23 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.media3.common.Player import androidx.media3.common.Player
import com.m3u.business.channel.PlayerState
import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.isNotEmpty import com.m3u.core.util.basic.isNotEmpty
import com.m3u.data.database.model.AdjacentChannels import com.m3u.data.database.model.AdjacentChannels
import com.m3u.business.channel.MaskCenterRole.Pause
import com.m3u.business.channel.MaskCenterRole.Play
import com.m3u.business.channel.MaskCenterRole.Replay
import com.m3u.business.channel.components.MaskTextButton
import com.m3u.business.channel.components.PlayerMask
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.components.mask.MaskButton import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.components.mask.MaskCircleButton import com.m3u.smartphone.ui.business.channel.components.MaskTextButton
import com.m3u.material.components.mask.MaskPanel import com.m3u.smartphone.ui.business.channel.components.PlayerMask
import com.m3u.material.components.mask.MaskState import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.material.effects.currentBackStackEntry import com.m3u.smartphone.ui.material.components.Image
import com.m3u.material.ktx.thenIf import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.components.mask.MaskButton
import com.m3u.ui.FontFamilies import com.m3u.smartphone.ui.material.components.mask.MaskCircleButton
import com.m3u.ui.Image import com.m3u.smartphone.ui.material.components.mask.MaskPanel
import com.m3u.ui.helper.LocalHelper import com.m3u.smartphone.ui.material.components.mask.MaskState
import com.m3u.smartphone.ui.material.effects.currentBackStackEntry
import com.m3u.smartphone.ui.material.ktx.thenIf
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -164,10 +162,11 @@ internal fun ChannelMask(
playerState.playState playerState.playState
) { ) {
derivedStateOf { derivedStateOf {
val currentPlayer = playerState.player
when { when {
playerState.player == null -> false currentPlayer == null -> false
!playerState.player.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM) -> false !currentPlayer.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM) -> false
else -> with(playerState.player) { else -> with(currentPlayer) {
!isCurrentMediaItemDynamic && isCurrentMediaItemSeekable !isCurrentMediaItemDynamic && isCurrentMediaItemSeekable
} }
} }
@ -179,8 +178,7 @@ internal fun ChannelMask(
) { ) {
derivedStateOf { derivedStateOf {
playerState.player playerState.player
?.isCommandAvailable(Player.COMMAND_SET_SPEED_AND_PITCH) ?.isCommandAvailable(Player.COMMAND_SET_SPEED_AND_PITCH) == true
?: false
} }
} }
@ -555,14 +553,14 @@ private fun MaskCenterButton(
scaleY = scale scaleY = scale
}, },
icon = when (centerRole) { icon = when (centerRole) {
Play -> Icons.Rounded.PlayArrow MaskCenterRole.Play -> Icons.Rounded.PlayArrow
Pause -> Icons.Rounded.Pause MaskCenterRole.Pause -> Icons.Rounded.Pause
else -> Icons.Rounded.Refresh else -> Icons.Rounded.Refresh
}, },
onClick = when (centerRole) { onClick = when (centerRole) {
Replay -> onRetry MaskCenterRole.Replay -> onRetry
Play -> onPlay MaskCenterRole.Play -> onPlay
Pause -> onPause MaskCenterRole.Pause -> onPause
else -> { else -> {
{} {}
} }

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel package com.m3u.smartphone.ui.business.channel
import android.database.ContentObserver import android.database.ContentObserver
import android.os.Handler import android.os.Handler

View File

@ -1,9 +1,8 @@
package com.m3u.business.channel package com.m3u.smartphone.ui.business.channel
import android.Manifest import android.Manifest
import android.content.Intent import android.content.Intent
import android.graphics.Rect import android.graphics.Rect
import android.net.Uri
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -42,31 +41,33 @@ import com.m3u.core.util.basic.title
import com.m3u.data.database.model.AdjacentChannels import com.m3u.data.database.model.AdjacentChannels
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Playlist import com.m3u.data.database.model.Playlist
import com.m3u.business.channel.components.CoverPlaceholder
import com.m3u.business.channel.components.DlnaDevicesBottomSheet
import com.m3u.business.channel.components.FormatsBottomSheet
import com.m3u.business.channel.components.MaskGestureValuePanel
import com.m3u.business.channel.components.PlayerPanel
import com.m3u.business.channel.components.VerticalGestureArea
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.components.Background import com.m3u.smartphone.ui.material.components.Player
import com.m3u.material.components.PullPanelLayout import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.material.components.PullPanelLayoutValue import com.m3u.smartphone.ui.material.components.rememberPlayerState
import com.m3u.material.components.mask.MaskInterceptor
import com.m3u.material.components.mask.MaskState
import com.m3u.material.components.mask.rememberMaskState
import com.m3u.material.components.mask.toggle
import com.m3u.material.components.rememberPullPanelLayoutState
import com.m3u.material.ktx.checkPermissionOrRationale
import com.m3u.ui.Player
import com.m3u.ui.helper.LocalHelper
import com.m3u.ui.helper.OnPipModeChanged
import com.m3u.ui.rememberPlayerState
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import androidx.core.net.toUri import androidx.core.net.toUri
import com.m3u.business.channel.ChannelViewModel
import com.m3u.business.channel.PlayerState
import com.m3u.smartphone.ui.business.channel.components.CoverPlaceholder
import com.m3u.smartphone.ui.business.channel.components.DlnaDevicesBottomSheet
import com.m3u.smartphone.ui.business.channel.components.FormatsBottomSheet
import com.m3u.smartphone.ui.business.channel.components.MaskGestureValuePanel
import com.m3u.smartphone.ui.business.channel.components.PlayerPanel
import com.m3u.smartphone.ui.business.channel.components.VerticalGestureArea
import com.m3u.smartphone.ui.common.helper.OnPipModeChanged
import com.m3u.smartphone.ui.material.components.Background
import com.m3u.smartphone.ui.material.components.PullPanelLayout
import com.m3u.smartphone.ui.material.components.PullPanelLayoutValue
import com.m3u.smartphone.ui.material.components.mask.MaskInterceptor
import com.m3u.smartphone.ui.material.components.mask.MaskState
import com.m3u.smartphone.ui.material.components.mask.rememberMaskState
import com.m3u.smartphone.ui.material.components.mask.toggle
import com.m3u.smartphone.ui.material.components.rememberPullPanelLayoutState
import com.m3u.smartphone.ui.material.ktx.checkPermissionOrRationale
@Composable @Composable
fun ChannelRoute( fun ChannelRoute(

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel package com.m3u.smartphone.ui.business.channel
internal enum class MaskGesture { internal enum class MaskGesture {
VOLUME, BRIGHTNESS, SPEED VOLUME, BRIGHTNESS, SPEED

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel package com.m3u.smartphone.ui.business.channel
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
@ -9,14 +9,15 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.m3u.business.channel.ChannelViewModel
import com.m3u.core.Contracts import com.m3u.core.Contracts
import com.m3u.data.database.model.isSeries import com.m3u.data.database.model.isSeries
import com.m3u.data.repository.playlist.PlaylistRepository import com.m3u.data.repository.playlist.PlaylistRepository
import com.m3u.data.repository.channel.ChannelRepository import com.m3u.data.repository.channel.ChannelRepository
import com.m3u.data.service.MediaCommand import com.m3u.data.service.MediaCommand
import com.m3u.ui.Events.enableDPadReaction import com.m3u.smartphone.ui.common.helper.Helper
import com.m3u.ui.Toolkit import com.m3u.smartphone.ui.common.internal.Events.enableDPadReaction
import com.m3u.ui.helper.Helper import com.m3u.smartphone.ui.common.internal.Toolkit
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -72,7 +73,7 @@ class PlayerActivity : ComponentActivity() {
val playlist = playlistRepository.get(channel.playlistUrl) val playlist = playlistRepository.get(channel.playlistUrl)
when { when {
// series can not be played from shortcuts // series can not be played from shortcuts
playlist?.isSeries ?: false -> {} playlist?.isSeries == true -> {}
else -> { else -> {
helper.play(MediaCommand.Common(channel.id)) helper.play(MediaCommand.Common(channel.id))
} }

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel.components package com.m3u.smartphone.ui.business.channel.components
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
@ -13,9 +13,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.m3u.material.model.LocalDuration import com.m3u.smartphone.ui.material.model.LocalDuration
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.ui.Image import com.m3u.smartphone.ui.material.components.Image
@Composable @Composable
internal fun CoverPlaceholder( internal fun CoverPlaceholder(

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel.components package com.m3u.smartphone.ui.business.channel.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel.components package com.m3u.smartphone.ui.business.channel.components
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -27,12 +27,12 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.components.CircularProgressIndicator import com.m3u.smartphone.ui.material.components.CircularProgressIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import com.m3u.material.components.mask.MaskState import com.m3u.smartphone.ui.material.components.mask.MaskState
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.ui.UnstableBadge import com.m3u.smartphone.ui.material.components.UnstableBadge
import com.m3u.ui.UnstableValue import com.m3u.smartphone.ui.material.components.UnstableValue
import net.mm2d.upnp.Device import net.mm2d.upnp.Device
@Composable @Composable

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel.components package com.m3u.smartphone.ui.business.channel.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel.components package com.m3u.smartphone.ui.business.channel.components
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@ -36,8 +36,8 @@ import androidx.media3.common.C
import androidx.media3.common.Format import androidx.media3.common.Format
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import com.m3u.material.components.mask.MaskState import com.m3u.smartphone.ui.material.components.mask.MaskState
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel.components package com.m3u.smartphone.ui.business.channel.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -17,8 +17,8 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.ui.MonoText import com.m3u.smartphone.ui.material.components.MonoText
@Composable @Composable
internal fun MaskGestureValuePanel( internal fun MaskGestureValuePanel(

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel.components package com.m3u.smartphone.ui.business.channel.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -16,8 +16,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import com.m3u.material.components.mask.MaskState import com.m3u.smartphone.ui.material.components.mask.MaskState
import com.m3u.ui.FontFamilies import com.m3u.smartphone.ui.material.components.FontFamilies
@Composable @Composable
fun MaskTextButton( fun MaskTextButton(

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel.components package com.m3u.smartphone.ui.business.channel.components
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@ -20,9 +20,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.coerceAtLeast import androidx.compose.ui.unit.coerceAtLeast
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.m3u.material.components.mask.Mask import com.m3u.smartphone.ui.material.components.mask.Mask
import com.m3u.material.components.mask.MaskState import com.m3u.smartphone.ui.material.components.mask.MaskState
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
@Composable @Composable
internal fun PlayerMask( internal fun PlayerMask(

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel.components package com.m3u.smartphone.ui.business.channel.components
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@ -64,20 +64,20 @@ import com.m3u.data.database.model.Episode
import com.m3u.data.database.model.Programme import com.m3u.data.database.model.Programme
import com.m3u.data.database.model.ProgrammeRange import com.m3u.data.database.model.ProgrammeRange
import com.m3u.data.service.MediaCommand import com.m3u.data.service.MediaCommand
import com.m3u.material.components.Background import com.m3u.smartphone.ui.material.components.Background
import com.m3u.material.components.CircularProgressIndicator import com.m3u.smartphone.ui.material.components.CircularProgressIndicator
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import com.m3u.material.effects.BackStackEntry import com.m3u.smartphone.ui.material.effects.BackStackEntry
import com.m3u.material.effects.BackStackHandler import com.m3u.smartphone.ui.material.effects.BackStackHandler
import com.m3u.material.ktx.Edge import com.m3u.smartphone.ui.material.ktx.Edge
import com.m3u.material.ktx.blurEdges import com.m3u.smartphone.ui.material.ktx.blurEdges
import com.m3u.material.ktx.thenIf import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import com.m3u.ui.FontFamilies import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.ui.helper.LocalHelper import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.ui.util.TimeUtils.formatEOrSh import com.m3u.smartphone.TimeUtils.formatEOrSh
import com.m3u.ui.util.TimeUtils.toEOrSh import com.m3u.smartphone.TimeUtils.toEOrSh
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.Instant import kotlinx.datetime.Instant

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel.components package com.m3u.smartphone.ui.business.channel.components
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Spring import androidx.compose.animation.core.Spring
@ -63,12 +63,12 @@ import com.m3u.data.database.model.Programme
import com.m3u.data.database.model.ProgrammeRange import com.m3u.data.database.model.ProgrammeRange
import com.m3u.data.database.model.ProgrammeRange.Companion.HOUR_LENGTH import com.m3u.data.database.model.ProgrammeRange.Companion.HOUR_LENGTH
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import com.m3u.material.ktx.Edge import com.m3u.smartphone.ui.material.ktx.Edge
import com.m3u.material.ktx.blurEdges import com.m3u.smartphone.ui.material.ktx.blurEdges
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.ui.FontFamilies import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.ui.util.TimeUtils.formatEOrSh import com.m3u.smartphone.TimeUtils.formatEOrSh
import com.m3u.ui.util.TimeUtils.toEOrSh import com.m3u.smartphone.TimeUtils.toEOrSh
import eu.wewox.minabox.MinaBox import eu.wewox.minabox.MinaBox
import eu.wewox.minabox.MinaBoxItem import eu.wewox.minabox.MinaBoxItem
import eu.wewox.minabox.MinaBoxScrollDirection import eu.wewox.minabox.MinaBoxScrollDirection

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel.components package com.m3u.smartphone.ui.business.channel.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@ -10,8 +10,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.m3u.business.channel.ChannelMaskUtils.detectVerticalGesture import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.material.ktx.thenIf import com.m3u.smartphone.ui.business.channel.ChannelMaskUtils.detectVerticalGesture
@Composable @Composable
internal fun VerticalGestureArea( internal fun VerticalGestureArea(

View File

@ -0,0 +1,33 @@
package com.m3u.smartphone.ui.business.configuration
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.PaddingValues
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.m3u.business.playlist.configuration.PlaylistConfigurationNavigation
fun NavGraphBuilder.playlistConfigurationScreen(
contentPadding: PaddingValues = PaddingValues(),
) {
composable(
route = PlaylistConfigurationNavigation.PLAYLIST_CONFIGURATION_ROUTE,
arguments = listOf(
navArgument(PlaylistConfigurationNavigation.TYPE_PLAYLIST_URL) {
type = NavType.StringType
}
),
enterTransition = { slideInVertically { it } },
exitTransition = { fadeOut() },
popEnterTransition = { fadeIn() },
popExitTransition = { slideOutVertically { it } }
) {
PlaylistConfigurationRoute(
contentPadding = contentPadding
)
}
}

View File

@ -1,4 +1,4 @@
package com.m3u.business.playlist.configuration package com.m3u.smartphone.ui.business.configuration
import android.Manifest import android.Manifest
import android.content.Intent import android.content.Intent
@ -26,6 +26,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Save import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
@ -43,6 +44,8 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.m3u.business.playlist.configuration.EpgManifest
import com.m3u.business.playlist.configuration.PlaylistConfigurationViewModel
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
import com.m3u.core.wrapper.Resource import com.m3u.core.wrapper.Resource
import com.m3u.data.database.model.DataSource import com.m3u.data.database.model.DataSource
@ -50,19 +53,18 @@ import com.m3u.data.database.model.Playlist
import com.m3u.data.database.model.epgUrlsOrXtreamXmlUrl import com.m3u.data.database.model.epgUrlsOrXtreamXmlUrl
import com.m3u.data.parser.xtream.XtreamInfo import com.m3u.data.parser.xtream.XtreamInfo
import com.m3u.data.repository.playlist.PlaylistRepository import com.m3u.data.repository.playlist.PlaylistRepository
import com.m3u.business.playlist.configuration.components.AutoSyncProgrammesButton
import com.m3u.business.playlist.configuration.components.EpgManifestGallery
import com.m3u.business.playlist.configuration.components.SyncProgrammesButton
import com.m3u.business.playlist.configuration.components.XtreamPanel
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.components.Background import com.m3u.smartphone.ui.material.components.Background
import androidx.compose.material3.Icon import com.m3u.smartphone.ui.material.components.PlaceholderField
import com.m3u.material.components.PlaceholderField import com.m3u.smartphone.ui.material.ktx.checkPermissionOrRationale
import com.m3u.material.ktx.checkPermissionOrRationale import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.material.model.LocalHazeState import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.business.configuration.components.AutoSyncProgrammesButton
import com.m3u.ui.helper.LocalHelper import com.m3u.smartphone.ui.business.configuration.components.EpgManifestGallery
import com.m3u.ui.helper.Metadata import com.m3u.smartphone.ui.business.configuration.components.SyncProgrammesButton
import com.m3u.smartphone.ui.business.configuration.components.XtreamPanel
import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.smartphone.ui.common.helper.Metadata
import dev.chrisbanes.haze.HazeStyle import dev.chrisbanes.haze.HazeStyle
import dev.chrisbanes.haze.haze import dev.chrisbanes.haze.haze
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime

View File

@ -1,4 +1,4 @@
package com.m3u.business.playlist.configuration.components package com.m3u.smartphone.ui.business.configuration.components
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -15,9 +15,9 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
import com.m3u.i18n.R import com.m3u.i18n.R
import com.m3u.material.components.SelectionsDefaults import com.m3u.smartphone.ui.material.components.SelectionsDefaults
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
@Composable @Composable
internal fun AutoSyncProgrammesButton( internal fun AutoSyncProgrammesButton(

View File

@ -1,4 +1,4 @@
package com.m3u.business.playlist.configuration.components package com.m3u.smartphone.ui.business.configuration.components
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
@ -21,14 +21,14 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.m3u.business.playlist.configuration.EpgManifest
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
import com.m3u.data.database.model.Playlist import com.m3u.data.database.model.Playlist
import com.m3u.data.repository.playlist.PlaylistRepository import com.m3u.data.repository.playlist.PlaylistRepository
import com.m3u.business.playlist.configuration.EpgManifest
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.components.SelectionsDefaults import com.m3u.smartphone.ui.material.components.SelectionsDefaults
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
internal fun LazyListScope.EpgManifestGallery( internal fun LazyListScope.EpgManifestGallery(
playlistUrl: String, playlistUrl: String,

View File

@ -1,4 +1,4 @@
package com.m3u.business.playlist.configuration.components package com.m3u.smartphone.ui.business.configuration.components
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@ -22,10 +22,10 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.components.CircularProgressIndicator import com.m3u.smartphone.ui.material.components.CircularProgressIndicator
import com.m3u.material.components.SelectionsDefaults import com.m3u.smartphone.ui.material.components.SelectionsDefaults
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
@Composable @Composable

View File

@ -1,4 +1,4 @@
package com.m3u.business.playlist.configuration.components package com.m3u.smartphone.ui.business.configuration.components
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -23,10 +23,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.m3u.core.wrapper.Resource import com.m3u.core.wrapper.Resource
import com.m3u.data.parser.xtream.XtreamInfo import com.m3u.data.parser.xtream.XtreamInfo
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.ui.Badge import com.m3u.smartphone.ui.material.components.Badge
import com.m3u.ui.FontFamilies import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.ui.TextBadge import com.m3u.smartphone.ui.material.components.TextBadge
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
@Composable @Composable

View File

@ -1,13 +1,13 @@
package com.m3u.business.crash package com.m3u.smartphone.ui.business.crash
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import com.m3u.ui.Events.enableDPadReaction import com.m3u.smartphone.ui.common.helper.Helper
import com.m3u.ui.Toolkit import com.m3u.smartphone.ui.common.internal.Events.enableDPadReaction
import com.m3u.ui.helper.Helper import com.m3u.smartphone.ui.common.internal.Toolkit
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint

View File

@ -1,4 +1,4 @@
package com.m3u.business.crash package com.m3u.smartphone.ui.business.crash
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -10,9 +10,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.m3u.business.crash.navigation.Destination import com.m3u.smartphone.ui.business.crash.navigation.Destination
import com.m3u.business.crash.screen.detail.DetailScreen import com.m3u.smartphone.ui.business.crash.screen.detail.DetailScreen
import com.m3u.business.crash.screen.list.ListScreen import com.m3u.smartphone.ui.business.crash.screen.list.ListScreen
@Composable @Composable
internal fun CrashApp() { internal fun CrashApp() {

View File

@ -1,4 +1,4 @@
package com.m3u.business.crash package com.m3u.smartphone.ui.business.crash
import android.app.AlarmManager import android.app.AlarmManager
import android.app.PendingIntent import android.app.PendingIntent

View File

@ -1,4 +1,4 @@
package com.m3u.business.crash.components package com.m3u.smartphone.ui.business.crash.components
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Adb import androidx.compose.material.icons.rounded.Adb
@ -9,7 +9,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import com.m3u.material.components.Background import com.m3u.smartphone.ui.material.components.Background
import java.io.File import java.io.File
@Composable @Composable

View File

@ -1,4 +1,4 @@
package com.m3u.business.crash.navigation package com.m3u.smartphone.ui.business.crash.navigation
internal sealed class Destination { internal sealed class Destination {
data object List : Destination() data object List : Destination()

View File

@ -1,4 +1,4 @@
package com.m3u.business.crash.screen.detail package com.m3u.smartphone.ui.business.crash.screen.detail
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@ -18,10 +18,10 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.m3u.material.components.Background import com.m3u.smartphone.ui.material.components.Background
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.ui.MonoText import com.m3u.smartphone.ui.material.components.MonoText
@Composable @Composable
internal fun DetailScreen( internal fun DetailScreen(

View File

@ -1,4 +1,4 @@
package com.m3u.business.crash.screen.detail package com.m3u.smartphone.ui.business.crash.screen.detail
import android.net.Uri import android.net.Uri
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue

View File

@ -1,4 +1,4 @@
package com.m3u.business.crash.screen.list package com.m3u.smartphone.ui.business.crash.screen.list
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -7,9 +7,9 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.m3u.business.crash.components.FileItem import com.m3u.smartphone.ui.material.components.Background
import com.m3u.business.crash.screen.list.navigation.NavigateToDetail import com.m3u.smartphone.ui.business.crash.components.FileItem
import com.m3u.material.components.Background import com.m3u.smartphone.ui.business.crash.screen.list.navigation.NavigateToDetail
@Composable @Composable
internal fun ListScreen( internal fun ListScreen(

View File

@ -1,4 +1,4 @@
package com.m3u.business.crash.screen.list package com.m3u.smartphone.ui.business.crash.screen.list
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.m3u.core.architecture.FileProvider import com.m3u.core.architecture.FileProvider

View File

@ -0,0 +1,3 @@
package com.m3u.smartphone.ui.business.crash.screen.list.navigation
typealias NavigateToDetail = (path: String) -> Unit

View File

@ -1,4 +1,4 @@
package com.m3u.business.favorite package com.m3u.smartphone.ui.business.favorite
import android.content.res.Configuration import android.content.res.Configuration
import android.view.KeyEvent import android.view.KeyEvent
@ -24,25 +24,26 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.m3u.business.favorite.FavouriteViewModel
import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
import com.m3u.core.wrapper.Resource import com.m3u.core.wrapper.Resource
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.isSeries import com.m3u.data.database.model.isSeries
import com.m3u.data.service.MediaCommand import com.m3u.data.service.MediaCommand
import com.m3u.business.favorite.components.FavouriteGallery import com.m3u.core.wrapper.Sort
import com.m3u.i18n.R import com.m3u.i18n.R
import com.m3u.material.ktx.interceptVolumeEvent import com.m3u.smartphone.ui.material.ktx.interceptVolumeEvent
import com.m3u.material.ktx.thenIf import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.material.model.LocalHazeState import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.ui.EpisodesBottomSheet import com.m3u.smartphone.ui.business.favorite.components.FavouriteGallery
import com.m3u.ui.MediaSheet import com.m3u.smartphone.ui.material.components.EpisodesBottomSheet
import com.m3u.ui.MediaSheetValue import com.m3u.smartphone.ui.material.components.MediaSheet
import com.m3u.ui.Sort import com.m3u.smartphone.ui.material.components.MediaSheetValue
import com.m3u.ui.SortBottomSheet import com.m3u.smartphone.ui.material.components.SortBottomSheet
import com.m3u.ui.helper.Action import com.m3u.smartphone.ui.common.helper.Action
import com.m3u.ui.helper.LocalHelper import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.ui.helper.Metadata import com.m3u.smartphone.ui.common.helper.Metadata
import dev.chrisbanes.haze.HazeDefaults import dev.chrisbanes.haze.HazeDefaults
import dev.chrisbanes.haze.haze import dev.chrisbanes.haze.haze
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@ -1,4 +1,4 @@
package com.m3u.business.favorite.components package com.m3u.smartphone.ui.business.favorite.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -18,9 +18,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.m3u.core.wrapper.Resource import com.m3u.core.wrapper.Resource
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import com.m3u.material.components.VerticalDraggableScrollbar import com.m3u.smartphone.ui.material.components.VerticalDraggableScrollbar
import com.m3u.material.ktx.plus import com.m3u.smartphone.ui.material.ktx.plus
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
@Composable @Composable
internal fun FavouriteGallery( internal fun FavouriteGallery(

View File

@ -1,4 +1,4 @@
package com.m3u.business.favorite.components package com.m3u.smartphone.ui.business.favorite.components
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
@ -18,8 +18,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.days

View File

@ -1,4 +1,4 @@
package com.m3u.business.foryou package com.m3u.smartphone.ui.business.foryou
import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.view.KeyEvent import android.view.KeyEvent
@ -31,6 +31,8 @@ import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import com.m3u.business.foryou.ForyouViewModel
import com.m3u.business.foryou.Recommend
import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
import com.m3u.core.wrapper.Resource import com.m3u.core.wrapper.Resource
@ -39,20 +41,19 @@ import com.m3u.data.database.model.Playlist
import com.m3u.data.database.model.PlaylistWithCount import com.m3u.data.database.model.PlaylistWithCount
import com.m3u.data.database.model.isSeries import com.m3u.data.database.model.isSeries
import com.m3u.data.service.MediaCommand import com.m3u.data.service.MediaCommand
import com.m3u.business.foryou.components.HeadlineBackground
import com.m3u.business.foryou.components.PlaylistGallery
import com.m3u.business.foryou.components.recommend.Recommend
import com.m3u.business.foryou.components.recommend.RecommendGallery
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.ktx.composableOf import com.m3u.smartphone.ui.material.ktx.composableOf
import com.m3u.material.ktx.interceptVolumeEvent import com.m3u.smartphone.ui.material.ktx.interceptVolumeEvent
import com.m3u.material.ktx.thenIf import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.ui.EpisodesBottomSheet import com.m3u.smartphone.ui.business.foryou.components.HeadlineBackground
import com.m3u.ui.MediaSheet import com.m3u.smartphone.ui.business.foryou.components.PlaylistGallery
import com.m3u.ui.MediaSheetValue import com.m3u.smartphone.ui.business.foryou.components.recommend.RecommendGallery
import com.m3u.ui.helper.Action import com.m3u.smartphone.ui.material.components.EpisodesBottomSheet
import com.m3u.ui.helper.LocalHelper import com.m3u.smartphone.ui.material.components.MediaSheet
import com.m3u.ui.helper.Metadata import com.m3u.smartphone.ui.material.components.MediaSheetValue
import com.m3u.smartphone.ui.common.helper.Action
import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.smartphone.ui.common.helper.Metadata
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@ -111,7 +112,7 @@ fun ForyouRoute(
coroutineScope.launch { coroutineScope.launch {
val playlist = viewModel.getPlaylist(channel.playlistUrl) val playlist = viewModel.getPlaylist(channel.playlistUrl)
when { when {
playlist?.isSeries ?: false -> { playlist?.isSeries == true -> {
viewModel.series.value = channel viewModel.series.value = channel
} }

View File

@ -1,4 +1,4 @@
package com.m3u.business.foryou.components package com.m3u.smartphone.ui.business.foryou.components
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
@ -26,10 +26,10 @@ import coil.compose.AsyncImage
import coil.request.CachePolicy import coil.request.CachePolicy
import coil.request.ImageRequest import coil.request.ImageRequest
import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.material.transformation.BlurTransformation import com.m3u.smartphone.ui.material.transformation.BlurTransformation
import com.m3u.ui.helper.LocalHelper import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.ui.helper.Metadata import com.m3u.smartphone.ui.common.helper.Metadata
import com.m3u.ui.helper.useRailNav import com.m3u.smartphone.ui.common.helper.useRailNav
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Composable @Composable

View File

@ -1,10 +1,10 @@
package com.m3u.business.foryou.components package com.m3u.smartphone.ui.business.foryou.components
import androidx.annotation.FloatRange import androidx.annotation.FloatRange
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.m3u.business.foryou.R import com.m3u.business.foryou.R
import com.m3u.material.components.ProgressLottie import com.m3u.smartphone.ui.material.components.ProgressLottie
@Composable @Composable
internal fun Loading( internal fun Loading(

View File

@ -1,4 +1,4 @@
package com.m3u.business.foryou.components package com.m3u.smartphone.ui.business.foryou.components
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -31,12 +31,12 @@ import com.m3u.data.database.model.epgUrlsOrXtreamXmlUrl
import com.m3u.data.database.model.fromLocal import com.m3u.data.database.model.fromLocal
import com.m3u.data.database.model.type import com.m3u.data.database.model.type
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.ktx.plus import com.m3u.smartphone.ui.material.ktx.plus
import com.m3u.material.model.LocalHazeState import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.ui.helper.LocalHelper import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.ui.helper.Metadata import com.m3u.smartphone.ui.common.helper.Metadata
import com.m3u.ui.helper.useRailNav import com.m3u.smartphone.ui.common.helper.useRailNav
import dev.chrisbanes.haze.HazeDefaults import dev.chrisbanes.haze.HazeDefaults
import dev.chrisbanes.haze.haze import dev.chrisbanes.haze.haze
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn

View File

@ -1,4 +1,4 @@
package com.m3u.business.foryou.components package com.m3u.smartphone.ui.business.foryou.components
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -26,12 +26,12 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.m3u.material.components.CircularProgressIndicator import com.m3u.smartphone.ui.material.components.CircularProgressIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import com.m3u.ui.Badge import com.m3u.smartphone.ui.material.components.Badge
import com.m3u.ui.FontFamilies import com.m3u.smartphone.ui.material.components.FontFamilies
@Composable @Composable
internal fun PlaylistItem( internal fun PlaylistItem(

View File

@ -1,4 +1,4 @@
package com.m3u.business.foryou.components.recommend package com.m3u.smartphone.ui.business.foryou.components.recommend
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -13,13 +13,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.m3u.business.foryou.Recommend
import com.m3u.core.wrapper.eventOf import com.m3u.core.wrapper.eventOf
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Playlist import com.m3u.data.database.model.Playlist
import com.m3u.material.components.HorizontalPagerIndicator import com.m3u.smartphone.ui.common.internal.Events
import com.m3u.material.ktx.pageOffset import com.m3u.smartphone.ui.material.components.HorizontalPagerIndicator
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.ktx.pageOffset
import com.m3u.ui.Events import com.m3u.smartphone.ui.material.model.LocalSpacing
@Composable @Composable
internal fun RecommendGallery( internal fun RecommendGallery(

View File

@ -1,4 +1,4 @@
package com.m3u.business.foryou.components.recommend package com.m3u.smartphone.ui.business.foryou.components.recommend
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -39,14 +39,15 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp import androidx.compose.ui.util.lerp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.m3u.business.foryou.Recommend
import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.brush.RecommendCardContainerBrush import com.m3u.smartphone.ui.material.brush.RecommendCardContainerBrush
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import com.m3u.ui.FontFamilies import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.ui.createPremiumBrush import com.m3u.smartphone.ui.material.components.createPremiumBrush
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.days

View File

@ -1,40 +1,15 @@
package com.m3u.business.playlist.navigation package com.m3u.smartphone.ui.business.playlist
import android.net.Uri
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.m3u.business.playlist.PlaylistRoute import com.m3u.business.playlist.PlaylistNavigation
private const val PLAYLIST_ROUTE_PATH = "playlist_route"
object PlaylistNavigation {
internal const val TYPE_URL = "url"
const val PLAYLIST_ROUTE =
"$PLAYLIST_ROUTE_PATH?$TYPE_URL={$TYPE_URL}"
internal fun createPlaylistRoute(url: String): String {
return "$PLAYLIST_ROUTE_PATH?$TYPE_URL=$url"
}
}
fun NavController.navigateToPlaylist(
playlistUrl: String,
navOptions: NavOptions? = null,
) {
val encodedUrl = Uri.encode(playlistUrl)
val route = PlaylistNavigation.createPlaylistRoute(encodedUrl)
this.navigate(route, navOptions)
}
fun NavGraphBuilder.playlistScreen( fun NavGraphBuilder.playlistScreen(
navigateToChannel: () -> Unit, navigateToChannel: () -> Unit,

View File

@ -1,6 +1,6 @@
@file:Suppress("UsingMaterialAndMaterial3Libraries") @file:Suppress("UsingMaterialAndMaterial3Libraries")
package com.m3u.business.playlist package com.m3u.smartphone.ui.business.playlist
import android.Manifest import android.Manifest
import android.content.Intent import android.content.Intent
@ -81,6 +81,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.m3u.business.playlist.PlaylistViewModel
import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
import com.m3u.core.wrapper.Event import com.m3u.core.wrapper.Event
@ -92,29 +93,29 @@ import com.m3u.data.database.model.isSeries
import com.m3u.data.database.model.isVod import com.m3u.data.database.model.isVod
import com.m3u.data.database.model.type import com.m3u.data.database.model.type
import com.m3u.data.service.MediaCommand import com.m3u.data.service.MediaCommand
import com.m3u.business.playlist.components.PlaylistTabRow import com.m3u.core.wrapper.Sort
import com.m3u.business.playlist.components.ChannelGallery
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.components.TextField import com.m3u.smartphone.ui.material.components.TextField
import com.m3u.material.ktx.checkPermissionOrRationale import com.m3u.smartphone.ui.material.ktx.checkPermissionOrRationale
import com.m3u.material.ktx.interceptVolumeEvent import com.m3u.smartphone.ui.material.ktx.interceptVolumeEvent
import com.m3u.material.ktx.isAtTop import com.m3u.smartphone.ui.material.ktx.isAtTop
import com.m3u.material.ktx.only import com.m3u.smartphone.ui.material.ktx.only
import com.m3u.material.ktx.split import com.m3u.smartphone.ui.material.ktx.split
import com.m3u.material.ktx.thenIf import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.material.model.LocalHazeState import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.ui.Destination import com.m3u.smartphone.ui.business.playlist.components.ChannelGallery
import com.m3u.ui.EpisodesBottomSheet import com.m3u.smartphone.ui.business.playlist.components.PlaylistTabRow
import com.m3u.ui.EventHandler import com.m3u.smartphone.ui.material.components.Destination
import com.m3u.ui.MediaSheet import com.m3u.smartphone.ui.material.components.EpisodesBottomSheet
import com.m3u.ui.MediaSheetValue import com.m3u.smartphone.ui.material.components.EventHandler
import com.m3u.ui.Sort import com.m3u.smartphone.ui.material.components.MediaSheet
import com.m3u.ui.SortBottomSheet import com.m3u.smartphone.ui.material.components.MediaSheetValue
import com.m3u.ui.helper.Action import com.m3u.smartphone.ui.material.components.SortBottomSheet
import com.m3u.ui.helper.Fob import com.m3u.smartphone.ui.common.helper.Action
import com.m3u.ui.helper.LocalHelper import com.m3u.smartphone.ui.common.helper.Fob
import com.m3u.ui.helper.Metadata import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.smartphone.ui.common.helper.Metadata
import dev.chrisbanes.haze.HazeDefaults import dev.chrisbanes.haze.HazeDefaults
import dev.chrisbanes.haze.haze import dev.chrisbanes.haze.haze
import kotlinx.coroutines.delay import kotlinx.coroutines.delay

View File

@ -1,4 +1,4 @@
package com.m3u.business.playlist.components package com.m3u.smartphone.ui.business.playlist.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -23,10 +23,10 @@ import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Programme import com.m3u.data.database.model.Programme
import com.m3u.business.playlist.PlaylistViewModel import com.m3u.business.playlist.PlaylistViewModel
import com.m3u.material.components.CircularProgressIndicator import com.m3u.smartphone.ui.material.components.CircularProgressIndicator
import com.m3u.material.components.VerticalDraggableScrollbar import com.m3u.smartphone.ui.material.components.VerticalDraggableScrollbar
import com.m3u.material.ktx.plus import com.m3u.smartphone.ui.material.ktx.plus
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
@Composable @Composable
internal fun ChannelGallery( internal fun ChannelGallery(

View File

@ -1,4 +1,4 @@
package com.m3u.business.playlist.components package com.m3u.smartphone.ui.business.playlist.components
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
@ -44,11 +44,11 @@ import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.data.database.model.Programme import com.m3u.data.database.model.Programme
import com.m3u.data.database.model.Channel import com.m3u.data.database.model.Channel
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.components.CircularProgressIndicator import com.m3u.smartphone.ui.material.components.CircularProgressIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import com.m3u.ui.util.TimeUtils.formatEOrSh import com.m3u.smartphone.TimeUtils.formatEOrSh
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone

View File

@ -1,4 +1,4 @@
package com.m3u.business.playlist.components package com.m3u.smartphone.ui.business.playlist.components
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background import androidx.compose.foundation.background
@ -52,14 +52,14 @@ import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import com.m3u.material.effects.BackStackEntry import com.m3u.smartphone.ui.material.effects.BackStackEntry
import com.m3u.material.effects.BackStackHandler import com.m3u.smartphone.ui.material.effects.BackStackHandler
import com.m3u.material.ktx.Edge import com.m3u.smartphone.ui.material.ktx.Edge
import com.m3u.material.ktx.blurEdge import com.m3u.smartphone.ui.material.ktx.blurEdge
import com.m3u.material.ktx.thenIf import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.material.model.LocalHazeState import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import dev.chrisbanes.haze.HazeDefaults import dev.chrisbanes.haze.HazeDefaults
import dev.chrisbanes.haze.haze import dev.chrisbanes.haze.haze

View File

@ -1,4 +1,4 @@
package com.m3u.business.setting package com.m3u.smartphone.ui.business.setting
import android.net.Uri import android.net.Uri
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
@ -28,6 +28,8 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.m3u.business.setting.BackingUpAndRestoringState
import com.m3u.business.setting.SettingViewModel
import com.m3u.core.architecture.preferences.hiltPreferences import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.unit.DataUnit import com.m3u.core.unit.DataUnit
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
@ -35,19 +37,19 @@ import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.ColorScheme import com.m3u.data.database.model.ColorScheme
import com.m3u.data.database.model.DataSource import com.m3u.data.database.model.DataSource
import com.m3u.data.database.model.Playlist import com.m3u.data.database.model.Playlist
import com.m3u.business.setting.components.CanvasBottomSheet
import com.m3u.business.setting.fragments.AppearanceFragment
import com.m3u.business.setting.fragments.OptionalFragment
import com.m3u.business.setting.fragments.SubscriptionsFragment
import com.m3u.business.setting.fragments.preferences.PreferencesFragment
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.model.LocalHazeState import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.ui.Destination import com.m3u.smartphone.ui.business.setting.components.CanvasBottomSheet
import com.m3u.ui.EventHandler import com.m3u.smartphone.ui.business.setting.fragments.AppearanceFragment
import com.m3u.ui.Events import com.m3u.smartphone.ui.business.setting.fragments.OptionalFragment
import com.m3u.ui.SettingDestination import com.m3u.smartphone.ui.business.setting.fragments.SubscriptionsFragment
import com.m3u.ui.helper.Fob import com.m3u.smartphone.ui.business.setting.fragments.preferences.PreferencesFragment
import com.m3u.ui.helper.Metadata import com.m3u.smartphone.ui.material.components.Destination
import com.m3u.smartphone.ui.material.components.EventHandler
import com.m3u.smartphone.ui.material.components.SettingDestination
import com.m3u.smartphone.ui.common.helper.Fob
import com.m3u.smartphone.ui.common.helper.Metadata
import com.m3u.smartphone.ui.common.internal.Events
import dev.chrisbanes.haze.HazeDefaults import dev.chrisbanes.haze.HazeDefaults
import dev.chrisbanes.haze.haze import dev.chrisbanes.haze.haze

View File

@ -1,4 +1,4 @@
package com.m3u.business.setting.components package com.m3u.smartphone.ui.business.setting.components
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -39,10 +39,10 @@ import androidx.compose.ui.unit.dp
import com.m3u.data.database.model.ColorScheme import com.m3u.data.database.model.ColorScheme
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import com.m3u.material.ktx.createScheme import com.m3u.smartphone.ui.material.ktx.createScheme
import com.m3u.material.model.LocalSpacing import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.material.model.SugarColors import com.m3u.smartphone.ui.material.model.SugarColors
import com.m3u.ui.FontFamilies import com.m3u.smartphone.ui.material.components.FontFamilies
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
@Composable @Composable

View File

@ -1,12 +1,12 @@
package com.m3u.business.setting.components package com.m3u.smartphone.ui.business.setting.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.m3u.core.util.basic.title import com.m3u.core.util.basic.title
import com.m3u.material.components.CheckBoxPreference import com.m3u.smartphone.ui.material.components.CheckBoxPreference
import com.m3u.material.components.SwitchPreference import com.m3u.smartphone.ui.material.components.SwitchPreference
@Composable @Composable
fun CheckBoxSharedPreference( fun CheckBoxSharedPreference(

View File

@ -1,4 +1,4 @@
package com.m3u.business.setting.components package com.m3u.smartphone.ui.business.setting.components
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -24,9 +24,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.m3u.data.database.model.DataSource import com.m3u.data.database.model.DataSource
import com.m3u.material.components.ClickableSelection import com.m3u.smartphone.ui.material.components.ClickableSelection
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import com.m3u.material.components.SelectionsDefaults import com.m3u.smartphone.ui.material.components.SelectionsDefaults
@Composable @Composable
internal fun DataSourceSelection( internal fun DataSourceSelection(

View File

@ -1,4 +1,4 @@
package com.m3u.business.setting.components package com.m3u.smartphone.ui.business.setting.components
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.Delete

View File

@ -1,4 +1,4 @@
package com.m3u.business.setting.components package com.m3u.smartphone.ui.business.setting.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem

View File

@ -1,4 +1,4 @@
package com.m3u.business.setting.components package com.m3u.smartphone.ui.business.setting.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem

View File

@ -1,4 +1,4 @@
package com.m3u.business.setting.components package com.m3u.smartphone.ui.business.setting.components
import android.net.Uri import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
@ -17,7 +17,7 @@ import androidx.compose.ui.res.stringResource
import com.m3u.core.util.readFileName import com.m3u.core.util.readFileName
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import com.m3u.material.components.ToggleableSelection import com.m3u.smartphone.ui.material.components.ToggleableSelection
@Composable @Composable
internal fun LocalStorageButton( internal fun LocalStorageButton(

View File

@ -1,4 +1,4 @@
package com.m3u.business.setting.components package com.m3u.smartphone.ui.business.setting.components
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -6,7 +6,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.m3u.i18n.R.string import com.m3u.i18n.R.string
import com.m3u.material.components.ToggleableSelection import com.m3u.smartphone.ui.material.components.ToggleableSelection
@Composable @Composable
internal fun LocalStorageSwitch( internal fun LocalStorageSwitch(

Some files were not shown because too many files have changed in this diff Show More