- New Version Initialized

- Some deprecated fixes
- New functions and fallbacks
- Some bug fixes
This commit is contained in:
Michatec
2026-01-21 16:25:37 +01:00
parent 032728626e
commit 5412f59f61
8 changed files with 65 additions and 145 deletions

View File

@@ -1,20 +1,27 @@
apply plugin: 'com.android.application' plugins {
apply plugin: 'kotlin-android' id 'com.android.application'
apply plugin: 'kotlin-parcelize' id 'kotlin-parcelize'
}
androidComponents {
onVariants(selector().all()) { variant ->
variant.outputs.forEach { output ->
output.outputFileName.set("Radio.apk")
}
}
}
android { android {
namespace 'com.michatec.radio' namespace 'com.michatec.radio'
compileSdk 35 compileSdk 36
defaultConfig { defaultConfig {
applicationId 'com.michatec.radio' applicationId 'com.michatec.radio'
minSdk 28 minSdk 28
//noinspection OldTargetApi targetSdk 36
targetSdk 35 versionCode 140
versionCode 130 versionName '14'
versionName '13'
resourceConfigurations += ['en', 'de', 'el', 'nl', 'pl', 'ru','uk', 'ja', 'da', 'fr'] resourceConfigurations += ['en', 'de', 'el', 'nl', 'pl', 'ru','uk', 'ja', 'da', 'fr']
setProperty('archivesBaseName', 'Radio')
} }
compileOptions { compileOptions {

View File

@@ -182,7 +182,7 @@ class PlayerFragment : Fragment(),
(activity as AppCompatActivity).supportActionBar?.hide() (activity as AppCompatActivity).supportActionBar?.hide()
// set the same background color of the player sheet for the navigation bar // set the same background color of the player sheet for the navigation bar
(activity as AppCompatActivity).window.navigationBarColor = ContextCompat.getColor(requireActivity(), R.color.player_sheet_background) requireActivity().window.navigationBarColor = ContextCompat.getColor(requireActivity(), R.color.player_sheet_background)
// associate the ItemTouchHelper with the RecyclerView // associate the ItemTouchHelper with the RecyclerView
itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback()) itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback())
@@ -211,14 +211,14 @@ class PlayerFragment : Fragment(),
} }
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
val fromPosition = viewHolder.adapterPosition val fromPosition = viewHolder.bindingAdapterPosition
val toPosition = target.adapterPosition val toPosition = target.bindingAdapterPosition
collectionAdapter.onItemMove(fromPosition, toPosition) collectionAdapter.onItemMove(fromPosition, toPosition)
return true return true
} }
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val position = viewHolder.adapterPosition val position = viewHolder.bindingAdapterPosition
collectionAdapter.onItemDismiss(position) collectionAdapter.onItemDismiss(position)
} }
@@ -435,15 +435,15 @@ class PlayerFragment : Fragment(),
val swipeToDeleteHandler = object : UiHelper.SwipeToDeleteCallback(activity as Context) { val swipeToDeleteHandler = object : UiHelper.SwipeToDeleteCallback(activity as Context) {
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// ask user // ask user
val adapterPosition: Int = viewHolder.adapterPosition val bindingAdapterPosition: Int = viewHolder.bindingAdapterPosition
val dialogMessage = val dialogMessage =
"${getString(R.string.dialog_yes_no_message_remove_station)}\n\n- ${collection.stations[adapterPosition].name}" "${getString(R.string.dialog_yes_no_message_remove_station)}\n\n- ${collection.stations[bindingAdapterPosition].name}"
YesNoDialog(this@PlayerFragment as YesNoDialog.YesNoDialogListener).show( YesNoDialog(this@PlayerFragment as YesNoDialog.YesNoDialogListener).show(
context = activity as Context, context = activity as Context,
type = Keys.DIALOG_REMOVE_STATION, type = Keys.DIALOG_REMOVE_STATION,
messageString = dialogMessage, messageString = dialogMessage,
yesButton = R.string.dialog_yes_no_positive_button_remove_station, yesButton = R.string.dialog_yes_no_positive_button_remove_station,
payload = adapterPosition payload = bindingAdapterPosition
) )
} }
} }
@@ -455,8 +455,8 @@ class PlayerFragment : Fragment(),
object : UiHelper.SwipeToMarkStarredCallback(activity as Context) { object : UiHelper.SwipeToMarkStarredCallback(activity as Context) {
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// mark card starred // mark card starred
val adapterPosition: Int = viewHolder.adapterPosition val bindingAdapterPosition: Int = viewHolder.bindingAdapterPosition
collectionAdapter.toggleStarredStation(activity as Context, adapterPosition) collectionAdapter.toggleStarredStation(activity as Context, bindingAdapterPosition)
} }
} }
val swipeToMarkStarredItemTouchHelper = ItemTouchHelper(swipeToMarkStarredHandler) val swipeToMarkStarredItemTouchHelper = ItemTouchHelper(swipeToMarkStarredHandler)
@@ -501,22 +501,6 @@ class PlayerFragment : Fragment(),
} }
// /* Sets up the general playback controls - Note: station specific controls and views are updated in updatePlayerViews() */
// // it is probably okay to suppress this warning - the OnTouchListener on the time played view does only toggle the time duration / remaining display
// private fun setupPlaybackControls() {
//
// // main play/pause button
// layout.playButtonView.setOnClickListener {
// onPlayButtonTapped(playerState.stationUuid, playerState.playbackState)
// //onPlayButtonTapped(playerState.stationUuid, playerController.getPlaybackState().state) // todo remove
// }
//
// // register a callback to stay in sync
// playerController.registerCallback(mediaControllerCallback)
// }
/* Sets up the player */ /* Sets up the player */
private fun updatePlayerViews() { private fun updatePlayerViews() {
// get station // get station
@@ -591,7 +575,7 @@ class PlayerFragment : Fragment(),
/* Handles ACTION_SHOW_PLAYER request from notification */ /* Handles ACTION_SHOW_PLAYER request from notification */
private fun handleShowPlayer() { private fun handleShowPlayer() {
Log.i(TAG, "Tap on notification registered.") Log.i(TAG, "Tap on notification registered.")
// todo implement layout.showPlayer(requireActivity())
} }
@@ -804,7 +788,7 @@ class PlayerFragment : Fragment(),
.setAction(R.string.snackbar_show) { .setAction(R.string.snackbar_show) {
val releaseurl = getString(R.string.snackbar_url_app_home_page) val releaseurl = getString(R.string.snackbar_url_app_home_page)
val i = Intent(Intent.ACTION_VIEW) val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(releaseurl) i.data = releaseurl.toUri()
// Not sure that does anything // Not sure that does anything
i.putExtra("SOURCE", "SELF") i.putExtra("SOURCE", "SELF")
startActivity(i) startActivity(i)
@@ -823,9 +807,5 @@ class PlayerFragment : Fragment(),
request.tag = TAG request.tag = TAG
queue.add(request) queue.add(request)
} }
/*
* End of declaration
*/
} }

