4 Commits

Author SHA1 Message Date
9140b54a23 Update AndroidManifest to resolve App Link verification issues
- Update `tools:targetApi` from "tiramisu" to "33"
- Set `android:autoVerify="false"` for the MIME-type based intent-filter
- Add `tools:ignore="AppLinkUrlError"` to the wildcard host in the file-extension intent-filter
- Add comments clarifying App Link behavior for playlist links
2026-03-24 17:54:48 +01:00
57c4075f19 - Updated .gitignore 2026-03-24 12:40:25 +01:00
5334b88f1d Refactor code and clean up unused resources
- Replace `suspendCoroutine` with `suspendCancellableCoroutine` in helper classes for better cancellation support.
- Replace `bundleOf` with manual `Bundle` initialization in `MediaControllerExt` and `SettingsFragment`.
- Remove unused constants in `Keys.kt` and redundant `onTerminate` override in `Radio.kt`.
- Clean up syntax and remove semicolons in `MainActivity.kt`.
2026-03-24 12:36:04 +01:00
5978aab0aa - New Version Released.
- Updated the Gradle Structure.
- libs.versions.toml added.
2026-03-24 12:28:15 +01:00
13 changed files with 97 additions and 57 deletions

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@
/.idea
/build
/captures
/gradle/gradle-daemon-jvm.properties
/.kotlin

View File

@@ -1,5 +1,5 @@
plugins {
id 'com.android.application'
alias libs.plugins.android.application
id 'kotlin-parcelize'
}
@@ -19,8 +19,8 @@ android {
applicationId 'com.michatec.radio'
minSdk 28
targetSdk 36
versionCode 142
versionName '14.2'
versionCode 143
versionName '14.3'
resourceConfigurations += ['en', 'de', 'el', 'nl', 'pl', 'ru','uk', 'ja', 'da', 'fr']
}
@@ -55,26 +55,26 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// Google Stuff //
implementation 'com.google.android.material:material:1.13.0'
implementation 'com.google.code.gson:gson:2.13.2'
implementation libs.material
implementation libs.gson
// AndroidX Stuff //
implementation 'androidx.core:core-ktx:1.18.0'
implementation 'androidx.activity:activity-ktx:1.13.0'
implementation 'androidx.palette:palette-ktx:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.media:media:1.7.1'
implementation 'androidx.media3:media3-exoplayer:1.9.3'
implementation 'androidx.media3:media3-exoplayer-hls:1.9.3'
implementation 'androidx.media3:media3-session:1.9.3'
implementation 'androidx.media3:media3-datasource-okhttp:1.9.3'
implementation 'androidx.navigation:navigation-fragment-ktx:2.9.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.9.7'
implementation 'androidx.work:work-runtime-ktx:2.11.1'
implementation libs.core.ktx
implementation libs.activity.ktx
implementation libs.palette.ktx
implementation libs.preference.ktx
implementation libs.media
implementation libs.media3.exoplayer
implementation libs.media3.exoplayer.hls
implementation libs.media3.session
implementation libs.media3.datasource.okhttp
implementation libs.navigation.fragment.ktx
implementation libs.navigation.ui.ktx
implementation libs.work.runtime.ktx
implementation 'com.github.woheller69:FreeDroidWarn:V1.10'
implementation libs.freedroidwarn
// Volley HTTP request //
implementation 'com.android.volley:volley:1.2.1'
implementation 'androidx.compose.material3:material3:1.4.0'
implementation libs.volley
implementation libs.material3
}

View File

@@ -21,7 +21,7 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:targetApi="tiramisu">
tools:targetApi="33">
<!-- ANDROID AUTO SUPPORT -->
<!-- https://developer.android.com/training/auto/audio/ -->
@@ -58,6 +58,7 @@
</intent-filter>
<!-- react to playlist-links based on file extension -->
<!-- This is intended as an App Link for specific extensions -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
@@ -66,14 +67,16 @@
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="*" />
<data android:host="*"
tools:ignore="AppLinkUrlError" />
<data android:pathPattern=".*\\.m3u" />
<data android:pathPattern=".*\\.m3u8" />
<data android:pathPattern=".*\\.pls" />
</intent-filter>
<!-- react to playlist-links based on mimetype -->
<intent-filter android:autoVerify="true">
<!-- Note: MIME types prevent strict App Link verification, but are kept as requested -->
<intent-filter android:autoVerify="false">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />

View File

