34 Commits

Author SHA1 Message Date
Michachatz
ae16b12d28 Merge pull request #44 from Michatec/renovate/com.android.application-9.x
Update plugin com.android.application to v9.0.1
2026-02-15 20:39:40 +01:00
renovate[bot]
0697ee0dca Update plugin com.android.application to v9.0.1 2026-02-15 19:39:32 +00:00
Michachatz
27e324decc Merge pull request #43 from Michatec/renovate/androidx.activity-activity-ktx-1.x
Update dependency androidx.activity:activity-ktx to v1.12.4
2026-02-15 20:39:24 +01:00
Michachatz
618914617e Merge pull request #45 from Michatec/renovate/com.android.library-9.x
Update plugin com.android.library to v9.0.1
2026-02-15 20:39:12 +01:00
renovate[bot]
e81aa10125 Update plugin com.android.library to v9.0.1 2026-02-13 22:32:52 +00:00
renovate[bot]
44d25f92a0 Update dependency androidx.activity:activity-ktx to v1.12.4 2026-02-11 18:14:29 +00:00
Michachatz
95bf0e5c56 Merge pull request #38 from Michatec/renovate/kotlin-monorepo
Update plugin org.jetbrains.kotlin.android to v2.3.10
2026-02-08 12:52:02 +01:00
Michachatz
dddd180136 Merge pull request #39 from Michatec/renovate/androidx.media3-media3-datasource-okhttp-1.x
Update dependency androidx.media3:media3-datasource-okhttp to v1.9.2
2026-02-08 12:51:50 +01:00
Michachatz
46ddd4feca Merge pull request #40 from Michatec/renovate/androidx.media3-media3-exoplayer-1.x
Update dependency androidx.media3:media3-exoplayer to v1.9.2
2026-02-08 12:51:37 +01:00
renovate[bot]
e10c8ed68f Update dependency androidx.media3:media3-exoplayer to v1.9.2 2026-02-08 11:51:24 +00:00
Michachatz
6563820a00 Merge pull request #41 from Michatec/renovate/androidx.media3-media3-exoplayer-hls-1.x
Update dependency androidx.media3:media3-exoplayer-hls to v1.9.2
2026-02-08 12:51:04 +01:00
renovate[bot]
594801238a Update dependency androidx.media3:media3-exoplayer-hls to v1.9.2 2026-02-08 11:50:10 +00:00
renovate[bot]
8a1e0ef6c2 Update dependency androidx.media3:media3-datasource-okhttp to v1.9.2 2026-02-08 11:50:07 +00:00
Michachatz
053db2abaa Merge pull request #42 from Michatec/renovate/androidx.media3-media3-session-1.x
Update dependency androidx.media3:media3-session to v1.9.2
2026-02-08 12:49:48 +01:00
renovate[bot]
7e5d5eb6c1 Update dependency androidx.media3:media3-session to v1.9.2 2026-02-06 13:11:45 +00:00
renovate[bot]
1e1efd422c Update plugin org.jetbrains.kotlin.android to v2.3.10 2026-02-05 10:55:36 +00:00
Michachatz
2c1913896d Merge pull request #29 from Michatec/renovate/androidx.media3-media3-datasource-okhttp-1.x
Update dependency androidx.media3:media3-datasource-okhttp to v1.9.1
2026-01-30 18:56:34 +01:00
Michachatz
026e18ae22 Merge pull request #30 from Michatec/renovate/androidx.media3-media3-exoplayer-1.x
Update dependency androidx.media3:media3-exoplayer to v1.9.1
2026-01-30 18:56:18 +01:00
renovate[bot]
3b6ebe5e1b Update dependency androidx.media3:media3-exoplayer to v1.9.1 2026-01-30 17:55:55 +00:00
Michachatz
26eab4c64c Merge pull request #31 from Michatec/renovate/androidx.media3-media3-exoplayer-hls-1.x
Update dependency androidx.media3:media3-exoplayer-hls to v1.9.1
2026-01-30 18:55:43 +01:00
renovate[bot]
75b3deb210 Update dependency androidx.media3:media3-exoplayer-hls to v1.9.1 2026-01-30 17:55:23 +00:00
renovate[bot]
96389a5c81 Update dependency androidx.media3:media3-datasource-okhttp to v1.9.1 2026-01-30 17:55:19 +00:00
Michachatz
1544c07938 Merge pull request #32 from Michatec/renovate/androidx.media3-media3-session-1.x
Update dependency androidx.media3:media3-session to v1.9.1
2026-01-30 18:54:56 +01:00
Michachatz
65e9d1fed4 Merge pull request #33 from Michatec/renovate/androidx.navigation-navigation-fragment-ktx-2.x
Update dependency androidx.navigation:navigation-fragment-ktx to v2.9.7
2026-01-30 18:54:45 +01:00
renovate[bot]
9bfc1661b8 Update dependency androidx.navigation:navigation-fragment-ktx to v2.9.7 2026-01-30 17:54:21 +00:00
Michachatz
9a379675bf Merge pull request #34 from Michatec/renovate/androidx.navigation-navigation-ui-ktx-2.x
Update dependency androidx.navigation:navigation-ui-ktx to v2.9.7
2026-01-30 18:53:49 +01:00
renovate[bot]
529e359f7f Update dependency androidx.navigation:navigation-ui-ktx to v2.9.7 2026-01-30 17:52:58 +00:00
Michachatz
fc3a1f767a Merge pull request #35 from Michatec/renovate/androidx.activity-activity-ktx-1.x
Update dependency androidx.activity:activity-ktx to v1.12.3
2026-01-30 18:52:45 +01:00
Michachatz
9197e5c2a9 Merge pull request #36 from Michatec/renovate/androidx.work-work-runtime-ktx-2.x
Update dependency androidx.work:work-runtime-ktx to v2.11.1
2026-01-30 18:52:34 +01:00
Michachatz
5920327283 Merge pull request #37 from Michatec/renovate/gradle-9.x
Update Gradle to v9.3.1
2026-01-30 18:52:22 +01:00
renovate[bot]
769cbb505f Update Gradle to v9.3.1 2026-01-29 16:34:13 +00:00
renovate[bot]
1f5d61759c Update dependency androidx.work:work-runtime-ktx to v2.11.1 2026-01-29 04:41:58 +00:00
renovate[bot]
5b1d6d96c7 Update dependency androidx.activity:activity-ktx to v1.12.3 2026-01-29 04:41:54 +00:00
renovate[bot]
606154cfb3 Update dependency androidx.media3:media3-session to v1.9.1 2026-01-26 17:46:05 +00:00
36 changed files with 281 additions and 252 deletions

