mirror of
https://github.com/Michatec/Radio.git
synced 2026-03-31 23:46:28 +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:
@@ -19,8 +19,8 @@ android {
|
||||
applicationId 'com.michatec.radio'
|
||||
minSdk 28
|
||||
targetSdk 36
|
||||
versionCode 143
|
||||
versionName '14.3'
|
||||
versionCode 144
|
||||
versionName '14.4'
|
||||
resourceConfigurations += ['en', 'de', 'el', 'nl', 'pl', 'ru','uk', 'ja', 'da', 'fr']
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ dependencies {
|
||||
implementation libs.navigation.fragment.ktx
|
||||
implementation libs.navigation.ui.ktx
|
||||
implementation libs.work.runtime.ktx
|
||||
implementation libs.leanback
|
||||
|
||||
implementation libs.freedroidwarn
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
@@ -10,6 +17,7 @@
|
||||
<application
|
||||
android:name=".Radio"
|
||||
android:allowBackup="true"
|
||||
android:banner="@mipmap/ic_launcher"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
@@ -32,17 +40,19 @@
|
||||
android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||
android:resource="@mipmap/ic_launcher" />
|
||||
|
||||
<!-- Main activity for radio station playback on phone -->
|
||||
<!-- Main activity for radio station playback on phone and TV -->
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|screenSize"
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:theme="@style/SplashTheme"
|
||||
android:exported="true">
|
||||
|
||||
<!-- react to main intents -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- react to be recognized as a music player -->
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
app/src/main/res/color/selector_card_station_stroke.xml
Normal file
5
app/src/main/res/color/selector_card_station_stroke.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/list_card_stroke_focused" android:state_focused="true" />
|
||||
<item android:color="@color/list_card_stroke_background" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/search_result_background_selected" android:state_focused="true" />
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
</selector>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="@color/list_card_stroke_focused" android:state_focused="true" />
|
||||
<item android:color="@color/list_card_stroke_background" />
|
||||
</selector>
|
||||
11
app/src/main/res/drawable/selector_generic_button_focus.xml
Normal file
11
app/src/main/res/drawable/selector_generic_button_focus.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_focused="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#80FFFFFF" />
|
||||
<corners android:radius="8dp" />
|
||||
<stroke android:width="3dp" android:color="@color/default_neutral_white" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
</selector>
|
||||
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- View is "selected" -->
|
||||
<!-- View is "selected" or "focused" (for TV) -->
|
||||
<item android:drawable="@drawable/shape_search_result_item_selected" android:state_selected="true" />
|
||||
<item android:drawable="@drawable/shape_search_result_item_selected" android:state_focused="true" />
|
||||
|
||||
<!-- Default state. -->
|
||||
<item android:drawable="@drawable/shape_search_result_item" />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/default_neutral_lighter" />
|
||||
android:width="4dp"
|
||||
android:color="@color/default_neutral_white" />
|
||||
<size
|
||||
android:width="56dp"
|
||||
android:height="56dp" />
|
||||
|
||||
12
app/src/main/res/drawable/splash_screen.xml
Normal file
12
app/src/main/res/drawable/splash_screen.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/splashBackgroundColor" />
|
||||
<item
|
||||
android:width="160dp"
|
||||
android:height="160dp"
|
||||
android:gravity="center">
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/bottom_sheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/shape_player_sheet_background"
|
||||
android:padding="24dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/station_icon"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:background="@drawable/shape_cover_small"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/RoundedCorners"
|
||||
app:srcCompat="@drawable/ic_default_station_image_72dp" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/player_station_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.HeadlineMedium"
|
||||
android:textColor="@color/player_sheet_text_main"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toStartOf="@+id/player_play_button"
|
||||
app:layout_constraintStart_toEndOf="@+id/station_icon"
|
||||
app:layout_constraintTop_toTopOf="@+id/station_icon"
|
||||
tools:text="Station Name" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/player_station_metadata"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
||||
android:textColor="@color/player_sheet_text_main"
|
||||
app:layout_constraintEnd_toEndOf="@+id/player_station_name"
|
||||
app:layout_constraintStart_toStartOf="@+id/player_station_name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/player_station_name"
|
||||
tools:text="Metadata Info" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/player_play_button"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:background="@drawable/selector_play_button"
|
||||
android:focusable="true"
|
||||
android:scaleType="center"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/station_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/station_icon"
|
||||
app:srcCompat="@drawable/ic_player_play_symbol_42dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/detailed_controls_row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintTop_toBottomOf="@+id/station_icon">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sheet_previous_metadata_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/selector_generic_button_focus"
|
||||
android:focusable="true"
|
||||
app:srcCompat="@drawable/ic_chevron_left_24dp" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/sheet_metadata_history"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:textColor="@color/player_sheet_text_main"
|
||||
tools:text="Metadata History" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sheet_next_metadata_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/selector_generic_button_focus"
|
||||
android:focusable="true"
|
||||
app:srcCompat="@drawable/ic_chevron_right_24dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/copy_station_metadata_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="@drawable/selector_generic_button_focus"
|
||||
android:focusable="true"
|
||||
app:srcCompat="@drawable/ic_copy_content_24dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sleep_timer_start_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="@drawable/selector_generic_button_focus"
|
||||
android:focusable="true"
|
||||
app:srcCompat="@drawable/ic_sleep_timer_24dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sleep_timer_cancel_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@drawable/selector_generic_button_focus"
|
||||
android:focusable="true"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_clear_24dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/sleep_timer_remaining_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
||||
android:textColor="@color/player_sheet_text_main"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/detailed_controls_row"
|
||||
tools:text="15:00" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
54
app/src/main/res/layout-television/dialog_add_station.xml
Normal file
54
app/src/main/res/layout-television/dialog_add_station.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="1000dp"
|
||||
android:layout_height="500dp"
|
||||
android:padding="16dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/station_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.7" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/dialog_button_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/dialog_positive_button"
|
||||
style="@style/Widget.Material3.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dialog_find_station_button_add" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/dialog_negative_button"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dialog_generic_button_cancel" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
88
app/src/main/res/layout-television/dialog_find_station.xml
Normal file
88
app/src/main/res/layout-television/dialog_find_station.xml
Normal file
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="1000dp"
|
||||
android:layout_height="500dp"
|
||||
android:padding="16dp">
|
||||
|
||||
<androidx.appcompat.widget.SearchView
|
||||
android:id="@+id/station_search_box_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:iconifiedByDefault="false"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:queryHint="@string/dialog_find_station_hint" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/search_request_progress_indicator"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="@+id/station_search_box_view"
|
||||
app:layout_constraintStart_toStartOf="@+id/station_search_box_view"
|
||||
app:layout_constraintTop_toBottomOf="@+id/station_search_box_view" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/no_results_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dialog_find_station_no_results"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/search_request_progress_indicator" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/station_search_result_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/no_results_text_view" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.7" />
|
||||
|
||||
<!-- Right side: Actions -->
|
||||
<LinearLayout
|
||||
android:id="@+id/dialog_button_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/guideline"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/dialog_positive_button"
|
||||
style="@style/Widget.Material3.Button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dialog_find_station_button_add" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/dialog_negative_button"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dialog_generic_button_cancel" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="800dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="24dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/dialog_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dialog_error_message_default"
|
||||
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
|
||||
android:textColor="@color/text_default"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/dialog_error_message_default" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/dialog_details_link"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:padding="8dp"
|
||||
android:text="@string/dialog_generic_details_button"
|
||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
||||
android:textColor="@color/text_default"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/dialog_message"
|
||||
tools:text="@string/dialog_generic_details_button" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/dialog_details"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:scrollbars="vertical"
|
||||
android:text="@string/dialog_opml_import_details_default"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
||||
android:textColor="@color/text_default"
|
||||
android:textIsSelectable="true"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/dialog_details_link"
|
||||
tools:text="@string/dialog_opml_import_details_default" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
61
app/src/main/res/layout-television/element_search_result.xml
Normal file
61
app/src/main/res/layout-television/element_search_result.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:nextFocusRight="@+id/dialog_positive_button"
|
||||
android:background="@drawable/selector_search_result_item">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/station_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleLarge"
|
||||
android:textColor="@color/text_default"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Station Name" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/station_url"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="marquee"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
||||
android:textColor="@color/text_lightweight"
|
||||
app:layout_constraintEnd_toEndOf="@+id/station_name"
|
||||
app:layout_constraintStart_toStartOf="@+id/station_name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/station_name"
|
||||
tools:text="http://stream.url" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/station_bitrate"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
||||
android:textColor="@color/text_lightweight"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/station_url"
|
||||
app:layout_constraintStart_toStartOf="@+id/station_url"
|
||||
app:layout_constraintTop_toBottomOf="@+id/station_url"
|
||||
tools:text="128 kbps" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
206
app/src/main/res/layout-television/fragment_player.xml
Normal file
206
app/src/main/res/layout-television/fragment_player.xml
Normal file
@@ -0,0 +1,206 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/station_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:padding="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<include
|
||||
layout="@layout/element_onboarding"
|
||||
android:visibility="gone" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1.2"
|
||||
android:background="@color/player_sheet_background"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/player_ui"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="24dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/station_icon"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/shape_cover_small"
|
||||
android:contentDescription="@string/descr_player_station_image"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/RoundedCorners"
|
||||
app:srcCompat="@drawable/ic_default_station_image_72dp" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/player_station_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:ellipsize="marquee"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.HeadlineMedium"
|
||||
android:textColor="@color/player_sheet_text_main"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/station_icon"
|
||||
tools:text="Station Name" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/player_station_metadata"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
||||
android:textColor="@color/player_sheet_text_main"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/player_station_name"
|
||||
tools:text="Artist - Title" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/controls_row"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/player_station_metadata">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/player_prev_button"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@drawable/selector_generic_button_focus"
|
||||
android:contentDescription="@string/descr_expanded_player_metadata_previous_button"
|
||||
android:focusable="true"
|
||||
android:padding="12dp"
|
||||
app:srcCompat="@drawable/ic_chevron_left_24dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/player_play_button"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:background="@drawable/selector_play_button"
|
||||
android:contentDescription="@string/descr_player_playback_button"
|
||||
android:focusable="true"
|
||||
android:scaleType="center"
|
||||
app:srcCompat="@drawable/ic_player_play_symbol_42dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/player_next_button"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:background="@drawable/selector_generic_button_focus"
|
||||
android:contentDescription="@string/descr_expanded_player_metadata_next_button"
|
||||
android:focusable="true"
|
||||
android:padding="12dp"
|
||||
app:srcCompat="@drawable/ic_chevron_right_24dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/secondary_controls_row"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/controls_row">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/copy_station_metadata_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/selector_generic_button_focus"
|
||||
android:focusable="true"
|
||||
android:padding="8dp"
|
||||
app:srcCompat="@drawable/ic_copy_content_24dp"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sleep_timer_start_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/selector_generic_button_focus"
|
||||
android:focusable="true"
|
||||
android:padding="8dp"
|
||||
app:srcCompat="@drawable/ic_sleep_timer_24dp"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sleep_timer_cancel_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@drawable/selector_generic_button_focus"
|
||||
android:focusable="true"
|
||||
android:padding="8dp"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_clear_24dp"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/player_buffering_indicator"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:indeterminateTint="@color/player_button_buffering"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/controls_row"
|
||||
app:layout_constraintEnd_toEndOf="@+id/controls_row"
|
||||
app:layout_constraintStart_toStartOf="@+id/controls_row"
|
||||
app:layout_constraintTop_toTopOf="@+id/controls_row" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/sleep_timer_remaining_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:textColor="@color/player_sheet_text_main"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/secondary_controls_row"
|
||||
tools:text="15:00" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -29,4 +29,49 @@
|
||||
app:layout_constraintTop_toBottomOf="@+id/main_toolbar"
|
||||
app:navGraph="@navigation/nav_graph_main" />
|
||||
|
||||
<!-- SPLASH / LOADING SCREEN -->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/loading_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/splashBackgroundColor"
|
||||
android:elevation="10dp"
|
||||
android:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/loading_logo"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="160dp"
|
||||
android:contentDescription="@string/icon_launcher"
|
||||
android:src="@mipmap/ic_launcher"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.4" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/loading"
|
||||
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
|
||||
android:textColor="@color/default_neutral_white"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loading_logo" />
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/loading_logo"
|
||||
android:visibility="invisible" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -12,6 +12,8 @@
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:stateListAnimator="@null"
|
||||
app:backgroundTint="@color/list_card_background"
|
||||
app:icon="@drawable/ic_add_24dp"
|
||||
@@ -28,6 +30,8 @@
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:stateListAnimator="@null"
|
||||
app:backgroundTint="@color/list_card_background"
|
||||
app:icon="@drawable/ic_settings_24dp"
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<item name="colorSurface">@color/player_sheet_background</item>
|
||||
<item name="materialAlertDialogBodyTextStyle">@style/TextAppearance.MaterialComponents.Body1</item>
|
||||
<item name="android:backgroundDimAmount">0.64</item>
|
||||
<item name="colorControlActivated">#FFDAE2FF</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.App.TimePicker" parent="ThemeOverlay.MaterialComponents.TimePicker">
|
||||
@@ -49,5 +50,10 @@
|
||||
<item name="android:background">@color/player_sheet_background</item>
|
||||
<item name="dialogCornerRadius">28dp</item>
|
||||
<item name="checkedTextViewStyle">@style/AlertDialog.TextColor</item>
|
||||
<item name="colorControlActivated">#FFDAE2FF</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
<style name="AlertDialog.TextColor" parent="@style/TextAppearance.MaterialComponents.Body1">
|
||||
<item name="android:textColor">?attr/colorControlNormal</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<resources>
|
||||
<!-- NIGHT THEME COLORS -->
|
||||
|
||||
|
||||
8
app/src/main/res/values-television/styles.xml
Normal file
8
app/src/main/res/values-television/styles.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="SplashTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<item name="android:windowBackground">@drawable/splash_screen</item>
|
||||
<item name="android:statusBarColor">@color/splashBackgroundColor</item>
|
||||
<item name="android:navigationBarColor">@color/splashBackgroundColor</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<resources>
|
||||
<!-- DAY THEME COLORS -->
|
||||
|
||||
@@ -16,6 +15,7 @@
|
||||
<!-- list -->
|
||||
<color name="list_card_background">#FFFEFBFF</color>
|
||||
<color name="list_card_stroke_background">#FFC0C6DD</color>
|
||||
<color name="list_card_stroke_focused">#FF495D92</color>
|
||||
<color name="list_card_cover_background">#FFE7E0EC</color>
|
||||
<color name="list_card_delete_background">#FFB3261E</color>
|
||||
<color name="list_card_delete_icon">#FFFFFFFF</color>
|
||||
|
||||
@@ -140,4 +140,6 @@
|
||||
<string name="snackbar_url_app_home_page" translatable="false">https://github.com/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="icon_launcher" translatable="false">Icon launcher.</string>
|
||||
<string name="loading">Loading...</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,29 +2,21 @@
|
||||
<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>
|
||||
|
||||
<!-- Do not use primary colored elevation overlays to present a visual hierarchy - TOO COLORFUL -->
|
||||
<item name="elevationOverlayEnabled">false</item>
|
||||
|
||||
<!-- Switch Theming -->
|
||||
<item name="colorControlActivated">#FFDAE2FF</item>
|
||||
<item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat.Material3</item>
|
||||
|
||||
<!-- Material Alert Dialog Theming -->
|
||||
<item name="materialAlertDialogTheme">@style/ThemeOverlay.App.MaterialAlertDialog</item>
|
||||
<item name="alertDialogTheme">@style/ThemeOverlay.App.AlertDialogTheme</item>
|
||||
|
||||
<!-- Material Time Picker Theming -->
|
||||
<item name="materialTimePickerTheme">@style/ThemeOverlay.App.TimePicker</item>
|
||||
|
||||
<!-- Use "light" Status Bar -->
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="SplashTheme" parent="AppTheme" />
|
||||
|
||||
<style name="Preference.SwitchPreferenceCompat.Material3" parent="@style/Preference.SwitchPreferenceCompat.Material">
|
||||
<item name="widgetLayout">@layout/preference_switch</item>
|
||||
</style>
|
||||
@@ -38,7 +30,11 @@
|
||||
<style name="ThemeOverlay.App.AlertDialogTheme" parent="@style/ThemeOverlay.Material3.MaterialAlertDialog">
|
||||
<item name="android:background">@color/list_card_background</item>
|
||||
<item name="dialogCornerRadius">28dp</item>
|
||||
<item name="checkedTextViewStyle">@style/AlertDialog.TextColor</item>
|
||||
<!-- TV Fix: Explicitly set accent color for radio buttons/checkboxes -->
|
||||
<item name="colorAccent">@color/icon_default</item>
|
||||
<item name="colorControlActivated">@color/icon_default</item>
|
||||
<item name="android:textColorPrimary">@color/text_default</item>
|
||||
<item name="android:textColorSecondary">@color/text_lightweight</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.App.TimePicker" parent="ThemeOverlay.MaterialComponents.TimePicker">
|
||||
@@ -46,13 +42,16 @@
|
||||
</style>
|
||||
|
||||
<style name="AlertDialog.TextColor" parent="@style/TextAppearance.MaterialComponents.Body1">
|
||||
<item name="android:textColor">?attr/colorControlNormal</item>
|
||||
<item name="android:textColor">@color/text_default</item>
|
||||
</style>
|
||||
|
||||
<style name="App.Widget.Material3.CardView.Outlined" parent="@style/Widget.Material3.CardView.Outlined">
|
||||
<item name="strokeColor">@color/list_card_stroke_background</item>
|
||||
<item name="strokeWidth">3dp</item>
|
||||
<item name="strokeColor">@color/selector_card_station_stroke</item>
|
||||
<item name="strokeWidth">4dp</item>
|
||||
<item name="cardCornerRadius">24dp</item>
|
||||
<item name="android:focusable">true</item>
|
||||
<item name="android:clickable">true</item>
|
||||
<item name="android:descendantFocusability">afterDescendants</item>
|
||||
</style>
|
||||
|
||||
<style name="App.Widget.MaterialComponents.TextView" parent="@style/Widget.MaterialComponents.TextView">
|
||||
|
||||
@@ -3,24 +3,25 @@ activityKtx = "1.13.0"
|
||||
agp = "9.1.0"
|
||||
coreKtx = "1.18.0"
|
||||
freedroidwarn = "V1.10"
|
||||
gradleToolchainsFoojayResolverConvention = "1.0.0"
|
||||
gson = "2.13.2"
|
||||
kotlin = "2.3.20"
|
||||
leanback = "1.2.0"
|
||||
material = "1.13.0"
|
||||
material3 = "1.4.0"
|
||||
media = "1.7.1"
|
||||
media3 = "1.9.3"
|
||||
media3 = "1.10.0"
|
||||
navigation = "2.9.7"
|
||||
paletteKtx = "1.0.0"
|
||||
preferenceKtx = "1.2.1"
|
||||
volley = "1.2.1"
|
||||
workRuntimeKtx = "2.11.1"
|
||||
workRuntimeKtx = "2.11.2"
|
||||
|
||||
[libraries]
|
||||
activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" }
|
||||
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
freedroidwarn = { group = "com.github.woheller69", name = "FreeDroidWarn", version.ref = "freedroidwarn" }
|
||||
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
|
||||
leanback = { group = "androidx.leanback", name = "leanback", version.ref = "leanback" }
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
|
||||
media = { group = "androidx.media", name = "media", version.ref = "media" }
|
||||
@@ -38,5 +39,4 @@ work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||
foojay = { id = "org.gradle.toolchains.foojay-resolver-convention", version.ref = "gradleToolchainsFoojayResolverConvention" }
|
||||
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
|
||||
Reference in New Issue
Block a user