Added context menu to PlayQueueActivity and PlayQueueAdapter.

This commit is contained in:
casey langen 2017-11-19 20:59:41 -08:00
parent a573265092
commit 93f1980b77
29 changed files with 209 additions and 178 deletions

View File

@ -3,13 +3,13 @@ package io.casey.musikcube.remote
import android.arch.persistence.room.Room import android.arch.persistence.room.Room
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.facebook.stetho.Stetho import com.facebook.stetho.Stetho
import io.casey.musikcube.remote.ui.settings.model.ConnectionsDb
import io.casey.musikcube.remote.service.playback.impl.streaming.offline.OfflineDb
import io.casey.musikcube.remote.injection.DaggerAppComponent
import io.casey.musikcube.remote.injection.AppComponent import io.casey.musikcube.remote.injection.AppComponent
import io.casey.musikcube.remote.injection.AppModule import io.casey.musikcube.remote.injection.AppModule
import io.casey.musikcube.remote.injection.DaggerAppComponent
import io.casey.musikcube.remote.injection.ServiceModule import io.casey.musikcube.remote.injection.ServiceModule
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy
import io.casey.musikcube.remote.service.playback.impl.streaming.offline.OfflineDb
import io.casey.musikcube.remote.ui.settings.model.ConnectionsDb
import io.casey.musikcube.remote.ui.shared.util.NetworkUtil import io.casey.musikcube.remote.ui.shared.util.NetworkUtil
import io.fabric.sdk.android.Fabric import io.fabric.sdk.android.Fabric

View File

@ -2,14 +2,12 @@ package io.casey.musikcube.remote.injection
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import io.casey.musikcube.remote.service.websocket.model.IDataProvider
import io.casey.musikcube.remote.model.impl.remote.RemoteDataProvider
import io.casey.musikcube.remote.service.websocket.WebSocketService import io.casey.musikcube.remote.service.websocket.WebSocketService
import io.casey.musikcube.remote.service.websocket.model.IDataProvider
import io.casey.musikcube.remote.service.websocket.model.impl.remote.RemoteDataProvider
@Module @Module
class DataModule { class DataModule {
@Provides @Provides
fun providesDataProvider(wss: WebSocketService): IDataProvider { fun providesDataProvider(wss: WebSocketService): IDataProvider = RemoteDataProvider(wss)
return RemoteDataProvider(wss)
}
} }

View File

