mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
repo: add play variant
This commit is contained in:
parent
e465892013
commit
349c961f1a
6 changed files with 956 additions and 0 deletions
|
|
@ -37,6 +37,11 @@ android {
|
||||||
dimension = "default"
|
dimension = "default"
|
||||||
applicationId "com.cappielloantonio.notquitemy.tempo"
|
applicationId "com.cappielloantonio.notquitemy.tempo"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
play {
|
||||||
|
dimension = "default"
|
||||||
|
applicationId "com.cappielloantonio.play.tempo"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
@ -90,6 +95,7 @@ dependencies {
|
||||||
implementation 'androidx.media3:media3-ui:1.4.0'
|
implementation 'androidx.media3:media3-ui:1.4.0'
|
||||||
implementation 'androidx.media3:media3-exoplayer-hls:1.4.0'
|
implementation 'androidx.media3:media3-exoplayer-hls:1.4.0'
|
||||||
tempoImplementation 'androidx.media3:media3-cast:1.4.0'
|
tempoImplementation 'androidx.media3:media3-cast:1.4.0'
|
||||||
|
playImplementation 'androidx.media3:media3-cast:1.4.0'
|
||||||
|
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||||
annotationProcessor 'androidx.room:room-compiler:2.6.1'
|
annotationProcessor 'androidx.room:room-compiler:2.6.1'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,497 @@
|
||||||
|
package com.cappielloantonio.tempo.service
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.MediaItem.SubtitleConfiguration
|
||||||
|
import androidx.media3.common.MediaMetadata
|
||||||
|
import androidx.media3.session.LibraryResult
|
||||||
|
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences.getServerId
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.util.concurrent.Futures
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
|
|
||||||
|
object MediaBrowserTree {
|
||||||
|
|
||||||
|
private lateinit var automotiveRepository: AutomotiveRepository
|
||||||
|
|
||||||
|
private var treeNodes: MutableMap<String, MediaItemNode> = mutableMapOf()
|
||||||
|
|
||||||
|
private var isInitialized = false
|
||||||
|
|
||||||
|
// Root
|
||||||
|
private const val ROOT_ID = "[rootID]"
|
||||||
|
|
||||||
|
// First level
|
||||||
|
private const val HOME_ID = "[homeID]"
|
||||||
|
private const val LIBRARY_ID = "[libraryID]"
|
||||||
|
private const val OTHER_ID = "[otherID]"
|
||||||
|
|
||||||
|
// Second level HOME_ID
|
||||||
|
private const val MOST_PLAYED_ID = "[mostPlayedID]"
|
||||||
|
private const val LAST_PLAYED_ID = "[lastPlayedID]"
|
||||||
|
private const val RECENTLY_ADDED_ID = "[recentlyAddedID]"
|
||||||
|
private const val RECENT_SONGS_ID = "[recentSongsID]"
|
||||||
|
private const val MADE_FOR_YOU_ID = "[madeForYouID]"
|
||||||
|
private const val STARRED_TRACKS_ID = "[starredTracksID]"
|
||||||
|
private const val STARRED_ALBUMS_ID = "[starredAlbumsID]"
|
||||||
|
private const val STARRED_ARTISTS_ID = "[starredArtistsID]"
|
||||||
|
private const val RANDOM_ID = "[randomID]"
|
||||||
|
|
||||||
|
// Second level LIBRARY_ID
|
||||||
|
private const val FOLDER_ID = "[folderID]"
|
||||||
|
private const val INDEX_ID = "[indexID]"
|
||||||
|
private const val DIRECTORY_ID = "[directoryID]"
|
||||||
|
private const val PLAYLIST_ID = "[playlistID]"
|
||||||
|
|
||||||
|
// Second level OTHER_ID
|
||||||
|
private const val PODCAST_ID = "[podcastID]"
|
||||||
|
private const val RADIO_ID = "[radioID]"
|
||||||
|
|
||||||
|
private const val ALBUM_ID = "[albumID]"
|
||||||
|
private const val ARTIST_ID = "[artistID]"
|
||||||
|
|
||||||
|
private class MediaItemNode(val item: MediaItem) {
|
||||||
|
private val children: MutableList<MediaItem> = ArrayList()
|
||||||
|
|
||||||
|
fun addChild(childID: String) {
|
||||||
|
this.children.add(treeNodes[childID]!!.item)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getChildren(): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
|
val listenableFuture = SettableFuture.create<LibraryResult<ImmutableList<MediaItem>>>()
|
||||||
|
val libraryResult = LibraryResult.ofItemList(children, null)
|
||||||
|
|
||||||
|
listenableFuture.set(libraryResult)
|
||||||
|
|
||||||
|
return listenableFuture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildMediaItem(
|
||||||
|
title: String,
|
||||||
|
mediaId: String,
|
||||||
|
isPlayable: Boolean,
|
||||||
|
isBrowsable: Boolean,
|
||||||
|
mediaType: @MediaMetadata.MediaType Int,
|
||||||
|
subtitleConfigurations: List<SubtitleConfiguration> = mutableListOf(),
|
||||||
|
album: String? = null,
|
||||||
|
artist: String? = null,
|
||||||
|
genre: String? = null,
|
||||||
|
sourceUri: Uri? = null,
|
||||||
|
imageUri: Uri? = null
|
||||||
|
): MediaItem {
|
||||||
|
val metadata =
|
||||||
|
MediaMetadata.Builder()
|
||||||
|
.setAlbumTitle(album)
|
||||||
|
.setTitle(title)
|
||||||
|
.setArtist(artist)
|
||||||
|
.setGenre(genre)
|
||||||
|
.setIsBrowsable(isBrowsable)
|
||||||
|
.setIsPlayable(isPlayable)
|
||||||
|
.setArtworkUri(imageUri)
|
||||||
|
.setMediaType(mediaType)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return MediaItem.Builder()
|
||||||
|
.setMediaId(mediaId)
|
||||||
|
.setSubtitleConfigurations(subtitleConfigurations)
|
||||||
|
.setMediaMetadata(metadata)
|
||||||
|
.setUri(sourceUri)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initialize(automotiveRepository: AutomotiveRepository) {
|
||||||
|
this.automotiveRepository = automotiveRepository
|
||||||
|
|
||||||
|
if (isInitialized) return
|
||||||
|
|
||||||
|
isInitialized = true
|
||||||
|
|
||||||
|
// Root level
|
||||||
|
|
||||||
|
treeNodes[ROOT_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Root Folder",
|
||||||
|
mediaId = ROOT_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// First level
|
||||||
|
|
||||||
|
treeNodes[HOME_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Home",
|
||||||
|
mediaId = HOME_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[LIBRARY_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Library",
|
||||||
|
mediaId = LIBRARY_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[OTHER_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Other",
|
||||||
|
mediaId = OTHER_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[ROOT_ID]!!.addChild(HOME_ID)
|
||||||
|
treeNodes[ROOT_ID]!!.addChild(LIBRARY_ID)
|
||||||
|
treeNodes[ROOT_ID]!!.addChild(OTHER_ID)
|
||||||
|
|
||||||
|
// Second level HOME_ID
|
||||||
|
|
||||||
|
treeNodes[MOST_PLAYED_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Most played",
|
||||||
|
mediaId = MOST_PLAYED_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[LAST_PLAYED_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Last played",
|
||||||
|
mediaId = LAST_PLAYED_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[RECENTLY_ADDED_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Recently added",
|
||||||
|
mediaId = RECENTLY_ADDED_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[RECENT_SONGS_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Recent songs",
|
||||||
|
mediaId = RECENT_SONGS_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[MADE_FOR_YOU_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Made for you",
|
||||||
|
mediaId = MADE_FOR_YOU_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[STARRED_TRACKS_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Starred tracks",
|
||||||
|
mediaId = STARRED_TRACKS_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[STARRED_ALBUMS_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Starred albums",
|
||||||
|
mediaId = STARRED_ALBUMS_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[STARRED_ARTISTS_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Starred artists",
|
||||||
|
mediaId = STARRED_ARTISTS_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[RANDOM_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Random",
|
||||||
|
mediaId = RANDOM_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[HOME_ID]!!.addChild(MOST_PLAYED_ID)
|
||||||
|
treeNodes[HOME_ID]!!.addChild(LAST_PLAYED_ID)
|
||||||
|
treeNodes[HOME_ID]!!.addChild(RECENTLY_ADDED_ID)
|
||||||
|
treeNodes[HOME_ID]!!.addChild(RECENT_SONGS_ID)
|
||||||
|
treeNodes[HOME_ID]!!.addChild(MADE_FOR_YOU_ID)
|
||||||
|
treeNodes[HOME_ID]!!.addChild(STARRED_TRACKS_ID)
|
||||||
|
treeNodes[HOME_ID]!!.addChild(STARRED_ALBUMS_ID)
|
||||||
|
treeNodes[HOME_ID]!!.addChild(STARRED_ARTISTS_ID)
|
||||||
|
treeNodes[HOME_ID]!!.addChild(RANDOM_ID)
|
||||||
|
|
||||||
|
// Second level LIBRARY_ID
|
||||||
|
|
||||||
|
treeNodes[FOLDER_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Folders",
|
||||||
|
mediaId = FOLDER_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[PLAYLIST_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Playlists",
|
||||||
|
mediaId = PLAYLIST_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[LIBRARY_ID]!!.addChild(FOLDER_ID)
|
||||||
|
treeNodes[LIBRARY_ID]!!.addChild(PLAYLIST_ID)
|
||||||
|
|
||||||
|
// Second level OTHER_ID
|
||||||
|
|
||||||
|
treeNodes[PODCAST_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Podcasts",
|
||||||
|
mediaId = PODCAST_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[RADIO_ID] =
|
||||||
|
MediaItemNode(
|
||||||
|
buildMediaItem(
|
||||||
|
title = "Radio stations",
|
||||||
|
mediaId = RADIO_ID,
|
||||||
|
isPlayable = false,
|
||||||
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_RADIO_STATIONS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
treeNodes[OTHER_ID]!!.addChild(PODCAST_ID)
|
||||||
|
treeNodes[OTHER_ID]!!.addChild(RADIO_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRootItem(): MediaItem {
|
||||||
|
return treeNodes[ROOT_ID]!!.item
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getChildren(
|
||||||
|
id: String
|
||||||
|
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
|
return when (id) {
|
||||||
|
ROOT_ID -> treeNodes[ROOT_ID]?.getChildren()!!
|
||||||
|
HOME_ID -> treeNodes[HOME_ID]?.getChildren()!!
|
||||||
|
LIBRARY_ID -> treeNodes[LIBRARY_ID]?.getChildren()!!
|
||||||
|
OTHER_ID -> treeNodes[OTHER_ID]?.getChildren()!!
|
||||||
|
|
||||||
|
MOST_PLAYED_ID -> automotiveRepository.getAlbums(id, "frequent", 100)
|
||||||
|
LAST_PLAYED_ID -> automotiveRepository.getAlbums(id, "recent", 100)
|
||||||
|
RECENTLY_ADDED_ID -> automotiveRepository.getAlbums(id, "newest", 100)
|
||||||
|
RECENT_SONGS_ID -> automotiveRepository.getRecentlyPlayedSongs(getServerId(),100)
|
||||||
|
MADE_FOR_YOU_ID -> automotiveRepository.getStarredArtists(id)
|
||||||
|
STARRED_TRACKS_ID -> automotiveRepository.starredSongs
|
||||||
|
STARRED_ALBUMS_ID -> automotiveRepository.getStarredAlbums(id)
|
||||||
|
STARRED_ARTISTS_ID -> automotiveRepository.getStarredArtists(id)
|
||||||
|
RANDOM_ID -> automotiveRepository.getRandomSongs(100)
|
||||||
|
FOLDER_ID -> automotiveRepository.getMusicFolders(id)
|
||||||
|
PLAYLIST_ID -> automotiveRepository.getPlaylists(id)
|
||||||
|
PODCAST_ID -> automotiveRepository.getNewestPodcastEpisodes(100)
|
||||||
|
RADIO_ID -> automotiveRepository.internetRadioStations
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
if (id.startsWith(MOST_PLAYED_ID)) {
|
||||||
|
return automotiveRepository.getAlbumTracks(
|
||||||
|
id.removePrefix(
|
||||||
|
MOST_PLAYED_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(LAST_PLAYED_ID)) {
|
||||||
|
return automotiveRepository.getAlbumTracks(
|
||||||
|
id.removePrefix(
|
||||||
|
LAST_PLAYED_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(RECENTLY_ADDED_ID)) {
|
||||||
|
return automotiveRepository.getAlbumTracks(
|
||||||
|
id.removePrefix(
|
||||||
|
RECENTLY_ADDED_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(MADE_FOR_YOU_ID)) {
|
||||||
|
return automotiveRepository.getMadeForYou(
|
||||||
|
id.removePrefix(
|
||||||
|
MADE_FOR_YOU_ID
|
||||||
|
),
|
||||||
|
20
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(STARRED_ALBUMS_ID)) {
|
||||||
|
return automotiveRepository.getAlbumTracks(
|
||||||
|
id.removePrefix(
|
||||||
|
STARRED_ALBUMS_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(STARRED_ARTISTS_ID)) {
|
||||||
|
return automotiveRepository.getArtistAlbum(
|
||||||
|
STARRED_ALBUMS_ID,
|
||||||
|
id.removePrefix(
|
||||||
|
STARRED_ARTISTS_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(FOLDER_ID)) {
|
||||||
|
return automotiveRepository.getIndexes(
|
||||||
|
INDEX_ID,
|
||||||
|
id.removePrefix(
|
||||||
|
FOLDER_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(INDEX_ID)) {
|
||||||
|
return automotiveRepository.getDirectories(
|
||||||
|
DIRECTORY_ID,
|
||||||
|
id.removePrefix(
|
||||||
|
INDEX_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(DIRECTORY_ID)) {
|
||||||
|
return automotiveRepository.getDirectories(
|
||||||
|
DIRECTORY_ID,
|
||||||
|
id.removePrefix(
|
||||||
|
DIRECTORY_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(PLAYLIST_ID)) {
|
||||||
|
return automotiveRepository.getPlaylistSongs(
|
||||||
|
id.removePrefix(
|
||||||
|
PLAYLIST_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(ALBUM_ID)) {
|
||||||
|
return automotiveRepository.getAlbumTracks(
|
||||||
|
id.removePrefix(
|
||||||
|
ALBUM_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(ARTIST_ID)) {
|
||||||
|
return automotiveRepository.getArtistAlbum(
|
||||||
|
ALBUM_ID,
|
||||||
|
id.removePrefix(
|
||||||
|
ARTIST_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/androidx/media/issues/156
|
||||||
|
fun getItems(mediaItems: List<MediaItem>): List<MediaItem> {
|
||||||
|
val updatedMediaItems = ArrayList<MediaItem>()
|
||||||
|
|
||||||
|
mediaItems.forEach {
|
||||||
|
if (it.localConfiguration?.uri != null) {
|
||||||
|
updatedMediaItems.add(it)
|
||||||
|
} else {
|
||||||
|
val sessionMediaItem = automotiveRepository.getSessionMediaItem(it.mediaId)
|
||||||
|
|
||||||
|
if (sessionMediaItem != null) {
|
||||||
|
var toAdd = automotiveRepository.getMetadatas(sessionMediaItem.timestamp!!)
|
||||||
|
val index = toAdd.indexOfFirst { mediaItem -> mediaItem.mediaId == it.mediaId }
|
||||||
|
|
||||||
|
toAdd = toAdd.subList(index, toAdd.size)
|
||||||
|
|
||||||
|
updatedMediaItems.addAll(toAdd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updatedMediaItems
|
||||||
|
}
|
||||||
|
|
||||||
|
fun search(query: String): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
|
return automotiveRepository.search(
|
||||||
|
query,
|
||||||
|
ALBUM_ID,
|
||||||
|
ARTIST_ID
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
package com.cappielloantonio.tempo.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.session.CommandButton
|
||||||
|
import androidx.media3.session.LibraryResult
|
||||||
|
import androidx.media3.session.MediaLibraryService
|
||||||
|
import androidx.media3.session.MediaSession
|
||||||
|
import androidx.media3.session.SessionCommand
|
||||||
|
import androidx.media3.session.SessionResult
|
||||||
|
import com.cappielloantonio.tempo.R
|
||||||
|
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.util.concurrent.Futures
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
|
||||||
|
open class MediaLibrarySessionCallback(
|
||||||
|
context: Context,
|
||||||
|
automotiveRepository: AutomotiveRepository
|
||||||
|
) :
|
||||||
|
MediaLibraryService.MediaLibrarySession.Callback {
|
||||||
|
|
||||||
|
init {
|
||||||
|
MediaBrowserTree.initialize(automotiveRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val customLayoutCommandButtons: List<CommandButton> = listOf(
|
||||||
|
CommandButton.Builder()
|
||||||
|
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
|
||||||
|
.setSessionCommand(
|
||||||
|
SessionCommand(
|
||||||
|
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY
|
||||||
|
)
|
||||||
|
).setIconResId(R.drawable.exo_icon_shuffle_off).build(),
|
||||||
|
|
||||||
|
CommandButton.Builder()
|
||||||
|
.setDisplayName(context.getString(R.string.exo_controls_shuffle_off_description))
|
||||||
|
.setSessionCommand(
|
||||||
|
SessionCommand(
|
||||||
|
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY
|
||||||
|
)
|
||||||
|
).setIconResId(R.drawable.exo_icon_shuffle_on).build()
|
||||||
|
)
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
val mediaNotificationSessionCommands =
|
||||||
|
MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
|
||||||
|
.also { builder ->
|
||||||
|
customLayoutCommandButtons.forEach { commandButton ->
|
||||||
|
commandButton.sessionCommand?.let { builder.add(it) }
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
override fun onConnect(
|
||||||
|
session: MediaSession, controller: MediaSession.ControllerInfo
|
||||||
|
): MediaSession.ConnectionResult {
|
||||||
|
if (session.isMediaNotificationController(controller) || session.isAutomotiveController(
|
||||||
|
controller
|
||||||
|
) || session.isAutoCompanionController(controller)
|
||||||
|
) {
|
||||||
|
val customLayout =
|
||||||
|
customLayoutCommandButtons[if (session.player.shuffleModeEnabled) 1 else 0]
|
||||||
|
|
||||||
|
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||||
|
.setAvailableSessionCommands(mediaNotificationSessionCommands)
|
||||||
|
.setCustomLayout(ImmutableList.of(customLayout)).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
override fun onCustomCommand(
|
||||||
|
session: MediaSession,
|
||||||
|
controller: MediaSession.ControllerInfo,
|
||||||
|
customCommand: SessionCommand,
|
||||||
|
args: Bundle
|
||||||
|
): ListenableFuture<SessionResult> {
|
||||||
|
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
|
||||||
|
session.player.shuffleModeEnabled = true
|
||||||
|
session.setCustomLayout(
|
||||||
|
session.mediaNotificationControllerInfo!!,
|
||||||
|
ImmutableList.of(customLayoutCommandButtons[1])
|
||||||
|
)
|
||||||
|
|
||||||
|
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
|
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
|
||||||
|
session.player.shuffleModeEnabled = false
|
||||||
|
session.setCustomLayout(
|
||||||
|
session.mediaNotificationControllerInfo!!,
|
||||||
|
ImmutableList.of(customLayoutCommandButtons[0])
|
||||||
|
)
|
||||||
|
|
||||||
|
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetLibraryRoot(
|
||||||
|
session: MediaLibraryService.MediaLibrarySession,
|
||||||
|
browser: MediaSession.ControllerInfo,
|
||||||
|
params: MediaLibraryService.LibraryParams?
|
||||||
|
): ListenableFuture<LibraryResult<MediaItem>> {
|
||||||
|
return Futures.immediateFuture(LibraryResult.ofItem(MediaBrowserTree.getRootItem(), params))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetChildren(
|
||||||
|
session: MediaLibraryService.MediaLibrarySession,
|
||||||
|
browser: MediaSession.ControllerInfo,
|
||||||
|
parentId: String,
|
||||||
|
page: Int,
|
||||||
|
pageSize: Int,
|
||||||
|
params: MediaLibraryService.LibraryParams?
|
||||||
|
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
|
return MediaBrowserTree.getChildren(parentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAddMediaItems(
|
||||||
|
mediaSession: MediaSession,
|
||||||
|
controller: MediaSession.ControllerInfo,
|
||||||
|
mediaItems: List<MediaItem>
|
||||||
|
): ListenableFuture<List<MediaItem>> {
|
||||||
|
return super.onAddMediaItems(
|
||||||
|
mediaSession,
|
||||||
|
controller,
|
||||||
|
MediaBrowserTree.getItems(mediaItems)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSearch(
|
||||||
|
session: MediaLibraryService.MediaLibrarySession,
|
||||||
|
browser: MediaSession.ControllerInfo,
|
||||||
|
query: String,
|
||||||
|
params: MediaLibraryService.LibraryParams?
|
||||||
|
): ListenableFuture<LibraryResult<Void>> {
|
||||||
|
session.notifySearchResultChanged(browser, query, 60, params)
|
||||||
|
return Futures.immediateFuture(LibraryResult.ofVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetSearchResult(
|
||||||
|
session: MediaLibraryService.MediaLibrarySession,
|
||||||
|
browser: MediaSession.ControllerInfo,
|
||||||
|
query: String,
|
||||||
|
page: Int,
|
||||||
|
pageSize: Int,
|
||||||
|
params: MediaLibraryService.LibraryParams?
|
||||||
|
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
|
return MediaBrowserTree.search(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
|
||||||
|
"android.media3.session.demo.SHUFFLE_ON"
|
||||||
|
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
|
||||||
|
"android.media3.session.demo.SHUFFLE_OFF"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
package com.cappielloantonio.tempo.service
|
||||||
|
|
||||||
|
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||||
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
import android.app.TaskStackBuilder
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.media3.cast.CastPlayer
|
||||||
|
import androidx.media3.cast.SessionAvailabilityListener
|
||||||
|
import androidx.media3.common.AudioAttributes
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.Tracks
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.exoplayer.DefaultLoadControl
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
|
import androidx.media3.session.MediaLibraryService
|
||||||
|
import androidx.media3.session.MediaSession.ControllerInfo
|
||||||
|
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
||||||
|
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||||
|
import com.cappielloantonio.tempo.util.Constants
|
||||||
|
import com.cappielloantonio.tempo.util.DownloadUtil
|
||||||
|
import com.cappielloantonio.tempo.util.Preferences
|
||||||
|
import com.cappielloantonio.tempo.util.ReplayGainUtil
|
||||||
|
import com.google.android.gms.cast.framework.CastContext
|
||||||
|
import com.google.android.gms.common.ConnectionResult
|
||||||
|
import com.google.android.gms.common.GoogleApiAvailability
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||||
|
private lateinit var automotiveRepository: AutomotiveRepository
|
||||||
|
private lateinit var player: ExoPlayer
|
||||||
|
private lateinit var castPlayer: CastPlayer
|
||||||
|
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
initializeRepository()
|
||||||
|
initializePlayer()
|
||||||
|
initializeCastPlayer()
|
||||||
|
initializeMediaLibrarySession()
|
||||||
|
initializePlayerListener()
|
||||||
|
|
||||||
|
setPlayer(
|
||||||
|
null,
|
||||||
|
if (this::castPlayer.isInitialized && castPlayer.isCastSessionAvailable) castPlayer else player
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
|
||||||
|
return mediaLibrarySession
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
|
val player = mediaLibrarySession.player
|
||||||
|
|
||||||
|
if (!player.playWhenReady || player.mediaItemCount == 0) {
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
releasePlayer()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeRepository() {
|
||||||
|
automotiveRepository = AutomotiveRepository()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializePlayer() {
|
||||||
|
player = ExoPlayer.Builder(this)
|
||||||
|
.setRenderersFactory(getRenderersFactory())
|
||||||
|
.setMediaSourceFactory(getMediaSourceFactory())
|
||||||
|
.setAudioAttributes(AudioAttributes.DEFAULT, true)
|
||||||
|
.setHandleAudioBecomingNoisy(true)
|
||||||
|
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||||
|
.setLoadControl(initializeLoadControl())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeCastPlayer() {
|
||||||
|
if (GoogleApiAvailability.getInstance()
|
||||||
|
.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS
|
||||||
|
) {
|
||||||
|
castPlayer = CastPlayer(CastContext.getSharedInstance(this))
|
||||||
|
castPlayer.setSessionAvailabilityListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeMediaLibrarySession() {
|
||||||
|
val sessionActivityPendingIntent =
|
||||||
|
TaskStackBuilder.create(this).run {
|
||||||
|
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
|
||||||
|
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaLibrarySession =
|
||||||
|
MediaLibrarySession.Builder(this, player, createLibrarySessionCallback())
|
||||||
|
.setSessionActivity(sessionActivityPendingIntent)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createLibrarySessionCallback(): MediaLibrarySession.Callback {
|
||||||
|
return MediaLibrarySessionCallback(this, automotiveRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializePlayerListener() {
|
||||||
|
player.addListener(object : Player.Listener {
|
||||||
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||||
|
if (mediaItem == null) return
|
||||||
|
|
||||||
|
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
|
||||||
|
MediaManager.setLastPlayedTimestamp(mediaItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTracksChanged(tracks: Tracks) {
|
||||||
|
ReplayGainUtil.setReplayGain(player, tracks)
|
||||||
|
MediaManager.scrobble(player.currentMediaItem, false)
|
||||||
|
|
||||||
|
if (player.currentMediaItemIndex + 1 == player.mediaItemCount)
|
||||||
|
MediaManager.continuousPlay(player.currentMediaItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
|
if (!isPlaying) {
|
||||||
|
MediaManager.setPlayingPausedTimestamp(
|
||||||
|
player.currentMediaItem,
|
||||||
|
player.currentPosition
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
MediaManager.scrobble(player.currentMediaItem, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||||
|
super.onPlaybackStateChanged(playbackState)
|
||||||
|
|
||||||
|
if (!player.hasNextMediaItem() &&
|
||||||
|
playbackState == Player.STATE_ENDED &&
|
||||||
|
player.mediaMetadata.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC
|
||||||
|
) {
|
||||||
|
MediaManager.scrobble(player.currentMediaItem, true)
|
||||||
|
MediaManager.saveChronology(player.currentMediaItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPositionDiscontinuity(
|
||||||
|
oldPosition: Player.PositionInfo,
|
||||||
|
newPosition: Player.PositionInfo,
|
||||||
|
reason: Int
|
||||||
|
) {
|
||||||
|
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||||||
|
|
||||||
|
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||||||
|
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||||
|
MediaManager.scrobble(oldPosition.mediaItem, true)
|
||||||
|
MediaManager.saveChronology(oldPosition.mediaItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
|
||||||
|
MediaManager.setLastPlayedTimestamp(newPosition.mediaItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeLoadControl(): DefaultLoadControl {
|
||||||
|
return DefaultLoadControl.Builder()
|
||||||
|
.setBufferDurationsMs(
|
||||||
|
(DefaultLoadControl.DEFAULT_MIN_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
|
||||||
|
(DefaultLoadControl.DEFAULT_MAX_BUFFER_MS * Preferences.getBufferingStrategy()).toInt(),
|
||||||
|
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
|
||||||
|
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setPlayer(oldPlayer: Player?, newPlayer: Player) {
|
||||||
|
if (oldPlayer === newPlayer) return
|
||||||
|
oldPlayer?.stop()
|
||||||
|
mediaLibrarySession.player = newPlayer
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun releasePlayer() {
|
||||||
|
if (this::castPlayer.isInitialized) castPlayer.setSessionAvailabilityListener(null)
|
||||||
|
if (this::castPlayer.isInitialized) castPlayer.release()
|
||||||
|
player.release()
|
||||||
|
mediaLibrarySession.release()
|
||||||
|
automotiveRepository.deleteMetadata()
|
||||||
|
clearListener()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
|
||||||
|
|
||||||
|
private fun getMediaSourceFactory() =
|
||||||
|
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
|
||||||
|
|
||||||
|
override fun onCastSessionAvailable() {
|
||||||
|
setPlayer(player, castPlayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCastSessionUnavailable() {
|
||||||
|
setPlayer(castPlayer, player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.cappielloantonio.tempo.ui.fragment;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.R;
|
||||||
|
import com.cappielloantonio.tempo.databinding.FragmentToolbarBinding;
|
||||||
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
|
import com.google.android.gms.cast.framework.CastButtonFactory;
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
public class ToolbarFragment extends Fragment {
|
||||||
|
private static final String TAG = "ToolbarFragment";
|
||||||
|
|
||||||
|
private FragmentToolbarBinding bind;
|
||||||
|
private MainActivity activity;
|
||||||
|
|
||||||
|
public ToolbarFragment() {
|
||||||
|
// Required empty public constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
inflater.inflate(R.menu.main_page_menu, menu);
|
||||||
|
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
activity = (MainActivity) getActivity();
|
||||||
|
|
||||||
|
bind = FragmentToolbarBinding.inflate(inflater, container, false);
|
||||||
|
View view = bind.getRoot();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.action_search) {
|
||||||
|
activity.navController.navigate(R.id.searchFragment);
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.action_settings) {
|
||||||
|
activity.navController.navigate(R.id.settingsFragment);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.cappielloantonio.tempo.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.google.android.gms.cast.framework.CastContext;
|
||||||
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
|
import com.google.android.gms.common.GoogleApiAvailability;
|
||||||
|
|
||||||
|
public class Flavors {
|
||||||
|
public static void initializeCastContext(Context context) {
|
||||||
|
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS)
|
||||||
|
CastContext.getSharedInstance(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue