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 {
implementation(project(":core"))
implementation(project(":ui"))
implementation(project(":data"))
implementation(libs.androidx.core.ktx)
@ -39,6 +39,5 @@ dependencies {
ksp(libs.androidx.hilt.compiler)
implementation(libs.androidx.hilt.work)
implementation(libs.minabox)
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.install
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.Channel
import com.m3u.data.database.model.DataSource
@ -74,15 +75,15 @@ class ChannelViewModel @Inject constructor(
private val logger = delegate.install(Profiles.VIEWMODEL_CHANNEL)
// searched screencast devices
internal var devices by mutableStateOf(emptyList<Device>())
var devices by mutableStateOf(emptyList<Device>())
private val _volume: MutableStateFlow<Float> by lazy {
MutableStateFlow(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) / 100f)
}
internal val volume = _volume.asStateFlow()
val volume = _volume.asStateFlow()
internal val channel: StateFlow<Channel?> = playerManager.channel
internal val playlist: StateFlow<Playlist?> = playerManager.playlist
val channel: StateFlow<Channel?> = playerManager.channel
val playlist: StateFlow<Playlist?> = playerManager.playlist
val adjacentChannels: StateFlow<AdjacentChannels?> = flatmapCombined(
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
if (it.isSeries || it.isVod) return@map false
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 ->
all
.mapValues { (_, formats) -> formats }
.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 group = groups.find { it.type == type } ?: return
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)
}
// channel playing state
internal val playerState: StateFlow<PlayerState> = combine(
val playerState: StateFlow<PlayerState> = combine(
playerManager.player,
playerManager.playbackState,
playerManager.size,
@ -168,14 +169,14 @@ class ChannelViewModel @Inject constructor(
private val _isDevicesVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
// show searching devices dialog or not
internal val isDevicesVisible = _isDevicesVisible.asStateFlow()
val isDevicesVisible = _isDevicesVisible.asStateFlow()
private val _searching: MutableStateFlow<Boolean> = MutableStateFlow(false)
// searching or not
internal val searching = _searching.asStateFlow()
val searching = _searching.asStateFlow()
internal fun openDlnaDevices() {
fun openDlnaDevices() {
viewModelScope.launch {
delay(800.milliseconds)
_searching.value = true
@ -189,7 +190,7 @@ class ChannelViewModel @Inject constructor(
_isDevicesVisible.value = true
}
internal fun closeDlnaDevices() {
fun closeDlnaDevices() {
runCatching {
_searching.value = false
_isDevicesVisible.value = false
@ -213,7 +214,7 @@ class ChannelViewModel @Inject constructor(
private var controlPoint: ControlPoint? = null
internal fun connectDlnaDevice(device: Device) {
fun connectDlnaDevice(device: Device) {
val url = channel.value?.url ?: return
device.findAction(ACTION_SET_AV_TRANSPORT_URI)?.invoke(
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 {
val id = channel.value?.id ?: return@launch
channelRepository.favouriteOrUnfavourite(id)
}
}
internal fun onVolume(target: Float) {
fun onVolume(target: Float) {
_volume.update { target }
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
audioManager.setStreamVolume(
@ -246,7 +247,7 @@ class ChannelViewModel @Inject constructor(
// controlPoint?.setVolume((target * 100).roundToInt(), null)
}
internal fun getPreviousChannel() {
fun getPreviousChannel() {
viewModelScope.launch {
val previousChannelId = adjacentChannels.value?.prevId
if (adjacentChannels.value != null && previousChannelId != null) {
@ -255,7 +256,7 @@ class ChannelViewModel @Inject constructor(
}
}
internal fun getNextChannel() {
fun getNextChannel() {
viewModelScope.launch {
val nextChannelId = adjacentChannels.value?.nextId
if (adjacentChannels.value != null && nextChannelId != null) {
@ -279,7 +280,7 @@ class ChannelViewModel @Inject constructor(
playerManager.pauseOrContinue(isContinued)
}
internal val programmeReminderIds: StateFlow<List<Int>> = workManager.getWorkInfosFlow(
val programmeReminderIds: StateFlow<List<Int>> = workManager.getWorkInfosFlow(
WorkQuery.fromStates(
WorkInfo.State.ENQUEUED
)
@ -319,14 +320,14 @@ class ChannelViewModel @Inject constructor(
// the channels which is in the same category with the current channel
// 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())
Pager(PagingConfig(10)) {
channelRepository.pagingAllByPlaylistUrl(
playlist.url,
channel.value?.category.orEmpty(),
"",
ChannelRepository.Sort.UNSPECIFIED
Sort.UNSPECIFIED
)
}
.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())
val relationId = channel.relationId ?: return@flatMapLatest flowOf(PagingData.empty())
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)
val relationId = channel.relationId ?: return@flatMapLatest flowOf(defaultProgrammeRange)
programmeRepository
@ -377,7 +378,7 @@ class ChannelViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L)
)
internal fun onSpeedUpdated(race: Float) {
fun onSpeedUpdated(race: Float) {
playerManager.updateSpeed(race)
}

View File

@ -6,7 +6,7 @@ import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
@Immutable
internal data class PlayerState(
data class PlayerState(
val playState: @Player.State Int = Player.STATE_IDLE,
val videoSize: Rect = Rect(),
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 {
implementation(project(":core"))
implementation(project(":ui"))
implementation(project(":data"))
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.preferences.Preferences
import com.m3u.core.wrapper.Resource
import com.m3u.core.wrapper.Sort
import com.m3u.core.wrapper.asResource
import com.m3u.core.wrapper.mapResource
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.service.MediaCommand
import com.m3u.data.service.PlayerManager
import com.m3u.ui.Sort
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow
@ -161,9 +161,9 @@ class FavouriteViewModel @Inject constructor(
}
}
internal val series = MutableStateFlow<Channel?>(null)
internal val seriesReplay = MutableStateFlow(0)
internal val episodes: StateFlow<Resource<List<XtreamChannelInfo.Episode>>> = series
val series = MutableStateFlow<Channel?>(null)
val seriesReplay = MutableStateFlow(0)
val episodes: StateFlow<Resource<List<XtreamChannelInfo.Episode>>> = series
.combine(seriesReplay) { series, _ -> series }
.flatMapLatest { series ->
if (series == null) flow { }
@ -177,10 +177,10 @@ class FavouriteViewModel @Inject constructor(
started = SharingStarted.Lazily
)
internal suspend fun getPlaylist(playlistUrl: String): Playlist? =
suspend fun getPlaylist(playlistUrl: String): Playlist? =
playlistRepository.get(playlistUrl)
internal fun playRandomly() {
fun playRandomly() {
viewModelScope.launch {
val channel = channelRepository.getRandomIgnoreSeriesAndHidden() ?: return@launch
playerManager.play(

View File

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

View File

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

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 {
implementation(project(":core"))
implementation(project(":ui"))
implementation(project(":data"))
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"
object PlaylistConfigurationNavigation {
internal const val TYPE_PLAYLIST_URL = "playlist_url"
const val TYPE_PLAYLIST_URL = "playlist_url"
const val PLAYLIST_CONFIGURATION_ROUTE =
"$PLAYLIST_CONFIGURATION_ROUTE_PATH?$TYPE_PLAYLIST_URL={$TYPE_PLAYLIST_URL}"
@ -34,25 +34,3 @@ fun NavController.navigateToPlaylistConfiguration(
val route = PlaylistConfigurationNavigation.createPlaylistConfigurationRoute(encodedUrl)
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 javax.inject.Inject
internal typealias EpgManifest = Map<Playlist, Boolean>
typealias EpgManifest = Map<Playlist, Boolean>
@HiltViewModel
class PlaylistConfigurationViewModel @Inject constructor(
@ -53,7 +53,7 @@ class PlaylistConfigurationViewModel @Inject constructor(
private val logger = delegate.install(Profiles.VIEWMODEL_PLAYLIST_CONFIGURATION)
private val playlistUrl: StateFlow<String> = savedStateHandle
.getStateFlow(PlaylistConfigurationNavigation.TYPE_PLAYLIST_URL, "")
internal val playlist: StateFlow<Playlist?> = playlistUrl.flatMapLatest {
val playlist: StateFlow<Playlist?> = playlistUrl.flatMapLatest {
playlistRepository.observe(it)
}
.stateIn(
@ -62,7 +62,7 @@ class PlaylistConfigurationViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L)
)
internal val xtreamUserInfo: StateFlow<Resource<XtreamInfo.UserInfo>> =
val xtreamUserInfo: StateFlow<Resource<XtreamInfo.UserInfo>> =
playlist.map { playlist ->
playlist ?: return@map null
if (playlist.source != DataSource.Xtream) return@map null
@ -81,7 +81,7 @@ class PlaylistConfigurationViewModel @Inject constructor(
started = SharingStarted.Lazily
)
internal val manifest: StateFlow<EpgManifest> = combine(
val manifest: StateFlow<EpgManifest> = combine(
playlistRepository.observeAllEpgs(),
playlist
) { epgs, playlist ->
@ -93,7 +93,7 @@ class PlaylistConfigurationViewModel @Inject constructor(
started = SharingStarted.Lazily,
initialValue = emptyMap()
)
internal val subscribingOrRefreshingWorkInfo: StateFlow<WorkInfo?> = workManager
val subscribingOrRefreshingWorkInfo: StateFlow<WorkInfo?> = workManager
.getWorkInfosFlow(
WorkQuery.fromStates(
WorkInfo.State.RUNNING,
@ -114,7 +114,7 @@ class PlaylistConfigurationViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(5_000L)
)
internal val expired: StateFlow<LocalDateTime?> = playlistUrl
val expired: StateFlow<LocalDateTime?> = playlistUrl
.flatMapLatest { 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
viewModelScope.launch {
playlistRepository.onUpdatePlaylistTitle(playlistUrl, title)
}
}
internal fun onUpdatePlaylistUserAgent(userAgent: String?) {
fun onUpdatePlaylistUserAgent(userAgent: String?) {
val playlistUrl = playlistUrl.value
viewModelScope.launch {
playlistRepository.onUpdatePlaylistUserAgent(playlistUrl, userAgent)
}
}
internal fun onUpdateEpgPlaylist(usecase: PlaylistRepository.EpgPlaylistUseCase) {
fun onUpdateEpgPlaylist(usecase: PlaylistRepository.EpgPlaylistUseCase) {
viewModelScope.launch {
playlistRepository.onUpdateEpgPlaylist(usecase)
}
}
internal fun onUpdatePlaylistAutoRefreshProgrammes() {
fun onUpdatePlaylistAutoRefreshProgrammes() {
val playlistUrl = playlistUrl.value
viewModelScope.launch {
playlistRepository.onUpdatePlaylistAutoRefreshProgrammes(playlistUrl)
}
}
internal fun onSyncProgrammes() {
fun onSyncProgrammes() {
val playlistUrl = playlistUrl.value
SubscriptionWorker.epg(workManager, playlistUrl, true)
}
internal fun onCancelSyncProgrammes() {
fun onCancelSyncProgrammes() {
val workInfo = subscribingOrRefreshingWorkInfo.value
workInfo?.id?.let { workManager.cancelWorkById(it) }
}

View File

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

View File

@ -21,7 +21,7 @@ android {
dependencies {
implementation(project(":core"))
implementation(project(":ui"))
implementation(project(":data"))
implementation(libs.androidx.core.ktx)
@ -35,8 +35,4 @@ dependencies {
implementation(libs.androidx.work.runtime.ktx)
ksp(libs.androidx.hilt.compiler)
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
internal enum class BackingUpAndRestoringState {
enum class BackingUpAndRestoringState {
NONE, BACKING_UP, RESTORING, BOTH;
companion object {

View File

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

View File

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

View File

@ -13,7 +13,6 @@ android {
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.google.material)
implementation(libs.androidx.media3.exoplayer)
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
import androidx.paging.PagingSource
import com.m3u.core.wrapper.Sort
import com.m3u.data.database.model.AdjacentChannels
import com.m3u.data.database.model.Channel
import kotlinx.coroutines.flow.Flow
@ -35,12 +36,4 @@ interface ChannelRepository {
fun observeAllUnseenFavourites(limit: Duration): Flow<List<Channel>>
fun observeAllFavourite(): 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.Channel
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.catch
import kotlinx.datetime.Clock

View File

@ -55,7 +55,6 @@ ktor-server = "3.0.0-beta-1"
mm2d-mmupnp = "3.1.6"
symbolProcessingApi = "2.0.0-1.0.22"
profileinstaller = "1.4.1"
tvFoundation = "1.0.0-alpha12"
[libraries]
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-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-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" }
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" }
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]
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(":core")
include(":data")
include(":material")
include(":ui")
include(
":business:foryou",
":business:favorite",
":business:setting",
":business:playlist",
":business:playlist-configuration",
":business:channel",
":business:crash"
":business:channel"
)
include(":baselineprofile")
include(":i18n")

View File

@ -10,6 +10,7 @@ plugins {
alias(libs.plugins.androidx.baselineprofile)
id("kotlin-parcelize")
}
android {
namespace = "com.m3u.smartphone"
compileSdk = 35
@ -120,38 +121,68 @@ baselineProfile {
dependencies {
implementation(project(":core"))
implementation(project(":ui"))
implementation(project(":data"))
// business
implementation(project(":business:foryou"))
implementation(project(":business:favorite"))
implementation(project(":business:setting"))
implementation(project(":business:playlist"))
implementation(project(":business:channel"))
implementation(project(":business:playlist-configuration"))
implementation(project(":business:crash"))
// baselineprofile
implementation(libs.androidx.profileinstaller)
"baselineProfile"(project(":baselineprofile"))
// base
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.activity.compose)
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.compose)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.core.splashscreen)
// work
implementation(libs.androidx.work.runtime.ktx)
// dagger
implementation(libs.google.dagger.hilt)
ksp(libs.google.dagger.hilt.compiler)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.work.runtime.ktx)
ksp(libs.androidx.hilt.compiler)
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.material3)
// accompanist
implementation(libs.google.accompanist.permissions)
// performance
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 com.m3u.core.architecture.logger.Logger
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 javax.inject.Inject

View File

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

View File

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

View File

@ -41,7 +41,7 @@ import com.m3u.smartphone.R
import com.m3u.core.Contracts
import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Programme
import com.m3u.ui.util.TimeUtils.formatEOrSh
import com.m3u.smartphone.TimeUtils.formatEOrSh
import dagger.hilt.android.EntryPointAccessors
import kotlinx.datetime.Instant
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.data.tv.model.RemoteDirection
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.Scaffold
import com.m3u.ui.Destination
import com.m3u.ui.FontFamilies
import com.m3u.ui.SnackHost
import com.m3u.smartphone.ui.material.components.Destination
import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.smartphone.ui.material.components.SnackHost
@Composable
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 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.dp
import androidx.media3.common.Player
import com.m3u.business.channel.PlayerState
import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.isNotEmpty
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.material.components.mask.MaskButton
import com.m3u.material.components.mask.MaskCircleButton
import com.m3u.material.components.mask.MaskPanel
import com.m3u.material.components.mask.MaskState
import com.m3u.material.effects.currentBackStackEntry
import com.m3u.material.ktx.thenIf
import com.m3u.material.model.LocalSpacing
import com.m3u.ui.FontFamilies
import com.m3u.ui.Image
import com.m3u.ui.helper.LocalHelper
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.business.channel.components.MaskTextButton
import com.m3u.smartphone.ui.business.channel.components.PlayerMask
import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.smartphone.ui.material.components.Image
import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.smartphone.ui.material.components.mask.MaskButton
import com.m3u.smartphone.ui.material.components.mask.MaskCircleButton
import com.m3u.smartphone.ui.material.components.mask.MaskPanel
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.launch
import kotlin.math.absoluteValue
@ -164,10 +162,11 @@ internal fun ChannelMask(
playerState.playState
) {
derivedStateOf {
val currentPlayer = playerState.player
when {
playerState.player == null -> false
!playerState.player.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM) -> false
else -> with(playerState.player) {
currentPlayer == null -> false
!currentPlayer.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM) -> false
else -> with(currentPlayer) {
!isCurrentMediaItemDynamic && isCurrentMediaItemSeekable
}
}
@ -179,8 +178,7 @@ internal fun ChannelMask(
) {
derivedStateOf {
playerState.player
?.isCommandAvailable(Player.COMMAND_SET_SPEED_AND_PITCH)
?: false
?.isCommandAvailable(Player.COMMAND_SET_SPEED_AND_PITCH) == true
}
}
@ -555,14 +553,14 @@ private fun MaskCenterButton(
scaleY = scale
},
icon = when (centerRole) {
Play -> Icons.Rounded.PlayArrow
Pause -> Icons.Rounded.Pause
MaskCenterRole.Play -> Icons.Rounded.PlayArrow
MaskCenterRole.Pause -> Icons.Rounded.Pause
else -> Icons.Rounded.Refresh
},
onClick = when (centerRole) {
Replay -> onRetry
Play -> onPlay
Pause -> onPause
MaskCenterRole.Replay -> onRetry
MaskCenterRole.Play -> onPlay
MaskCenterRole.Pause -> onPause
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.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.content.Intent
import android.graphics.Rect
import android.net.Uri
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
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.Channel
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.material.components.Background
import com.m3u.material.components.PullPanelLayout
import com.m3u.material.components.PullPanelLayoutValue
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 com.m3u.smartphone.ui.material.components.Player
import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.smartphone.ui.material.components.rememberPlayerState
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.time.Duration.Companion.milliseconds
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
fun ChannelRoute(

View File

@ -1,4 +1,4 @@
package com.m3u.business.channel
package com.m3u.smartphone.ui.business.channel
internal enum class MaskGesture {
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.res.Configuration
@ -9,14 +9,15 @@ import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import com.m3u.business.channel.ChannelViewModel
import com.m3u.core.Contracts
import com.m3u.data.database.model.isSeries
import com.m3u.data.repository.playlist.PlaylistRepository
import com.m3u.data.repository.channel.ChannelRepository
import com.m3u.data.service.MediaCommand
import com.m3u.ui.Events.enableDPadReaction
import com.m3u.ui.Toolkit
import com.m3u.ui.helper.Helper
import com.m3u.smartphone.ui.common.helper.Helper
import com.m3u.smartphone.ui.common.internal.Events.enableDPadReaction
import com.m3u.smartphone.ui.common.internal.Toolkit
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -72,7 +73,7 @@ class PlayerActivity : ComponentActivity() {
val playlist = playlistRepository.get(channel.playlistUrl)
when {
// series can not be played from shortcuts
playlist?.isSeries ?: false -> {}
playlist?.isSeries == true -> {}
else -> {
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.core.tween
@ -13,9 +13,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
import com.m3u.material.model.LocalDuration
import com.m3u.material.model.LocalSpacing
import com.m3u.ui.Image
import com.m3u.smartphone.ui.material.model.LocalDuration
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.components.Image
@Composable
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.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.foundation.clickable
@ -27,12 +27,12 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.m3u.core.util.basic.title
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 com.m3u.material.components.mask.MaskState
import com.m3u.material.model.LocalSpacing
import com.m3u.ui.UnstableBadge
import com.m3u.ui.UnstableValue
import com.m3u.smartphone.ui.material.components.mask.MaskState
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.components.UnstableBadge
import com.m3u.smartphone.ui.material.components.UnstableValue
import net.mm2d.upnp.Device
@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.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.fillMaxWidth
@ -36,8 +36,8 @@ import androidx.media3.common.C
import androidx.media3.common.Format
import com.m3u.i18n.R.string
import androidx.compose.material3.Icon
import com.m3u.material.components.mask.MaskState
import com.m3u.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.components.mask.MaskState
import com.m3u.smartphone.ui.material.model.LocalSpacing
import kotlinx.coroutines.launch
@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.Row
@ -17,8 +17,8 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.m3u.material.model.LocalSpacing
import com.m3u.ui.MonoText
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.components.MonoText
@Composable
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.Row
@ -16,8 +16,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.material3.IconButton
import com.m3u.material.components.mask.MaskState
import com.m3u.ui.FontFamilies
import com.m3u.smartphone.ui.material.components.mask.MaskState
import com.m3u.smartphone.ui.material.components.FontFamilies
@Composable
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.fadeIn
@ -20,9 +20,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.coerceAtLeast
import androidx.compose.ui.unit.dp
import com.m3u.material.components.mask.Mask
import com.m3u.material.components.mask.MaskState
import com.m3u.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.components.mask.Mask
import com.m3u.smartphone.ui.material.components.mask.MaskState
import com.m3u.smartphone.ui.material.model.LocalSpacing
@Composable
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.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.ProgrammeRange
import com.m3u.data.service.MediaCommand
import com.m3u.material.components.Background
import com.m3u.material.components.CircularProgressIndicator
import com.m3u.smartphone.ui.material.components.Background
import com.m3u.smartphone.ui.material.components.CircularProgressIndicator
import androidx.compose.material3.IconButton
import com.m3u.material.effects.BackStackEntry
import com.m3u.material.effects.BackStackHandler
import com.m3u.material.ktx.Edge
import com.m3u.material.ktx.blurEdges
import com.m3u.material.ktx.thenIf
import com.m3u.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape
import com.m3u.ui.FontFamilies
import com.m3u.ui.helper.LocalHelper
import com.m3u.ui.util.TimeUtils.formatEOrSh
import com.m3u.ui.util.TimeUtils.toEOrSh
import com.m3u.smartphone.ui.material.effects.BackStackEntry
import com.m3u.smartphone.ui.material.effects.BackStackHandler
import com.m3u.smartphone.ui.material.ktx.Edge
import com.m3u.smartphone.ui.material.ktx.blurEdges
import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.smartphone.TimeUtils.formatEOrSh
import com.m3u.smartphone.TimeUtils.toEOrSh
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
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.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.Companion.HOUR_LENGTH
import androidx.compose.material3.Icon
import com.m3u.material.ktx.Edge
import com.m3u.material.ktx.blurEdges
import com.m3u.material.model.LocalSpacing
import com.m3u.ui.FontFamilies
import com.m3u.ui.util.TimeUtils.formatEOrSh
import com.m3u.ui.util.TimeUtils.toEOrSh
import com.m3u.smartphone.ui.material.ktx.Edge
import com.m3u.smartphone.ui.material.ktx.blurEdges
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.smartphone.TimeUtils.formatEOrSh
import com.m3u.smartphone.TimeUtils.toEOrSh
import eu.wewox.minabox.MinaBox
import eu.wewox.minabox.MinaBoxItem
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.interaction.MutableInteractionSource
@ -10,8 +10,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import com.m3u.business.channel.ChannelMaskUtils.detectVerticalGesture
import com.m3u.material.ktx.thenIf
import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.smartphone.ui.business.channel.ChannelMaskUtils.detectVerticalGesture
@Composable
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.content.Intent
@ -26,6 +26,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
@ -43,6 +44,8 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.wrapper.Resource
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.parser.xtream.XtreamInfo
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.material.components.Background
import androidx.compose.material3.Icon
import com.m3u.material.components.PlaceholderField
import com.m3u.material.ktx.checkPermissionOrRationale
import com.m3u.material.model.LocalHazeState
import com.m3u.material.model.LocalSpacing
import com.m3u.ui.helper.LocalHelper
import com.m3u.ui.helper.Metadata
import com.m3u.smartphone.ui.material.components.Background
import com.m3u.smartphone.ui.material.components.PlaceholderField
import com.m3u.smartphone.ui.material.ktx.checkPermissionOrRationale
import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.business.configuration.components.AutoSyncProgrammesButton
import com.m3u.smartphone.ui.business.configuration.components.EpgManifestGallery
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.haze
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.clickable
@ -15,9 +15,9 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.m3u.core.util.basic.title
import com.m3u.i18n.R
import com.m3u.material.components.SelectionsDefaults
import com.m3u.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape
import com.m3u.smartphone.ui.material.components.SelectionsDefaults
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
@Composable
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.border
@ -21,14 +21,14 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.m3u.business.playlist.configuration.EpgManifest
import com.m3u.core.util.basic.title
import com.m3u.data.database.model.Playlist
import com.m3u.data.repository.playlist.PlaylistRepository
import com.m3u.business.playlist.configuration.EpgManifest
import com.m3u.i18n.R.string
import com.m3u.material.components.SelectionsDefaults
import com.m3u.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape
import com.m3u.smartphone.ui.material.components.SelectionsDefaults
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
internal fun LazyListScope.EpgManifestGallery(
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.fadeIn
@ -22,10 +22,10 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.m3u.core.util.basic.title
import com.m3u.i18n.R.string
import com.m3u.material.components.CircularProgressIndicator
import com.m3u.material.components.SelectionsDefaults
import com.m3u.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape
import com.m3u.smartphone.ui.material.components.CircularProgressIndicator
import com.m3u.smartphone.ui.material.components.SelectionsDefaults
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import kotlinx.datetime.LocalDateTime
@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.foundation.layout.Arrangement
@ -23,10 +23,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.m3u.core.wrapper.Resource
import com.m3u.data.parser.xtream.XtreamInfo
import com.m3u.material.model.LocalSpacing
import com.m3u.ui.Badge
import com.m3u.ui.FontFamilies
import com.m3u.ui.TextBadge
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.components.Badge
import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.smartphone.ui.material.components.TextBadge
import kotlinx.datetime.Instant
@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.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import com.m3u.ui.Events.enableDPadReaction
import com.m3u.ui.Toolkit
import com.m3u.ui.helper.Helper
import com.m3u.smartphone.ui.common.helper.Helper
import com.m3u.smartphone.ui.common.internal.Events.enableDPadReaction
import com.m3u.smartphone.ui.common.internal.Toolkit
import dagger.hilt.android.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.compose.foundation.layout.Box
@ -10,9 +10,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.m3u.business.crash.navigation.Destination
import com.m3u.business.crash.screen.detail.DetailScreen
import com.m3u.business.crash.screen.list.ListScreen
import com.m3u.smartphone.ui.business.crash.navigation.Destination
import com.m3u.smartphone.ui.business.crash.screen.detail.DetailScreen
import com.m3u.smartphone.ui.business.crash.screen.list.ListScreen
@Composable
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.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.rounded.Adb
@ -9,7 +9,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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
@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 {
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.result.contract.ActivityResultContracts
@ -18,10 +18,10 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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 com.m3u.material.model.LocalSpacing
import com.m3u.ui.MonoText
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.components.MonoText
@Composable
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 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.layout.fillMaxSize
@ -7,9 +7,9 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import com.m3u.business.crash.components.FileItem
import com.m3u.business.crash.screen.list.navigation.NavigateToDetail
import com.m3u.material.components.Background
import com.m3u.smartphone.ui.material.components.Background
import com.m3u.smartphone.ui.business.crash.components.FileItem
import com.m3u.smartphone.ui.business.crash.screen.list.navigation.NavigateToDetail
@Composable
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 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.view.KeyEvent
@ -24,25 +24,26 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.m3u.business.favorite.FavouriteViewModel
import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.title
import com.m3u.core.wrapper.Resource
import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.isSeries
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.material.ktx.interceptVolumeEvent
import com.m3u.material.ktx.thenIf
import com.m3u.material.model.LocalHazeState
import com.m3u.ui.EpisodesBottomSheet
import com.m3u.ui.MediaSheet
import com.m3u.ui.MediaSheetValue
import com.m3u.ui.Sort
import com.m3u.ui.SortBottomSheet
import com.m3u.ui.helper.Action
import com.m3u.ui.helper.LocalHelper
import com.m3u.ui.helper.Metadata
import com.m3u.smartphone.ui.material.ktx.interceptVolumeEvent
import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.smartphone.ui.business.favorite.components.FavouriteGallery
import com.m3u.smartphone.ui.material.components.EpisodesBottomSheet
import com.m3u.smartphone.ui.material.components.MediaSheet
import com.m3u.smartphone.ui.material.components.MediaSheetValue
import com.m3u.smartphone.ui.material.components.SortBottomSheet
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 dev.chrisbanes.haze.HazeDefaults
import dev.chrisbanes.haze.haze
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.Box
@ -18,9 +18,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.m3u.core.wrapper.Resource
import com.m3u.data.database.model.Channel
import com.m3u.material.components.VerticalDraggableScrollbar
import com.m3u.material.ktx.plus
import com.m3u.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.components.VerticalDraggableScrollbar
import com.m3u.smartphone.ui.material.ktx.plus
import com.m3u.smartphone.ui.material.model.LocalSpacing
@Composable
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.material3.CardDefaults
@ -18,8 +18,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import com.m3u.data.database.model.Channel
import com.m3u.i18n.R.string
import com.m3u.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
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.view.KeyEvent
@ -31,6 +31,8 @@ import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.util.basic.title
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.isSeries
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.material.ktx.composableOf
import com.m3u.material.ktx.interceptVolumeEvent
import com.m3u.material.ktx.thenIf
import com.m3u.ui.EpisodesBottomSheet
import com.m3u.ui.MediaSheet
import com.m3u.ui.MediaSheetValue
import com.m3u.ui.helper.Action
import com.m3u.ui.helper.LocalHelper
import com.m3u.ui.helper.Metadata
import com.m3u.smartphone.ui.material.ktx.composableOf
import com.m3u.smartphone.ui.material.ktx.interceptVolumeEvent
import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.smartphone.ui.business.foryou.components.HeadlineBackground
import com.m3u.smartphone.ui.business.foryou.components.PlaylistGallery
import com.m3u.smartphone.ui.business.foryou.components.recommend.RecommendGallery
import com.m3u.smartphone.ui.material.components.EpisodesBottomSheet
import com.m3u.smartphone.ui.material.components.MediaSheet
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.launch
import kotlin.time.Duration.Companion.milliseconds
@ -111,7 +112,7 @@ fun ForyouRoute(
coroutineScope.launch {
val playlist = viewModel.getPlaylist(channel.playlistUrl)
when {
playlist?.isSeries ?: false -> {
playlist?.isSeries == true -> {
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.core.tween
@ -26,10 +26,10 @@ import coil.compose.AsyncImage
import coil.request.CachePolicy
import coil.request.ImageRequest
import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.material.transformation.BlurTransformation
import com.m3u.ui.helper.LocalHelper
import com.m3u.ui.helper.Metadata
import com.m3u.ui.helper.useRailNav
import com.m3u.smartphone.ui.material.transformation.BlurTransformation
import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.smartphone.ui.common.helper.Metadata
import com.m3u.smartphone.ui.common.helper.useRailNav
import kotlin.math.roundToInt
@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.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.m3u.business.foryou.R
import com.m3u.material.components.ProgressLottie
import com.m3u.smartphone.ui.material.components.ProgressLottie
@Composable
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.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.type
import com.m3u.i18n.R.string
import com.m3u.material.ktx.plus
import com.m3u.material.model.LocalHazeState
import com.m3u.material.model.LocalSpacing
import com.m3u.ui.helper.LocalHelper
import com.m3u.ui.helper.Metadata
import com.m3u.ui.helper.useRailNav
import com.m3u.smartphone.ui.material.ktx.plus
import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.common.helper.LocalHelper
import com.m3u.smartphone.ui.common.helper.Metadata
import com.m3u.smartphone.ui.common.helper.useRailNav
import dev.chrisbanes.haze.HazeDefaults
import dev.chrisbanes.haze.haze
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.layout.Arrangement
@ -26,12 +26,12 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
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 com.m3u.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape
import com.m3u.ui.Badge
import com.m3u.ui.FontFamilies
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import com.m3u.smartphone.ui.material.components.Badge
import com.m3u.smartphone.ui.material.components.FontFamilies
@Composable
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.Column
@ -13,13 +13,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.dp
import com.m3u.business.foryou.Recommend
import com.m3u.core.wrapper.eventOf
import com.m3u.data.database.model.Channel
import com.m3u.data.database.model.Playlist
import com.m3u.material.components.HorizontalPagerIndicator
import com.m3u.material.ktx.pageOffset
import com.m3u.material.model.LocalSpacing
import com.m3u.ui.Events
import com.m3u.smartphone.ui.common.internal.Events
import com.m3u.smartphone.ui.material.components.HorizontalPagerIndicator
import com.m3u.smartphone.ui.material.ktx.pageOffset
import com.m3u.smartphone.ui.material.model.LocalSpacing
@Composable
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.background
@ -39,14 +39,15 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.m3u.business.foryou.Recommend
import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.title
import com.m3u.i18n.R.string
import com.m3u.material.brush.RecommendCardContainerBrush
import com.m3u.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape
import com.m3u.ui.FontFamilies
import com.m3u.ui.createPremiumBrush
import com.m3u.smartphone.ui.material.brush.RecommendCardContainerBrush
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import com.m3u.smartphone.ui.material.components.FontFamilies
import com.m3u.smartphone.ui.material.components.createPremiumBrush
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
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.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.PaddingValues
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.m3u.business.playlist.PlaylistRoute
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)
}
import com.m3u.business.playlist.PlaylistNavigation
fun NavGraphBuilder.playlistScreen(
navigateToChannel: () -> Unit,

View File

@ -1,6 +1,6 @@
@file:Suppress("UsingMaterialAndMaterial3Libraries")
package com.m3u.business.playlist
package com.m3u.smartphone.ui.business.playlist
import android.Manifest
import android.content.Intent
@ -81,6 +81,7 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.google.accompanist.permissions.rememberPermissionState
import com.m3u.business.playlist.PlaylistViewModel
import com.m3u.core.architecture.preferences.hiltPreferences
import com.m3u.core.util.basic.title
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.type
import com.m3u.data.service.MediaCommand
import com.m3u.business.playlist.components.PlaylistTabRow
import com.m3u.business.playlist.components.ChannelGallery
import com.m3u.core.wrapper.Sort
import com.m3u.i18n.R.string
import com.m3u.material.components.TextField
import com.m3u.material.ktx.checkPermissionOrRationale
import com.m3u.material.ktx.interceptVolumeEvent
import com.m3u.material.ktx.isAtTop
import com.m3u.material.ktx.only
import com.m3u.material.ktx.split
import com.m3u.material.ktx.thenIf
import com.m3u.material.model.LocalHazeState
import com.m3u.material.model.LocalSpacing
import com.m3u.ui.Destination
import com.m3u.ui.EpisodesBottomSheet
import com.m3u.ui.EventHandler
import com.m3u.ui.MediaSheet
import com.m3u.ui.MediaSheetValue
import com.m3u.ui.Sort
import com.m3u.ui.SortBottomSheet
import com.m3u.ui.helper.Action
import com.m3u.ui.helper.Fob
import com.m3u.ui.helper.LocalHelper
import com.m3u.ui.helper.Metadata
import com.m3u.smartphone.ui.material.components.TextField
import com.m3u.smartphone.ui.material.ktx.checkPermissionOrRationale
import com.m3u.smartphone.ui.material.ktx.interceptVolumeEvent
import com.m3u.smartphone.ui.material.ktx.isAtTop
import com.m3u.smartphone.ui.material.ktx.only
import com.m3u.smartphone.ui.material.ktx.split
import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.business.playlist.components.ChannelGallery
import com.m3u.smartphone.ui.business.playlist.components.PlaylistTabRow
import com.m3u.smartphone.ui.material.components.Destination
import com.m3u.smartphone.ui.material.components.EpisodesBottomSheet
import com.m3u.smartphone.ui.material.components.EventHandler
import com.m3u.smartphone.ui.material.components.MediaSheet
import com.m3u.smartphone.ui.material.components.MediaSheetValue
import com.m3u.smartphone.ui.material.components.SortBottomSheet
import com.m3u.smartphone.ui.common.helper.Action
import com.m3u.smartphone.ui.common.helper.Fob
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.haze
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.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.Programme
import com.m3u.business.playlist.PlaylistViewModel
import com.m3u.material.components.CircularProgressIndicator
import com.m3u.material.components.VerticalDraggableScrollbar
import com.m3u.material.ktx.plus
import com.m3u.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.components.CircularProgressIndicator
import com.m3u.smartphone.ui.material.components.VerticalDraggableScrollbar
import com.m3u.smartphone.ui.material.ktx.plus
import com.m3u.smartphone.ui.material.model.LocalSpacing
@Composable
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.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.Channel
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 com.m3u.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape
import com.m3u.ui.util.TimeUtils.formatEOrSh
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import com.m3u.smartphone.TimeUtils.formatEOrSh
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
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.foundation.background
@ -52,14 +52,14 @@ import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.material3.IconButton
import com.m3u.material.effects.BackStackEntry
import com.m3u.material.effects.BackStackHandler
import com.m3u.material.ktx.Edge
import com.m3u.material.ktx.blurEdge
import com.m3u.material.ktx.thenIf
import com.m3u.material.model.LocalHazeState
import com.m3u.material.model.LocalSpacing
import com.m3u.material.shape.AbsoluteSmoothCornerShape
import com.m3u.smartphone.ui.material.effects.BackStackEntry
import com.m3u.smartphone.ui.material.effects.BackStackHandler
import com.m3u.smartphone.ui.material.ktx.Edge
import com.m3u.smartphone.ui.material.ktx.blurEdge
import com.m3u.smartphone.ui.material.ktx.thenIf
import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.shape.AbsoluteSmoothCornerShape
import dev.chrisbanes.haze.HazeDefaults
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 androidx.activity.compose.BackHandler
@ -28,6 +28,8 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect
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.unit.DataUnit
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.DataSource
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.material.model.LocalHazeState
import com.m3u.ui.Destination
import com.m3u.ui.EventHandler
import com.m3u.ui.Events
import com.m3u.ui.SettingDestination
import com.m3u.ui.helper.Fob
import com.m3u.ui.helper.Metadata
import com.m3u.smartphone.ui.material.model.LocalHazeState
import com.m3u.smartphone.ui.business.setting.components.CanvasBottomSheet
import com.m3u.smartphone.ui.business.setting.fragments.AppearanceFragment
import com.m3u.smartphone.ui.business.setting.fragments.OptionalFragment
import com.m3u.smartphone.ui.business.setting.fragments.SubscriptionsFragment
import com.m3u.smartphone.ui.business.setting.fragments.preferences.PreferencesFragment
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.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.foundation.layout.Box
@ -39,10 +39,10 @@ import androidx.compose.ui.unit.dp
import com.m3u.data.database.model.ColorScheme
import com.m3u.i18n.R.string
import androidx.compose.material3.Icon
import com.m3u.material.ktx.createScheme
import com.m3u.material.model.LocalSpacing
import com.m3u.material.model.SugarColors
import com.m3u.ui.FontFamilies
import com.m3u.smartphone.ui.material.ktx.createScheme
import com.m3u.smartphone.ui.material.model.LocalSpacing
import com.m3u.smartphone.ui.material.model.SugarColors
import com.m3u.smartphone.ui.material.components.FontFamilies
@OptIn(ExperimentalStdlibApi::class)
@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.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import com.m3u.core.util.basic.title
import com.m3u.material.components.CheckBoxPreference
import com.m3u.material.components.SwitchPreference
import com.m3u.smartphone.ui.material.components.CheckBoxPreference
import com.m3u.smartphone.ui.material.components.SwitchPreference
@Composable
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.layout.Arrangement
@ -24,9 +24,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
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 com.m3u.material.components.SelectionsDefaults
import com.m3u.smartphone.ui.material.components.SelectionsDefaults
@Composable
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.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.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.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 androidx.activity.compose.rememberLauncherForActivityResult
@ -17,7 +17,7 @@ import androidx.compose.ui.res.stringResource
import com.m3u.core.util.readFileName
import com.m3u.i18n.R.string
import androidx.compose.material3.Icon
import com.m3u.material.components.ToggleableSelection
import com.m3u.smartphone.ui.material.components.ToggleableSelection
@Composable
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.Text
@ -6,7 +6,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.m3u.i18n.R.string
import com.m3u.material.components.ToggleableSelection
import com.m3u.smartphone.ui.material.components.ToggleableSelection
@Composable
internal fun LocalStorageSwitch(

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