44 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
Michachatz
c3c2ccfdd9 Add FreeDroidWarn import to MainActivity 2026-03-24 08:04:30 +01:00
Michachatz
38fb4d162b Change Maven repository URL from jetpack.io to jitpack.io 2026-03-24 08:00:10 +01:00
Michachatz
3c3c18104b Fix case sensitivity in FreeDroidWarn dependency 2026-03-24 07:58:25 +01:00
Michachatz
4e1ee7d6a7 Fix version tag format for FreeDroidWarn dependency 2026-03-24 07:56:42 +01:00
Michachatz
64ba020eae Update FreeDroidWarn dependency version to 1.10 2026-03-24 07:52:22 +01:00
Michachatz
b35d7bd67c Add JitPack and Jetpack repositories to settings 2026-03-24 07:48:05 +01:00
Michachatz
d3dd098639 Remove unused repositories block in build.gradle
Removed unused repositories block from allprojects.
2026-03-24 07:45:59 +01:00
Michachatz
a8f6c7f946 Re-add allprojects block with repositories configuration 2026-03-24 07:43:30 +01:00
Michachatz
765ccc9250 Add JitPack repository configuration
Add JitPack repository to all projects.
2026-03-24 07:41:22 +01:00
Michachatz
c9d6cf27ec Show warning on upgrade in MainActivity 2026-03-24 07:37:04 +01:00
Michachatz
7b7579d416 Add FreeDroidWarn library dependency 2026-03-24 07:34:09 +01:00
Michachatz
d452d7f7ee Merge pull request #57 from Michatec/renovate/gradle-9.x
Update Gradle to v9.4.1
2026-03-19 17:34:25 +01:00
renovate[bot]
2bb3d22cab Update Gradle to v9.4.1 2026-03-19 09:40:18 +00:00
Michachatz
b4ed3e107c Merge pull request #53 from Michatec/renovate/androidx.media3-media3-datasource-okhttp-1.x
Update dependency androidx.media3:media3-datasource-okhttp to v1.9.3
2026-03-17 08:09:16 +01:00
Michachatz
ef843b601a Merge pull request #54 from Michatec/renovate/androidx.media3-media3-exoplayer-1.x
Update dependency androidx.media3:media3-exoplayer to v1.9.3
2026-03-17 08:09:05 +01:00
renovate[bot]
219d54f4e4 Update dependency androidx.media3:media3-exoplayer to v1.9.3 2026-03-17 07:08:20 +00:00
Michachatz
c8a39bf2d7 Merge pull request #55 from Michatec/renovate/androidx.media3-media3-exoplayer-hls-1.x
Update dependency androidx.media3:media3-exoplayer-hls to v1.9.3
2026-03-17 08:07:59 +01:00
renovate[bot]
81ff920c2c Update dependency androidx.media3:media3-exoplayer-hls to v1.9.3 2026-03-17 07:07:36 +00:00
renovate[bot]
f4a5209e14 Update dependency androidx.media3:media3-datasource-okhttp to v1.9.3 2026-03-17 07:07:33 +00:00
Michachatz
c7b7bdcbed Merge pull request #56 from Michatec/renovate/androidx.media3-media3-session-1.x
Update dependency androidx.media3:media3-session to v1.9.3
2026-03-17 08:07:10 +01:00
Michachatz
eae9176f21 Merge pull request #52 from Michatec/renovate/kotlin-monorepo
Update plugin org.jetbrains.kotlin.android to v2.3.20
2026-03-17 08:06:54 +01:00
renovate[bot]
b3a833fa44 Update dependency androidx.media3:media3-session to v1.9.3 2026-03-16 20:52:12 +00:00
renovate[bot]
35a8ed46ff Update plugin org.jetbrains.kotlin.android to v2.3.20 2026-03-16 09:20:44 +00:00
Michachatz
fd18943878 Change copyright year to 2026
Update copyright year in the LICENSE file.
2026-03-13 06:30:07 +01:00
Michachatz
094fb508e2 Merge pull request #51 from Michatec/renovate/androidx.core-core-ktx-1.x
Update dependency androidx.core:core-ktx to v1.18.0
2026-03-13 06:28:00 +01:00
renovate[bot]
874cf1eb39 Update dependency androidx.core:core-ktx to v1.18.0 2026-03-13 05:26:26 +00:00
Michachatz
211cb387ad Merge pull request #50 from Michatec/renovate/androidx.activity-activity-ktx-1.x
Update dependency androidx.activity:activity-ktx to v1.13.0
2026-03-13 06:25:57 +01:00
renovate[bot]
5481a06343 Update dependency androidx.activity:activity-ktx to v1.13.0 2026-03-11 18:10:24 +00:00
fbafcb9773 - New Version Released
- Updated MainActivity.kt
2026-03-08 16:03:28 +01:00
27c405fd58 - Added Daemon Update
- Updated Icons in PlayerService.kt
2026-03-08 15:45:03 +01:00
Michachatz
c2cba26c66 Merge pull request #49 from Michatec/renovate/gradle-9.x
Update Gradle to v9.4.0
2026-03-04 21:15:21 +01:00
renovate[bot]
9224e45b18 Update Gradle to v9.4.0 2026-03-04 13:05:49 +00:00
Michachatz
90164d7e6a Merge pull request #48 from Michatec/renovate/com.android.library-9.x
Update plugin com.android.library to v9.1.0
2026-03-04 07:36:28 +01:00
renovate[bot]
45e208e7a9 Update plugin com.android.library to v9.1.0 2026-03-04 06:35:34 +00:00
Michachatz
1d040e3edc Merge pull request #47 from Michatec/renovate/com.android.application-9.x
Update plugin com.android.application to v9.1.0
2026-03-04 07:35:02 +01:00
renovate[bot]
efe17bc82b Update plugin com.android.application to v9.1.0 2026-03-03 16:37:22 +00:00
Michachatz
8b06309c64 Merge pull request #46 from Michatec/renovate/major-github-artifact-actions
Update actions/upload-artifact action to v7
2026-02-27 09:41:56 +01:00
669fd4683c - Release of 14.1
- Added new features
- Some bug fixes
2026-02-27 09:40:59 +01:00
26b155a721 - Added the Station update button to the SettingsFragment.kt
- Added the strings for the language: german and english
2026-02-27 08:59:04 +01:00
renovate[bot]
99f7863749 Update actions/upload-artifact action to v7 2026-02-26 21:01:43 +00:00
27 changed files with 184 additions and 179 deletions

