mirror of
https://github.com/Michatec/Radio.git
synced 2026-03-31 23:46:28 +02:00
Compare commits
7 Commits
c057aaaf2f
...
f96bdc6f07
| Author | SHA1 | Date | |
|---|---|---|---|
| f96bdc6f07 | |||
| 7d63f16c2c | |||
| 47ff40e676 | |||
| dad709f5df | |||
| 89b13e152c | |||
| 1eefe1acc4 | |||
| 1fa8102e1c |
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
intent.action = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// clear intent action to prevent double calls
|
|
||||||
(activity as Activity).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()
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
<item
|
<item
|
||||||
android:width="160dp"
|
android:width="160dp"
|
||||||
android:height="160dp"
|
android:height="160dp"
|
||||||
android:gravity="center">
|
android:gravity="center"
|
||||||
<bitmap
|
android:drawable="@mipmap/ic_launcher" />
|
||||||
android:gravity="center"
|
|
||||||
android:src="@mipmap/ic_launcher" />
|
|
||||||
</item>
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
@@ -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">
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
|
|
||||||
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
|
||||||
<!-- Set AppCompat’s 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>
|
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user