Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MOBILE-188/Features such as recommend,review in profile screen #366

Merged
Merged
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.listenbrainz.android.ui.screens.listens

import android.os.Bundle
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
Expand All @@ -11,25 +13,43 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import org.listenbrainz.android.model.Listen
import org.listenbrainz.android.model.Metadata
import org.listenbrainz.android.model.TrackMetadata
import org.listenbrainz.android.model.feed.ReviewEntityType
import org.listenbrainz.android.ui.components.ListenCardSmall
import org.listenbrainz.android.ui.components.LoadingAnimation
import org.listenbrainz.android.ui.components.dialogs.Dialog
import org.listenbrainz.android.ui.components.dialogs.PersonalRecommendationDialog
import org.listenbrainz.android.ui.components.dialogs.PinDialog
import org.listenbrainz.android.ui.components.dialogs.ReviewDialog
import org.listenbrainz.android.ui.components.dialogs.rememberDialogsState
import org.listenbrainz.android.ui.screens.feed.FeedUiState
import org.listenbrainz.android.ui.screens.feed.SocialDropdown
import org.listenbrainz.android.ui.screens.profile.UserData
import org.listenbrainz.android.ui.screens.settings.PreferencesUiState
import org.listenbrainz.android.ui.theme.ListenBrainzTheme
import org.listenbrainz.android.util.Constants
import org.listenbrainz.android.util.Utils
import org.listenbrainz.android.viewmodel.FeedViewModel
import org.listenbrainz.android.viewmodel.ListensViewModel
import org.listenbrainz.android.viewmodel.SocialViewModel

Expand Down Expand Up @@ -64,20 +84,44 @@ fun ListensScreen(
)
}

private enum class ListenDialogBundleKeys {
PAGE,
EVENT_INDEX;
companion object {
fun listenDialogBundle(page: Int, eventIndex: Int): Bundle {
return Bundle().apply {
putInt(PAGE.name, page)
putInt(EVENT_INDEX.name, eventIndex)
}
}
}
}



