mirror of
https://github.com/Michatec/Radio.git
synced 2026-04-01 07:56:27 +02:00
Compare commits
53 Commits
14.2
...
f038e01981
| Author | SHA1 | Date | |
|---|---|---|---|
| f038e01981 | |||
| f66b5af810 | |||
| a6a9daf3a3 | |||
| d79d5e15fa | |||
| c3f37092de | |||
| 48d14fecdb | |||
| c8293b1b6d | |||
| 51c8d5b303 | |||
| 180d5c2218 | |||
| 391999c406 | |||
| fb779bd661 | |||
| 46ebf21c06 | |||
|
|
2d2d95875f | ||
|
|
46e7b905c8 | ||
|
|
d033ae6344 | ||
|
|
fc275d349b | ||
|
|
4e6f7c7c67 | ||
|
|
272d6fd908 | ||
|
|
4a30828c99 | ||
|
|
963f6e7618 | ||
|
|
29ead7e1d8 | ||
| 9140b54a23 | |||
| 57c4075f19 | |||
| 5334b88f1d | |||
| 5978aab0aa | |||
|
|
c3c2ccfdd9 | ||
|
|
38fb4d162b | ||
|
|
3c3c18104b | ||
|
|
4e1ee7d6a7 | ||
|
|
64ba020eae | ||
|
|
b35d7bd67c | ||
|
|
d3dd098639 | ||
|
|
a8f6c7f946 | ||
|
|
765ccc9250 | ||
|
|
c9d6cf27ec | ||
|
|
7b7579d416 | ||
|
|
d452d7f7ee | ||
|
|
2bb3d22cab | ||
|
|
b4ed3e107c | ||
|
|
ef843b601a | ||
|
|
219d54f4e4 | ||
|
|
c8a39bf2d7 | ||
|
|
81ff920c2c | ||
|
|
f4a5209e14 | ||
|
|
c7b7bdcbed | ||
|
|
eae9176f21 | ||
|
|
b3a833fa44 | ||
|
|
35a8ed46ff | ||
|
|
fd18943878 | ||
|
|
094fb508e2 | ||
|
|
874cf1eb39 | ||
|
|
211cb387ad | ||
|
|
5481a06343 |
8
.github/workflows/gradle-publish.yml
vendored
8
.github/workflows/gradle-publish.yml
vendored
@@ -3,8 +3,10 @@ name: Build and publish APK
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
tags:
|
branches: [ "main" ]
|
||||||
- 'b*'
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ANDROID_HOME: /usr/local/lib/android/sdk/
|
ANDROID_HOME: /usr/local/lib/android/sdk/
|
||||||
@@ -38,7 +40,7 @@ jobs:
|
|||||||
## It is not necessary to check for cache hit as it
|
## It is not necessary to check for cache hit as it
|
||||||
## will not download Android SDK again
|
## will not download Android SDK again
|
||||||
#if: steps.cache-android-sdk.outputs.cache-hit != 'true'
|
#if: steps.cache-android-sdk.outputs.cache-hit != 'true'
|
||||||
uses: android-actions/setup-android@v3
|
uses: android-actions/setup-android@v4
|
||||||
with:
|
with:
|
||||||
packages: ''
|
packages: ''
|
||||||
|
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@
|
|||||||
/.idea
|
/.idea
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
|
/gradle/gradle-daemon-jvm.properties
|
||||||
|
/.kotlin
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Copyright (c) 2025 - Michatec
|
Copyright (c) 2026 - Michatec
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
|||||||
30
README.md
30
README.md
@@ -3,6 +3,7 @@
|
|||||||
### ℹ️ About Radio
|
### ℹ️ About Radio
|
||||||
**Radio is an application with a minimalist approach to listening to radio over the Internet.** <br>
|
**Radio is an application with a minimalist approach to listening to radio over the Internet.** <br>
|
||||||
**Radio only offers a very basic search option, and it imports audio streaming links when you tap them in a web browser.** <br>
|
**Radio only offers a very basic search option, and it imports audio streaming links when you tap them in a web browser.** <br>
|
||||||
|
**Radio now also supports Android TV (Beta).** <br>
|
||||||
**Pull request are welcome at any time.**<br>
|
**Pull request are welcome at any time.**<br>
|
||||||
|
|
||||||
**Radio is free software. It is released under the [MIT open source license](https://opensource.org/licenses/MIT).**
|
**Radio is free software. It is released under the [MIT open source license](https://opensource.org/licenses/MIT).**
|
||||||
@@ -10,6 +11,19 @@
|
|||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>❗Warning</summary>
|
||||||
|
<br>
|
||||||
|
<p>
|
||||||
|
Google has announced that, starting in 2026/2027, all apps on certified Android devices will require the developer to submit personal identity details directly to Google.
|
||||||
|
|
||||||
|
Since the developers of this app do not agree to this requirement, this app will no longer work on certified Android devices after that time.
|
||||||
|
</p>
|
||||||
|
<a href="https://github.com/woheller69/FreeDroidWarn">ℹ️ More Information</a>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>⚙️ Install Radio</summary>
|
<summary>⚙️ Install Radio</summary>
|
||||||
<br>
|
<br>
|
||||||
@@ -57,6 +71,22 @@ You can help out the radio-browser.info community by [adding the missing station
|
|||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>📺 Android TV Controls</summary>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
When **Edit Stations** is enabled:
|
||||||
|
- Press **← (Left)** on the remote to open the detailed station editing area
|
||||||
|
- Press **2** or **Back** to close the editing area
|
||||||
|
|
||||||
|
**General TV** Controls:
|
||||||
|
- Press **0** or **DEL** to remove the selected radio station
|
||||||
|
- Press **1** or **SPACE** to make the selected radio station favourite
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>📜️ Credit</summary>
|
<summary>📜️ Credit</summary>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
alias libs.plugins.android.application
|
||||||
id 'kotlin-parcelize'
|
id 'kotlin-parcelize'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,8 +19,8 @@ android {
|
|||||||
applicationId 'com.michatec.radio'
|
applicationId 'com.michatec.radio'
|
||||||
minSdk 28
|
minSdk 28
|
||||||
targetSdk 36
|
targetSdk 36
|
||||||
versionCode 142
|
versionCode 144
|
||||||
versionName '14.2'
|
versionName '14.4'
|
||||||
resourceConfigurations += ['en', 'de', 'el', 'nl', 'pl', 'ru','uk', 'ja', 'da', 'fr']
|
resourceConfigurations += ['en', 'de', 'el', 'nl', 'pl', 'ru','uk', 'ja', 'da', 'fr']
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,24 +55,27 @@ dependencies {
|
|||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
// Google Stuff //
|
// Google Stuff //
|
||||||
implementation 'com.google.android.material:material:1.13.0'
|
implementation libs.material
|
||||||
implementation 'com.google.code.gson:gson:2.13.2'
|
implementation libs.gson
|
||||||
|
|
||||||
// AndroidX Stuff //
|
// AndroidX Stuff //
|
||||||
implementation 'androidx.core:core-ktx:1.17.0'
|
implementation libs.core.ktx
|
||||||
implementation 'androidx.activity:activity-ktx:1.12.4'
|
implementation libs.activity.ktx
|
||||||
implementation 'androidx.palette:palette-ktx:1.0.0'
|
implementation libs.palette.ktx
|
||||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
implementation libs.preference.ktx
|
||||||
implementation 'androidx.media:media:1.7.1'
|
implementation libs.media
|
||||||
implementation 'androidx.media3:media3-exoplayer:1.9.2'
|
implementation libs.media3.exoplayer
|
||||||
implementation 'androidx.media3:media3-exoplayer-hls:1.9.2'
|
implementation libs.media3.exoplayer.hls
|
||||||
implementation 'androidx.media3:media3-session:1.9.2'
|
implementation libs.media3.session
|
||||||
implementation 'androidx.media3:media3-datasource-okhttp:1.9.2'
|
implementation libs.media3.datasource.okhttp
|
||||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.9.7'
|
implementation libs.navigation.fragment.ktx
|
||||||
implementation 'androidx.navigation:navigation-ui-ktx:2.9.7'
|
implementation libs.navigation.ui.ktx
|
||||||
implementation 'androidx.work:work-runtime-ktx:2.11.1'
|
implementation libs.work.runtime.ktx
|
||||||
|
implementation libs.leanback
|
||||||
|
|
||||||
|
implementation libs.freedroidwarn
|
||||||
|
|
||||||
// Volley HTTP request //
|
// Volley HTTP request //
|
||||||
implementation 'com.android.volley:volley:1.2.1'
|
implementation libs.volley
|
||||||
implementation 'androidx.compose.material3:material3:1.4.0'
|
implementation libs.material3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
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.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
@@ -10,6 +17,7 @@
|
|||||||
<application
|
<application
|
||||||
android:name=".Radio"
|
android:name=".Radio"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
android:banner="@mipmap/ic_launcher"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@@ -21,7 +29,7 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="tiramisu">
|
tools:targetApi="33">
|
||||||
|
|
||||||
<!-- ANDROID AUTO SUPPORT -->
|
<!-- ANDROID AUTO SUPPORT -->
|
||||||
<!-- https://developer.android.com/training/auto/audio/ -->
|
<!-- https://developer.android.com/training/auto/audio/ -->
|
||||||
@@ -32,17 +40,19 @@
|
|||||||
android:name="com.google.android.gms.car.notification.SmallIcon"
|
android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||||
android:resource="@mipmap/ic_launcher" />
|
android:resource="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
<!-- Main activity for radio station playback on phone -->
|
<!-- Main activity for radio station playback on phone and TV -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
android:windowSoftInputMode="adjustPan"
|
android:windowSoftInputMode="adjustPan"
|
||||||
|
android:theme="@style/SplashTheme"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<!-- react to main intents -->
|
<!-- react to main intents -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<!-- react to be recognized as a music player -->
|
<!-- react to be recognized as a music player -->
|
||||||
@@ -58,6 +68,7 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<!-- react to playlist-links based on file extension -->
|
<!-- react to playlist-links based on file extension -->
|
||||||
|
<!-- This is intended as an App Link for specific extensions -->
|
||||||
<intent-filter android:autoVerify="true">
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
@@ -66,14 +77,16 @@
|
|||||||
|
|
||||||
<data android:scheme="http" />
|
<data android:scheme="http" />
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
<data android:host="*" />
|
<data android:host="*"
|
||||||
|
tools:ignore="AppLinkUrlError" />
|
||||||
<data android:pathPattern=".*\\.m3u" />
|
<data android:pathPattern=".*\\.m3u" />
|
||||||
<data android:pathPattern=".*\\.m3u8" />
|
<data android:pathPattern=".*\\.m3u8" />
|
||||||
<data android:pathPattern=".*\\.pls" />
|
<data android:pathPattern=".*\\.pls" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<!-- react to playlist-links based on mimetype -->
|
<!-- react to playlist-links based on mimetype -->
|
||||||
<intent-filter android:autoVerify="true">
|
<!-- Note: MIME types prevent strict App Link verification, but are kept as requested -->
|
||||||
|
<intent-filter android:autoVerify="false">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|||||||
@@ -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
|
package com.michatec.radio
|
||||||
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -78,7 +64,6 @@ object Keys {
|
|||||||
// default const values
|
// default const values
|
||||||
const val DEFAULT_SIZE_OF_METADATA_HISTORY: Int = 25
|
const val DEFAULT_SIZE_OF_METADATA_HISTORY: Int = 25
|
||||||
const val DEFAULT_MAX_LENGTH_OF_METADATA_ENTRY: Int = 127
|
const val DEFAULT_MAX_LENGTH_OF_METADATA_ENTRY: Int = 127
|
||||||
const val DEFAULT_DOWNLOAD_OVER_MOBILE: Boolean = false
|
|
||||||
const val ACTIVE_DOWNLOADS_EMPTY: String = "zero"
|
const val ACTIVE_DOWNLOADS_EMPTY: String = "zero"
|
||||||
const val DEFAULT_MAX_RECONNECTION_COUNT: Int = 30
|
const val DEFAULT_MAX_RECONNECTION_COUNT: Int = 30
|
||||||
const val LARGE_BUFFER_SIZE_MULTIPLIER: Int = 8
|
const val LARGE_BUFFER_SIZE_MULTIPLIER: Int = 8
|
||||||
@@ -149,9 +134,6 @@ object Keys {
|
|||||||
const val RADIO_BROWSER_API_BASE: String = "all.api.radio-browser.info"
|
const val RADIO_BROWSER_API_BASE: String = "all.api.radio-browser.info"
|
||||||
const val RADIO_BROWSER_API_DEFAULT: String = "de1.api.radio-browser.info"
|
const val RADIO_BROWSER_API_DEFAULT: String = "de1.api.radio-browser.info"
|
||||||
|
|
||||||
// locations
|
|
||||||
const val LOCATION_DEFAULT_STATION_IMAGE: String = "android.resource://com.michatec.radio/drawable/ic_default_station_image_24dp"
|
|
||||||
|
|
||||||
// sizes (in dp)
|
// sizes (in dp)
|
||||||
const val SIZE_STATION_IMAGE_CARD: Int = 72
|
const val SIZE_STATION_IMAGE_CARD: Int = 72
|
||||||
const val SIZE_STATION_IMAGE_MAXIMUM: Int = 640
|
const val SIZE_STATION_IMAGE_MAXIMUM: Int = 640
|
||||||
|
|||||||
@@ -15,7 +15,11 @@
|
|||||||
package com.michatec.radio
|
package com.michatec.radio
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
@@ -25,7 +29,7 @@ import androidx.navigation.ui.navigateUp
|
|||||||
import com.michatec.radio.helpers.AppThemeHelper
|
import com.michatec.radio.helpers.AppThemeHelper
|
||||||
import com.michatec.radio.helpers.FileHelper
|
import com.michatec.radio.helpers.FileHelper
|
||||||
import com.michatec.radio.helpers.PreferencesHelper
|
import com.michatec.radio.helpers.PreferencesHelper
|
||||||
|
import org.woheller69.freeDroidWarn.FreeDroidWarn
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MainActivity class
|
* MainActivity class
|
||||||
@@ -38,8 +42,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
/* Overrides onCreate from AppCompatActivity */
|
/* Overrides onCreate from AppCompatActivity */
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
setTheme(R.style.AppTheme)
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
// Free Android
|
||||||
|
FreeDroidWarn.showWarningOnUpgrade(this, BuildConfig.VERSION_CODE)
|
||||||
|
|
||||||
// set up views
|
// set up views
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
|
||||||
@@ -55,6 +63,15 @@ class MainActivity : AppCompatActivity() {
|
|||||||
NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration)
|
NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration)
|
||||||
supportActionBar?.hide()
|
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
|
// register listener for changes in shared preferences
|
||||||
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
|
PreferencesHelper.registerPreferenceChangeListener(sharedPreferenceChangeListener)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ class PlayerFragment : Fragment(),
|
|||||||
swipeToMarkStarredItemTouchHelper.attachToRecyclerView(layout.recyclerView)
|
swipeToMarkStarredItemTouchHelper.attachToRecyclerView(layout.recyclerView)
|
||||||
|
|
||||||
// set up sleep timer start button
|
// set up sleep timer start button
|
||||||
layout.sheetSleepTimerStartButtonView.setOnClickListener {
|
layout.sheetSleepTimerStartButtonView?.setOnClickListener {
|
||||||
when (controller?.isPlaying) {
|
when (controller?.isPlaying) {
|
||||||
true -> {
|
true -> {
|
||||||
val timePicker = MaterialTimePicker.Builder()
|
val timePicker = MaterialTimePicker.Builder()
|
||||||
@@ -492,12 +492,28 @@ class PlayerFragment : Fragment(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set up sleep timer cancel button
|
// set up sleep timer cancel button
|
||||||
layout.sheetSleepTimerCancelButtonView.setOnClickListener {
|
layout.sheetSleepTimerCancelButtonView?.setOnClickListener {
|
||||||
playerState.sleepTimerRunning = false
|
playerState.sleepTimerRunning = false
|
||||||
controller?.cancelSleepTimer()
|
controller?.cancelSleepTimer()
|
||||||
togglePeriodicSleepTimerUpdateRequest()
|
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 */
|
/* Sets up the player */
|
||||||
@@ -740,6 +756,7 @@ class PlayerFragment : Fragment(),
|
|||||||
layout.showBufferingIndicator(buffering = false)
|
layout.showBufferingIndicator(buffering = false)
|
||||||
} else {
|
} else {
|
||||||
// playback is paused or stopped
|
// playback is paused or stopped
|
||||||
|
layout.updateSleepTimer(activity as Context, 0L)
|
||||||
// check if buffering (playback is not active but playWhenReady is true)
|
// check if buffering (playback is not active but playWhenReady is true)
|
||||||
if (controller?.playWhenReady == true) {
|
if (controller?.playWhenReady == true) {
|
||||||
// playback is buffering, show the buffering indicator
|
// playback is buffering, show the buffering indicator
|
||||||
|
|||||||
@@ -210,12 +210,9 @@ class PlayerService : MediaLibraryService() {
|
|||||||
/* Cancels sleep timer */
|
/* Cancels sleep timer */
|
||||||
private fun cancelSleepTimer() {
|
private fun cancelSleepTimer() {
|
||||||
if (this::sleepTimer.isInitialized) {
|
if (this::sleepTimer.isInitialized) {
|
||||||
if (manuallyCancelledSleepTimer) {
|
sleepTimer.cancel()
|
||||||
sleepTimerTimeRemaining = 0L
|
|
||||||
sleepTimer.cancel()
|
|
||||||
}
|
|
||||||
manuallyCancelledSleepTimer = false
|
|
||||||
}
|
}
|
||||||
|
sleepTimerTimeRemaining = 0L
|
||||||
// store timer state
|
// store timer state
|
||||||
PreferencesHelper.saveSleepTimerRunning(isRunning = false)
|
PreferencesHelper.saveSleepTimerRunning(isRunning = false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,4 @@ class Radio : Application() {
|
|||||||
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
|
AppThemeHelper.setTheme(PreferencesHelper.loadThemeSelection())
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Implements onTerminate */
|
|
||||||
override fun onTerminate() {
|
|
||||||
super.onTerminate()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
package com.michatec.radio
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
@@ -28,7 +14,6 @@ import androidx.activity.result.ActivityResult
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.*
|
import androidx.preference.*
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
@@ -93,6 +78,8 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
|||||||
val index: Int = preference.entryValues.indexOf(newValue)
|
val index: Int = preference.entryValues.indexOf(newValue)
|
||||||
preferenceThemeSelection.summary =
|
preferenceThemeSelection.summary =
|
||||||
"${getString(R.string.pref_theme_selection_summary)} ${preference.entries[index]}"
|
"${getString(R.string.pref_theme_selection_summary)} ${preference.entries[index]}"
|
||||||
|
|
||||||
|
AppThemeHelper.setTheme(newValue as String)
|
||||||
return@setOnPreferenceChangeListener true
|
return@setOnPreferenceChangeListener true
|
||||||
} else {
|
} else {
|
||||||
return@setOnPreferenceChangeListener false
|
return@setOnPreferenceChangeListener false
|
||||||
@@ -194,7 +181,8 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
|||||||
preferenceEnableEditingStreamUri.key = Keys.PREF_EDIT_STREAMS_URIS
|
preferenceEnableEditingStreamUri.key = Keys.PREF_EDIT_STREAMS_URIS
|
||||||
preferenceEnableEditingStreamUri.summaryOn = getString(R.string.pref_edit_station_stream_summary_enabled)
|
preferenceEnableEditingStreamUri.summaryOn = getString(R.string.pref_edit_station_stream_summary_enabled)
|
||||||
preferenceEnableEditingStreamUri.summaryOff = getString(R.string.pref_edit_station_stream_summary_disabled)
|
preferenceEnableEditingStreamUri.summaryOff = getString(R.string.pref_edit_station_stream_summary_disabled)
|
||||||
preferenceEnableEditingStreamUri.setDefaultValue(PreferencesHelper.loadEditStreamUrisEnabled())
|
preferenceEnableEditingStreamUri.setDefaultValue(PreferencesHelper.loadEditStreamUrisEnabled(context))
|
||||||
|
preferenceEnableEditingStreamUri.isEnabled = PreferencesHelper.loadEditStreamUrisEnabled(context)
|
||||||
|
|
||||||
|
|
||||||
// set up "Edit Stations" preference
|
// set up "Edit Stations" preference
|
||||||
@@ -204,7 +192,7 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
|||||||
preferenceEnableEditingGeneral.key = Keys.PREF_EDIT_STATIONS
|
preferenceEnableEditingGeneral.key = Keys.PREF_EDIT_STATIONS
|
||||||
preferenceEnableEditingGeneral.summaryOn = getString(R.string.pref_edit_station_summary_enabled)
|
preferenceEnableEditingGeneral.summaryOn = getString(R.string.pref_edit_station_summary_enabled)
|
||||||
preferenceEnableEditingGeneral.summaryOff = getString(R.string.pref_edit_station_summary_disabled)
|
preferenceEnableEditingGeneral.summaryOff = getString(R.string.pref_edit_station_summary_disabled)
|
||||||
preferenceEnableEditingGeneral.setDefaultValue(PreferencesHelper.loadEditStationsEnabled())
|
preferenceEnableEditingGeneral.setDefaultValue(PreferencesHelper.loadEditStationsEnabled(context))
|
||||||
preferenceEnableEditingGeneral.setOnPreferenceChangeListener { _, newValue ->
|
preferenceEnableEditingGeneral.setOnPreferenceChangeListener { _, newValue ->
|
||||||
when (newValue) {
|
when (newValue) {
|
||||||
true -> {
|
true -> {
|
||||||
@@ -427,9 +415,9 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
|||||||
val sourceUri: Uri? = result.data?.data
|
val sourceUri: Uri? = result.data?.data
|
||||||
if (sourceUri != null) {
|
if (sourceUri != null) {
|
||||||
// open and import OPML in player fragment
|
// open and import OPML in player fragment
|
||||||
val bundle: Bundle = bundleOf(
|
val bundle = Bundle().apply {
|
||||||
Keys.ARG_RESTORE_COLLECTION to "$sourceUri"
|
putString(Keys.ARG_RESTORE_COLLECTION, "$sourceUri")
|
||||||
)
|
}
|
||||||
this.findNavController().navigate(R.id.player_destination, bundle)
|
this.findNavController().navigate(R.id.player_destination, bundle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -445,7 +433,9 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
|||||||
Snackbar.LENGTH_LONG
|
Snackbar.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
// update collection in player screen
|
// update collection in player screen
|
||||||
val bundle: Bundle = bundleOf(Keys.ARG_UPDATE_COLLECTION to true)
|
val bundle = Bundle().apply {
|
||||||
|
putBoolean(Keys.ARG_UPDATE_COLLECTION, true)
|
||||||
|
}
|
||||||
this.findNavController().navigate(R.id.player_destination, bundle)
|
this.findNavController().navigate(R.id.player_destination, bundle)
|
||||||
} else {
|
} else {
|
||||||
ErrorDialog().show(
|
ErrorDialog().show(
|
||||||
@@ -466,9 +456,9 @@ class SettingsFragment : PreferenceFragmentCompat(), YesNoDialog.YesNoDialogList
|
|||||||
Snackbar.LENGTH_LONG
|
Snackbar.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
// update collection in player screen
|
// update collection in player screen
|
||||||
val bundle: Bundle = bundleOf(
|
val bundle = Bundle().apply {
|
||||||
Keys.ARG_UPDATE_IMAGES to true
|
putBoolean(Keys.ARG_UPDATE_IMAGES, true)
|
||||||
)
|
}
|
||||||
this.findNavController().navigate(R.id.player_destination, bundle)
|
this.findNavController().navigate(R.id.player_destination, bundle)
|
||||||
} else {
|
} else {
|
||||||
ErrorDialog().show(
|
ErrorDialog().show(
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import android.content.Context
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
|
import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -62,8 +63,8 @@ class CollectionAdapter(
|
|||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private lateinit var collectionViewModel: CollectionViewModel
|
private lateinit var collectionViewModel: CollectionViewModel
|
||||||
private var collection: Collection = Collection()
|
private var collection: Collection = Collection()
|
||||||
private var editStationsEnabled: Boolean = PreferencesHelper.loadEditStationsEnabled()
|
private var editStationsEnabled: Boolean = PreferencesHelper.loadEditStationsEnabled(context)
|
||||||
private var editStationStreamsEnabled: Boolean = PreferencesHelper.loadEditStreamUrisEnabled()
|
private var editStationStreamsEnabled: Boolean = PreferencesHelper.loadEditStreamUrisEnabled(context)
|
||||||
private var expandedStationUuid: String = PreferencesHelper.loadStationListStreamUuid()
|
private var expandedStationUuid: String = PreferencesHelper.loadStationListStreamUuid()
|
||||||
private var expandedStationPosition: Int = -1
|
private var expandedStationPosition: Int = -1
|
||||||
var isExpandedForEdit: Boolean = false
|
var isExpandedForEdit: Boolean = false
|
||||||
@@ -214,6 +215,8 @@ class CollectionAdapter(
|
|||||||
stationViewHolder.stationNameEditView.imeOptions =
|
stationViewHolder.stationNameEditView.imeOptions =
|
||||||
EditorInfo.IME_ACTION_DONE
|
EditorInfo.IME_ACTION_DONE
|
||||||
}
|
}
|
||||||
|
// Allow internal focus
|
||||||
|
stationViewHolder.stationCardView.descendantFocusability = ViewGroup.FOCUS_AFTER_DESCENDANTS
|
||||||
}
|
}
|
||||||
// hide edit views
|
// hide edit views
|
||||||
else -> {
|
else -> {
|
||||||
@@ -222,6 +225,8 @@ class CollectionAdapter(
|
|||||||
stationViewHolder.stationStarredView.isVisible = station.starred
|
stationViewHolder.stationStarredView.isVisible = station.starred
|
||||||
stationViewHolder.editViews.isGone = true
|
stationViewHolder.editViews.isGone = true
|
||||||
stationViewHolder.stationUriEditView.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
|
false -> stationViewHolder.playButtonView.visibility = View.INVISIBLE
|
||||||
}
|
}
|
||||||
stationViewHolder.stationCardView.setOnClickListener {
|
stationViewHolder.stationCardView.setOnClickListener {
|
||||||
|
if (expandedStationPosition == stationViewHolder.bindingAdapterPosition) return@setOnClickListener
|
||||||
collectionAdapterListener.onPlayButtonTapped(station.uuid)
|
collectionAdapterListener.onPlayButtonTapped(station.uuid)
|
||||||
}
|
}
|
||||||
stationViewHolder.playButtonView.setOnClickListener {
|
stationViewHolder.playButtonView.setOnClickListener {
|
||||||
@@ -401,6 +407,38 @@ class CollectionAdapter(
|
|||||||
stationViewHolder.stationImageView.setOnClickListener {
|
stationViewHolder.stationImageView.setOnClickListener {
|
||||||
collectionAdapterListener.onPlayButtonTapped(station.uuid)
|
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_NUMPAD_2, KeyEvent.KEYCODE_2, KeyEvent.KEYCODE_BACK -> {
|
||||||
|
if (expandedStationPosition == stationViewHolder.bindingAdapterPosition) {
|
||||||
|
val position: Int = stationViewHolder.bindingAdapterPosition
|
||||||
|
toggleEditViews(position, station.uuid)
|
||||||
|
return@setOnKeyListener true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_NUMPAD_0, KeyEvent.KEYCODE_0, KeyEvent.KEYCODE_DEL -> {
|
||||||
|
removeStation(context, stationViewHolder.bindingAdapterPosition)
|
||||||
|
return@setOnKeyListener true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_NUMPAD_1, KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_SPACE -> {
|
||||||
|
toggleStarredStation(context, stationViewHolder.bindingAdapterPosition)
|
||||||
|
return@setOnKeyListener true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
stationViewHolder.playButtonView.setOnLongClickListener {
|
stationViewHolder.playButtonView.setOnLongClickListener {
|
||||||
if (editStationsEnabled) {
|
if (editStationsEnabled) {
|
||||||
val position: Int = stationViewHolder.bindingAdapterPosition
|
val position: Int = stationViewHolder.bindingAdapterPosition
|
||||||
@@ -649,9 +687,9 @@ class CollectionAdapter(
|
|||||||
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
|
||||||
when (key) {
|
when (key) {
|
||||||
Keys.PREF_EDIT_STATIONS -> editStationsEnabled =
|
Keys.PREF_EDIT_STATIONS -> editStationsEnabled =
|
||||||
PreferencesHelper.loadEditStationsEnabled()
|
PreferencesHelper.loadEditStationsEnabled(context)
|
||||||
Keys.PREF_EDIT_STREAMS_URIS -> editStationStreamsEnabled =
|
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.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.widget.Button
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.view.isGone
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -45,6 +47,8 @@ class AddStationDialog (
|
|||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private lateinit var dialog: AlertDialog
|
private lateinit var dialog: AlertDialog
|
||||||
private lateinit var stationSearchResultList: RecyclerView
|
private lateinit var stationSearchResultList: RecyclerView
|
||||||
|
private var customPositiveButton: Button? = null
|
||||||
|
private var customNegativeButton: Button? = null
|
||||||
private lateinit var searchResultAdapter: SearchResultAdapter
|
private lateinit var searchResultAdapter: SearchResultAdapter
|
||||||
private var station: Station = Station()
|
private var station: Station = Station()
|
||||||
|
|
||||||
@@ -73,6 +77,10 @@ class AddStationDialog (
|
|||||||
// set up list of search results
|
// set up list of search results
|
||||||
setupRecyclerView(context)
|
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
|
// add okay ("Add") button
|
||||||
builder.setPositiveButton(R.string.dialog_find_station_button_add) { _, _ ->
|
builder.setPositiveButton(R.string.dialog_find_station_button_add) { _, _ ->
|
||||||
// listen for click on add button
|
// listen for click on add button
|
||||||
@@ -88,6 +96,17 @@ class AddStationDialog (
|
|||||||
searchResultAdapter.stopPrePlayback()
|
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
|
// set dialog view
|
||||||
builder.setView(view)
|
builder.setView(view)
|
||||||
|
|
||||||
@@ -95,8 +114,16 @@ class AddStationDialog (
|
|||||||
dialog = builder.create()
|
dialog = builder.create()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
// initially disable "Add" button
|
// handle button visibility and state
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
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 */
|
/* Implement activateAddButton to enable the "Add" button */
|
||||||
override fun activateAddButton() {
|
override fun activateAddButton() {
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
|
||||||
|
customPositiveButton?.isEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Implement deactivateAddButton to disable the "Add" button */
|
/* Implement deactivateAddButton to disable the "Add" button */
|
||||||
override fun deactivateAddButton() {
|
override fun deactivateAddButton() {
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||||
|
customPositiveButton?.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import android.os.Handler
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.Button
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
@@ -63,6 +64,8 @@ class FindStationDialog (
|
|||||||
private lateinit var searchRequestProgressIndicator: ProgressBar
|
private lateinit var searchRequestProgressIndicator: ProgressBar
|
||||||
private lateinit var noSearchResultsTextView: MaterialTextView
|
private lateinit var noSearchResultsTextView: MaterialTextView
|
||||||
private lateinit var stationSearchResultList: RecyclerView
|
private lateinit var stationSearchResultList: RecyclerView
|
||||||
|
private var customPositiveButton: Button? = null
|
||||||
|
private var customNegativeButton: Button? = null
|
||||||
private lateinit var searchResultAdapter: SearchResultAdapter
|
private lateinit var searchResultAdapter: SearchResultAdapter
|
||||||
private lateinit var radioBrowserSearch: RadioBrowserSearch
|
private lateinit var radioBrowserSearch: RadioBrowserSearch
|
||||||
private lateinit var directInputCheck: DirectInputCheck
|
private lateinit var directInputCheck: DirectInputCheck
|
||||||
@@ -134,6 +137,10 @@ class FindStationDialog (
|
|||||||
// set up list of search results
|
// set up list of search results
|
||||||
setupRecyclerView(context)
|
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
|
// add okay ("Add") button
|
||||||
builder.setPositiveButton(R.string.dialog_find_station_button_add) { _, _ ->
|
builder.setPositiveButton(R.string.dialog_find_station_button_add) { _, _ ->
|
||||||
// listen for click on add button
|
// listen for click on add button
|
||||||
@@ -152,6 +159,18 @@ class FindStationDialog (
|
|||||||
searchResultAdapter.stopPrePlayback()
|
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
|
// listen for input
|
||||||
stationSearchBoxView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
stationSearchBoxView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextChange(query: String): Boolean {
|
override fun onQueryTextChange(query: String): Boolean {
|
||||||
@@ -174,10 +193,18 @@ class FindStationDialog (
|
|||||||
dialog = builder.create()
|
dialog = builder.create()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
// initially disable "Add" button
|
// handle button visibility and state
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
if (customPositiveButton != null) {
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isAllCaps = true
|
// hide default buttons if custom ones are used
|
||||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).isAllCaps = true
|
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 */
|
/* Makes the "Add" button clickable */
|
||||||
override fun activateAddButton() {
|
override fun activateAddButton() {
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = true
|
||||||
|
customPositiveButton?.isEnabled = true
|
||||||
searchRequestProgressIndicator.isGone = true
|
searchRequestProgressIndicator.isGone = true
|
||||||
noSearchResultsTextView.isGone = true
|
noSearchResultsTextView.isGone = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deactivateAddButton() {
|
override fun deactivateAddButton() {
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||||
|
customPositiveButton?.isEnabled = false
|
||||||
searchRequestProgressIndicator.isGone = true
|
searchRequestProgressIndicator.isGone = true
|
||||||
noSearchResultsTextView.isGone = true
|
noSearchResultsTextView.isGone = true
|
||||||
}
|
}
|
||||||
@@ -256,6 +285,7 @@ class FindStationDialog (
|
|||||||
/* Resets the dialog layout to default state */
|
/* Resets the dialog layout to default state */
|
||||||
private fun resetLayout(clearAdapter: Boolean = false) {
|
private fun resetLayout(clearAdapter: Boolean = false) {
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||||
|
customPositiveButton?.isEnabled = false
|
||||||
searchRequestProgressIndicator.isGone = true
|
searchRequestProgressIndicator.isGone = true
|
||||||
noSearchResultsTextView.isGone = true
|
noSearchResultsTextView.isGone = true
|
||||||
searchResultAdapter.resetSelection(clearAdapter)
|
searchResultAdapter.resetSelection(clearAdapter)
|
||||||
@@ -265,6 +295,7 @@ class FindStationDialog (
|
|||||||
/* Display the "No Results" error - hide other unneeded views */
|
/* Display the "No Results" error - hide other unneeded views */
|
||||||
private fun showNoResultsError() {
|
private fun showNoResultsError() {
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||||
|
customPositiveButton?.isEnabled = false
|
||||||
searchRequestProgressIndicator.isGone = true
|
searchRequestProgressIndicator.isGone = true
|
||||||
noSearchResultsTextView.isVisible = true
|
noSearchResultsTextView.isVisible = true
|
||||||
}
|
}
|
||||||
@@ -273,6 +304,7 @@ class FindStationDialog (
|
|||||||
/* Display the "No Results" error - hide other unneeded views */
|
/* Display the "No Results" error - hide other unneeded views */
|
||||||
private fun showProgressIndicator() {
|
private fun showProgressIndicator() {
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false
|
||||||
|
customPositiveButton?.isEnabled = false
|
||||||
searchRequestProgressIndicator.isVisible = true
|
searchRequestProgressIndicator.isVisible = true
|
||||||
noSearchResultsTextView.isGone = true
|
noSearchResultsTextView.isGone = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ package com.michatec.radio.extensions
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.media3.session.MediaController
|
import androidx.media3.session.MediaController
|
||||||
import androidx.media3.session.SessionCommand
|
import androidx.media3.session.SessionCommand
|
||||||
import androidx.media3.session.SessionResult
|
import androidx.media3.session.SessionResult
|
||||||
@@ -71,8 +70,11 @@ fun MediaController.play(context: Context, station: Station) {
|
|||||||
|
|
||||||
/* Starts playback with of a stream url */
|
/* Starts playback with of a stream url */
|
||||||
fun MediaController.playStreamDirectly(streamUri: String) {
|
fun MediaController.playStreamDirectly(streamUri: String) {
|
||||||
|
val bundle = Bundle().apply {
|
||||||
|
putString(Keys.KEY_STREAM_URI, streamUri)
|
||||||
|
}
|
||||||
sendCustomCommand(
|
sendCustomCommand(
|
||||||
SessionCommand(Keys.CMD_PLAY_STREAM, Bundle.EMPTY),
|
SessionCommand(Keys.CMD_PLAY_STREAM, Bundle.EMPTY),
|
||||||
bundleOf(Pair(Keys.KEY_STREAM_URI, streamUri))
|
bundle
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,6 @@ import kotlinx.coroutines.Dispatchers.IO
|
|||||||
import java.io.*
|
import java.io.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -292,7 +291,7 @@ object FileHelper {
|
|||||||
collection: Collection,
|
collection: Collection,
|
||||||
lastUpdate: Date
|
lastUpdate: Date
|
||||||
) {
|
) {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCancellableCoroutine { cont ->
|
||||||
cont.resume(saveCollection(context, collection, lastUpdate))
|
cont.resume(saveCollection(context, collection, lastUpdate))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,7 +310,7 @@ object FileHelper {
|
|||||||
originalFileUri: Uri,
|
originalFileUri: Uri,
|
||||||
targetFileUri: Uri
|
targetFileUri: Uri
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCancellableCoroutine { cont ->
|
||||||
cont.resume(copyFile(context, originalFileUri, targetFileUri))
|
cont.resume(copyFile(context, originalFileUri, targetFileUri))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,7 +318,7 @@ object FileHelper {
|
|||||||
|
|
||||||
/* Suspend function: Exports collection of stations as M3U file - local backup copy */
|
/* Suspend function: Exports collection of stations as M3U file - local backup copy */
|
||||||
suspend fun backupCollectionAsM3uSuspended(context: Context, collection: Collection) {
|
suspend fun backupCollectionAsM3uSuspended(context: Context, collection: Collection) {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCancellableCoroutine { cont ->
|
||||||
Log.v(TAG, "Backing up collection as M3U - Thread: ${Thread.currentThread().name}")
|
Log.v(TAG, "Backing up collection as M3U - Thread: ${Thread.currentThread().name}")
|
||||||
// create M3U string
|
// create M3U string
|
||||||
val m3uString: String = CollectionHelper.createM3uString(collection)
|
val m3uString: String = CollectionHelper.createM3uString(collection)
|
||||||
@@ -338,7 +337,7 @@ object FileHelper {
|
|||||||
|
|
||||||
/* Suspend function: Exports collection of stations as PLS file - local backup copy */
|
/* Suspend function: Exports collection of stations as PLS file - local backup copy */
|
||||||
suspend fun backupCollectionAsPlsSuspended(context: Context, collection: Collection) {
|
suspend fun backupCollectionAsPlsSuspended(context: Context, collection: Collection) {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCancellableCoroutine { cont ->
|
||||||
Log.v(TAG, "Backing up collection as PLS - Thread: ${Thread.currentThread().name}")
|
Log.v(TAG, "Backing up collection as PLS - Thread: ${Thread.currentThread().name}")
|
||||||
// create PLS string
|
// create PLS string
|
||||||
val plsString: String = CollectionHelper.createPlsString(collection)
|
val plsString: String = CollectionHelper.createPlsString(collection)
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ import android.net.ConnectivityManager
|
|||||||
import android.net.Network
|
import android.net.Network
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.michatec.radio.Keys
|
import com.michatec.radio.Keys
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.UnknownHostException
|
import java.net.UnknownHostException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -105,7 +105,7 @@ object NetworkHelper {
|
|||||||
|
|
||||||
/* Suspend function: Detects content type (mime type) from given URL string - async using coroutine */
|
/* Suspend function: Detects content type (mime type) from given URL string - async using coroutine */
|
||||||
suspend fun detectContentTypeSuspended(urlString: String): ContentType {
|
suspend fun detectContentTypeSuspended(urlString: String): ContentType {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCancellableCoroutine { cont ->
|
||||||
cont.resume(detectContentType(urlString))
|
cont.resume(detectContentType(urlString))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ object NetworkHelper {
|
|||||||
|
|
||||||
/* Suspend function: Gets a random radio-browser.info api address - async using coroutine */
|
/* Suspend function: Gets a random radio-browser.info api address - async using coroutine */
|
||||||
suspend fun getRadioBrowserServerSuspended(): String {
|
suspend fun getRadioBrowserServerSuspended(): String {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCancellableCoroutine { cont ->
|
||||||
val serverAddress: String = try {
|
val serverAddress: String = try {
|
||||||
// get all available radio browser servers
|
// get all available radio browser servers
|
||||||
val serverAddressList: Array<InetAddress> =
|
val serverAddressList: Array<InetAddress> =
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package com.michatec.radio.helpers
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
@@ -223,13 +224,15 @@ object PreferencesHelper {
|
|||||||
|
|
||||||
|
|
||||||
/* Loads value of the option: Edit Stations */
|
/* Loads value of the option: Edit Stations */
|
||||||
fun loadEditStationsEnabled(): Boolean {
|
fun loadEditStationsEnabled(context: Context): Boolean {
|
||||||
return sharedPreferences.getBoolean(Keys.PREF_EDIT_STATIONS, true)
|
val defaultValue = !context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
||||||
|
return sharedPreferences.getBoolean(Keys.PREF_EDIT_STATIONS, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Loads value of the option: Edit Station Streams */
|
/* Loads value of the option: Edit Station Streams */
|
||||||
fun loadEditStreamUrisEnabled(): Boolean {
|
fun loadEditStreamUrisEnabled(context: Context): Boolean {
|
||||||
return sharedPreferences.getBoolean(Keys.PREF_EDIT_STREAMS_URIS, true)
|
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
|
package com.michatec.radio.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
@@ -19,6 +5,7 @@ import android.content.ClipData
|
|||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.AnimatedVectorDrawable
|
import android.graphics.drawable.AnimatedVectorDrawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -54,30 +41,32 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
var recyclerView: RecyclerView = rootView.findViewById(R.id.station_list)
|
var recyclerView: RecyclerView = rootView.findViewById(R.id.station_list)
|
||||||
val layoutManager: LinearLayoutManager
|
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 sheetMetadataViews: Group
|
||||||
private var sleepTimerRunningViews: Group = rootView.findViewById(R.id.sleep_timer_running_views)
|
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 downloadProgressIndicator: ProgressBar? = rootView.findViewById(R.id.download_progress_indicator)
|
||||||
private var stationImageView: ImageView = rootView.findViewById(R.id.station_icon)
|
private var stationImageView: ImageView? = rootView.findViewById(R.id.station_icon)
|
||||||
private var stationNameView: TextView = rootView.findViewById(R.id.player_station_name)
|
private var stationNameView: TextView? = rootView.findViewById(R.id.player_station_name)
|
||||||
private var metadataView: TextView = rootView.findViewById(R.id.player_station_metadata)
|
private var metadataView: TextView? = rootView.findViewById(R.id.player_station_metadata)
|
||||||
var playButtonView: ImageButton = rootView.findViewById(R.id.player_play_button)
|
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 bufferingIndicator: ProgressBar = rootView.findViewById(R.id.player_buffering_indicator)
|
||||||
private var sheetStreamingLinkHeadline: TextView = rootView.findViewById(R.id.sheet_streaming_link_headline)
|
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 sheetStreamingLinkView: TextView? = rootView.findViewById(R.id.sheet_streaming_link)
|
||||||
private var sheetMetadataHistoryHeadline: TextView = rootView.findViewById(R.id.sheet_metadata_headline)
|
private var sheetMetadataHistoryHeadline: TextView? = rootView.findViewById(R.id.sheet_metadata_headline)
|
||||||
private var sheetMetadataHistoryView: TextView = rootView.findViewById(R.id.sheet_metadata_history)
|
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 sheetNextMetadataView: ImageButton? = rootView.findViewById(R.id.sheet_next_metadata_button)
|
||||||
private var sheetPreviousMetadataView: ImageButton = rootView.findViewById(R.id.sheet_previous_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 sheetCopyMetadataButtonView: ImageButton? = rootView.findViewById(R.id.copy_station_metadata_button)
|
||||||
private var sheetShareLinkButtonView: ImageView = rootView.findViewById(R.id.sheet_share_link_button)
|
private var sheetShareLinkButtonView: ImageView? = rootView.findViewById(R.id.sheet_share_link_button)
|
||||||
private var sheetBitrateView: TextView = rootView.findViewById(R.id.sheet_bitrate_view)
|
private var sheetBitrateView: TextView? = rootView.findViewById(R.id.sheet_bitrate_view)
|
||||||
var sheetSleepTimerStartButtonView: ImageButton = rootView.findViewById(R.id.sleep_timer_start_button)
|
var sheetSleepTimerStartButtonView: ImageButton? = rootView.findViewById(R.id.sleep_timer_start_button)
|
||||||
var sheetSleepTimerCancelButtonView: ImageButton = rootView.findViewById(R.id.sleep_timer_cancel_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 sheetSleepTimerRemainingTimeView: TextView = rootView.findViewById(R.id.sleep_timer_remaining_time)
|
||||||
private var onboardingLayout: ConstraintLayout = rootView.findViewById(R.id.onboarding_layout)
|
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 metadataHistory: MutableList<String>
|
||||||
private var metadataHistoryPosition: Int
|
private var metadataHistoryPosition: Int
|
||||||
private var isBuffering: Boolean
|
private var isBuffering: Boolean
|
||||||
@@ -97,31 +86,31 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
recyclerView.itemAnimator = DefaultItemAnimator()
|
recyclerView.itemAnimator = DefaultItemAnimator()
|
||||||
|
|
||||||
// set up metadata history next and previous buttons
|
// set up metadata history next and previous buttons
|
||||||
sheetPreviousMetadataView.setOnClickListener {
|
sheetPreviousMetadataView?.setOnClickListener {
|
||||||
if (metadataHistory.isNotEmpty()) {
|
if (metadataHistory.isNotEmpty()) {
|
||||||
if (metadataHistoryPosition > 0) {
|
if (metadataHistoryPosition > 0) {
|
||||||
metadataHistoryPosition -= 1
|
metadataHistoryPosition -= 1
|
||||||
} else {
|
} else {
|
||||||
metadataHistoryPosition = metadataHistory.size - 1
|
metadataHistoryPosition = metadataHistory.size - 1
|
||||||
}
|
}
|
||||||
sheetMetadataHistoryView.text = metadataHistory[metadataHistoryPosition]
|
sheetMetadataHistoryView?.text = metadataHistory[metadataHistoryPosition]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sheetNextMetadataView.setOnClickListener {
|
sheetNextMetadataView?.setOnClickListener {
|
||||||
if (metadataHistory.isNotEmpty()) {
|
if (metadataHistory.isNotEmpty()) {
|
||||||
if (metadataHistoryPosition < metadataHistory.size - 1) {
|
if (metadataHistoryPosition < metadataHistory.size - 1) {
|
||||||
metadataHistoryPosition += 1
|
metadataHistoryPosition += 1
|
||||||
} else {
|
} else {
|
||||||
metadataHistoryPosition = 0
|
metadataHistoryPosition = 0
|
||||||
}
|
}
|
||||||
sheetMetadataHistoryView.text = metadataHistory[metadataHistoryPosition]
|
sheetMetadataHistoryView?.text = metadataHistory[metadataHistoryPosition]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sheetMetadataHistoryView.setOnLongClickListener {
|
sheetMetadataHistoryView?.setOnLongClickListener {
|
||||||
copyMetadataHistoryToClipboard()
|
copyMetadataHistoryToClipboard()
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
sheetMetadataHistoryHeadline.setOnLongClickListener {
|
sheetMetadataHistoryHeadline?.setOnLongClickListener {
|
||||||
copyMetadataHistoryToClipboard()
|
copyMetadataHistoryToClipboard()
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
@@ -137,29 +126,29 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
|
|
||||||
// set default metadata views, when playback has stopped
|
// set default metadata views, when playback has stopped
|
||||||
if (!isPlaying) {
|
if (!isPlaying) {
|
||||||
metadataView.text = station.name
|
metadataView?.text = station.name
|
||||||
sheetMetadataHistoryView.text = station.name
|
sheetMetadataHistoryView?.text = station.name
|
||||||
// sheetMetadataHistoryView.isSelected = true
|
// sheetMetadataHistoryView.isSelected = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// update name
|
// update name
|
||||||
stationNameView.text = station.name
|
stationNameView?.text = station.name
|
||||||
|
|
||||||
// toggle text scrolling (marquee) if necessary
|
// toggle text scrolling (marquee) if necessary
|
||||||
stationNameView.isSelected = isPlaying
|
stationNameView?.isSelected = isPlaying
|
||||||
|
|
||||||
// reduce the shadow left and right because of scrolling (Marquee)
|
// reduce the shadow left and right because of scrolling (Marquee)
|
||||||
stationNameView.setFadingEdgeLength(8)
|
stationNameView?.setFadingEdgeLength(8)
|
||||||
|
|
||||||
// update cover
|
// update cover
|
||||||
if (station.imageColor != -1) {
|
if (station.imageColor != -1) {
|
||||||
stationImageView.setBackgroundColor(station.imageColor)
|
stationImageView?.setBackgroundColor(station.imageColor)
|
||||||
}
|
}
|
||||||
stationImageView.setImageBitmap(ImageHelper.getStationImage(context, station.smallImage))
|
stationImageView?.setImageBitmap(ImageHelper.getStationImage(context, station.smallImage))
|
||||||
stationImageView.contentDescription = "${context.getString(R.string.descr_player_station_image)}: ${station.name}"
|
stationImageView?.contentDescription = "${context.getString(R.string.descr_player_station_image)}: ${station.name}"
|
||||||
|
|
||||||
// update streaming link
|
// update streaming link
|
||||||
sheetStreamingLinkView.text = station.getStreamUri()
|
sheetStreamingLinkView?.text = station.getStreamUri()
|
||||||
|
|
||||||
val bitrateText: CharSequence = if (station.codec.isNotEmpty()) {
|
val bitrateText: CharSequence = if (station.codec.isNotEmpty()) {
|
||||||
if (station.bitrate == 0) {
|
if (station.bitrate == 0) {
|
||||||
@@ -188,50 +177,50 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update bitrate
|
// update bitrate
|
||||||
sheetBitrateView.text = bitrateText
|
sheetBitrateView?.text = bitrateText
|
||||||
|
|
||||||
// update click listeners
|
// update click listeners
|
||||||
sheetStreamingLinkHeadline.setOnClickListener {
|
sheetStreamingLinkHeadline?.setOnClickListener {
|
||||||
copyToClipboard(
|
copyToClipboard(
|
||||||
context,
|
context,
|
||||||
sheetStreamingLinkView.text
|
sheetStreamingLinkView?.text ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sheetStreamingLinkView.setOnClickListener {
|
sheetStreamingLinkView?.setOnClickListener {
|
||||||
copyToClipboard(
|
copyToClipboard(
|
||||||
context,
|
context,
|
||||||
sheetStreamingLinkView.text
|
sheetStreamingLinkView?.text ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sheetMetadataHistoryHeadline.setOnClickListener {
|
sheetMetadataHistoryHeadline?.setOnClickListener {
|
||||||
copyToClipboard(
|
copyToClipboard(
|
||||||
context,
|
context,
|
||||||
sheetMetadataHistoryView.text
|
sheetMetadataHistoryView?.text ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sheetMetadataHistoryView.setOnClickListener {
|
sheetMetadataHistoryView?.setOnClickListener {
|
||||||
copyToClipboard(
|
copyToClipboard(
|
||||||
context,
|
context,
|
||||||
sheetMetadataHistoryView.text
|
sheetMetadataHistoryView?.text ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sheetCopyMetadataButtonView.setOnClickListener {
|
sheetCopyMetadataButtonView?.setOnClickListener {
|
||||||
copyToClipboard(
|
copyToClipboard(
|
||||||
context,
|
context,
|
||||||
sheetMetadataHistoryView.text
|
sheetMetadataHistoryView?.text ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sheetBitrateView.setOnClickListener {
|
sheetBitrateView?.setOnClickListener {
|
||||||
copyToClipboard(
|
copyToClipboard(
|
||||||
context,
|
context,
|
||||||
sheetBitrateView.text
|
sheetBitrateView?.text ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sheetShareLinkButtonView.setOnClickListener {
|
sheetShareLinkButtonView?.setOnClickListener {
|
||||||
val share = Intent.createChooser(Intent().apply {
|
val share = Intent.createChooser(Intent().apply {
|
||||||
action = Intent.ACTION_SEND
|
action = Intent.ACTION_SEND
|
||||||
putExtra(Intent.EXTRA_TITLE, stationNameView.text)
|
putExtra(Intent.EXTRA_TITLE, stationNameView?.text)
|
||||||
putExtra(Intent.EXTRA_TEXT, sheetStreamingLinkView.text)
|
putExtra(Intent.EXTRA_TEXT, sheetStreamingLinkView?.text ?: "")
|
||||||
type = "text/plain"
|
type = "text/plain"
|
||||||
}, null)
|
}, null)
|
||||||
context.startActivity(share)
|
context.startActivity(share)
|
||||||
@@ -264,11 +253,11 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
fun updateMetadata(metadataHistoryList: MutableList<String>?) {
|
fun updateMetadata(metadataHistoryList: MutableList<String>?) {
|
||||||
if (!metadataHistoryList.isNullOrEmpty()) {
|
if (!metadataHistoryList.isNullOrEmpty()) {
|
||||||
metadataHistory = metadataHistoryList
|
metadataHistory = metadataHistoryList
|
||||||
if (metadataHistory.last() != metadataView.text) {
|
if (metadataHistory.last() != metadataView?.text) {
|
||||||
metadataHistoryPosition = metadataHistory.size - 1
|
metadataHistoryPosition = metadataHistory.size - 1
|
||||||
val metadataString = metadataHistory[metadataHistoryPosition]
|
val metadataString = metadataHistory[metadataHistoryPosition]
|
||||||
metadataView.text = metadataString
|
metadataView?.text = metadataString
|
||||||
sheetMetadataHistoryView.text = metadataString
|
sheetMetadataHistoryView?.text = metadataString
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,14 +267,16 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
fun updateSleepTimer(context: Context, timeRemaining: Long = 0L) {
|
fun updateSleepTimer(context: Context, timeRemaining: Long = 0L) {
|
||||||
when (timeRemaining) {
|
when (timeRemaining) {
|
||||||
0L -> {
|
0L -> {
|
||||||
sleepTimerRunningViews.isGone = true
|
sleepTimerRunningViews?.isGone = true
|
||||||
|
sheetSleepTimerRemainingTimeView.isVisible = false
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
sleepTimerRunningViews.isVisible = true
|
sleepTimerRunningViews?.isVisible = true
|
||||||
|
sheetSleepTimerRemainingTimeView.isVisible = true
|
||||||
val sleepTimerTimeRemaining = DateTimeHelper.convertToHoursMinutesSeconds(timeRemaining)
|
val sleepTimerTimeRemaining = DateTimeHelper.convertToHoursMinutesSeconds(timeRemaining)
|
||||||
sheetSleepTimerRemainingTimeView.text = sleepTimerTimeRemaining
|
sheetSleepTimerRemainingTimeView.text = sleepTimerTimeRemaining
|
||||||
sheetSleepTimerRemainingTimeView.contentDescription = "${context.getString(R.string.descr_expanded_player_sleep_timer_remaining_time)}: $sleepTimerTimeRemaining"
|
sheetSleepTimerRemainingTimeView.contentDescription = "${context.getString(R.string.descr_expanded_player_sleep_timer_remaining_time)}: $sleepTimerTimeRemaining"
|
||||||
stationNameView.isSelected = false
|
stationNameView?.isSelected = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,11 +288,11 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
playButtonView.setImageResource(R.drawable.ic_audio_waves_animated)
|
playButtonView.setImageResource(R.drawable.ic_audio_waves_animated)
|
||||||
val animatedVectorDrawable = playButtonView.drawable as? AnimatedVectorDrawable
|
val animatedVectorDrawable = playButtonView.drawable as? AnimatedVectorDrawable
|
||||||
animatedVectorDrawable?.start()
|
animatedVectorDrawable?.start()
|
||||||
sheetSleepTimerStartButtonView.isVisible = true
|
sheetSleepTimerStartButtonView?.isVisible = true
|
||||||
// bufferingIndicator.isVisible = false
|
// bufferingIndicator.isVisible = false
|
||||||
} else {
|
} else {
|
||||||
playButtonView.setImageResource(R.drawable.ic_player_play_symbol_42dp)
|
playButtonView.setImageResource(R.drawable.ic_player_play_symbol_42dp)
|
||||||
sheetSleepTimerStartButtonView.isVisible = false
|
sheetSleepTimerStartButtonView?.isVisible = false
|
||||||
// bufferingIndicator.isVisible = isBuffering
|
// bufferingIndicator.isVisible = isBuffering
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,8 +307,8 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
/* Toggles visibility of the download progress indicator */
|
/* Toggles visibility of the download progress indicator */
|
||||||
fun toggleDownloadProgressIndicator() {
|
fun toggleDownloadProgressIndicator() {
|
||||||
when (PreferencesHelper.loadActiveDownloads()) {
|
when (PreferencesHelper.loadActiveDownloads()) {
|
||||||
Keys.ACTIVE_DOWNLOADS_EMPTY -> downloadProgressIndicator.isGone = true
|
Keys.ACTIVE_DOWNLOADS_EMPTY -> downloadProgressIndicator?.isGone = true
|
||||||
else -> downloadProgressIndicator.isVisible = true
|
else -> downloadProgressIndicator?.isVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,27 +329,27 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
|
|
||||||
/* Initiates the rotation animation of the play button */
|
/* Initiates the rotation animation of the play button */
|
||||||
fun animatePlaybackButtonStateTransition(context: Context, isPlaying: Boolean) {
|
fun animatePlaybackButtonStateTransition(context: Context, isPlaying: Boolean) {
|
||||||
when (isPlaying) {
|
if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||||
true -> {
|
// TV: Toggle play button immediately for snappier feel
|
||||||
val rotateClockwise = AnimationUtils.loadAnimation(context, R.anim.rotate_clockwise_slow)
|
togglePlayButton(isPlaying)
|
||||||
rotateClockwise.setAnimationListener(createAnimationListener(true))
|
} else {
|
||||||
playButtonView.startAnimation(rotateClockwise)
|
// Handy/Tablet: Rotate the play button
|
||||||
}
|
val rotateAnimation = AnimationUtils.loadAnimation(context, if (isPlaying) R.anim.rotate_clockwise_slow else R.anim.rotate_counterclockwise_fast)
|
||||||
false -> {
|
rotateAnimation.setAnimationListener(createAnimationListener(isPlaying))
|
||||||
val rotateCounterClockwise = AnimationUtils.loadAnimation(context, R.anim.rotate_counterclockwise_fast)
|
playButtonView.startAnimation(rotateAnimation)
|
||||||
rotateCounterClockwise.setAnimationListener(createAnimationListener(false))
|
|
||||||
playButtonView.startAnimation(rotateCounterClockwise)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Shows player */
|
/* Shows player */
|
||||||
fun showPlayer(context: Context): Boolean {
|
fun showPlayer(context: Context): Boolean {
|
||||||
UiHelper.setViewMargins(context, recyclerView, 0, 0, 0, Keys.BOTTOM_SHEET_PEEK_HEIGHT)
|
if (bottomSheetBehavior != null) {
|
||||||
if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_HIDDEN && onboardingLayout.isGone) {
|
UiHelper.setViewMargins(context, recyclerView, 0, 0, 0, Keys.BOTTOM_SHEET_PEEK_HEIGHT)
|
||||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
if (bottomSheetBehavior?.state == BottomSheetBehavior.STATE_HIDDEN && onboardingLayout.isGone) {
|
||||||
|
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UiHelper.setViewMargins(context, recyclerView, 0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -367,15 +358,15 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
/* Hides player */
|
/* Hides player */
|
||||||
private fun hidePlayer(context: Context): Boolean {
|
private fun hidePlayer(context: Context): Boolean {
|
||||||
UiHelper.setViewMargins(context, recyclerView, 0, 0, 0, 0)
|
UiHelper.setViewMargins(context, recyclerView, 0, 0, 0, 0)
|
||||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_HIDDEN
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Minimizes player sheet if expanded */
|
/* Minimizes player sheet if expanded */
|
||||||
fun minimizePlayerIfExpanded(): Boolean {
|
fun minimizePlayerIfExpanded(): Boolean {
|
||||||
return if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
|
return if (bottomSheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) {
|
||||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -388,7 +379,7 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
return object : Animation.AnimationListener {
|
return object : Animation.AnimationListener {
|
||||||
override fun onAnimationStart(animation: Animation) {}
|
override fun onAnimationStart(animation: Animation) {}
|
||||||
override fun onAnimationEnd(animation: Animation) {
|
override fun onAnimationEnd(animation: Animation) {
|
||||||
// set up button symbol and playback indicator afterwards
|
// set up button symbol and playback indicator afterward
|
||||||
togglePlayButton(isPlaying)
|
togglePlayButton(isPlaying)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,38 +390,40 @@ data class LayoutHolder(var rootView: View) {
|
|||||||
|
|
||||||
/* Sets up the player (BottomSheet) */
|
/* Sets up the player (BottomSheet) */
|
||||||
private fun setupBottomSheet() {
|
private fun setupBottomSheet() {
|
||||||
// show / hide the small player
|
if (bottomSheetBehavior != null) {
|
||||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
// show / hide the small player
|
||||||
bottomSheetBehavior.addBottomSheetCallback(object :
|
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
BottomSheetBehavior.BottomSheetCallback() {
|
bottomSheetBehavior?.addBottomSheetCallback(object :
|
||||||
override fun onSlide(view: View, slideOffset: Float) {
|
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)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
override fun onStateChanged(view: View, state: Int) {
|
||||||
// toggle collapsed state on tap
|
when (state) {
|
||||||
bottomSheet.setOnClickListener { toggleBottomSheetState() }
|
BottomSheetBehavior.STATE_COLLAPSED -> Unit // do nothing
|
||||||
stationImageView.setOnClickListener { toggleBottomSheetState() }
|
BottomSheetBehavior.STATE_DRAGGING -> Unit // do nothing
|
||||||
stationNameView.setOnClickListener { toggleBottomSheetState() }
|
BottomSheetBehavior.STATE_EXPANDED -> Unit // do nothing
|
||||||
metadataView.setOnClickListener { toggleBottomSheetState() }
|
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 */
|
/* Toggle expanded/collapsed state of bottom sheet */
|
||||||
private fun toggleBottomSheetState() {
|
private fun toggleBottomSheetState() {
|
||||||
when (bottomSheetBehavior.state) {
|
when (bottomSheetBehavior?.state) {
|
||||||
BottomSheetBehavior.STATE_COLLAPSED -> bottomSheetBehavior.state =
|
BottomSheetBehavior.STATE_COLLAPSED -> bottomSheetBehavior?.state =
|
||||||
BottomSheetBehavior.STATE_EXPANDED
|
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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
<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_selected="true" />
|
||||||
|
<item android:drawable="@drawable/shape_search_result_item_selected" android:state_focused="true" />
|
||||||
|
|
||||||
<!-- Default state. -->
|
<!-- Default state. -->
|
||||||
<item android:drawable="@drawable/shape_search_result_item" />
|
<item android:drawable="@drawable/shape_search_result_item" />
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:shape="oval">
|
android:shape="oval">
|
||||||
<stroke
|
<stroke
|
||||||
android:width="2dp"
|
android:width="4dp"
|
||||||
android:color="@color/default_neutral_lighter" />
|
android:color="@color/default_neutral_white" />
|
||||||
<size
|
<size
|
||||||
android:width="56dp"
|
android:width="56dp"
|
||||||
android:height="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:layout_constraintTop_toBottomOf="@+id/main_toolbar"
|
||||||
app:navGraph="@navigation/nav_graph_main" />
|
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -12,6 +12,8 @@
|
|||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_marginBottom="24dp"
|
android:layout_marginBottom="24dp"
|
||||||
|
android:focusable="true"
|
||||||
|
android:clickable="true"
|
||||||
android:stateListAnimator="@null"
|
android:stateListAnimator="@null"
|
||||||
app:backgroundTint="@color/list_card_background"
|
app:backgroundTint="@color/list_card_background"
|
||||||
app:icon="@drawable/ic_add_24dp"
|
app:icon="@drawable/ic_add_24dp"
|
||||||
@@ -28,6 +30,8 @@
|
|||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_marginBottom="24dp"
|
android:layout_marginBottom="24dp"
|
||||||
|
android:focusable="true"
|
||||||
|
android:clickable="true"
|
||||||
android:stateListAnimator="@null"
|
android:stateListAnimator="@null"
|
||||||
app:backgroundTint="@color/list_card_background"
|
app:backgroundTint="@color/list_card_background"
|
||||||
app:icon="@drawable/ic_settings_24dp"
|
app:icon="@drawable/ic_settings_24dp"
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
<item name="colorOnSurfaceVariant">@android:color/system_accent1_100</item>
|
<item name="colorOnSurfaceVariant">@android:color/system_accent1_100</item>
|
||||||
<item name="android:colorBackground">@android:color/system_neutral1_900</item>
|
<item name="android:colorBackground">@android:color/system_neutral1_900</item>
|
||||||
<item name="android:textColorHighlight">@android:color/system_accent1_500</item>
|
<item name="android:textColorHighlight">@android:color/system_accent1_500</item>
|
||||||
|
<item name="colorControlHighlight">#33FFFFFF</item>
|
||||||
|
<item name="android:colorControlHighlight">#33FFFFFF</item>
|
||||||
|
<item name="android:colorFocusedHighlight">#80FFFFFF</item>
|
||||||
|
|
||||||
<!-- Do not use primary colored elevation overlays to present a visual hierarchy - TOO COLORFUL -->
|
<!-- Do not use primary colored elevation overlays to present a visual hierarchy - TOO COLORFUL -->
|
||||||
<item name="elevationOverlayEnabled">false</item>
|
<item name="elevationOverlayEnabled">false</item>
|
||||||
@@ -39,6 +42,7 @@
|
|||||||
<item name="colorSurface">@color/player_sheet_background</item>
|
<item name="colorSurface">@color/player_sheet_background</item>
|
||||||
<item name="materialAlertDialogBodyTextStyle">@style/TextAppearance.MaterialComponents.Body1</item>
|
<item name="materialAlertDialogBodyTextStyle">@style/TextAppearance.MaterialComponents.Body1</item>
|
||||||
<item name="android:backgroundDimAmount">0.64</item>
|
<item name="android:backgroundDimAmount">0.64</item>
|
||||||
|
<item name="colorControlActivated">#FFDAE2FF</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ThemeOverlay.App.TimePicker" parent="ThemeOverlay.MaterialComponents.TimePicker">
|
<style name="ThemeOverlay.App.TimePicker" parent="ThemeOverlay.MaterialComponents.TimePicker">
|
||||||
@@ -49,5 +53,12 @@
|
|||||||
<item name="android:background">@color/player_sheet_background</item>
|
<item name="android:background">@color/player_sheet_background</item>
|
||||||
<item name="dialogCornerRadius">28dp</item>
|
<item name="dialogCornerRadius">28dp</item>
|
||||||
<item name="checkedTextViewStyle">@style/AlertDialog.TextColor</item>
|
<item name="checkedTextViewStyle">@style/AlertDialog.TextColor</item>
|
||||||
|
<item name="colorControlActivated">#FFDAE2FF</item>
|
||||||
|
<item name="colorControlHighlight">#33FFFFFF</item>
|
||||||
|
<item name="android:colorFocusedHighlight">#80FFFFFF</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AlertDialog.TextColor" parent="@style/TextAppearance.MaterialComponents.Body1">
|
||||||
|
<item name="android:textColor">?attr/colorControlNormal</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
<!-- NIGHT THEME COLORS -->
|
<!-- NIGHT THEME COLORS -->
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
<item name="colorAccent">#FFDAE2FF</item>
|
<item name="colorAccent">#FFDAE2FF</item>
|
||||||
<item name="colorOnPrimary">#FF182E60</item>
|
<item name="colorOnPrimary">#FF182E60</item>
|
||||||
<item name="android:textColorHighlight">#FF495D92</item>
|
<item name="android:textColorHighlight">#FF495D92</item>
|
||||||
|
<item name="colorControlHighlight">#33FFFFFF</item>
|
||||||
|
<item name="android:colorControlHighlight">#33FFFFFF</item>
|
||||||
|
<item name="android:colorFocusedHighlight">#80FFFFFF</item>
|
||||||
|
|
||||||
<!-- Do not use primary colored elevation overlays to present a visual hierarchy - TOO COLORFUL -->
|
<!-- Do not use primary colored elevation overlays to present a visual hierarchy - TOO COLORFUL -->
|
||||||
<item name="elevationOverlayEnabled">false</item>
|
<item name="elevationOverlayEnabled">false</item>
|
||||||
|
|||||||
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>
|
||||||
@@ -6,6 +6,9 @@
|
|||||||
<item name="colorPrimary">#FF495D92</item>
|
<item name="colorPrimary">#FF495D92</item>
|
||||||
<item name="colorAccent">#FF495D92</item>
|
<item name="colorAccent">#FF495D92</item>
|
||||||
<item name="android:textColorHighlight">#FF495D92</item>
|
<item name="android:textColorHighlight">#FF495D92</item>
|
||||||
|
<item name="colorControlHighlight">#33000000</item>
|
||||||
|
<item name="android:colorControlHighlight">#33000000</item>
|
||||||
|
<item name="android:colorFocusedHighlight">#80000000</item>
|
||||||
|
|
||||||
<!-- Do not use primary colored elevation overlays to present a visual hierarchy - TOO COLORFUL -->
|
<!-- Do not use primary colored elevation overlays to present a visual hierarchy - TOO COLORFUL -->
|
||||||
<item name="elevationOverlayEnabled">false</item>
|
<item name="elevationOverlayEnabled">false</item>
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
<item name="colorOnSurfaceVariant">@android:color/system_accent1_600</item>
|
<item name="colorOnSurfaceVariant">@android:color/system_accent1_600</item>
|
||||||
<item name="android:colorBackground">@android:color/system_neutral2_10</item>
|
<item name="android:colorBackground">@android:color/system_neutral2_10</item>
|
||||||
<item name="android:textColorHighlight">@android:color/system_accent1_200</item>
|
<item name="android:textColorHighlight">@android:color/system_accent1_200</item>
|
||||||
|
<item name="colorControlHighlight">#22000000</item>
|
||||||
|
<item name="android:colorControlHighlight">#22000000</item>
|
||||||
|
<item name="android:colorFocusedHighlight">#66000000</item>
|
||||||
|
|
||||||
<!-- Do not use primary colored elevation overlays to present a visual hierarchy - TOO COLORFUL -->
|
<!-- Do not use primary colored elevation overlays to present a visual hierarchy - TOO COLORFUL -->
|
||||||
<item name="elevationOverlayEnabled">false</item>
|
<item name="elevationOverlayEnabled">false</item>
|
||||||
@@ -38,6 +41,8 @@
|
|||||||
<item name="colorSurface">@color/list_card_background</item>
|
<item name="colorSurface">@color/list_card_background</item>
|
||||||
<item name="materialAlertDialogBodyTextStyle">@style/TextAppearance.MaterialComponents.Body1</item>
|
<item name="materialAlertDialogBodyTextStyle">@style/TextAppearance.MaterialComponents.Body1</item>
|
||||||
<item name="android:backgroundDimAmount">0.64</item>
|
<item name="android:backgroundDimAmount">0.64</item>
|
||||||
|
<item name="colorControlHighlight">#22000000</item>
|
||||||
|
<item name="android:colorFocusedHighlight">#66000000</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ThemeOverlay.App.TimePicker" parent="ThemeOverlay.MaterialComponents.TimePicker">
|
<style name="ThemeOverlay.App.TimePicker" parent="ThemeOverlay.MaterialComponents.TimePicker">
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<resources>
|
<resources>
|
||||||
<!-- DAY THEME COLORS -->
|
<!-- DAY THEME COLORS -->
|
||||||
|
|
||||||
@@ -16,6 +15,7 @@
|
|||||||
<!-- list -->
|
<!-- list -->
|
||||||
<color name="list_card_background">#FFFEFBFF</color>
|
<color name="list_card_background">#FFFEFBFF</color>
|
||||||
<color name="list_card_stroke_background">#FFC0C6DD</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_cover_background">#FFE7E0EC</color>
|
||||||
<color name="list_card_delete_background">#FFB3261E</color>
|
<color name="list_card_delete_background">#FFB3261E</color>
|
||||||
<color name="list_card_delete_icon">#FFFFFFFF</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_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="snackbar_github_update_check_url" translatable="false">https://api.github.com/repos/michatec/Radio/releases/latest</string>
|
||||||
<string name="app_name" translatable="false">Radio</string>
|
<string name="app_name" translatable="false">Radio</string>
|
||||||
|
<string name="icon_launcher" translatable="false">Icon launcher.</string>
|
||||||
|
<string name="loading">Loading...</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2,29 +2,24 @@
|
|||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
<!-- Set AppCompat’s colors -->
|
|
||||||
<item name="colorPrimary">#FF495D92</item>
|
<item name="colorPrimary">#FF495D92</item>
|
||||||
<item name="colorAccent">#FF495D92</item>
|
<item name="colorAccent">#FF495D92</item>
|
||||||
<item name="android:textColorHighlight">#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>
|
<item name="elevationOverlayEnabled">false</item>
|
||||||
|
<item name="colorControlActivated">#FFDAE2FF</item>
|
||||||
<!-- Switch Theming -->
|
|
||||||
<item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat.Material3</item>
|
<item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat.Material3</item>
|
||||||
|
|
||||||
<!-- Material Alert Dialog Theming -->
|
|
||||||
<item name="materialAlertDialogTheme">@style/ThemeOverlay.App.MaterialAlertDialog</item>
|
<item name="materialAlertDialogTheme">@style/ThemeOverlay.App.MaterialAlertDialog</item>
|
||||||
<item name="alertDialogTheme">@style/ThemeOverlay.App.AlertDialogTheme</item>
|
<item name="alertDialogTheme">@style/ThemeOverlay.App.AlertDialogTheme</item>
|
||||||
|
|
||||||
<!-- Material Time Picker Theming -->
|
|
||||||
<item name="materialTimePickerTheme">@style/ThemeOverlay.App.TimePicker</item>
|
<item name="materialTimePickerTheme">@style/ThemeOverlay.App.TimePicker</item>
|
||||||
|
|
||||||
<!-- Use "light" Status Bar -->
|
|
||||||
<item name="android:windowLightStatusBar">true</item>
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
|
<item name="colorControlHighlight">#80000000</item>
|
||||||
|
<item name="android:colorControlHighlight">#80000000</item>
|
||||||
|
<item name="android:colorFocusedHighlight">#FF333333</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="SplashTheme" parent="AppTheme" />
|
||||||
|
|
||||||
<style name="Preference.SwitchPreferenceCompat.Material3" parent="@style/Preference.SwitchPreferenceCompat.Material">
|
<style name="Preference.SwitchPreferenceCompat.Material3" parent="@style/Preference.SwitchPreferenceCompat.Material">
|
||||||
<item name="widgetLayout">@layout/preference_switch</item>
|
<item name="widgetLayout">@layout/preference_switch</item>
|
||||||
</style>
|
</style>
|
||||||
@@ -38,7 +33,13 @@
|
|||||||
<style name="ThemeOverlay.App.AlertDialogTheme" parent="@style/ThemeOverlay.Material3.MaterialAlertDialog">
|
<style name="ThemeOverlay.App.AlertDialogTheme" parent="@style/ThemeOverlay.Material3.MaterialAlertDialog">
|
||||||
<item name="android:background">@color/list_card_background</item>
|
<item name="android:background">@color/list_card_background</item>
|
||||||
<item name="dialogCornerRadius">28dp</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>
|
||||||
|
<item name="colorControlHighlight">#80000000</item>
|
||||||
|
<item name="android:colorFocusedHighlight">#FF333333</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ThemeOverlay.App.TimePicker" parent="ThemeOverlay.MaterialComponents.TimePicker">
|
<style name="ThemeOverlay.App.TimePicker" parent="ThemeOverlay.MaterialComponents.TimePicker">
|
||||||
@@ -46,13 +47,16 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AlertDialog.TextColor" parent="@style/TextAppearance.MaterialComponents.Body1">
|
<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>
|
||||||
|
|
||||||
<style name="App.Widget.Material3.CardView.Outlined" parent="@style/Widget.Material3.CardView.Outlined">
|
<style name="App.Widget.Material3.CardView.Outlined" parent="@style/Widget.Material3.CardView.Outlined">
|
||||||
<item name="strokeColor">@color/list_card_stroke_background</item>
|
<item name="strokeColor">@color/selector_card_station_stroke</item>
|
||||||
<item name="strokeWidth">3dp</item>
|
<item name="strokeWidth">4dp</item>
|
||||||
<item name="cardCornerRadius">24dp</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>
|
||||||
|
|
||||||
<style name="App.Widget.MaterialComponents.TextView" parent="@style/Widget.MaterialComponents.TextView">
|
<style name="App.Widget.MaterialComponents.TextView" parent="@style/Widget.MaterialComponents.TextView">
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '9.1.0' apply false
|
alias libs.plugins.android.application apply false
|
||||||
id 'com.android.library' version '9.1.0' apply false
|
alias libs.plugins.android.library apply false
|
||||||
id 'org.jetbrains.kotlin.android' version "2.3.10" apply false
|
alias libs.plugins.jetbrains.kotlin.android apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('clean', Delete) {
|
tasks.register('clean', Delete) {
|
||||||
|
|||||||
42
gradle/libs.versions.toml
Normal file
42
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
[versions]
|
||||||
|
activityKtx = "1.13.0"
|
||||||
|
agp = "9.1.0"
|
||||||
|
coreKtx = "1.18.0"
|
||||||
|
freedroidwarn = "V1.10"
|
||||||
|
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.10.0"
|
||||||
|
navigation = "2.9.7"
|
||||||
|
paletteKtx = "1.0.0"
|
||||||
|
preferenceKtx = "1.2.1"
|
||||||
|
volley = "1.2.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" }
|
||||||
|
media3-datasource-okhttp = { group = "androidx.media3", name = "media3-datasource-okhttp", version.ref = "media3" }
|
||||||
|
media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
|
||||||
|
media3-exoplayer-hls = { group = "androidx.media3", name = "media3-exoplayer-hls", version.ref = "media3" }
|
||||||
|
media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
|
||||||
|
navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation" }
|
||||||
|
navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation" }
|
||||||
|
palette-ktx = { group = "androidx.palette", name = "palette-ktx", version.ref = "paletteKtx" }
|
||||||
|
preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preferenceKtx" }
|
||||||
|
volley = { group = "com.android.volley", name = "volley", version.ref = "volley" }
|
||||||
|
work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||||
|
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ pluginManagement {
|
|||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
|
||||||
}
|
}
|
||||||
@@ -13,6 +15,7 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user