mirror of
https://github.com/apognu/otter
synced 2025-02-17 11:20:34 +01:00
Better separation between FOSS and full version. Chromecast playback, basic queue management and playback controls are functional.
This commit is contained in:
parent
3654e28c0c
commit
9ed7eab761
@ -2,10 +2,18 @@ package com.github.apognu.otter
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Menu
|
||||
import com.github.apognu.otter.utils.log
|
||||
import com.github.apognu.otter.playback.PlayerService
|
||||
import com.github.apognu.otter.utils.CastInterface
|
||||
|
||||
object Cast {
|
||||
fun init(context: Context) {}
|
||||
fun setupButton(context: Context, menu: Menu?) {}
|
||||
class Cast(val context: Context, val switchListener: PlayerService.OnPlayerSwitchListener, playerEventListener: PlayerService.PlayerEventListener) : CastInterface {
|
||||
companion object {
|
||||
fun init(context: Context) {}
|
||||
fun setupButton(context: Context, menu: Menu?) {}
|
||||
|
||||
fun get(
|
||||
context: Context,
|
||||
playerSwitchListener: PlayerService.OnPlayerSwitchListener,
|
||||
playerEventListener: PlayerService.PlayerEventListener
|
||||
): Cast? = null
|
||||
}
|
||||
}
|
||||
|
||||
|
24
app/src/foss/res/menu-land/toolbar.xml
Normal file
24
app/src/foss/res/menu-land/toolbar.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_search"
|
||||
android:icon="@drawable/search"
|
||||
android:title="@string/toolbar_search"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_only_my_music"
|
||||
android:checkable="true"
|
||||
android:title="@string/only_my_music"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/settings"
|
||||
android:icon="@drawable/settings"
|
||||
android:iconTint="@android:color/white"
|
||||
android:title="@string/title_settings"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
35
app/src/foss/res/menu/toolbar.xml
Normal file
35
app/src/foss/res/menu/toolbar.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_queue"
|
||||
android:icon="@drawable/queue"
|
||||
android:title="@string/playback_queue"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_search"
|
||||
android:icon="@drawable/search"
|
||||
android:title="@string/toolbar_search"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_only_my_music"
|
||||
android:checkable="true"
|
||||
android:title="@string/only_my_music"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_downloads"
|
||||
android:icon="@drawable/downloads"
|
||||
android:title="@string/title_downloads"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/settings"
|
||||
android:icon="@drawable/settings"
|
||||
android:title="@string/title_settings"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
@ -1,17 +1,142 @@
|
||||
package com.github.apognu.otter
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.view.Menu
|
||||
import com.github.apognu.otter.playback.PlayerService
|
||||
import com.github.apognu.otter.utils.AppContext
|
||||
import com.github.apognu.otter.utils.CastInterface
|
||||
import com.github.apognu.otter.utils.Track
|
||||
import com.github.apognu.otter.utils.mustNormalizeUrl
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.Timeline
|
||||
import com.google.android.exoplayer2.ext.cast.CastPlayer
|
||||
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener
|
||||
import com.google.android.gms.cast.MediaInfo
|
||||
import com.google.android.gms.cast.MediaMetadata
|
||||
import com.google.android.gms.cast.MediaQueueItem
|
||||
import com.google.android.gms.cast.framework.CastButtonFactory
|
||||
import com.google.android.gms.cast.framework.CastContext
|
||||
import com.google.android.gms.common.images.WebImage
|
||||
import com.preference.PowerPreference
|
||||
|
||||
object Cast {
|
||||
fun init(context: Context) {
|
||||
CastContext.getSharedInstance(context)
|
||||
fun Player.onCast(): CastPlayer? {
|
||||
return if (this is CastPlayer) this
|
||||
else null
|
||||
}
|
||||
|
||||
class Cast(val context: Context, val switchListener: PlayerService.OnPlayerSwitchListener, playerEventListener: PlayerService.PlayerEventListener) : CastInterface {
|
||||
companion object {
|
||||
fun init(context: Context) {
|
||||
CastContext.getSharedInstance(context)
|
||||
}
|
||||
|
||||
fun setupButton(context: Context, menu: Menu?) {
|
||||
CastButtonFactory.setUpMediaRouteButton(context, menu, R.id.cast)
|
||||
}
|
||||
|
||||
fun get(
|
||||
context: Context,
|
||||
playerSwitchListener: PlayerService.OnPlayerSwitchListener,
|
||||
playerEventListener: PlayerService.PlayerEventListener
|
||||
): Cast = Cast(context, playerSwitchListener, playerEventListener)
|
||||
}
|
||||
|
||||
fun setupButton(context: Context, menu: Menu?) {
|
||||
CastButtonFactory.setUpMediaRouteButton(context, menu, R.id.cast)
|
||||
private val player: Player
|
||||
|
||||
init {
|
||||
player = CastPlayer(CastContext.getSharedInstance(context)).apply {
|
||||
addListener(playerEventListener)
|
||||
setSessionAvailabilityListener(CastSessionListener())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPlayer(context: Context): Player = player
|
||||
|
||||
override fun replaceQueue(tracks: List<Track>) {
|
||||
player.onCast()?.let { castPlayer ->
|
||||
tracks
|
||||
.map { track -> buildMediaQueueItem(track) }
|
||||
.apply {
|
||||
castPlayer.loadItems(this.toTypedArray(), 0, 0, Player.REPEAT_MODE_OFF)
|
||||
castPlayer.playWhenReady = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun addToQueue(tracks: List<Track>) {
|
||||
player.onCast()?.let { castPlayer ->
|
||||
tracks
|
||||
.map { track -> buildMediaQueueItem(track) }
|
||||
.forEach {
|
||||
castPlayer.addItems(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun insertNext(track: Track, current: Int) {
|
||||
player.onCast()?.let { castPlayer ->
|
||||
val period = Timeline.Period().run {
|
||||
player.currentTimeline.getPeriod(current + 1, this)
|
||||
}
|
||||
|
||||
castPlayer.addItems(period.id.toString().toInt(), buildMediaQueueItem(track))
|
||||
}
|
||||
}
|
||||
|
||||
override fun remove(index: Int) {
|
||||
player.onCast()?.let { castPlayer ->
|
||||
val period = Timeline.Period().run {
|
||||
player.currentTimeline.getPeriod(index, this)
|
||||
}
|
||||
|
||||
castPlayer.removeItem(period.id.toString().toInt())
|
||||
}
|
||||
}
|
||||
|
||||
override fun move(oldPosition: Int, newPosition: Int) {
|
||||
player.onCast()?.let { castPlayer ->
|
||||
val period = Timeline.Period().run {
|
||||
player.currentTimeline.getPeriod(oldPosition, this)
|
||||
}
|
||||
|
||||
castPlayer.moveItem(period.id.toString().toInt(), newPosition)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildMediaQueueItem(track: Track): MediaQueueItem {
|
||||
val listenUrl = mustNormalizeUrl(track.bestUpload()?.listen_url ?: "")
|
||||
val token = PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).getString("listen_token", "")
|
||||
val metadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MUSIC_TRACK).apply {
|
||||
putString(MediaMetadata.KEY_ARTIST, track.artist.name)
|
||||
putString(MediaMetadata.KEY_ALBUM_TITLE, track.album.title)
|
||||
putString(MediaMetadata.KEY_TITLE, track.title)
|
||||
|
||||
addImage(WebImage(Uri.parse(mustNormalizeUrl(track.album.cover()))))
|
||||
}
|
||||
|
||||
val url = Uri.parse(listenUrl)
|
||||
.buildUpon()
|
||||
.appendQueryParameter("token", token)
|
||||
.build()
|
||||
.toString()
|
||||
|
||||
val mediaInfo = MediaInfo.Builder(url)
|
||||
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
||||
.setMetadata(metadata)
|
||||
.build()
|
||||
|
||||
return MediaQueueItem.Builder(mediaInfo).build()
|
||||
}
|
||||
|
||||
inner class CastSessionListener : SessionAvailabilityListener {
|
||||
override fun onCastSessionAvailable() {
|
||||
switchListener.switchToRemote()
|
||||
}
|
||||
|
||||
override fun onCastSessionUnavailable() {
|
||||
switchListener.switchToLocal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,18 +2,17 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<!-- <item
|
||||
<item
|
||||
android:id="@+id/cast"
|
||||
android:iconTint="@android:color/white"
|
||||
android:title="@string/toolbar_cast"
|
||||
android:title="Cast!"
|
||||
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
|
||||
app:showAsAction="ifRoom" /> -->
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_search"
|
||||
android:icon="@drawable/search"
|
||||
android:title="@string/toolbar_search"
|
||||
app:showAsAction="ifRoom" />
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_only_my_music"
|
||||
@ -24,8 +23,7 @@
|
||||
<item
|
||||
android:id="@+id/settings"
|
||||
android:icon="@drawable/settings"
|
||||
android:iconTint="@android:color/white"
|
||||
android:title="@string/title_settings"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
</menu>
|
13
app/src/full/res/values/styles.xml
Normal file
13
app/src/full/res/values/styles.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="AppTheme" parent="AppTheme.Base">
|
||||
<item name="mediaRouteTheme">@style/AppTheme.MediaRouteTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.MediaRouteTheme" parent="Theme.MediaRouter">
|
||||
<item name="mediaRouteButtonStyle">@style/AppTheme.MediaRouteTheme.ButtonStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.MediaRouteTheme.ButtonStyle" parent="Widget.MediaRouter.Light.MediaRouteButton">
|
||||
<item name="mediaRouteButtonTint">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
@ -12,6 +12,7 @@ import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.support.v4.media.session.MediaSessionCompat
|
||||
import android.view.KeyEvent
|
||||
import com.github.apognu.otter.Cast
|
||||
import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.google.android.exoplayer2.C
|
||||
@ -30,6 +31,11 @@ import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PlayerService : Service() {
|
||||
interface OnPlayerSwitchListener {
|
||||
fun switchToLocal()
|
||||
fun switchToRemote()
|
||||
}
|
||||
|
||||
private lateinit var queue: QueueManager
|
||||
private val jobs = mutableListOf<Job>()
|
||||
|
||||
@ -40,7 +46,10 @@ class PlayerService : Service() {
|
||||
|
||||
private lateinit var mediaControlsManager: MediaControlsManager
|
||||
private lateinit var mediaSession: MediaSessionCompat
|
||||
private lateinit var player: SimpleExoPlayer
|
||||
|
||||
private lateinit var player: Player
|
||||
private lateinit var localPlayer: SimpleExoPlayer
|
||||
private var cast: Cast? = null
|
||||
|
||||
private lateinit var playerEventListener: PlayerEventListener
|
||||
private val headphonesUnpluggedReceiver = HeadphonesUnpluggedReceiver()
|
||||
@ -58,7 +67,6 @@ class PlayerService : Service() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
queue = QueueManager(this)
|
||||
radioPlayer = RadioPlayer(this)
|
||||
|
||||
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
@ -85,13 +93,15 @@ class PlayerService : Service() {
|
||||
|
||||
mediaControlsManager = MediaControlsManager(this, mediaSession)
|
||||
|
||||
player = SimpleExoPlayer.Builder(this).build().apply {
|
||||
localPlayer = SimpleExoPlayer.Builder(this).build().apply {
|
||||
playWhenReady = false
|
||||
|
||||
playerEventListener = PlayerEventListener().also {
|
||||
addListener(it)
|
||||
}
|
||||
|
||||
cast = Cast.get(this@PlayerService, PlayerSwitchListener(), playerEventListener)
|
||||
|
||||
MediaSessionConnector(mediaSession).also {
|
||||
it.setPlayer(this)
|
||||
it.setMediaButtonEventHandler { player, _, mediaButtonEvent ->
|
||||
@ -111,8 +121,11 @@ class PlayerService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
player = if (cast?.isCastSessionAvailable() == true) cast!!.getPlayer(this) else localPlayer
|
||||
queue = QueueManager(this, cast)
|
||||
|
||||
if (queue.current > -1) {
|
||||
player.prepare(queue.datasources, true, true)
|
||||
player.onLocal()?.prepare(queue.datasources, true, true)
|
||||
|
||||
Cache.get(this, "progress")?.let { progress ->
|
||||
player.seekTo(queue.current, progress.readLine().toLong())
|
||||
@ -143,7 +156,7 @@ class PlayerService : Service() {
|
||||
if (!command.fromRadio) radioPlayer.stop()
|
||||
|
||||
queue.replace(command.queue)
|
||||
player.prepare(queue.datasources, true, true)
|
||||
player.onLocal()?.prepare(queue.datasources, true, true)
|
||||
|
||||
state(true)
|
||||
|
||||
@ -262,7 +275,7 @@ class PlayerService : Service() {
|
||||
}
|
||||
|
||||
if (state && player.playbackState == Player.STATE_IDLE) {
|
||||
player.prepare(queue.datasources)
|
||||
player.onLocal()?.prepare(queue.datasources)
|
||||
}
|
||||
|
||||
var allowed = !state
|
||||
@ -394,7 +407,7 @@ class PlayerService : Service() {
|
||||
EventBus.send(Event.PlaybackError(getString(R.string.error_playback)))
|
||||
|
||||
queue.current++
|
||||
player.prepare(queue.datasources, true, true)
|
||||
player.onLocal()?.prepare(queue.datasources, true, true)
|
||||
player.seekTo(queue.current, 0)
|
||||
player.playWhenReady = true
|
||||
|
||||
@ -406,7 +419,7 @@ class PlayerService : Service() {
|
||||
override fun onAudioFocusChange(focus: Int) {
|
||||
when (focus) {
|
||||
AudioManager.AUDIOFOCUS_GAIN -> {
|
||||
player.volume = 1f
|
||||
player.onLocal()?.volume = 1f
|
||||
|
||||
state(stateWhenLostFocus)
|
||||
stateWhenLostFocus = false
|
||||
@ -424,9 +437,21 @@ class PlayerService : Service() {
|
||||
|
||||
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
|
||||
stateWhenLostFocus = player.playWhenReady
|
||||
player.volume = 0.3f
|
||||
player.onLocal()?.volume = 0.3f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class PlayerSwitchListener : OnPlayerSwitchListener {
|
||||
override fun switchToLocal() {
|
||||
player = localPlayer
|
||||
}
|
||||
|
||||
override fun switchToRemote() {
|
||||
cast?.let { cast ->
|
||||
player = cast.getPlayer(this@PlayerService)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory
|
||||
import com.google.android.exoplayer2.util.Util
|
||||
import com.google.gson.Gson
|
||||
|
||||
class QueueManager(val context: Context) {
|
||||
class QueueManager(val context: Context, val cast: CastInterface?) {
|
||||
var metadata: MutableList<Track> = mutableListOf()
|
||||
val datasources = ConcatenatingMediaSource()
|
||||
var current = -1
|
||||
@ -84,6 +84,8 @@ class QueueManager(val context: Context) {
|
||||
datasources.clear()
|
||||
datasources.addMediaSources(sources)
|
||||
|
||||
cast?.replaceQueue(tracks)
|
||||
|
||||
persist()
|
||||
|
||||
EventBus.send(Event.QueueChanged)
|
||||
@ -102,6 +104,8 @@ class QueueManager(val context: Context) {
|
||||
metadata.addAll(tracks)
|
||||
datasources.addMediaSources(sources)
|
||||
|
||||
cast?.addToQueue(tracks)
|
||||
|
||||
persist()
|
||||
|
||||
EventBus.send(Event.QueueChanged)
|
||||
@ -120,25 +124,29 @@ class QueueManager(val context: Context) {
|
||||
move(metadata.indexOf(track), current + 1)
|
||||
}
|
||||
|
||||
cast?.insertNext(track, current)
|
||||
|
||||
persist()
|
||||
|
||||
EventBus.send(Event.QueueChanged)
|
||||
}
|
||||
|
||||
fun remove(track: Track) {
|
||||
metadata.indexOf(track).let {
|
||||
if (it < 0) {
|
||||
metadata.indexOf(track).let { trackIndex ->
|
||||
if (trackIndex < 0) {
|
||||
return
|
||||
}
|
||||
|
||||
datasources.removeMediaSource(it)
|
||||
metadata.removeAt(it)
|
||||
datasources.removeMediaSource(trackIndex)
|
||||
metadata.removeAt(trackIndex)
|
||||
|
||||
if (it == current) {
|
||||
cast?.remove(trackIndex)
|
||||
|
||||
if (trackIndex == current) {
|
||||
CommandBus.send(Command.NextTrack)
|
||||
}
|
||||
|
||||
if (it < current) {
|
||||
if (trackIndex < current) {
|
||||
current--
|
||||
}
|
||||
}
|
||||
@ -156,6 +164,8 @@ class QueueManager(val context: Context) {
|
||||
datasources.moveMediaSource(oldPosition, newPosition)
|
||||
metadata.add(newPosition, metadata.removeAt(oldPosition))
|
||||
|
||||
cast?.move(oldPosition, newPosition)
|
||||
|
||||
persist()
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,6 @@ sealed class Response {
|
||||
object EventBus {
|
||||
fun send(event: Event) {
|
||||
GlobalScope.launch(IO) {
|
||||
Otter.get().eventBus.log()
|
||||
Otter.get().eventBus.offer(event)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package com.github.apognu.otter.utils
|
||||
|
||||
import android.content.Context
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
|
||||
interface CastInterface {
|
||||
fun isCastSessionAvailable(): Boolean = false
|
||||
fun getPlayer(context: Context): Player = SimpleExoPlayer.Builder(context).build()
|
||||
|
||||
fun replaceQueue(tracks: List<Track>) {}
|
||||
fun addToQueue(tracks: List<Track>) {}
|
||||
fun insertNext(track: Track, current: Int) {}
|
||||
fun remove(index: Int) {}
|
||||
fun move(oldPosition: Int, newPosition: Int) {}
|
||||
}
|
@ -6,6 +6,8 @@ import com.github.apognu.otter.R
|
||||
import com.github.apognu.otter.fragments.BrowseFragment
|
||||
import com.github.apognu.otter.repositories.Repository
|
||||
import com.github.kittinunf.fuel.core.Request
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import com.google.android.exoplayer2.offline.Download
|
||||
import com.google.gson.Gson
|
||||
import com.squareup.picasso.Picasso
|
||||
@ -77,3 +79,8 @@ fun Request.authorize(): Request {
|
||||
}
|
||||
|
||||
fun Download.getMetadata(): DownloadInfo? = Gson().fromJson(String(this.request.data), DownloadInfo::class.java)
|
||||
|
||||
fun Player.onLocal(): SimpleExoPlayer? {
|
||||
return if (this is SimpleExoPlayer) this
|
||||
else null
|
||||
}
|
||||
|
@ -4,9 +4,12 @@ import com.google.android.exoplayer2.offline.Download
|
||||
import com.preference.PowerPreference
|
||||
|
||||
data class User(
|
||||
val full_username: String
|
||||
val full_username: String,
|
||||
val tokens: UserTokens
|
||||
)
|
||||
|
||||
data class UserTokens(val listen: String)
|
||||
|
||||
sealed class CacheItem<D : Any>(val data: List<D>)
|
||||
class ArtistsCache(data: List<Artist>) : CacheItem<Artist>(data)
|
||||
class AlbumsCache(data: List<Album>) : CacheItem<Album>(data)
|
||||
|
@ -20,6 +20,7 @@ object Userinfo {
|
||||
|
||||
PowerPreference.getFileByName(AppContext.PREFS_CREDENTIALS).apply {
|
||||
setString("actor_username", user.full_username)
|
||||
setString("listen_token", user.tokens.listen)
|
||||
}
|
||||
|
||||
user
|
||||
|
@ -1,6 +1,8 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<style name="AppTheme" parent="AppTheme.Base"></style>
|
||||
|
||||
<style name="AppTheme.Base" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<item name="android:forceDarkAllowed" tools:targetApi="q">true</item>
|
||||
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
@ -16,8 +18,6 @@
|
||||
|
||||
<item name="buttonBarNegativeButtonStyle">@style/AppTheme.DialogButtonStyle</item>
|
||||
<item name="buttonBarPositiveButtonStyle">@style/AppTheme.DialogButtonStyle</item>
|
||||
|
||||
<item name="mediaRouteTheme">@style/AppTheme.MediaRouteTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Fragment"></style>
|
||||
@ -101,12 +101,4 @@
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.MediaRouteTheme" parent="Theme.MediaRouter">
|
||||
<item name="mediaRouteButtonStyle">@style/AppTheme.MediaRouteTheme.ButtonStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.MediaRouteTheme.ButtonStyle" parent="Widget.MediaRouter.Light.MediaRouteButton">
|
||||
<item name="mediaRouteButtonTint">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user