7 Commits

Author SHA1 Message Date
f96bdc6f07 style(ui): hide loading layout directly 2026-03-30 12:01:37 +02:00
7d63f16c2c chore: cleanup AppThemeHelper and add loading string 2026-03-30 11:34:16 +02:00
47ff40e676 style(ui): remove navigation bar color overrides and redundant v27 styles 2026-03-30 11:12:21 +02:00
dad709f5df feat(ui): implement edge-to-edge support and improve splash transition
- Enable edge-to-edge display in `MainActivity` and handle window insets in `PlayerFragment`.
- Add a fade-out animation when hiding the loading overlay.
- Sync player state with the controller during setup and reset playing state in preferences on service destruction.
- Simplify splash screen drawable and update background color attributes in layout.
- Remove manual navigation bar color overrides.
2026-03-30 11:06:41 +02:00
89b13e152c refactor(ui): improve intent handling and collection view state
- Update `handleStartIntent` and `handleStartPlayer` in `PlayerFragment` to ensure intents are only cleared if successfully handled and stations are valid.
- Refine star icon rendering in `CollectionAdapter` by clearing color filters when appropriate.
- Remove obsolete commented-out code and update log messages in `PlayerFragment`, `PlayerService`, and `LayoutHolder`.
- Uncomment maintenance preference updates in `SettingsFragment`.
- Reformat code for better readability in `CollectionAdapter`.
2026-03-30 10:22:05 +02:00
1eefe1acc4 Merge remote-tracking branch 'origin/main' 2026-03-30 09:18:34 +02:00
1fa8102e1c feat(ui): implement station reordering via DPAD for TV navigation 2026-03-30 09:18:15 +02:00
19 changed files with 155 additions and 103 deletions

View File