@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ListensScreen(
scrollRequestState: Boolean,
onScrollToTop: (suspend () -> Unit) -> Unit,
socialViewModel: SocialViewModel = hiltViewModel(),
pranavkonidena marked this conversation as resolved.
Show resolved Hide resolved
feedViewModel: FeedViewModel = hiltViewModel(),
uiState: ListensUiState,
preferencesUiState: PreferencesUiState,
updateNotificationServicePermissionStatus: () -> Unit,
validateUserToken: suspend (String) -> Boolean,
setToken: (String) -> Unit,
playListen: (TrackMetadata) -> Unit
playListen: (TrackMetadata) -> Unit,
uriHandler: UriHandler = LocalUriHandler.current
) {
val listState = rememberLazyListState()
val dropdownItemIndex: MutableState<Int?> = rememberSaveable {
mutableStateOf(null)
}
val dialogsState = rememberDialogsState()
val feedUiState by feedViewModel.uiState.collectAsState()
val context = LocalContext.current


// Scroll to the top when shouldScrollToTop becomes true
LaunchedEffect(scrollRequestState) {
Expand Down Expand Up @@ -134,7 +178,8 @@ fun ListensScreen(
}
}

items(items = uiState.listens) { listen ->
itemsIndexed(items = uiState.listens) { index , listen ->
val metadata = Metadata(trackMetadata = listen.trackMetadata)
ListenCardSmall(
modifier = Modifier.padding(
horizontal = ListenBrainzTheme.paddings.horizontal,
Expand All @@ -145,12 +190,73 @@ fun ListensScreen(
coverArtUrl = Utils.getCoverArtUrl(
caaReleaseMbid = listen.trackMetadata.mbidMapping?.caaReleaseMbid,
caaId = listen.trackMetadata.mbidMapping?.caaId
)
),
dropDown = {
SocialDropdown(
isExpanded = dropdownItemIndex.value == index,
onDismiss = {
dropdownItemIndex.value = null
},
metadata = metadata,
onRecommend = {
try {
socialViewModel.recommend(metadata)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong, we do not propagate errors to the UI right now so this will always result in a successful toast. If you want, you can propagate the error via Resource class and then check if the response was successful then return error. We can ditch errorFlow for this as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember its okay to change the core code if you feel its right. At most I will say is "This is wrong" and you will learn a thing or two. Great work overall.

Toast.makeText(context , Constants.Strings.RECOMMENDATION_GREETING , Toast.LENGTH_SHORT).show()
}
catch (e : Error) {
Toast.makeText(context , Constants.Strings.ERROR_MESSAGE , Toast.LENGTH_SHORT).show()
}
dropdownItemIndex.value = null
},
onPersonallyRecommend = {
dialogsState.activateDialog(Dialog.PERSONAL_RECOMMENDATION , ListenDialogBundleKeys.listenDialogBundle(0, index))
dropdownItemIndex.value = null
},
onReview = {
dialogsState.activateDialog(Dialog.REVIEW , ListenDialogBundleKeys.listenDialogBundle(0, index))
dropdownItemIndex.value = null
},
onPin = {
dialogsState.activateDialog(Dialog.PIN , ListenDialogBundleKeys.listenDialogBundle(0, index))
dropdownItemIndex.value = null
},
onOpenInMusicBrainz = {
try {
uriHandler.openUri("https://musicbrainz.org/recording/${metadata.trackMetadata?.mbidMapping?.recordingMbid}")
}
catch(e : Error) {
Toast.makeText(context , Constants.Strings.ERROR_MESSAGE , Toast.LENGTH_SHORT).show()
}
dropdownItemIndex.value = null
}

)
},
enableDropdownIcon = true,
onDropdownIconClick = {
dropdownItemIndex.value = index
}
) {
playListen(listen.trackMetadata)
}
}
}
Dialogs(
deactivateDialog = {
dialogsState.deactivateDialog()
},
currentDialog = dialogsState.currentDialog,
currentIndex = dialogsState.metadata?.getInt(ListenDialogBundleKeys.EVENT_INDEX.name),
listens = uiState.listens,
onPin = {metadata, blurbContent -> socialViewModel.pin(metadata , blurbContent)},
searchUsers = {
query -> feedViewModel.searchUser(query)
},
feedUiState = feedUiState,
isCritiqueBrainzLinked = { feedViewModel.isCritiqueBrainzLinked() },
onReview = {type, blurbContent, rating, locale, metadata -> socialViewModel.review(metadata , type , blurbContent , rating , locale) },
onPersonallyRecommend = {metadata, users, blurbContent -> socialViewModel.personallyRecommend(metadata, users, blurbContent)}
)

// Loading Animation
AnimatedVisibility(
Expand All @@ -164,6 +270,75 @@ fun ListensScreen(
}
}

@Composable
private fun Dialogs(
deactivateDialog: () -> Unit,
currentDialog: Dialog,
feedUiState: FeedUiState,
currentIndex : Int?,
listens : List<Listen>,
onPin : (metadata: Metadata , blurbContent : String) -> Unit,
searchUsers : (String) -> Unit,
isCritiqueBrainzLinked: suspend () -> Boolean?,
onReview : (type: ReviewEntityType, blurbContent: String, rating: Int?, locale: String , metadata : Metadata) -> Unit,
onPersonallyRecommend : (metadata : Metadata , users : List<String> , blurbContent : String) -> Unit

) {
val context = LocalContext.current
when (currentDialog) {
Dialog.NONE -> Unit
Dialog.PIN -> {
PinDialog(trackName = listens[currentIndex!!].trackMetadata.trackName, artistName = listens[currentIndex].trackMetadata.artistName, onDismiss = deactivateDialog, onSubmit = {
blurbContent -> try {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here as well. Please take care everywhere you've done this.

onPin(Metadata(trackMetadata = listens[currentIndex].trackMetadata) , blurbContent)
Toast.makeText(context , Constants.Strings.PIN_GREETING , Toast.LENGTH_SHORT).show()
} catch (e : Error) {
Toast.makeText(context , Constants.Strings.ERROR_MESSAGE , Toast.LENGTH_SHORT).show()
}
})
}
Dialog.PERSONAL_RECOMMENDATION -> {
PersonalRecommendationDialog(
trackName = listens[currentIndex!!].trackMetadata.trackName,
onDismiss = deactivateDialog,
searchResult = feedUiState.searchResult,
searchUsers = searchUsers,
onSubmit = {
users, blurbContent -> try {
onPersonallyRecommend(
Metadata(trackMetadata = listens[currentIndex].trackMetadata),
users,
blurbContent
)
Toast.makeText(context , Constants.Strings.PERSONAL_RECOMMENDATION_GREETING , Toast.LENGTH_SHORT).show()
}
catch (e : Error) {
Toast.makeText(context , Constants.Strings.ERROR_MESSAGE , Toast.LENGTH_SHORT).show()
}
}
)
}
Dialog.REVIEW -> {
ReviewDialog(
trackName = listens[currentIndex!!].trackMetadata.trackName,
artistName = listens[currentIndex].trackMetadata.artistName,
releaseName = listens[currentIndex].trackMetadata.releaseName,
onDismiss = deactivateDialog,
isCritiqueBrainzLinked = isCritiqueBrainzLinked,
onSubmit = {
type, blurbContent, rating, locale -> try {
onReview(type, blurbContent, rating, locale , Metadata(trackMetadata = listens[currentIndex].trackMetadata))
Toast.makeText(context , Constants.Strings.REVIEW_GREETING , Toast.LENGTH_SHORT).show()
}
catch (e : Error) {
Toast.makeText(context , Constants.Strings.ERROR_MESSAGE , Toast.LENGTH_SHORT).show()
}
}
)
}
}
}

@Preview
@Composable
fun ListensScreenPreview() {
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/org/listenbrainz/android/util/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ object Constants {
const val REFRESH_TOKEN = "refresh_token"
const val STATUS_LOGGED_IN = 1
const val STATUS_LOGGED_OUT = 0
const val RECOMMENDATION_GREETING = "Song recommended successfully!"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pranavkonidena Please move these to the strings file in res. That would enable us to be able to translate these.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pranavkonidena please remove the strings from Constants and use them directly from res/strings

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@akshaaatt So sorry , I misunderstood what I needed to do.. hopefully it is fixed now

const val PERSONAL_RECOMMENDATION_GREETING = "Song recommendation sent successfully!"
const val REVIEW_GREETING = "Song review submitted successfully!"
const val PIN_GREETING = "Song pinned successfully!"
const val ERROR_MESSAGE = "Error occurred! Please try again later!"

const val CHANNEL_NOTI_SCROBBLING = "noti_scrobbling"
const val CHANNEL_NOTI_SCR_ERR = "noti_scrobble_errors"
Expand Down
Loading