mirror of
https://github.com/Michatec/Radio.git
synced 2026-04-01 16:06:27 +02:00
Add support for Android TV and update dependencies
* Implement initial Android TV support, including `LEANBACK_LAUNCHER` intent filter, hardware feature declarations, and television-specific layouts for the player, search results, and dialogs. * Add a splash/loading screen for the TV interface and a dedicated `SplashTheme`. * Improve DPAD navigation by adding `OnKeyListener` for station cards and allowing focus on internal elements. * Update `LayoutHolder` and `PlayerFragment` to handle TV layouts and add previous/next station navigation buttons. * Adjust `PreferencesHelper` to disable station editing by default on TV devices. * Update `androidx.media3` to v1.10.0, `work-runtime-ktx` to v2.11.2, and add `androidx.leanback` dependency. * Bump `versionCode` to 144 and `versionName` to 14.4. * Refactor `PlayerService` to simplify sleep timer cancellation logic. * Remove stale copyright headers and license comments from several Kotlin files.
This commit is contained in:
@@ -1,17 +1,3 @@
|
||||
/*
|
||||
* Keys.kt
|
||||
* Implements the keys used throughout the app
|
||||
* This object hosts all keys used to control Radio state
|
||||
*
|
||||
* This file is part of
|
||||
* TRANSISTOR - Radio App for Android
|
||||
*
|
||||
* Copyright (c) 2015-22 - Y20K.org
|
||||
* Licensed under the MIT-License
|
||||
* http://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
|
||||
package com.michatec.radio
|
||||
|
||||
import java.util.*
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
package com.michatec.radio
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
@@ -38,6 +42,7 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
/* Overrides onCreate from AppCompatActivity */
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(R.style.AppTheme)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Free Android
|
||||
@@ -58,6 +63,15 @@ class MainActivity : AppCompatActivity() {
|
||||
NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration)
|
||||
supportActionBar?.hide()
|
||||
|
||||
// TV-specific loading logic
|
||||
if (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
findViewById<View>(R.id.loading_layout)?.visibility = View.GONE
|
||||
}, 1500)
|
||||
} else {
|
||||
findViewById<View>(R.id.loading_layout)?.visibility = View.GONE
|
||||
}
|
||||
|
||||
// register listener for changes in shared preferences
|
||||
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||
}
|
||||
|
||||
@@ -462,7 +462,7 @@ class PlayerFragment : Fragment(),
|
||||
swipeToMarkStarredItemTouchHelper.attachToRecyclerView(layout.recyclerView)
|
||||
|
||||
// set up sleep timer start button
|
||||
layout.sheetSleepTimerStartButtonView.setOnClickListener {
|
||||
layout.sheetSleepTimerStartButtonView?.setOnClickListener {
|
||||
when (controller?.isPlaying) {
|
||||
true -> {
|
||||
val timePicker = MaterialTimePicker.Builder()
|
||||
@@ -492,12 +492,28 @@ class PlayerFragment : Fragment(),
|
||||
}
|
||||
|
||||
// set up sleep timer cancel button
|
||||
layout.sheetSleepTimerCancelButtonView.setOnClickListener {
|
||||
layout.sheetSleepTimerCancelButtonView?.setOnClickListener {
|
||||
playerState.sleepTimerRunning = false
|
||||
controller?.cancelSleepTimer()
|
||||
togglePeriodicSleepTimerUpdateRequest()
|
||||
}
|
||||
|
||||
// set up TV station navigation
|
||||
layout.playerPrevButtonView?.setOnClickListener {
|
||||
val currentPosition = CollectionHelper.getStationPosition(collection, playerState.stationUuid)
|
||||
if (currentPosition > 0) {
|
||||
val prevStation = collection.stations[currentPosition - 1]
|
||||
onPlayButtonTapped(prevStation.uuid)
|
||||
}
|
||||
}
|
||||
layout.playerNextButtonView?.setOnClickListener {
|
||||
val currentPosition = CollectionHelper.getStationPosition(collection, playerState.stationUuid)
|
||||
if (currentPosition < collection.stations.size - 1) {
|
||||
val nextStation = collection.stations[currentPosition + 1]
|
||||
onPlayButtonTapped(nextStation.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Sets up the player */
|
||||
@@ -740,6 +756,7 @@ class PlayerFragment : Fragment(),
|
||||
layout.showBufferingIndicator(buffering = false)
|
||||
} else {
|
||||
// playback is paused or stopped
|
||||
layout.updateSleepTimer(activity as Context, 0L)
|
||||
// check if buffering (playback is not active but playWhenReady is true)
|
||||
if (controller?.playWhenReady == true) {
|
||||
// playback is buffering, show the buffering indicator
|
||||
|
||||
@@ -210,12 +210,9 @@ class PlayerService : MediaLibraryService() {
|
||||
/* Cancels sleep timer */
|
||||
private fun cancelSleepTimer() {
|
||||
if (this::sleepTimer.isInitialized) {
|
||||
if (manuallyCancelledSleepTimer) {
|
||||
sleepTimerTimeRemaining = 0L
|
||||
sleepTimer.cancel()
|
||||
}
|
||||
manuallyCancelledSleepTimer = false
|
||||
sleepTimer.cancel()
|
||||
}
|
||||
sleepTimerTimeRemaining = 0L
|
||||
// store timer state
|
||||
PreferencesHelper.saveSleepTimerRunning(isRunning = false)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
/*
|
||||
* SettingsFragment.kt
|
||||
* Implements the SettingsFragment fragment
|
||||
* A SettingsFragment displays the user accessible settings of the app
|
||||
*
|
||||
* This file is part of
|
||||
* TRANSISTOR - Radio App for Android
|
||||
*
|
||||
* Copyright (c) 2015-22 - Y20K.org
|
||||
* Licensed under the MIT-License
|
||||
* http://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
|
||||
package com.michatec.radio
|
||||
|
||||
import android.app.Activity
|
||||
@@ -92,6 +78,8 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||
val index: Int = preference.entryValues.indexOf(newValue)
|
||||
preferenceThemeSelection.summary =
|
||||
"${getString(R.string.pref_theme_selection_summary)} ${preference.entries[index]}"
|
||||
|
||||
AppThemeHelper.setTheme(newValue as String)
|
||||
return@setOnPreferenceChangeListener true
|
||||
} else {
|
||||
return@setOnPreferenceChangeListener false
|
||||
@@ -193,7 +181,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||
preferenceEnableEditingStreamUri.key = Keys.PREF_EDIT_STREAMS_URIS
|
||||
preferenceEnableEditingStreamUri.summaryOn = getString(R.string.pref_edit_station_stream_summary_enabled)
|
||||
preferenceEnableEditingStreamUri.summaryOff = getString(R.string.pref_edit_station_stream_summary_disabled)
|
||||
preferenceEnableEditingStreamUri.setDefaultValue(PreferencesHelper.loadEditStreamUrisEnabled())
|
||||
preferenceEnableEditingStreamUri.setDefaultValue(PreferencesHelper.loadEditStreamUrisEnabled(context))
|
||||
|
||||
|
||||
// set up "Edit Stations" preference
|
||||
@@ -203,7 +191,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
||||
preferenceEnableEditingGeneral.key = Keys.PREF_EDIT_STATIONS
|
||||
preferenceEnableEditingGeneral.summaryOn = getString(R.string.pref_edit_station_summary_enabled)
|
||||
preferenceEnableEditingGeneral.summaryOff = getString(R.string.pref_edit_station_summary_disabled)
|
||||
preferenceEnableEditingGeneral.setDefaultValue(PreferencesHelper.loadEditStationsEnabled())
|
||||
preferenceEnableEditingGeneral.setDefaultValue(PreferencesHelper.loadEditStationsEnabled(context))
|
||||
preferenceEnableEditingGeneral.setOnPreferenceChangeListener { _, newValue ->
|
||||
when (newValue) {
|
||||
true -> {
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -62,8 +63,8 @@ class CollectionAdapter(
|
||||
/* Main class variables */
|
||||
private lateinit var collectionViewModel: CollectionViewModel
|
||||
private var collection: Collection = Collection()
|
||||
private var editStationsEnabled: Boolean = PreferencesHelper.loadEditStationsEnabled()
|
||||
private var editStationStreamsEnabled: Boolean = PreferencesHelper.loadEditStreamUrisEnabled()
|
||||
private var editStationsEnabled: Boolean = PreferencesHelper.loadEditStationsEnabled(context)
|
||||
private var editStationStreamsEnabled: Boolean = PreferencesHelper.loadEditStreamUrisEnabled(context)
|
||||
private var expandedStationUuid: String = PreferencesHelper.loadStationListStreamUuid()
|
||||
private var expandedStationPosition: Int = -1
|
||||
var isExpandedForEdit: Boolean = false
|
||||
@@ -214,6 +215,8 @@ class CollectionAdapter(
|
||||
stationViewHolder.stationNameEditView.imeOptions =
|
||||
EditorInfo.IME_ACTION_DONE
|
||||
}
|
||||
// Allow internal focus
|
||||
stationViewHolder.stationCardView.descendantFocusability = ViewGroup.FOCUS_AFTER_DESCENDANTS
|
||||
}
|
||||
// hide edit views
|
||||
else -> {
|
||||
@@ -222,6 +225,8 @@ class CollectionAdapter(
|
||||
stationViewHolder.stationStarredView.isVisible = station.starred
|
||||
stationViewHolder.editViews.isGone = true
|
||||
stationViewHolder.stationUriEditView.isGone = true
|
||||
// Block internal focus
|
||||
stationViewHolder.stationCardView.descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -387,6 +392,7 @@ class CollectionAdapter(
|
||||
false -> stationViewHolder.playButtonView.visibility = View.INVISIBLE
|
||||
}
|
||||
stationViewHolder.stationCardView.setOnClickListener {
|
||||
if (expandedStationPosition == stationViewHolder.bindingAdapterPosition) return@setOnClickListener
|
||||
collectionAdapterListener.onPlayButtonTapped(station.uuid)
|
||||
}
|
||||
stationViewHolder.playButtonView.setOnClickListener {
|
||||
@@ -401,6 +407,29 @@ class CollectionAdapter(
|
||||
stationViewHolder.stationImageView.setOnClickListener {
|
||||
collectionAdapterListener.onPlayButtonTapped(station.uuid)
|
||||
}
|
||||
|
||||
// TV improvement: Allow opening edit view with DPAD_LEFT
|
||||
stationViewHolder.stationCardView.setOnKeyListener { _, keyCode, event ->
|
||||
if (event.action == KeyEvent.ACTION_DOWN) {
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
||||
if (editStationsEnabled && expandedStationPosition != stationViewHolder.bindingAdapterPosition) {
|
||||
val position: Int = stationViewHolder.bindingAdapterPosition
|
||||
toggleEditViews(position, station.uuid)
|
||||
return@setOnKeyListener true
|
||||
}
|
||||
}
|
||||
KeyEvent.KEYCODE_BACK -> {
|
||||
if (expandedStationPosition == stationViewHolder.bindingAdapterPosition) {
|
||||
toggleEditViews(stationViewHolder.bindingAdapterPosition, station.uuid)
|
||||
return@setOnKeyListener true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
stationViewHolder.playButtonView.setOnLongClickListener {
|
||||
if (editStationsEnabled) {
|
||||
val position: Int = stationViewHolder.bindingAdapterPosition
|
||||
@@ -649,9 +678,9 @@ class CollectionAdapter(
|
||||
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||
when (key) {
|
||||
Keys.PREF_EDIT_STATIONS -> editStationsEnabled =
|
||||
PreferencesHelper.loadEditStationsEnabled()
|
||||
PreferencesHelper.loadEditStationsEnabled(context)
|
||||
Keys.PREF_EDIT_STREAMS_URIS -> editStationStreamsEnabled =
|
||||
PreferencesHelper.loadEditStreamUrisEnabled()
|
||||
PreferencesHelper.loadEditStreamUrisEnabled(context)
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
@@ -16,7 +16,9 @@ package com.michatec.radio.dialogs
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Button
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isGone
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -45,6 +47,8 @@ class AddStationDialog (
|
||||
/* Main class variables */
|
||||
private lateinit var dialog: AlertDialog
|
||||
private lateinit var stationSearchResultList: RecyclerView
|
||||
private var customPositiveButton: Button? = null
|
||||
private var customNegativeButton: Button? = null
|
||||
private lateinit var searchResultAdapter: SearchResultAdapter
|
||||
private var station: Station = Station()
|
||||
|
||||
@@ -73,6 +77,10 @@ class AddStationDialog (
|
||||
// set up list of search results
|
||||
setupRecyclerView(context)
|
||||
|
||||
// find custom buttons (for TV layout)
|
||||
customPositiveButton = view.findViewById(R.id.dialog_positive_button)
|
||||
customNegativeButton = view.findViewById(R.id.dialog_negative_button)
|
||||
|
||||
// add okay ("Add") button
|
||||
builder.setPositiveButton(R.string.dialog_find_station_button_add) { _, _ ->
|
||||
// listen for click on add button
|
||||
@@ -88,6 +96,17 @@ class AddStationDialog (
|
||||
searchResultAdapter.stopPrePlayback()
|
||||
}
|
||||
|
||||
// set up custom buttons if they exist (TV layout)
|
||||
customPositiveButton?.setOnClickListener {
|
||||
listener.onAddStationDialog(station)
|
||||
searchResultAdapter.stopPrePlayback()
|
||||
dialog.dismiss()
|
||||
}
|
||||
customNegativeButton?.setOnClickListener {
|
||||
searchResultAdapter.stopPrePlayback()
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
// set dialog view
|
||||
builder.setView(view)
|
||||
|
||||
@@ -95,8 +114,16 @@ class AddStationDialog (
|
||||
dialog = builder.create()
|
||||
dialog.show()
|
||||
|
||||
// initially disable "Add" button
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
// handle button visibility and state
|
||||
if (customPositiveButton != null) {
|
||||
// hide default buttons if custom ones are used
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isGone = true
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isGone = true
|
||||
customPositiveButton?.isEnabled = false
|
||||
} else {
|
||||
// initially disable default "Add" button
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,12 +144,14 @@ class AddStationDialog (
|
||||
/* Implement activateAddButton to enable the "Add" button */
|
||||
override fun activateAddButton() {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
|
||||
customPositiveButton?.isEnabled = true
|
||||
}
|
||||
|
||||
|
||||
/* Implement deactivateAddButton to disable the "Add" button */
|
||||
override fun deactivateAddButton() {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
customPositiveButton?.isEnabled = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Button
|
||||
import android.widget.ProgressBar
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.SearchView
|
||||
@@ -63,6 +64,8 @@ class FindStationDialog (
|
||||
private lateinit var searchRequestProgressIndicator: ProgressBar
|
||||
private lateinit var noSearchResultsTextView: MaterialTextView
|
||||
private lateinit var stationSearchResultList: RecyclerView
|
||||
private var customPositiveButton: Button? = null
|
||||
private var customNegativeButton: Button? = null
|
||||
private lateinit var searchResultAdapter: SearchResultAdapter
|
||||
private lateinit var radioBrowserSearch: RadioBrowserSearch
|
||||
private lateinit var directInputCheck: DirectInputCheck
|
||||
@@ -134,6 +137,10 @@ class FindStationDialog (
|
||||
// set up list of search results
|
||||
setupRecyclerView(context)
|
||||
|
||||
// find custom buttons (for TV layout)
|
||||
customPositiveButton = view.findViewById(R.id.dialog_positive_button)
|
||||
customNegativeButton = view.findViewById(R.id.dialog_negative_button)
|
||||
|
||||
// add okay ("Add") button
|
||||
builder.setPositiveButton(R.string.dialog_find_station_button_add) { _, _ ->
|
||||
// listen for click on add button
|
||||
@@ -152,6 +159,18 @@ class FindStationDialog (
|
||||
searchResultAdapter.stopPrePlayback()
|
||||
}
|
||||
|
||||
// set up custom buttons if they exist (TV layout)
|
||||
customPositiveButton?.setOnClickListener {
|
||||
listener.onFindStationDialog(station)
|
||||
searchResultAdapter.stopPrePlayback()
|
||||
dialog.dismiss()
|
||||
}
|
||||
customNegativeButton?.setOnClickListener {
|
||||
radioBrowserSearch.stopSearchRequest()
|
||||
searchResultAdapter.stopPrePlayback()
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
// listen for input
|
||||
stationSearchBoxView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextChange(query: String): Boolean {
|
||||
@@ -174,10 +193,18 @@ class FindStationDialog (
|
||||
dialog = builder.create()
|
||||
dialog.show()
|
||||
|
||||
// initially disable "Add" button
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isAllCaps = true
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isAllCaps = true
|
||||
// handle button visibility and state
|
||||
if (customPositiveButton != null) {
|
||||
// hide default buttons if custom ones are used
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isGone = true
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isGone = true
|
||||
customPositiveButton?.isEnabled = false
|
||||
} else {
|
||||
// initially disable default "Add" button
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isAllCaps = true
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isAllCaps = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -242,12 +269,14 @@ class FindStationDialog (
|
||||
/* Makes the "Add" button clickable */
|
||||
override fun activateAddButton() {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
|
||||
customPositiveButton?.isEnabled = true
|
||||
searchRequestProgressIndicator.isGone = true
|
||||
noSearchResultsTextView.isGone = true
|
||||
}
|
||||
|
||||
override fun deactivateAddButton() {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
customPositiveButton?.isEnabled = false
|
||||
searchRequestProgressIndicator.isGone = true
|
||||
noSearchResultsTextView.isGone = true
|
||||
}
|
||||
@@ -256,6 +285,7 @@ class FindStationDialog (
|
||||
/* Resets the dialog layout to default state */
|
||||
private fun resetLayout(clearAdapter: Boolean = false) {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
customPositiveButton?.isEnabled = false
|
||||
searchRequestProgressIndicator.isGone = true
|
||||
noSearchResultsTextView.isGone = true
|
||||
searchResultAdapter.resetSelection(clearAdapter)
|
||||
@@ -265,6 +295,7 @@ class FindStationDialog (
|
||||
/* Display the "No Results" error - hide other unneeded views */
|
||||
private fun showNoResultsError() {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
customPositiveButton?.isEnabled = false
|
||||
searchRequestProgressIndicator.isGone = true
|
||||
noSearchResultsTextView.isVisible = true
|
||||
}
|
||||
@@ -273,6 +304,7 @@ class FindStationDialog (
|
||||
/* Display the "No Results" error - hide other unneeded views */
|
||||
private fun showProgressIndicator() {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||
customPositiveButton?.isEnabled = false
|
||||
searchRequestProgressIndicator.isVisible = true
|
||||
noSearchResultsTextView.isGone = true
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package com.michatec.radio.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
@@ -223,13 +224,15 @@ object PreferencesHelper {
|
||||
|
||||
|
||||
/* Loads value of the option: Edit Stations */
|
||||
fun loadEditStationsEnabled(): Boolean {
|
||||
return sharedPreferences.getBoolean(Keys.PREF_EDIT_STATIONS, true)
|
||||
fun loadEditStationsEnabled(context: Context): Boolean {
|
||||
val defaultValue = !context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||
return sharedPreferences.getBoolean(Keys.PREF_EDIT_STATIONS, defaultValue)
|
||||
}
|
||||
|
||||
/* Loads value of the option: Edit Station Streams */
|
||||
fun loadEditStreamUrisEnabled(): Boolean {
|
||||
return sharedPreferences.getBoolean(Keys.PREF_EDIT_STREAMS_URIS, true)
|
||||
fun loadEditStreamUrisEnabled(context: Context): Boolean {
|
||||
val defaultValue = !context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||
return sharedPreferences.getBoolean(Keys.PREF_EDIT_STREAMS_URIS, defaultValue)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
/*
|
||||
* LayoutHolder.kt
|
||||
* Implements the LayoutHolder class
|
||||
* A LayoutHolder hold references to the main views
|
||||
*
|
||||
* This file is part of
|
||||
* TRANSISTOR - Radio App for Android
|
||||
*
|
||||
* Copyright (c) 2015-22 - Y20K.org
|
||||
* Licensed under the MIT-License
|
||||
* http://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
|
||||
package com.michatec.radio.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
@@ -54,30 +40,32 @@ data class LayoutHolder(var rootView: View) {
|
||||
/* Main class variables */
|
||||
var recyclerView: RecyclerView = rootView.findViewById(R.id.station_list)
|
||||
val layoutManager: LinearLayoutManager
|
||||
private var bottomSheet: ConstraintLayout = rootView.findViewById(R.id.bottom_sheet)
|
||||
private var bottomSheet: ConstraintLayout? = rootView.findViewById(R.id.bottom_sheet)
|
||||
|
||||
//private var sheetMetadataViews: Group
|
||||
private var sleepTimerRunningViews: Group = rootView.findViewById(R.id.sleep_timer_running_views)
|
||||
private var downloadProgressIndicator: ProgressBar = rootView.findViewById(R.id.download_progress_indicator)
|
||||
private var stationImageView: ImageView = rootView.findViewById(R.id.station_icon)
|
||||
private var stationNameView: TextView = rootView.findViewById(R.id.player_station_name)
|
||||
private var metadataView: TextView = rootView.findViewById(R.id.player_station_metadata)
|
||||
private var sleepTimerRunningViews: Group? = rootView.findViewById(R.id.sleep_timer_running_views)
|
||||
private var downloadProgressIndicator: ProgressBar? = rootView.findViewById(R.id.download_progress_indicator)
|
||||
private var stationImageView: ImageView? = rootView.findViewById(R.id.station_icon)
|
||||
private var stationNameView: TextView? = rootView.findViewById(R.id.player_station_name)
|
||||
private var metadataView: TextView? = rootView.findViewById(R.id.player_station_metadata)
|
||||
var playButtonView: ImageButton = rootView.findViewById(R.id.player_play_button)
|
||||
var playerPrevButtonView: ImageButton? = rootView.findViewById(R.id.player_prev_button)
|
||||
var playerNextButtonView: ImageButton? = rootView.findViewById(R.id.player_next_button)
|
||||
private var bufferingIndicator: ProgressBar = rootView.findViewById(R.id.player_buffering_indicator)
|
||||
private var sheetStreamingLinkHeadline: TextView = rootView.findViewById(R.id.sheet_streaming_link_headline)
|
||||
private var sheetStreamingLinkView: TextView = rootView.findViewById(R.id.sheet_streaming_link)
|
||||
private var sheetMetadataHistoryHeadline: TextView = rootView.findViewById(R.id.sheet_metadata_headline)
|
||||
private var sheetMetadataHistoryView: TextView = rootView.findViewById(R.id.sheet_metadata_history)
|
||||
private var sheetNextMetadataView: ImageButton = rootView.findViewById(R.id.sheet_next_metadata_button)
|
||||
private var sheetPreviousMetadataView: ImageButton = rootView.findViewById(R.id.sheet_previous_metadata_button)
|
||||
private var sheetCopyMetadataButtonView: ImageButton = rootView.findViewById(R.id.copy_station_metadata_button)
|
||||
private var sheetShareLinkButtonView: ImageView = rootView.findViewById(R.id.sheet_share_link_button)
|
||||
private var sheetBitrateView: TextView = rootView.findViewById(R.id.sheet_bitrate_view)
|
||||
var sheetSleepTimerStartButtonView: ImageButton = rootView.findViewById(R.id.sleep_timer_start_button)
|
||||
var sheetSleepTimerCancelButtonView: ImageButton = rootView.findViewById(R.id.sleep_timer_cancel_button)
|
||||
private var sheetStreamingLinkHeadline: TextView? = rootView.findViewById(R.id.sheet_streaming_link_headline)
|
||||
private var sheetStreamingLinkView: TextView? = rootView.findViewById(R.id.sheet_streaming_link)
|
||||
private var sheetMetadataHistoryHeadline: TextView? = rootView.findViewById(R.id.sheet_metadata_headline)
|
||||
private var sheetMetadataHistoryView: TextView? = rootView.findViewById(R.id.sheet_metadata_history)
|
||||
private var sheetNextMetadataView: ImageButton? = rootView.findViewById(R.id.sheet_next_metadata_button)
|
||||
private var sheetPreviousMetadataView: ImageButton? = rootView.findViewById(R.id.sheet_previous_metadata_button)
|
||||
private var sheetCopyMetadataButtonView: ImageButton? = rootView.findViewById(R.id.copy_station_metadata_button)
|
||||
private var sheetShareLinkButtonView: ImageView? = rootView.findViewById(R.id.sheet_share_link_button)
|
||||
private var sheetBitrateView: TextView? = rootView.findViewById(R.id.sheet_bitrate_view)
|
||||
var sheetSleepTimerStartButtonView: ImageButton? = rootView.findViewById(R.id.sleep_timer_start_button)
|
||||
var sheetSleepTimerCancelButtonView: ImageButton? = rootView.findViewById(R.id.sleep_timer_cancel_button)
|
||||
private var sheetSleepTimerRemainingTimeView: TextView = rootView.findViewById(R.id.sleep_timer_remaining_time)
|
||||
private var onboardingLayout: ConstraintLayout = rootView.findViewById(R.id.onboarding_layout)
|
||||
private var bottomSheetBehavior: BottomSheetBehavior<ConstraintLayout> = BottomSheetBehavior.from(bottomSheet)
|
||||
private var bottomSheetBehavior: BottomSheetBehavior<ConstraintLayout>? = bottomSheet?.let { BottomSheetBehavior.from(it) }
|
||||
private var metadataHistory: MutableList<String>
|
||||
private var metadataHistoryPosition: Int
|
||||
private var isBuffering: Boolean
|
||||
@@ -97,31 +85,31 @@ data class LayoutHolder(var rootView: View) {
|
||||
recyclerView.itemAnimator = DefaultItemAnimator()
|
||||
|
||||
// set up metadata history next and previous buttons
|
||||
sheetPreviousMetadataView.setOnClickListener {
|
||||
sheetPreviousMetadataView?.setOnClickListener {
|
||||
if (metadataHistory.isNotEmpty()) {
|
||||
if (metadataHistoryPosition > 0) {
|
||||
metadataHistoryPosition -= 1
|
||||
} else {
|
||||
metadataHistoryPosition = metadataHistory.size - 1
|
||||
}
|
||||
sheetMetadataHistoryView.text = metadataHistory[metadataHistoryPosition]
|
||||
sheetMetadataHistoryView?.text = metadataHistory[metadataHistoryPosition]
|
||||
}
|
||||
}
|
||||
sheetNextMetadataView.setOnClickListener {
|
||||
sheetNextMetadataView?.setOnClickListener {
|
||||
if (metadataHistory.isNotEmpty()) {
|
||||
if (metadataHistoryPosition < metadataHistory.size - 1) {
|
||||
metadataHistoryPosition += 1
|
||||
} else {
|
||||
metadataHistoryPosition = 0
|
||||
}
|
||||
sheetMetadataHistoryView.text = metadataHistory[metadataHistoryPosition]
|
||||
sheetMetadataHistoryView?.text = metadataHistory[metadataHistoryPosition]
|
||||
}
|
||||
}
|
||||
sheetMetadataHistoryView.setOnLongClickListener {
|
||||
sheetMetadataHistoryView?.setOnLongClickListener {
|
||||
copyMetadataHistoryToClipboard()
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
sheetMetadataHistoryHeadline.setOnLongClickListener {
|
||||
sheetMetadataHistoryHeadline?.setOnLongClickListener {
|
||||
copyMetadataHistoryToClipboard()
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
@@ -137,29 +125,29 @@ data class LayoutHolder(var rootView: View) {
|
||||
|
||||
// set default metadata views, when playback has stopped
|
||||
if (!isPlaying) {
|
||||
metadataView.text = station.name
|
||||
sheetMetadataHistoryView.text = station.name
|
||||
metadataView?.text = station.name
|
||||
sheetMetadataHistoryView?.text = station.name
|
||||
// sheetMetadataHistoryView.isSelected = true
|
||||
}
|
||||
|
||||
// update name
|
||||
stationNameView.text = station.name
|
||||
stationNameView?.text = station.name
|
||||
|
||||
// toggle text scrolling (marquee) if necessary
|
||||
stationNameView.isSelected = isPlaying
|
||||
stationNameView?.isSelected = isPlaying
|
||||
|
||||
// reduce the shadow left and right because of scrolling (Marquee)
|
||||
stationNameView.setFadingEdgeLength(8)
|
||||
stationNameView?.setFadingEdgeLength(8)
|
||||
|
||||
// update cover
|
||||
if (station.imageColor != -1) {
|
||||
stationImageView.setBackgroundColor(station.imageColor)
|
||||
stationImageView?.setBackgroundColor(station.imageColor)
|
||||
}
|
||||
stationImageView.setImageBitmap(ImageHelper.getStationImage(context, station.smallImage))
|
||||
stationImageView.contentDescription = "${context.getString(R.string.descr_player_station_image)}: ${station.name}"
|
||||
stationImageView?.setImageBitmap(ImageHelper.getStationImage(context, station.smallImage))
|
||||
stationImageView?.contentDescription = "${context.getString(R.string.descr_player_station_image)}: ${station.name}"
|
||||
|
||||
// update streaming link
|
||||
sheetStreamingLinkView.text = station.getStreamUri()
|
||||
sheetStreamingLinkView?.text = station.getStreamUri()
|
||||
|
||||
val bitrateText: CharSequence = if (station.codec.isNotEmpty()) {
|
||||
if (station.bitrate == 0) {
|
||||
@@ -188,50 +176,50 @@ data class LayoutHolder(var rootView: View) {
|
||||
}
|
||||
|
||||
// update bitrate
|
||||
sheetBitrateView.text = bitrateText
|
||||
sheetBitrateView?.text = bitrateText
|
||||
|
||||
// update click listeners
|
||||
sheetStreamingLinkHeadline.setOnClickListener {
|
||||
sheetStreamingLinkHeadline?.setOnClickListener {
|
||||
copyToClipboard(
|
||||
context,
|
||||
sheetStreamingLinkView.text
|
||||
sheetStreamingLinkView?.text ?: ""
|
||||
)
|
||||
}
|
||||
sheetStreamingLinkView.setOnClickListener {
|
||||
sheetStreamingLinkView?.setOnClickListener {
|
||||
copyToClipboard(
|
||||
context,
|
||||
sheetStreamingLinkView.text
|
||||
sheetStreamingLinkView?.text ?: ""
|
||||
)
|
||||
}
|
||||
sheetMetadataHistoryHeadline.setOnClickListener {
|
||||
sheetMetadataHistoryHeadline?.setOnClickListener {
|
||||
copyToClipboard(
|
||||
context,
|
||||
sheetMetadataHistoryView.text
|
||||
sheetMetadataHistoryView?.text ?: ""
|
||||
)
|
||||
}
|
||||
sheetMetadataHistoryView.setOnClickListener {
|
||||
sheetMetadataHistoryView?.setOnClickListener {
|
||||
copyToClipboard(
|
||||
context,
|
||||
sheetMetadataHistoryView.text
|
||||
sheetMetadataHistoryView?.text ?: ""
|
||||
)
|
||||
}
|
||||
sheetCopyMetadataButtonView.setOnClickListener {
|
||||
sheetCopyMetadataButtonView?.setOnClickListener {
|
||||
copyToClipboard(
|
||||
context,
|
||||
sheetMetadataHistoryView.text
|
||||
sheetMetadataHistoryView?.text ?: ""
|
||||
)
|
||||
}
|
||||
sheetBitrateView.setOnClickListener {
|
||||
sheetBitrateView?.setOnClickListener {
|
||||
copyToClipboard(
|
||||
context,
|
||||
sheetBitrateView.text
|
||||
sheetBitrateView?.text ?: ""
|
||||
)
|
||||
}
|
||||
sheetShareLinkButtonView.setOnClickListener {
|
||||
sheetShareLinkButtonView?.setOnClickListener {
|
||||
val share = Intent.createChooser(Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TITLE, stationNameView.text)
|
||||
putExtra(Intent.EXTRA_TEXT, sheetStreamingLinkView.text)
|
||||
putExtra(Intent.EXTRA_TITLE, stationNameView?.text)
|
||||
putExtra(Intent.EXTRA_TEXT, sheetStreamingLinkView?.text ?: "")
|
||||
type = "text/plain"
|
||||
}, null)
|
||||
context.startActivity(share)
|
||||
@@ -264,11 +252,11 @@ data class LayoutHolder(var rootView: View) {
|
||||
fun updateMetadata(metadataHistoryList: MutableList<String>?) {
|
||||
if (!metadataHistoryList.isNullOrEmpty()) {
|
||||
metadataHistory = metadataHistoryList
|
||||
if (metadataHistory.last() != metadataView.text) {
|
||||
if (metadataHistory.last() != metadataView?.text) {
|
||||
metadataHistoryPosition = metadataHistory.size - 1
|
||||
val metadataString = metadataHistory[metadataHistoryPosition]
|
||||
metadataView.text = metadataString
|
||||
sheetMetadataHistoryView.text = metadataString
|
||||
metadataView?.text = metadataString
|
||||
sheetMetadataHistoryView?.text = metadataString
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,14 +266,16 @@ data class LayoutHolder(var rootView: View) {
|
||||
fun updateSleepTimer(context: Context, timeRemaining: Long = 0L) {
|
||||
when (timeRemaining) {
|
||||
0L -> {
|
||||
sleepTimerRunningViews.isGone = true
|
||||
sleepTimerRunningViews?.isGone = true
|
||||
sheetSleepTimerRemainingTimeView.isVisible = false
|
||||
}
|
||||
else -> {
|
||||
sleepTimerRunningViews.isVisible = true
|
||||
sleepTimerRunningViews?.isVisible = true
|
||||
sheetSleepTimerRemainingTimeView.isVisible = true
|
||||
val sleepTimerTimeRemaining = DateTimeHelper.convertToHoursMinutesSeconds(timeRemaining)
|
||||
sheetSleepTimerRemainingTimeView.text = sleepTimerTimeRemaining
|
||||
sheetSleepTimerRemainingTimeView.contentDescription = "${context.getString(R.string.descr_expanded_player_sleep_timer_remaining_time)}: $sleepTimerTimeRemaining"
|
||||
stationNameView.isSelected = false
|
||||
stationNameView?.isSelected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,11 +287,11 @@ data class LayoutHolder(var rootView: View) {
|
||||
playButtonView.setImageResource(R.drawable.ic_audio_waves_animated)
|
||||
val animatedVectorDrawable = playButtonView.drawable as? AnimatedVectorDrawable
|
||||
animatedVectorDrawable?.start()
|
||||
sheetSleepTimerStartButtonView.isVisible = true
|
||||
sheetSleepTimerStartButtonView?.isVisible = true
|
||||
// bufferingIndicator.isVisible = false
|
||||
} else {
|
||||
playButtonView.setImageResource(R.drawable.ic_player_play_symbol_42dp)
|
||||
sheetSleepTimerStartButtonView.isVisible = false
|
||||
sheetSleepTimerStartButtonView?.isVisible = false
|
||||
// bufferingIndicator.isVisible = isBuffering
|
||||
}
|
||||
}
|
||||
@@ -316,8 +306,8 @@ data class LayoutHolder(var rootView: View) {
|
||||
/* Toggles visibility of the download progress indicator */
|
||||
fun toggleDownloadProgressIndicator() {
|
||||
when (PreferencesHelper.loadActiveDownloads()) {
|
||||
Keys.ACTIVE_DOWNLOADS_EMPTY -> downloadProgressIndicator.isGone = true
|
||||
else -> downloadProgressIndicator.isVisible = true
|
||||
Keys.ACTIVE_DOWNLOADS_EMPTY -> downloadProgressIndicator?.isGone = true
|
||||
else -> downloadProgressIndicator?.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,27 +328,20 @@ data class LayoutHolder(var rootView: View) {
|
||||
|
||||
/* Initiates the rotation animation of the play button */
|
||||
fun animatePlaybackButtonStateTransition(context: Context, isPlaying: Boolean) {
|
||||
when (isPlaying) {
|
||||
true -> {
|
||||
val rotateClockwise = AnimationUtils.loadAnimation(context, R.anim.rotate_clockwise_slow)
|
||||
rotateClockwise.setAnimationListener(createAnimationListener(true))
|
||||
playButtonView.startAnimation(rotateClockwise)
|
||||
}
|
||||
false -> {
|
||||
val rotateCounterClockwise = AnimationUtils.loadAnimation(context, R.anim.rotate_counterclockwise_fast)
|
||||
rotateCounterClockwise.setAnimationListener(createAnimationListener(false))
|
||||
playButtonView.startAnimation(rotateCounterClockwise)
|
||||
}
|
||||
|
||||
}
|
||||
// Toggle play button immediately for snappier feel
|
||||
togglePlayButton(isPlaying)
|
||||
}
|
||||
|
||||
|
||||
/* Shows player */
|
||||
fun showPlayer(context: Context): Boolean {
|
||||
UiHelper.setViewMargins(context, recyclerView, 0, 0, 0, Keys.BOTTOM_SHEET_PEEK_HEIGHT)
|
||||
if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_HIDDEN && onboardingLayout.isGone) {
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
if (bottomSheetBehavior != null) {
|
||||
UiHelper.setViewMargins(context, recyclerView, 0, 0, 0, Keys.BOTTOM_SHEET_PEEK_HEIGHT)
|
||||
if (bottomSheetBehavior?.state == BottomSheetBehavior.STATE_HIDDEN && onboardingLayout.isGone) {
|
||||
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
}
|
||||
} else {
|
||||
UiHelper.setViewMargins(context, recyclerView, 0, 0, 0, 0)
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -367,15 +350,15 @@ data class LayoutHolder(var rootView: View) {
|
||||
/* Hides player */
|
||||
private fun hidePlayer(context: Context): Boolean {
|
||||
UiHelper.setViewMargins(context, recyclerView, 0, 0, 0, 0)
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
/* Minimizes player sheet if expanded */
|
||||
fun minimizePlayerIfExpanded(): Boolean {
|
||||
return if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
return if (bottomSheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -399,38 +382,40 @@ data class LayoutHolder(var rootView: View) {
|
||||
|
||||
/* Sets up the player (BottomSheet) */
|
||||
private fun setupBottomSheet() {
|
||||
// show / hide the small player
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
bottomSheetBehavior.addBottomSheetCallback(object :
|
||||
BottomSheetBehavior.BottomSheetCallback() {
|
||||
override fun onSlide(view: View, slideOffset: Float) {
|
||||
}
|
||||
|
||||
override fun onStateChanged(view: View, state: Int) {
|
||||
when (state) {
|
||||
BottomSheetBehavior.STATE_COLLAPSED -> Unit // do nothing
|
||||
BottomSheetBehavior.STATE_DRAGGING -> Unit // do nothing
|
||||
BottomSheetBehavior.STATE_EXPANDED -> Unit // do nothing
|
||||
BottomSheetBehavior.STATE_HALF_EXPANDED -> Unit // do nothing
|
||||
BottomSheetBehavior.STATE_SETTLING -> Unit // do nothing
|
||||
BottomSheetBehavior.STATE_HIDDEN -> showPlayer(rootView.context)
|
||||
if (bottomSheetBehavior != null) {
|
||||
// show / hide the small player
|
||||
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
bottomSheetBehavior?.addBottomSheetCallback(object :
|
||||
BottomSheetBehavior.BottomSheetCallback() {
|
||||
override fun onSlide(view: View, slideOffset: Float) {
|
||||
}
|
||||
}
|
||||
})
|
||||
// toggle collapsed state on tap
|
||||
bottomSheet.setOnClickListener { toggleBottomSheetState() }
|
||||
stationImageView.setOnClickListener { toggleBottomSheetState() }
|
||||
stationNameView.setOnClickListener { toggleBottomSheetState() }
|
||||
metadataView.setOnClickListener { toggleBottomSheetState() }
|
||||
|
||||
override fun onStateChanged(view: View, state: Int) {
|
||||
when (state) {
|
||||
BottomSheetBehavior.STATE_COLLAPSED -> Unit // do nothing
|
||||
BottomSheetBehavior.STATE_DRAGGING -> Unit // do nothing
|
||||
BottomSheetBehavior.STATE_EXPANDED -> Unit // do nothing
|
||||
BottomSheetBehavior.STATE_HALF_EXPANDED -> Unit // do nothing
|
||||
BottomSheetBehavior.STATE_SETTLING -> Unit // do nothing
|
||||
BottomSheetBehavior.STATE_HIDDEN -> showPlayer(rootView.context)
|
||||
}
|
||||
}
|
||||
})
|
||||
// toggle collapsed state on tap
|
||||
bottomSheet?.setOnClickListener { toggleBottomSheetState() }
|
||||
stationImageView?.setOnClickListener { toggleBottomSheetState() }
|
||||
stationNameView?.setOnClickListener { toggleBottomSheetState() }
|
||||
metadataView?.setOnClickListener { toggleBottomSheetState() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Toggle expanded/collapsed state of bottom sheet */
|
||||
private fun toggleBottomSheetState() {
|
||||
when (bottomSheetBehavior.state) {
|
||||
BottomSheetBehavior.STATE_COLLAPSED -> bottomSheetBehavior.state =
|
||||
when (bottomSheetBehavior?.state) {
|
||||
BottomSheetBehavior.STATE_COLLAPSED -> bottomSheetBehavior?.state =
|
||||
BottomSheetBehavior.STATE_EXPANDED
|
||||
else -> bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
else -> bottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user