View File

@@ -397,75 +397,6 @@ class PlayerService : MediaLibraryService() {
return super.onCustomCommand(session, controller, customCommand, args) return super.onCustomCommand(session, controller, customCommand, args)
} }
override fun onPlayerCommandRequest(
session: MediaSession,
controller: MediaSession.ControllerInfo,
playerCommand: Int
): Int {
// playerCommand = one of COMMAND_PLAY_PAUSE, COMMAND_PREPARE, COMMAND_STOP, COMMAND_SEEK_TO_DEFAULT_POSITION, COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, COMMAND_SEEK_TO_PREVIOUS, COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, COMMAND_SEEK_TO_NEXT, COMMAND_SEEK_TO_MEDIA_ITEM, COMMAND_SEEK_BACK, COMMAND_SEEK_FORWARD, COMMAND_SET_SPEED_AND_PITCH, COMMAND_SET_SHUFFLE_MODE, COMMAND_SET_REPEAT_MODE, COMMAND_GET_CURRENT_MEDIA_ITEM, COMMAND_GET_TIMELINE, COMMAND_GET_MEDIA_ITEMS_METADATA, COMMAND_SET_MEDIA_ITEMS_METADATA, COMMAND_CHANGE_MEDIA_ITEMS, COMMAND_GET_AUDIO_ATTRIBUTES, COMMAND_GET_VOLUME, COMMAND_GET_DEVICE_VOLUME, COMMAND_SET_VOLUME, COMMAND_SET_DEVICE_VOLUME, COMMAND_ADJUST_DEVICE_VOLUME, COMMAND_SET_VIDEO_SURFACE, COMMAND_GET_TEXT, COMMAND_SET_TRACK_SELECTION_PARAMETERS or COMMAND_GET_TRACK_INFOS. */
// emulate headphone buttons
// start/pause: adb shell input keyevent 85
// next: adb shell input keyevent 87
// prev: adb shell input keyevent 88
when (playerCommand) {
Player.COMMAND_SEEK_TO_NEXT -> {
player.addMediaItem(
CollectionHelper.getNextMediaItem(
this@PlayerService,
collection,
player.currentMediaItem?.mediaId ?: String()
)
)
player.prepare()
player.play()
return SessionResult.RESULT_SUCCESS
}
Player.COMMAND_SEEK_TO_PREVIOUS -> {
player.addMediaItem(
CollectionHelper.getPreviousMediaItem(
this@PlayerService,
collection,
player.currentMediaItem?.mediaId ?: String()
)
)
player.prepare()
player.play()
return SessionResult.RESULT_SUCCESS
}
Player.COMMAND_PREPARE -> {
return if (playLastStation) {
// special case: system requested media resumption (see also onGetLibraryRoot)
player.addMediaItem(CollectionHelper.getRecent(this@PlayerService, collection))
player.prepare()
playLastStation = false
SessionResult.RESULT_SUCCESS
} else {
super.onPlayerCommandRequest(session, controller, playerCommand)
}
}
Player.COMMAND_PLAY_PAUSE -> {
return if (player.isPlaying) {
super.onPlayerCommandRequest(session, controller, playerCommand)
} else {
// seek to the start of the "live window"
player.seekTo(0)
SessionResult.RESULT_SUCCESS
}
}
// Player.COMMAND_PLAY_PAUSE -> {
// // override pause with stop, to prevent unnecessary buffering
// if (player.isPlaying) {
// player.stop()
// return SessionResult.RESULT_INFO_SKIPPED
// } else {
// return super.onPlayerCommandRequest(session, controller, playerCommand)
// }
// }
else -> {
return super.onPlayerCommandRequest(session, controller, playerCommand)
}
}
}
} }
@@ -480,21 +411,21 @@ class PlayerService : MediaLibraryService() {
customLayout: ImmutableList<CommandButton>, customLayout: ImmutableList<CommandButton>,
showPauseButton: Boolean showPauseButton: Boolean
): ImmutableList<CommandButton> { ): ImmutableList<CommandButton> {
val seekToPreviousCommandButton = CommandButton.Builder().apply { val seekToPreviousCommandButton = CommandButton.Builder()
setPlayerCommand(Player.COMMAND_SEEK_TO_PREVIOUS) .setPlayerCommand(Player.COMMAND_SEEK_TO_PREVIOUS)
setIconResId(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().apply { val playCommandButton = CommandButton.Builder()
setPlayerCommand(Player.COMMAND_PLAY_PAUSE) .setPlayerCommand(Player.COMMAND_PLAY_PAUSE)
setIconResId(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().apply { val seekToNextCommandButton = CommandButton.Builder()
setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT) .setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT)
setIconResId(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(
seekToPreviousCommandButton, seekToPreviousCommandButton,
playCommandButton, playCommandButton,
@@ -544,19 +475,19 @@ class PlayerService : MediaLibraryService() {
when (player.playbackState) { when (player.playbackState) {
// player is able to immediately play from its current position // player is able to immediately play from its current position
Player.STATE_READY -> { Player.STATE_READY -> {
// todo stopSelf()
} }
// buffering - data needs to be loaded // buffering - data needs to be loaded
Player.STATE_BUFFERING -> { Player.STATE_BUFFERING -> {
// todo stopSelf()
} }
// player finished playing all media // player finished playing all media
Player.STATE_ENDED -> { Player.STATE_ENDED -> {
// todo stopSelf()
} }
// initial state or player is stopped or playback failed // initial state or player is stopped or playback failed
Player.STATE_IDLE -> { Player.STATE_IDLE -> {
// todo stopSelf()
} }
} }
} }
@@ -585,7 +516,10 @@ class PlayerService : MediaLibraryService() {
override fun onPlayerError(error: PlaybackException) { override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error) super.onPlayerError(error)
Log.d(TAG, "PlayerError occurred: ${error.errorCodeName}") Log.d(TAG, "PlayerError occurred: ${error.errorCodeName}")
// todo: test if playback needs to be restarted if (error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED || error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT) {
player.prepare()
player.play()
}
} }

View File

@@ -62,7 +62,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
(activity as AppCompatActivity).supportActionBar?.show() (activity as AppCompatActivity).supportActionBar?.show()
(activity as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true) (activity as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true)
(activity as AppCompatActivity).supportActionBar?.title = getString(R.string.fragment_settings_title) (activity as AppCompatActivity).supportActionBar?.title = getString(R.string.fragment_settings_title)
(activity as AppCompatActivity).window.navigationBarColor = getColor(requireContext(), android.R.attr.colorBackground) requireActivity().window.navigationBarColor = getColor(requireContext(), android.R.attr.colorBackground)
} }
/* Overrides onCreatePreferences from PreferenceFragmentCompat */ /* Overrides onCreatePreferences from PreferenceFragmentCompat */

View File

@@ -264,12 +264,12 @@ class CollectionAdapter(
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
}) })
stationViewHolder.cancelButton.setOnClickListener { stationViewHolder.cancelButton.setOnClickListener {
val position: Int = stationViewHolder.adapterPosition val position: Int = stationViewHolder.bindingAdapterPosition
toggleEditViews(position, station.uuid) toggleEditViews(position, station.uuid)
UiHelper.hideSoftKeyboard(context, stationViewHolder.stationNameEditView) UiHelper.hideSoftKeyboard(context, stationViewHolder.stationNameEditView)
} }
stationViewHolder.saveButton.setOnClickListener { stationViewHolder.saveButton.setOnClickListener {
val position: Int = stationViewHolder.adapterPosition val position: Int = stationViewHolder.bindingAdapterPosition
toggleEditViews(position, station.uuid) toggleEditViews(position, station.uuid)
saveStation( saveStation(
station, station,
@@ -280,15 +280,15 @@ class CollectionAdapter(
UiHelper.hideSoftKeyboard(context, stationViewHolder.stationNameEditView) UiHelper.hideSoftKeyboard(context, stationViewHolder.stationNameEditView)
} }
stationViewHolder.placeOnHomeScreenButton.setOnClickListener { stationViewHolder.placeOnHomeScreenButton.setOnClickListener {
val position: Int = stationViewHolder.adapterPosition val position: Int = stationViewHolder.bindingAdapterPosition
ShortcutHelper.placeShortcut(context, station) ShortcutHelper.placeShortcut(context, station)
toggleEditViews(position, station.uuid) toggleEditViews(position, station.uuid)
UiHelper.hideSoftKeyboard(context, stationViewHolder.stationNameEditView) UiHelper.hideSoftKeyboard(context, stationViewHolder.stationNameEditView)
} }
stationViewHolder.stationImageChangeView.setOnClickListener { stationViewHolder.stationImageChangeView.setOnClickListener {
val position: Int = stationViewHolder.adapterPosition val position: Int = stationViewHolder.bindingAdapterPosition
collectionAdapterListener.onChangeImageButtonTapped(station.uuid) collectionAdapterListener.onChangeImageButtonTapped(station.uuid)
stationViewHolder.adapterPosition stationViewHolder.bindingAdapterPosition
toggleEditViews(position, station.uuid) toggleEditViews(position, station.uuid)
UiHelper.hideSoftKeyboard(context, stationViewHolder.stationNameEditView) UiHelper.hideSoftKeyboard(context, stationViewHolder.stationNameEditView)
} }
@@ -378,7 +378,7 @@ class CollectionAdapter(
} }
stationViewHolder.playButtonView.setOnLongClickListener { stationViewHolder.playButtonView.setOnLongClickListener {
if (editStationsEnabled) { if (editStationsEnabled) {
val position: Int = stationViewHolder.adapterPosition val position: Int = stationViewHolder.bindingAdapterPosition
toggleEditViews(position, station.uuid) toggleEditViews(position, station.uuid)
return@setOnLongClickListener true return@setOnLongClickListener true
} else { } else {
@@ -387,7 +387,7 @@ class CollectionAdapter(
} }
stationViewHolder.stationNameView.setOnLongClickListener { stationViewHolder.stationNameView.setOnLongClickListener {
if (editStationsEnabled) { if (editStationsEnabled) {
val position: Int = stationViewHolder.adapterPosition val position: Int = stationViewHolder.bindingAdapterPosition
toggleEditViews(position, station.uuid) toggleEditViews(position, station.uuid)
return@setOnLongClickListener true return@setOnLongClickListener true
} else { } else {
@@ -396,7 +396,7 @@ class CollectionAdapter(
} }
stationViewHolder.stationStarredView.setOnLongClickListener { stationViewHolder.stationStarredView.setOnLongClickListener {
if (editStationsEnabled) { if (editStationsEnabled) {
val position: Int = stationViewHolder.adapterPosition val position: Int = stationViewHolder.bindingAdapterPosition
toggleEditViews(position, station.uuid) toggleEditViews(position, station.uuid)
return@setOnLongClickListener true return@setOnLongClickListener true
} else { } else {
@@ -405,7 +405,7 @@ class CollectionAdapter(
} }
stationViewHolder.stationImageView.setOnLongClickListener { stationViewHolder.stationImageView.setOnLongClickListener {
if (editStationsEnabled) { if (editStationsEnabled) {
val position: Int = stationViewHolder.adapterPosition val position: Int = stationViewHolder.bindingAdapterPosition
toggleEditViews(position, station.uuid) toggleEditViews(position, station.uuid)
return@setOnLongClickListener true return@setOnLongClickListener true
} else { } else {
@@ -471,7 +471,7 @@ class CollectionAdapter(
} else if (holder is StationViewHolder) { } else if (holder is StationViewHolder) {
// get station from position // get station from position
collection.stations[holder.getAdapterPosition()] collection.stations[holder.bindingAdapterPosition]
for (data in payloads) { for (data in payloads) {
when (data as Int) { when (data as Int) {

View File

@@ -106,7 +106,7 @@ class SearchResultAdapter(
} }
// mark selected if necessary // mark selected if necessary
val isSelected = selectedPosition == holder.adapterPosition val isSelected = selectedPosition == holder.bindingAdapterPosition
searchResultViewHolder.searchResultLayout.isSelected = isSelected searchResultViewHolder.searchResultLayout.isSelected = isSelected
// toggle text scrolling (marquee) if necessary // toggle text scrolling (marquee) if necessary
@@ -121,7 +121,7 @@ class SearchResultAdapter(
searchResultViewHolder.searchResultLayout.setOnClickListener { searchResultViewHolder.searchResultLayout.setOnClickListener {
// move marked position // move marked position
val previousSelectedPosition = selectedPosition val previousSelectedPosition = selectedPosition
selectedPosition = holder.adapterPosition selectedPosition = holder.bindingAdapterPosition
notifyItemChanged(previousSelectedPosition) notifyItemChanged(previousSelectedPosition)
notifyItemChanged(selectedPosition) notifyItemChanged(selectedPosition)
@@ -133,7 +133,7 @@ class SearchResultAdapter(
resetSelection(false) resetSelection(false)
} else { } else {
// get the selected station from searchResults // get the selected station from searchResults
val selectedStation = searchResults[holder.adapterPosition] val selectedStation = searchResults[holder.bindingAdapterPosition]
// perform pre-playback here // perform pre-playback here
performPrePlayback(searchResultViewHolder.searchResultLayout.context, selectedStation.getStreamUri()) performPrePlayback(searchResultViewHolder.searchResultLayout.context, selectedStation.getStreamUri())
// hand over station // hand over station

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- App Name --> <!-- App Name -->
<string name="app_version_name" translatable="false">\"Blue\"</string> <string name="app_version_name" translatable="false">\"Red\"</string>
<!-- Accessibility Descriptions --> <!-- Accessibility Descriptions -->
<string name="descr_app_icon">App icon depicting an old radio</string> <string name="descr_app_icon">App icon depicting an old radio</string>

View File

@@ -14,4 +14,3 @@ org.gradle.jvmargs=-Xmx1536m
# enables androidx repo # enables androidx repo
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true