@@ -78,7 +78,6 @@ object Keys {
// default const values
const val DEFAULT_SIZE_OF_METADATA_HISTORY: Int = 25
const val DEFAULT_MAX_LENGTH_OF_METADATA_ENTRY: Int = 127
const val DEFAULT_DOWNLOAD_OVER_MOBILE: Boolean = false
const val ACTIVE_DOWNLOADS_EMPTY: String = "zero"
const val DEFAULT_MAX_RECONNECTION_COUNT: Int = 30
const val LARGE_BUFFER_SIZE_MULTIPLIER: Int = 8
@@ -149,9 +148,6 @@ object Keys {
const val RADIO_BROWSER_API_BASE: String = "all.api.radio-browser.info"
const val RADIO_BROWSER_API_DEFAULT: String = "de1.api.radio-browser.info"
// locations
const val LOCATION_DEFAULT_STATION_IMAGE: String = "android.resource://com.michatec.radio/drawable/ic_default_station_image_24dp"
// sizes (in dp)
const val SIZE_STATION_IMAGE_CARD: Int = 72
const val SIZE_STATION_IMAGE_MAXIMUM: Int = 640

View File

@@ -25,7 +25,7 @@ import androidx.navigation.ui.navigateUp
import com.michatec.radio.helpers.AppThemeHelper
import com.michatec.radio.helpers.FileHelper
import com.michatec.radio.helpers.PreferencesHelper
import org.woheller69.freeDroidWarn.FreeDroidWarn;
import org.woheller69.freeDroidWarn.FreeDroidWarn
/*
* MainActivity class
@@ -41,7 +41,7 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
// Free Android
FreeDroidWarn.showWarningOnUpgrade(this, BuildConfig.VERSION_CODE);
FreeDroidWarn.showWarningOnUpgrade(this, BuildConfig.VERSION_CODE)
// set up views
setContentView(R.layout.activity_main)

View File

@@ -34,9 +34,4 @@ class Radio : Application() {
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
}
/* Implements onTerminate */
override fun onTerminate() {
super.onTerminate()
}
}

View File

@@ -28,7 +28,6 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import androidx.navigation.fragment.findNavController
import androidx.preference.*
import com.google.android.material.snackbar.Snackbar
@@ -427,9 +426,9 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
val sourceUri: Uri? = result.data?.data
if (sourceUri != null) {
// open and import OPML in player fragment
val bundle: Bundle = bundleOf(
Keys.ARG_RESTORE_COLLECTION to "$sourceUri"
)
val bundle = Bundle().apply {
putString(Keys.ARG_RESTORE_COLLECTION, "$sourceUri")
}
this.findNavController().navigate(R.id.player_destination, bundle)
}
}
@@ -445,7 +444,9 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
Snackbar.LENGTH_LONG
).show()
// update collection in player screen
val bundle: Bundle = bundleOf(Keys.ARG_UPDATE_COLLECTION to true)
val bundle = Bundle().apply {
putBoolean(Keys.ARG_UPDATE_COLLECTION, true)
}
this.findNavController().navigate(R.id.player_destination, bundle)
} else {
ErrorDialog().show(
@@ -466,9 +467,9 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
Snackbar.LENGTH_LONG
).show()
// update collection in player screen
val bundle: Bundle = bundleOf(
Keys.ARG_UPDATE_IMAGES to true
)
val bundle = Bundle().apply {
putBoolean(Keys.ARG_UPDATE_IMAGES, true)
}
this.findNavController().navigate(R.id.player_destination, bundle)
} else {
ErrorDialog().show(

View File

@@ -16,7 +16,6 @@ package com.michatec.radio.extensions
import android.content.Context
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.media3.session.MediaController
import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionResult
@@ -71,8 +70,11 @@ fun MediaController.play(context: Context, station: Station) {
/* Starts playback with of a stream url */
fun MediaController.playStreamDirectly(streamUri: String) {
val bundle = Bundle().apply {
putString(Keys.KEY_STREAM_URI, streamUri)
}
sendCustomCommand(
SessionCommand(Keys.CMD_PLAY_STREAM, Bundle.EMPTY),
bundleOf(Pair(Keys.KEY_STREAM_URI, streamUri))
bundle
)
}

View File

@@ -33,7 +33,6 @@ import kotlinx.coroutines.Dispatchers.IO
import java.io.*
import java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
/*
@@ -292,7 +291,7 @@ object FileHelper {
collection: Collection,
lastUpdate: Date
) {
return suspendCoroutine { cont ->
return suspendCancellableCoroutine { cont ->
cont.resume(saveCollection(context, collection, lastUpdate))
}
}
@@ -311,7 +310,7 @@ object FileHelper {
originalFileUri: Uri,
targetFileUri: Uri
): Boolean {
return suspendCoroutine { cont ->
return suspendCancellableCoroutine { cont ->
cont.resume(copyFile(context, originalFileUri, targetFileUri))
}
}
@@ -319,7 +318,7 @@ object FileHelper {
/* Suspend function: Exports collection of stations as M3U file - local backup copy */
suspend fun backupCollectionAsM3uSuspended(context: Context, collection: Collection) {
return suspendCoroutine { cont ->
return suspendCancellableCoroutine { cont ->
Log.v(TAG, "Backing up collection as M3U - Thread: ${Thread.currentThread().name}")
// create M3U string
val m3uString: String = CollectionHelper.createM3uString(collection)
@@ -338,7 +337,7 @@ object FileHelper {
/* Suspend function: Exports collection of stations as PLS file - local backup copy */
suspend fun backupCollectionAsPlsSuspended(context: Context, collection: Collection) {
return suspendCoroutine { cont ->
return suspendCancellableCoroutine { cont ->
Log.v(TAG, "Backing up collection as PLS - Thread: ${Thread.currentThread().name}")
// create PLS string
val plsString: String = CollectionHelper.createPlsString(collection)

View File

@@ -19,13 +19,13 @@ import android.net.ConnectivityManager
import android.net.Network
import android.util.Log
import com.michatec.radio.Keys
import kotlinx.coroutines.suspendCancellableCoroutine
import java.net.HttpURLConnection
import java.net.InetAddress
import java.net.URL
import java.net.UnknownHostException
import java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
/*
@@ -105,7 +105,7 @@ object NetworkHelper {
/* Suspend function: Detects content type (mime type) from given URL string - async using coroutine */
suspend fun detectContentTypeSuspended(urlString: String): ContentType {
return suspendCoroutine { cont ->
return suspendCancellableCoroutine { cont ->
cont.resume(detectContentType(urlString))
}
}
@@ -113,7 +113,7 @@ object NetworkHelper {
/* Suspend function: Gets a random radio-browser.info api address - async using coroutine */
suspend fun getRadioBrowserServerSuspended(): String {
return suspendCoroutine { cont ->
return suspendCancellableCoroutine { cont ->
val serverAddress: String = try {
// get all available radio browser servers
val serverAddressList: Array<InetAddress> =

View File

@@ -1,9 +1,9 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '9.1.0' apply false
id 'com.android.library' version '9.1.0' apply false
id 'org.jetbrains.kotlin.android' version "2.3.20" apply false
alias libs.plugins.android.application apply false
alias libs.plugins.android.library apply false
alias libs.plugins.jetbrains.kotlin.android apply false
}
tasks.register('clean', Delete) {

42
gradle/libs.versions.toml Normal file
View File

@@ -0,0 +1,42 @@
[versions]
activityKtx = "1.13.0"
agp = "9.1.0"
coreKtx = "1.18.0"
freedroidwarn = "V1.10"
gradleToolchainsFoojayResolverConvention = "1.0.0"
gson = "2.13.2"
kotlin = "2.3.20"
material = "1.13.0"
material3 = "1.4.0"
media = "1.7.1"
media3 = "1.9.3"
navigation = "2.9.7"
paletteKtx = "1.0.0"
preferenceKtx = "1.2.1"
volley = "1.2.1"
workRuntimeKtx = "2.11.1"
[libraries]
activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" }
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
freedroidwarn = { group = "com.github.woheller69", name = "FreeDroidWarn", version.ref = "freedroidwarn" }
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
media = { group = "androidx.media", name = "media", version.ref = "media" }
media3-datasource-okhttp = { group = "androidx.media3", name = "media3-datasource-okhttp", version.ref = "media3" }
media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
media3-exoplayer-hls = { group = "androidx.media3", name = "media3-exoplayer-hls", version.ref = "media3" }
media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation" }
navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation" }
palette-ktx = { group = "androidx.palette", name = "palette-ktx", version.ref = "paletteKtx" }
preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preferenceKtx" }
volley = { group = "com.android.volley", name = "volley", version.ref = "volley" }
work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
foojay = { id = "org.gradle.toolchains.foojay-resolver-convention", version.ref = "gradleToolchainsFoojayResolverConvention" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

2
gradlew vendored
View File

@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.