Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

Commit

Permalink
feat: add saved playlist tab
Browse files Browse the repository at this point in the history
  • Loading branch information
SuhasDissa committed Oct 30, 2023
1 parent 846ad1b commit 5775dfc
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 9 deletions.
5 changes: 5 additions & 0 deletions app/src/main/java/app/suhasdissa/vibeyou/AppContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import app.suhasdissa.vibeyou.backend.repository.AuthRepository
import app.suhasdissa.vibeyou.backend.repository.AuthRepositoryImpl
import app.suhasdissa.vibeyou.backend.repository.LocalMusicRepository
import app.suhasdissa.vibeyou.backend.repository.PipedMusicRepository
import app.suhasdissa.vibeyou.backend.repository.PlaylistRepository
import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepository
import app.suhasdissa.vibeyou.backend.repository.SongDatabaseRepositoryImpl
import com.google.common.util.concurrent.ListenableFuture
Expand All @@ -16,6 +17,7 @@ interface AppContainer {
val songDatabaseRepository: SongDatabaseRepository
val pipedMusicRepository: PipedMusicRepository
val localMusicRepository: LocalMusicRepository
val playlistRepository: PlaylistRepository
val authRepository: AuthRepository
val controllerFuture: ListenableFuture<MediaController>
val contentResolver: ContentResolver
Expand All @@ -35,6 +37,9 @@ class DefaultAppContainer(
override val localMusicRepository: LocalMusicRepository by lazy {
LocalMusicRepository(contentResolver, database.searchDao())
}
override val playlistRepository: PlaylistRepository by lazy {
PlaylistRepository(database.playlistDao(), database.songsDao())
}
override val authRepository: AuthRepository by lazy {
AuthRepositoryImpl()
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/app/suhasdissa/vibeyou/Destination.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ sealed class Destination(val route: String) {
object AppearanceSettings : Destination("appearance_settings")
object Playlists : Destination("playlist_screen")
object LocalPlaylists : Destination("local_playlist_screen")
object SavedPlaylists : Destination("saved_playlist_screen")
object Artist : Destination("artist")
object LocalArtist : Destination("local_artist")
}
9 changes: 9 additions & 0 deletions app/src/main/java/app/suhasdissa/vibeyou/NavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.navigation.compose.composable
import app.suhasdissa.vibeyou.backend.viewmodel.LocalSearchViewModel
import app.suhasdissa.vibeyou.backend.viewmodel.LocalSongViewModel
import app.suhasdissa.vibeyou.backend.viewmodel.PipedSearchViewModel
import app.suhasdissa.vibeyou.backend.viewmodel.PlaylistViewModel
import app.suhasdissa.vibeyou.ui.screens.home.HomeScreen
import app.suhasdissa.vibeyou.ui.screens.search.AlbumScreen
import app.suhasdissa.vibeyou.ui.screens.search.ArtistScreen
Expand Down Expand Up @@ -93,6 +94,14 @@ fun AppNavHost(navHostController: NavHostController) {
}
}

composable(Destination.SavedPlaylists.route) {
CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
val searchViewModel: PlaylistViewModel =
viewModel(factory = PlaylistViewModel.Factory)
AlbumScreen(searchViewModel.albumInfoState)
}
}

composable(route = Destination.Artist.route) {
CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
val searchViewModel: PipedSearchViewModel =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.room.Transaction
import app.suhasdissa.vibeyou.backend.database.entities.PlaylistEntity
import app.suhasdissa.vibeyou.backend.database.entities.PlaylistWithSongs
import app.suhasdissa.vibeyou.backend.database.entities.SongPlaylistMap
import kotlinx.coroutines.flow.Flow

@Dao
interface PlaylistDao {
Expand All @@ -18,9 +19,12 @@ interface PlaylistDao {
@Insert(entity = SongPlaylistMap::class, onConflict = OnConflictStrategy.REPLACE)
fun addPlaylistMaps(maps: List<SongPlaylistMap>)

@Transaction
@Query("SELECT * from playlists")
fun getAllPlaylists(): List<PlaylistWithSongs>
fun getAllPlaylists(): Flow<List<PlaylistEntity>>

@Transaction
@Query("SELECT * from playlists WHERE id=:id")
fun getPlaylist(id: String): PlaylistWithSongs

@Delete(entity = PlaylistEntity::class)
fun removePlaylist(playlist: PlaylistEntity)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package app.suhasdissa.vibeyou.backend.repository

import app.suhasdissa.vibeyou.backend.data.Album
import app.suhasdissa.vibeyou.backend.data.Song
import app.suhasdissa.vibeyou.backend.database.dao.PlaylistDao
import app.suhasdissa.vibeyou.backend.database.dao.SongsDao
import app.suhasdissa.vibeyou.backend.database.entities.PlaylistEntity
import app.suhasdissa.vibeyou.backend.database.entities.PlaylistWithSongs
import app.suhasdissa.vibeyou.backend.database.entities.SongPlaylistMap
import app.suhasdissa.vibeyou.utils.asPlaylistEntity
import app.suhasdissa.vibeyou.utils.asSongEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext

class PlaylistRepository(private val playlistDao: PlaylistDao, private val songsDao: SongsDao) {
private fun createNew(album: Album) {
playlistDao.addPlaylist(
album.asPlaylistEntity
)
}

private fun addSongsToPlaylist(playlistId: String, songs: List<Song>) {
songsDao.addSongs(songs.map { it.asSongEntity })
val songMap = songs.map { SongPlaylistMap(playlistId, it.id) }
playlistDao.addPlaylistMaps(songMap)
}

suspend fun newPlaylistWithSongs(album: Album, songs: List<Song>) =
withContext(Dispatchers.IO) {
createNew(album)
addSongsToPlaylist(album.id, songs)
}

fun getPlaylists(): Flow<List<PlaylistEntity>> = playlistDao.getAllPlaylists()

suspend fun getPlaylist(id: String): PlaylistWithSongs =
withContext(Dispatchers.IO) { return@withContext playlistDao.getPlaylist(id) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package app.suhasdissa.vibeyou.backend.viewmodel

import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import app.suhasdissa.vibeyou.MellowMusicApplication
import app.suhasdissa.vibeyou.backend.data.Album
import app.suhasdissa.vibeyou.backend.data.Song
import app.suhasdissa.vibeyou.backend.repository.PlaylistRepository
import app.suhasdissa.vibeyou.backend.viewmodel.state.AlbumInfoState
import app.suhasdissa.vibeyou.utils.asSong
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch

class PlaylistViewModel(private val playlistRepository: PlaylistRepository) : ViewModel() {
var albumInfoState: AlbumInfoState by mutableStateOf(AlbumInfoState.Loading)
private set

var albums = playlistRepository.getPlaylists().stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000L),
initialValue = listOf()
)

fun getPlaylistInfo(playlist: Album) {
viewModelScope.launch {
albumInfoState = AlbumInfoState.Loading
albumInfoState = try {
Log.e("PlaylistViewModel", "Getting info")
val info = playlistRepository.getPlaylist(playlist.id)
AlbumInfoState.Success(
playlist,
info.songs.map { it.asSong }
)
} catch (e: Exception) {
Log.e("Playlist Info", e.toString())
AlbumInfoState.Error
}
}
}

fun newPlaylistWithSongs(album: Album, songs: List<Song>) {
viewModelScope.launch {
playlistRepository.newPlaylistWithSongs(album, songs)
}
}

companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as MellowMusicApplication)
PlaylistViewModel(
application.container.playlistRepository
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDrawerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand All @@ -38,6 +39,7 @@ import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
Expand Down Expand Up @@ -137,6 +139,7 @@ fun HomeScreen(
}
})
}) {
val viewModelStoreOwner = LocalViewModelStoreOwner.current!!
NavHost(
navController,
startDestination = Destination.LocalMusic.route,
Expand All @@ -146,9 +149,11 @@ fun HomeScreen(
exitTransition = { ExitTransition.None }
) {
composable(Destination.PipedMusic.route) {
MusicScreen()
LaunchedEffect(Unit) {
currentDestination = Destination.PipedMusic
CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
MusicScreen(onNavigate)
LaunchedEffect(Unit) {
currentDestination = Destination.PipedMusic
}
}
}
composable(Destination.LocalMusic.route) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,29 @@ import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import app.suhasdissa.vibeyou.Destination
import app.suhasdissa.vibeyou.R
import app.suhasdissa.vibeyou.backend.viewmodel.PlaylistViewModel
import app.suhasdissa.vibeyou.ui.components.AlbumList
import app.suhasdissa.vibeyou.ui.screens.songs.SongsScreen
import app.suhasdissa.vibeyou.utils.asAlbum
import kotlinx.coroutines.launch

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MusicScreen() {
val pagerState = rememberPagerState { 2 }
fun MusicScreen(
onNavigate: (Destination) -> Unit,
playlistViewModel: PlaylistViewModel = viewModel(factory = PlaylistViewModel.Factory)
) {
val pagerState = rememberPagerState { 3 }
val scope = rememberCoroutineScope()
Column {
TabRow(selectedTabIndex = pagerState.currentPage, Modifier.fillMaxWidth()) {
Expand Down Expand Up @@ -58,14 +68,33 @@ fun MusicScreen() {
style = MaterialTheme.typography.titleMedium
)
}
Tab(selected = (pagerState.currentPage == 2), onClick = {
view.playSoundEffect(SoundEffectConstants.CLICK)
scope.launch {
pagerState.animateScrollToPage(
2
)
}
}) {
Text(
stringResource(R.string.playlists),
Modifier.padding(10.dp),
style = MaterialTheme.typography.titleMedium
)
}
}
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize()
) { index ->
val albums by playlistViewModel.albums.collectAsState()
when (index) {
0 -> SongsScreen(showFavourites = false)
1 -> SongsScreen(showFavourites = true)
2 -> AlbumList(items = albums.map { it.asAlbum }, onClickCard = {
playlistViewModel.getPlaylistInfo(it)
onNavigate(Destination.SavedPlaylists)
}, onLongPress = {})
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import app.suhasdissa.vibeyou.R
import app.suhasdissa.vibeyou.backend.data.Song
import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel
import app.suhasdissa.vibeyou.backend.viewmodel.PlaylistViewModel
import app.suhasdissa.vibeyou.backend.viewmodel.state.AlbumInfoState
import app.suhasdissa.vibeyou.ui.components.IllustratedMessageScreen
import app.suhasdissa.vibeyou.ui.components.LoadingScreen
Expand All @@ -51,7 +52,8 @@ import coil.compose.AsyncImage
@Composable
fun AlbumScreen(
state: AlbumInfoState,
playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory)
playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory),
playlistViewModel: PlaylistViewModel = viewModel(factory = PlaylistViewModel.Factory)
) {
MiniPlayerScaffold {
when (state) {
Expand Down Expand Up @@ -131,7 +133,10 @@ fun AlbumScreen(
FilledTonalButton(
modifier = Modifier.fillMaxWidth(),
onClick = {
playerViewModel.saveSongs(state.songs)
playlistViewModel.newPlaylistWithSongs(
state.album,
state.songs
)
Toast.makeText(
context,
context.getString(
Expand Down
18 changes: 18 additions & 0 deletions app/src/main/java/app/suhasdissa/vibeyou/utils/Mappers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.media3.common.MediaMetadata
import app.suhasdissa.vibeyou.backend.data.Album
import app.suhasdissa.vibeyou.backend.data.Artist
import app.suhasdissa.vibeyou.backend.data.Song
import app.suhasdissa.vibeyou.backend.database.entities.PlaylistEntity
import app.suhasdissa.vibeyou.backend.database.entities.SongEntity
import app.suhasdissa.vibeyou.backend.models.PipedSongResponse
import app.suhasdissa.vibeyou.backend.models.playlists.Playlist
Expand Down Expand Up @@ -61,6 +62,23 @@ val Playlist.asAlbum: Album
thumbnailUri = thumbnail.toUri()
)

val Album.asPlaylistEntity: PlaylistEntity
get() = PlaylistEntity(
id = id,
title = title,
type = type,
subTitle = artistsText,
thumbnailUrl = thumbnailUri.toString()
)
val PlaylistEntity.asAlbum: Album
get() = Album(
id = id,
title = title,
thumbnailUri = thumbnailUrl?.toUri(),
artistsText = subTitle ?: "",
isLocal = true,
type = type
)
val app.suhasdissa.vibeyou.backend.models.artists.Artist.asArtist: Artist
get() = Artist(
id = artistId,
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,5 @@
<string name="added_all_the_songs_to_the_library">Added all the songs to the library</string>
<string name="add_all_songs_to_the_library">Add all songs to the library</string>
<string name="clear_queue">Clear Queue</string>
<string name="playlists">Playlists</string>
</resources>

0 comments on commit 5775dfc

Please sign in to comment.