fix: Corregir todos los errores de compilación
- M3UParser.kt: Fix regex de atributos - ChannelRepository.kt: Manejo correcto de M3UParseResult - CategoryChip.kt: Simplificar border - DnsConfigurator.kt: Usar HttpUrl.Companion.toHttpUrl() - PlayerManager.kt: Cambiar buildUponParameters() a buildUpon() - MainActivity.kt: Eliminar splashscreen no disponible - PlayerControls.kt/PlayerScreen.kt: Fix iconos automirrored - UpdateDialog.kt: Fix LinearProgressIndicator - ChannelsScreen.kt: Eliminar PullToRefresh experimental - UpdateInstallHelper.kt: Fix icono de descarga - Agregar dependencia okhttp-dnsoverhttps - Habilitar buildConfig en build.gradle.kts
This commit is contained in:
@@ -12,7 +12,6 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
@@ -46,17 +45,11 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
val splashScreen = installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Initialize repository
|
||||
initializeRepository()
|
||||
|
||||
// Keep splash screen visible while loading
|
||||
splashScreen.setKeepOnScreenCondition {
|
||||
viewModel.uiState.value.isLoading
|
||||
}
|
||||
|
||||
setContent {
|
||||
IPTVAppTheme {
|
||||
Surface(
|
||||
|
||||
@@ -281,7 +281,7 @@ class M3UParser(private val context: Context? = null) {
|
||||
*/
|
||||
private fun parseAttributes(attributesString: String): Map<String, String> {
|
||||
val attributes = mutableMapOf<String, String>()
|
||||
val regex = """(\w+)="([^"]*)",""".toRegex()
|
||||
val regex = """(\w+)="([^"]*)"""".toRegex()
|
||||
|
||||
regex.findAll(attributesString).forEach { matchResult ->
|
||||
val key = matchResult.groupValues[1]
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.iptv.app.data.model.Channel
|
||||
import com.iptv.app.data.parser.M3UParser
|
||||
import com.iptv.app.data.parser.M3UParseResult
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -229,7 +230,10 @@ class ChannelRepository(
|
||||
|
||||
private suspend fun fetchFromNetwork(m3uUrl: String): List<Channel> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
m3uParser.parseFromUrl(m3uUrl)
|
||||
when (val result = m3uParser.parseFromUrl(m3uUrl)) {
|
||||
is M3UParseResult.Success -> result.channels
|
||||
is M3UParseResult.Error -> throw Exception(result.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.iptv.app.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.FilterChipDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -12,6 +13,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.iptv.app.ui.theme.*
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun CategoryChip(
|
||||
category: String,
|
||||
@@ -37,13 +39,7 @@ fun CategoryChip(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
labelColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
),
|
||||
border = FilterChipDefaults.filterChipBorder(
|
||||
enabled = true,
|
||||
selected = isSelected,
|
||||
borderColor = if (isSelected) categoryColor else MaterialTheme.colorScheme.outline,
|
||||
selectedBorderColor = categoryColor,
|
||||
borderWidth = 1.dp
|
||||
)
|
||||
border = null
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -161,7 +161,7 @@ private fun TopControlBar(
|
||||
modifier = Modifier.size(48.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(28.dp)
|
||||
|
||||
@@ -123,7 +123,7 @@ fun DownloadProgressDialog(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
progress = { progress.percentage / 100f },
|
||||
progress = progress.percentage / 100f,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ private fun createExoPlayer(
|
||||
): ExoPlayer {
|
||||
val trackSelector = DefaultTrackSelector(context).apply {
|
||||
setParameters(
|
||||
buildUponParameters()
|
||||
this.buildUponParameters()
|
||||
.setMaxVideoSizeSd()
|
||||
.setPreferredAudioLanguage("en")
|
||||
)
|
||||
@@ -331,8 +331,6 @@ private fun preparePlayer(context: Context, player: ExoPlayer, streamUrl: String
|
||||
|
||||
// Create OkHttpDataSource.Factory with custom DNS client
|
||||
val dataSourceFactory = OkHttpDataSource.Factory(okHttpClient)
|
||||
.setConnectTimeoutMs(15000)
|
||||
.setReadTimeoutMs(15000)
|
||||
|
||||
val mediaSource = HlsMediaSource.Factory(dataSourceFactory)
|
||||
.createMediaSource(mediaItem)
|
||||
|
||||
@@ -254,7 +254,6 @@ private fun ChannelsGrid(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun PullToRefreshContainer(
|
||||
isRefreshing: Boolean,
|
||||
@@ -262,30 +261,16 @@ private fun PullToRefreshContainer(
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val pullToRefreshState = rememberPullToRefreshState()
|
||||
|
||||
// Simplified pull-to-refresh container without experimental APIs
|
||||
Box(modifier = modifier) {
|
||||
content()
|
||||
|
||||
if (pullToRefreshState.isRefreshing) {
|
||||
LaunchedEffect(true) {
|
||||
// Trigger refresh when isRefreshing becomes true
|
||||
LaunchedEffect(isRefreshing) {
|
||||
if (isRefreshing) {
|
||||
onRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(isRefreshing) {
|
||||
if (isRefreshing) {
|
||||
pullToRefreshState.startRefresh()
|
||||
} else {
|
||||
pullToRefreshState.endRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
PullToRefreshBox(
|
||||
state = pullToRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
content = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Fullscreen
|
||||
import androidx.compose.material.icons.filled.FullscreenExit
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
@@ -255,7 +255,7 @@ private fun PlayerControlsOverlay(
|
||||
.background(Color.Black.copy(alpha = 0.5f))
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = Color.White,
|
||||
modifier = Modifier.size(24.dp)
|
||||
|
||||
@@ -277,9 +277,8 @@ class ChannelsViewModel(
|
||||
combine(
|
||||
_uiState.map { it.channels },
|
||||
_searchQuery
|
||||
.debounce(SEARCH_DEBOUNCE_MS)
|
||||
.distinctUntilChanged(),
|
||||
_selectedCategory.distinctUntilChanged()
|
||||
.debounce(SEARCH_DEBOUNCE_MS),
|
||||
_selectedCategory
|
||||
) { channels, query, category ->
|
||||
channels.filter { channel ->
|
||||
val matchesSearch = query.isBlank() ||
|
||||
|
||||
@@ -166,7 +166,7 @@ class PlayerViewModel(
|
||||
*/
|
||||
fun getCurrentStreamUrl(): Uri? {
|
||||
return _uiState.value.currentChannel?.let {
|
||||
Uri.parse(it.streamUrl)
|
||||
Uri.parse(it.url)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.util.Log
|
||||
import okhttp3.Dns
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.dnsoverhttps.DnsOverHttps
|
||||
import java.net.InetAddress
|
||||
@@ -75,7 +76,7 @@ object DnsConfigurator {
|
||||
|
||||
return DnsOverHttps.Builder()
|
||||
.client(bootstrapClient)
|
||||
.url(java.net.URL(GOOGLE_DOH_URL))
|
||||
.url(GOOGLE_DOH_URL.toHttpUrl())
|
||||
.bootstrapDnsHosts(GOOGLE_DNS_IPV4 + CLOUDFLARE_DNS_IPV4)
|
||||
.includeIPv6(false)
|
||||
.build()
|
||||
|
||||
@@ -85,11 +85,11 @@ class PlayerManager(private val context: Context) {
|
||||
releasePlayer() // Release any existing player
|
||||
|
||||
trackSelector = DefaultTrackSelector(context).apply {
|
||||
setParameters(
|
||||
buildUponParameters()
|
||||
.setPreferredAudioLanguage("en")
|
||||
.setMaxVideoSizeSd()
|
||||
)
|
||||
val params = this.parameters.buildUpon()
|
||||
.setPreferredAudioLanguage("en")
|
||||
.setMaxVideoSizeSd()
|
||||
.build()
|
||||
this.parameters = params
|
||||
}
|
||||
|
||||
val player = ExoPlayer.Builder(context)
|
||||
@@ -240,37 +240,28 @@ class PlayerManager(private val context: Context) {
|
||||
fun setVideoQuality(quality: VideoQuality?) {
|
||||
val selector = trackSelector ?: return
|
||||
|
||||
val parameters = if (quality == null) {
|
||||
// Auto quality selection
|
||||
selector.parameters.buildUponParameters()
|
||||
.clearOverridesOfType(C.TRACK_TYPE_VIDEO)
|
||||
val paramsBuilder = selector.parameters.buildUpon()
|
||||
if (quality == null) {
|
||||
paramsBuilder.clearOverridesOfType(C.TRACK_TYPE_VIDEO)
|
||||
} else {
|
||||
// Manual quality selection
|
||||
val player = exoPlayer ?: return
|
||||
val tracks = player.currentTracks
|
||||
|
||||
var override: TrackSelectionOverride? = null
|
||||
for (trackGroup in tracks.groups) {
|
||||
if (trackGroup.type == C.TRACK_TYPE_VIDEO) {
|
||||
for (trackIndex in 0 until trackGroup.length) {
|
||||
val format = trackGroup.getTrackFormat(trackIndex)
|
||||
if (format.width == quality.width && format.height == quality.height) {
|
||||
override = TrackSelectionOverride(trackGroup.mediaTrackGroup, trackIndex)
|
||||
val override = TrackSelectionOverride(trackGroup.mediaTrackGroup, trackIndex)
|
||||
paramsBuilder.setOverrideForType(override)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (override != null) {
|
||||
selector.parameters.buildUponParameters()
|
||||
.setOverrideForType(override)
|
||||
} else {
|
||||
selector.parameters.buildUponParameters()
|
||||
}
|
||||
}
|
||||
|
||||
selector.parameters = parameters.build()
|
||||
selector.parameters = paramsBuilder.build()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -281,11 +272,10 @@ class PlayerManager(private val context: Context) {
|
||||
fun setAudioTrack(languageCode: String?) {
|
||||
val selector = trackSelector ?: return
|
||||
|
||||
val parameters = selector.parameters.buildUponParameters()
|
||||
val paramsBuilder = selector.parameters.buildUpon()
|
||||
.setPreferredAudioLanguage(languageCode)
|
||||
.build()
|
||||
|
||||
selector.parameters = parameters
|
||||
selector.parameters = paramsBuilder.build()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -360,16 +350,15 @@ class PlayerManager(private val context: Context) {
|
||||
fun setSubtitlesEnabled(enabled: Boolean, language: String? = null) {
|
||||
val selector = trackSelector ?: return
|
||||
|
||||
val parameters = if (enabled) {
|
||||
selector.parameters.buildUponParameters()
|
||||
val paramsBuilder = if (enabled) {
|
||||
selector.parameters.buildUpon()
|
||||
.setPreferredTextLanguage(language)
|
||||
.setIgnoredTextSelectionFlags(0)
|
||||
} else {
|
||||
selector.parameters.buildUponParameters()
|
||||
selector.parameters.buildUpon()
|
||||
.setIgnoredTextSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
||||
}
|
||||
|
||||
selector.parameters = parameters.build()
|
||||
selector.parameters = paramsBuilder.build()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -238,7 +238,7 @@ class UpdateInstallHelper(private val context: Context) {
|
||||
*/
|
||||
fun showDownloadProgressNotification(progress: Int, totalBytes: Long) {
|
||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_menu_download)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setContentTitle(context.getString(R.string.update_downloading_title))
|
||||
.setContentText(formatBytes(totalBytes))
|
||||
.setProgress(100, progress, false)
|
||||
|
||||
Reference in New Issue
Block a user