From 614e3fa7c49b7f7a10e2e28fe53d0251282cf137 Mon Sep 17 00:00:00 2001 From: Pranav <122373207+pranavkonidena@users.noreply.github.com> Date: Thu, 15 Feb 2024 18:27:18 +0530 Subject: [PATCH 1/9] Added review , recommend , pin , personal recommendation in profile screen --- .../ui/screens/listens/ListensScreen.kt | 183 +++++++++++++++++- .../listenbrainz/android/util/Constants.kt | 5 + 2 files changed, 184 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt index 4c83d3d2..26da7592 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt @@ -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 @@ -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 @@ -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(), + 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 = 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) { @@ -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, @@ -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) + Toast.makeText(context , "Song recommended successfully!" , Toast.LENGTH_SHORT).show() + } + catch (e : Error) { + Toast.makeText(context , "Error Occoured!" , 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( @@ -164,6 +270,75 @@ fun ListensScreen( } } +@Composable +private fun Dialogs( + deactivateDialog: () -> Unit, + currentDialog: Dialog, + feedUiState: FeedUiState, + currentIndex : Int?, + listens : List, + 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 , 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 { + 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() { diff --git a/app/src/main/java/org/listenbrainz/android/util/Constants.kt b/app/src/main/java/org/listenbrainz/android/util/Constants.kt index 16deebdd..74cc0e63 100644 --- a/app/src/main/java/org/listenbrainz/android/util/Constants.kt +++ b/app/src/main/java/org/listenbrainz/android/util/Constants.kt @@ -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!" + 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" From 49cfe0224e9bea6bc2dfc11cdfbb9c1783a6a0c9 Mon Sep 17 00:00:00 2001 From: Pranav <122373207+pranavkonidena@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:51:00 +0530 Subject: [PATCH 2/9] Used error message , greeting everywhere --- .../listenbrainz/android/ui/screens/listens/ListensScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt index 26da7592..4aa9b2e9 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt @@ -201,10 +201,10 @@ fun ListensScreen( onRecommend = { try { socialViewModel.recommend(metadata) - Toast.makeText(context , "Song recommended successfully!" , Toast.LENGTH_SHORT).show() + Toast.makeText(context , Constants.Strings.RECOMMENDATION_GREETING , Toast.LENGTH_SHORT).show() } catch (e : Error) { - Toast.makeText(context , "Error Occoured!" , Toast.LENGTH_SHORT).show() + Toast.makeText(context , Constants.Strings.ERROR_MESSAGE , Toast.LENGTH_SHORT).show() } dropdownItemIndex.value = null }, From ef65ecd3df66ffd005f88a5ffba16629a252d93f Mon Sep 17 00:00:00 2001 From: Pranav <122373207+pranavkonidena@users.noreply.github.com> Date: Sun, 18 Feb 2024 11:38:49 +0530 Subject: [PATCH 3/9] Removed unused profile route and socialviewModel in yim23 --- .../ui/screens/yim23/YearInMusic23Activity.kt | 4 +--- .../yim23/navigation/Yim23Navigation.kt | 19 ------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/yim23/YearInMusic23Activity.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/yim23/YearInMusic23Activity.kt index a3700dcc..f641f483 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/yim23/YearInMusic23Activity.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/yim23/YearInMusic23Activity.kt @@ -25,7 +25,6 @@ class YearInMusic23Activity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val yim23ViewModel : Yim23ViewModel by viewModels() - val socialViewModel : SocialViewModel by viewModels() val networkConnectivityViewModel: NetworkConnectivityViewModel = ViewModelProvider(this)[NetworkConnectivityViewModelImpl::class.java] @@ -37,8 +36,7 @@ class YearInMusic23Activity : ComponentActivity() { Toast.LENGTH_LONG).show() finish() } - Yim23Navigation(yimViewModel = yim23ViewModel, socialViewModel = socialViewModel - , networkConnectivityViewModel = networkConnectivityViewModel, activity = this) + Yim23Navigation(yimViewModel = yim23ViewModel ,networkConnectivityViewModel = networkConnectivityViewModel, activity = this) } } } \ No newline at end of file diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/yim23/navigation/Yim23Navigation.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/yim23/navigation/Yim23Navigation.kt index e7f5a3ec..da15981a 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/yim23/navigation/Yim23Navigation.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/yim23/navigation/Yim23Navigation.kt @@ -61,13 +61,10 @@ private const val screenTransitionDuration = 900 @Composable fun Yim23Navigation( yimViewModel: Yim23ViewModel, - socialViewModel: SocialViewModel, activity: ComponentActivity, networkConnectivityViewModel: NetworkConnectivityViewModel, ) { val navController = rememberNavController() - var scrollToTopState by remember { mutableStateOf(false) } - val scope = rememberCoroutineScope() NavHost( navController = navController, modifier = Modifier.fillMaxSize(), @@ -94,22 +91,6 @@ fun Yim23Navigation( Yim23HomeScreen(viewModel = yimViewModel, networkConnectivityViewModel = networkConnectivityViewModel,navController = navController, activity = activity) } - composable(route = AppNavigationItem.Profile.route){ - Surface (color = ListenBrainzTheme.colorScheme.background) { - ProfileScreen( - onScrollToTop = { scrollToTop -> - scope.launch { - if (scrollToTopState){ - scrollToTop() - scrollToTopState = false - } - } - }, - scrollRequestState = false - ) - } - } - addYimScreen( route = Yim23Screens.YimChartTitleScreen.name ){ Yim23ChartTitleScreen(viewModel = yimViewModel, navController = navController) From b27b88226bfb4ca250ccfc52b221e3e6884c503e Mon Sep 17 00:00:00 2001 From: Pranav <122373207+pranavkonidena@users.noreply.github.com> Date: Sun, 18 Feb 2024 11:49:50 +0530 Subject: [PATCH 4/9] Passed down snackBarState as reqd and used snackbar's instead of toasts --- .../android/ui/navigation/AppNavigation.kt | 5 +- .../ui/screens/listens/ListensScreen.kt | 62 +++++++++++++------ .../android/ui/screens/main/MainActivity.kt | 5 +- .../ui/screens/profile/ProfileScreen.kt | 7 ++- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/listenbrainz/android/ui/navigation/AppNavigation.kt b/app/src/main/java/org/listenbrainz/android/ui/navigation/AppNavigation.kt index 84f9345a..bc13fcfd 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/navigation/AppNavigation.kt @@ -1,6 +1,7 @@ package org.listenbrainz.android.ui.navigation import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation.NavController @@ -20,6 +21,7 @@ fun AppNavigation( navController: NavController = rememberNavController(), scrollRequestState: Boolean, onScrollToTop: (suspend () -> Unit) -> Unit, + snackbarState : SnackbarHostState ) { NavHost( navController = navController as NavHostController, @@ -38,7 +40,8 @@ fun AppNavigation( composable(route = AppNavigationItem.Profile.route){ ProfileScreen( onScrollToTop = onScrollToTop, - scrollRequestState = scrollRequestState + scrollRequestState = scrollRequestState, + snackbarState = snackbarState ) } composable(route = AppNavigationItem.Settings.route){ diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt index 4aa9b2e9..f9416fb5 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt @@ -17,12 +17,14 @@ 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.material3.SnackbarHostState 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.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -31,6 +33,7 @@ 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 kotlinx.coroutines.launch import org.listenbrainz.android.model.Listen import org.listenbrainz.android.model.Metadata import org.listenbrainz.android.model.TrackMetadata @@ -59,6 +62,7 @@ fun ListensScreen( socialViewModel: SocialViewModel = hiltViewModel(), scrollRequestState: Boolean, onScrollToTop: (suspend () -> Unit) -> Unit, + snackbarState : SnackbarHostState ) { val uiState by viewModel.uiState.collectAsState() @@ -80,7 +84,8 @@ fun ListensScreen( }, playListen = { socialViewModel.playListen(it) - } + }, + snackbarState = snackbarState ) } @@ -112,7 +117,8 @@ fun ListensScreen( validateUserToken: suspend (String) -> Boolean, setToken: (String) -> Unit, playListen: (TrackMetadata) -> Unit, - uriHandler: UriHandler = LocalUriHandler.current + uriHandler: UriHandler = LocalUriHandler.current, + snackbarState: SnackbarHostState ) { val listState = rememberLazyListState() val dropdownItemIndex: MutableState = rememberSaveable { @@ -120,7 +126,7 @@ fun ListensScreen( } val dialogsState = rememberDialogsState() val feedUiState by feedViewModel.uiState.collectAsState() - val context = LocalContext.current + val scope = rememberCoroutineScope() // Scroll to the top when shouldScrollToTop becomes true @@ -201,10 +207,14 @@ fun ListensScreen( onRecommend = { try { socialViewModel.recommend(metadata) - Toast.makeText(context , Constants.Strings.RECOMMENDATION_GREETING , Toast.LENGTH_SHORT).show() + scope.launch { + snackbarState.showSnackbar(Constants.Strings.RECOMMENDATION_GREETING) + } } catch (e : Error) { - Toast.makeText(context , Constants.Strings.ERROR_MESSAGE , Toast.LENGTH_SHORT).show() + scope.launch { + snackbarState.showSnackbar(Constants.Strings.ERROR_MESSAGE) + } } dropdownItemIndex.value = null }, @@ -225,7 +235,9 @@ fun ListensScreen( uriHandler.openUri("https://musicbrainz.org/recording/${metadata.trackMetadata?.mbidMapping?.recordingMbid}") } catch(e : Error) { - Toast.makeText(context , Constants.Strings.ERROR_MESSAGE , Toast.LENGTH_SHORT).show() + scope.launch { + snackbarState.showSnackbar(Constants.Strings.ERROR_MESSAGE) + } } dropdownItemIndex.value = null } @@ -255,7 +267,8 @@ fun ListensScreen( 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)} + onPersonallyRecommend = {metadata, users, blurbContent -> socialViewModel.personallyRecommend(metadata, users, blurbContent)}, + snackbarState = snackbarState ) // Loading Animation @@ -281,19 +294,23 @@ private fun Dialogs( searchUsers : (String) -> Unit, isCritiqueBrainzLinked: suspend () -> Boolean?, onReview : (type: ReviewEntityType, blurbContent: String, rating: Int?, locale: String , metadata : Metadata) -> Unit, - onPersonallyRecommend : (metadata : Metadata , users : List , blurbContent : String) -> Unit - + onPersonallyRecommend : (metadata : Metadata , users : List , blurbContent : String) -> Unit, + snackbarState: SnackbarHostState ) { - val context = LocalContext.current + val scope = rememberCoroutineScope() when (currentDialog) { Dialog.NONE -> Unit Dialog.PIN -> { PinDialog(trackName = listens[currentIndex!!].trackMetadata.trackName, artistName = listens[currentIndex].trackMetadata.artistName, onDismiss = deactivateDialog, onSubmit = { blurbContent -> try { onPin(Metadata(trackMetadata = listens[currentIndex].trackMetadata) , blurbContent) - Toast.makeText(context , Constants.Strings.PIN_GREETING , Toast.LENGTH_SHORT).show() + scope.launch { + snackbarState.showSnackbar(Constants.Strings.PIN_GREETING) + } } catch (e : Error) { - Toast.makeText(context , Constants.Strings.ERROR_MESSAGE , Toast.LENGTH_SHORT).show() + scope.launch { + snackbarState.showSnackbar(Constants.Strings.ERROR_MESSAGE) + } } }) } @@ -310,10 +327,14 @@ private fun Dialogs( users, blurbContent ) - Toast.makeText(context , Constants.Strings.PERSONAL_RECOMMENDATION_GREETING , Toast.LENGTH_SHORT).show() + scope.launch { + snackbarState.showSnackbar(Constants.Strings.PERSONAL_RECOMMENDATION_GREETING) + } } catch (e : Error) { - Toast.makeText(context , Constants.Strings.ERROR_MESSAGE , Toast.LENGTH_SHORT).show() + scope.launch { + snackbarState.showSnackbar(Constants.Strings.ERROR_MESSAGE) + } } } ) @@ -328,11 +349,15 @@ private fun Dialogs( 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() + scope.launch { + snackbarState.showSnackbar(Constants.Strings.REVIEW_GREETING) + } } catch (e : Error) { - Toast.makeText(context , Constants.Strings.ERROR_MESSAGE , Toast.LENGTH_SHORT).show() - } + scope.launch { + snackbarState.showSnackbar(Constants.Strings.ERROR_MESSAGE) + } + } } ) } @@ -350,6 +375,7 @@ fun ListensScreenPreview() { preferencesUiState = PreferencesUiState(), validateUserToken = { true }, setToken = {}, - playListen = {} + playListen = {}, + snackbarState = SnackbarHostState() ) } diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt index 925972dc..48890922 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/main/MainActivity.kt @@ -138,7 +138,7 @@ class MainActivity : ComponentActivity() { val backdropScaffoldState = rememberBackdropScaffoldState(initialValue = BackdropValue.Revealed) var scrollToTopState by remember { mutableStateOf(false) } - val snackbarState = SnackbarHostState() + val snackbarState = remember { SnackbarHostState() } val searchBarState = rememberSearchBarState() val scope = rememberCoroutineScope() @@ -183,7 +183,8 @@ class MainActivity : ComponentActivity() { scrollToTopState = false } } - } + }, + snackbarState = snackbarState ) } } diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/profile/ProfileScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/profile/ProfileScreen.kt index 2a70412e..f0206fd5 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/profile/ProfileScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/profile/ProfileScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -44,7 +45,8 @@ fun ProfileScreen( context: Context = LocalContext.current, viewModel: ProfileViewModel = hiltViewModel(), scrollRequestState: Boolean, - onScrollToTop: (suspend () -> Unit) -> Unit + onScrollToTop: (suspend () -> Unit) -> Unit, + snackbarState : SnackbarHostState ) { val scrollState = rememberScrollState() @@ -61,7 +63,8 @@ fun ProfileScreen( STATUS_LOGGED_IN -> { ListensScreen( onScrollToTop = onScrollToTop, - scrollRequestState = scrollRequestState + scrollRequestState = scrollRequestState, + snackbarState = snackbarState ) } else -> { From b6d3f3c64ad11ddedadeaf00109be5684f70b698 Mon Sep 17 00:00:00 2001 From: Pranav <122373207+pranavkonidena@users.noreply.github.com> Date: Sun, 18 Feb 2024 21:47:58 +0530 Subject: [PATCH 5/9] Made seperate flow for success messages for actions in socialViewModel , used it to display success message --- .../android/model/SocialUiState.kt | 3 +- .../android/ui/components/SuccessBar.kt | 60 +++++++++++++ .../ui/screens/listens/ListensScreen.kt | 86 +++++++++---------- .../android/viewmodel/BaseViewModel.kt | 12 ++- .../android/viewmodel/SocialViewModel.kt | 22 ++++- 5 files changed, 132 insertions(+), 51 deletions(-) create mode 100644 app/src/main/java/org/listenbrainz/android/ui/components/SuccessBar.kt diff --git a/app/src/main/java/org/listenbrainz/android/model/SocialUiState.kt b/app/src/main/java/org/listenbrainz/android/model/SocialUiState.kt index c6faa9bf..f06043e4 100644 --- a/app/src/main/java/org/listenbrainz/android/model/SocialUiState.kt +++ b/app/src/main/java/org/listenbrainz/android/model/SocialUiState.kt @@ -1,5 +1,6 @@ package org.listenbrainz.android.model data class SocialUiState( - val error: ResponseError? = null + val error: ResponseError? = null, + val successMsg : String? = null ) \ No newline at end of file diff --git a/app/src/main/java/org/listenbrainz/android/ui/components/SuccessBar.kt b/app/src/main/java/org/listenbrainz/android/ui/components/SuccessBar.kt new file mode 100644 index 00000000..48a92dc2 --- /dev/null +++ b/app/src/main/java/org/listenbrainz/android/ui/components/SuccessBar.kt @@ -0,0 +1,60 @@ +package org.listenbrainz.android.ui.components + +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.listenbrainz.android.model.ResponseError +import org.listenbrainz.android.ui.theme.ListenBrainzTheme + + +@Composable +fun SuccessBar( + message : String?, + onMessageShown: () -> Unit, + snackbarState : SnackbarHostState +) { + LaunchedEffect(message) { + if (message != null) { + delay(4000) + onMessageShown() + } + } + + AnimatedVisibility( + visible = message != null, + enter = expandVertically(), + exit = shrinkVertically() + ) { + LaunchedEffect(key1 = message){ + if(message != null){ + snackbarState.showSnackbar(message) + } + } + } +} + +@Preview +@Preview(uiMode = UI_MODE_NIGHT_YES) +@Composable +private fun SuccessBarPreview() { + ListenBrainzTheme { + ErrorBar(error = ResponseError.NETWORK_ERROR) {} + } +} \ No newline at end of file diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt index f9416fb5..53f4caf4 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt @@ -1,7 +1,6 @@ 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 @@ -28,7 +27,6 @@ import androidx.compose.runtime.rememberCoroutineScope 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 @@ -36,10 +34,13 @@ import androidx.hilt.navigation.compose.hiltViewModel import kotlinx.coroutines.launch import org.listenbrainz.android.model.Listen import org.listenbrainz.android.model.Metadata +import org.listenbrainz.android.model.SocialUiState import org.listenbrainz.android.model.TrackMetadata import org.listenbrainz.android.model.feed.ReviewEntityType +import org.listenbrainz.android.ui.components.ErrorBar import org.listenbrainz.android.ui.components.ListenCardSmall import org.listenbrainz.android.ui.components.LoadingAnimation +import org.listenbrainz.android.ui.components.SuccessBar import org.listenbrainz.android.ui.components.dialogs.Dialog import org.listenbrainz.android.ui.components.dialogs.PersonalRecommendationDialog import org.listenbrainz.android.ui.components.dialogs.PinDialog @@ -67,6 +68,7 @@ fun ListensScreen( val uiState by viewModel.uiState.collectAsState() val preferencesUiState by viewModel.preferencesUiState.collectAsState() + val socialUiState by socialViewModel.uiState.collectAsState() ListensScreen( scrollRequestState = scrollRequestState, @@ -85,7 +87,8 @@ fun ListensScreen( playListen = { socialViewModel.playListen(it) }, - snackbarState = snackbarState + snackbarState = snackbarState, + socialUiState = socialUiState ) } @@ -118,7 +121,8 @@ fun ListensScreen( setToken: (String) -> Unit, playListen: (TrackMetadata) -> Unit, uriHandler: UriHandler = LocalUriHandler.current, - snackbarState: SnackbarHostState + snackbarState: SnackbarHostState, + socialUiState: SocialUiState ) { val listState = rememberLazyListState() val dropdownItemIndex: MutableState = rememberSaveable { @@ -127,7 +131,6 @@ fun ListensScreen( val dialogsState = rememberDialogsState() val feedUiState by feedViewModel.uiState.collectAsState() val scope = rememberCoroutineScope() - // Scroll to the top when shouldScrollToTop becomes true LaunchedEffect(scrollRequestState) { @@ -205,17 +208,7 @@ fun ListensScreen( }, metadata = metadata, onRecommend = { - try { - socialViewModel.recommend(metadata) - scope.launch { - snackbarState.showSnackbar(Constants.Strings.RECOMMENDATION_GREETING) - } - } - catch (e : Error) { - scope.launch { - snackbarState.showSnackbar(Constants.Strings.ERROR_MESSAGE) - } - } + socialViewModel.recommend(metadata) dropdownItemIndex.value = null }, onPersonallyRecommend = { @@ -253,6 +246,10 @@ fun ListensScreen( } } } + + ErrorBar(error = socialUiState.error, onErrorShown = { socialViewModel.clearErrorFlow() }) + SuccessBar(message = socialUiState.successMsg, onMessageShown = { socialViewModel.clearMsgFlow() }, snackbarState = snackbarState) + Dialogs( deactivateDialog = { dialogsState.deactivateDialog() @@ -268,7 +265,8 @@ fun ListensScreen( 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)}, - snackbarState = snackbarState + snackbarState = snackbarState, + socialUiState = socialUiState ) // Loading Animation @@ -295,24 +293,22 @@ private fun Dialogs( isCritiqueBrainzLinked: suspend () -> Boolean?, onReview : (type: ReviewEntityType, blurbContent: String, rating: Int?, locale: String , metadata : Metadata) -> Unit, onPersonallyRecommend : (metadata : Metadata , users : List , blurbContent : String) -> Unit, - snackbarState: SnackbarHostState + snackbarState: SnackbarHostState, + socialUiState: SocialUiState ) { val scope = rememberCoroutineScope() when (currentDialog) { Dialog.NONE -> Unit Dialog.PIN -> { PinDialog(trackName = listens[currentIndex!!].trackMetadata.trackName, artistName = listens[currentIndex].trackMetadata.artistName, onDismiss = deactivateDialog, onSubmit = { - blurbContent -> try { + blurbContent -> onPin(Metadata(trackMetadata = listens[currentIndex].trackMetadata) , blurbContent) - scope.launch { + }) + LaunchedEffect(socialUiState.error){ + if(socialUiState.error == null){ snackbarState.showSnackbar(Constants.Strings.PIN_GREETING) } - } catch (e : Error) { - scope.launch { - snackbarState.showSnackbar(Constants.Strings.ERROR_MESSAGE) - } - } - }) + } } Dialog.PERSONAL_RECOMMENDATION -> { PersonalRecommendationDialog( @@ -321,22 +317,18 @@ private fun Dialogs( searchResult = feedUiState.searchResult, searchUsers = searchUsers, onSubmit = { - users, blurbContent -> try { + users, blurbContent -> onPersonallyRecommend( Metadata(trackMetadata = listens[currentIndex].trackMetadata), users, blurbContent ) - scope.launch { - snackbarState.showSnackbar(Constants.Strings.PERSONAL_RECOMMENDATION_GREETING) - } - } - catch (e : Error) { - scope.launch { - snackbarState.showSnackbar(Constants.Strings.ERROR_MESSAGE) + if(socialUiState.error == null){ + scope.launch { + snackbarState.showSnackbar(Constants.Strings.PERSONAL_RECOMMENDATION_GREETING) + } } } - } ) } Dialog.REVIEW -> { @@ -346,18 +338,19 @@ private fun Dialogs( 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)) - scope.launch { - snackbarState.showSnackbar(Constants.Strings.REVIEW_GREETING) + onSubmit = { type, blurbContent, rating, locale -> + onReview( + type, + blurbContent, + rating, + locale, + Metadata(trackMetadata = listens[currentIndex].trackMetadata) + ) + if (socialUiState.error == null) { + scope.launch { + snackbarState.showSnackbar(Constants.Strings.REVIEW_GREETING) } } - catch (e : Error) { - scope.launch { - snackbarState.showSnackbar(Constants.Strings.ERROR_MESSAGE) - } - } } ) } @@ -376,6 +369,7 @@ fun ListensScreenPreview() { validateUserToken = { true }, setToken = {}, playListen = {}, - snackbarState = SnackbarHostState() + snackbarState = SnackbarHostState(), + socialUiState = SocialUiState() ) } diff --git a/app/src/main/java/org/listenbrainz/android/viewmodel/BaseViewModel.kt b/app/src/main/java/org/listenbrainz/android/viewmodel/BaseViewModel.kt index 4a118d8f..042be486 100644 --- a/app/src/main/java/org/listenbrainz/android/viewmodel/BaseViewModel.kt +++ b/app/src/main/java/org/listenbrainz/android/viewmodel/BaseViewModel.kt @@ -10,7 +10,7 @@ import org.listenbrainz.android.model.ResponseError abstract class BaseViewModel: ViewModel() { protected val errorFlow = MutableStateFlow(null) - + protected val successMsgFlow = MutableStateFlow(null) /** Visible Ui-State for UI to consume.*/ abstract val uiState: StateFlow @@ -22,9 +22,19 @@ abstract class BaseViewModel: ViewModel() { errorFlow.emit(error) } } + + protected fun emitMsg(message : String?){ + viewModelScope.launch { + successMsgFlow.emit(message) + } + } /** Call this function to reset [errorFlow]'s latest emission.*/ fun clearErrorFlow() { emitError(null) } + + fun clearMsgFlow() { + emitMsg(null) + } } \ No newline at end of file diff --git a/app/src/main/java/org/listenbrainz/android/viewmodel/SocialViewModel.kt b/app/src/main/java/org/listenbrainz/android/viewmodel/SocialViewModel.kt index 605e7e33..a77b6d21 100644 --- a/app/src/main/java/org/listenbrainz/android/viewmodel/SocialViewModel.kt +++ b/app/src/main/java/org/listenbrainz/android/viewmodel/SocialViewModel.kt @@ -6,7 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -23,6 +23,7 @@ import org.listenbrainz.android.model.feed.ReviewEntityType import org.listenbrainz.android.repository.preferences.AppPreferences import org.listenbrainz.android.repository.remoteplayer.RemotePlaybackHandler import org.listenbrainz.android.repository.social.SocialRepository +import org.listenbrainz.android.util.Constants import org.listenbrainz.android.util.Resource import javax.inject.Inject @@ -39,8 +40,11 @@ class SocialViewModel @Inject constructor( override val uiState: StateFlow = createUiStateFlow() override fun createUiStateFlow(): StateFlow = - errorFlow.map { - SocialUiState(it) + combine( + errorFlow, + successMsgFlow + ){ + error , message -> SocialUiState(error , message) }.stateIn( viewModelScope, SharingStarted.Eagerly, @@ -103,6 +107,9 @@ class SocialViewModel @Inject constructor( if (result.status == Resource.Status.FAILED){ emitError(result.error) } + else if(result.status == Resource.Status.SUCCESS){ + emitMsg(Constants.Strings.RECOMMENDATION_GREETING) + } } } @@ -127,6 +134,9 @@ class SocialViewModel @Inject constructor( if (result.status == Resource.Status.FAILED){ emitError(result.error) } + else if(result.status == Resource.Status.SUCCESS){ + emitMsg(Constants.Strings.PERSONAL_RECOMMENDATION_GREETING) + } } } @@ -151,6 +161,9 @@ class SocialViewModel @Inject constructor( if (result.status == Resource.Status.FAILED){ emitError(result.error) } + else if(result.status == Resource.Status.SUCCESS){ + emitMsg(Constants.Strings.REVIEW_GREETING) + } } } @@ -166,6 +179,9 @@ class SocialViewModel @Inject constructor( if (result.status == Resource.Status.FAILED){ emitError(result.error) } + else if(result.status == Resource.Status.SUCCESS){ + emitMsg(Constants.Strings.PIN_GREETING) + } } } From da3b2a8a09984a48e15d4a4737c8fcd05e14bdf7 Mon Sep 17 00:00:00 2001 From: Pranav <122373207+pranavkonidena@users.noreply.github.com> Date: Sun, 18 Feb 2024 22:15:37 +0530 Subject: [PATCH 6/9] Removed viewmodels from listen screen so that preview renders --- .../ui/screens/listens/ListensScreen.kt | 98 +++++++++++-------- 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt index 53f4caf4..6cad2566 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt @@ -61,6 +61,7 @@ import org.listenbrainz.android.viewmodel.SocialViewModel fun ListensScreen( viewModel: ListensViewModel = hiltViewModel(), socialViewModel: SocialViewModel = hiltViewModel(), + feedViewModel : FeedViewModel = hiltViewModel(), scrollRequestState: Boolean, onScrollToTop: (suspend () -> Unit) -> Unit, snackbarState : SnackbarHostState @@ -69,15 +70,21 @@ fun ListensScreen( val uiState by viewModel.uiState.collectAsState() val preferencesUiState by viewModel.preferencesUiState.collectAsState() val socialUiState by socialViewModel.uiState.collectAsState() - + val feedUiState by feedViewModel.uiState.collectAsState() + val dropdownItemIndex: MutableState = rememberSaveable { + mutableStateOf(null) + } + ListensScreen( scrollRequestState = scrollRequestState, onScrollToTop = onScrollToTop, uiState = uiState, + feedUiState = feedUiState, preferencesUiState = preferencesUiState, updateNotificationServicePermissionStatus = { viewModel.updateNotificationServicePermissionStatus() }, + dropdownItemIndex = dropdownItemIndex, validateUserToken = { token -> viewModel.validateUserToken(token) }, @@ -88,7 +95,33 @@ fun ListensScreen( socialViewModel.playListen(it) }, snackbarState = snackbarState, - socialUiState = socialUiState + socialUiState = socialUiState, + onRecommend = {metadata -> + socialViewModel.recommend(metadata) + dropdownItemIndex.value = null + }, + onErrorShown = { + socialViewModel.clearErrorFlow() + }, + onMessageShown = { + socialViewModel.clearMsgFlow() + }, + onPin = { + metadata, blurbContent -> socialViewModel.pin(metadata , blurbContent) + dropdownItemIndex.value = null + }, + searchUsers = { + query -> feedViewModel.searchUser(query) + }, + 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) + } ) } @@ -112,24 +145,30 @@ private enum class ListenDialogBundleKeys { fun ListensScreen( scrollRequestState: Boolean, onScrollToTop: (suspend () -> Unit) -> Unit, - socialViewModel: SocialViewModel = hiltViewModel(), - feedViewModel: FeedViewModel = hiltViewModel(), uiState: ListensUiState, + feedUiState: FeedUiState, preferencesUiState: PreferencesUiState, updateNotificationServicePermissionStatus: () -> Unit, + dropdownItemIndex : MutableState, validateUserToken: suspend (String) -> Boolean, setToken: (String) -> Unit, playListen: (TrackMetadata) -> Unit, uriHandler: UriHandler = LocalUriHandler.current, snackbarState: SnackbarHostState, - socialUiState: SocialUiState + socialUiState: SocialUiState, + onRecommend : (metadata : Metadata) -> Unit, + onErrorShown : () -> Unit, + onMessageShown : () -> Unit, + 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, blurbContent: String) -> Unit ) { val listState = rememberLazyListState() - val dropdownItemIndex: MutableState = rememberSaveable { - mutableStateOf(null) - } + val dialogsState = rememberDialogsState() - val feedUiState by feedViewModel.uiState.collectAsState() + val scope = rememberCoroutineScope() // Scroll to the top when shouldScrollToTop becomes true @@ -207,10 +246,7 @@ fun ListensScreen( dropdownItemIndex.value = null }, metadata = metadata, - onRecommend = { - socialViewModel.recommend(metadata) - dropdownItemIndex.value = null - }, + onRecommend = { onRecommend(metadata) }, onPersonallyRecommend = { dialogsState.activateDialog(Dialog.PERSONAL_RECOMMENDATION , ListenDialogBundleKeys.listenDialogBundle(0, index)) dropdownItemIndex.value = null @@ -247,8 +283,8 @@ fun ListensScreen( } } - ErrorBar(error = socialUiState.error, onErrorShown = { socialViewModel.clearErrorFlow() }) - SuccessBar(message = socialUiState.successMsg, onMessageShown = { socialViewModel.clearMsgFlow() }, snackbarState = snackbarState) + ErrorBar(error = socialUiState.error, onErrorShown = onErrorShown ) + SuccessBar(message = socialUiState.successMsg, onMessageShown = onMessageShown, snackbarState = snackbarState) Dialogs( deactivateDialog = { @@ -257,14 +293,12 @@ fun ListensScreen( 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) - }, + onPin = {metadata, blurbContent -> onPin(metadata, blurbContent)}, + searchUsers = { query -> searchUsers(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)}, + isCritiqueBrainzLinked = isCritiqueBrainzLinked, + onReview = {type, blurbContent, rating, locale, metadata -> onReview(type, blurbContent, rating, locale, metadata) }, + onPersonallyRecommend = {metadata, users, blurbContent -> onPersonallyRecommend(metadata, users, blurbContent)}, snackbarState = snackbarState, socialUiState = socialUiState ) @@ -296,7 +330,6 @@ private fun Dialogs( snackbarState: SnackbarHostState, socialUiState: SocialUiState ) { - val scope = rememberCoroutineScope() when (currentDialog) { Dialog.NONE -> Unit Dialog.PIN -> { @@ -323,11 +356,6 @@ private fun Dialogs( users, blurbContent ) - if(socialUiState.error == null){ - scope.launch { - snackbarState.showSnackbar(Constants.Strings.PERSONAL_RECOMMENDATION_GREETING) - } - } } ) } @@ -346,11 +374,6 @@ private fun Dialogs( locale, Metadata(trackMetadata = listens[currentIndex].trackMetadata) ) - if (socialUiState.error == null) { - scope.launch { - snackbarState.showSnackbar(Constants.Strings.REVIEW_GREETING) - } - } } ) } @@ -361,15 +384,8 @@ private fun Dialogs( @Composable fun ListensScreenPreview() { ListensScreen( - onScrollToTop = {}, scrollRequestState = false, - updateNotificationServicePermissionStatus = {}, - uiState = ListensUiState(), - preferencesUiState = PreferencesUiState(), - validateUserToken = { true }, - setToken = {}, - playListen = {}, + onScrollToTop = {}, snackbarState = SnackbarHostState(), - socialUiState = SocialUiState() ) } From cbfe3f87c4046b96742461162b235c8ba9daff9a Mon Sep 17 00:00:00 2001 From: Pranav <122373207+pranavkonidena@users.noreply.github.com> Date: Sun, 18 Feb 2024 22:47:17 +0530 Subject: [PATCH 7/9] Add reqd params in preview function --- .../ui/screens/listens/ListensScreen.kt | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt index 6cad2566..995c4631 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt @@ -23,6 +23,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment @@ -384,8 +385,25 @@ private fun Dialogs( @Composable fun ListensScreenPreview() { ListensScreen( - scrollRequestState = false, onScrollToTop = {}, - snackbarState = SnackbarHostState(), + scrollRequestState = false, + updateNotificationServicePermissionStatus = {}, + uiState = ListensUiState(), + feedUiState = FeedUiState(), + preferencesUiState = PreferencesUiState(), + validateUserToken = { true }, + setToken = {}, + playListen = {}, + socialUiState = SocialUiState(), + onRecommend = {}, + onErrorShown = {}, + onMessageShown = {}, + onPin = {_,_ ->}, + searchUsers = {_ ->}, + isCritiqueBrainzLinked = { true }, + onReview = {_,_,_,_,_ ->}, + onPersonallyRecommend = {_,_,_ ->}, + dropdownItemIndex = remember { mutableStateOf(null) }, + snackbarState = remember { SnackbarHostState() } ) } From a87ae57002fbd4136fbeac52be3e4496ccfb673e Mon Sep 17 00:00:00 2001 From: Pranav <122373207+pranavkonidena@users.noreply.github.com> Date: Wed, 21 Feb 2024 19:09:21 +0530 Subject: [PATCH 8/9] Added greeting texts to string resources for locale translation --- app/src/main/res/values/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 43c9be5d..1695cdbb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -93,6 +93,11 @@ Add some text to search No email client found Search history deleted + Song recommended successfully! + Song recommendation sent successfully! + Song review submitted successfully! + Song pinned successfully! + Error occurred! Please try again later! You need to login in order to view your collections. From 73ad09b4fc89c2ecc5bed0edd8cae54d7ab6af78 Mon Sep 17 00:00:00 2001 From: Pranav <122373207+pranavkonidena@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:42:10 +0530 Subject: [PATCH 9/9] Removed pin,review,recommend strings from constants --- .../android/model/SocialUiState.kt | 2 +- .../android/ui/components/SuccessBar.kt | 32 +++++++------------ .../ui/screens/listens/ListensScreen.kt | 12 ++++--- .../listenbrainz/android/util/Constants.kt | 5 --- .../android/viewmodel/BaseViewModel.kt | 6 ++-- .../android/viewmodel/SocialViewModel.kt | 10 +++--- 6 files changed, 28 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/org/listenbrainz/android/model/SocialUiState.kt b/app/src/main/java/org/listenbrainz/android/model/SocialUiState.kt index f06043e4..c2d24bf6 100644 --- a/app/src/main/java/org/listenbrainz/android/model/SocialUiState.kt +++ b/app/src/main/java/org/listenbrainz/android/model/SocialUiState.kt @@ -2,5 +2,5 @@ package org.listenbrainz.android.model data class SocialUiState( val error: ResponseError? = null, - val successMsg : String? = null + val successMsgId : Int? = null ) \ No newline at end of file diff --git a/app/src/main/java/org/listenbrainz/android/ui/components/SuccessBar.kt b/app/src/main/java/org/listenbrainz/android/ui/components/SuccessBar.kt index 48a92dc2..aa6aa344 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/components/SuccessBar.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/components/SuccessBar.kt @@ -4,47 +4,37 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Text import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import org.listenbrainz.android.model.ResponseError import org.listenbrainz.android.ui.theme.ListenBrainzTheme - +import org.listenbrainz.android.R @Composable fun SuccessBar( - message : String?, + resId : Int?, onMessageShown: () -> Unit, snackbarState : SnackbarHostState ) { - LaunchedEffect(message) { - if (message != null) { + val context = LocalContext.current; + LaunchedEffect(resId) { + if (resId != null) { delay(4000) onMessageShown() } } AnimatedVisibility( - visible = message != null, + visible = resId != null, enter = expandVertically(), exit = shrinkVertically() ) { - LaunchedEffect(key1 = message){ - if(message != null){ - snackbarState.showSnackbar(message) + LaunchedEffect(key1 = resId){ + if(resId != null){ + snackbarState.showSnackbar(context.getString(resId)) } } } @@ -55,6 +45,6 @@ fun SuccessBar( @Composable private fun SuccessBarPreview() { ListenBrainzTheme { - ErrorBar(error = ResponseError.NETWORK_ERROR) {} + SuccessBar(resId = R.string.about_title , onMessageShown = {} , snackbarState = SnackbarHostState()) } } \ No newline at end of file diff --git a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt index 995c4631..74204df4 100644 --- a/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt +++ b/app/src/main/java/org/listenbrainz/android/ui/screens/listens/ListensScreen.kt @@ -28,11 +28,13 @@ import androidx.compose.runtime.rememberCoroutineScope 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 kotlinx.coroutines.launch +import org.listenbrainz.android.R import org.listenbrainz.android.model.Listen import org.listenbrainz.android.model.Metadata import org.listenbrainz.android.model.SocialUiState @@ -52,7 +54,6 @@ 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 @@ -171,6 +172,8 @@ fun ListensScreen( val dialogsState = rememberDialogsState() val scope = rememberCoroutineScope() + + val context = LocalContext.current // Scroll to the top when shouldScrollToTop becomes true LaunchedEffect(scrollRequestState) { @@ -266,7 +269,7 @@ fun ListensScreen( } catch(e : Error) { scope.launch { - snackbarState.showSnackbar(Constants.Strings.ERROR_MESSAGE) + snackbarState.showSnackbar(context.getString(R.string.err_generic_toast)) } } dropdownItemIndex.value = null @@ -285,7 +288,7 @@ fun ListensScreen( } ErrorBar(error = socialUiState.error, onErrorShown = onErrorShown ) - SuccessBar(message = socialUiState.successMsg, onMessageShown = onMessageShown, snackbarState = snackbarState) + SuccessBar(resId = socialUiState.successMsgId, onMessageShown = onMessageShown, snackbarState = snackbarState) Dialogs( deactivateDialog = { @@ -331,6 +334,7 @@ private fun Dialogs( snackbarState: SnackbarHostState, socialUiState: SocialUiState ) { + val context = LocalContext.current when (currentDialog) { Dialog.NONE -> Unit Dialog.PIN -> { @@ -340,7 +344,7 @@ private fun Dialogs( }) LaunchedEffect(socialUiState.error){ if(socialUiState.error == null){ - snackbarState.showSnackbar(Constants.Strings.PIN_GREETING) + snackbarState.showSnackbar(context.getString(R.string.pin_greeting)) } } } diff --git a/app/src/main/java/org/listenbrainz/android/util/Constants.kt b/app/src/main/java/org/listenbrainz/android/util/Constants.kt index 74cc0e63..16deebdd 100644 --- a/app/src/main/java/org/listenbrainz/android/util/Constants.kt +++ b/app/src/main/java/org/listenbrainz/android/util/Constants.kt @@ -35,11 +35,6 @@ 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!" - 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" diff --git a/app/src/main/java/org/listenbrainz/android/viewmodel/BaseViewModel.kt b/app/src/main/java/org/listenbrainz/android/viewmodel/BaseViewModel.kt index 042be486..9e8952da 100644 --- a/app/src/main/java/org/listenbrainz/android/viewmodel/BaseViewModel.kt +++ b/app/src/main/java/org/listenbrainz/android/viewmodel/BaseViewModel.kt @@ -10,7 +10,7 @@ import org.listenbrainz.android.model.ResponseError abstract class BaseViewModel: ViewModel() { protected val errorFlow = MutableStateFlow(null) - protected val successMsgFlow = MutableStateFlow(null) + protected val successMsgFlow = MutableStateFlow(null) /** Visible Ui-State for UI to consume.*/ abstract val uiState: StateFlow @@ -23,9 +23,9 @@ abstract class BaseViewModel: ViewModel() { } } - protected fun emitMsg(message : String?){ + protected fun emitMsg(messageId : Int?){ viewModelScope.launch { - successMsgFlow.emit(message) + successMsgFlow.emit(messageId) } } diff --git a/app/src/main/java/org/listenbrainz/android/viewmodel/SocialViewModel.kt b/app/src/main/java/org/listenbrainz/android/viewmodel/SocialViewModel.kt index a77b6d21..99af8003 100644 --- a/app/src/main/java/org/listenbrainz/android/viewmodel/SocialViewModel.kt +++ b/app/src/main/java/org/listenbrainz/android/viewmodel/SocialViewModel.kt @@ -23,9 +23,9 @@ import org.listenbrainz.android.model.feed.ReviewEntityType import org.listenbrainz.android.repository.preferences.AppPreferences import org.listenbrainz.android.repository.remoteplayer.RemotePlaybackHandler import org.listenbrainz.android.repository.social.SocialRepository -import org.listenbrainz.android.util.Constants import org.listenbrainz.android.util.Resource import javax.inject.Inject +import org.listenbrainz.android.R @HiltViewModel class SocialViewModel @Inject constructor( @@ -108,7 +108,7 @@ class SocialViewModel @Inject constructor( emitError(result.error) } else if(result.status == Resource.Status.SUCCESS){ - emitMsg(Constants.Strings.RECOMMENDATION_GREETING) + emitMsg(R.string.recommendation_greeting) } } } @@ -135,7 +135,7 @@ class SocialViewModel @Inject constructor( emitError(result.error) } else if(result.status == Resource.Status.SUCCESS){ - emitMsg(Constants.Strings.PERSONAL_RECOMMENDATION_GREETING) + emitMsg(R.string.personal_recommendation_greeting) } } @@ -162,7 +162,7 @@ class SocialViewModel @Inject constructor( emitError(result.error) } else if(result.status == Resource.Status.SUCCESS){ - emitMsg(Constants.Strings.REVIEW_GREETING) + emitMsg(R.string.review_greeting) } } } @@ -180,7 +180,7 @@ class SocialViewModel @Inject constructor( emitError(result.error) } else if(result.status == Resource.Status.SUCCESS){ - emitMsg(Constants.Strings.PIN_GREETING) + emitMsg(R.string.pin_greeting) } } }