diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/Database.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/Database.kt index 418b163dd6..1383a23e81 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/Database.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/Database.kt @@ -62,6 +62,7 @@ import it.fast4x.rimusic.models.SortedSongPlaylistMap import it.fast4x.rimusic.service.LOCAL_KEY_PREFIX import it.fast4x.rimusic.utils.isExplicit import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import org.intellij.lang.annotations.MagicConstant import kotlin.collections.sortedBy @@ -196,19 +197,19 @@ interface Database { @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY artistsText") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY artistsText") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByArtistAsc(): Flow> @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY artistsText DESC") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY artistsText DESC") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByArtistDesc(): Flow> @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY totalPlayTimeMs") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY totalPlayTimeMs") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByPlayTimeAsc(): Flow> @@ -224,7 +225,7 @@ interface Database { @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY totalPlayTimeMs DESC") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY totalPlayTimeMs DESC") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByPlayTimeDesc(): Flow> @@ -240,63 +241,67 @@ interface Database { @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY title COLLATE NOCASE ASC") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY title COLLATE NOCASE ASC") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByTitleAsc(): Flow> @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY title COLLATE NOCASE DESC") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY title COLLATE NOCASE DESC") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByTitleDesc(): Flow> @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY ROWID") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY ROWID") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByRowIdAsc(): Flow> @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY ROWID DESC") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY ROWID DESC") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByRowIdDesc(): Flow> @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY likedAt") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY likedAt") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByLikedAtAsc(): Flow> @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY likedAt DESC") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY likedAt DESC") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByLikedAtDesc(): Flow> @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT DISTINCT S.* FROM Song S LEFT JOIN Event E ON E.songId=S.id " + - "WHERE likedAt IS NOT NULL " + - "ORDER BY E.timestamp DESC") + @Query( + "SELECT DISTINCT S.* FROM Song S LEFT JOIN Event E ON E.songId=S.id " + + "WHERE likedAt IS NOT NULL AND likedAt > 0 " + + "ORDER BY E.timestamp DESC" + ) fun songsFavoritesByDatePlayedDesc(): Flow> @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT DISTINCT S.* FROM Song S LEFT JOIN Event E ON E.songId=S.id " + - "WHERE likedAt IS NOT NULL " + - "ORDER BY E.timestamp") + @Query( + "SELECT DISTINCT S.* FROM Song S LEFT JOIN Event E ON E.songId=S.id " + + "WHERE likedAt IS NOT NULL AND likedAt > 0 " + + "ORDER BY E.timestamp" + ) fun songsFavoritesByDatePlayedAsc(): Flow> @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY durationText") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY durationText") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByDurationAsc(): Flow> @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction - @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL ORDER BY durationText DESC") + @Query("SELECT * FROM Song WHERE likedAt IS NOT NULL AND likedAt > 0 ORDER BY durationText DESC") @RewriteQueriesToDropUnusedColumns fun songsFavoritesByDurationDesc(): Flow> @@ -337,6 +342,122 @@ interface Database { } } + @Transaction + @Query("SELECT DISTINCT Song.*, Format.contentLength, Album.title FROM Song " + + "LEFT JOIN SongAlbumMap ON Song.id = SongAlbumMap.songId " + + "LEFT JOIN Album ON Album.id = SongAlbumMap.albumId " + + "LEFT JOIN Format ON Format.songId = Song.id " + + "WHERE likedAt IS NOT NULL AND likedAt = -1 " + + " ORDER BY "+ + " CASE :sortOrder WHEN 'ASC' THEN totalPlayTimeMs END ASC," + + " CASE :sortOrder WHEN 'DESC' THEN totalPlayTimeMs END DESC") + @RewriteQueriesToDropUnusedColumns + fun songsDislikedByPlayTime(sortOrder: String): Flow> + + fun songsDislikedByRelativePlayTime(sortOrder: SortOrder): Flow> { + val songs = songsDislikedByPlayTime(sortOrder.toSQLString()) + songs.map { it } + return songs.map { + when(sortOrder) { + SortOrder.Ascending -> it.sortedBy { se -> se.relativePlayTime() } + else -> it.sortedByDescending { se -> se.relativePlayTime() } + } + } + } + + @Transaction + @Query("SELECT DISTINCT Song.*, Format.contentLength, Album.title FROM Song " + + "LEFT JOIN SongAlbumMap ON Song.id = SongAlbumMap.songId " + + "LEFT JOIN Album ON Album.id = SongAlbumMap.albumId " + + "LEFT JOIN Format ON Format.songId = Song.id " + + "WHERE likedAt IS NOT NULL AND likedAt = -1 " + + " ORDER BY "+ + " CASE :sortOrder WHEN 'ASC' THEN Song.title END COLLATE NOCASE ASC," + + " CASE :sortOrder WHEN 'DESC' THEN Song.title END COLLATE NOCASE DESC") + @RewriteQueriesToDropUnusedColumns + fun songsDislikedByTitle(sortOrder: String): Flow> + + @Transaction + @Query("SELECT DISTINCT Song.*, Format.contentLength, Album.title FROM Song " + + "LEFT JOIN SongAlbumMap ON Song.id = SongAlbumMap.songId " + + "LEFT JOIN Album ON Album.id = SongAlbumMap.albumId " + + "LEFT JOIN Format ON Format.songId = Song.id " + + "WHERE likedAt IS NOT NULL AND likedAt = -1 " + + " ORDER BY "+ + " CASE :sortOrder WHEN 'ASC' THEN Song.ROWID END ASC," + + " CASE :sortOrder WHEN 'DESC' THEN Song.ROWID END DESC") + @RewriteQueriesToDropUnusedColumns + fun songsDislikedByRowId(sortOrder: String): Flow> + + @Transaction + @Query("SELECT DISTINCT Song.*, Format.contentLength, Album.title FROM Song " + + "LEFT JOIN SongAlbumMap ON Song.id = SongAlbumMap.songId " + + "LEFT JOIN Album ON Album.id = SongAlbumMap.albumId " + + "LEFT JOIN Format ON Format.songId = Song.id " + + "LEFT JOIN Event E ON E.songId=Song.id " + + "WHERE likedAt IS NOT NULL AND likedAt = -1" + + " ORDER BY "+ + " CASE :sortOrder WHEN 'ASC' THEN E.timestamp END ASC," + + " CASE :sortOrder WHEN 'DESC' THEN E.timestamp END DESC") + fun songsDislikedByDatePlayed(sortOrder: String): Flow> + + @Transaction + @Query("SELECT DISTINCT Song.*, Format.contentLength, Album.title FROM Song " + + "LEFT JOIN SongAlbumMap ON Song.id = SongAlbumMap.songId " + + "LEFT JOIN Album ON Album.id = SongAlbumMap.albumId " + + "LEFT JOIN Format ON Format.songId = Song.id " + + "WHERE likedAt IS NOT NULL AND likedAt = -1" + + " ORDER BY " + + " CASE :sortOrder WHEN 'ASC' THEN artistsText END ASC," + + " CASE :sortOrder WHEN 'DESC' THEN artistsText END DESC") + @RewriteQueriesToDropUnusedColumns + fun songsDislikedByArtist(sortOrder: String): Flow> + + @Transaction + @Query("SELECT DISTINCT Song.*, Format.contentLength, Album.title FROM Song " + + "LEFT JOIN SongAlbumMap ON Song.id = SongAlbumMap.songId " + + "LEFT JOIN Album ON Album.id = SongAlbumMap.albumId " + + "LEFT JOIN Format ON Format.songId = Song.id " + + "WHERE likedAt IS NOT NULL AND likedAt = -1 "+ + "ORDER BY " + + " CASE :sortOrder WHEN 'ASC' THEN durationText END ASC," + + " CASE :sortOrder WHEN 'DESC' THEN durationText END DESC") + @RewriteQueriesToDropUnusedColumns + fun songsDislikedByDuration(sortOrder: String): Flow> + + @Transaction + @Query("SELECT DISTINCT Song.*, Format.contentLength, Album.title FROM Song " + + "LEFT JOIN SongAlbumMap ON Song.id = SongAlbumMap.songId " + + "LEFT JOIN Album ON Album.id = SongAlbumMap.albumId " + + "LEFT JOIN Format ON Format.songId = Song.id " + + "WHERE Song.likedAt = -1 AND Song.id NOT LIKE '$LOCAL_KEY_PREFIX%' " + + "ORDER BY " + + " CASE :sortOrder WHEN 'ASC' THEN Album.title END COLLATE NOCASE ASC," + + " CASE :sortOrder WHEN 'DESC' THEN Album.title END COLLATE NOCASE DESC") + @RewriteQueriesToDropUnusedColumns + fun songsDislikedByAlbumName(sortOrder: String): Flow> + + fun SortOrder.toSQLString(): String { + return when(this){ + SortOrder.Ascending -> "ASC" + SortOrder.Descending -> "DESC" + } + } + + fun songsDisliked(sortBy: SongSortBy, sortOrder: SortOrder): Flow> { + return when(sortBy){ + SongSortBy.PlayTime -> songsDislikedByPlayTime(sortOrder.toSQLString()) + SongSortBy.RelativePlayTime -> songsDislikedByRelativePlayTime(sortOrder) + SongSortBy.Title -> songsDislikedByTitle(sortOrder.toSQLString()) + SongSortBy.DateAdded -> songsDislikedByRowId(sortOrder.toSQLString()) + SongSortBy.DatePlayed -> songsDislikedByDatePlayed(sortOrder.toSQLString()) + SongSortBy.DateLiked -> flowOf(emptyList()) // TODO correct text, method + SongSortBy.Artist -> songsDislikedByArtist(sortOrder.toSQLString()) + SongSortBy.Duration -> songsDislikedByDuration(sortOrder.toSQLString()) + SongSortBy.AlbumName -> songsDislikedByAlbumName(sortOrder.toSQLString()) + } + } + @SuppressWarnings(RoomWarnings.QUERY_MISMATCH) @Transaction @Query("SELECT DISTINCT Song.*, Album.title as albumTitle FROM Song " + @@ -1346,7 +1467,7 @@ interface Database { @Query("UPDATE Playlist SET name = REPLACE(name,'${PINNED_PREFIX}','') WHERE id = :playlistId") fun unPinPlaylist(playlistId: Long): Int - @Query("SELECT count(id) FROM Song WHERE id = :songId and likedAt IS NOT NULL") + @Query("SELECT count(id) FROM Song WHERE id = :songId and likedAt IS NOT NULL and likedAt > 0") fun songliked(songId: String): Int @Query("SELECT * FROM Song WHERE id = :id") diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/enums/BuiltInPlaylist.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/enums/BuiltInPlaylist.kt index ddf851cb9a..250f5cf8b1 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/enums/BuiltInPlaylist.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/enums/BuiltInPlaylist.kt @@ -6,5 +6,6 @@ enum class BuiltInPlaylist { Offline, Downloaded, Top, - OnDevice + OnDevice, + Disliked } diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/models/Song.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/models/Song.kt index 37b5279fa9..da9ab230d1 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/models/Song.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/models/Song.kt @@ -5,6 +5,7 @@ import androidx.room.Entity import androidx.room.PrimaryKey import it.fast4x.rimusic.cleanPrefix import it.fast4x.rimusic.utils.durationTextToMillis +import it.fast4x.rimusic.utils.setDisLikeState import it.fast4x.rimusic.utils.setLikeState import kotlinx.serialization.Serializable @@ -40,6 +41,12 @@ data class Song( ) } + fun toggleDislike(): Song { + return copy( + likedAt = setDisLikeState(likedAt) + ) + } + fun cleanTitle() = cleanPrefix( this.title ) fun relativePlayTime(): Double { diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/SwipeableContent.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/SwipeableContent.kt index edef409cbb..6bd13bdfb9 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/SwipeableContent.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/SwipeableContent.kt @@ -35,6 +35,7 @@ import it.fast4x.innertube.Innertube import it.fast4x.rimusic.Database import it.fast4x.rimusic.R import it.fast4x.rimusic.appContext +import it.fast4x.rimusic.cleanPrefix import it.fast4x.rimusic.enums.AlbumSwipeAction import it.fast4x.rimusic.enums.DownloadedStateMedia import it.fast4x.rimusic.enums.PlaylistSwipeAction @@ -189,7 +190,7 @@ fun SwipeableQueueItem( } else if (!isYouTubeSyncEnabled()){ mediaItemToggleLike(mediaItem) val message: String - val mTitle: String = mediaItem.mediaMetadata.title?.toString() ?: "" + val mTitle: String = cleanPrefix(mediaItem.mediaMetadata.title?.toString() ?: "") val mArtist: String = mediaItem.mediaMetadata.artist?.toString() ?: "" if (likedAt == -1L) { message = @@ -275,7 +276,7 @@ fun SwipeablePlaylistItem( } else if (!isYouTubeSyncEnabled()){ mediaItemToggleLike(mediaItem) val message: String - val mTitle: String = mediaItem.mediaMetadata.title?.toString() ?: "" + val mTitle: String = cleanPrefix(mediaItem.mediaMetadata.title?.toString() ?: "") val mArtist: String = mediaItem.mediaMetadata.artist?.toString() ?: "" if (likedAt == -1L) { message = diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/IconButton.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/IconButton.kt index 31f6f92d74..39d55e70ff 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/IconButton.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/IconButton.kt @@ -1,9 +1,11 @@ package it.fast4x.rimusic.ui.components.themed import androidx.annotation.DrawableRes +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.Indication import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -39,9 +41,11 @@ fun HeaderIconButton( ) } +@OptIn(ExperimentalFoundationApi::class) @Composable fun IconButton( onClick: () -> Unit, + onLongClick: (() -> Unit)? = null, @DrawableRes icon: Int, color: Color, modifier: Modifier = Modifier, @@ -53,11 +57,12 @@ fun IconButton( contentDescription = null, colorFilter = ColorFilter.tint(color), modifier = Modifier - .clickable( + .combinedClickable( indication = indication ?: ripple(bounded = false), interactionSource = remember { MutableInteractionSource() }, enabled = enabled, - onClick = onClick + onClick = onClick, + onLongClick = onLongClick ) .then(modifier) ) diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/MediaItemGridMenu.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/MediaItemGridMenu.kt index 9d3dbc607a..ae4cef66b8 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/MediaItemGridMenu.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/MediaItemGridMenu.kt @@ -96,12 +96,16 @@ import kotlinx.coroutines.withContext import it.fast4x.rimusic.colorPalette import it.fast4x.rimusic.context import it.fast4x.rimusic.enums.PopupType +import it.fast4x.rimusic.models.Song import it.fast4x.rimusic.service.MyDownloadHelper import it.fast4x.rimusic.typography import it.fast4x.rimusic.ui.screens.settings.isYouTubeSyncEnabled import it.fast4x.rimusic.utils.addSongToYtPlaylist import it.fast4x.rimusic.utils.addToYtLikedSong import it.fast4x.rimusic.utils.isNetworkConnected +import it.fast4x.rimusic.utils.getLikeState +import it.fast4x.rimusic.utils.setDisLikeState +import it.fast4x.rimusic.utils.unlikeYtVideoOrSong @OptIn(UnstableApi::class) @Composable @@ -448,7 +452,7 @@ fun MediaItemGridMenu ( Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { IconButton( - icon = if (likedAt == null) R.drawable.heart_outline else R.drawable.heart, + icon = getLikeState(mediaItem.mediaId), color = colorPalette().favoritesIcon, onClick = { if (!isNetworkConnected(appContext()) && isYouTubeSyncEnabled()) { @@ -462,6 +466,25 @@ fun MediaItemGridMenu ( } } }, + onLongClick = { + if (!isNetworkConnected(appContext()) && isYouTubeSyncEnabled()) { + SmartMessage(appContext().resources.getString(R.string.no_connection), context = appContext(), type = PopupType.Error) + } else if (!isYouTubeSyncEnabled()){ + Database.asyncTransaction { + if (like(mediaItem.mediaId, setDisLikeState(likedAt)) == 0){ + insert(mediaItem, Song::toggleDislike) + } + MyDownloadHelper.autoDownloadWhenLiked(context, mediaItem) + updateData = !updateData + } + } else { + CoroutineScope(Dispatchers.IO).launch { + // currently can not implement dislike for sync so only unliking song + unlikeYtVideoOrSong(mediaItem) + updateData = !updateData + } + } + }, modifier = Modifier .padding(all = 4.dp) .size(24.dp) diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/MediaItemMenu.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/MediaItemMenu.kt index ff652130f6..51a5effc23 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/MediaItemMenu.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/components/themed/MediaItemMenu.kt @@ -128,6 +128,9 @@ import it.fast4x.rimusic.utils.addSongToYtPlaylist import it.fast4x.rimusic.utils.addToYtLikedSong import it.fast4x.rimusic.utils.isNetworkConnected import it.fast4x.rimusic.utils.removeYTSongFromPlaylist +import it.fast4x.rimusic.utils.mediaItemToggleLike +import it.fast4x.rimusic.utils.setDisLikeState +import it.fast4x.rimusic.utils.unlikeYtVideoOrSong import timber.log.Timber import java.time.LocalTime.now import java.time.format.DateTimeFormatter @@ -1292,9 +1295,7 @@ fun MediaItemMenu( SmartMessage(appContext().resources.getString(R.string.no_connection), context = appContext(), type = PopupType.Error) } else if (!isYouTubeSyncEnabled()){ Database.asyncTransaction { - if (like(mediaItem.mediaId, setLikeState(likedAt)) == 0) { - insert(mediaItem, Song::toggleLike) - } + mediaItemToggleLike(mediaItem) MyDownloadHelper.autoDownloadWhenLiked(context(), mediaItem) } } else { @@ -1303,6 +1304,23 @@ fun MediaItemMenu( } } }, + onLongClick = { + if (!isNetworkConnected(appContext()) && isYouTubeSyncEnabled()) { + SmartMessage(appContext().resources.getString(R.string.no_connection), context = appContext(), type = PopupType.Error) + } else if (!isYouTubeSyncEnabled()){ + Database.asyncTransaction { + if (like(mediaItem.mediaId, setDisLikeState(likedAt)) == 0) { + insert(mediaItem, Song::toggleDislike) + } + MyDownloadHelper.autoDownloadWhenLiked(context(), mediaItem) + } + } else { + CoroutineScope(Dispatchers.IO).launch { + // currently disliking can not be implemented for syncing so just unliking songs + unlikeYtVideoOrSong(mediaItem) + } + } + }, modifier = Modifier .padding(all = 4.dp) .size(24.dp) diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/album/AlbumDetails.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/album/AlbumDetails.kt index 9f017aed2c..13d6774ea1 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/album/AlbumDetails.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/album/AlbumDetails.kt @@ -158,6 +158,7 @@ import it.fast4x.rimusic.utils.addToYtLikedSongs import it.fast4x.rimusic.utils.addToYtPlaylist import it.fast4x.rimusic.utils.asAlbum import it.fast4x.rimusic.utils.isNetworkConnected +import it.fast4x.rimusic.utils.mediaItemSetLiked import it.fast4x.rimusic.utils.mediaItemToggleLike import kotlinx.coroutines.flow.first import timber.log.Timber @@ -1113,12 +1114,8 @@ fun AlbumDetails( SmartMessage(appContext().resources.getString(R.string.no_connection), context = appContext(), type = PopupType.Error) } else if (!isYouTubeSyncEnabled()){ songs.forEach { song -> - val likedAt: Long? = song.likedAt - if (likedAt == null) { - mediaItemToggleLike(song.asMediaItem) - } + mediaItemSetLiked(song.asMediaItem) } - } else { val totalSongsToLike = songs.filter { it.likedAt in listOf(-1L,null) diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/builtinplaylist/BuiltInPlaylistScreen.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/builtinplaylist/BuiltInPlaylistScreen.kt index 141b9945a8..5809fc51c3 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/builtinplaylist/BuiltInPlaylistScreen.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/builtinplaylist/BuiltInPlaylistScreen.kt @@ -29,6 +29,7 @@ import it.fast4x.rimusic.utils.showFavoritesPlaylistKey import it.fast4x.rimusic.utils.showMyTopPlaylistKey import it.fast4x.rimusic.utils.showOnDevicePlaylistKey import it.fast4x.rimusic.ui.components.Skeleton +import it.fast4x.rimusic.utils.showDislikedPlaylistKey @ExperimentalMaterialApi @ExperimentalTextApi @@ -52,7 +53,8 @@ fun BuiltInPlaylistScreen( BuiltInPlaylist.Downloaded -> 2 BuiltInPlaylist.Top -> 3 BuiltInPlaylist.OnDevice -> 4 - else -> 5 + BuiltInPlaylist.Disliked -> 5 + else -> 6 }) } @@ -65,6 +67,7 @@ fun BuiltInPlaylistScreen( val showMyTopPlaylist by rememberPreference(showMyTopPlaylistKey, true) val showDownloadedPlaylist by rememberPreference(showDownloadedPlaylistKey, true) val showOnDevicePlaylist by rememberPreference(showOnDevicePlaylistKey, true) + val showDislikedPlaylist by rememberPreference(showDislikedPlaylistKey, false) PersistMapCleanup(tagPrefix = "${builtInPlaylist.name}/") @@ -84,6 +87,8 @@ fun BuiltInPlaylistScreen( item(3, stringResource(R.string.my_playlist_top).format(maxTopPlaylistItems.number) , R.drawable.trending) if(showOnDevicePlaylist) item(4, stringResource(R.string.on_device), R.drawable.musical_notes) + if(showDislikedPlaylist) + item(5, stringResource(R.string.disliked), R.drawable.heart_dislike) } ) { currentTabIndex -> saveableStateHolder.SaveableStateProvider(key = currentTabIndex) { @@ -93,7 +98,8 @@ fun BuiltInPlaylistScreen( 1 -> BuiltInPlaylist.Offline 2 -> BuiltInPlaylist.Downloaded 3 -> BuiltInPlaylist.Top - else -> BuiltInPlaylist.OnDevice + 4 -> BuiltInPlaylist.OnDevice + else -> BuiltInPlaylist.Disliked } if( playlist == BuiltInPlaylist.OnDevice ) diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/builtinplaylist/BuiltInPlaylistSongs.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/builtinplaylist/BuiltInPlaylistSongs.kt index 0cd4f56398..efe498ce0d 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/builtinplaylist/BuiltInPlaylistSongs.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/builtinplaylist/BuiltInPlaylistSongs.kt @@ -464,6 +464,7 @@ fun BuiltInPlaylistSongs( BuiltInPlaylist.Downloaded -> context.resources.getString(R.string.downloaded) BuiltInPlaylist.Offline -> context.resources.getString(R.string.cached) BuiltInPlaylist.Top -> context.resources.getString(R.string.playlist_top) + BuiltInPlaylist.Disliked -> context.resources.getString(R.string.disliked) }, placeholder = stringResource(R.string.enter_the_playlist_name), setValue = { text -> @@ -518,6 +519,7 @@ fun BuiltInPlaylistSongs( BuiltInPlaylist.Downloaded -> stringResource(R.string.downloaded) BuiltInPlaylist.Offline -> stringResource(R.string.cached) BuiltInPlaylist.Top -> stringResource(R.string.my_playlist_top).format(maxTopPlaylistItems.number) + BuiltInPlaylist.Disliked -> stringResource(R.string.disliked) }, iconId = R.drawable.search, enabled = true, @@ -547,6 +549,7 @@ fun BuiltInPlaylistSongs( BuiltInPlaylist.Downloaded -> R.drawable.downloaded BuiltInPlaylist.Offline -> R.drawable.sync BuiltInPlaylist.Top -> R.drawable.trending + BuiltInPlaylist.Disliked -> R.drawable.heart_dislike }, colorTint = colorPalette().favoritesIcon, name = when (builtInPlaylist) { @@ -556,6 +559,7 @@ fun BuiltInPlaylistSongs( BuiltInPlaylist.Downloaded -> stringResource(R.string.downloaded) BuiltInPlaylist.Offline -> stringResource(R.string.cached) BuiltInPlaylist.Top -> stringResource(R.string.playlist_top) + BuiltInPlaylist.Disliked -> stringResource(R.string.disliked) }, songCount = null, thumbnailSizeDp = playlistThumbnailSizeDp, @@ -1192,7 +1196,8 @@ fun BuiltInPlaylistSongs( when (builtInPlaylist) { BuiltInPlaylist.Favorites, BuiltInPlaylist.Downloaded, - BuiltInPlaylist.Top -> NonQueuedMediaItemMenuLibrary( + BuiltInPlaylist.Top, + BuiltInPlaylist.Disliked -> NonQueuedMediaItemMenuLibrary( navController = navController, mediaItem = song.asMediaItem, onDismiss = menuState::hide, diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/home/HomeSongs.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/home/HomeSongs.kt index 9e27ffac6d..95e9152221 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/home/HomeSongs.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/home/HomeSongs.kt @@ -250,6 +250,7 @@ fun HomeSongs( BuiltInPlaylist.Downloaded -> context.resources.getString(R.string.downloaded) BuiltInPlaylist.Offline -> context.resources.getString(R.string.cached) BuiltInPlaylist.Top -> context.resources.getString(R.string.playlist_top) + BuiltInPlaylist.Disliked -> context.resources.getString(R.string.disliked) } } @@ -392,6 +393,7 @@ fun HomeSongs( ) } BuiltInPlaylist.OnDevice -> flowOf() + BuiltInPlaylist.Disliked -> flowOf() // probably no need to implement }.flowOn( Dispatchers.IO ).distinctUntilChanged().collect { /* diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/home/HomeSongsModern.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/home/HomeSongsModern.kt index 7e91662951..b01953e7e5 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/home/HomeSongsModern.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/home/HomeSongsModern.kt @@ -209,6 +209,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import kotlin.system.exitProcess +import it.fast4x.rimusic.utils.showDislikedPlaylistKey @OptIn(ExperimentalMaterial3Api::class) @@ -388,6 +389,7 @@ fun HomeSongsModern( /************ */ val showFavoritesPlaylist by rememberPreference(showFavoritesPlaylistKey, true) + val showDislikedPlaylist by rememberPreference(showDislikedPlaylistKey, false) val showCachedPlaylist by rememberPreference(showCachedPlaylistKey, true) val showMyTopPlaylist by rememberPreference(showMyTopPlaylistKey, true) val showDownloadedPlaylist by rememberPreference(showDownloadedPlaylistKey, true) @@ -404,6 +406,8 @@ fun HomeSongsModern( BuiltInPlaylist.Top to String.format(stringResource(R.string.my_playlist_top),maxTopPlaylistItems.number) if (showOnDevicePlaylist) buttonsList += BuiltInPlaylist.OnDevice to stringResource(R.string.on_device) + if (showDislikedPlaylist) buttonsList += + BuiltInPlaylist.Disliked to stringResource(R.string.disliked) val excludeSongWithDurationLimit by rememberPreference(excludeSongsWithDurationLimitKey, DurationInMinutes.Disabled) val hapticFeedback = LocalHapticFeedback.current @@ -418,7 +422,8 @@ fun HomeSongsModern( } } - BuiltInPlaylist.Downloaded, BuiltInPlaylist.Favorites, BuiltInPlaylist.Offline, BuiltInPlaylist.Top -> { + BuiltInPlaylist.Downloaded, BuiltInPlaylist.Favorites, BuiltInPlaylist.Offline, + BuiltInPlaylist.Top, BuiltInPlaylist.Disliked -> { LaunchedEffect(Unit, builtInPlaylist, sortBy, sortOrder, filter, topPlaylistPeriod) { @@ -466,6 +471,16 @@ fun HomeSongsModern( } } + if(builtInPlaylist == BuiltInPlaylist.Disliked) { + Database.songsDisliked(sortBy, sortOrder) + .collect { + items = + if (autoShuffle) + it.shuffled() + else it + } + } + if (builtInPlaylist == BuiltInPlaylist.Offline) { Database @@ -777,6 +792,7 @@ fun HomeSongsModern( BuiltInPlaylist.Downloaded -> context.resources.getString(R.string.downloaded) BuiltInPlaylist.Offline -> context.resources.getString(R.string.cached) BuiltInPlaylist.Top -> context.resources.getString(R.string.playlist_top) + BuiltInPlaylist.Disliked -> context.resources.getString(R.string.disliked) }, placeholder = stringResource(R.string.enter_the_playlist_name), setValue = { text -> diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/ondevice/DeviceListSongsScreen.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/ondevice/DeviceListSongsScreen.kt index 5eeb1f10ec..042844cd33 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/ondevice/DeviceListSongsScreen.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/ondevice/DeviceListSongsScreen.kt @@ -28,6 +28,7 @@ import it.fast4x.rimusic.utils.showFavoritesPlaylistKey import it.fast4x.rimusic.utils.showMyTopPlaylistKey import it.fast4x.rimusic.utils.showOnDevicePlaylistKey import it.fast4x.rimusic.ui.components.Skeleton +import it.fast4x.rimusic.utils.showDislikedPlaylistKey @ExperimentalMaterialApi @ExperimentalTextApi @@ -55,6 +56,7 @@ fun DeviceListSongsScreen( ) val showFavoritesPlaylist by rememberPreference(showFavoritesPlaylistKey, true) + val showDislikedPlaylist by rememberPreference(showDislikedPlaylistKey, false) val showCachedPlaylist by rememberPreference(showCachedPlaylistKey, true) val showMyTopPlaylist by rememberPreference(showMyTopPlaylistKey, true) val showDownloadedPlaylist by rememberPreference(showDownloadedPlaylistKey, true) @@ -78,6 +80,8 @@ fun DeviceListSongsScreen( item(3, stringResource(R.string.my_playlist_top) + " ${maxTopPlaylistItems.number}" , R.drawable.trending) if(showOnDevicePlaylist) item(4, stringResource(R.string.on_device), R.drawable.musical_notes) + if(showDislikedPlaylist) + item(5, stringResource(R.string.disliked), R.drawable.heart_dislike) } ) { currentTabIndex -> saveableStateHolder.SaveableStateProvider(key = currentTabIndex) { @@ -87,7 +91,8 @@ fun DeviceListSongsScreen( 1 -> BuiltInPlaylist.Offline 2 -> BuiltInPlaylist.Downloaded 3 -> BuiltInPlaylist.Top - else -> BuiltInPlaylist.OnDevice + 4 -> BuiltInPlaylist.OnDevice + else -> BuiltInPlaylist.Disliked } if( builtInPlaylist == BuiltInPlaylist.OnDevice ) diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/MiniPlayer.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/MiniPlayer.kt index 6da975885b..2cbc9379d6 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/MiniPlayer.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/MiniPlayer.kt @@ -74,6 +74,7 @@ import it.fast4x.rimusic.enums.NavRoutes import it.fast4x.rimusic.enums.PopupType import it.fast4x.rimusic.service.MyDownloadHelper import it.fast4x.rimusic.service.modern.PlayerServiceModern +import it.fast4x.rimusic.models.Song import it.fast4x.rimusic.thumbnailShape import it.fast4x.rimusic.typography import it.fast4x.rimusic.ui.components.themed.NowPlayingSongIndicator @@ -90,8 +91,7 @@ import it.fast4x.rimusic.utils.conditional import it.fast4x.rimusic.utils.disableClosingPlayerSwipingDownKey import it.fast4x.rimusic.utils.disableScrollingTextKey import it.fast4x.rimusic.utils.effectRotationKey -import it.fast4x.rimusic.utils.getLikedIcon -import it.fast4x.rimusic.utils.getUnlikedIcon +import it.fast4x.rimusic.utils.getLikeState import it.fast4x.rimusic.utils.intent import it.fast4x.rimusic.utils.isExplicit import it.fast4x.rimusic.utils.isNetworkConnected @@ -102,8 +102,10 @@ import it.fast4x.rimusic.utils.playPrevious import it.fast4x.rimusic.utils.positionAndDurationState import it.fast4x.rimusic.utils.rememberPreference import it.fast4x.rimusic.utils.semiBold +import it.fast4x.rimusic.utils.setDisLikeState import it.fast4x.rimusic.utils.shouldBePlaying import it.fast4x.rimusic.utils.thumbnail +import it.fast4x.rimusic.utils.unlikeYtVideoOrSong import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.distinctUntilChanged @@ -175,14 +177,15 @@ fun MiniPlayer( } var updateLike by rememberSaveable { mutableStateOf(false) } + var updateDislike by rememberSaveable { mutableStateOf(false) } - LaunchedEffect(updateLike) { + LaunchedEffect(updateLike, updateDislike) { if (updateLike) { if (!isNetworkConnected(appContext()) && isYouTubeSyncEnabled()) { SmartMessage(appContext().resources.getString(R.string.no_connection), context = appContext(), type = PopupType.Error) } else if (!isYouTubeSyncEnabled()){ mediaItemToggleLike(mediaItem) - if (likedAt == null) + if (likedAt == null || likedAt == -1L) SmartMessage(context.resources.getString(R.string.added_to_favorites), context = context) else SmartMessage(context.resources.getString(R.string.removed_from_favorites), context = context) @@ -193,6 +196,27 @@ fun MiniPlayer( } updateLike = false } + if (updateDislike) { + if (!isNetworkConnected(appContext()) && isYouTubeSyncEnabled()) { + SmartMessage(appContext().resources.getString(R.string.no_connection), context = appContext(), type = PopupType.Error) + } else if (!isYouTubeSyncEnabled()){ + Database.asyncTransaction { + if (like(mediaItem.mediaId, setDisLikeState(likedAt)) == 0) + insert(mediaItem, Song::toggleDislike) + MyDownloadHelper.autoDownloadWhenLiked(context, mediaItem) + } + if (likedAt == null || likedAt!! > 0L) + SmartMessage(context.resources.getString(R.string.added_to_disliked), context = context) + else + SmartMessage(context.resources.getString(R.string.removed_from_disliked), context = context) + } else { + CoroutineScope(Dispatchers.IO).launch { + // can currently not implement dislike for sync, so unliking the song + unlikeYtVideoOrSong(mediaItem) + } + } + updateDislike = false + } } val positionAndDuration by binder.player.positionAndDurationState() @@ -256,9 +280,11 @@ fun MiniPlayer( imageVector = when (dismissState.targetValue) { SwipeToDismissBoxValue.StartToEnd -> { if (miniPlayerType == MiniPlayerType.Modern) ImageVector.vectorResource(R.drawable.play_skip_back) else - if (likedAt == null) - ImageVector.vectorResource(R.drawable.heart_outline) - else ImageVector.vectorResource(R.drawable.heart) + if (likedAt == null) + ImageVector.vectorResource(R.drawable.heart_outline) + else if(likedAt == -1L) + ImageVector.vectorResource(R.drawable.heart_dislike) + else ImageVector.vectorResource(R.drawable.heart) } SwipeToDismissBoxValue.EndToStart -> ImageVector.vectorResource(R.drawable.play_skip_forward) SwipeToDismissBoxValue.Settled -> ImageVector.vectorResource(R.drawable.play) @@ -455,11 +481,14 @@ fun MiniPlayer( ) if (miniPlayerType == MiniPlayerType.Modern) it.fast4x.rimusic.ui.components.themed.IconButton( - icon = if (likedAt == null) getUnlikedIcon() else getLikedIcon(), + icon = getLikeState(mediaItem.mediaId), color = colorPalette().favoritesIcon, onClick = { updateLike = true }, + onLongClick = { + updateDislike = true + }, modifier = Modifier .rotate(rotationAngle) .padding(horizontal = 2.dp, vertical = 8.dp) diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/components/controls/Essential.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/components/controls/Essential.kt index 035052ca5b..6bbcb39795 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/components/controls/Essential.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/components/controls/Essential.kt @@ -1,5 +1,6 @@ package it.fast4x.rimusic.ui.screens.player.components.controls +import android.annotation.SuppressLint import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.animateDp import androidx.compose.animation.core.animateFloatAsState @@ -91,6 +92,7 @@ import it.fast4x.rimusic.utils.getLikeState import it.fast4x.rimusic.utils.getUnlikedIcon import it.fast4x.rimusic.utils.isNetworkConnected import it.fast4x.rimusic.utils.jumpPreviousKey +import it.fast4x.rimusic.utils.mediaItemToggleLike import it.fast4x.rimusic.utils.playNext import it.fast4x.rimusic.utils.playPrevious import it.fast4x.rimusic.utils.playerBackgroundColorsKey @@ -98,16 +100,19 @@ import it.fast4x.rimusic.utils.playerControlsTypeKey import it.fast4x.rimusic.utils.queueLoopTypeKey import it.fast4x.rimusic.utils.rememberPreference import it.fast4x.rimusic.utils.semiBold +import it.fast4x.rimusic.utils.setDisLikeState import it.fast4x.rimusic.utils.setLikeState import it.fast4x.rimusic.utils.setQueueLoopState import it.fast4x.rimusic.utils.showthumbnailKey import it.fast4x.rimusic.utils.textCopyToClipboard import it.fast4x.rimusic.utils.textoutlineKey +import it.fast4x.rimusic.utils.unlikeYtVideoOrSong import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +@SuppressLint("UnusedBoxWithConstraintsScope") @UnstableApi @ExperimentalFoundationApi @Composable @@ -268,28 +273,49 @@ fun InfoAlbumAndArtistEssential( } else if (!isYouTubeSyncEnabled()){ Database.asyncTransaction { CoroutineScope(Dispatchers.IO).launch { - if (like(mediaId, setLikeState(likedAt)) == 0) { + currentMediaItem.takeIf { it?.mediaId == mediaId }.let { mediaItem -> + if(mediaItem != null){ + mediaItemToggleLike(mediaItem) + MyDownloadHelper.autoDownloadWhenLiked(context(), mediaItem) + } + } + } + } + } else { + CoroutineScope(Dispatchers.IO).launch { + if (currentMediaItem != null) { + addToYtLikedSong(currentMediaItem) + } + } + } + if (effectRotationEnabled) isRotated = !isRotated + }, + onLongClick = { + if (!isNetworkConnected(appContext()) && isYouTubeSyncEnabled()) { + SmartMessage(appContext().resources.getString(R.string.no_connection), context = appContext(), type = PopupType.Error) + } else if (!isYouTubeSyncEnabled()){ + Database.asyncTransaction { + CoroutineScope(Dispatchers.IO).launch { + if (like(mediaId, setDisLikeState(likedAt)) == 0) { currentMediaItem ?.takeIf { it.mediaId == mediaId } ?.let { - insert(currentMediaItem, Song::toggleLike) + insert(currentMediaItem, Song::toggleDislike) } } + if(currentMediaItem != null){ + MyDownloadHelper.autoDownloadWhenLiked(context(), currentMediaItem) + } } } } else { CoroutineScope(Dispatchers.IO).launch { if (currentMediaItem != null) { - addToYtLikedSong(currentMediaItem) + // currently can not implement disliking for sync, so only unliking the song + unlikeYtVideoOrSong(currentMediaItem) } } } - if (currentMediaItem != null) { - MyDownloadHelper.autoDownloadWhenLiked( - context(), - currentMediaItem - ) - } if (effectRotationEnabled) isRotated = !isRotated }, modifier = Modifier @@ -449,12 +475,9 @@ fun ControlsEssential( } else if (!isYouTubeSyncEnabled()){ Database.asyncTransaction { CoroutineScope(Dispatchers.IO).launch { - if (like(mediaId, setLikeState(likedAt)) == 0) { - currentMediaItem - ?.takeIf { it.mediaId == mediaId } - ?.let { - insert(currentMediaItem, Song::toggleLike) - } + currentMediaItem?.takeIf { it.mediaId == mediaId }?.let { mediaItem -> + mediaItemToggleLike(mediaItem) + MyDownloadHelper.autoDownloadWhenLiked(context(), currentMediaItem) } } } @@ -465,11 +488,29 @@ fun ControlsEssential( } } } - if (currentMediaItem != null) { - MyDownloadHelper.autoDownloadWhenLiked( - context(), - currentMediaItem - ) + if (effectRotationEnabled) isRotated = !isRotated + }, + onLongClick = { + if (!isNetworkConnected(appContext()) && isYouTubeSyncEnabled()) { + SmartMessage(appContext().resources.getString(R.string.no_connection), context = appContext(), type = PopupType.Error) + } else if (!isYouTubeSyncEnabled()){ + Database.asyncTransaction { + CoroutineScope(Dispatchers.IO).launch { + currentMediaItem?.takeIf { it.mediaId == mediaId }?.let { mediaItem -> + if (like(mediaId, setDisLikeState(likedAt)) == 0){ + insert(currentMediaItem, Song::toggleDislike) + } + MyDownloadHelper.autoDownloadWhenLiked(context(), currentMediaItem) + } + } + } + } else { + CoroutineScope(Dispatchers.IO).launch { + if (currentMediaItem != null) { + // currently can not implement dislike for sync, so unliking the song + unlikeYtVideoOrSong(currentMediaItem) + } + } } if (effectRotationEnabled) isRotated = !isRotated }, diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/components/controls/Modern.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/components/controls/Modern.kt index d392285f1c..1d10915abe 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/components/controls/Modern.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/player/components/controls/Modern.kt @@ -83,6 +83,7 @@ import it.fast4x.rimusic.utils.getLikeState import it.fast4x.rimusic.utils.getUnlikedIcon import it.fast4x.rimusic.utils.isNetworkConnected import it.fast4x.rimusic.utils.jumpPreviousKey +import it.fast4x.rimusic.utils.mediaItemToggleLike import it.fast4x.rimusic.utils.playNext import it.fast4x.rimusic.utils.playPrevious import it.fast4x.rimusic.utils.playerBackgroundColorsKey @@ -90,10 +91,12 @@ import it.fast4x.rimusic.utils.playerControlsTypeKey import it.fast4x.rimusic.utils.playerInfoShowIconsKey import it.fast4x.rimusic.utils.rememberPreference import it.fast4x.rimusic.utils.semiBold +import it.fast4x.rimusic.utils.setDisLikeState import it.fast4x.rimusic.utils.setLikeState import it.fast4x.rimusic.utils.showthumbnailKey import it.fast4x.rimusic.utils.textCopyToClipboard import it.fast4x.rimusic.utils.textoutlineKey +import it.fast4x.rimusic.utils.unlikeYtVideoOrSong import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -245,14 +248,11 @@ fun InfoAlbumAndArtistModern( if (!isNetworkConnected(appContext()) && isYouTubeSyncEnabled()) { SmartMessage(appContext().resources.getString(R.string.no_connection), context = appContext(), type = PopupType.Error) } else if (!isYouTubeSyncEnabled()){ - Database.asyncTransaction { - CoroutineScope(Dispatchers.IO).launch { - if (like(mediaId, setLikeState(likedAt)) == 0) { - currentMediaItem - ?.takeIf { it.mediaId == mediaId } - ?.let { - insert(currentMediaItem, Song::toggleLike) - } + currentMediaItem?.takeIf { it.mediaId == mediaId }.let { mediaItem -> + if (mediaItem != null) { + Database.asyncQuery { + mediaItemToggleLike(mediaItem) + MyDownloadHelper.autoDownloadWhenLiked(context(), mediaItem) } } } @@ -263,11 +263,29 @@ fun InfoAlbumAndArtistModern( } } } - if (currentMediaItem != null) { - MyDownloadHelper.autoDownloadWhenLiked( - context(), - currentMediaItem - ) + if (effectRotationEnabled) isRotated = !isRotated + }, + onLongClick = { + if (!isNetworkConnected(appContext()) && isYouTubeSyncEnabled()) { + SmartMessage(appContext().resources.getString(R.string.no_connection), context = appContext(), type = PopupType.Error) + } else if (!isYouTubeSyncEnabled()){ + currentMediaItem?.takeIf { it.mediaId == mediaId }.let { mediaItem -> + if (mediaItem != null) { + Database.asyncTransaction { + if (like(mediaId, setDisLikeState(likedAt)) == 0) { + insert(mediaItem, Song::toggleDislike) + } + MyDownloadHelper.autoDownloadWhenLiked(context(), mediaItem) + } + } + } + } else { + CoroutineScope(Dispatchers.IO).launch { + if (currentMediaItem != null) { + // currently can not implement dislike for sync, so unliking song + unlikeYtVideoOrSong(currentMediaItem) + } + } } if (effectRotationEnabled) isRotated = !isRotated }, diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/playlist/PlaylistSongList.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/playlist/PlaylistSongList.kt index 82b6b41f80..0556fee193 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/playlist/PlaylistSongList.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/playlist/PlaylistSongList.kt @@ -157,6 +157,7 @@ import it.fast4x.rimusic.utils.formatAsDuration import it.fast4x.rimusic.utils.getHttpClient import it.fast4x.rimusic.utils.isNetworkConnected import it.fast4x.rimusic.utils.languageDestination +import it.fast4x.rimusic.utils.mediaItemSetLiked import it.fast4x.rimusic.utils.setLikeState import kotlinx.coroutines.flow.filterNotNull import me.bush.translator.Language @@ -784,12 +785,7 @@ fun PlaylistSongList( } else if (!isYouTubeSyncEnabled()){ Database.asyncTransaction { playlistPage!!.songs.filter { getLikedAt(it.asMediaItem.mediaId) in listOf(-1L,null) }.forEachIndexed { _, song -> - Database.asyncTransaction { - if (like(song.asMediaItem.mediaId, setLikeState(song.asSong.likedAt)) == 0 - ) { - insert(song.asMediaItem, Song::toggleLike) - } - } + mediaItemSetLiked(song.asMediaItem) } SmartMessage( context.resources.getString(R.string.done), diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/settings/UiSettings.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/settings/UiSettings.kt index b09d510748..6c716e5790 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/settings/UiSettings.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/ui/screens/settings/UiSettings.kt @@ -238,6 +238,7 @@ import it.fast4x.rimusic.utils.autoDownloadSongKey import it.fast4x.rimusic.utils.autoDownloadSongWhenAlbumBookmarkedKey import it.fast4x.rimusic.utils.autoDownloadSongWhenLikedKey import it.fast4x.rimusic.utils.customColorKey +import it.fast4x.rimusic.utils.showDislikedPlaylistKey @Composable fun DefaultUiSettings() { @@ -338,6 +339,8 @@ fun DefaultUiSettings() { showDownloadedPlaylist = true var showOnDevicePlaylist by rememberPreference(showOnDevicePlaylistKey, true) showOnDevicePlaylist = true + var showDislikedPlaylist by rememberPreference(showDislikedPlaylistKey, false) + showDislikedPlaylist = false var shakeEventEnabled by rememberPreference(shakeEventEnabledKey, false) shakeEventEnabled = false var useVolumeKeysToChangeSong by rememberPreference(useVolumeKeysToChangeSongKey, false) @@ -612,6 +615,7 @@ fun UiSettings( var showMyTopPlaylist by rememberPreference(showMyTopPlaylistKey, true) var showDownloadedPlaylist by rememberPreference(showDownloadedPlaylistKey, true) var showOnDevicePlaylist by rememberPreference(showOnDevicePlaylistKey, true) + var showDislikedPlaylist by rememberPreference(showDislikedPlaylistKey, false) var showFloatingIcon by rememberPreference(showFloatingIconKey, false) var menuStyle by rememberPreference(menuStyleKey, MenuStyle.List) var transitionEffect by rememberPreference(transitionEffectKey, TransitionEffect.Scale) @@ -1445,6 +1449,14 @@ fun UiSettings( onCheckedChange = { showOnDevicePlaylist = it } ) + if (search.input.isBlank() || "${stringResource(R.string.show)} ${stringResource(R.string.disliked)}".contains(search.input,true)) + SwitchSettingEntry( + title = "${stringResource(R.string.show)} ${stringResource(R.string.disliked)}", + text = "", + isChecked = showDislikedPlaylist, + onCheckedChange = { showDislikedPlaylist = it } + ) + /* SettingsGroupSpacer() SettingsEntryGroupText(title = stringResource(R.string.playlists).uppercase()) diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/LikeState.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/LikeState.kt index 60ed97b9af..daf5a4876c 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/LikeState.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/LikeState.kt @@ -36,6 +36,15 @@ fun setLikeState(likedAt: Long?): Long? { } //println("mediaItem setLikeState: $current") return current +} +fun setDisLikeState(likedAt: Long?): Long? { + val current = + when (likedAt) { + -1L -> null + null -> -1L + else -> -1L + } + return current } diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/Preferences.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/Preferences.kt index 831013211a..41f71179aa 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/Preferences.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/Preferences.kt @@ -128,6 +128,7 @@ const val navigationBarPositionKey = "navigationBarPosition" const val navigationBarTypeKey = "navigationBarType" const val pauseBetweenSongsKey = "pauseBetweenSongs" const val showFavoritesPlaylistKey = "showFavoritesPlaylist" +const val showDislikedPlaylistKey = "showDislikedPlaylistKey" const val showCachedPlaylistKey = "showCachedPlaylist" const val showMyTopPlaylistKey = "showMyTopPlaylist" const val showDownloadedPlaylistKey = "showDownloadedPlaylist" diff --git a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/Utils.kt b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/Utils.kt index 7094427cef..04a481d494 100644 --- a/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/Utils.kt +++ b/composeApp/src/androidMain/kotlin/it/fast4x/rimusic/utils/Utils.kt @@ -124,18 +124,33 @@ fun songToggleLike( song: Song ) { fun mediaItemToggleLike( mediaItem: MediaItem) { Database.asyncTransaction { if (songExist(mediaItem.mediaId) == 0) - insert(mediaItem, Song::toggleLike) - //else { - if (songliked(mediaItem.mediaId) == 0) - like( - mediaItem.mediaId, - System.currentTimeMillis() - ) - else like( + insert(mediaItem) + if (getLikedAt(mediaItem.mediaId) in listOf(null, -1L)) + like( mediaItem.mediaId, - null + System.currentTimeMillis() + ) + else like( + mediaItem.mediaId, + null + ) + MyDownloadHelper.autoDownloadWhenLiked( + context(), + mediaItem + ) + } +} + +@UnstableApi +fun mediaItemSetLiked( mediaItem: MediaItem ) { + Database.asyncTransaction { + if (songExist(mediaItem.mediaId) == 0) + insert(mediaItem) + if (getLikedAt(mediaItem.mediaId) in listOf(null, -1L)) + like( + mediaItem.mediaId, + System.currentTimeMillis() ) - //} MyDownloadHelper.autoDownloadWhenLiked( context(), mediaItem @@ -1138,29 +1153,39 @@ suspend fun addToYtLikedSong(mediaItem: MediaItem){ ) } } else { - removelikeVideoOrSong(mediaItem.mediaId) - .onSuccess { - Database.asyncTransaction { - like(mediaItem.mediaId, null) - MyDownloadHelper.autoDownloadWhenLiked( - context(), - mediaItem - ) + unlikeYtVideoOrSong(mediaItem) + } + } +} + +@OptIn(UnstableApi::class) +suspend fun unlikeYtVideoOrSong(mediaItem: MediaItem){ + if(isYouTubeSyncEnabled()){ + removelikeVideoOrSong(mediaItem.mediaId) + .onSuccess { + Database.asyncTransaction { + if(songExist(mediaItem.mediaId) == 0){ + insert(mediaItem) } - SmartMessage( - appContext().resources.getString(R.string.song_unliked_yt), - context = appContext(), - durationLong = false - ) - } - .onFailure { - SmartMessage( - appContext().resources.getString(R.string.songs_unliked_yt_failed), - context = appContext(), - durationLong = false + like(mediaItem.mediaId, null) + MyDownloadHelper.autoDownloadWhenLiked( + context(), + mediaItem ) } - } + SmartMessage( + appContext().resources.getString(R.string.song_unliked_yt), + context = appContext(), + durationLong = false + ) + } + .onFailure { + SmartMessage( + appContext().resources.getString(R.string.songs_unliked_yt_failed), + context = appContext(), + durationLong = false + ) + } } } diff --git a/composeApp/src/androidMain/res/values/strings.xml b/composeApp/src/androidMain/res/values/strings.xml index 67dae01bc4..33ffce95b6 100644 --- a/composeApp/src/androidMain/res/values/strings.xml +++ b/composeApp/src/androidMain/res/values/strings.xml @@ -23,6 +23,7 @@ Enter the playlist name Playlists Favorites + Disliked Quick picks Songs Other versions @@ -556,6 +557,7 @@ Bottom Added to favorites Removed from favorites + Added to disliked Filipino Transparent background Info type