@@ -83,11 +83,12 @@ You can help out the radio-browser.info community by [adding the missing station
When **Edit Stations** is enabled: When **Edit Stations** is enabled:
- Press **← (Left)** on the remote to open the detailed station editing area - Press **← (Left)** on the remote to open the detailed station editing area
- Press **2** or **Back** to close the editing area - Press **3** or **Back** to close the editing area
**General TV** Controls: **General TV** Controls:
- Press **0** or **DEL** to remove the selected radio station - Press **0** or **DEL** to remove the selected radio station
- Press **1** or **SPACE** to make the selected radio station favourite - Press **1** or **SPACE** to make the selected radio station favourite
- Press **2** or **PLUS** to reorder the selected radio station
</details> </details>

View File

@@ -6,8 +6,10 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.view.View import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.view.isVisible
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI import androidx.navigation.ui.NavigationUI
@@ -28,6 +30,7 @@ class MainActivity : AppCompatActivity() {
/* Overrides onCreate from AppCompatActivity */ /* Overrides onCreate from AppCompatActivity */
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
setTheme(R.style.AppTheme) setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -49,11 +52,11 @@ class MainActivity : AppCompatActivity() {
NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration) NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration)
supportActionBar?.hide() supportActionBar?.hide()
// TV-specific loading logic // TV-specific loading logic: Hide the overlay once the app is ready
if (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { if (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
findViewById<View>(R.id.loading_layout)?.visibility = View.GONE hideLoadingOverlay()
}, 1500) }, 1200)
} else { } else {
findViewById<View>(R.id.loading_layout)?.visibility = View.GONE findViewById<View>(R.id.loading_layout)?.visibility = View.GONE
} }
@@ -62,6 +65,18 @@ class MainActivity : AppCompatActivity() {
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener) PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
} }
/* Hides the loading/splash overlay */
private fun hideLoadingOverlay() {
findViewById<View>(R.id.loading_layout)?.let { overlay ->
if (overlay.isVisible) {
overlay.animate()
.alpha(0f)
.setDuration(500)
.withEndAction { overlay.visibility = View.GONE }
}
}
}
/* Overrides onResume from AppCompatActivity */ /* Overrides onResume from AppCompatActivity */
override fun onResume() { override fun onResume() {
@@ -75,7 +90,6 @@ class MainActivity : AppCompatActivity() {
/* 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
val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_host_container) as NavHostFragment val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_host_container) as NavHostFragment
val navController = navHostFragment.navController val navController = navHostFragment.navController
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()

View File

@@ -22,6 +22,9 @@ import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
@@ -167,8 +170,11 @@ class PlayerFragment : Fragment(),
// hide action bar // hide action bar
(activity as AppCompatActivity).supportActionBar?.hide() (activity as AppCompatActivity).supportActionBar?.hide()
// set the same background color of the player sheet for the navigation bar ViewCompat.setOnApplyWindowInsetsListener(layout.rootView) { v, insets ->
requireActivity().window.navigationBarColor = ContextCompat.getColor(requireActivity(), R.color.player_sheet_background) val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updatePadding(bottom = systemBars.bottom)
insets
}
// associate the ItemTouchHelper with the RecyclerView // associate the ItemTouchHelper with the RecyclerView
itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback()) itemTouchHelper = ItemTouchHelper(ItemTouchHelperCallback())
itemTouchHelper?.attachToRecyclerView(layout.recyclerView) itemTouchHelper?.attachToRecyclerView(layout.recyclerView)
@@ -242,7 +248,6 @@ class PlayerFragment : Fragment(),
// load player state // load player state
playerState = PreferencesHelper.loadPlayerState() playerState = PreferencesHelper.loadPlayerState()
// recreate player ui // recreate player ui
// setupPlaybackControls()
updatePlayerViews() updatePlayerViews()
updateStationListState() updateStationListState()
togglePeriodicSleepTimerUpdateRequest() togglePeriodicSleepTimerUpdateRequest()
@@ -250,7 +255,7 @@ class PlayerFragment : Fragment(),
observeCollectionViewModel() observeCollectionViewModel()
// 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)
@@ -405,6 +410,13 @@ class PlayerFragment : Fragment(),
private fun setupController() { private fun setupController() {
val controller: MediaController = this.controller ?: return val controller: MediaController = this.controller ?: return
controller.addListener(playerListener) controller.addListener(playerListener)
// Sync local playerState with actual controller state
if (playerState.isPlaying != controller.isPlaying) {
playerState.isPlaying = controller.isPlaying
updatePlayerViews()
}
requestMetadataUpdate() requestMetadataUpdate()
// handle start intent // handle start intent
handleStartIntent() handleStartIntent()
@@ -561,15 +573,27 @@ class PlayerFragment : Fragment(),
/* Handles this activity's start intent */ /* Handles this activity's start intent */
private fun handleStartIntent() { private fun handleStartIntent() {
if ((activity as Activity).intent.action != null) { val intent = (activity as Activity).intent
when ((activity as Activity).intent.action) { if (intent.action != null && intent.action?.isNotEmpty() == true) {
Keys.ACTION_SHOW_PLAYER -> handleShowPlayer() var handled = false
Intent.ACTION_VIEW -> handleViewIntent() when (intent.action) {
Keys.ACTION_START -> handleStartPlayer() Keys.ACTION_SHOW_PLAYER -> {
handleShowPlayer()
handled = true
}
Intent.ACTION_VIEW -> {
handleViewIntent()
handled = true
}
Keys.ACTION_START -> {
handled = handleStartPlayer()
} }
} }
if (handled) {
// clear intent action to prevent double calls // clear intent action to prevent double calls
(activity as Activity).intent.action = "" intent.action = ""
}
}
} }
@@ -590,12 +614,12 @@ class PlayerFragment : Fragment(),
val scheme: String = intentUri.scheme ?: String() val scheme: String = intentUri.scheme ?: String()
// CASE: intent is a web link // CASE: intent is a web link
if (scheme.startsWith("http")) { if (scheme.startsWith("http")) {
Log.i(TAG, "Transistor was started to handle a web link.") Log.i(TAG, "Radio was started to handle a web link.")
stationList.addAll(CollectionHelper.createStationsFromUrl(intentUri.toString())) stationList.addAll(CollectionHelper.createStationsFromUrl(intentUri.toString()))
} }
// CASE: intent is a local file // CASE: intent is a local file
else if (scheme.startsWith("content")) { else if (scheme.startsWith("content")) {
Log.i(TAG, "Transistor was started to handle a local audio playlist.") Log.i(TAG, "Radio was started to handle a local audio playlist.")
stationList.addAll(CollectionHelper.createStationListFromContentUri(activity as Context, intentUri)) stationList.addAll(CollectionHelper.createStationListFromContentUri(activity as Context, intentUri))
} }
withContext(Main) { withContext(Main) {
@@ -612,17 +636,30 @@ class PlayerFragment : Fragment(),
/* Handles START_PLAYER_SERVICE request from App Shortcut */ /* Handles START_PLAYER_SERVICE request from App Shortcut */
private fun handleStartPlayer() { private fun handleStartPlayer(): Boolean {
if (controller == null || collection.stations.isEmpty()) {
return false
}
val intent: Intent = (activity as Activity).intent val intent: Intent = (activity as Activity).intent
if (intent.hasExtra(Keys.EXTRA_START_LAST_PLAYED_STATION)) { if (intent.hasExtra(Keys.EXTRA_START_LAST_PLAYED_STATION)) {
controller?.play(activity as Context, CollectionHelper.getStation(collection, playerState.stationUuid)) val station = CollectionHelper.getStation(collection, playerState.stationUuid)
if (station.isValid()) {
controller?.play(activity as Context, station)
return true
}
} else if (intent.hasExtra(Keys.EXTRA_STATION_UUID)) { } else if (intent.hasExtra(Keys.EXTRA_STATION_UUID)) {
val uuid: String = intent.getStringExtra(Keys.EXTRA_STATION_UUID) ?: String() val uuid: String = intent.getStringExtra(Keys.EXTRA_STATION_UUID) ?: String()
controller?.play(activity as Context, CollectionHelper.getStation(collection, uuid)) val station = CollectionHelper.getStation(collection, uuid)
if (station.isValid()) {
controller?.play(activity as Context, station)
return true
}
} else if (intent.hasExtra(Keys.EXTRA_STREAM_URI)) { } else if (intent.hasExtra(Keys.EXTRA_STREAM_URI)) {
val streamUri: String = intent.getStringExtra(Keys.EXTRA_STREAM_URI) ?: String() val streamUri: String = intent.getStringExtra(Keys.EXTRA_STREAM_URI) ?: String()
controller?.playStreamDirectly(streamUri) controller?.playStreamDirectly(streamUri)
return true
} }
return false
} }
@@ -640,9 +677,9 @@ class PlayerFragment : Fragment(),
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()

View File

@@ -81,7 +81,8 @@ class PlayerService : MediaLibraryService() {
/* Overrides onDestroy from Service */ /* Overrides onDestroy from Service */
override fun onDestroy() { override fun onDestroy() {
// player.removeAnalyticsListener(analyticsListener) // Reset playing state in preferences
PreferencesHelper.saveIsPlaying(false)
player.removeListener(playerListener) player.removeListener(playerListener)
player.release() player.release()
mediaLibrarySession.release() mediaLibrarySession.release()
@@ -241,11 +242,6 @@ class PlayerService : MediaLibraryService() {
async(Dispatchers.Default) { FileHelper.readCollectionSuspended(context) } async(Dispatchers.Default) { FileHelper.readCollectionSuspended(context) }
// wait for result and update collection // wait for result and update collection
collection = deferred.await() collection = deferred.await()
// // special case: trigger metadata view update for stations that have no metadata
// if (player.isPlaying && station.name == getCurrentMetadata()) {
// station = CollectionHelper.getStation(collection, station.uuid)
// updateMetadata(null)
// }
} }
} }
@@ -429,7 +425,6 @@ class PlayerService : MediaLibraryService() {
currentMediaId, currentMediaId,
isPlaying isPlaying
) )
//updatePlayerState(station, playbackState)
if (isPlaying) { if (isPlaying) {
// playback is active // playback is active
@@ -506,10 +501,6 @@ class PlayerService : MediaLibraryService() {
// try to reconnect every 5 seconds - up to 20 times // try to reconnect every 5 seconds - up to 20 times
if (loadErrorInfo.errorCount <= Keys.DEFAULT_MAX_RECONNECTION_COUNT && loadErrorInfo.exception is HttpDataSource.HttpDataSourceException) { if (loadErrorInfo.errorCount <= Keys.DEFAULT_MAX_RECONNECTION_COUNT && loadErrorInfo.exception is HttpDataSource.HttpDataSourceException) {
return Keys.RECONNECTION_WAIT_INTERVAL return Keys.RECONNECTION_WAIT_INTERVAL
// } else {
// CoroutineScope(Main).launch {
// player.stop()
// }
} }
return C.TIME_UNSET return C.TIME_UNSET
} }

View File

@@ -21,7 +21,6 @@ import com.michatec.radio.dialogs.ErrorDialog
import com.michatec.radio.dialogs.ThemeSelectionDialog import com.michatec.radio.dialogs.ThemeSelectionDialog
import com.michatec.radio.dialogs.YesNoDialog import com.michatec.radio.dialogs.YesNoDialog
import com.michatec.radio.helpers.* import com.michatec.radio.helpers.*
import com.michatec.radio.helpers.AppThemeHelper.getColor
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -46,7 +45,6 @@ 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)
requireActivity().window.navigationBarColor = getColor(requireContext(), android.R.attr.colorBackground)
} }
/* Overrides onCreatePreferences from PreferenceFragmentCompat */ /* Overrides onCreatePreferences from PreferenceFragmentCompat */
@@ -245,7 +243,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
val preferenceCategoryMaintenance = PreferenceCategory(activity as Context) val preferenceCategoryMaintenance = PreferenceCategory(activity as Context)
preferenceCategoryMaintenance.title = getString(R.string.pref_maintenance_title) preferenceCategoryMaintenance.title = getString(R.string.pref_maintenance_title)
preferenceCategoryMaintenance.contains(preferenceUpdateStationImages) preferenceCategoryMaintenance.contains(preferenceUpdateStationImages)
// preferenceCategoryMaintenance.contains(preferenceUpdateCollection) preferenceCategoryMaintenance.contains(preferenceUpdateCollection)
val preferenceCategoryImportExport = PreferenceCategory(activity as Context) val preferenceCategoryImportExport = PreferenceCategory(activity as Context)
preferenceCategoryImportExport.title = getString(R.string.pref_backup_import_export_title) preferenceCategoryImportExport.title = getString(R.string.pref_backup_import_export_title)
@@ -549,7 +547,6 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
requestRestoreCollectionLauncher.launch(intent) requestRestoreCollectionLauncher.launch(intent)
} catch (exception: Exception) { } catch (exception: Exception) {
Log.e(TAG, "Unable to open file picker for ZIP.\n$exception") Log.e(TAG, "Unable to open file picker for ZIP.\n$exception")
// Toast.makeText(activity as Context, R.string.toast_message_install_file_helper, Toast.LENGTH_LONG).show()
} }
} }
} }