View File

@@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -67,7 +67,25 @@ jobs:
mv app-release-aligned.apk app-release.apk mv app-release-aligned.apk app-release.apk
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v6
with: with:
name: app-release name: app-release
path: app-release.apk path: app-release.apk
publish:
runs-on: ubuntu-latest
needs: build
permissions:
contents: write
steps:
- name: Download artifact
uses: actions/download-artifact@v7
with:
name: app-release
path: app-release.apk
- name: Create release
uses: ncipollo/release-action@v1
with:
artifacts: "app-release.apk"
draft: true

2
.gitignore vendored
View File

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

View File

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

View File

@@ -13,7 +13,7 @@
<details> <details>
<summary>⚙️ Install Radio</summary> <summary>⚙️ Install Radio</summary>
<br> <br>
<a href="https://github.com/michatec/Radio/releases/latest"><img src="https://user-images.githubusercontent.com/15986930/229208526-e5a63be5-0d0b-48ab-a222-9f2f2faf0ee4.png" alt="Preview" height="80px"></a> <a href="https://github.com/michatec/Radio/releases/latest"><img src="https://user-images.githubusercontent.com/15986930/229208526-e5a63be5-0d0b-48ab-a222-9f2f2faf0ee4.png" height="80px"></a>
</details> </details>
---------------------------------------- ----------------------------------------

View File

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

View File

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

View File

@@ -60,6 +60,7 @@ object Keys {
// preferences // preferences
const val PREF_RADIO_BROWSER_API: String = "RADIO_BROWSER_API" 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_THEME_SELECTION: String = "THEME_SELECTION"
const val PREF_LAST_UPDATE_COLLECTION: String = "LAST_UPDATE_COLLECTION" const val PREF_LAST_UPDATE_COLLECTION: String = "LAST_UPDATE_COLLECTION"
const val PREF_COLLECTION_SIZE: String = "COLLECTION_SIZE" const val PREF_COLLECTION_SIZE: String = "COLLECTION_SIZE"
@@ -78,6 +79,7 @@ object Keys {
// default const values // default const values
const val DEFAULT_SIZE_OF_METADATA_HISTORY: Int = 25 const val DEFAULT_SIZE_OF_METADATA_HISTORY: Int = 25
const val DEFAULT_MAX_LENGTH_OF_METADATA_ENTRY: Int = 127 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 ACTIVE_DOWNLOADS_EMPTY: String = "zero"
const val DEFAULT_MAX_RECONNECTION_COUNT: Int = 30 const val DEFAULT_MAX_RECONNECTION_COUNT: Int = 30
const val LARGE_BUFFER_SIZE_MULTIPLIER: Int = 8 const val LARGE_BUFFER_SIZE_MULTIPLIER: Int = 8
@@ -136,7 +138,7 @@ object Keys {
const val FOLDER_AUDIO: String = "audio" const val FOLDER_AUDIO: String = "audio"
const val FOLDER_IMAGES: String = "images" const val FOLDER_IMAGES: String = "images"
const val FOLDER_TEMP: String = "temp" const val FOLDER_TEMP: String = "temp"
const val RADIO_LEGACY_FOLDER_COLLECTION: String = "Collection" const val URLRADIO_LEGACY_FOLDER_COLLECTION: String = "Collection"
// file names and extensions // file names and extensions
const val COLLECTION_FILE: String = "collection.json" const val COLLECTION_FILE: String = "collection.json"
@@ -148,6 +150,9 @@ object Keys {
const val RADIO_BROWSER_API_BASE: String = "all.api.radio-browser.info" const val RADIO_BROWSER_API_BASE: String = "all.api.radio-browser.info"
const val RADIO_BROWSER_API_DEFAULT: String = "de1.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) // sizes (in dp)
const val SIZE_STATION_IMAGE_CARD: Int = 72 const val SIZE_STATION_IMAGE_CARD: Int = 72
const val SIZE_STATION_IMAGE_MAXIMUM: Int = 640 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.AppThemeHelper
import com.michatec.radio.helpers.FileHelper import com.michatec.radio.helpers.FileHelper
import com.michatec.radio.helpers.PreferencesHelper import com.michatec.radio.helpers.PreferencesHelper
import org.woheller69.freeDroidWarn.FreeDroidWarn
/* /*
* MainActivity class * MainActivity class
@@ -40,9 +40,6 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// Free Android
FreeDroidWarn.showWarningOnUpgrade(this, BuildConfig.VERSION_CODE)
// set up views // set up views
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
@@ -63,16 +60,6 @@ class MainActivity : AppCompatActivity() {
} }
/* Overrides onResume from AppCompatActivity */
override fun onResume() {
try {
super.onResume()
} catch (_: ClassCastException) {
// Do nothing
}
}
/* Overrides onSupportNavigateUp from AppCompatActivity */ /* Overrides onSupportNavigateUp from AppCompatActivity */
override fun onSupportNavigateUp(): Boolean { override fun onSupportNavigateUp(): Boolean {
// Taken from: https://developer.android.com/guide/navigation/navigation-ui#action_bar // Taken from: https://developer.android.com/guide/navigation/navigation-ui#action_bar

View File

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

View File

@@ -280,8 +280,19 @@ class PlayerService : MediaLibraryService() {
val updatedMediaItems: List<MediaItem> = val updatedMediaItems: List<MediaItem> =
mediaItems.map { mediaItem -> mediaItems.map { mediaItem ->
CollectionHelper.getItem(this@PlayerService, collection, mediaItem.mediaId) 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) return Futures.immediateFuture(updatedMediaItems)
// val updatedMediaItems = mediaItems.map { mediaItem ->
// mediaItem.buildUpon().apply {
// setUri(mediaItem.requestMetadata.mediaUri)
// }.build()
// }
// return Futures.immediateFuture(updatedMediaItems)
} }
@@ -400,19 +411,19 @@ class PlayerService : MediaLibraryService() {
customLayout: ImmutableList<CommandButton>, customLayout: ImmutableList<CommandButton>,
showPauseButton: Boolean showPauseButton: Boolean
): ImmutableList<CommandButton> { ): ImmutableList<CommandButton> {
val seekToPreviousCommandButton = CommandButton.Builder(CommandButton.ICON_UNDEFINED) val seekToPreviousCommandButton = CommandButton.Builder()
.setPlayerCommand(Player.COMMAND_SEEK_TO_PREVIOUS) .setPlayerCommand(Player.COMMAND_SEEK_TO_PREVIOUS)
.setCustomIconResId(R.drawable.ic_notification_skip_to_previous_36dp) .setIconResId(R.drawable.ic_notification_skip_to_previous_36dp)
.setEnabled(true) .setEnabled(true)
.build() .build()
val playCommandButton = CommandButton.Builder(CommandButton.ICON_UNDEFINED) val playCommandButton = CommandButton.Builder()
.setPlayerCommand(Player.COMMAND_PLAY_PAUSE) .setPlayerCommand(Player.COMMAND_PLAY_PAUSE)
.setCustomIconResId(if (player.isPlaying) R.drawable.ic_notification_stop_36dp else R.drawable.ic_notification_play_36dp) .setIconResId(if (player.isPlaying) R.drawable.ic_notification_stop_36dp else R.drawable.ic_notification_play_36dp)
.setEnabled(true) .setEnabled(true)
.build() .build()
val seekToNextCommandButton = CommandButton.Builder(CommandButton.ICON_UNDEFINED) val seekToNextCommandButton = CommandButton.Builder()
.setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT) .setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT)
.setCustomIconResId(R.drawable.ic_notification_skip_to_next_36dp) .setIconResId(R.drawable.ic_notification_skip_to_next_36dp)
.setEnabled(true) .setEnabled(true)
.build() .build()
val commandButtons: MutableList<CommandButton> = mutableListOf( val commandButtons: MutableList<CommandButton> = mutableListOf(
@@ -487,10 +498,15 @@ class PlayerService : MediaLibraryService() {
if (!playWhenReady) { if (!playWhenReady) {
when (reason) { when (reason) {
Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM -> { Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM -> {
stopSelf() // playback reached end: stop / end playback
} }
else -> { else -> {
stopSelf() // 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)
} }
} }
} }
@@ -569,6 +585,7 @@ class PlayerService : MediaLibraryService() {
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName) intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName)
intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC) intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC)
sendBroadcast(intent) sendBroadcast(intent)
// note: remember to broadcast AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, when not needed anymore
} }
} }
} }