View File

@@ -67,7 +67,7 @@ jobs:
mv app-release-aligned.apk app-release.apk
- name: Upload artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: app-release
path: app-release.apk

4
.gitignore vendored
View File

@@ -4,4 +4,6 @@
/local.properties
/.idea
/build
/captures
/captures
/gradle/gradle-daemon-jvm.properties
/.kotlin

View File

@@ -1,7 +1,7 @@
The MIT License (MIT)
=====================
Copyright (c) 2025 - Michatec
Copyright (c) 2026 - Michatec
--------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy

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 141
versionName '14.1'
versionCode 143
versionName '14.3'
resourceConfigurations += ['en', 'de', 'el', 'nl', 'pl', 'ru','uk', 'ja', 'da', 'fr']
}
@@ -55,24 +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.17.0'
implementation 'androidx.activity:activity-ktx:1.12.4'
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.2'
implementation 'androidx.media3:media3-exoplayer-hls:1.9.2'
implementation 'androidx.media3:media3-session:1.9.2'
implementation 'androidx.media3:media3-datasource-okhttp:1.9.2'
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 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

@@ -60,7 +60,6 @@ object Keys {
// preferences
const val PREF_RADIO_BROWSER_API: String = "RADIO_BROWSER_API"
const val PREF_ONE_TIME_HOUSEKEEPING_NECESSARY: String = "ONE_TIME_HOUSEKEEPING_NECESSARY_VERSIONCODE_95" // increment to current app version code to trigger housekeeping that runs only once
const val PREF_THEME_SELECTION: String = "THEME_SELECTION"
const val PREF_LAST_UPDATE_COLLECTION: String = "LAST_UPDATE_COLLECTION"
const val PREF_COLLECTION_SIZE: String = "COLLECTION_SIZE"
@@ -79,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
@@ -138,7 +136,7 @@ object Keys {
const val FOLDER_AUDIO: String = "audio"
const val FOLDER_IMAGES: String = "images"
const val FOLDER_TEMP: String = "temp"
const val URLRADIO_LEGACY_FOLDER_COLLECTION: String = "Collection"
const val RADIO_LEGACY_FOLDER_COLLECTION: String = "Collection"
// file names and extensions
const val COLLECTION_FILE: String = "collection.json"
@@ -150,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
/*
* MainActivity class
@@ -39,6 +39,9 @@ class MainActivity : AppCompatActivity() {
/* Overrides onCreate from AppCompatActivity */
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Free Android
FreeDroidWarn.showWarningOnUpgrade(this, BuildConfig.VERSION_CODE)
// set up views
setContentView(R.layout.activity_main)
@@ -60,6 +63,16 @@ class MainActivity : AppCompatActivity() {
}
/* Overrides onResume from AppCompatActivity */
override fun onResume() {
try {
super.onResume()
} catch (_: ClassCastException) {
// Do nothing
}
}
/* Overrides onSupportNavigateUp from AppCompatActivity */
override fun onSupportNavigateUp(): Boolean {
// Taken from: https://developer.android.com/guide/navigation/navigation-ui#action_bar

View File

@@ -265,7 +265,7 @@ class PlayerFragment : Fragment(),
// handle navigation arguments
handleNavigationArguments()
// // handle start intent - if started via tap on rss link
// handleStartIntent()
handleStartIntent()
// start watching for changes in shared preferences
PreferencesHelper.registerPreferenceChangeListener(this as SharedPreferences.OnSharedPreferenceChangeListener)
}
@@ -636,16 +636,16 @@ class PlayerFragment : Fragment(),
collectionViewModel.collectionLiveData.observe(this) {
// update collection
collection = it
//// // updates current station in player views
//// playerState = PreferencesHelper.loadPlayerState()
// updates current station in player views
playerState = PreferencesHelper.loadPlayerState()
// // get station
// val station: Station = CollectionHelper.getStation(collection, playerState.stationUuid)
val station: Station = CollectionHelper.getStation(collection, playerState.stationUuid)
// // update player views
// layout.updatePlayerViews(activity as Context, station, playerState.isPlaying)
//// // handle start intent
//// handleStartIntent()
//// // handle navigation arguments
//// handleNavigationArguments()
layout.updatePlayerViews(activity as Context, station, playerState.isPlaying)
// handle start intent
handleStartIntent()
// handle navigation arguments
handleNavigationArguments()
}
collectionViewModel.collectionSizeLiveData.observe(this) {
// size of collection changed

View File

@@ -280,19 +280,8 @@ class PlayerService : MediaLibraryService() {
val updatedMediaItems: List<MediaItem> =
mediaItems.map { mediaItem ->
CollectionHelper.getItem(this@PlayerService, collection, mediaItem.mediaId)
// if (mediaItem.requestMetadata.searchQuery != null)
// getMediaItemFromSearchQuery(mediaItem.requestMetadata.searchQuery!!)
// else MediaItemTree.getItem(mediaItem.mediaId) ?: mediaItem
}
return Futures.immediateFuture(updatedMediaItems)
// val updatedMediaItems = mediaItems.map { mediaItem ->
// mediaItem.buildUpon().apply {
// setUri(mediaItem.requestMetadata.mediaUri)
// }.build()
// }
// return Futures.immediateFuture(updatedMediaItems)
}
@@ -411,19 +400,19 @@ class PlayerService : MediaLibraryService() {
customLayout: ImmutableList<CommandButton>,
showPauseButton: Boolean
): ImmutableList<CommandButton> {
val seekToPreviousCommandButton = CommandButton.Builder()
val seekToPreviousCommandButton = CommandButton.Builder(CommandButton.ICON_UNDEFINED)
.setPlayerCommand(Player.COMMAND_SEEK_TO_PREVIOUS)
.setIconResId(R.drawable.ic_notification_skip_to_previous_36dp)
.setCustomIconResId(R.drawable.ic_notification_skip_to_previous_36dp)
.setEnabled(true)
.build()
val playCommandButton = CommandButton.Builder()
val playCommandButton = CommandButton.Builder(CommandButton.ICON_UNDEFINED)
.setPlayerCommand(Player.COMMAND_PLAY_PAUSE)
.setIconResId(if (player.isPlaying) R.drawable.ic_notification_stop_36dp else R.drawable.ic_notification_play_36dp)
.setCustomIconResId(if (player.isPlaying) R.drawable.ic_notification_stop_36dp else R.drawable.ic_notification_play_36dp)
.setEnabled(true)
.build()
val seekToNextCommandButton = CommandButton.Builder()
val seekToNextCommandButton = CommandButton.Builder(CommandButton.ICON_UNDEFINED)
.setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT)
.setIconResId(R.drawable.ic_notification_skip_to_next_36dp)
.setCustomIconResId(R.drawable.ic_notification_skip_to_next_36dp)
.setEnabled(true)
.build()
val commandButtons: MutableList<CommandButton> = mutableListOf(
@@ -498,15 +487,10 @@ class PlayerService : MediaLibraryService() {
if (!playWhenReady) {
when (reason) {
Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM -> {
// playback reached end: stop / end playback
stopSelf()
}
else -> {
// playback has been paused by user or OS: update media session and save state
// PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST or
// PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS or
// PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY or
// PLAY_WHEN_READY_CHANGE_REASON_REMOTE
// handlePlaybackChange(PlaybackStateCompat.STATE_PAUSED)
stopSelf()
}
}
}
@@ -585,7 +569,6 @@ class PlayerService : MediaLibraryService() {
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName)
intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
sendBroadcast(intent)
// note: remember to broadcast AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, when not needed anymore
}
}
}

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
@@ -116,16 +115,21 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
}
// // set up "Update Stations" preference
// val preferenceUpdateCollection: Preference = Preference(activity as Context)
// preferenceUpdateCollection.title = getString(R.string.pref_update_collection_title)
// preferenceUpdateCollection.setIcon(R.drawable.ic_refresh_24dp)
// preferenceUpdateCollection.summary = getString(R.string.pref_update_collection_summary)
// preferenceUpdateCollection.setOnPreferenceClickListener {
// // show dialog
// YesNoDialog(this).show(context = activity as Context, type = Keys.DIALOG_UPDATE_COLLECTION, message = R.string.dialog_yes_no_message_update_collection, yesButton = R.string.dialog_yes_no_positive_button_update_collection)
// return@setOnPreferenceClickListener true
// }
// set up "Update Stations" preference
val preferenceUpdateCollection = Preference(activity as Context)
preferenceUpdateCollection.title = getString(R.string.pref_update_collection_title)
preferenceUpdateCollection.setIcon(R.drawable.ic_refresh_24dp)
preferenceUpdateCollection.summary = getString(R.string.pref_update_collection_summary)
preferenceUpdateCollection.setOnPreferenceClickListener {
// show dialog
YesNoDialog(this).show(
context = activity as Context,
type = Keys.DIALOG_UPDATE_COLLECTION,
message = R.string.dialog_yes_no_message_update_collection,
yesButton = R.string.dialog_yes_no_positive_button_update_collection,
)
return@setOnPreferenceClickListener true
}
// set up "M3U Export" preference
@@ -299,7 +303,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
screen.addPreference(preferenceThemeSelection)
screen.addPreference(preferenceCategoryMaintenance)
screen.addPreference(preferenceUpdateStationImages)
// screen.addPreference(preferenceUpdateCollection)
screen.addPreference(preferenceUpdateCollection)
screen.addPreference(preferenceCategoryImportExport)
screen.addPreference(preferenceM3uExport)
screen.addPreference(preferencePlsExport)
@@ -422,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)
}
}
@@ -440,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(
@@ -461,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

@@ -19,6 +19,7 @@ import android.text.method.ScrollingMovementMethod
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -81,8 +82,7 @@ class ErrorDialog {
// add okay button
builder.setPositiveButton(R.string.dialog_generic_button_okay) { _, _ ->
// listen for click on okay button
// do nothing
Toast.makeText(context, R.string.dialog_generic_button_okay, Toast.LENGTH_SHORT).show()
}
// display error dialog

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

@@ -644,48 +644,6 @@ object CollectionHelper {
LocalBroadcastManager.getInstance(context).sendBroadcast(collectionChangedIntent)
}
// /* Creates MediaMetadata for a single station - used in media session*/
// fun buildStationMediaMetadata(context: Context, station: Station, metadata: String): MediaMetadataCompat {
// return MediaMetadataCompat.Builder().apply {
// putString(MediaMetadataCompat.METADATA_KEY_ARTIST, station.name)
// putString(MediaMetadataCompat.METADATA_KEY_TITLE, metadata)
// putString(MediaMetadataCompat.METADATA_KEY_ALBUM, context.getString(R.string.app_name))
// putString(MediaMetadataCompat.METADATA_KEY_MEDIA_URI, station.getStreamUri())
// putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, ImageHelper.getScaledStationImage(context, station.image, Keys.SIZE_COVER_LOCK_SCREEN))
// //putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, station.image)
// }.build()
// }
//
//
// /* Creates MediaItem for a station - used by collection provider */
// fun buildStationMediaMetaItem(context: Context, station: Station): MediaBrowserCompat.MediaItem {
// val mediaDescriptionBuilder = MediaDescriptionCompat.Builder()
// mediaDescriptionBuilder.setMediaId(station.uuid)
// mediaDescriptionBuilder.setTitle(station.name)
// mediaDescriptionBuilder.setIconBitmap(ImageHelper.getScaledStationImage(context, station.image, Keys.SIZE_COVER_LOCK_SCREEN))
// // mediaDescriptionBuilder.setIconUri(station.image.toUri())
// return MediaBrowserCompat.MediaItem(mediaDescriptionBuilder.build(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
// }
//
//
// /* Creates description for a station - used in MediaSessionConnector */
// fun buildStationMediaDescription(context: Context, station: Station, metadata: String): MediaDescriptionCompat {
// val coverBitmap: Bitmap = ImageHelper.getScaledStationImage(context, station.image, Keys.SIZE_COVER_LOCK_SCREEN)
// val extras: Bundle = Bundle()
// extras.putParcelable(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, coverBitmap)
// extras.putParcelable(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, coverBitmap)
// return MediaDescriptionCompat.Builder().apply {
// setMediaId(station.uuid)
// setIconBitmap(coverBitmap)
// setIconUri(station.image.toUri())
// setTitle(metadata)
// setSubtitle(station.name)
// setExtras(extras)
// }.build()
// }
/* Creates a MediaItem with MediaMetadata for a single radio station - used to prepare player */
fun buildMediaItem(context: Context, station: Station): MediaItem {
// put uri in RequestMetadata - credit: https://stackoverflow.com/a/70103460

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
/*
@@ -246,7 +245,7 @@ object FileHelper {
File(activity.getExternalFilesDir(Keys.FOLDER_COLLECTION), Keys.COLLECTION_M3U_FILE)
if (!m3uFile.exists()) {
m3uFile = File(
activity.getExternalFilesDir(Keys.URLRADIO_LEGACY_FOLDER_COLLECTION),
activity.getExternalFilesDir(Keys.RADIO_LEGACY_FOLDER_COLLECTION),
Keys.COLLECTION_M3U_FILE
)
}
@@ -270,7 +269,7 @@ object FileHelper {
File(activity.getExternalFilesDir(Keys.FOLDER_COLLECTION), Keys.COLLECTION_PLS_FILE)
if (!plsFile.exists()) {
plsFile = File(
activity.getExternalFilesDir(Keys.URLRADIO_LEGACY_FOLDER_COLLECTION),
activity.getExternalFilesDir(Keys.RADIO_LEGACY_FOLDER_COLLECTION),
Keys.COLLECTION_PLS_FILE
)
}
@@ -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,14 +113,14 @@ 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> =
InetAddress.getAllByName(Keys.RADIO_BROWSER_API_BASE)
// select a random address
serverAddressList[Random().nextInt(serverAddressList.size)].canonicalHostName
} catch (e: UnknownHostException) {
} catch (_: UnknownHostException) {
Keys.RADIO_BROWSER_API_DEFAULT
}
PreferencesHelper.saveRadioBrowserApiAddress(serverAddress)

View File

@@ -1,17 +1,3 @@
/*
* UiHelper.kt
* Implements the UiHelper object
* A UiHelper provides helper methods for User Interface related tasks
*
* This file is part of
* TRANSISTOR - Radio App for Android
*
* Copyright (c) 2015-22 - Y20K.org
* Licensed under the MIT-License
* http://opensource.org/licenses/MIT
*/
package com.michatec.radio.helpers
import android.content.Context

View File

@@ -313,18 +313,6 @@ data class LayoutHolder(var rootView: View) {
isBuffering = buffering
}
/* Toggles visibility of player depending on playback state - hiding it when playback is stopped (not paused or playing) */
// fun togglePlayerVisibility(context: Context, playbackState: Int): Boolean {
// when (playbackState) {
// PlaybackStateCompat.STATE_STOPPED -> return hidePlayer(context)
// PlaybackStateCompat.STATE_NONE -> return hidePlayer(context)
// PlaybackStateCompat.STATE_ERROR -> return hidePlayer(context)
// else -> return showPlayer(context)
// }
// }
/* Toggles visibility of the download progress indicator */
fun toggleDownloadProgressIndicator() {
when (PreferencesHelper.loadActiveDownloads()) {

View File

@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/icon_default"
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4
7.58,4 4,7.58 4,12h2
c0,-3.31 2.69,-6 6,-6
1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z
M6.35,17.65C7.8,19.1 9.79,20 12,20
c4.42,0 8,-3.58 8,-8h-2
c0,3.31 -2.69,6 -6,6
-1.66,0 -3.14,-0.69 -4.22,-1.78L11,13H4v7l2.35,-2.35z"/>
</vector>

View File

@@ -51,6 +51,10 @@
<string name="player_sheet_h2_station_metadata">Momentan läuft</string>
<string name="player_sheet_h2_stream_url">Streaming-Adresse</string>
<!-- Settings -->
<string name="pref_update_collection_title">Senderinformationnen aktualisieren</string>
<string name="pref_update_collection_summary">Die neueste Version aller Senderinformationen herunterladen.</string>
<string name="dialog_yes_no_message_update_collection">Die neueste Version aller Senderinformationen herunterladen?</string>
<string name="dialog_yes_no_positive_button_update_collection">Aktualisieren</string>
<string name="pref_advanced_title">Erweitert</string>
<string name="pref_app_version_summary">Version</string>
<string name="pref_app_version_title">App-Version</string>

View File

@@ -59,6 +59,10 @@
<string name="player_sheet_h2_stream_url">Streaming link</string>
<!-- Settings -->
<string name="pref_update_collection_title">Update Stations</string>
<string name="pref_update_collection_summary">Download latest version of all station.</string>
<string name="dialog_yes_no_message_update_collection">Download latest version of all station?</string>
<string name="dialog_yes_no_positive_button_update_collection">Update</string>
<string name="pref_advanced_title">Advanced</string>
<string name="pref_app_version_summary">Version</string>
<string name="pref_app_version_title">App Version</string>

View File

@@ -1,11 +1,11 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '9.0.1' apply false
id 'com.android.library' version '9.0.1' apply false
id 'org.jetbrains.kotlin.android' version "2.3.10" 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) {
delete rootProject.buildDir()
}
}

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" }

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

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/HEAD/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/.

View File

@@ -3,13 +3,19 @@ pluginManagement {
google()
mavenCentral()
gradlePluginPortal()
maven { url 'https://jitpack.io' }
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}