diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e41be14..e2dfe38 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -36,6 +36,7 @@ android { } buildFeatures { compose = true + buildConfig = true } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 874d531..5dbd350 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,8 @@ diff --git a/app/src/main/java/xyz/cottongin/radio247/MainActivity.kt b/app/src/main/java/xyz/cottongin/radio247/MainActivity.kt index 37bd929..29e576c 100644 --- a/app/src/main/java/xyz/cottongin/radio247/MainActivity.kt +++ b/app/src/main/java/xyz/cottongin/radio247/MainActivity.kt @@ -7,6 +7,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.enableEdgeToEdge import androidx.core.content.ContextCompat import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent @@ -26,6 +27,7 @@ import xyz.cottongin.radio247.ui.theme.Radio247Theme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() super.onCreate(savedInstanceState) setContent { val requestNotificationPermission = rememberLauncherForActivityResult( diff --git a/app/src/main/java/xyz/cottongin/radio247/audio/MetadataFormatter.kt b/app/src/main/java/xyz/cottongin/radio247/audio/MetadataFormatter.kt new file mode 100644 index 0000000..9141eb9 --- /dev/null +++ b/app/src/main/java/xyz/cottongin/radio247/audio/MetadataFormatter.kt @@ -0,0 +1,14 @@ +package xyz.cottongin.radio247.audio + +object MetadataFormatter { + fun formatTrackInfo(metadata: IcyMetadata?, fallback: String = "\u266A"): String { + if (metadata == null) return fallback + return when { + metadata.artist != null && metadata.title != null -> + "${metadata.artist} \u2014 ${metadata.title}" + metadata.title != null -> metadata.title + metadata.artist != null -> metadata.artist + else -> fallback + } + } +} diff --git a/app/src/main/java/xyz/cottongin/radio247/service/NotificationHelper.kt b/app/src/main/java/xyz/cottongin/radio247/service/NotificationHelper.kt index ab341f6..dfa230e 100644 --- a/app/src/main/java/xyz/cottongin/radio247/service/NotificationHelper.kt +++ b/app/src/main/java/xyz/cottongin/radio247/service/NotificationHelper.kt @@ -9,6 +9,7 @@ import androidx.core.app.NotificationCompat import android.support.v4.media.session.MediaSessionCompat import xyz.cottongin.radio247.R import xyz.cottongin.radio247.audio.IcyMetadata +import xyz.cottongin.radio247.audio.MetadataFormatter import xyz.cottongin.radio247.data.model.Station class NotificationHelper(private val context: Context) { @@ -41,11 +42,11 @@ class NotificationHelper(private val context: Context) { .setContentText( when { isReconnecting -> "Reconnecting..." - metadata?.title != null -> metadata.raw + metadata != null -> MetadataFormatter.formatTrackInfo(metadata, fallback = "Playing") else -> "Playing" } ) - .setSmallIcon(R.drawable.ic_radio_placeholder) + .setSmallIcon(R.drawable.ic_notification) .setOngoing(true) .setStyle( androidx.media.app.NotificationCompat.MediaStyle() diff --git a/app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt b/app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt index ff2fb1e..d9b53de 100644 --- a/app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt +++ b/app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt @@ -26,12 +26,16 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.wrapContentWidth @@ -81,6 +85,7 @@ import androidx.compose.ui.draw.clipToBounds import androidx.compose.foundation.layout.aspectRatio import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.graphicsLayer @@ -109,6 +114,7 @@ import coil3.size.Size import xyz.cottongin.radio247.R import xyz.cottongin.radio247.RadioApplication import xyz.cottongin.radio247.audio.IcyMetadata +import xyz.cottongin.radio247.audio.MetadataFormatter import xyz.cottongin.radio247.audio.StreamInfo import xyz.cottongin.radio247.service.PlaybackState import com.skydoves.cloudy.cloudy @@ -432,7 +438,8 @@ private fun ArtworkImage( Image( painter = painterResource(R.drawable.ic_radio_placeholder), contentDescription = null, - modifier = Modifier.size(120.dp) + modifier = Modifier.size(120.dp), + colorFilter = ColorFilter.tint(contrastingTextColor(bgColor)) ) } } @@ -903,13 +910,16 @@ private fun PortraitContent( val cardShape = RoundedCornerShape(14.dp) val backBtnShape = RoundedCornerShape(10.dp) + val statusBarPadding = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val navBarPadding = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + Box(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) { Box( modifier = Modifier .weight(1f) .fillMaxWidth() - .padding(start = 24.dp, end = 24.dp, top = 48.dp, bottom = 8.dp), + .padding(start = 24.dp, end = 24.dp, top = statusBarPadding + 48.dp, bottom = 8.dp), contentAlignment = Alignment.Center ) { ArtworkImage( @@ -968,12 +978,12 @@ private fun PortraitContent( } } - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(navBarPadding + 8.dp)) } Box( modifier = Modifier - .padding(start = 12.dp, top = 12.dp) + .padding(start = 12.dp, top = statusBarPadding + 12.dp) .size(40.dp) .clip(backBtnShape) .background(bgColor.copy(alpha = 0.5f)) @@ -1135,16 +1145,8 @@ private fun OverlayCards( } } -private fun formatTrackInfo(metadata: IcyMetadata?): String { - if (metadata == null) return "\u266A" - return when { - metadata.artist != null && metadata.title != null -> - "${metadata.artist} \u2014 ${metadata.title}" - metadata.title != null -> metadata.title - metadata.artist != null -> metadata.artist - else -> "\u266A" - } -} +private fun formatTrackInfo(metadata: IcyMetadata?): String = + MetadataFormatter.formatTrackInfo(metadata) private fun formatElapsed(millis: Long): String { if (millis <= 0) return "0s" diff --git a/app/src/main/java/xyz/cottongin/radio247/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/xyz/cottongin/radio247/ui/screens/settings/SettingsScreen.kt index 9e6d608..ea60a26 100644 --- a/app/src/main/java/xyz/cottongin/radio247/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/xyz/cottongin/radio247/ui/screens/settings/SettingsScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Switch @@ -106,24 +107,28 @@ fun SettingsScreen( } } - Column(modifier = modifier.fillMaxSize()) { - TopAppBar( - title = { Text("Settings") }, - navigationIcon = { - IconButton(onClick = onBack) { - Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - titleContentColor = MaterialTheme.colorScheme.onSurface, - navigationIconContentColor = MaterialTheme.colorScheme.onSurface + Scaffold( + modifier = modifier.fillMaxSize(), + topBar = { + TopAppBar( + title = { Text("Settings") }, + navigationIcon = { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + titleContentColor = MaterialTheme.colorScheme.onSurface, + navigationIconContentColor = MaterialTheme.colorScheme.onSurface + ) ) - ) - + } + ) { innerPadding -> Column( modifier = Modifier .fillMaxSize() + .padding(innerPadding) .verticalScroll(rememberScrollState()) .padding(16.dp) ) { @@ -256,6 +261,35 @@ fun SettingsScreen( HorizontalDivider() Spacer(modifier = Modifier.height(24.dp)) + // ── ABOUT ── + SectionHeader("ABOUT") + Text( + text = "24/7 Radio", + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = "Version ${xyz.cottongin.radio247.BuildConfig.VERSION_NAME}", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = "Personal-use internet radio streaming app with ultra-low latency playback and Icecast/Shoutcast metadata support.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = xyz.cottongin.radio247.BuildConfig.APPLICATION_ID, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) + ) + + Spacer(modifier = Modifier.height(24.dp)) + HorizontalDivider() + Spacer(modifier = Modifier.height(24.dp)) + // ── RESET ── SectionHeader("RESET") SettingRow("Also delete saved stations/playlists") { diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..3d74408 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..510c69f --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_notification.xml b/app/src/main/res/drawable/ic_notification.xml new file mode 100644 index 0000000..d914216 --- /dev/null +++ b/app/src/main/res/drawable/ic_notification.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6b78462 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6b78462 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.xml b/app/src/main/res/mipmap-hdpi/ic_launcher.xml new file mode 100644 index 0000000..7d9d46b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.xml b/app/src/main/res/mipmap-mdpi/ic_launcher.xml new file mode 100644 index 0000000..7d9d46b --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.xml b/app/src/main/res/mipmap-xhdpi/ic_launcher.xml new file mode 100644 index 0000000..7d9d46b --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.xml b/app/src/main/res/mipmap-xxhdpi/ic_launcher.xml new file mode 100644 index 0000000..7d9d46b --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.xml b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.xml new file mode 100644 index 0000000..7d9d46b --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + +