15 Commits
14 ... 14.1

Author SHA1 Message Date
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
Michachatz
008170811a Update issue templates 2026-02-24 16:26:38 +01:00
Michachatz
c0072fe54f Aktualisieren von gradle-publish.yml 2026-02-24 16:23:14 +01:00
c62c61f2f1 - Removed some old Systems
- Removed some old functions
2026-02-24 14:55:21 +01:00
5ca2b9b7ef - Some Bug fixes
- Remove Code berg icon
- Updated README.md
- Updated AndroidManifest.xml
- Updated Dependencies
- Fix some code
2026-02-24 14:46:03 +01:00
Michatec
9b5c7e3c04 - New version Initialized 2026-01-25 17:07:30 +01:00
Michatec
4466654e92 - Fix unused import 2026-01-25 16:33:45 +01:00
Michatec
b2de7bd534 - Progress Bar added
- CollectionAdapter.kt updated
- File download optimized
- Housekeeping updated
2026-01-25 16:33:17 +01:00
Michatec
d3dfcb98f9 - Layout Changes
- HLS media extraction
2026-01-25 16:08:22 +01:00
Michachatz
0adb906438 Aktualisieren von provider_paths.xml 2026-01-22 06:55:40 +01:00
Michachatz
49e63d3aaa Aktualisieren von shortcuts.xml 2026-01-22 06:55:08 +01:00
Michatec
f892a137ce - Fix .yml 2026-01-21 18:14:38 +01:00
35 changed files with 270 additions and 289 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
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

@@ -0,0 +1,20 @@
---
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

@@ -51,6 +51,10 @@ jobs:
- name: Check APK path - name: Check APK path
run: ls -R app/build/outputs/apk run: ls -R app/build/outputs/apk
- name: Zipalign APK
run: |
/usr/local/lib/android/sdk/build-tools/34.0.0/zipalign -v -p 4 ${{ env.APK_PATH }} app-release-aligned.apk
- name: Sign APK - name: Sign APK
env: env:
SIGN_CERT: ${{ secrets.SIGN_CERT }} SIGN_CERT: ${{ secrets.SIGN_CERT }}
@@ -58,35 +62,12 @@ jobs:
run: | run: |
echo "$SIGN_CERT" | base64 -d > cert.der echo "$SIGN_CERT" | base64 -d > cert.der
echo "$SIGN_KEY" | base64 -d > key.der echo "$SIGN_KEY" | base64 -d > key.der
mv ${{ env.APK_PATH }} app-release.apk ${{ env.APKSIGNER }} sign --key key.der --cert cert.der app-release-aligned.apk
${{ env.APKSIGNER }} sign --key key.der --cert cert.der app-release.apk
rm cert.der key.der rm cert.der key.der
- name: Zipalign APK
run: |
/usr/local/lib/android/sdk/build-tools/34.0.0/zipalign -v -p 4 app-release.apk app-release-aligned.apk
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@v6 uses: actions/upload-artifact@v7
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

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" 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" alt="Preview" height="80px"></a>
</details> </details>
---------------------------------------- ----------------------------------------

View File

@@ -19,8 +19,8 @@ android {
applicationId 'com.michatec.radio' applicationId 'com.michatec.radio'
minSdk 28 minSdk 28
targetSdk 36 targetSdk 36
versionCode 140 versionCode 141
versionName '14' 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']
} }
@@ -60,17 +60,17 @@ dependencies {
// AndroidX Stuff // // AndroidX Stuff //
implementation 'androidx.core:core-ktx:1.17.0' implementation 'androidx.core:core-ktx:1.17.0'
implementation 'androidx.activity:activity-ktx:1.12.2' implementation 'androidx.activity:activity-ktx:1.12.4'
implementation 'androidx.palette:palette-ktx:1.0.0' implementation 'androidx.palette:palette-ktx:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.1' implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.media:media:1.7.1' implementation 'androidx.media:media:1.7.1'
implementation 'androidx.media3:media3-exoplayer:1.9.0' implementation 'androidx.media3:media3-exoplayer:1.9.2'
implementation 'androidx.media3:media3-exoplayer-hls:1.9.0' implementation 'androidx.media3:media3-exoplayer-hls:1.9.2'
implementation 'androidx.media3:media3-session:1.9.0' implementation 'androidx.media3:media3-session:1.9.2'
implementation 'androidx.media3:media3-datasource-okhttp:1.9.0' implementation 'androidx.media3:media3-datasource-okhttp:1.9.2'
implementation 'androidx.navigation:navigation-fragment-ktx:2.9.6' implementation 'androidx.navigation:navigation-fragment-ktx:2.9.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.9.6' implementation 'androidx.navigation:navigation-ui-ktx:2.9.7'
implementation 'androidx.work:work-runtime-ktx:2.11.0' implementation 'androidx.work:work-runtime-ktx:2.11.1'
// Volley HTTP request // // Volley HTTP request //
implementation 'com.android.volley:volley:1.2.1' implementation 'com.android.volley:volley:1.2.1'

View File

@@ -58,7 +58,7 @@
</intent-filter> </intent-filter>
<!-- react to playlist-links based on file extension --> <!-- react to playlist-links based on file extension -->
<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" />
@@ -73,7 +73,7 @@
</intent-filter> </intent-filter>
<!-- react to playlist-links based on mimetype --> <!-- react to playlist-links based on mimetype -->
<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" />
@@ -81,7 +81,8 @@
<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,7 +60,6 @@ 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"
@@ -138,7 +137,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 URLRADIO_LEGACY_FOLDER_COLLECTION: String = "Collection" const val RADIO_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"

View File

@@ -24,7 +24,6 @@ import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.navigateUp 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.ImportHelper
import com.michatec.radio.helpers.PreferencesHelper import com.michatec.radio.helpers.PreferencesHelper
@@ -41,18 +40,6 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// house-keeping: determine if edit stations is enabled by default todo: remove in 2023
if (PreferencesHelper.isHouseKeepingNecessary()) {
// house-keeping 1: remove hard coded default image
ImportHelper.removeDefaultStationImageUris(this)
// house-keeping 2: if existing user detected, enable Edit Stations by default
if (PreferencesHelper.loadCollectionSize() != -1) {
// existing user detected - enable Edit Stations by default
PreferencesHelper.saveEditStationsEnabled(true)
}
PreferencesHelper.saveHouseKeepingNecessaryState()
}
// set up views // set up views
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)

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,19 +280,8 @@ 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)
} }
@@ -498,15 +487,10 @@ 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 -> {
// playback reached end: stop / end playback stopSelf()
} }
else -> { else -> {
// playback has been paused by user or OS: update media session and save state stopSelf()
// 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)
} }
} }
} }
@@ -585,7 +569,6 @@ 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,7 +34,6 @@ class Radio : Application() {
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection()) AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
} }
/* Implements onTerminate */ /* Implements onTerminate */
override fun onTerminate() { override fun onTerminate() {
super.onTerminate() super.onTerminate()

View File

@@ -116,16 +116,21 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
} }
// // set up "Update Stations" preference // set up "Update Stations" preference
// val preferenceUpdateCollection: Preference = Preference(activity as Context) val preferenceUpdateCollection = 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(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) YesNoDialog(this).show(
// return@setOnPreferenceClickListener true 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 // set up "M3U Export" preference
@@ -299,7 +304,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)

View File