View File

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

View File

@@ -28,6 +28,7 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.os.bundleOf
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.* import androidx.preference.*
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@@ -115,21 +116,16 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
} }
// set up "Update Stations" preference // // set up "Update Stations" preference
val preferenceUpdateCollection = Preference(activity as Context) // val preferenceUpdateCollection: Preference = Preference(activity as Context)
preferenceUpdateCollection.title = getString(R.string.pref_update_collection_title) // preferenceUpdateCollection.title = getString(R.string.pref_update_collection_title)
preferenceUpdateCollection.setIcon(R.drawable.ic_refresh_24dp) // preferenceUpdateCollection.setIcon(R.drawable.ic_refresh_24dp)
preferenceUpdateCollection.summary = getString(R.string.pref_update_collection_summary) // preferenceUpdateCollection.summary = getString(R.string.pref_update_collection_summary)
preferenceUpdateCollection.setOnPreferenceClickListener { // preferenceUpdateCollection.setOnPreferenceClickListener {
// show dialog // // show dialog
YesNoDialog(this).show( // 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)
context = activity as Context, // return@setOnPreferenceClickListener true
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 // set up "M3U Export" preference
@@ -303,7 +299,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
screen.addPreference(preferenceThemeSelection) screen.addPreference(preferenceThemeSelection)
screen.addPreference(preferenceCategoryMaintenance) screen.addPreference(preferenceCategoryMaintenance)
screen.addPreference(preferenceUpdateStationImages) screen.addPreference(preferenceUpdateStationImages)
screen.addPreference(preferenceUpdateCollection) // screen.addPreference(preferenceUpdateCollection)
screen.addPreference(preferenceCategoryImportExport) screen.addPreference(preferenceCategoryImportExport)
screen.addPreference(preferenceM3uExport) screen.addPreference(preferenceM3uExport)
screen.addPreference(preferencePlsExport) screen.addPreference(preferencePlsExport)
@@ -426,9 +422,9 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
val sourceUri: Uri? = result.data?.data val sourceUri: Uri? = result.data?.data
if (sourceUri != null) { if (sourceUri != null) {
// open and import OPML in player fragment // open and import OPML in player fragment
val bundle = Bundle().apply { val bundle: Bundle = bundleOf(
putString(Keys.ARG_RESTORE_COLLECTION, "$sourceUri") Keys.ARG_RESTORE_COLLECTION to "$sourceUri"
} )
this.findNavController().navigate(R.id.player_destination, bundle) this.findNavController().navigate(R.id.player_destination, bundle)
} }
} }
@@ -444,9 +440,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
Snackbar.LENGTH_LONG Snackbar.LENGTH_LONG
).show() ).show()
// update collection in player screen // update collection in player screen
val bundle = Bundle().apply { val bundle: Bundle = bundleOf(Keys.ARG_UPDATE_COLLECTION to true)
putBoolean(Keys.ARG_UPDATE_COLLECTION, true)
}
this.findNavController().navigate(R.id.player_destination, bundle) this.findNavController().navigate(R.id.player_destination, bundle)
} else { } else {
ErrorDialog().show( ErrorDialog().show(
@@ -467,9 +461,9 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
Snackbar.LENGTH_LONG Snackbar.LENGTH_LONG
).show() ).show()
// update collection in player screen // update collection in player screen
val bundle = Bundle().apply { val bundle: Bundle = bundleOf(
putBoolean(Keys.ARG_UPDATE_IMAGES, true) Keys.ARG_UPDATE_IMAGES to true
} )
this.findNavController().navigate(R.id.player_destination, bundle) this.findNavController().navigate(R.id.player_destination, bundle)
} else { } else {
ErrorDialog().show( ErrorDialog().show(

View File

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

View File

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

View File

@@ -644,6 +644,48 @@ object CollectionHelper {
LocalBroadcastManager.getInstance(context).sendBroadcast(collectionChangedIntent) 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 */ /* Creates a MediaItem with MediaMetadata for a single radio station - used to prepare player */
fun buildMediaItem(context: Context, station: Station): MediaItem { fun buildMediaItem(context: Context, station: Station): MediaItem {
// put uri in RequestMetadata - credit: https://stackoverflow.com/a/70103460 // put uri in RequestMetadata - credit: https://stackoverflow.com/a/70103460

View File

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

View File

@@ -25,7 +25,6 @@ import com.michatec.radio.R
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import androidx.core.graphics.createBitmap
/* /*
@@ -99,7 +98,7 @@ object ImageHelper {
} }
// create empty bitmap and canvas // create empty bitmap and canvas
val outputImage: Bitmap = createBitmap(size, size, Bitmap.Config.ARGB_8888) val outputImage: Bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
val imageCanvas = Canvas(outputImage) val imageCanvas = Canvas(outputImage)
// draw square background // draw square background

View File

@@ -0,0 +1,42 @@
/*
* ImportHelper.kt
* Implements the ImportHelper object
* A ImportHelper provides methods for integrating station files from Radio v3
*
* 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
import com.michatec.radio.Keys
import com.michatec.radio.core.Collection
/*
* ImportHelper object
*/
object ImportHelper {
/* */
fun removeDefaultStationImageUris(context: Context) {
val collection: Collection = FileHelper.readCollection(context)
collection.stations.forEach { station ->
if (station.image == Keys.LOCATION_DEFAULT_STATION_IMAGE) {
station.image = String()
}
if (station.smallImage == Keys.LOCATION_DEFAULT_STATION_IMAGE) {
station.smallImage = String()
}
}
CollectionHelper.saveCollection(context, collection, async = false)
}
}

View File

@@ -19,13 +19,13 @@ import android.net.ConnectivityManager
import android.net.Network import android.net.Network
import android.util.Log import android.util.Log
import com.michatec.radio.Keys import com.michatec.radio.Keys
import kotlinx.coroutines.suspendCancellableCoroutine
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.InetAddress import java.net.InetAddress
import java.net.URL import java.net.URL
import java.net.UnknownHostException import java.net.UnknownHostException
import java.util.* import java.util.*
import kotlin.coroutines.resume 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 function: Detects content type (mime type) from given URL string - async using coroutine */
suspend fun detectContentTypeSuspended(urlString: String): ContentType { suspend fun detectContentTypeSuspended(urlString: String): ContentType {
return suspendCancellableCoroutine { cont -> return suspendCoroutine { cont ->
cont.resume(detectContentType(urlString)) cont.resume(detectContentType(urlString))
} }
} }
@@ -113,14 +113,14 @@ object NetworkHelper {
/* Suspend function: Gets a random radio-browser.info api address - async using coroutine */ /* Suspend function: Gets a random radio-browser.info api address - async using coroutine */
suspend fun getRadioBrowserServerSuspended(): String { suspend fun getRadioBrowserServerSuspended(): String {
return suspendCancellableCoroutine { cont -> return suspendCoroutine { cont ->
val serverAddress: String = try { val serverAddress: String = try {
// get all available radio browser servers // get all available radio browser servers
val serverAddressList: Array<InetAddress> = val serverAddressList: Array<InetAddress> =
InetAddress.getAllByName(Keys.RADIO_BROWSER_API_BASE) InetAddress.getAllByName(Keys.RADIO_BROWSER_API_BASE)
// select a random address // select a random address
serverAddressList[Random().nextInt(serverAddressList.size)].canonicalHostName serverAddressList[Random().nextInt(serverAddressList.size)].canonicalHostName
} catch (_: UnknownHostException) { } catch (e: UnknownHostException) {
Keys.RADIO_BROWSER_API_DEFAULT Keys.RADIO_BROWSER_API_DEFAULT
} }
PreferencesHelper.saveRadioBrowserApiAddress(serverAddress) PreferencesHelper.saveRadioBrowserApiAddress(serverAddress)

View File

@@ -213,6 +213,20 @@ object PreferencesHelper {
} }
/* Checks if housekeeping work needs to be done - used usually in DownloadWorker "REQUEST_UPDATE_COLLECTION" */
fun isHouseKeepingNecessary(): Boolean {
return sharedPreferences.getBoolean(Keys.PREF_ONE_TIME_HOUSEKEEPING_NECESSARY, true)
}
/* Saves state of housekeeping */
fun saveHouseKeepingNecessaryState(state: Boolean = false) {
sharedPreferences.edit {
putBoolean(Keys.PREF_ONE_TIME_HOUSEKEEPING_NECESSARY, state)
}
}
/* Load currently selected app theme */ /* Load currently selected app theme */
fun loadThemeSelection(): String { fun loadThemeSelection(): String {
return sharedPreferences.getString( return sharedPreferences.getString(
@@ -227,6 +241,15 @@ object PreferencesHelper {
return sharedPreferences.getBoolean(Keys.PREF_EDIT_STATIONS, true) return sharedPreferences.getBoolean(Keys.PREF_EDIT_STATIONS, true)
} }
/* Saves value of the option: Edit Stations (only needed for migration) */
fun saveEditStationsEnabled(enabled: Boolean = false) {
sharedPreferences.edit {
putBoolean(Keys.PREF_EDIT_STATIONS, enabled)
}
}
/* Loads value of the option: Edit Station Streams */ /* Loads value of the option: Edit Station Streams */
fun loadEditStreamUrisEnabled(): Boolean { fun loadEditStreamUrisEnabled(): Boolean {
return sharedPreferences.getBoolean(Keys.PREF_EDIT_STREAMS_URIS, true) return sharedPreferences.getBoolean(Keys.PREF_EDIT_STREAMS_URIS, true)
@@ -253,7 +276,7 @@ object PreferencesHelper {
fun downloadOverMobile(): Boolean { fun downloadOverMobile(): Boolean {
return sharedPreferences.getBoolean( return sharedPreferences.getBoolean(
Keys.PREF_DOWNLOAD_OVER_MOBILE, Keys.PREF_DOWNLOAD_OVER_MOBILE,
false Keys.DEFAULT_DOWNLOAD_OVER_MOBILE
) )
} }

View File

@@ -1,3 +1,17 @@
/*
* 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 package com.michatec.radio.helpers
import android.content.Context import android.content.Context

View File

@@ -129,7 +129,7 @@ class RadioBrowserSearch(private var radioBrowserSearchListener: RadioBrowserSea
/* Listens for (positive) server responses to search requests */ /* Listens for (positive) server responses to search requests */
private val responseListener: Response.Listener<JSONArray> = Response.Listener { response -> private val responseListener: Response.Listener<JSONArray> = Response.Listener<JSONArray> { response ->
if (response != null) { if (response != null) {
radioBrowserSearchListener.onRadioBrowserSearchResults(createRadioBrowserResult(response.toString())) radioBrowserSearchListener.onRadioBrowserSearchResults(createRadioBrowserResult(response.toString()))
} }

View File

@@ -18,6 +18,7 @@ import android.content.Context
import android.media.AudioAttributes import android.media.AudioAttributes
import android.media.AudioFocusRequest import android.media.AudioFocusRequest
import android.media.AudioManager import android.media.AudioManager
import android.os.Build
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -239,7 +240,7 @@ class SearchResultAdapter(
/* /*
* Inner class: ViewHolder for a radio station search result * Inner class: ViewHolder for a radio station search result
*/ */
private class SearchResultViewHolder(var searchResultLayout: View) : private inner class SearchResultViewHolder(var searchResultLayout: View) :
RecyclerView.ViewHolder(searchResultLayout) { RecyclerView.ViewHolder(searchResultLayout) {
val nameView: MaterialTextView = searchResultLayout.findViewById(R.id.station_name) val nameView: MaterialTextView = searchResultLayout.findViewById(R.id.station_name)
val streamView: MaterialTextView = searchResultLayout.findViewById(R.id.station_url) val streamView: MaterialTextView = searchResultLayout.findViewById(R.id.station_url)

View File

@@ -313,6 +313,18 @@ data class LayoutHolder(var rootView: View) {
isBuffering = buffering 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 */ /* Toggles visibility of the download progress indicator */
fun toggleDownloadProgressIndicator() { fun toggleDownloadProgressIndicator() {
when (PreferencesHelper.loadActiveDownloads()) { when (PreferencesHelper.loadActiveDownloads()) {

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/icon_default"
android:pathData="m11.999,2.623c-5.54,0 -9.999,4.462 -9.999,10.001 -0,1.878 0.529,3.721 1.528,5.313l8.336,-10.778c0.06,-0.077 0.209,-0.077 0.27,0l3.482,4.502h-2.494l0.054,0.199h2.594l0.736,0.949h-3.062l0.086,0.311h3.217l0.647,0.836h-3.631l0.113,0.402h3.827l0.579,0.745h-4.198l0.137,0.49h4.438l0.508,0.655h-4.764l0.152,0.542h5.031l0.468,0.606h-5.33l0.152,0.542h5.598c0.998,-1.592 1.528,-3.435 1.528,-5.313 0,-5.54 -4.462,-10.001 -10.001,-10.001zM15.044,18.543 L15.196,19.085h4.438c0.145,-0.171 0.294,-0.361 0.428,-0.542zM15.365,19.69 L15.515,20.232h2.969c0.194,-0.166 0.405,-0.357 0.594,-0.542zM15.684,20.838 L15.836,21.377h1.003c0.296,-0.17 0.571,-0.343 0.868,-0.539z" />
</vector>

View File

@@ -1,17 +0,0 @@
<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,10 +51,6 @@
<string name="player_sheet_h2_station_metadata">Momentan läuft</string> <string name="player_sheet_h2_station_metadata">Momentan läuft</string>
<string name="player_sheet_h2_stream_url">Streaming-Adresse</string> <string name="player_sheet_h2_stream_url">Streaming-Adresse</string>
<!-- Settings --> <!-- 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_advanced_title">Erweitert</string>
<string name="pref_app_version_summary">Version</string> <string name="pref_app_version_summary">Version</string>
<string name="pref_app_version_title">App-Version</string> <string name="pref_app_version_title">App-Version</string>

View File

@@ -59,10 +59,6 @@
<string name="player_sheet_h2_stream_url">Streaming link</string> <string name="player_sheet_h2_stream_url">Streaming link</string>
<!-- Settings --> <!-- 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_advanced_title">Advanced</string>
<string name="pref_app_version_summary">Version</string> <string name="pref_app_version_summary">Version</string>
<string name="pref_app_version_title">App Version</string> <string name="pref_app_version_title">App Version</string>

View File

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

View File

@@ -1,42 +0,0 @@
[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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

2
gradlew vendored
View File

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

View File

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