@ -11,9 +11,9 @@ import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.module.AppGlideModule
import io.casey.musikcube.remote.ui.settings.constants.Prefs import io.casey.musikcube.remote.ui.settings.constants.Prefs
import okhttp3.* import okhttp3.*
import java.io.InputStream
import io.casey.musikcube.remote.ui.shared.model.albumart.canIntercept as canInterceptArtwork import io.casey.musikcube.remote.ui.shared.model.albumart.canIntercept as canInterceptArtwork
import io.casey.musikcube.remote.ui.shared.model.albumart.intercept as interceptArtwork import io.casey.musikcube.remote.ui.shared.model.albumart.intercept as interceptArtwork
import java.io.InputStream
@GlideModule @GlideModule
class GlideModule : AppGlideModule() { class GlideModule : AppGlideModule() {

View File

@ -1,17 +1,17 @@
package io.casey.musikcube.remote.injection package io.casey.musikcube.remote.injection
import dagger.Component import dagger.Component
import io.casey.musikcube.remote.ui.home.activity.MainActivity
import io.casey.musikcube.remote.ui.category.activity.*
import io.casey.musikcube.remote.ui.albums.activity.AlbumBrowseActivity import io.casey.musikcube.remote.ui.albums.activity.AlbumBrowseActivity
import io.casey.musikcube.remote.ui.playqueue.activity.PlayQueueActivity import io.casey.musikcube.remote.ui.category.activity.CategoryBrowseActivity
import io.casey.musikcube.remote.ui.shared.activity.BaseActivity import io.casey.musikcube.remote.ui.home.activity.MainActivity
import io.casey.musikcube.remote.ui.shared.view.EmptyListView
import io.casey.musikcube.remote.ui.home.view.MainMetadataView import io.casey.musikcube.remote.ui.home.view.MainMetadataView
import io.casey.musikcube.remote.ui.playqueue.activity.PlayQueueActivity
import io.casey.musikcube.remote.ui.settings.activity.ConnectionsActivity import io.casey.musikcube.remote.ui.settings.activity.ConnectionsActivity
import io.casey.musikcube.remote.ui.settings.activity.SettingsActivity import io.casey.musikcube.remote.ui.settings.activity.SettingsActivity
import io.casey.musikcube.remote.ui.shared.activity.BaseActivity
import io.casey.musikcube.remote.ui.shared.mixin.DataProviderMixin import io.casey.musikcube.remote.ui.shared.mixin.DataProviderMixin
import io.casey.musikcube.remote.ui.shared.mixin.ItemContextMenuMixin import io.casey.musikcube.remote.ui.shared.mixin.ItemContextMenuMixin
import io.casey.musikcube.remote.ui.shared.view.EmptyListView
import io.casey.musikcube.remote.ui.tracks.activity.TrackListActivity import io.casey.musikcube.remote.ui.tracks.activity.TrackListActivity
@ViewScope @ViewScope

View File

@ -1,10 +1,10 @@
package io.casey.musikcube.remote.service.playback package io.casey.musikcube.remote.service.playback
import io.casey.musikcube.remote.Application import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.service.playback.impl.streaming.offline.OfflineTrack
import io.casey.musikcube.remote.service.playback.impl.player.ExoPlayerWrapper import io.casey.musikcube.remote.service.playback.impl.player.ExoPlayerWrapper
import io.casey.musikcube.remote.service.playback.impl.player.MediaPlayerWrapper import io.casey.musikcube.remote.service.playback.impl.player.MediaPlayerWrapper
import io.casey.musikcube.remote.service.playback.impl.streaming.offline.OfflineTrack
import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.util.Preconditions import io.casey.musikcube.remote.util.Preconditions
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers

View File

@ -21,12 +21,12 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util import com.google.android.exoplayer2.util.Util
import io.casey.musikcube.remote.Application import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.service.playback.PlayerWrapper import io.casey.musikcube.remote.service.playback.PlayerWrapper
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy
import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.ui.settings.constants.Prefs
import io.casey.musikcube.remote.ui.shared.util.NetworkUtil import io.casey.musikcube.remote.ui.shared.util.NetworkUtil
import io.casey.musikcube.remote.util.Preconditions import io.casey.musikcube.remote.util.Preconditions
import io.casey.musikcube.remote.ui.settings.constants.Prefs
import okhttp3.Cache import okhttp3.Cache
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.io.File import java.io.File

View File

@ -9,11 +9,11 @@ import android.os.PowerManager
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import io.casey.musikcube.remote.Application import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.service.playback.PlayerWrapper import io.casey.musikcube.remote.service.playback.PlayerWrapper
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy
import io.casey.musikcube.remote.util.Preconditions import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.ui.settings.constants.Prefs import io.casey.musikcube.remote.ui.settings.constants.Prefs
import io.casey.musikcube.remote.util.Preconditions
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*

View File

@ -4,7 +4,6 @@ import android.os.Handler
import io.casey.musikcube.remote.Application import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.injection.DaggerServiceComponent import io.casey.musikcube.remote.injection.DaggerServiceComponent
import io.casey.musikcube.remote.injection.DataModule import io.casey.musikcube.remote.injection.DataModule
import io.casey.musikcube.remote.model.impl.remote.RemoteTrack
import io.casey.musikcube.remote.service.playback.IPlaybackService import io.casey.musikcube.remote.service.playback.IPlaybackService
import io.casey.musikcube.remote.service.playback.PlaybackState import io.casey.musikcube.remote.service.playback.PlaybackState
import io.casey.musikcube.remote.service.playback.RepeatMode import io.casey.musikcube.remote.service.playback.RepeatMode
@ -13,6 +12,7 @@ import io.casey.musikcube.remote.service.websocket.SocketMessage
import io.casey.musikcube.remote.service.websocket.WebSocketService import io.casey.musikcube.remote.service.websocket.WebSocketService
import io.casey.musikcube.remote.service.websocket.model.IDataProvider import io.casey.musikcube.remote.service.websocket.model.IDataProvider
import io.casey.musikcube.remote.service.websocket.model.ITrack import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.service.websocket.model.impl.remote.RemoteTrack
import io.casey.musikcube.remote.ui.shared.model.TrackListSlidingWindow import io.casey.musikcube.remote.ui.shared.model.TrackListSlidingWindow
import io.reactivex.Observable import io.reactivex.Observable
import org.json.JSONObject import org.json.JSONObject

View File

@ -8,9 +8,9 @@ import com.danikula.videocache.CacheListener
import com.danikula.videocache.HttpProxyCacheServer import com.danikula.videocache.HttpProxyCacheServer
import com.danikula.videocache.file.Md5FileNameGenerator import com.danikula.videocache.file.Md5FileNameGenerator
import io.casey.musikcube.remote.Application import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.ui.settings.constants.Prefs
import io.casey.musikcube.remote.ui.shared.util.NetworkUtil import io.casey.musikcube.remote.ui.shared.util.NetworkUtil
import io.casey.musikcube.remote.util.Strings import io.casey.musikcube.remote.util.Strings
import io.casey.musikcube.remote.ui.settings.constants.Prefs
import java.io.File import java.io.File
import java.util.* import java.util.*

View File

@ -12,7 +12,6 @@ import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.R import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.injection.DaggerServiceComponent import io.casey.musikcube.remote.injection.DaggerServiceComponent
import io.casey.musikcube.remote.injection.DataModule import io.casey.musikcube.remote.injection.DataModule
import io.casey.musikcube.remote.model.impl.remote.RemoteTrack
import io.casey.musikcube.remote.service.playback.IPlaybackService import io.casey.musikcube.remote.service.playback.IPlaybackService
import io.casey.musikcube.remote.service.playback.PlaybackState import io.casey.musikcube.remote.service.playback.PlaybackState
import io.casey.musikcube.remote.service.playback.PlayerWrapper import io.casey.musikcube.remote.service.playback.PlayerWrapper
@ -21,6 +20,7 @@ import io.casey.musikcube.remote.service.system.SystemService
import io.casey.musikcube.remote.service.websocket.Messages import io.casey.musikcube.remote.service.websocket.Messages
import io.casey.musikcube.remote.service.websocket.model.IDataProvider import io.casey.musikcube.remote.service.websocket.model.IDataProvider
import io.casey.musikcube.remote.service.websocket.model.ITrack import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.service.websocket.model.impl.remote.RemoteTrack
import io.casey.musikcube.remote.ui.settings.constants.Prefs import io.casey.musikcube.remote.ui.settings.constants.Prefs
import io.casey.musikcube.remote.ui.shared.model.TrackListSlidingWindow import io.casey.musikcube.remote.ui.shared.model.TrackListSlidingWindow
import io.casey.musikcube.remote.util.Strings import io.casey.musikcube.remote.util.Strings

View File

@ -5,10 +5,10 @@ import android.arch.persistence.room.RoomDatabase
import io.casey.musikcube.remote.Application import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.injection.DaggerDataComponent import io.casey.musikcube.remote.injection.DaggerDataComponent
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy import io.casey.musikcube.remote.service.playback.impl.streaming.StreamProxy
import io.casey.musikcube.remote.util.Strings
import io.casey.musikcube.remote.service.websocket.Messages import io.casey.musikcube.remote.service.websocket.Messages
import io.casey.musikcube.remote.service.websocket.SocketMessage import io.casey.musikcube.remote.service.websocket.SocketMessage
import io.casey.musikcube.remote.service.websocket.WebSocketService import io.casey.musikcube.remote.service.websocket.WebSocketService
import io.casey.musikcube.remote.util.Strings
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
@ -22,6 +22,7 @@ abstract class OfflineDb : RoomDatabase() {
@Inject lateinit var wss: WebSocketService @Inject lateinit var wss: WebSocketService
init { init {
DaggerDataComponent.builder() DaggerDataComponent.builder()
.appComponent(Application.appComponent) .appComponent(Application.appComponent)
.build().inject(this) .build().inject(this)

View File

@ -21,19 +21,19 @@ import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import com.bumptech.glide.request.transition.Transition import com.bumptech.glide.request.transition.Transition
import io.casey.musikcube.remote.Application import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.ui.home.activity.MainActivity
import io.casey.musikcube.remote.R import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.injection.GlideApp import io.casey.musikcube.remote.injection.GlideApp
import io.casey.musikcube.remote.injection.GlideRequest import io.casey.musikcube.remote.injection.GlideRequest
import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.service.playback.PlaybackServiceFactory import io.casey.musikcube.remote.service.playback.PlaybackServiceFactory
import io.casey.musikcube.remote.service.playback.PlaybackState import io.casey.musikcube.remote.service.playback.PlaybackState
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamingPlaybackService import io.casey.musikcube.remote.service.playback.impl.streaming.StreamingPlaybackService
import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.ui.home.activity.MainActivity
import io.casey.musikcube.remote.ui.settings.constants.Prefs
import io.casey.musikcube.remote.ui.shared.extension.fallback import io.casey.musikcube.remote.ui.shared.extension.fallback
import io.casey.musikcube.remote.ui.shared.model.albumart.Size import io.casey.musikcube.remote.ui.shared.model.albumart.Size
import io.casey.musikcube.remote.util.Debouncer import io.casey.musikcube.remote.util.Debouncer
import io.casey.musikcube.remote.util.Strings import io.casey.musikcube.remote.util.Strings
import io.casey.musikcube.remote.ui.settings.constants.Prefs
import android.support.v4.app.NotificationCompat.Action as NotifAction import android.support.v4.app.NotificationCompat.Action as NotifAction
import io.casey.musikcube.remote.ui.shared.model.albumart.getUrl as getAlbumArtUrl import io.casey.musikcube.remote.ui.shared.model.albumart.getUrl as getAlbumArtUrl

View File

@ -1,8 +1,8 @@
package io.casey.musikcube.remote.model.impl.remote package io.casey.musikcube.remote.service.websocket.model.impl.remote
import io.casey.musikcube.remote.service.websocket.model.IAlbum
import io.casey.musikcube.remote.service.playback.impl.remote.Metadata import io.casey.musikcube.remote.service.playback.impl.remote.Metadata
import io.casey.musikcube.remote.service.websocket.Messages import io.casey.musikcube.remote.service.websocket.Messages
import io.casey.musikcube.remote.service.websocket.model.IAlbum
import org.json.JSONObject import org.json.JSONObject
class RemoteAlbum(val json: JSONObject) : IAlbum { class RemoteAlbum(val json: JSONObject) : IAlbum {

View File

@ -1,7 +1,7 @@
package io.casey.musikcube.remote.model.impl.remote package io.casey.musikcube.remote.service.websocket.model.impl.remote
import io.casey.musikcube.remote.service.websocket.model.IAlbumArtist
import io.casey.musikcube.remote.service.websocket.Messages import io.casey.musikcube.remote.service.websocket.Messages
import io.casey.musikcube.remote.service.websocket.model.IAlbumArtist
import org.json.JSONObject import org.json.JSONObject
class RemoteAlbumArtist(private val json: JSONObject) : IAlbumArtist { class RemoteAlbumArtist(private val json: JSONObject) : IAlbumArtist {

View File

@ -1,7 +1,7 @@
package io.casey.musikcube.remote.model.impl.remote package io.casey.musikcube.remote.service.websocket.model.impl.remote
import io.casey.musikcube.remote.service.websocket.model.ICategoryValue
import io.casey.musikcube.remote.service.websocket.Messages import io.casey.musikcube.remote.service.websocket.Messages
import io.casey.musikcube.remote.service.websocket.model.ICategoryValue
import org.json.JSONObject import org.json.JSONObject
class RemoteCategoryValue(private val categoryType: String, class RemoteCategoryValue(private val categoryType: String,

View File

@ -1,4 +1,4 @@
package io.casey.musikcube.remote.model.impl.remote package io.casey.musikcube.remote.service.websocket.model.impl.remote
import io.casey.musikcube.remote.service.websocket.Messages import io.casey.musikcube.remote.service.websocket.Messages
import io.casey.musikcube.remote.service.websocket.SocketMessage import io.casey.musikcube.remote.service.websocket.SocketMessage
@ -33,9 +33,8 @@ class RemoteDataProvider(private val service: WebSocketService) : IDataProvider
override val state: IDataProvider.State override val state: IDataProvider.State
get() = currentState get() = currentState
override fun getAlbums(filter: String): Observable<List<IAlbum>> { override fun getAlbums(filter: String): Observable<List<IAlbum>> =
return getAlbumsForCategory("", 0, filter) getAlbumsForCategory("", 0, filter)
}
override fun getAlbumsForCategory(categoryType: String, categoryId: Long, filter: String): Observable<List<IAlbum>> { override fun getAlbumsForCategory(categoryType: String, categoryId: Long, filter: String): Observable<List<IAlbum>> {
val message = SocketMessage.Builder val message = SocketMessage.Builder
@ -100,9 +99,8 @@ class RemoteDataProvider(private val service: WebSocketService) : IDataProvider
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
override fun getTracks(filter: String): Observable<List<ITrack>> { override fun getTracks(filter: String): Observable<List<ITrack>> =
return getTracks(-1, -1, filter) getTracks(-1, -1, filter)
}
override fun getTrackCountByCategory(category: String, id: Long, filter: String): Observable<Int> { override fun getTrackCountByCategory(category: String, id: Long, filter: String): Observable<Int> {
val message = SocketMessage.Builder val message = SocketMessage.Builder
@ -118,9 +116,8 @@ class RemoteDataProvider(private val service: WebSocketService) : IDataProvider
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
override fun getTracksByCategory(category: String, id: Long, filter: String): Observable<List<ITrack>> { override fun getTracksByCategory(category: String, id: Long, filter: String): Observable<List<ITrack>> =
return getTracksByCategory(category, id, -1, -1, filter) getTracksByCategory(category, id, -1, -1, filter)
}
override fun getTracksByCategory(category: String, id: Long, limit: Int, offset: Int, filter: String): Observable<List<ITrack>> { override fun getTracksByCategory(category: String, id: Long, limit: Int, offset: Int, filter: String): Observable<List<ITrack>> {
val builder = SocketMessage.Builder val builder = SocketMessage.Builder
@ -152,9 +149,8 @@ class RemoteDataProvider(private val service: WebSocketService) : IDataProvider
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
override fun getPlayQueueTracks(filter: String): Observable<List<ITrack>> { override fun getPlayQueueTracks(filter: String): Observable<List<ITrack>> =
return getPlayQueueTracks(-1, -1, filter) getPlayQueueTracks(-1, -1, filter)
}
override fun getPlayQueueTracks(limit: Int, offset: Int, filter: String): Observable<List<ITrack>> { override fun getPlayQueueTracks(limit: Int, offset: Int, filter: String): Observable<List<ITrack>> {
val builder = SocketMessage.Builder val builder = SocketMessage.Builder
@ -360,17 +356,14 @@ class RemoteDataProvider(private val service: WebSocketService) : IDataProvider
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
} }
override fun observeState(): Observable<Pair<IDataProvider.State, IDataProvider.State>> { override fun observeState(): Observable<Pair<IDataProvider.State, IDataProvider.State>> =
return connectionStatePublisher.observeOn(AndroidSchedulers.mainThread()) connectionStatePublisher.observeOn(AndroidSchedulers.mainThread())
}
override fun observePlayQueue(): Observable<Unit> { override fun observePlayQueue(): Observable<Unit> =
return playQueueStatePublisher.observeOn(AndroidSchedulers.mainThread()) playQueueStatePublisher.observeOn(AndroidSchedulers.mainThread())
}
override fun observeAuthFailure(): Observable<Unit> { override fun observeAuthFailure(): Observable<Unit> =
return authFailurePublisher.observeOn(AndroidSchedulers.mainThread()) authFailurePublisher.observeOn(AndroidSchedulers.mainThread())
}
override fun attach() { override fun attach() {
service.addClient(client) service.addClient(client)
@ -404,13 +397,12 @@ class RemoteDataProvider(private val service: WebSocketService) : IDataProvider
} }
} }
private fun mapState(state: WebSocketService.State): IDataProvider.State { private fun mapState(state: WebSocketService.State): IDataProvider.State =
return when (state) { when (state) {
WebSocketService.State.Disconnected -> IDataProvider.State.Disconnected WebSocketService.State.Disconnected -> IDataProvider.State.Disconnected
WebSocketService.State.Connecting -> IDataProvider.State.Connecting WebSocketService.State.Connecting -> IDataProvider.State.Connecting
WebSocketService.State.Connected -> IDataProvider.State.Connected WebSocketService.State.Connected -> IDataProvider.State.Connected
} }
}
companion object { companion object {
private fun createTrackListSubquery(categoryType: String, categoryId: Long, filter: String): JSONObject { private fun createTrackListSubquery(categoryType: String, categoryId: Long, filter: String): JSONObject {

View File

@ -1,7 +1,7 @@
package io.casey.musikcube.remote.model.impl.remote package io.casey.musikcube.remote.service.websocket.model.impl.remote
import io.casey.musikcube.remote.service.websocket.model.IPlaylist
import io.casey.musikcube.remote.service.websocket.Messages import io.casey.musikcube.remote.service.websocket.Messages
import io.casey.musikcube.remote.service.websocket.model.IPlaylist
import org.json.JSONObject import org.json.JSONObject
class RemotePlaylist(private val json: JSONObject) : IPlaylist { class RemotePlaylist(private val json: JSONObject) : IPlaylist {

View File

@ -1,8 +1,8 @@
package io.casey.musikcube.remote.model.impl.remote package io.casey.musikcube.remote.service.websocket.model.impl.remote
import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.service.playback.impl.remote.Metadata import io.casey.musikcube.remote.service.playback.impl.remote.Metadata
import io.casey.musikcube.remote.service.websocket.Messages import io.casey.musikcube.remote.service.websocket.Messages
import io.casey.musikcube.remote.service.websocket.model.ITrack
import org.json.JSONObject import org.json.JSONObject
class RemoteTrack(val json: JSONObject) : ITrack { class RemoteTrack(val json: JSONObject) : ITrack {

View File

@ -32,11 +32,11 @@ import io.casey.musikcube.remote.ui.playqueue.activity.PlayQueueActivity
import io.casey.musikcube.remote.ui.settings.activity.SettingsActivity import io.casey.musikcube.remote.ui.settings.activity.SettingsActivity
import io.casey.musikcube.remote.ui.settings.constants.Prefs import io.casey.musikcube.remote.ui.settings.constants.Prefs
import io.casey.musikcube.remote.ui.shared.activity.BaseActivity import io.casey.musikcube.remote.ui.shared.activity.BaseActivity
import io.casey.musikcube.remote.ui.shared.mixin.DataProviderMixin
import io.casey.musikcube.remote.ui.shared.mixin.PlaybackMixin
import io.casey.musikcube.remote.ui.shared.extension.getColorCompat import io.casey.musikcube.remote.ui.shared.extension.getColorCompat
import io.casey.musikcube.remote.ui.shared.extension.setCheckWithoutEvent import io.casey.musikcube.remote.ui.shared.extension.setCheckWithoutEvent
import io.casey.musikcube.remote.ui.shared.extension.showSnackbar import io.casey.musikcube.remote.ui.shared.extension.showSnackbar
import io.casey.musikcube.remote.ui.shared.mixin.DataProviderMixin
import io.casey.musikcube.remote.ui.shared.mixin.PlaybackMixin
import io.casey.musikcube.remote.ui.shared.util.Duration import io.casey.musikcube.remote.ui.shared.util.Duration
import io.casey.musikcube.remote.ui.shared.util.UpdateCheck import io.casey.musikcube.remote.ui.shared.util.UpdateCheck
import io.casey.musikcube.remote.ui.tracks.activity.TrackListActivity import io.casey.musikcube.remote.ui.tracks.activity.TrackListActivity

View File

@ -3,41 +3,52 @@ package io.casey.musikcube.remote.ui.playqueue.activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
import io.casey.musikcube.remote.R import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.service.websocket.model.IDataProvider import io.casey.musikcube.remote.service.websocket.model.IDataProvider
import io.casey.musikcube.remote.service.websocket.model.ITrack import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.ui.playqueue.adapter.PlayQueueAdapter
import io.casey.musikcube.remote.ui.shared.activity.BaseActivity import io.casey.musikcube.remote.ui.shared.activity.BaseActivity
import io.casey.musikcube.remote.ui.shared.extension.* import io.casey.musikcube.remote.ui.shared.extension.addTransportFragment
import io.casey.musikcube.remote.ui.shared.extension.enableUpNavigation
import io.casey.musikcube.remote.ui.shared.extension.setTitleFromIntent
import io.casey.musikcube.remote.ui.shared.extension.setupDefaultRecyclerView
import io.casey.musikcube.remote.ui.shared.mixin.DataProviderMixin import io.casey.musikcube.remote.ui.shared.mixin.DataProviderMixin
import io.casey.musikcube.remote.ui.shared.mixin.ItemContextMenuMixin
import io.casey.musikcube.remote.ui.shared.mixin.PlaybackMixin import io.casey.musikcube.remote.ui.shared.mixin.PlaybackMixin
import io.casey.musikcube.remote.ui.shared.model.TrackListSlidingWindow import io.casey.musikcube.remote.ui.shared.model.TrackListSlidingWindow
import io.casey.musikcube.remote.ui.shared.view.EmptyListView import io.casey.musikcube.remote.ui.shared.view.EmptyListView
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
class PlayQueueActivity : BaseActivity() { class PlayQueueActivity : BaseActivity() {
private var adapter: Adapter = Adapter()
private var offlineQueue: Boolean = false private var offlineQueue: Boolean = false
private lateinit var data: DataProviderMixin private lateinit var data: DataProviderMixin
private lateinit var playback: PlaybackMixin private lateinit var playback: PlaybackMixin
private lateinit var tracks: TrackListSlidingWindow private lateinit var tracks: TrackListSlidingWindow
private lateinit var adapter: PlayQueueAdapter
private lateinit var emptyView: EmptyListView private lateinit var emptyView: EmptyListView
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
component.inject(this) component.inject(this)
data = mixin(DataProviderMixin()) data = mixin(DataProviderMixin())
playback = mixin(PlaybackMixin(playbackEvents)) playback = mixin(PlaybackMixin(playbackEvents))
mixin(ItemContextMenuMixin(this))
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.recycler_view_activity) setContentView(R.layout.recycler_view_activity)
val queryFactory = playback.service.playlistQueryFactory
offlineQueue = playback.service.playlistQueryFactory.offline()
val recyclerView = findViewById<FastScrollRecyclerView>(R.id.recycler_view) val recyclerView = findViewById<FastScrollRecyclerView>(R.id.recycler_view)
tracks = TrackListSlidingWindow(recyclerView, data.provider, queryFactory)
tracks.setInitialPosition(intent.getIntExtra(EXTRA_PLAYING_INDEX, -1))
tracks.setOnMetadataLoadedListener(slidingWindowListener)
adapter = PlayQueueAdapter(tracks, playback, adapterListener)
setupDefaultRecyclerView(recyclerView, adapter) setupDefaultRecyclerView(recyclerView, adapter)
emptyView = findViewById(R.id.empty_list_view) emptyView = findViewById(R.id.empty_list_view)
@ -45,13 +56,6 @@ class PlayQueueActivity : BaseActivity() {
emptyView.emptyMessage = getString(R.string.play_queue_empty) emptyView.emptyMessage = getString(R.string.play_queue_empty)
emptyView.alternateView = recyclerView emptyView.alternateView = recyclerView
val queryFactory = playback.service.playlistQueryFactory
offlineQueue = playback.service.playlistQueryFactory.offline()
tracks = TrackListSlidingWindow(recyclerView, data.provider, queryFactory)
tracks.setInitialPosition(intent.getIntExtra(EXTRA_PLAYING_INDEX, -1))
tracks.setOnMetadataLoadedListener(slidingWindowListener)
data.provider.observeState().subscribeBy( data.provider.observeState().subscribeBy(
onNext = { states -> onNext = { states ->
if (states.first == IDataProvider.State.Connected) { if (states.first == IDataProvider.State.Connected) {
@ -84,9 +88,12 @@ class PlayQueueActivity : BaseActivity() {
} }
} }
private val onItemClickListener = View.OnClickListener { v -> private val adapterListener = object: PlayQueueAdapter.EventListener {
if (v.tag is Int) { override fun onItemClicked(position: Int) =
playback.service.playAt(v.tag as Int) playback.service.playAt(position)
override fun onActionClicked(view: View, value: ITrack) {
mixin(ItemContextMenuMixin::class.java)?.showForTrack(value, view)
} }
} }
@ -97,61 +104,11 @@ class PlayQueueActivity : BaseActivity() {
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
} }
private inner class ViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val title: TextView = itemView.findViewById(R.id.title)
private val subtitle: TextView = itemView.findViewById(R.id.subtitle)
private val trackNum: TextView = itemView.findViewById(R.id.track_num)
internal fun bind(track: ITrack?, position: Int) {
trackNum.text = (position + 1).toString()
itemView.tag = position
var titleColor = R.color.theme_foreground
var subtitleColor = R.color.theme_disabled_foreground
if (track == null) {
title.text = "-"
subtitle.text = "-"
}
else {
val playing = playback.service.playingTrack
val entryExternalId = track.externalId
val playingExternalId = playing.externalId
if (entryExternalId == playingExternalId) {
titleColor = R.color.theme_green
subtitleColor = R.color.theme_yellow
}
title.text = fallback(track.title, "-")
subtitle.text = fallback(track.albumArtist, "-")
}
title.setTextColor(getColorCompat(titleColor))
subtitle.setTextColor(getColorCompat(subtitleColor))
}
}
private inner class Adapter : RecyclerView.Adapter<ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.play_queue_row, parent, false)
view.setOnClickListener(onItemClickListener)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(tracks.getTrack(position), position)
}
override fun getItemCount(): Int = tracks.count
}
private val slidingWindowListener = object : TrackListSlidingWindow.OnMetadataLoadedListener { private val slidingWindowListener = object : TrackListSlidingWindow.OnMetadataLoadedListener {
override fun onReloaded(count: Int) { override fun onReloaded(count: Int) =
emptyView.update(data.provider.state, count) emptyView.update(data.provider.state, count)
}
override fun onMetadataLoaded(offset: Int, count: Int) {} override fun onMetadataLoaded(offset: Int, count: Int) = Unit
} }
companion object { companion object {

View File

@ -0,0 +1,75 @@
package io.casey.musikcube.remote.ui.playqueue.adapter
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.ui.shared.extension.fallback
import io.casey.musikcube.remote.ui.shared.extension.getColorCompat
import io.casey.musikcube.remote.ui.shared.mixin.PlaybackMixin
import io.casey.musikcube.remote.ui.shared.model.TrackListSlidingWindow
class PlayQueueAdapter(val tracks: TrackListSlidingWindow,
val playback: PlaybackMixin,
val listener: EventListener): RecyclerView.Adapter<PlayQueueAdapter.ViewHolder>()
{
interface EventListener {
fun onItemClicked(position: Int)
fun onActionClicked(view: View, value: ITrack)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.play_queue_row, parent, false)
val action = view.findViewById<View>(R.id.action)
view.setOnClickListener{ v -> listener.onItemClicked(v.tag as Int) }
action.setOnClickListener{ v -> listener.onActionClicked(v, v.tag as ITrack) }
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(tracks.getTrack(position), position)
}
override fun getItemCount(): Int = tracks.count
inner class ViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val title = itemView.findViewById<TextView>(R.id.title)
private val subtitle = itemView.findViewById<TextView>(R.id.subtitle)
private val trackNum = itemView.findViewById<TextView>(R.id.track_num)
private val action = itemView.findViewById<View>(R.id.action)
internal fun bind(track: ITrack?, position: Int) {
trackNum.text = (position + 1).toString()
itemView.tag = position
action.tag = track
var titleColor = R.color.theme_foreground
var subtitleColor = R.color.theme_disabled_foreground
if (track == null) {
title.text = "-"
subtitle.text = "-"
}
else {
val playing = playback.service.playingTrack
val entryExternalId = track.externalId
val playingExternalId = playing.externalId
if (entryExternalId == playingExternalId) {
titleColor = R.color.theme_green
subtitleColor = R.color.theme_yellow
}
title.text = fallback(track.title, "-")
subtitle.text = fallback(track.albumArtist, "-")
}
title.setTextColor(getColorCompat(titleColor))
subtitle.setTextColor(getColorCompat(subtitleColor))
}
}
}

View File

@ -20,8 +20,8 @@ import com.uacf.taskrunner.Tasks
import io.casey.musikcube.remote.Application import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.R import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.ui.settings.model.Connection import io.casey.musikcube.remote.ui.settings.model.Connection
import io.casey.musikcube.remote.ui.shared.extension.*
import io.casey.musikcube.remote.ui.shared.activity.BaseActivity import io.casey.musikcube.remote.ui.shared.activity.BaseActivity
import io.casey.musikcube.remote.ui.shared.extension.*
private val EXTRA_CONNECTION = "extra_connection" private val EXTRA_CONNECTION = "extra_connection"

View File

@ -14,7 +14,6 @@ import android.support.v7.widget.RecyclerView
import android.support.v7.widget.SearchView import android.support.v7.widget.SearchView
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.CheckBox import android.widget.CheckBox
import android.widget.CompoundButton import android.widget.CompoundButton

View File

@ -7,7 +7,6 @@ import android.support.v7.app.AppCompatActivity
import io.casey.musikcube.remote.framework.IMixin import io.casey.musikcube.remote.framework.IMixin
import io.casey.musikcube.remote.framework.MixinSet import io.casey.musikcube.remote.framework.MixinSet
import io.casey.musikcube.remote.framework.ViewModel import io.casey.musikcube.remote.framework.ViewModel
import io.casey.musikcube.remote.ui.shared.mixin.ItemContextMenuMixin
import io.casey.musikcube.remote.ui.shared.mixin.ViewModelMixin import io.casey.musikcube.remote.ui.shared.mixin.ViewModelMixin
open class BaseDialogFragment: DialogFragment(), ViewModel.Provider { open class BaseDialogFragment: DialogFragment(), ViewModel.Provider {

View File

@ -8,9 +8,10 @@ import io.casey.musikcube.remote.service.websocket.model.IDataProvider
import io.casey.musikcube.remote.service.websocket.model.ITrack import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.subscribeBy
class TrackListSlidingWindow(private val recyclerView: FastScrollRecyclerView, class TrackListSlidingWindow(private val recyclerView: FastScrollRecyclerView,
val dataProvider: IDataProvider, private val dataProvider: IDataProvider,
private val queryFactory: TrackListSlidingWindow.QueryFactory) private val queryFactory: TrackListSlidingWindow.QueryFactory)
{ {
private var scrollState = RecyclerView.SCROLL_STATE_IDLE private var scrollState = RecyclerView.SCROLL_STATE_IDLE
@ -29,9 +30,7 @@ class TrackListSlidingWindow(private val recyclerView: FastScrollRecyclerView,
} }
private val cache = object : LinkedHashMap<Int, CacheEntry>() { private val cache = object : LinkedHashMap<Int, CacheEntry>() {
override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Int, CacheEntry>): Boolean { override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Int, CacheEntry>): Boolean = size >= MAX_SIZE
return size >= MAX_SIZE
}
} }
interface OnMetadataLoadedListener { interface OnMetadataLoadedListener {
@ -74,20 +73,20 @@ class TrackListSlidingWindow(private val recyclerView: FastScrollRecyclerView,
val countObservable = queryFactory.count() val countObservable = queryFactory.count()
if (countObservable != null) { if (countObservable != null) {
countObservable.subscribe( countObservable.subscribeBy(
{ newCount -> onNext = { newCount ->
count = newCount count = newCount
if (initialPosition != -1) { if (initialPosition != -1) {
recyclerView.scrollToPosition(initialPosition) recyclerView.scrollToPosition(initialPosition)
initialPosition = -1 initialPosition = -1
} }
loadedListener?.onReloaded(count) loadedListener?.onReloaded(count)
}, },
{ _ -> onError = { _ ->
Log.d("TrackListSlidingWindow", "message send failed, likely canceled") Log.d("TrackListSlidingWindow", "message send failed, likely canceled")
}) })
queried = true queried = true
} }
@ -127,7 +126,7 @@ class TrackListSlidingWindow(private val recyclerView: FastScrollRecyclerView,
fun getTrack(index: Int): ITrack? { fun getTrack(index: Int): ITrack? {
val track = cache[index] val track = cache[index]
if (track?.dirty ?: true) { if (track == null || track.dirty) {
if (scrollState == RecyclerView.SCROLL_STATE_IDLE) { if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
getPageAround(index) getPageAround(index)
} }
@ -167,8 +166,8 @@ class TrackListSlidingWindow(private val recyclerView: FastScrollRecyclerView,
queryOffset = offset queryOffset = offset
queryLimit = limit queryLimit = limit
pageRequest.subscribe( pageRequest.subscribeBy(
{ response -> onNext = { response ->
queryLimit = -1 queryLimit = -1
queryOffset = queryLimit queryOffset = queryLimit
@ -183,23 +182,20 @@ class TrackListSlidingWindow(private val recyclerView: FastScrollRecyclerView,
notifyAdapterChanged() notifyAdapterChanged()
notifyMetadataLoaded(offset, i) notifyMetadataLoaded(offset, i)
}, },
{ _ -> onError = { _ ->
Log.d("TrackListSlidingWindow", "message send failed, likely canceled") Log.d("TrackListSlidingWindow", "message send failed, likely canceled")
}) })
} }
} }
private fun notifyAdapterChanged() { private fun notifyAdapterChanged() =
recyclerView.adapter.notifyDataSetChanged() recyclerView.adapter.notifyDataSetChanged()
}
private fun notifyMetadataLoaded(offset: Int, count: Int) { private fun notifyMetadataLoaded(offset: Int, count: Int) =
loadedListener?.onMetadataLoaded(offset, count) loadedListener?.onMetadataLoaded(offset, count)
}
private fun scrolling(): Boolean { private fun scrolling(): Boolean =
return scrollState != RecyclerView.SCROLL_STATE_IDLE || fastScrollerActive scrollState != RecyclerView.SCROLL_STATE_IDLE || fastScrollerActive
}
private val recyclerViewScrollListener = object : RecyclerView.OnScrollListener() { private val recyclerViewScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) { override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {

View File

@ -5,9 +5,11 @@ import android.util.LruCache
import io.casey.musikcube.remote.Application import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.service.websocket.model.IAlbum import io.casey.musikcube.remote.service.websocket.model.IAlbum
import io.casey.musikcube.remote.service.websocket.model.ITrack import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.util.Strings
import io.casey.musikcube.remote.ui.settings.constants.Prefs import io.casey.musikcube.remote.ui.settings.constants.Prefs
import okhttp3.* import io.casey.musikcube.remote.util.Strings
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import java.io.IOException import java.io.IOException

View File

@ -9,14 +9,14 @@ import android.widget.FrameLayout
import android.widget.TextView import android.widget.TextView
import io.casey.musikcube.remote.Application import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.R import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.service.websocket.model.IDataProvider
import io.casey.musikcube.remote.injection.DaggerViewComponent import io.casey.musikcube.remote.injection.DaggerViewComponent
import io.casey.musikcube.remote.injection.DataModule import io.casey.musikcube.remote.injection.DataModule
import io.casey.musikcube.remote.service.playback.PlaybackServiceFactory import io.casey.musikcube.remote.service.playback.PlaybackServiceFactory
import io.casey.musikcube.remote.service.playback.impl.streaming.StreamingPlaybackService import io.casey.musikcube.remote.service.playback.impl.streaming.StreamingPlaybackService
import io.casey.musikcube.remote.ui.tracks.activity.TrackListActivity
import io.casey.musikcube.remote.ui.shared.extension.setVisible
import io.casey.musikcube.remote.service.websocket.WebSocketService import io.casey.musikcube.remote.service.websocket.WebSocketService
import io.casey.musikcube.remote.service.websocket.model.IDataProvider
import io.casey.musikcube.remote.ui.shared.extension.setVisible
import io.casey.musikcube.remote.ui.tracks.activity.TrackListActivity
import javax.inject.Inject import javax.inject.Inject
import io.casey.musikcube.remote.service.websocket.WebSocketService.State as WebSocketState import io.casey.musikcube.remote.service.websocket.WebSocketService.State as WebSocketState

View File

@ -6,7 +6,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import io.casey.musikcube.remote.R import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.service.playback.IPlaybackService
import io.casey.musikcube.remote.service.websocket.model.ITrack import io.casey.musikcube.remote.service.websocket.model.ITrack
import io.casey.musikcube.remote.ui.shared.extension.fallback import io.casey.musikcube.remote.ui.shared.extension.fallback
import io.casey.musikcube.remote.ui.shared.extension.getColorCompat import io.casey.musikcube.remote.ui.shared.extension.getColorCompat

View File

@ -1,28 +1,30 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:selectableItemBackground" android:background="?android:selectableItemBackground"
android:minHeight="52dp" android:minHeight="52dp"
android:padding="8dp" > android:padding="8dp">
<TextView <TextView
android:id="@+id/track_num" android:id="@+id/track_num"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginLeft="8dp" android:layout_marginStart="8dp"
android:layout_marginRight="12dp" android:layout_marginEnd="12dp"
android:layout_alignParentLeft="true" android:layout_alignParentStart="true"
android:textColor="@color/theme_disabled_foreground" android:textColor="@color/theme_disabled_foreground"
android:text="1" /> tools:text="1" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_toRightOf="@id/track_num" android:layout_centerVertical="true"
android:layout_toEndOf="@id/track_num"
android:layout_toStartOf="@+id/action"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
@ -34,7 +36,7 @@
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:textColor="@color/theme_foreground" android:textColor="@color/theme_foreground"
android:text="title"/> tools:text="title"/>
<TextView <TextView
android:textSize="12dp" android:textSize="12dp"
@ -46,9 +48,20 @@
android:maxLines="1" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
android:textColor="@color/theme_disabled_foreground" android:textColor="@color/theme_disabled_foreground"
android:text="subtitle"/> tools:text="subtitle"/>
</LinearLayout> </LinearLayout>
<ImageView
android:id="@+id/action"
android:background="?android:selectableItemBackground"
android:src="@drawable/ic_overflow"
android:scaleType="center"
android:padding="4dp"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_width="36dp"
android:layout_height="36dp"
android:gravity="center"/>
</RelativeLayout> </RelativeLayout>