@@ -24,6 +24,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
@@ -193,6 +194,8 @@ class CollectionAdapter(
setStationImage(stationViewHolder, station) setStationImage(stationViewHolder, station)
setStationButtons(stationViewHolder, station) setStationButtons(stationViewHolder, station)
setEditViews(stationViewHolder, station) setEditViews(stationViewHolder, station)
setPlaybackProgress(stationViewHolder, station)
setDownloadProgress(stationViewHolder, station)
// show / hide edit views // show / hide edit views
when (expandedStationPosition) { when (expandedStationPosition) {
@@ -248,6 +251,28 @@ class CollectionAdapter(
} }
/* Sets the playback progress view */
private fun setPlaybackProgress(stationViewHolder: StationViewHolder, station: Station) {
if (station.bufferingProgress > 0) {
stationViewHolder.bufferingProgress.progress = station.bufferingProgress
stationViewHolder.bufferingProgress.isVisible = true
} else {
stationViewHolder.bufferingProgress.isGone = true
}
}
/* Sets the download progress view */
private fun setDownloadProgress(stationViewHolder: StationViewHolder, station: Station) {
if (station.downloadProgress > 0) {
stationViewHolder.downloadProgress.progress = station.downloadProgress
stationViewHolder.downloadProgress.isVisible = true
} else {
stationViewHolder.downloadProgress.isGone = true
}
}
/* Sets the edit views */ /* Sets the edit views */
private fun setEditViews(stationViewHolder: StationViewHolder, station: Station) { private fun setEditViews(stationViewHolder: StationViewHolder, station: Station) {
stationViewHolder.stationNameEditView.setText(station.name, TextView.BufferType.EDITABLE) stationViewHolder.stationNameEditView.setText(station.name, TextView.BufferType.EDITABLE)
@@ -471,24 +496,25 @@ class CollectionAdapter(
} else if (holder is StationViewHolder) { } else if (holder is StationViewHolder) {
// get station from position // get station from position
collection.stations[holder.bindingAdapterPosition] val station: Station = collection.stations[holder.bindingAdapterPosition]
for (data in payloads) { for (data in payloads) {
when (data as Int) { when (data as Int) {
Keys.HOLDER_UPDATE_COVER -> { Keys.HOLDER_UPDATE_COVER -> {
// todo implement setStationImage(holder, station)
setStarredIcon(holder, station)
} }
Keys.HOLDER_UPDATE_NAME -> { Keys.HOLDER_UPDATE_NAME -> {
// todo implement setStationName(holder, station)
} }
Keys.HOLDER_UPDATE_PLAYBACK_STATE -> { Keys.HOLDER_UPDATE_PLAYBACK_STATE -> {
// todo implement setStationButtons(holder, station)
} }
Keys.HOLDER_UPDATE_PLAYBACK_PROGRESS -> { Keys.HOLDER_UPDATE_PLAYBACK_PROGRESS -> {
// todo implement setPlaybackProgress(holder, station)
} }
Keys.HOLDER_UPDATE_DOWNLOAD_STATE -> { Keys.HOLDER_UPDATE_DOWNLOAD_STATE -> {
// todo implement setDownloadProgress(holder, station)
} }
} }
} }
@@ -575,21 +601,6 @@ class CollectionAdapter(
} }
// /* Initiates update of a station's information */ // todo move to CollectionHelper
// private fun updateStation(context: Context, station: Station) {
// if (station.radioBrowserStationUuid.isNotEmpty()) {
// // get updated station from radio browser - results are handled by onRadioBrowserSearchResults
// val radioBrowserSearch: RadioBrowserSearch = RadioBrowserSearch(context, this)
// radioBrowserSearch.searchStation(context, station.radioBrowserStationUuid, Keys.SEARCH_TYPE_BY_UUID)
// } else if (station.remoteStationLocation.isNotEmpty()) {
// // download playlist // todo check content type detection is necessary here
// DownloadHelper.downloadPlaylists(context, arrayOf(station.remoteStationLocation))
// } else {
// Log.w(TAG, "Unable to update station: ${station.name}.")
// }
// }
/* Determines if position is last */ /* Determines if position is last */
private fun isPositionFooter(position: Int): Boolean { private fun isPositionFooter(position: Int): Boolean {
return position == collection.stations.size return position == collection.stations.size
@@ -600,7 +611,7 @@ class CollectionAdapter(
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
private fun updateRecyclerView(oldCollection: Collection, newCollection: Collection) { private fun updateRecyclerView(oldCollection: Collection, newCollection: Collection) {
collection = newCollection collection = newCollection
if (oldCollection.stations.size == 0 && newCollection.stations.size > 0) { if (oldCollection.stations.isEmpty() && newCollection.stations.isNotEmpty()) {
// data set has been initialized - redraw the whole list // data set has been initialized - redraw the whole list
notifyDataSetChanged() notifyDataSetChanged()
} else { } else {
@@ -672,6 +683,8 @@ class CollectionAdapter(
val stationImageView: ImageView = stationCardLayout.findViewById(R.id.station_icon) val stationImageView: ImageView = stationCardLayout.findViewById(R.id.station_icon)
val stationNameView: TextView = stationCardLayout.findViewById(R.id.station_name) val stationNameView: TextView = stationCardLayout.findViewById(R.id.station_name)
val stationStarredView: ImageView = stationCardLayout.findViewById(R.id.starred_icon) val stationStarredView: ImageView = stationCardLayout.findViewById(R.id.starred_icon)
val bufferingProgress: ProgressBar = stationCardLayout.findViewById(R.id.buffering_progress)
val downloadProgress: ProgressBar = stationCardLayout.findViewById(R.id.download_progress)
// val menuButtonView: ImageView = stationCardLayout.findViewById(R.id.menu_button) // val menuButtonView: ImageView = stationCardLayout.findViewById(R.id.menu_button)
val playButtonView: ImageView = stationCardLayout.findViewById(R.id.playback_button) val playButtonView: ImageView = stationCardLayout.findViewById(R.id.playback_button)

View File

@@ -48,7 +48,9 @@ data class Station(
@Expose var radioBrowserStationUuid: String = String(), @Expose var radioBrowserStationUuid: String = String(),
@Expose var radioBrowserChangeUuid: String = String(), @Expose var radioBrowserChangeUuid: String = String(),
@Expose var bitrate: Int = 0, @Expose var bitrate: Int = 0,
@Expose var codec: String = String() @Expose var codec: String = String(),
@Expose var bufferingProgress: Int = 0,
@Expose var downloadProgress: Int = 0
) : Parcelable { ) : Parcelable {
@@ -79,6 +81,12 @@ data class Station(
} }
/* Getter for media type */
fun getMediaType(): String {
return streamContent
}
/* Creates a deep copy of a Station */ /* Creates a deep copy of a Station */
fun deepCopy(): Station { fun deepCopy(): Station {
return Station( return Station(
@@ -101,7 +109,9 @@ data class Station(
radioBrowserStationUuid = radioBrowserStationUuid, radioBrowserStationUuid = radioBrowserStationUuid,
radioBrowserChangeUuid = radioBrowserChangeUuid, radioBrowserChangeUuid = radioBrowserChangeUuid,
bitrate = bitrate, bitrate = bitrate,
codec = codec codec = codec,
bufferingProgress = bufferingProgress,
downloadProgress = downloadProgress
) )
} }
} }

View File

@@ -19,6 +19,7 @@ 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
@@ -81,8 +82,7 @@ class ErrorDialog {
// add okay button // add okay button
builder.setPositiveButton(R.string.dialog_generic_button_okay) { _, _ -> builder.setPositiveButton(R.string.dialog_generic_button_okay) { _, _ ->
// listen for click on okay button Toast.makeText(context, R.string.dialog_generic_button_okay, Toast.LENGTH_SHORT).show()
// do nothing
} }
// display error dialog // display error dialog

View File

@@ -20,6 +20,8 @@ import androidx.media3.common.Metadata
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.extractor.metadata.icy.IcyHeaders import androidx.media3.extractor.metadata.icy.IcyHeaders
import androidx.media3.extractor.metadata.icy.IcyInfo import androidx.media3.extractor.metadata.icy.IcyInfo
import androidx.media3.extractor.metadata.id3.Id3Frame
import androidx.media3.extractor.metadata.id3.TextInformationFrame
import com.michatec.radio.Keys import com.michatec.radio.Keys
import kotlin.math.min import kotlin.math.min
@@ -37,28 +39,51 @@ object AudioHelper {
/* Extract audio stream metadata */ /* Extract audio stream metadata */
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
fun getMetadataString(metadata: Metadata): String { fun getMetadataString(metadata: Metadata): String {
var metadataString = String() var title = ""
var artist = ""
var album = ""
for (i in 0 until metadata.length()) { for (i in 0 until metadata.length()) {
// extract IceCast metadata // extract IceCast metadata
when (val entry = metadata.get(i)) { when (val entry = metadata.get(i)) {
is IcyInfo -> { is IcyInfo -> {
metadataString = entry.title.toString() title = entry.title.toString()
} }
is IcyHeaders -> { is IcyHeaders -> {
Log.i(TAG, "icyHeaders:" + entry.name + " - " + entry.genre) Log.i(TAG, "icyHeaders:" + entry.name + " - " + entry.genre)
} }
is Id3Frame -> {
when (entry) {
is TextInformationFrame -> {
when (entry.id) {
"TIT2" -> title = entry.values.getOrNull(0) ?: "" // Title
"TPE1" -> artist = entry.values.getOrNull(0) ?: "" // Artist
"TALB" -> album = entry.values.getOrNull(0) ?: "" // Album
}
}
else -> {
Log.d(TAG, "Unhandled ID3 frame: ${entry.javaClass.simpleName}")
}
}
}
else -> { else -> {
Log.w(TAG, "Unsupported metadata received (type = ${entry.javaClass.simpleName})") Log.w(TAG, "Unsupported metadata received (type = ${entry.javaClass.simpleName})")
} }
} }
// TODO implement HLS metadata extraction (Id3Frame / PrivFrame) }
// https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/metadata/Metadata.Entry.html // Build metadata string
var metadataString = title
if (artist.isNotEmpty() && title.isNotEmpty()) {
metadataString = "$artist - $title"
}
if (album.isNotEmpty() && metadataString.isNotEmpty()) {
metadataString += " ($album)"
} }
// ensure a max length of the metadata string // ensure a max length of the metadata string
if (metadataString.isNotEmpty()) { if (metadataString.isNotEmpty()) {
metadataString = metadataString.substring(0, min(metadataString.length, Keys.DEFAULT_MAX_LENGTH_OF_METADATA_ENTRY)) metadataString = metadataString.take(min(metadataString.length, Keys.DEFAULT_MAX_LENGTH_OF_METADATA_ENTRY))
} }
return metadataString return metadataString
} }

View File

@@ -644,51 +644,8 @@ 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 {
// todo implement HLS MediaItems
// put uri in RequestMetadata - credit: https://stackoverflow.com/a/70103460 // put uri in RequestMetadata - credit: https://stackoverflow.com/a/70103460
val requestMetadata = MediaItem.RequestMetadata.Builder().apply { val requestMetadata = MediaItem.RequestMetadata.Builder().apply {
setMediaUri(station.getStreamUri().toUri()) setMediaUri(station.getStreamUri().toUri())
@@ -713,7 +670,7 @@ object CollectionHelper {
setMediaId(station.uuid) setMediaId(station.uuid)
setRequestMetadata(requestMetadata) setRequestMetadata(requestMetadata)
setMediaMetadata(mediaMetadata) setMediaMetadata(mediaMetadata)
//setMimeType(station.getMediaType()) setMimeType(station.getMediaType())
setUri(station.getStreamUri().toUri()) setUri(station.getStreamUri().toUri())
}.build() }.build()
} }

View File

@@ -246,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.URLRADIO_LEGACY_FOLDER_COLLECTION), activity.getExternalFilesDir(Keys.RADIO_LEGACY_FOLDER_COLLECTION),
Keys.COLLECTION_M3U_FILE Keys.COLLECTION_M3U_FILE
) )
} }
@@ -270,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.URLRADIO_LEGACY_FOLDER_COLLECTION), activity.getExternalFilesDir(Keys.RADIO_LEGACY_FOLDER_COLLECTION),
Keys.COLLECTION_PLS_FILE Keys.COLLECTION_PLS_FILE
) )
} }
@@ -417,16 +417,14 @@ object FileHelper {
/* Reads InputStream from file uri and returns it as String */ /* Reads InputStream from file uri and returns it as String */
private fun readTextFileFromFile(context: Context): String { private fun readTextFileFromFile(context: Context): String {
// todo read https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html
// https://developer.android.com/training/secure-file-sharing/retrieve-info
// check if file exists // check if file exists
val file = File(context.getExternalFilesDir(Keys.FOLDER_COLLECTION), Keys.COLLECTION_FILE) val file = File(context.getExternalFilesDir(Keys.FOLDER_COLLECTION), Keys.COLLECTION_FILE)
if (!file.exists() || !file.canRead()) { if (!file.exists() || !file.canRead()) {
return String() return String()
} }
// read until last line reached // read until last line reached
val stream: InputStream = file.inputStream() val uri = Uri.fromFile(file)
val stream: InputStream = context.contentResolver.openInputStream(uri) ?: return String()
val reader = BufferedReader(InputStreamReader(stream)) val reader = BufferedReader(InputStreamReader(stream))
val builder: StringBuilder = StringBuilder() val builder: StringBuilder = StringBuilder()
reader.forEachLine { reader.forEachLine {

View File

@@ -25,6 +25,7 @@ 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
/* /*
@@ -98,7 +99,7 @@ object ImageHelper {
} }
// create empty bitmap and canvas // create empty bitmap and canvas
val outputImage: Bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) val outputImage: Bitmap = createBitmap(size, size, Bitmap.Config.ARGB_8888)
val imageCanvas = Canvas(outputImage) val imageCanvas = Canvas(outputImage)
// draw square background // draw square background

View File

@@ -1,42 +0,0 @@
/*
* 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

@@ -120,7 +120,7 @@ object NetworkHelper {
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 (e: UnknownHostException) { } catch (_: UnknownHostException) {
Keys.RADIO_BROWSER_API_DEFAULT Keys.RADIO_BROWSER_API_DEFAULT
} }
PreferencesHelper.saveRadioBrowserApiAddress(serverAddress) PreferencesHelper.saveRadioBrowserApiAddress(serverAddress)

View File

@@ -213,20 +213,6 @@ 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(
@@ -241,15 +227,6 @@ 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)
@@ -276,7 +253,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,
Keys.DEFAULT_DOWNLOAD_OVER_MOBILE false
) )
} }

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 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<JSONArray> { response -> private val responseListener: Response.Listener<JSONArray> = Response.Listener { response ->
if (response != null) { if (response != null) {
radioBrowserSearchListener.onRadioBrowserSearchResults(createRadioBrowserResult(response.toString())) radioBrowserSearchListener.onRadioBrowserSearchResults(createRadioBrowserResult(response.toString()))
} }

View File

@@ -18,7 +18,6 @@ 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
@@ -240,7 +239,7 @@ class SearchResultAdapter(
/* /*
* Inner class: ViewHolder for a radio station search result * Inner class: ViewHolder for a radio station search result
*/ */
private inner class SearchResultViewHolder(var searchResultLayout: View) : private 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

@@ -14,6 +14,7 @@
package com.michatec.radio.ui package com.michatec.radio.ui
import android.annotation.SuppressLint
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
@@ -131,6 +132,7 @@ data class LayoutHolder(var rootView: View) {
/* Updates the player views */ /* Updates the player views */
@SuppressLint("DefaultLocale")
fun updatePlayerViews(context: Context, station: Station, isPlaying: Boolean) { fun updatePlayerViews(context: Context, station: Station, isPlaying: Boolean) {
// set default metadata views, when playback has stopped // set default metadata views, when playback has stopped
@@ -164,12 +166,20 @@ data class LayoutHolder(var rootView: View) {
// show only the codec when the bitrate is at "0" from radio-browser.info API // show only the codec when the bitrate is at "0" from radio-browser.info API
station.codec station.codec
} else { } else {
val kiloBytesPerSecond = station.bitrate / 8F
val dataRateString = if (kiloBytesPerSecond >= 1000) {
String.format("%.2f mb/s", kiloBytesPerSecond / 1000F)
} else {
String.format("%.0f kb/s", kiloBytesPerSecond)
}
// show the bitrate and codec if the result is available in the radio-browser.info API // show the bitrate and codec if the result is available in the radio-browser.info API
buildString { buildString {
append(station.codec) append(station.codec)
append(" | ") append(" | ")
append(station.bitrate) append(station.bitrate)
append("kbps") append("kbps")
append(" | ")
append(dataRateString)
} }
} }
} else { } else {
@@ -303,18 +313,6 @@ 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

@@ -1,9 +0,0 @@
<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

@@ -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

@@ -31,6 +31,21 @@
app:shapeAppearanceOverlay="@style/RoundedCorners" app:shapeAppearanceOverlay="@style/RoundedCorners"
app:srcCompat="@drawable/ic_default_station_image_72dp" /> app:srcCompat="@drawable/ic_default_station_image_72dp" />
<ProgressBar
android:id="@+id/download_progress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="0dp"
android:layout_height="4dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:progressTint="@color/player_button_background"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@+id/station_icon"
app:layout_constraintStart_toStartOf="@+id/station_icon"
app:layout_constraintTop_toTopOf="@+id/station_icon"
tools:visibility="visible" />
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/change_image_view" android:id="@+id/change_image_view"
android:layout_width="0dp" android:layout_width="0dp"
@@ -155,6 +170,21 @@
app:layout_constraintEnd_toStartOf="@+id/save_button" app:layout_constraintEnd_toStartOf="@+id/save_button"
app:layout_constraintTop_toTopOf="@+id/save_button" /> app:layout_constraintTop_toTopOf="@+id/save_button" />
<ProgressBar
android:id="@+id/buffering_progress"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="0dp"
android:layout_height="4dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:progressTint="@color/player_button_background"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Group <androidx.constraintlayout.widget.Group
android:id="@+id/default_edit_views" android:id="@+id/default_edit_views"
android:layout_width="0dp" android:layout_width="0dp"

View File

@@ -51,6 +51,10 @@
<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,6 +59,10 @@
<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

@@ -2,7 +2,7 @@
<paths> <paths>
<external-files-path <external-files-path
name="my_images" name="my_images"
path="Android/data/com.michatec.urlradio/files/Pictures" /> path="Android/data/com.michatec.radio/files/Pictures" />
<external-path <external-path
name="external_files" name="external_files"
path="." /> path="." />

View File

@@ -10,7 +10,7 @@
android:shortcutShortLabel="@string/shortcut_last_station_short_label"> android:shortcutShortLabel="@string/shortcut_last_station_short_label">
<intent <intent
android:action="com.jamal2367.urlradio.action.START" android:action="com.michatec.radio.action.START"
android:targetClass="com.michatec.radio.MainActivity" android:targetClass="com.michatec.radio.MainActivity"
android:targetPackage="com.michatec.radio"> android:targetPackage="com.michatec.radio">
<extra <extra

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 {
id 'com.android.application' version '9.0.0' apply false id 'com.android.application' version '9.0.1' apply false
id 'com.android.library' version '9.0.0' apply false id 'com.android.library' version '9.0.1' apply false
id 'org.jetbrains.kotlin.android' version "2.3.0" apply false id 'org.jetbrains.kotlin.android' version "2.3.10" apply false
} }
tasks.register('clean', Delete) { tasks.register('clean', Delete) {

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.3.0-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