View File

@@ -3,6 +3,7 @@ package com.michatec.radio.collection
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.KeyEvent import android.view.KeyEvent
@@ -14,8 +15,8 @@ import android.widget.ImageView
import android.widget.ProgressBar 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.constraintlayout.widget.Group import androidx.constraintlayout.widget.Group
import androidx.core.content.ContextCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
@@ -25,6 +26,7 @@ import androidx.navigation.findNavController
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.michatec.radio.Keys import com.michatec.radio.Keys
@@ -54,6 +56,7 @@ class CollectionAdapter(
private var expandedStationUuid: String = PreferencesHelper.loadStationListStreamUuid() private var expandedStationUuid: String = PreferencesHelper.loadStationListStreamUuid()
private var expandedStationPosition: Int = -1 private var expandedStationPosition: Int = -1
var isExpandedForEdit: Boolean = false var isExpandedForEdit: Boolean = false
private var reorderStationUuid: String = ""
/* Listener Interface */ /* Listener Interface */
@@ -184,6 +187,15 @@ class CollectionAdapter(
setPlaybackProgress(stationViewHolder, station) setPlaybackProgress(stationViewHolder, station)
setDownloadProgress(stationViewHolder, station) setDownloadProgress(stationViewHolder, station)
// highlight if reordering
if (reorderStationUuid == station.uuid) {
stationViewHolder.stationCardView.setStrokeColor(
ColorStateList.valueOf(
ContextCompat.getColor(context, R.color.cardview_reordering)
)
)
}
// show / hide edit views // show / hide edit views
when (expandedStationPosition) { when (expandedStationPosition) {
// show edit views // show edit views
@@ -310,6 +322,21 @@ class CollectionAdapter(
} }
} }
/* Shows / hides the reorder highlight for a station */
private fun toggleReorderMode(position: Int, stationUuid: String) {
if (reorderStationUuid == stationUuid) {
reorderStationUuid = ""
saveCollectionAfterDragDrop()
} else {
// collapse edit views if necessary
if (isExpandedForEdit) {
toggleEditViews(expandedStationPosition, expandedStationUuid)
}
reorderStationUuid = stationUuid
}
notifyItemChanged(position)
}
/* Shows / hides the edit view for a station */ /* Shows / hides the edit view for a station */
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
@@ -345,12 +372,16 @@ class CollectionAdapter(
when (station.starred) { when (station.starred) {
true -> { true -> {
if (station.imageColor != -1) { if (station.imageColor != -1) {
// stationViewHolder.stationCardView.setCardBackgroundColor(station.imageColor)
stationViewHolder.stationStarredView.setColorFilter(station.imageColor) stationViewHolder.stationStarredView.setColorFilter(station.imageColor)
} else {
stationViewHolder.stationStarredView.clearColorFilter()
} }
stationViewHolder.stationStarredView.isVisible = true stationViewHolder.stationStarredView.isVisible = true
} }
false -> stationViewHolder.stationStarredView.isGone = true false -> {
stationViewHolder.stationStarredView.clearColorFilter()
stationViewHolder.stationStarredView.isGone = true
}
} }
} }
@@ -378,6 +409,7 @@ class CollectionAdapter(
false -> stationViewHolder.playButtonView.visibility = View.INVISIBLE false -> stationViewHolder.playButtonView.visibility = View.INVISIBLE
} }
stationViewHolder.stationCardView.setOnClickListener { stationViewHolder.stationCardView.setOnClickListener {
if (reorderStationUuid.isNotEmpty()) return@setOnClickListener
if (expandedStationPosition == stationViewHolder.bindingAdapterPosition) return@setOnClickListener if (expandedStationPosition == stationViewHolder.bindingAdapterPosition) return@setOnClickListener
collectionAdapterListener.onPlayButtonTapped(station.uuid) collectionAdapterListener.onPlayButtonTapped(station.uuid)
} }
@@ -394,9 +426,35 @@ class CollectionAdapter(
collectionAdapterListener.onPlayButtonTapped(station.uuid) collectionAdapterListener.onPlayButtonTapped(station.uuid)
} }
// TV improvement: Allow opening edit view with DPAD_LEFT // TV improvement: Allow reordering with DPAD
stationViewHolder.stationCardView.setOnKeyListener { _, keyCode, event -> stationViewHolder.stationCardView.setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN) { if (event.action == KeyEvent.ACTION_DOWN) {
// Reorder mode handling
if (reorderStationUuid == station.uuid) {
when (keyCode) {
KeyEvent.KEYCODE_DPAD_UP -> {
val currentPos = stationViewHolder.bindingAdapterPosition
if (currentPos > 0) {
onItemMove(currentPos, currentPos - 1)
}
return@setOnKeyListener true
}
KeyEvent.KEYCODE_DPAD_DOWN -> {
val currentPos = stationViewHolder.bindingAdapterPosition
if (currentPos < collection.stations.size - 1) {
onItemMove(currentPos, currentPos + 1)
}
return@setOnKeyListener true
}
KeyEvent.KEYCODE_NUMPAD_2, KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_PLUS,
KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_DPAD_CENTER -> {
toggleReorderMode(stationViewHolder.bindingAdapterPosition, station.uuid)
return@setOnKeyListener true
}
else -> return@setOnKeyListener true // Consume other keys in reorder mode
}
}
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_LEFT -> {
if (editStationsEnabled && expandedStationPosition != stationViewHolder.bindingAdapterPosition) { if (editStationsEnabled && expandedStationPosition != stationViewHolder.bindingAdapterPosition) {
@@ -405,7 +463,7 @@ class CollectionAdapter(
return@setOnKeyListener true return@setOnKeyListener true
} }
} }
KeyEvent.KEYCODE_NUMPAD_2, KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_BACK -> { KeyEvent.KEYCODE_NUMPAD_3, KeyEvent.KEYCODE_3, KeyEvent.KEYCODE_BACK -> {
if (expandedStationPosition == stationViewHolder.bindingAdapterPosition) { if (expandedStationPosition == stationViewHolder.bindingAdapterPosition) {
val position: Int = stationViewHolder.bindingAdapterPosition val position: Int = stationViewHolder.bindingAdapterPosition
toggleEditViews(position, station.uuid) toggleEditViews(position, station.uuid)
@@ -420,6 +478,10 @@ class CollectionAdapter(
toggleStarredStation(context, stationViewHolder.bindingAdapterPosition) toggleStarredStation(context, stationViewHolder.bindingAdapterPosition)
return@setOnKeyListener true return@setOnKeyListener true
} }
KeyEvent.KEYCODE_NUMPAD_2, KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_PLUS -> {
toggleReorderMode(stationViewHolder.bindingAdapterPosition, station.uuid)
return@setOnKeyListener true
}
} }
} }
false false
@@ -703,7 +765,7 @@ class CollectionAdapter(
*/ */
private class StationViewHolder(stationCardLayout: View) : private class StationViewHolder(stationCardLayout: View) :
RecyclerView.ViewHolder(stationCardLayout) { RecyclerView.ViewHolder(stationCardLayout) {
val stationCardView: CardView = stationCardLayout.findViewById(R.id.station_card) val stationCardView: MaterialCardView = stationCardLayout.findViewById(R.id.station_card)
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)

View File

@@ -1,11 +1,7 @@
package com.michatec.radio.helpers package com.michatec.radio.helpers
import android.content.Context import android.content.Context
import android.content.res.TypedArray
import android.util.Log import android.util.Log
import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import com.michatec.radio.Keys import com.michatec.radio.Keys
import com.michatec.radio.R import com.michatec.radio.R
@@ -19,8 +15,6 @@ object AppThemeHelper {
/* Define log tag */ /* Define log tag */
private val TAG: String = AppThemeHelper::class.java.simpleName private val TAG: String = AppThemeHelper::class.java.simpleName
private val sTypedValue = TypedValue()
/* Sets app theme */ /* Sets app theme */
fun setTheme(nightModeState: String) { fun setTheme(nightModeState: String) {
when (nightModeState) { when (nightModeState) {
@@ -62,14 +56,4 @@ object AppThemeHelper {
else -> context.getString(R.string.pref_theme_selection_mode_device_default) else -> context.getString(R.string.pref_theme_selection_mode_device_default)
} }
} }
@ColorInt
fun getColor(context: Context, @AttrRes resource: Int): Int {
val a: TypedArray = context.obtainStyledAttributes(sTypedValue.data, intArrayOf(resource))
val color = a.getColor(0, 0)
a.recycle()
return color
}
} }

View File

@@ -21,7 +21,6 @@ object ShortcutHelper {
/* Places shortcut on Home screen */ /* Places shortcut on Home screen */
fun placeShortcut(context: Context, station: Station) { fun placeShortcut(context: Context, station: Station) {
// credit: https://medium.com/@BladeCoder/using-support-library-26-0-0-you-can-do-bb75911e01e8
if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) { if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) {
val shortcut: ShortcutInfoCompat = ShortcutInfoCompat.Builder(context, station.name) val shortcut: ShortcutInfoCompat = ShortcutInfoCompat.Builder(context, station.name)
.setShortLabel(station.name) .setShortLabel(station.name)

View File

@@ -128,7 +128,6 @@ data class LayoutHolder(var rootView: View) {
if (!isPlaying) { if (!isPlaying) {
metadataView?.text = station.name metadataView?.text = station.name
sheetMetadataHistoryView?.text = station.name sheetMetadataHistoryView?.text = station.name
// sheetMetadataHistoryView.isSelected = true
} }
// update name // update name
@@ -289,11 +288,9 @@ data class LayoutHolder(var rootView: View) {
val animatedVectorDrawable = playButtonView.drawable as? AnimatedVectorDrawable val animatedVectorDrawable = playButtonView.drawable as? AnimatedVectorDrawable
animatedVectorDrawable?.start() animatedVectorDrawable?.start()
sheetSleepTimerStartButtonView?.isVisible = true sheetSleepTimerStartButtonView?.isVisible = true
// bufferingIndicator.isVisible = false
} else { } else {
playButtonView.setImageResource(R.drawable.ic_player_play_symbol_42dp) playButtonView.setImageResource(R.drawable.ic_player_play_symbol_42dp)
sheetSleepTimerStartButtonView?.isVisible = false sheetSleepTimerStartButtonView?.isVisible = false
// bufferingIndicator.isVisible = isBuffering
} }
} }

View File

@@ -4,9 +4,6 @@
<item <item
android:width="160dp" android:width="160dp"
android:height="160dp" android:height="160dp"
android:gravity="center">
<bitmap
android:gravity="center" android:gravity="center"
android:src="@mipmap/ic_launcher" /> android:drawable="@mipmap/ic_launcher" />
</item>
</layer-list> </layer-list>

View File

@@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context=".MainActivity"> tools:context=".MainActivity">

View File

@@ -32,4 +32,5 @@
<color name="player_sheet_text_main">@android:color/system_neutral1_50</color> <color name="player_sheet_text_main">@android:color/system_neutral1_50</color>
<color name="player_sheet_icon">@android:color/system_neutral1_50</color> <color name="player_sheet_icon">@android:color/system_neutral1_50</color>
<color name="cardview_reordering">@android:color/system_accent1_100</color>
</resources> </resources>

View File

@@ -31,9 +31,6 @@
<!-- Don't show light status bar --> <!-- Don't show light status bar -->
<item name="android:statusBarColor">@android:color/transparent</item> <item name="android:statusBarColor">@android:color/transparent</item>
<!-- Set Navigation Bar color -->
<item name="android:navigationBarColor">@color/player_sheet_background</item>
<!-- Set splash screen icon background color --> <!-- Set splash screen icon background color -->
<item name="android:windowSplashScreenIconBackgroundColor">@color/splashBackgroundColor</item> <item name="android:windowSplashScreenIconBackgroundColor">@color/splashBackgroundColor</item>
</style> </style>

View File

@@ -31,4 +31,5 @@
<color name="player_sheet_text_main">#FFFFFFFF</color> <color name="player_sheet_text_main">#FFFFFFFF</color>
<color name="player_sheet_icon">#FFFFFFFF</color> <color name="player_sheet_icon">#FFFFFFFF</color>
<color name="cardview_reordering">#FFDAE2FF</color>
</resources> </resources>

View File

@@ -3,6 +3,5 @@
<style name="SplashTheme" parent="Theme.Material3.DayNight.NoActionBar"> <style name="SplashTheme" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:windowBackground">@drawable/splash_screen</item> <item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:statusBarColor">@color/splashBackgroundColor</item> <item name="android:statusBarColor">@color/splashBackgroundColor</item>
<item name="android:navigationBarColor">@color/splashBackgroundColor</item>
</style> </style>
</resources> </resources>

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Set AppCompats colors -->
<item name="colorPrimary">#FF495D92</item>
<item name="colorAccent">#FF495D92</item>
<item name="android:textColorHighlight">#FF495D92</item>
<item name="colorControlHighlight">#33000000</item>
<item name="android:colorControlHighlight">#33000000</item>
<item name="android:colorFocusedHighlight">#80000000</item>
<!-- Do not use primary colored elevation overlays to present a visual hierarchy - TOO COLORFUL -->
<item name="elevationOverlayEnabled">false</item>
<!-- Switch Theming -->
<item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat.Material3</item>
<!-- Material Alert Dialog Theming -->
<item name="alertDialogTheme">@style/ThemeOverlay.App.AlertDialogTheme</item>
<!-- Use "light" Status Bar -->
<item name="android:windowLightStatusBar">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<!-- Use "light" Navigation Bar -->
<item name="android:windowLightNavigationBar">true</item>
</style>
</resources>

View File

@@ -37,4 +37,5 @@
<color name="default_neutral_medium_light">@android:color/system_neutral1_300</color> <color name="default_neutral_medium_light">@android:color/system_neutral1_300</color>
<color name="default_neutral_dark">@android:color/system_neutral1_600</color> <color name="default_neutral_dark">@android:color/system_neutral1_600</color>
<color name="cardview_reordering">@android:color/system_accent1_900</color>
</resources> </resources>

View File

@@ -39,5 +39,5 @@
<color name="default_neutral_85percent">#D9313033</color> <color name="default_neutral_85percent">#D9313033</color>
<color name="splashBackgroundColor">#FF1D3E66</color> <color name="splashBackgroundColor">#FF1D3E66</color>
<color name="cardview_reordering">#161C2C</color>
</resources> </resources>

View File

@@ -141,5 +141,7 @@
<string name="snackbar_github_update_check_url" translatable="false">https://api.github.com/repos/michatec/Radio/releases/latest</string> <string name="snackbar_github_update_check_url" translatable="false">https://api.github.com/repos/michatec/Radio/releases/latest</string>
<string name="app_name" translatable="false">Radio</string> <string name="app_name" translatable="false">Radio</string>
<string name="icon_launcher" translatable="false">Icon launcher.</string> <string name="icon_launcher" translatable="false">Icon launcher.</string>
<!-- Extras -->
<string name="loading">Loading...</string> <string name="loading">Loading...</string>
</resources> </resources>