From 6119e0b1c49ddfb46e2a6ac656022a0a23e65256 Mon Sep 17 00:00:00 2001 From: benha Date: Wed, 26 Oct 2022 17:46:53 -0400 Subject: [PATCH 01/16] Enable notifications and add log statements --- .../ui/viewmodels/OnboardingViewModel.kt | 2 + .../volume/util/NotificationService.kt | 37 ++++++++++--------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/volume/ui/viewmodels/OnboardingViewModel.kt b/app/src/main/java/com/cornellappdev/volume/ui/viewmodels/OnboardingViewModel.kt index b911e3b..a7b4fc6 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/viewmodels/OnboardingViewModel.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/viewmodels/OnboardingViewModel.kt @@ -1,5 +1,6 @@ package com.cornellappdev.volume.ui.viewmodels +import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -73,6 +74,7 @@ class OnboardingViewModel @Inject constructor( } fun queryAllPublications() = viewModelScope.launch { + Log.d("device_token",userPreferencesRepository.fetchDeviceToken() ) try { onboardingUiState = onboardingUiState.copy( publicationsState = PublicationsRetrievalState.Success( diff --git a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt index 96e5f5d..3bc6af5 100644 --- a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt +++ b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt @@ -93,10 +93,11 @@ class NotificationService : FirebaseMessagingService() { notification: RemoteMessage.Notification, data: MutableMap ) { + Log.d("notif_sent", "here") // TODO test out notifications. It leverages Navigation Deep linking, not sure if it works // https://developer.android.com/jetpack/compose/navigation#deeplinks -// val intent = Intent(this, MainActivity::class.java) -// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + val intent = Intent(this, MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) var deepLinkIntent: Intent? = null // What's sent back to the MainActivity depends on the type of the notification @@ -110,18 +111,18 @@ class NotificationService : FirebaseMessagingService() { MainActivity::class.java ) // ) -// intent.putExtra( -// NotificationDataKeys.NOTIFICATION_TYPE.key, -// NotificationType.NEW_ARTICLE.type -// ) -// intent.putExtra( -// NotificationDataKeys.ARTICLE_ID.key, -// data[NotificationDataKeys.ARTICLE_ID.key] -// ) -// intent.putExtra( -// NotificationDataKeys.ARTICLE_URL.key, -// data[NotificationDataKeys.ARTICLE_URL.key] -// ) + intent.putExtra( + NotificationDataKeys.NOTIFICATION_TYPE.key, + NotificationType.NEW_ARTICLE.type + ) + intent.putExtra( + NotificationDataKeys.ARTICLE_ID.key, + data[NotificationDataKeys.ARTICLE_ID.key] + ) + intent.putExtra( + NotificationDataKeys.ARTICLE_URL.key, + data[NotificationDataKeys.ARTICLE_URL.key] + ) } NotificationType.WEEKLY_DEBRIEF.type -> { // We simply just need to identify the type of the notification. The @@ -132,10 +133,10 @@ class NotificationService : FirebaseMessagingService() { this, MainActivity::class.java ) -// intent.putExtra( -// NotificationDataKeys.NOTIFICATION_TYPE.key, -// NotificationType.NEW_ARTICLE.type -// ) + intent.putExtra( + NotificationDataKeys.NOTIFICATION_TYPE.key, + NotificationType.NEW_ARTICLE.type + ) } } From d0feda1c53e5e118b9ba8be5d8df323af7be04ce Mon Sep 17 00:00:00 2001 From: ckd38 Date: Thu, 3 Nov 2022 14:03:52 -0400 Subject: [PATCH 02/16] Change notifications to strictly be data notifications --- app/src/main/AndroidManifest.xml | 16 +- .../volume/data/models/Social.kt | 15 +- .../volume/navigation/MainTabbedNavigation.kt | 9 +- .../volume/navigation/NavigationItem.kt | 1 + .../components/general/ArticleComponents.kt | 2 +- .../IndividualPublicationComponents.kt | 180 +++++++----------- .../general/PublicationComponents.kt | 30 +-- .../ui/components/onboarding/SecondPage.kt | 4 +- .../volume/ui/screens/BookmarkScreen.kt | 4 +- .../volume/ui/screens/HomeScreen.kt | 13 +- .../ui/screens/IndividualPublicationScreen.kt | 4 +- .../volume/ui/screens/PublicationsScreen.kt | 10 +- .../volume/util/NotificationService.kt | 98 +++++----- 13 files changed, 179 insertions(+), 207 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9543b58..bdf12f6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,6 +13,10 @@ android:supportsRtl="true" android:theme="@style/Theme.Volumeandroidrevamp"> + + @@ -27,7 +31,6 @@ - @@ -46,6 +49,17 @@ android:host="weekly_debrief" android:scheme="volume" /> + + + + + + + + + Unit diff --git a/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt b/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt index 25764d6..f0a8fa4 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt @@ -2,29 +2,30 @@ package com.cornellappdev.volume.ui.components.general import androidx.compose.animation.Crossfade import androidx.compose.foundation.Image +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.ClickableText import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable 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.draw.clip -import androidx.compose.ui.draw.scale +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration @@ -34,6 +35,8 @@ import androidx.compose.ui.unit.sp import coil.compose.AsyncImage import com.cornellappdev.volume.R import com.cornellappdev.volume.data.models.Publication +import com.cornellappdev.volume.data.models.Social.Companion.formattedSocialNameMap +import com.cornellappdev.volume.data.models.Social.Companion.socialLogoMap import com.cornellappdev.volume.ui.theme.* @Composable @@ -49,7 +52,6 @@ fun CreateIndividualPublicationHeading( .wrapContentHeight() ) { Box { - AsyncImage( model = publication.backgroundImageURL, contentScale = ContentScale.Crop, @@ -61,24 +63,26 @@ fun CreateIndividualPublicationHeading( AsyncImage( model = publication.profileImageURL, contentScale = ContentScale.Crop, + alignment = Alignment.TopStart, modifier = Modifier - .align(Alignment.TopStart) - .padding(start = (8.dp), top = 130.dp) + .padding(start = 8.dp, top = 130.dp) .size(64.dp) - .clip(CircleShape), + .clip(CircleShape) + .shadow(4.dp, CircleShape), contentDescription = null ) } + Row( modifier = Modifier - .padding(top = 10.dp) - .fillMaxWidth() + .padding(start = 12.dp, top = 10.dp, end = 12.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween ) { Text( modifier = Modifier - .padding(start = 12.dp, top = 2.dp) - .wrapContentHeight() - .width(230.dp), + .weight(1f) + .padding(end = 20.dp), text = publication.name, maxLines = 3, overflow = TextOverflow.Ellipsis, @@ -86,9 +90,9 @@ fun CreateIndividualPublicationHeading( fontWeight = FontWeight.Medium, fontSize = 18.sp ) + Button( modifier = Modifier - .padding(start = 30.dp) .height(33.dp), onClick = { hasBeenClicked.value = !hasBeenClicked.value @@ -111,11 +115,10 @@ fun CreateIndividualPublicationHeading( Icon( Icons.Default.Add, contentDescription = "Follow", - modifier = Modifier.scale(.8f), tint = VolumeOrange ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) Text( - modifier = Modifier.padding(start = 6.dp), text = "Follow", textAlign = TextAlign.Center, fontFamily = lato, @@ -124,12 +127,11 @@ fun CreateIndividualPublicationHeading( color = VolumeOrange ) } - } } } - } + Text( modifier = Modifier.padding(start = 12.dp), text = "${publication.numArticles.toInt()} articles · ${publication.shoutouts.toInt()} shoutouts", @@ -151,124 +153,72 @@ fun CreateIndividualPublicationHeading( Row( modifier = Modifier .fillMaxWidth() + .horizontalScroll(rememberScrollState()) .padding(top = 10.dp, start = 12.dp, end = 12.dp), - horizontalArrangement = Arrangement.Start + horizontalArrangement = Arrangement.spacedBy(13.dp) ) { for (social in publication.socials) { - if (social.social == "instagram") { - Image( - modifier = Modifier - .scale(1.3f), - painter = painterResource(R.drawable.ic_instagram), - contentDescription = null, - ) - HyperlinkText( - fullText = "Instagram", - modifier = Modifier.padding(start = 10.dp), - hyperLinks = Pair("Instagram", social.url), - style = TextStyle(fontFamily = lato, color = VolumeOrange) - ) - } - if (social.social == "facebook") { - Image( - modifier = Modifier - .scale(1.3f), - painter = painterResource(R.drawable.ic_facebook), - contentDescription = null, - ) + // Capitalizes the first letter in the social name + val socialName = formattedSocialNameMap.getOrDefault(social.social, social.social) + + Row { + // Make sure that the drawable is in the socialLogoMap or the painter is null HyperlinkText( - fullText = "Facebook", - modifier = Modifier.padding(start = 10.dp), - hyperLinks = Pair("Facebook", social.url), - style = TextStyle(fontFamily = lato, color = VolumeOrange) + displayText = socialName, + uri = social.url, + style = TextStyle(fontFamily = lato, color = VolumeOrange), + painter = socialLogoMap[socialName]?.let { painterResource(it) }, ) } } - Image( - modifier = Modifier - .padding(start = 10.dp) - .scale(1.3f), - painter = painterResource(R.drawable.ic_link), - contentDescription = null, - ) - val httpsIndex = publication.websiteURL.indexOf("http") + 8 - val wwwIndex = publication.websiteURL.indexOf("www") - var endIndex = Math.max( - publication.websiteURL.indexOf("com") + 3, - publication.websiteURL.indexOf("org") + 3 - ) - endIndex = Math.max(endIndex, publication.websiteURL.indexOf("al") + 2) - endIndex = Math.max(endIndex, publication.websiteURL.indexOf("edu") + 3) - endIndex = Math.max(endIndex, publication.websiteURL.indexOf("net") + 3) - var urlString: String = if (wwwIndex == -1) { - "www." + publication.websiteURL.substring(httpsIndex, endIndex) - } else { - publication.websiteURL.substring(wwwIndex, endIndex) - } + + val websiteURL = + publication.websiteURL.removePrefix("https://").removePrefix("http://") + .removePrefix("www.") + HyperlinkText( - fullText = urlString, - modifier = Modifier.padding(start = 10.dp), - hyperLinks = Pair(publication.name, publication.websiteURL), + displayText = websiteURL, + uri = publication.websiteURL, style = TextStyle( fontFamily = lato, color = VolumeOrange, textDecoration = TextDecoration.Underline - ) + ), + painter = painterResource(R.drawable.ic_link), ) } - } } @Composable fun HyperlinkText( - modifier: Modifier = Modifier, - fullText: String, - hyperLinks: Pair, - style: TextStyle + displayText: String, + uri: String, + style: TextStyle, + painter: Painter? ) { - val annotatedString = buildAnnotatedString { - append(fullText) - val startIndex = fullText.indexOf(hyperLinks.first) - val endIndex = startIndex + hyperLinks.first.length - addStyle( - style = SpanStyle( - color = VolumeOrange, - fontSize = 12.sp, - ), - start = startIndex, - end = endIndex - ) - addStringAnnotation( - tag = "URL", - annotation = hyperLinks.second, - start = startIndex, - end = endIndex - ) - - addStyle( - style = SpanStyle( - fontSize = 12.sp - ), - start = 0, - end = fullText.length - ) - } - val uriHandler = LocalUriHandler.current - ClickableText( - modifier = modifier, - text = annotatedString, - maxLines = 1, - style = style, - overflow = TextOverflow.Ellipsis, + TextButton( + contentPadding = PaddingValues(0.dp), onClick = { - annotatedString - .getStringAnnotations("URL", it, it) - .firstOrNull()?.let { stringAnnotation -> - uriHandler.openUri(stringAnnotation.item) - } - }, - ) + uriHandler.openUri(uri) + } + ) { + if (painter != null) { + Image( + painter = painter, + contentDescription = "Icon", + ) + + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + } + + Text( + text = displayText, + maxLines = 1, + style = style, + overflow = TextOverflow.Ellipsis, + ) + } } diff --git a/app/src/main/java/com/cornellappdev/volume/ui/components/general/PublicationComponents.kt b/app/src/main/java/com/cornellappdev/volume/ui/components/general/PublicationComponents.kt index 36bd9cb..f24461f 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/components/general/PublicationComponents.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/components/general/PublicationComponents.kt @@ -13,6 +13,7 @@ import androidx.compose.material.icons.filled.Done import androidx.compose.runtime.Composable 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.draw.clip import androidx.compose.ui.graphics.Color @@ -36,7 +37,7 @@ import com.cornellappdev.volume.ui.theme.* * @param followButtonClicked */ @Composable -fun CreateHorizontalPublicationRow( +fun CreatePublicationRow( publication: Publication, followButtonClicked: (Publication, Boolean) -> Unit, ) { @@ -156,12 +157,12 @@ fun CreateHorizontalPublicationRow( } @Composable -fun CreateHorizontalPublicationRowFollowing( +fun CreatePublicationRow( publication: Publication, onPublicationClick: (Publication) -> Unit, - followButtonClicked: (Publication, Boolean) -> Unit, + followButtonClicked: (Publication, Boolean) -> Unit +) { - ) { var hasBeenClicked = false Row( modifier = Modifier @@ -224,7 +225,6 @@ fun CreateHorizontalPublicationRowFollowing( } } } - } Row( @@ -233,7 +233,6 @@ fun CreateHorizontalPublicationRowFollowing( .height(IntrinsicSize.Min) .padding(bottom = 2.dp) ) { - Text( modifier = Modifier.padding(end = 20.dp), text = publication.bio, @@ -281,26 +280,27 @@ fun CreateHorizontalPublicationRowFollowing( } @Composable -fun CreateFollowPublicationRow( +fun CreatePublicationColumn( publication: Publication, onPublicationClick: (Publication) -> Unit ) { val title = publication.name - Column(modifier = Modifier - .wrapContentHeight() - .width(100.dp) - .clickable { - onPublicationClick(publication) - }) { - + Column( + modifier = Modifier + .wrapContentHeight() + .width(100.dp) + .clickable { + onPublicationClick(publication) + }, + horizontalAlignment = Alignment.CenterHorizontally + ) { AsyncImage( model = publication.profileImageURL, modifier = Modifier .height(100.dp) .width(100.dp) .clip(CircleShape), contentDescription = null, contentScale = ContentScale.Crop ) - Text( modifier = Modifier.padding(bottom = 2.dp, top = 2.dp), text = title, diff --git a/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt b/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt index 293a64c..d557ec5 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt @@ -35,7 +35,7 @@ import com.cornellappdev.volume.R import com.cornellappdev.volume.analytics.EventType import com.cornellappdev.volume.analytics.NavigationSource import com.cornellappdev.volume.analytics.VolumeEvent -import com.cornellappdev.volume.ui.components.general.CreateHorizontalPublicationRow +import com.cornellappdev.volume.ui.components.general.CreatePublicationRow import com.cornellappdev.volume.ui.states.PublicationsRetrievalState import com.cornellappdev.volume.ui.theme.* import com.cornellappdev.volume.ui.viewmodels.OnboardingViewModel @@ -130,7 +130,7 @@ fun SecondPage( ) { publication -> // Clicking on row IN onboarding should not lead to IndividualPublicationScreen. They are not // an official user yet so they shouldn't be interacting with the articles. - CreateHorizontalPublicationRow(publication = publication) { publicationFromCallback, isFollowing -> + CreatePublicationRow(publication = publication) { publicationFromCallback, isFollowing -> if (isFollowing) { onboardingViewModel.addPublicationToFollowed( publicationFromCallback.slug diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/BookmarkScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/BookmarkScreen.kt index 47a7e0a..157edc2 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/BookmarkScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/BookmarkScreen.kt @@ -27,7 +27,7 @@ import androidx.lifecycle.SavedStateHandle import com.cornellappdev.volume.R import com.cornellappdev.volume.analytics.NavigationSource import com.cornellappdev.volume.data.models.Article -import com.cornellappdev.volume.ui.components.general.CreateHorizontalArticleRow +import com.cornellappdev.volume.ui.components.general.CreateArticleRow import com.cornellappdev.volume.ui.states.ArticlesRetrievalState import com.cornellappdev.volume.ui.theme.VolumeOffWhite import com.cornellappdev.volume.ui.theme.VolumeOrange @@ -187,7 +187,7 @@ fun BookmarkScreen( } }, dismissContent = { - CreateHorizontalArticleRow( + CreateArticleRow( article = article, isABookmarkedArticle = true ) { diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt index fff24b0..e0881fc 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt @@ -4,12 +4,10 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Scaffold import androidx.compose.material.Text @@ -17,8 +15,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -28,12 +24,9 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.volume.R import com.cornellappdev.volume.analytics.NavigationSource import com.cornellappdev.volume.data.models.Article +import com.cornellappdev.volume.ui.components.general.CreateArticleRow import com.cornellappdev.volume.ui.components.general.CreateBigReadRow -import com.cornellappdev.volume.ui.components.general.CreateHorizontalArticleRow -import com.cornellappdev.volume.ui.components.onboarding.isScrolledToTheEnd -import com.cornellappdev.volume.ui.components.onboarding.isScrolledToTheStart import com.cornellappdev.volume.ui.states.ArticlesRetrievalState -import com.cornellappdev.volume.ui.theme.VolumeOffWhite import com.cornellappdev.volume.ui.theme.VolumeOrange import com.cornellappdev.volume.ui.theme.lato import com.cornellappdev.volume.ui.theme.notoserif @@ -133,7 +126,7 @@ fun HomeScreen( verticalArrangement = Arrangement.spacedBy(20.dp), ) { followingArticlesState.articles.forEach { article -> - CreateHorizontalArticleRow( + CreateArticleRow( article ) { onArticleClick( @@ -243,7 +236,7 @@ fun HomeScreen( verticalArrangement = Arrangement.spacedBy(20.dp), ) { otherArticlesState.articles.forEach { article -> - CreateHorizontalArticleRow( + CreateArticleRow( article ) { onArticleClick( diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/IndividualPublicationScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/IndividualPublicationScreen.kt index 1c04663..9213cf0 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/IndividualPublicationScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/IndividualPublicationScreen.kt @@ -11,7 +11,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.volume.analytics.NavigationSource import com.cornellappdev.volume.data.models.Article -import com.cornellappdev.volume.ui.components.general.CreateHorizontalArticleRow +import com.cornellappdev.volume.ui.components.general.CreateArticleRow import com.cornellappdev.volume.ui.components.general.CreateIndividualPublicationHeading import com.cornellappdev.volume.ui.states.ArticlesRetrievalState import com.cornellappdev.volume.ui.states.PublicationRetrievalState @@ -79,7 +79,7 @@ fun IndividualPublicationScreen(individualPublicationViewModel: IndividualPublic .padding(top = 20.dp, start = 12.dp, end = 12.dp) ) { articlesByPublicationState.articles.forEach { article -> - CreateHorizontalArticleRow( + CreateArticleRow( article ) { onArticleClick( diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt index a745ba7..c9bb1cf 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt @@ -20,8 +20,8 @@ import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.volume.R import com.cornellappdev.volume.data.models.Publication -import com.cornellappdev.volume.ui.components.general.CreateFollowPublicationRow -import com.cornellappdev.volume.ui.components.general.CreateHorizontalPublicationRowFollowing +import com.cornellappdev.volume.ui.components.general.CreatePublicationColumn +import com.cornellappdev.volume.ui.components.general.CreatePublicationRow import com.cornellappdev.volume.ui.states.PublicationsRetrievalState import com.cornellappdev.volume.ui.theme.VolumeOrange import com.cornellappdev.volume.ui.theme.notoserif @@ -94,7 +94,7 @@ fun PublicationsScreen( ) { items(followingPublicationsState.publications) { publication -> - CreateFollowPublicationRow(publication) { + CreatePublicationColumn(publication) { onPublicationClick(publication) } } @@ -146,7 +146,7 @@ fun PublicationsScreen( .padding(start = 12.dp, end = 12.dp) ) { morePublicationsState.publications.forEach { publication -> - CreateHorizontalPublicationRowFollowing( + CreatePublicationRow( publication = publication, onPublicationClick ) { publicationFromCallback, isFollowing -> @@ -167,4 +167,4 @@ fun PublicationsScreen( } } }) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt index 3bc6af5..d9276f6 100644 --- a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt +++ b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt @@ -10,7 +10,6 @@ import android.graphics.BitmapFactory import android.media.RingtoneManager import android.util.Log import androidx.core.app.NotificationCompat -import androidx.core.app.TaskStackBuilder import androidx.core.net.toUri import com.cornellappdev.volume.MainActivity import com.cornellappdev.volume.R @@ -33,11 +32,16 @@ class NotificationService : FirebaseMessagingService() { * as keys for the intent bundle to the MainActivity. * * @property key key name + * @see [NotificationRepo](https://github.com/cuappdev/volume-backend/blob/main/src/repos/NotificationRepo.ts) */ enum class NotificationDataKeys(val key: String) { + NOTIFICATION_TYPE("notificationType"), ARTICLE_ID("articleID"), ARTICLE_URL("articleURL"), - NOTIFICATION_TYPE("notification_type") + MAGAZINE_ID("magazineID"), + MAGAZINE_PDF("magazinePDF"), + TITLE("title"), + BODY("body") } /** @@ -47,7 +51,8 @@ class NotificationService : FirebaseMessagingService() { */ enum class NotificationType(val type: String) { WEEKLY_DEBRIEF("weekly_debrief"), - NEW_ARTICLE("new_article") + NEW_ARTICLE("new_article"), + NEW_MAGAZINE("new_magazine") } /** @@ -55,21 +60,8 @@ class NotificationService : FirebaseMessagingService() { * * @param remoteMessage Object representing the message received from Firebase Cloud Messaging. */ - override fun onMessageReceived(remoteMessage: RemoteMessage) { - // There are two types of messages data messages and notification messages. Data messages are handled - // here in onMessageReceived whether the app is in the foreground or background. Data messages are the type - // traditionally used with GCM. Notification messages are only received here in onMessageReceived when the app - // is in the foreground. When the app is in the background an automatically generated notification is displayed. - // When the user taps on the notification they are returned to the app. Messages containing both notification - // and data payloads are treated as notification messages. The Firebase console always sends notification - // messages. For more see: https://firebase.google.com/docs/cloud-messaging/concept-options - - // Check if message contains a notification payload. - remoteMessage.notification?.let { - sendNotification(it, remoteMessage.data) - Log.d(TAG, "Message Notification Body: ${it.body}") - } - } + override fun onMessageReceived(remoteMessage: RemoteMessage) = + sendNotification(remoteMessage.data) /** * Called if the FCM registration token is updated. This may occur if the security of @@ -90,14 +82,11 @@ class NotificationService : FirebaseMessagingService() { * Create and show a simple notification containing the received FCM message. */ private fun sendNotification( - notification: RemoteMessage.Notification, data: MutableMap ) { - Log.d("notif_sent", "here") + Log.d(TAG, data.toString()) // TODO test out notifications. It leverages Navigation Deep linking, not sure if it works // https://developer.android.com/jetpack/compose/navigation#deeplinks - val intent = Intent(this, MainActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) var deepLinkIntent: Intent? = null // What's sent back to the MainActivity depends on the type of the notification @@ -110,19 +99,18 @@ class NotificationService : FirebaseMessagingService() { this, MainActivity::class.java ) +// intent.putExtra( +// NotificationDataKeys.NOTIFICATION_TYPE.key, +// NotificationType.NEW_ARTICLE.type +// ) +// intent.putExtra( +// NotificationDataKeys.ARTICLE_ID.key, +// data[NotificationDataKeys.ARTICLE_ID.key] +// ) +// intent.putExtra( +// NotificationDataKeys.ARTICLE_URL.key, +// data[NotificationDataKeys.ARTICLE_URL.key] // ) - intent.putExtra( - NotificationDataKeys.NOTIFICATION_TYPE.key, - NotificationType.NEW_ARTICLE.type - ) - intent.putExtra( - NotificationDataKeys.ARTICLE_ID.key, - data[NotificationDataKeys.ARTICLE_ID.key] - ) - intent.putExtra( - NotificationDataKeys.ARTICLE_URL.key, - data[NotificationDataKeys.ARTICLE_URL.key] - ) } NotificationType.WEEKLY_DEBRIEF.type -> { // We simply just need to identify the type of the notification. The @@ -133,19 +121,25 @@ class NotificationService : FirebaseMessagingService() { this, MainActivity::class.java ) - intent.putExtra( - NotificationDataKeys.NOTIFICATION_TYPE.key, - NotificationType.NEW_ARTICLE.type +// intent.putExtra( +// NotificationDataKeys.NOTIFICATION_TYPE.key, +// NotificationType.NEW_ARTICLE.type +// ) + } + NotificationType.NEW_MAGAZINE.type -> { + deepLinkIntent = Intent( + Intent.ACTION_VIEW, + "volume://${Routes.OPEN_MAGAZINE.route}/${data[NotificationDataKeys.MAGAZINE_ID.key]}".toUri(), + this, + MainActivity::class.java ) } } - val pendingIntent: PendingIntent? = TaskStackBuilder.create(this).run { - if (deepLinkIntent != null) { - addNextIntentWithParentStack(deepLinkIntent) - } - getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) - } + val pendingIntent = + PendingIntent.getActivity(this, 0, deepLinkIntent, PendingIntent.FLAG_ONE_SHOT) + + pendingIntent.send() val channelId = getString((R.string.default_notification_channel_id)) val notificationBuilder = NotificationCompat.Builder(this, channelId) @@ -156,19 +150,19 @@ class NotificationService : FirebaseMessagingService() { R.drawable.volume_icon ) ) - .setContentTitle(notification.title) - .setContentText(notification.body) + .setContentTitle(data[NotificationDataKeys.TITLE.key]) + .setContentText(data[NotificationDataKeys.BODY.key]) .setAutoCancel(true) .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) .setContentIntent(pendingIntent) - if (notification.imageUrl != null) { - val bitmap: Bitmap? = getBitmapFromUrl(notification.imageUrl.toString()) - notificationBuilder.setStyle( - NotificationCompat.BigPictureStyle() - .bigPicture(bitmap) - ) - } +// if (data.imageUrl != null) { +// val bitmap: Bitmap? = getBitmapFromUrl(data.imageUrl.toString()) +// notificationBuilder.setStyle( +// NotificationCompat.BigPictureStyle() +// .bigPicture(bitmap) +// ) +// } val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager From f727685da74e23023488a0bd0cf4933319a9b110 Mon Sep 17 00:00:00 2001 From: ckd38 Date: Thu, 3 Nov 2022 14:04:52 -0400 Subject: [PATCH 03/16] Add transitions to OPEN_MAGAZINE --- .../volume/navigation/MainTabbedNavigation.kt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt b/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt index 22466c3..d7f38a0 100644 --- a/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt +++ b/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt @@ -223,7 +223,18 @@ private fun MainScreenNavigationConfigurations( route = "${Routes.OPEN_MAGAZINE.route}/{magazineId}/{navigationSourceName}", deepLinks = listOf( navDeepLink { uriPattern = "volume://${Routes.OPEN_MAGAZINE.route}/{magazineId}" } - )) {} + ), + enterTransition = { + fadeIn( + initialAlpha = 0f, + animationSpec = tween(durationMillis = 1500) + ) + }, + exitTransition = { + fadeOut( + animationSpec = tween(durationMillis = 1500) + ) + }) {} composable(Routes.PUBLICATIONS.route) { PublicationsScreen( onPublicationClick = From b968fef0a4382f88595b6f8b488087217183d963 Mon Sep 17 00:00:00 2001 From: ckd38 Date: Thu, 3 Nov 2022 14:58:51 -0400 Subject: [PATCH 04/16] Delete pendingIntent.send() --- .../java/com/cornellappdev/volume/util/NotificationService.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt index d9276f6..2720076 100644 --- a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt +++ b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt @@ -139,8 +139,6 @@ class NotificationService : FirebaseMessagingService() { val pendingIntent = PendingIntent.getActivity(this, 0, deepLinkIntent, PendingIntent.FLAG_ONE_SHOT) - pendingIntent.send() - val channelId = getString((R.string.default_notification_channel_id)) val notificationBuilder = NotificationCompat.Builder(this, channelId) .setSmallIcon(R.drawable.volume_icon) From 9b7da53ff06fed19457e9847ca9dbbfa0cf9131d Mon Sep 17 00:00:00 2001 From: ckd38 Date: Fri, 4 Nov 2022 14:14:05 -0400 Subject: [PATCH 05/16] Change from "Saved" to "Bookmarks" --- .../java/com/cornellappdev/volume/navigation/NavigationItem.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/cornellappdev/volume/navigation/NavigationItem.kt b/app/src/main/java/com/cornellappdev/volume/navigation/NavigationItem.kt index 9414e8e..d9e3f8a 100644 --- a/app/src/main/java/com/cornellappdev/volume/navigation/NavigationItem.kt +++ b/app/src/main/java/com/cornellappdev/volume/navigation/NavigationItem.kt @@ -42,7 +42,7 @@ sealed class NavigationItem( Routes.BOOKMARKS.route, R.drawable.ic_bookmark_gray, R.drawable.ic_bookmark_orange, - "Saved" + "Bookmarks" ) companion object { From e6d79e1ee3965a6c3f88a731b98265e03201fab3 Mon Sep 17 00:00:00 2001 From: ckd38 Date: Fri, 4 Nov 2022 17:23:13 -0400 Subject: [PATCH 06/16] Finalize Notifications and formatting --- app/build.gradle | 4 +- .../com/cornellappdev/volume/MainActivity.kt | 4 +- .../volume/data/models/Social.kt | 5 +- .../cornellappdev/volume/di/NetworkModule.kt | 2 +- .../volume/navigation/MainTabbedNavigation.kt | 10 +- .../volume/navigation/NavigationItem.kt | 50 +++-- .../IndividualPublicationComponents.kt | 194 +++++++++--------- .../volume/ui/screens/BookmarkScreen.kt | 194 +++++++++--------- .../volume/ui/screens/HomeScreen.kt | 30 +-- .../ui/screens/IndividualPublicationScreen.kt | 24 ++- .../volume/ui/screens/PublicationsScreen.kt | 15 +- .../volume/util/NotificationService.kt | 141 ++++++------- app/src/main/proto/user_prefs.proto | 2 + app/src/main/res/drawable/ic_instagram.xml | 14 +- 14 files changed, 357 insertions(+), 332 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6011a7d..cc84a72 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,8 +24,6 @@ android { versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - buildConfigField("String", "PROD_ENDPOINT", secretsProperties['PROD_ENDPOINT']) - buildConfigField("String", "DEV_ENDPOINT", secretsProperties['DEV_ENDPOINT']) buildConfigField("String", "FEEDBACK_FORM", secretsProperties['FEEDBACK_FORM']) buildConfigField("String", "WEBSITE", secretsProperties['WEBSITE']) @@ -39,9 +37,11 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' resValue("bool", "FIREBASE_ANALYTICS_DEACTIVATED", "false") + buildConfigField("String", "ENDPOINT", secretsProperties['PROD_ENDPOINT']) } debug { resValue("bool", "FIREBASE_ANALYTICS_DEACTIVATED", "true") + buildConfigField("String", "ENDPOINT", secretsProperties['DEV_ENDPOINT']) } } diff --git a/app/src/main/java/com/cornellappdev/volume/MainActivity.kt b/app/src/main/java/com/cornellappdev/volume/MainActivity.kt index 8a0eb71..a33c63a 100644 --- a/app/src/main/java/com/cornellappdev/volume/MainActivity.kt +++ b/app/src/main/java/com/cornellappdev/volume/MainActivity.kt @@ -1,6 +1,7 @@ package com.cornellappdev.volume import android.os.Bundle +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material.MaterialTheme @@ -18,7 +19,8 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + Log.d("MainActivity", BuildConfig.DEBUG.toString()) + Log.d("MainActivity", BuildConfig.ENDPOINT) val onboardingCompleted = runBlocking { return@runBlocking userPreferences.fetchOnboardingCompleted() } diff --git a/app/src/main/java/com/cornellappdev/volume/data/models/Social.kt b/app/src/main/java/com/cornellappdev/volume/data/models/Social.kt index 6da7c2c..be9fa4d 100644 --- a/app/src/main/java/com/cornellappdev/volume/data/models/Social.kt +++ b/app/src/main/java/com/cornellappdev/volume/data/models/Social.kt @@ -14,10 +14,11 @@ data class Social( ) { companion object { val formattedSocialNameMap = mapOf( - "instagram" to "Instagram", + "insta" to "Instagram", "facebook" to "Facebook", "linkedin" to "LinkedIn", - "twitter" to "Twitter" + "twitter" to "Twitter", + "youtube" to "YouTube" ) val socialLogoMap = mapOf("Instagram" to R.drawable.ic_instagram, "Facebook" to R.drawable.ic_facebook) diff --git a/app/src/main/java/com/cornellappdev/volume/di/NetworkModule.kt b/app/src/main/java/com/cornellappdev/volume/di/NetworkModule.kt index d431367..c803a4f 100644 --- a/app/src/main/java/com/cornellappdev/volume/di/NetworkModule.kt +++ b/app/src/main/java/com/cornellappdev/volume/di/NetworkModule.kt @@ -17,7 +17,7 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) object NetworkModule { - private const val ENDPOINT = BuildConfig.DEV_ENDPOINT + private const val ENDPOINT = BuildConfig.ENDPOINT @Singleton @Provides diff --git a/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt b/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt index d7f38a0..45306e3 100644 --- a/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt +++ b/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt @@ -26,7 +26,6 @@ import com.google.accompanist.navigation.animation.AnimatedNavHost import com.google.accompanist.navigation.animation.composable import com.google.accompanist.navigation.animation.rememberAnimatedNavController - @OptIn(ExperimentalAnimationApi::class) @Composable fun TabbedNavigationSetup(onboardingCompleted: Boolean) { @@ -52,6 +51,9 @@ fun TabbedNavigationSetup(onboardingCompleted: Boolean) { Routes.BOOKMARKS.route -> { showBottomBar.value = true } + Routes.SETTINGS.route -> { + showBottomBar.value = false + } Routes.ABOUT_US.route -> { showBottomBar.value = false } @@ -107,7 +109,9 @@ fun BottomNavigationBar(navController: NavHostController, tabItems: List ) { object Home : NavigationItem( - Routes.HOME.route, - R.drawable.ic_volume_bars_gray, - R.drawable.ic_volume_bars_orange, - "For You" + route = Routes.HOME.route, + unselectedIconId = R.drawable.ic_volume_bars_gray, + selectedIconId = R.drawable.ic_volume_bars_orange, + title = "For You", + selectedRoutes = setOf(Routes.HOME.route, Routes.WEEKLY_DEBRIEF.route) ) object Magazines : NavigationItem( - Routes.MAGAZINES.route, - R.drawable.ic_magazine_icon_selected, - R.drawable.ic_magazine_icon_unselected, - "Magazines" + route = Routes.MAGAZINES.route, + unselectedIconId = R.drawable.ic_magazine_icon_unselected, + selectedIconId = R.drawable.ic_magazine_icon_selected, + title = "Magazines", + selectedRoutes = setOf(Routes.MAGAZINES.route) ) object Publications : NavigationItem( - Routes.PUBLICATIONS.route, - R.drawable.ic_publications_icon_selected, - R.drawable.ic_publications_icon_unselected, - "Publications" + route = Routes.PUBLICATIONS.route, + unselectedIconId = R.drawable.ic_publications_icon_unselected, + selectedIconId = R.drawable.ic_publications_icon_selected, + title = "Publications", + selectedRoutes = setOf( + Routes.PUBLICATIONS.route, + "${Routes.INDIVIDUAL_PUBLICATION.route}/{publicationSlug}" + ) ) - object Bookmarks : - NavigationItem( - Routes.BOOKMARKS.route, - R.drawable.ic_bookmark_gray, - R.drawable.ic_bookmark_orange, - "Bookmarks" - ) + object Bookmarks : NavigationItem( + route = Routes.BOOKMARKS.route, + unselectedIconId = R.drawable.ic_bookmark_gray, + selectedIconId = R.drawable.ic_bookmark_orange, + title = "Bookmarks", + selectedRoutes = setOf(Routes.BOOKMARKS.route) + ) companion object { val bottomNavTabList = listOf( Home, - Magazines, +// Magazines, Publications, Bookmarks ) diff --git a/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt b/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt index f0a8fa4..6feb802 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt @@ -73,119 +73,123 @@ fun CreateIndividualPublicationHeading( ) } - Row( - modifier = Modifier - .padding(start = 12.dp, top = 10.dp, end = 12.dp) - .fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween + Column( + modifier = Modifier.padding(horizontal = 12.dp) ) { - Text( - modifier = Modifier - .weight(1f) - .padding(end = 20.dp), - text = publication.name, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - fontFamily = notoserif, - fontWeight = FontWeight.Medium, - fontSize = 18.sp - ) - - Button( + Row( modifier = Modifier - .height(33.dp), - onClick = { - hasBeenClicked.value = !hasBeenClicked.value - followButtonClicked(hasBeenClicked.value) - }, - shape = RoundedCornerShape(5.dp), - colors = ButtonDefaults.buttonColors(backgroundColor = if (hasBeenClicked.value) VolumeOrange else GrayThree), + .padding(top = 10.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween ) { - Crossfade(targetState = hasBeenClicked.value) { hasBeenClicked -> - if (hasBeenClicked) { - Text( - text = "Following", - fontFamily = lato, - fontWeight = FontWeight.SemiBold, - fontSize = 12.sp, - color = GrayThree - ) - } else { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - Icons.Default.Add, - contentDescription = "Follow", - tint = VolumeOrange - ) - Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text( + modifier = Modifier + .weight(1f) + .padding(end = 20.dp), + text = publication.name, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + fontFamily = notoserif, + fontWeight = FontWeight.Medium, + fontSize = 18.sp + ) + + Button( + modifier = Modifier + .height(33.dp), + onClick = { + hasBeenClicked.value = !hasBeenClicked.value + followButtonClicked(hasBeenClicked.value) + }, + shape = RoundedCornerShape(5.dp), + colors = ButtonDefaults.buttonColors(backgroundColor = if (hasBeenClicked.value) VolumeOrange else GrayThree), + ) { + Crossfade(targetState = hasBeenClicked.value) { hasBeenClicked -> + if (hasBeenClicked) { Text( - text = "Follow", - textAlign = TextAlign.Center, + text = "Following", fontFamily = lato, fontWeight = FontWeight.SemiBold, fontSize = 12.sp, - color = VolumeOrange + color = GrayThree ) + } else { + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + Icons.Default.Add, + contentDescription = "Follow", + tint = VolumeOrange + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text( + text = "Follow", + textAlign = TextAlign.Center, + fontFamily = lato, + fontWeight = FontWeight.SemiBold, + fontSize = 12.sp, + color = VolumeOrange + ) + } } } } } - } - Text( - modifier = Modifier.padding(start = 12.dp), - text = "${publication.numArticles.toInt()} articles · ${publication.shoutouts.toInt()} shoutouts", - fontFamily = lato, - fontWeight = FontWeight.Medium, - fontSize = 10.sp, - color = GrayOne - ) + Text( + text = "${publication.numArticles.toInt()} articles · ${publication.shoutouts.toInt()} shoutouts", + fontFamily = lato, + fontWeight = FontWeight.Medium, + fontSize = 10.sp, + color = GrayOne + ) - Text( - modifier = Modifier.padding(start = 12.dp, top = 2.dp, end = 20.dp), - text = publication.bio, - maxLines = 6, - overflow = TextOverflow.Ellipsis, - fontFamily = lato, - fontWeight = FontWeight.Medium, - fontSize = 14.sp - ) - Row( - modifier = Modifier - .fillMaxWidth() - .horizontalScroll(rememberScrollState()) - .padding(top = 10.dp, start = 12.dp, end = 12.dp), - horizontalArrangement = Arrangement.spacedBy(13.dp) - ) { - for (social in publication.socials) { - // Capitalizes the first letter in the social name - val socialName = formattedSocialNameMap.getOrDefault(social.social, social.social) + Text( + modifier = Modifier.padding(top = 2.dp), + text = publication.bio, + maxLines = 6, + overflow = TextOverflow.Ellipsis, + fontFamily = lato, + fontWeight = FontWeight.Medium, + fontSize = 14.sp + ) - Row { - // Make sure that the drawable is in the socialLogoMap or the painter is null - HyperlinkText( - displayText = socialName, - uri = social.url, - style = TextStyle(fontFamily = lato, color = VolumeOrange), - painter = socialLogoMap[socialName]?.let { painterResource(it) }, - ) + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(13.dp) + ) { + for (social in publication.socials) { + // Capitalizes the first letter in the social name + val socialName = + formattedSocialNameMap.getOrDefault(social.social, social.social) + + Row { + // Make sure that the drawable is in the socialLogoMap or the painter is null + HyperlinkText( + displayText = socialName, + uri = social.url, + style = TextStyle(fontFamily = lato, color = VolumeOrange), + painter = socialLogoMap[socialName]?.let { painterResource(it) }, + ) + } } - } - val websiteURL = - publication.websiteURL.removePrefix("https://").removePrefix("http://") - .removePrefix("www.") + val websiteURL = + publication.websiteURL.removePrefix("https://").removePrefix("http://") + .removePrefix("www.").removeSuffix("/") - HyperlinkText( - displayText = websiteURL, - uri = publication.websiteURL, - style = TextStyle( - fontFamily = lato, - color = VolumeOrange, - textDecoration = TextDecoration.Underline - ), - painter = painterResource(R.drawable.ic_link), - ) + HyperlinkText( + displayText = websiteURL, + uri = publication.websiteURL, + style = TextStyle( + fontFamily = lato, + color = VolumeOrange, + textDecoration = TextDecoration.Underline + ), + painter = painterResource(R.drawable.ic_link), + ) + } } } } diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/BookmarkScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/BookmarkScreen.kt index 1e0394c..38fd7c8 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/BookmarkScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/BookmarkScreen.kt @@ -60,11 +60,11 @@ fun BookmarkScreen( } Scaffold(topBar = { - - Row(verticalAlignment = Alignment.CenterVertically) { - Row { + Row( + modifier = Modifier.padding(start = 12.dp, top = 20.dp), + ) { + Row(verticalAlignment = Alignment.Bottom) { Text( - modifier = Modifier.padding(start = 12.dp, top = 20.dp), text = "Bookmarks", fontFamily = notoserif, fontWeight = FontWeight.Medium, @@ -75,7 +75,7 @@ fun BookmarkScreen( painter = painterResource(R.drawable.ic_period), contentDescription = null, modifier = Modifier - .padding(start = 3.dp, top = 43.5.dp) + .padding(start = 3.dp, bottom = 10.dp) .scale(1.05F) ) } @@ -84,8 +84,7 @@ fun BookmarkScreen( Icon( Icons.Filled.Settings, contentDescription = "Settings", - tint = Color(0xFF838383), - modifier = Modifier.padding(top = 10.dp) + tint = Color(0xFF838383) ) } } @@ -105,115 +104,114 @@ fun BookmarkScreen( // TODO } is ArticlesRetrievalState.Success -> { - if (articleState.articles.isEmpty()) { - Column( - modifier = Modifier.fillMaxSize(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - Image( - painter = painterResource(id = R.drawable.ic_volume_bars_orange_large), - contentDescription = null - ) - + Column { + Column(modifier = Modifier.padding(start = 12.dp, top = 15.dp)) { Text( - text = "Nothing to see here!", + text = "Saved Articles", fontFamily = notoserif, - fontSize = 24.sp, + fontSize = 20.sp, fontWeight = FontWeight.Medium ) - - Text( - text = "You have no saved articles.", - fontFamily = lato, - fontSize = 12.sp, - fontWeight = FontWeight.Medium + Image( + painter = painterResource(R.drawable.ic_underline_other_article), + contentDescription = null, + modifier = Modifier + .padding(start = 4.dp) + .scale(1.05F) ) } - } else { - Column( - modifier = Modifier - .padding(horizontal = 12.dp), - ) { - Box { + + if (articleState.articles.isEmpty()) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(id = R.drawable.ic_volume_bars_orange_large), + contentDescription = null + ) + Text( - text = "Saved Articles", + text = "Nothing to see here!", fontFamily = notoserif, - fontSize = 20.sp, - fontWeight = FontWeight.Medium, - modifier = Modifier.padding(top = 25.dp) + fontSize = 24.sp, + fontWeight = FontWeight.Medium ) - Image( - painter = painterResource(R.drawable.ic_underline_other_article), - contentDescription = null, - modifier = Modifier - .padding(start = 2.dp, top = 50.dp) - .scale(1.05F) + + Text( + text = "You have no saved articles.", + fontFamily = lato, + fontSize = 12.sp, + fontWeight = FontWeight.Medium ) } - - Spacer(modifier = Modifier.height(20.dp)) - - LazyColumn( + } else { + Column( modifier = Modifier - .fillMaxSize(), - verticalArrangement = Arrangement.spacedBy(20.dp), + .padding(start = 12.dp, end = 12.dp, top = 20.dp), ) { - items(articleState.articles) { article -> - val dismissState = rememberDismissState() - if (dismissState.isDismissed(DismissDirection.EndToStart)) { - bookmarkViewModel.removeArticle(article.id) - } + LazyColumn( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(20.dp), + ) { + items(articleState.articles) { article -> + val dismissState = rememberDismissState() + if (dismissState.isDismissed(DismissDirection.EndToStart)) { + bookmarkViewModel.removeArticle(article.id) + } - SwipeToDismiss( - state = dismissState, - directions = setOf( - DismissDirection.EndToStart - ), + SwipeToDismiss( + state = dismissState, + directions = setOf( + DismissDirection.EndToStart + ), - background = { - val backgroundColor by animateColorAsState( - when (dismissState.targetValue) { - DismissValue.Default -> VolumeOffWhite - else -> VolumeOrange - } - ) + background = { + val backgroundColor by animateColorAsState( + when (dismissState.targetValue) { + DismissValue.Default -> VolumeOffWhite + else -> VolumeOrange + } + ) - val iconColor by animateColorAsState( - when (dismissState.targetValue) { - DismissValue.Default -> VolumeOrange - else -> VolumeOffWhite - } - ) + val iconColor by animateColorAsState( + when (dismissState.targetValue) { + DismissValue.Default -> VolumeOrange + else -> VolumeOffWhite + } + ) - val size by animateDpAsState(targetValue = if (dismissState.targetValue == DismissValue.Default) 24.dp else 48.dp) + val size by animateDpAsState(targetValue = if (dismissState.targetValue == DismissValue.Default) 24.dp else 48.dp) - Box( - Modifier - .fillMaxSize() - .background(backgroundColor) - .padding(horizontal = Dp(20f)), - contentAlignment = Alignment.CenterEnd - ) { - Icon( - Icons.Filled.Bookmark, - contentDescription = "Unbookmark", - modifier = Modifier.size(size), - tint = iconColor - ) - } - }, - dismissContent = { - CreateArticleRow( - article = article, - isABookmarkedArticle = true - ) { - onArticleClick( - article, - NavigationSource.BOOKMARK_ARTICLES - ) - } - }) + Box( + Modifier + .fillMaxSize() + .background(backgroundColor) + .padding(horizontal = Dp(20f)), + contentAlignment = Alignment.CenterEnd + ) { + Icon( + Icons.Filled.Bookmark, + contentDescription = "Unbookmark", + modifier = Modifier.size(size), + tint = iconColor + ) + } + }, + dismissContent = { + CreateArticleRow( + article = article, + isABookmarkedArticle = true + ) { + onArticleClick( + article, + NavigationSource.BOOKMARK_ARTICLES + ) + } + }) + } } } } diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt index ceaf7cd..efffdcb 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt @@ -24,8 +24,8 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.volume.R import com.cornellappdev.volume.analytics.NavigationSource import com.cornellappdev.volume.data.models.Article +import com.cornellappdev.volume.ui.components.general.CreateArticleRow import com.cornellappdev.volume.ui.components.general.CreateBigReadRow -import com.cornellappdev.volume.ui.components.general.CreateHorizontalArticleRow import com.cornellappdev.volume.ui.states.ArticlesRetrievalState import com.cornellappdev.volume.ui.theme.VolumeOrange import com.cornellappdev.volume.ui.theme.lato @@ -55,7 +55,7 @@ fun HomeScreen( .padding(start = 12.dp, top = innerPadding.calculateTopPadding()), ) { item { - Box { + Column { Text( text = "The Big Read", fontFamily = notoserif, @@ -67,7 +67,8 @@ fun HomeScreen( painter = painterResource(R.drawable.ic_underline_big_read), contentDescription = null, modifier = Modifier - .padding(start = 2.dp, top = 40.dp) + .offset(y = (-5).dp) + .padding(start = 2.dp) .scale(1.05F) ) } @@ -102,7 +103,7 @@ fun HomeScreen( } item { - Box { + Column { Text( text = "Following", fontFamily = notoserif, @@ -113,7 +114,7 @@ fun HomeScreen( painter = painterResource(R.drawable.ic_underline_following), contentDescription = null, modifier = Modifier - .padding(start = 0.dp, top = 25.dp) + .offset(y = (-5).dp) .scale(1.05F) ) } @@ -144,7 +145,7 @@ fun HomeScreen( verticalArrangement = Arrangement.spacedBy(20.dp), ) { followingArticlesState.articles.forEach { article -> - CreateHorizontalArticleRow( + CreateArticleRow( article ) { onArticleClick( @@ -178,7 +179,7 @@ fun HomeScreen( painter = painterResource(id = R.drawable.ic_volume_bars_orange), contentDescription = null, ) - Box(modifier = Modifier.padding(top = 10.dp)) { + Column(modifier = Modifier.padding(top = 10.dp)) { Text( text = "Nothing to see here!", fontFamily = notoserif, @@ -190,7 +191,8 @@ fun HomeScreen( painter = painterResource(R.drawable.ic_underline_nothing_new), contentDescription = null, modifier = Modifier - .padding(start = 5.dp, top = 20.dp) + .padding(start = 5.dp) + .offset(y = (-5).dp) .scale(1.05F) ) } @@ -216,7 +218,7 @@ fun HomeScreen( painter = painterResource(id = R.drawable.ic_volume_bars_orange), contentDescription = null, ) - Box(modifier = Modifier.padding(top = 10.dp)) { + Column(modifier = Modifier.padding(top = 10.dp)) { Text( text = "You're up to date!", fontFamily = notoserif, @@ -227,7 +229,8 @@ fun HomeScreen( painter = painterResource(R.drawable.ic_underline_up_to_date), contentDescription = null, modifier = Modifier - .padding(start = 1.dp, top = 16.dp) + .padding(start = 1.dp) + .offset(y = (-3).dp) .scale(1.05F) ) } @@ -245,7 +248,7 @@ fun HomeScreen( } item { - Box { + Column { Text( text = "Other Articles", fontFamily = notoserif, @@ -256,7 +259,8 @@ fun HomeScreen( painter = painterResource(R.drawable.ic_underline_other_article), contentDescription = null, modifier = Modifier - .padding(start = 2.dp, top = 25.dp) + .padding(start = 2.dp) + .offset(y = (-7).dp) .scale(1.05F) ) } @@ -283,7 +287,7 @@ fun HomeScreen( verticalArrangement = Arrangement.spacedBy(20.dp), ) { otherArticlesState.articles.forEach { article -> - CreateHorizontalArticleRow( + CreateArticleRow( article ) { onArticleClick( diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/IndividualPublicationScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/IndividualPublicationScreen.kt index 9213cf0..825978c 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/IndividualPublicationScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/IndividualPublicationScreen.kt @@ -15,18 +15,19 @@ import com.cornellappdev.volume.ui.components.general.CreateArticleRow import com.cornellappdev.volume.ui.components.general.CreateIndividualPublicationHeading import com.cornellappdev.volume.ui.states.ArticlesRetrievalState import com.cornellappdev.volume.ui.states.PublicationRetrievalState -import com.cornellappdev.volume.ui.theme.GrayFour +import com.cornellappdev.volume.ui.theme.GrayThree import com.cornellappdev.volume.ui.theme.VolumeOrange import com.cornellappdev.volume.ui.viewmodels.IndividualPublicationViewModel -//"61980a202fef10d6b7f20747" @Composable -fun IndividualPublicationScreen(individualPublicationViewModel: IndividualPublicationViewModel = hiltViewModel(), onArticleClick: (Article, NavigationSource) -> Unit) { - +fun IndividualPublicationScreen( + individualPublicationViewModel: IndividualPublicationViewModel = hiltViewModel(), + onArticleClick: (Article, NavigationSource) -> Unit +) { val publicationUiState = individualPublicationViewModel.publicationUiState LazyColumn { - item{ + item { when (val publicationState = publicationUiState.publicationState) { PublicationRetrievalState.Loading -> { Column( @@ -53,10 +54,14 @@ fun IndividualPublicationScreen(individualPublicationViewModel: IndividualPublic } } } - item{ - Divider(modifier=Modifier.padding(top=20.dp, start=100.dp, end=100.dp), color = GrayFour, thickness = 1.dp) + item { + Row { + Spacer(Modifier.weight(1f, true)) + Divider(Modifier.weight(1f, true), color = GrayThree, thickness = 2.dp) + Spacer(Modifier.weight(1f, true)) + } } - item{ + item { when (val articlesByPublicationState = publicationUiState.articlesByPublicationState) { ArticlesRetrievalState.Loading -> { Column( @@ -73,7 +78,8 @@ fun IndividualPublicationScreen(individualPublicationViewModel: IndividualPublic } is ArticlesRetrievalState.Success -> { - Column(verticalArrangement = Arrangement.spacedBy(20.dp), + Column( + verticalArrangement = Arrangement.spacedBy(20.dp), modifier = Modifier .wrapContentHeight() .padding(top = 20.dp, start = 12.dp, end = 12.dp) diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt index 7eaa069..46fd4d2 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt @@ -61,9 +61,10 @@ fun PublicationsScreen( .padding(top = innerPadding.calculateTopPadding()), ) { item { - Box { + Column( + modifier = Modifier.padding(start = 12.dp, top = 25.dp) + ) { Text( - modifier = Modifier.padding(start = 12.dp, top = 25.dp), text = "Following", fontFamily = notoserif, fontSize = 20.sp, @@ -73,7 +74,7 @@ fun PublicationsScreen( painter = painterResource(R.drawable.ic_underline_following), contentDescription = null, modifier = Modifier - .padding(start = 12.dp, top = 50.dp) + .offset(y = (-5).dp) .scale(1.05F) ) } @@ -112,9 +113,10 @@ fun PublicationsScreen( } } item { - Box { + Column( + modifier = Modifier.padding(start = 12.dp, top = 30.dp) + ) { Text( - modifier = Modifier.padding(start = 12.dp, top = 30.dp), text = "More Publications", fontFamily = notoserif, fontSize = 20.sp, @@ -124,7 +126,8 @@ fun PublicationsScreen( painter = painterResource(R.drawable.ic_underline_more_publications), contentDescription = null, modifier = Modifier - .padding(start = 16.dp, top = 55.dp) + .padding(start = 4.dp) + .offset(y = (-5).dp) .scale(1.06F) ) } diff --git a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt index 2720076..267a585 100644 --- a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt +++ b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt @@ -5,10 +5,8 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.graphics.Bitmap import android.graphics.BitmapFactory import android.media.RingtoneManager -import android.util.Log import androidx.core.app.NotificationCompat import androidx.core.net.toUri import com.cornellappdev.volume.MainActivity @@ -16,10 +14,9 @@ import com.cornellappdev.volume.R import com.cornellappdev.volume.navigation.Routes import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage +import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking -import java.io.InputStream -import java.net.HttpURLConnection -import java.net.URL + /** * Configures the NotificationService for Firebase Messaging @@ -78,115 +75,111 @@ class NotificationService : FirebaseMessagingService() { } } + /** + * Obtains a unique notification ID from the data store and stores the next integer. + */ + private fun getNextNotifId(): Int = runBlocking { + val id = userPreferencesStore.data.first().notificationId + userPreferencesStore.updateData { currentPreferences -> + currentPreferences.toBuilder().setNotificationId((id + 1) % Int.MAX_VALUE).build() + } + return@runBlocking id + } + /** * Create and show a simple notification containing the received FCM message. */ private fun sendNotification( data: MutableMap ) { - Log.d(TAG, data.toString()) - // TODO test out notifications. It leverages Navigation Deep linking, not sure if it works - // https://developer.android.com/jetpack/compose/navigation#deeplinks - var deepLinkIntent: Intent? = null + val notificationManager = + getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channelId = getString((R.string.default_notification_channel_id)) + val channel = NotificationChannel( + channelId, + packageName, + NotificationManager.IMPORTANCE_DEFAULT + ) + notificationManager.createNotificationChannel(channel) // What's sent back to the MainActivity depends on the type of the notification - // received from Firebase. The type is embedded in the data sent for the notification. - when (data[NotificationDataKeys.NOTIFICATION_TYPE.key]) { + // received from Firebase. The type is embedded in the data sent for the notification from the backend. + // + // Volume leverages deep links to send users to the proper composable from the notification. New + // deep links must be added to the Android Manifest. + // See: https://developer.android.com/jetpack/compose/navigation#deeplinks + val deepLinkIntent: Intent = when (data[NotificationDataKeys.NOTIFICATION_TYPE.key]) { NotificationType.NEW_ARTICLE.type -> { - deepLinkIntent = Intent( + Intent( Intent.ACTION_VIEW, "volume://${Routes.OPEN_ARTICLE.route}/${data[NotificationDataKeys.ARTICLE_ID.key]}".toUri(), this, MainActivity::class.java ) -// intent.putExtra( -// NotificationDataKeys.NOTIFICATION_TYPE.key, -// NotificationType.NEW_ARTICLE.type -// ) -// intent.putExtra( -// NotificationDataKeys.ARTICLE_ID.key, -// data[NotificationDataKeys.ARTICLE_ID.key] -// ) -// intent.putExtra( -// NotificationDataKeys.ARTICLE_URL.key, -// data[NotificationDataKeys.ARTICLE_URL.key] -// ) } NotificationType.WEEKLY_DEBRIEF.type -> { - // We simply just need to identify the type of the notification. The - // WeeklyDebrief can be retrieved from the UserRepository#GetUser - deepLinkIntent = Intent( + Intent( Intent.ACTION_VIEW, "volume://${Routes.WEEKLY_DEBRIEF.route}".toUri(), this, MainActivity::class.java ) -// intent.putExtra( -// NotificationDataKeys.NOTIFICATION_TYPE.key, -// NotificationType.NEW_ARTICLE.type -// ) } NotificationType.NEW_MAGAZINE.type -> { - deepLinkIntent = Intent( + Intent( Intent.ACTION_VIEW, "volume://${Routes.OPEN_MAGAZINE.route}/${data[NotificationDataKeys.MAGAZINE_ID.key]}".toUri(), this, MainActivity::class.java ) } + else -> { + Intent(this, MainActivity::class.java) + } } - val pendingIntent = - PendingIntent.getActivity(this, 0, deepLinkIntent, PendingIntent.FLAG_ONE_SHOT) - - val channelId = getString((R.string.default_notification_channel_id)) - val notificationBuilder = NotificationCompat.Builder(this, channelId) - .setSmallIcon(R.drawable.volume_icon) + val volumeNotification = NotificationCompat.Builder(this, channelId) + .setSmallIcon(R.drawable.ic_volume_v) .setLargeIcon( BitmapFactory.decodeResource( resources, - R.drawable.volume_icon + R.drawable.ic_volume_v ) ) .setContentTitle(data[NotificationDataKeys.TITLE.key]) .setContentText(data[NotificationDataKeys.BODY.key]) .setAutoCancel(true) .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) - .setContentIntent(pendingIntent) - -// if (data.imageUrl != null) { -// val bitmap: Bitmap? = getBitmapFromUrl(data.imageUrl.toString()) -// notificationBuilder.setStyle( -// NotificationCompat.BigPictureStyle() -// .bigPicture(bitmap) -// ) -// } - - val notificationManager = - getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - // Since android Oreo notification channel is needed. - val channel = NotificationChannel( - channelId, - packageName, - NotificationManager.IMPORTANCE_DEFAULT - ) - notificationManager.createNotificationChannel(channel) - - notificationManager.notify(0 /* ID of notification */, notificationBuilder.build()) - } - - private fun getBitmapFromUrl(imageUrl: String): Bitmap? { - return try { - val url = URL(imageUrl) - val connection: HttpURLConnection = url.openConnection() as HttpURLConnection - connection.doInput = true - connection.connect() - val input: InputStream = connection.inputStream - BitmapFactory.decodeStream(input) - } catch (e: Exception) { - Log.e(TAG, "Error in getting notification image: " + e.localizedMessage) - null + .setContentIntent( + PendingIntent.getActivity( + this, + 0, + deepLinkIntent, + PendingIntent.FLAG_IMMUTABLE + ) + ) + .setGroup(channelId) + + val summaryNotification = NotificationCompat.Builder(this, channelId) + .setContentTitle("Volume") + .setSmallIcon(R.drawable.ic_volume_v) + .setStyle( + NotificationCompat.InboxStyle() + .setSummaryText("New content on Volume!") + ) + .setGroup(channelId) + .setGroupSummary(true) + .build() + + notificationManager.apply { + notify( + getNextNotifId(), + volumeNotification.build() + ) + notify( + -1, + summaryNotification + ) } } diff --git a/app/src/main/proto/user_prefs.proto b/app/src/main/proto/user_prefs.proto index 191c287..86b4d70 100644 --- a/app/src/main/proto/user_prefs.proto +++ b/app/src/main/proto/user_prefs.proto @@ -16,4 +16,6 @@ message UserPreferences { repeated string bookmarked_articles = 4; map shoutout = 5; + + int32 notification_id = 6; } diff --git a/app/src/main/res/drawable/ic_instagram.xml b/app/src/main/res/drawable/ic_instagram.xml index 2121626..ff2da31 100644 --- a/app/src/main/res/drawable/ic_instagram.xml +++ b/app/src/main/res/drawable/ic_instagram.xml @@ -8,12 +8,12 @@ android:pathData="M0.848,0h12.97v13h-12.97z"/> - - + android:fillColor="#C4C4C4" /> + + From c772fc8e7202f855a1205aeed0036a6bc0d85bf4 Mon Sep 17 00:00:00 2001 From: ckd38 Date: Fri, 4 Nov 2022 17:24:28 -0400 Subject: [PATCH 07/16] Delete Log --- .../com/cornellappdev/volume/MainActivity.kt | 3 --- app/src/main/res/drawable/ic_volume_v.png | Bin 0 -> 17745 bytes 2 files changed, 3 deletions(-) create mode 100644 app/src/main/res/drawable/ic_volume_v.png diff --git a/app/src/main/java/com/cornellappdev/volume/MainActivity.kt b/app/src/main/java/com/cornellappdev/volume/MainActivity.kt index a33c63a..6cea3fa 100644 --- a/app/src/main/java/com/cornellappdev/volume/MainActivity.kt +++ b/app/src/main/java/com/cornellappdev/volume/MainActivity.kt @@ -1,7 +1,6 @@ package com.cornellappdev.volume import android.os.Bundle -import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material.MaterialTheme @@ -19,8 +18,6 @@ class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.d("MainActivity", BuildConfig.DEBUG.toString()) - Log.d("MainActivity", BuildConfig.ENDPOINT) val onboardingCompleted = runBlocking { return@runBlocking userPreferences.fetchOnboardingCompleted() } diff --git a/app/src/main/res/drawable/ic_volume_v.png b/app/src/main/res/drawable/ic_volume_v.png new file mode 100644 index 0000000000000000000000000000000000000000..a76c346ad495e1e9d9a05f307ffa4bd199bfa467 GIT binary patch literal 17745 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 literal 0 HcmV?d00001 From 230878d67dcd574d0de9693a5974b568e09797cf Mon Sep 17 00:00:00 2001 From: ckd38 Date: Fri, 4 Nov 2022 17:25:43 -0400 Subject: [PATCH 08/16] Remove comment --- .../ui/components/general/IndividualPublicationComponents.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt b/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt index 6feb802..caa9346 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/components/general/IndividualPublicationComponents.kt @@ -160,7 +160,6 @@ fun CreateIndividualPublicationHeading( horizontalArrangement = Arrangement.spacedBy(13.dp) ) { for (social in publication.socials) { - // Capitalizes the first letter in the social name val socialName = formattedSocialNameMap.getOrDefault(social.social, social.social) From 44426741483daaa542ece780e719aeac683f606e Mon Sep 17 00:00:00 2001 From: ckd38 Date: Sun, 6 Nov 2022 19:06:19 -0500 Subject: [PATCH 09/16] Add notification support for Android 13+ --- app/build.gradle | 7 +- app/src/main/AndroidManifest.xml | 1 + .../com/cornellappdev/volume/MainActivity.kt | 12 + .../repositories/UserPreferencesRepository.kt | 9 + .../volume/navigation/MainTabbedNavigation.kt | 9 +- .../general/PermissionRequestDialog.kt | 134 +++++ .../volume/ui/screens/HomeScreen.kt | 458 +++++++++--------- .../volume/ui/viewmodels/HomeViewModel.kt | 11 +- .../volume/util/NotificationService.kt | 14 +- app/src/main/proto/user_prefs.proto | 3 + app/src/main/res/values/strings.xml | 2 +- 11 files changed, 426 insertions(+), 234 deletions(-) create mode 100644 app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt diff --git a/app/build.gradle b/app/build.gradle index cc84a72..aeac94e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,7 +19,7 @@ android { defaultConfig { applicationId "com.cornellappdev.volume" minSdk 27 - targetSdk 32 + targetSdk 33 versionCode 1 versionName "1.0" @@ -107,11 +107,12 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' // Firebase - implementation platform('com.google.firebase:firebase-bom:27.0.0') + implementation platform('com.google.firebase:firebase-bom:31.0.2') implementation 'com.google.firebase:firebase-inappmessaging-display-ktx' + implementation 'com.vmadalin:easypermissions-ktx:1.0.0' + implementation 'com.google.accompanist:accompanist-permissions:0.27.0' implementation 'com.google.firebase:firebase-messaging-ktx' implementation 'com.google.firebase:firebase-analytics-ktx' - implementation 'com.google.firebase:firebase-installations:17.0.2' // Accompanist implementation 'com.google.accompanist:accompanist-pager:0.26.2-beta' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bdf12f6..6ae5f3d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + + currentPreferences.toBuilder().setNotificationFlowCompleted(value).build() + } + } + suspend fun addBookmarkedArticle(articleId: String) { userPreferencesStore.updateData { currentPreferences -> val currentBookmarks = currentPreferences.bookmarkedArticlesList.toHashSet() @@ -78,4 +84,7 @@ class UserPreferencesRepository @Inject constructor( suspend fun fetchShoutoutCount(articleId: String): Int = userPreferencesFlow.first().shoutoutMap.getOrDefault(articleId, 0) + + suspend fun fetchNotificationFlowStatus(): Boolean = + userPreferencesFlow.first().notificationFlowCompleted } diff --git a/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt b/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt index 45306e3..38f8e85 100644 --- a/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt +++ b/app/src/main/java/com/cornellappdev/volume/navigation/MainTabbedNavigation.kt @@ -5,6 +5,7 @@ import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.padding import androidx.compose.material.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable @@ -89,6 +90,7 @@ fun TabbedNavigationSetup(onboardingCompleted: Boolean) { modifier = Modifier.padding(innerPadding), isOnboardingCompleted = onboardingCompleted, navController = navController, + showBottomBar = showBottomBar ) } } @@ -122,6 +124,7 @@ fun BottomNavigationBar(navController: NavHostController, tabItems: List, ) { // The starting destination switches to onboarding if it isn't completed. AnimatedNavHost( @@ -157,8 +161,11 @@ private fun MainScreenNavigationConfigurations( }) { HomeScreen( onArticleClick = { article, navigationSource -> + FirstTimeShown.firstTimeShown = false navController.navigate("${Routes.OPEN_ARTICLE.route}/${article.id}/${navigationSource.name}") - }) + }, + showBottomBar = showBottomBar, + ) } composable(route = Routes.WEEKLY_DEBRIEF.route, deepLinks = listOf( navDeepLink { uriPattern = "volume://${Routes.WEEKLY_DEBRIEF.route}" } diff --git a/app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt b/app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt new file mode 100644 index 0000000..2781f45 --- /dev/null +++ b/app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt @@ -0,0 +1,134 @@ +package com.cornellappdev.volume.ui.components.general + +import android.Manifest +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import android.util.Log +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.cornellappdev.volume.ui.theme.VolumeOrange +import com.cornellappdev.volume.ui.theme.lato +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.PermissionStatus +import com.google.accompanist.permissions.rememberPermissionState + +/** + * Creates a Permission request dialog for requesting POST_NOTIFICATIONS permission. + * + * We only need to request the permissions if the Android phone is on version 13 or above. + */ +@OptIn(ExperimentalPermissionsApi::class) +@Composable +fun PermissionRequestDialog( + showBottomBar: MutableState, + notificationFlowStatus: Boolean, + updateNotificationFlowStatus: (Boolean) -> Unit +) { + var requestingPermission by remember { mutableStateOf(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) } + showBottomBar.value = !requestingPermission + + AnimatedVisibility( + visible = requestingPermission, + enter = fadeIn(), + exit = fadeOut() + ) { + val context = LocalContext.current + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + val notificationPermissionState = + rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS) + + when (val permissionStatus = notificationPermissionState.status) { + is PermissionStatus.Granted -> { + requestingPermission = false + } + is PermissionStatus.Denied -> { + Log.d("HomeScreen", "Showing rationale") + + Surface( + color = Color.Black.copy(alpha = 0.6f), + modifier = Modifier.fillMaxSize() + ) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Card( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(horizontal = 20.dp), + elevation = 10.dp + ) { + Column( + modifier = Modifier.padding(33.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Notifications are necessary to send you " + + "updates about new articles and magazines by publishers you follow and the Weekly Debrief! " + + if (permissionStatus.shouldShowRationale || !notificationFlowStatus) { + "" + } else { + "\n\nPlease click the button below to go to the settings to enable notifications." + }, + textAlign = TextAlign.Center, + fontFamily = lato, + fontWeight = FontWeight.Medium + ) + Spacer(modifier = Modifier.height(15.dp)) + Button( + onClick = { + if (permissionStatus.shouldShowRationale || !notificationFlowStatus) { + notificationPermissionState.launchPermissionRequest() + updateNotificationFlowStatus(true) + } else { + context.openSettings() + } + requestingPermission = false + }, + shape = RoundedCornerShape(5.dp), + colors = ButtonDefaults.buttonColors(backgroundColor = VolumeOrange), + ) { + Text( + text = if (permissionStatus.shouldShowRationale || !notificationFlowStatus) { + "Request Permission" + } else { + "Open Settings" + }, + color = Color.White, + fontFamily = lato + ) + } + } + } + } + } + } + } + } + } +} + +fun Context.openSettings() { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.data = Uri.fromParts("package", packageName, null) + startActivity(intent) +} diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt index efffdcb..68e2272 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/HomeScreen.kt @@ -26,6 +26,7 @@ import com.cornellappdev.volume.analytics.NavigationSource import com.cornellappdev.volume.data.models.Article import com.cornellappdev.volume.ui.components.general.CreateArticleRow import com.cornellappdev.volume.ui.components.general.CreateBigReadRow +import com.cornellappdev.volume.ui.components.general.PermissionRequestDialog import com.cornellappdev.volume.ui.states.ArticlesRetrievalState import com.cornellappdev.volume.ui.theme.VolumeOrange import com.cornellappdev.volume.ui.theme.lato @@ -35,271 +36,292 @@ import com.cornellappdev.volume.ui.viewmodels.HomeViewModel @Composable fun HomeScreen( homeViewModel: HomeViewModel = hiltViewModel(), - onArticleClick: (Article, NavigationSource) -> Unit + onArticleClick: (Article, NavigationSource) -> Unit, + showBottomBar: MutableState, ) { val homeUiState = homeViewModel.homeUiState var showPageBreak by remember { mutableStateOf(false) } - Scaffold(topBar = { - // TODO fix positioning, little weird on my phone not sure if that's the case universally - Image( - painter = painterResource(R.drawable.volume_title), - contentDescription = null, - modifier = Modifier - .scale(0.8f) - ) - }, content = { innerPadding -> - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(start = 12.dp, top = innerPadding.calculateTopPadding()), - ) { - item { - Column { - Text( - text = "The Big Read", - fontFamily = notoserif, - fontSize = 20.sp, - fontWeight = FontWeight.Medium, - modifier = Modifier.padding(top = 15.dp) - ) - Image( - painter = painterResource(R.drawable.ic_underline_big_read), - contentDescription = null, - modifier = Modifier - .offset(y = (-5).dp) - .padding(start = 2.dp) - .scale(1.05F) - ) + Box { + Scaffold(topBar = { + Image( + painter = painterResource(R.drawable.volume_title), + contentDescription = null, + modifier = Modifier + .scale(0.8f) + ) + }, content = { innerPadding -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(start = 12.dp, top = innerPadding.calculateTopPadding()), + ) { + item { + Column { + Text( + text = "The Big Read", + fontFamily = notoserif, + fontSize = 20.sp, + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(top = 15.dp) + ) + Image( + painter = painterResource(R.drawable.ic_underline_big_read), + contentDescription = null, + modifier = Modifier + .offset(y = (-5).dp) + .padding(start = 2.dp) + .scale(1.05F) + ) + } + Spacer(modifier = Modifier.height(25.dp)) } - Spacer(modifier = Modifier.height(25.dp)) - } - item { - when (val trendingArticlesState = homeUiState.trendingArticlesState) { - ArticlesRetrievalState.Loading -> { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - CircularProgressIndicator(color = VolumeOrange) + item { + when (val trendingArticlesState = homeUiState.trendingArticlesState) { + ArticlesRetrievalState.Loading -> { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + CircularProgressIndicator(color = VolumeOrange) + } } - } - ArticlesRetrievalState.Error -> { - // TODO Prompt to try again, queryTrendingArticles manually (it's public). Could be that internet is down. - } - is ArticlesRetrievalState.Success -> { - LazyRow(horizontalArrangement = Arrangement.spacedBy(24.dp)) { - items(trendingArticlesState.articles) { article -> - CreateBigReadRow(article) { - onArticleClick(article, NavigationSource.TRENDING_ARTICLES) + ArticlesRetrievalState.Error -> { + // TODO Prompt to try again, queryTrendingArticles manually (it's public). Could be that internet is down. + } + is ArticlesRetrievalState.Success -> { + LazyRow(horizontalArrangement = Arrangement.spacedBy(24.dp)) { + items(trendingArticlesState.articles) { article -> + CreateBigReadRow(article) { + onArticleClick( + article, + NavigationSource.TRENDING_ARTICLES + ) + } } } } } + Spacer(modifier = Modifier.height(25.dp)) } - Spacer(modifier = Modifier.height(25.dp)) - } - - item { - Column { - Text( - text = "Following", - fontFamily = notoserif, - fontSize = 20.sp, - fontWeight = FontWeight.Medium - ) - Image( - painter = painterResource(R.drawable.ic_underline_following), - contentDescription = null, - modifier = Modifier - .offset(y = (-5).dp) - .scale(1.05F) - ) + item { + Column { + Text( + text = "Following", + fontFamily = notoserif, + fontSize = 20.sp, + fontWeight = FontWeight.Medium + ) + Image( + painter = painterResource(R.drawable.ic_underline_following), + contentDescription = null, + modifier = Modifier + .offset(y = (-5).dp) + .scale(1.05F) + ) + } } - } - item { - when (val followingArticlesState = homeUiState.followingArticlesState) { - ArticlesRetrievalState.Loading -> { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - CircularProgressIndicator( - color = VolumeOrange, - modifier = Modifier.padding(vertical = 50.dp) - ) - } - } - ArticlesRetrievalState.Error -> { - // TODO Prompt to try again, queryFollowingArticles manually (it's public). Could be that internet is down. - } - is ArticlesRetrievalState.Success -> { - Box(modifier = Modifier.padding(top = 10.dp)) { + item { + when (val followingArticlesState = homeUiState.followingArticlesState) { + ArticlesRetrievalState.Loading -> { Column( - modifier = Modifier - .wrapContentHeight() - .padding(end = 12.dp), - verticalArrangement = Arrangement.spacedBy(20.dp), + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally ) { - followingArticlesState.articles.forEach { article -> - CreateArticleRow( - article - ) { - onArticleClick( - article, - NavigationSource.FOLLOWING_ARTICLES - ) + CircularProgressIndicator( + color = VolumeOrange, + modifier = Modifier.padding(vertical = 50.dp) + ) + } + } + ArticlesRetrievalState.Error -> { + // TODO Prompt to try again, queryFollowingArticles manually (it's public). Could be that internet is down. + } + is ArticlesRetrievalState.Success -> { + Box(modifier = Modifier.padding(top = 10.dp)) { + Column( + modifier = Modifier + .wrapContentHeight() + .padding(end = 12.dp), + verticalArrangement = Arrangement.spacedBy(20.dp), + ) { + followingArticlesState.articles.forEach { article -> + CreateArticleRow( + article + ) { + onArticleClick( + article, + NavigationSource.FOLLOWING_ARTICLES + ) + } } } + showPageBreak = true } - showPageBreak = true } } } - } - item { - AnimatedVisibility( - visible = showPageBreak, - enter = fadeIn(), - exit = fadeOut() - ) { - if (homeUiState.isFollowingEmpty) { - Column( - modifier = Modifier - .padding(vertical = 40.dp, horizontal = 16.dp) - .fillMaxWidth() - .wrapContentHeight(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Image( - painter = painterResource(id = R.drawable.ic_volume_bars_orange), - contentDescription = null, - ) - Column(modifier = Modifier.padding(top = 10.dp)) { - Text( - text = "Nothing to see here!", - fontFamily = notoserif, - fontSize = 16.sp, - fontWeight = FontWeight.Medium, - textAlign = TextAlign.Center - ) + item { + AnimatedVisibility( + visible = showPageBreak, + enter = fadeIn(), + exit = fadeOut() + ) { + if (homeUiState.isFollowingEmpty) { + Column( + modifier = Modifier + .padding(vertical = 40.dp, horizontal = 16.dp) + .fillMaxWidth() + .wrapContentHeight(), + horizontalAlignment = Alignment.CenterHorizontally + ) { Image( - painter = painterResource(R.drawable.ic_underline_nothing_new), + painter = painterResource(id = R.drawable.ic_volume_bars_orange), contentDescription = null, - modifier = Modifier - .padding(start = 5.dp) - .offset(y = (-5).dp) - .scale(1.05F) ) - } + Column(modifier = Modifier.padding(top = 10.dp)) { + Text( + text = "Nothing to see here!", + fontFamily = notoserif, + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.Center + ) + Image( + painter = painterResource(R.drawable.ic_underline_nothing_new), + contentDescription = null, + modifier = Modifier + .padding(start = 5.dp) + .offset(y = (-5).dp) + .scale(1.05F) + ) + } - Text( - text = "Follow some student publications that you are interested in.", - fontFamily = lato, - fontSize = 12.sp, - fontWeight = FontWeight.Medium, - textAlign = TextAlign.Center, - modifier = Modifier.padding(top = 10.dp) - ) - } - } else { - Column( - modifier = Modifier - .padding(vertical = 70.dp, horizontal = 16.dp) - .fillMaxWidth() - .wrapContentHeight(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Image( - painter = painterResource(id = R.drawable.ic_volume_bars_orange), - contentDescription = null, - ) - Column(modifier = Modifier.padding(top = 10.dp)) { Text( - text = "You're up to date!", - fontFamily = notoserif, + text = "Follow some student publications that you are interested in.", + fontFamily = lato, fontSize = 12.sp, - fontWeight = FontWeight.Medium + fontWeight = FontWeight.Medium, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 10.dp) ) + } + } else { + Column( + modifier = Modifier + .padding(vertical = 70.dp, horizontal = 16.dp) + .fillMaxWidth() + .wrapContentHeight(), + horizontalAlignment = Alignment.CenterHorizontally + ) { Image( - painter = painterResource(R.drawable.ic_underline_up_to_date), + painter = painterResource(id = R.drawable.ic_volume_bars_orange), contentDescription = null, - modifier = Modifier - .padding(start = 1.dp) - .offset(y = (-3).dp) - .scale(1.05F) + ) + Column(modifier = Modifier.padding(top = 10.dp)) { + Text( + text = "You're up to date!", + fontFamily = notoserif, + fontSize = 12.sp, + fontWeight = FontWeight.Medium + ) + Image( + painter = painterResource(R.drawable.ic_underline_up_to_date), + contentDescription = null, + modifier = Modifier + .padding(start = 1.dp) + .offset(y = (-3).dp) + .scale(1.05F) + ) + } + Text( + text = "You've seen all new articles from the publications you are following.", + fontFamily = lato, + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.Center, + modifier = Modifier.padding(top = 10.dp) ) } - Text( - text = "You've seen all new articles from the publications you are following.", - fontFamily = lato, - fontSize = 12.sp, - fontWeight = FontWeight.Medium, - textAlign = TextAlign.Center, - modifier = Modifier.padding(top = 10.dp) - ) } } } - } - item { - Column { - Text( - text = "Other Articles", - fontFamily = notoserif, - fontSize = 20.sp, - fontWeight = FontWeight.Medium - ) - Image( - painter = painterResource(R.drawable.ic_underline_other_article), - contentDescription = null, - modifier = Modifier - .padding(start = 2.dp) - .offset(y = (-7).dp) - .scale(1.05F) - ) + item { + Column { + Text( + text = "Other Articles", + fontFamily = notoserif, + fontSize = 20.sp, + fontWeight = FontWeight.Medium + ) + Image( + painter = painterResource(R.drawable.ic_underline_other_article), + contentDescription = null, + modifier = Modifier + .padding(start = 2.dp) + .offset(y = (-7).dp) + .scale(1.05F) + ) + } } - } - item { - when (val otherArticlesState = homeUiState.otherArticlesState) { - ArticlesRetrievalState.Loading -> { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - CircularProgressIndicator(color = VolumeOrange) + item { + when (val otherArticlesState = homeUiState.otherArticlesState) { + ArticlesRetrievalState.Loading -> { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + CircularProgressIndicator(color = VolumeOrange) + } } - } - ArticlesRetrievalState.Error -> { - // TODO Prompt to try again, queryAllArticles manually (it's public). Could be that internet is down. - } - is ArticlesRetrievalState.Success -> { - Column( - modifier = Modifier - .wrapContentHeight() - .padding(end = 12.dp, top = 25.dp), - verticalArrangement = Arrangement.spacedBy(20.dp), - ) { - otherArticlesState.articles.forEach { article -> - CreateArticleRow( - article - ) { - onArticleClick( - article, - NavigationSource.OTHER_ARTICLES - ) + ArticlesRetrievalState.Error -> { + // TODO Prompt to try again, queryAllArticles manually (it's public). Could be that internet is down. + } + is ArticlesRetrievalState.Success -> { + Column( + modifier = Modifier + .wrapContentHeight() + .padding(end = 12.dp, top = 25.dp), + verticalArrangement = Arrangement.spacedBy(20.dp), + ) { + otherArticlesState.articles.forEach { article -> + CreateArticleRow( + article + ) { + onArticleClick( + article, + NavigationSource.OTHER_ARTICLES + ) + } } } } } } } + }) + + if (FirstTimeShown.firstTimeShown) { + PermissionRequestDialog( + showBottomBar = showBottomBar, + notificationFlowStatus = homeViewModel.getNotificationPermissionFlowStatus(), + updateNotificationFlowStatus = { + homeViewModel.updateNotificationPermissionFlowStatus(it) + }) } - }) + } +} + +/** + * Keeps track of when app navigates away from HomeScreen so PermissionRequestDialog + * only occurs when the app FIRST is navigated to the HomeScreen. + */ +object FirstTimeShown { + var firstTimeShown = true } diff --git a/app/src/main/java/com/cornellappdev/volume/ui/viewmodels/HomeViewModel.kt b/app/src/main/java/com/cornellappdev/volume/ui/viewmodels/HomeViewModel.kt index 710706f..2db253b 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/viewmodels/HomeViewModel.kt @@ -14,6 +14,7 @@ import com.cornellappdev.volume.data.repositories.UserRepository import com.cornellappdev.volume.ui.states.ArticlesRetrievalState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import javax.inject.Inject // TODO add refreshing if user follows new publications? @@ -40,7 +41,7 @@ class HomeViewModel @Inject constructor( /** * State that holds information on whether the user has any followed articles */ - val isFollowingEmpty: Boolean = false + val isFollowingEmpty: Boolean = false, ) var homeUiState by mutableStateOf(HomeUiState()) @@ -52,6 +53,14 @@ class HomeViewModel @Inject constructor( queryTrendingArticles() } + fun getNotificationPermissionFlowStatus() = runBlocking { + return@runBlocking userPreferencesRepository.fetchNotificationFlowStatus() + } + + fun updateNotificationPermissionFlowStatus(value: Boolean) = viewModelScope.launch { + userPreferencesRepository.updateNotificationFlowStatus(value) + } + // Updates the state accordingly with the trending articles fun queryTrendingArticles(limit: Double? = NUMBER_OF_TRENDING_ARTICLES) = viewModelScope.launch { diff --git a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt index 267a585..e70025f 100644 --- a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt +++ b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt @@ -1,6 +1,5 @@ package com.cornellappdev.volume.util -import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context @@ -95,12 +94,6 @@ class NotificationService : FirebaseMessagingService() { val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val channelId = getString((R.string.default_notification_channel_id)) - val channel = NotificationChannel( - channelId, - packageName, - NotificationManager.IMPORTANCE_DEFAULT - ) - notificationManager.createNotificationChannel(channel) // What's sent back to the MainActivity depends on the type of the notification // received from Firebase. The type is embedded in the data sent for the notification from the backend. @@ -158,7 +151,8 @@ class NotificationService : FirebaseMessagingService() { PendingIntent.FLAG_IMMUTABLE ) ) - .setGroup(channelId) + .setGroup(packageName) + .build() val summaryNotification = NotificationCompat.Builder(this, channelId) .setContentTitle("Volume") @@ -167,14 +161,14 @@ class NotificationService : FirebaseMessagingService() { NotificationCompat.InboxStyle() .setSummaryText("New content on Volume!") ) - .setGroup(channelId) + .setGroup(packageName) .setGroupSummary(true) .build() notificationManager.apply { notify( getNextNotifId(), - volumeNotification.build() + volumeNotification ) notify( -1, diff --git a/app/src/main/proto/user_prefs.proto b/app/src/main/proto/user_prefs.proto index 86b4d70..4a7c802 100644 --- a/app/src/main/proto/user_prefs.proto +++ b/app/src/main/proto/user_prefs.proto @@ -18,4 +18,7 @@ message UserPreferences { map shoutout = 5; int32 notification_id = 6; + + // Keeps track of when the user is first presented with the PermissionRequestDialog + bool notification_flow_completed = 7; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index de0be38..db0eb52 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,7 +7,7 @@ %1$d shout-out %1$d shout-outs - fcm_default_channel + com.cornellappdev.volume Following From 6ca462809560c7696bb80097b9b65dee82761ad7 Mon Sep 17 00:00:00 2001 From: ckd38 Date: Sun, 6 Nov 2022 19:10:10 -0500 Subject: [PATCH 10/16] Remove Log --- .../volume/ui/components/general/PermissionRequestDialog.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt b/app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt index 2781f45..b60c9c2 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt @@ -6,7 +6,6 @@ import android.content.Intent import android.net.Uri import android.os.Build import android.provider.Settings -import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -58,8 +57,6 @@ fun PermissionRequestDialog( requestingPermission = false } is PermissionStatus.Denied -> { - Log.d("HomeScreen", "Showing rationale") - Surface( color = Color.Black.copy(alpha = 0.6f), modifier = Modifier.fillMaxSize() From 32db9edc417051c841e3a53303f670e70a6b22d7 Mon Sep 17 00:00:00 2001 From: ckd38 Date: Wed, 16 Nov 2022 11:52:18 -0500 Subject: [PATCH 11/16] Add Notification support for API 33 --- .../volume/ui/components/general/PermissionRequestDialog.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt b/app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt index b60c9c2..2781f45 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/components/general/PermissionRequestDialog.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.net.Uri import android.os.Build import android.provider.Settings +import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -57,6 +58,8 @@ fun PermissionRequestDialog( requestingPermission = false } is PermissionStatus.Denied -> { + Log.d("HomeScreen", "Showing rationale") + Surface( color = Color.Black.copy(alpha = 0.6f), modifier = Modifier.fillMaxSize() From fe1d4b70885cd63a5c3ea5ce4a93dffa25c730d5 Mon Sep 17 00:00:00 2001 From: Emily Hu Date: Wed, 16 Nov 2022 18:00:24 -0500 Subject: [PATCH 12/16] added the content type --- .../graphql/com/cornellappdev/volume/queries.graphql | 9 +++++++++ .../com/cornellappdev/volume/data/models/Publication.kt | 1 + .../volume/data/repositories/ArticleRepository.kt | 7 +++++++ .../volume/data/repositories/PublicationRepository.kt | 4 ++++ 4 files changed, 21 insertions(+) diff --git a/app/src/main/graphql/com/cornellappdev/volume/queries.graphql b/app/src/main/graphql/com/cornellappdev/volume/queries.graphql index 46f9c68..1995548 100644 --- a/app/src/main/graphql/com/cornellappdev/volume/queries.graphql +++ b/app/src/main/graphql/com/cornellappdev/volume/queries.graphql @@ -11,6 +11,7 @@ query AllArticles ($limit: Float) { rssURL slug shoutouts + contentTypes websiteURL numArticles socials { @@ -48,6 +49,7 @@ query AllPublications { shoutouts websiteURL numArticles + contentTypes socials { social url:URL @@ -75,6 +77,7 @@ query PublicationBySlug($slug: String!) { slug shoutouts websiteURL + contentTypes numArticles socials { social @@ -105,6 +108,7 @@ query TrendingArticles ($limit: Float) { rssURL slug shoutouts + contentTypes numArticles websiteURL socials { @@ -144,6 +148,7 @@ query ArticlesByPublicationSlug($slug: String!) { rssURL slug shoutouts + contentTypes websiteURL numArticles socials { @@ -180,6 +185,7 @@ query ArticlesByIDs($ids:[String!]!) { rssURL slug shoutouts + contentTypes websiteURL numArticles socials { @@ -217,6 +223,7 @@ query ArticleByID($id: String!) { rssURL slug shoutouts + contentTypes websiteURL numArticles socials { @@ -256,6 +263,7 @@ query ArticlesByPublicationSlugs($slugs: [String!]!) { rssURL slug shoutouts + contentTypes numArticles websiteURL socials { @@ -296,6 +304,7 @@ query ArticlesAfterDate($since: String!) { rssURL slug shoutouts + contentTypes numArticles websiteURL socials { diff --git a/app/src/main/java/com/cornellappdev/volume/data/models/Publication.kt b/app/src/main/java/com/cornellappdev/volume/data/models/Publication.kt index 2522729..c119935 100644 --- a/app/src/main/java/com/cornellappdev/volume/data/models/Publication.kt +++ b/app/src/main/java/com/cornellappdev/volume/data/models/Publication.kt @@ -25,6 +25,7 @@ data class Publication( val slug: String, val shoutouts: Double, val websiteURL: String, + val contentTypes: List, val numArticles: Double, val mostRecentArticle: Article? = null, val socials: List diff --git a/app/src/main/java/com/cornellappdev/volume/data/repositories/ArticleRepository.kt b/app/src/main/java/com/cornellappdev/volume/data/repositories/ArticleRepository.kt index 9bdaf79..7a15c9c 100644 --- a/app/src/main/java/com/cornellappdev/volume/data/repositories/ArticleRepository.kt +++ b/app/src/main/java/com/cornellappdev/volume/data/repositories/ArticleRepository.kt @@ -55,12 +55,14 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, + contentTypes = publication.contentTypes, websiteURL = publication.websiteURL, numArticles = publication.numArticles, socials = publication.socials .map { Social(it.social, it.url) } ), shoutouts = articleData.shoutouts, + nsfw = articleData.nsfw ) } @@ -84,6 +86,7 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, + contentTypes = publication.contentTypes, numArticles = publication.numArticles, websiteURL = publication.websiteURL, socials = publication.socials @@ -113,6 +116,7 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, + contentTypes = publication.contentTypes, numArticles = publication.numArticles, websiteURL = publication.websiteURL, socials = publication.socials @@ -142,6 +146,7 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, + contentTypes = publication.contentTypes, numArticles = publication.numArticles, websiteURL = publication.websiteURL, socials = publication.socials @@ -171,6 +176,7 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, + contentTypes = publication.contentTypes, numArticles = publication.numArticles, websiteURL = publication.websiteURL, socials = publication.socials @@ -200,6 +206,7 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, + contentTypes = publication.contentTypes, websiteURL = publication.websiteURL, numArticles = publication.numArticles, socials = publication.socials diff --git a/app/src/main/java/com/cornellappdev/volume/data/repositories/PublicationRepository.kt b/app/src/main/java/com/cornellappdev/volume/data/repositories/PublicationRepository.kt index 768594c..0115bcf 100644 --- a/app/src/main/java/com/cornellappdev/volume/data/repositories/PublicationRepository.kt +++ b/app/src/main/java/com/cornellappdev/volume/data/repositories/PublicationRepository.kt @@ -43,6 +43,7 @@ class PublicationRepository @Inject constructor(private val networkApi: NetworkA slug = publicationData.slug, shoutouts = publicationData.shoutouts, numArticles = publicationData.numArticles, + contentTypes = publicationData.contentTypes, websiteURL = publicationData.websiteURL, mostRecentArticle = publicationData.mostRecentArticle?.nsfw?.let { isNSFW -> Article( @@ -60,6 +61,7 @@ class PublicationRepository @Inject constructor(private val networkApi: NetworkA slug = publicationData.slug, shoutouts = publicationData.shoutouts, numArticles = publicationData.numArticles, + contentTypes = publicationData.contentTypes, websiteURL = publicationData.websiteURL, socials = publicationData.socials .map { Social(it.social, it.url) }), @@ -85,6 +87,7 @@ class PublicationRepository @Inject constructor(private val networkApi: NetworkA rssURL = publicationData.rssURL, slug = publicationData.slug, shoutouts = publicationData.shoutouts, + contentTypes = publicationData.contentTypes, numArticles = publicationData.numArticles, websiteURL = publicationData.websiteURL, mostRecentArticle = publicationData.mostRecentArticle?.nsfw?.let { isNSFW -> @@ -103,6 +106,7 @@ class PublicationRepository @Inject constructor(private val networkApi: NetworkA rssURL = publicationData.rssURL, slug = publicationData.slug, shoutouts = publicationData.shoutouts, + contentTypes = publicationData.contentTypes, websiteURL = publicationData.websiteURL, numArticles = publicationData.numArticles, socials = publicationData.socials From 3c56a596b7293ad682deb2bfaa7b240eb71bdf22 Mon Sep 17 00:00:00 2001 From: benha Date: Wed, 16 Nov 2022 18:00:40 -0500 Subject: [PATCH 13/16] Remove instances of debrief and magazines --- .../volume/ui/screens/PublicationsScreen.kt | 1 + .../volume/util/NotificationService.kt | 32 +++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt index 46fd4d2..4114ed3 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt @@ -172,6 +172,7 @@ fun PublicationsScreen( ) } } + } } } diff --git a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt index e70025f..413a35c 100644 --- a/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt +++ b/app/src/main/java/com/cornellappdev/volume/util/NotificationService.kt @@ -110,22 +110,22 @@ class NotificationService : FirebaseMessagingService() { MainActivity::class.java ) } - NotificationType.WEEKLY_DEBRIEF.type -> { - Intent( - Intent.ACTION_VIEW, - "volume://${Routes.WEEKLY_DEBRIEF.route}".toUri(), - this, - MainActivity::class.java - ) - } - NotificationType.NEW_MAGAZINE.type -> { - Intent( - Intent.ACTION_VIEW, - "volume://${Routes.OPEN_MAGAZINE.route}/${data[NotificationDataKeys.MAGAZINE_ID.key]}".toUri(), - this, - MainActivity::class.java - ) - } +// NotificationType.WEEKLY_DEBRIEF.type -> { +// Intent( +// Intent.ACTION_VIEW, +// "volume://${Routes.WEEKLY_DEBRIEF.route}".toUri(), +// this, +// MainActivity::class.java +// ) +// } +// NotificationType.NEW_MAGAZINE.type -> { +// Intent( +// Intent.ACTION_VIEW, +// "volume://${Routes.OPEN_MAGAZINE.route}/${data[NotificationDataKeys.MAGAZINE_ID.key]}".toUri(), +// this, +// MainActivity::class.java +// ) +// } else -> { Intent(this, MainActivity::class.java) } From ac8fd0213b66f068d998b98f08a796d55d86f80b Mon Sep 17 00:00:00 2001 From: benha Date: Wed, 16 Nov 2022 18:19:49 -0500 Subject: [PATCH 14/16] Hide Magazine Publications --- .../ui/components/onboarding/SecondPage.kt | 48 ++++++++++--------- .../volume/ui/screens/PublicationsScreen.kt | 27 ++++++----- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt b/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt index d557ec5..b21b3bd 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt @@ -130,29 +130,33 @@ fun SecondPage( ) { publication -> // Clicking on row IN onboarding should not lead to IndividualPublicationScreen. They are not // an official user yet so they shouldn't be interacting with the articles. - CreatePublicationRow(publication = publication) { publicationFromCallback, isFollowing -> - if (isFollowing) { - onboardingViewModel.addPublicationToFollowed( - publicationFromCallback.slug - ) - VolumeEvent.logEvent( - EventType.PUBLICATION, VolumeEvent.FOLLOW_PUBLICATION, - NavigationSource.ONBOARDING, - publicationFromCallback.slug - ) - } else { - onboardingViewModel.removePublicationFromFollowed( - publicationFromCallback.slug - ) - VolumeEvent.logEvent( - EventType.PUBLICATION, VolumeEvent.UNFOLLOW_PUBLICATION, - NavigationSource.ONBOARDING, - publicationFromCallback.slug - ) - } + if (!publication.contentTypes.contains("magazines")) { + CreatePublicationRow(publication = publication) { publicationFromCallback, isFollowing -> + if (isFollowing) { + onboardingViewModel.addPublicationToFollowed( + publicationFromCallback.slug + ) + VolumeEvent.logEvent( + EventType.PUBLICATION, + VolumeEvent.FOLLOW_PUBLICATION, + NavigationSource.ONBOARDING, + publicationFromCallback.slug + ) + } else { + onboardingViewModel.removePublicationFromFollowed( + publicationFromCallback.slug + ) + VolumeEvent.logEvent( + EventType.PUBLICATION, + VolumeEvent.UNFOLLOW_PUBLICATION, + NavigationSource.ONBOARDING, + publicationFromCallback.slug + ) + } - proceedEnabled = - onboardingViewModel.setOfPubsFollowed.isNotEmpty() + proceedEnabled = + onboardingViewModel.setOfPubsFollowed.isNotEmpty() + } } } } diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt index 4114ed3..9f3ba02 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt @@ -158,21 +158,22 @@ fun PublicationsScreen( .padding(start = 12.dp, end = 12.dp) ) { morePublicationsState.publications.forEach { publication -> - CreatePublicationRow( - publication = publication, - onPublicationClick - ) { publicationFromCallback, isFollowing -> - if (isFollowing) { - publicationsViewModel.followPublication( - publicationFromCallback.slug - ) - } else { - publicationsViewModel.unfollowPublication( - publicationFromCallback.slug - ) + if (!publication.contentTypes.contains("magazines")) { + CreatePublicationRow( + publication = publication, + onPublicationClick + ) { publicationFromCallback, isFollowing -> + if (isFollowing) { + publicationsViewModel.followPublication( + publicationFromCallback.slug + ) + } else { + publicationsViewModel.unfollowPublication( + publicationFromCallback.slug + ) + } } } - } } } From 0c0ee42cf2f0b3d9b068acdc56d2e69a342d09fc Mon Sep 17 00:00:00 2001 From: benha Date: Thu, 17 Nov 2022 12:25:43 -0500 Subject: [PATCH 15/16] Refactor content type to an enum class --- .../volume/data/models/Publication.kt | 7 +++++- .../data/repositories/ArticleRepository.kt | 25 ++++++++++++++----- .../repositories/PublicationRepository.kt | 17 ++++++++++--- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/volume/data/models/Publication.kt b/app/src/main/java/com/cornellappdev/volume/data/models/Publication.kt index c119935..abd0df7 100644 --- a/app/src/main/java/com/cornellappdev/volume/data/models/Publication.kt +++ b/app/src/main/java/com/cornellappdev/volume/data/models/Publication.kt @@ -12,6 +12,7 @@ package com.cornellappdev.volume.data.models * @property slug * @property shoutouts * @property websiteURL + * @property contentTypes * @property mostRecentArticle * @property socials */ @@ -25,8 +26,12 @@ data class Publication( val slug: String, val shoutouts: Double, val websiteURL: String, - val contentTypes: List, + val contentTypes: List, val numArticles: Double, val mostRecentArticle: Article? = null, val socials: List ) + +enum class ContentType{ + MAGAZINES, ARTICLES +} diff --git a/app/src/main/java/com/cornellappdev/volume/data/repositories/ArticleRepository.kt b/app/src/main/java/com/cornellappdev/volume/data/repositories/ArticleRepository.kt index 7a15c9c..5c8938a 100644 --- a/app/src/main/java/com/cornellappdev/volume/data/repositories/ArticleRepository.kt +++ b/app/src/main/java/com/cornellappdev/volume/data/repositories/ArticleRepository.kt @@ -3,6 +3,7 @@ package com.cornellappdev.volume.data.repositories import com.cornellappdev.volume.* import com.cornellappdev.volume.data.NetworkApi import com.cornellappdev.volume.data.models.Article +import com.cornellappdev.volume.data.models.ContentType import com.cornellappdev.volume.data.models.Publication import com.cornellappdev.volume.data.models.Social import javax.inject.Inject @@ -55,7 +56,9 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, - contentTypes = publication.contentTypes, + contentTypes = publication.contentTypes.map { + ContentType.valueOf(it.uppercase()) + }, websiteURL = publication.websiteURL, numArticles = publication.numArticles, socials = publication.socials @@ -86,7 +89,9 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, - contentTypes = publication.contentTypes, + contentTypes = publication.contentTypes.map { + ContentType.valueOf(it.uppercase()) + }, numArticles = publication.numArticles, websiteURL = publication.websiteURL, socials = publication.socials @@ -116,7 +121,9 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, - contentTypes = publication.contentTypes, + contentTypes = publication.contentTypes.map { + ContentType.valueOf(it.uppercase()) + }, numArticles = publication.numArticles, websiteURL = publication.websiteURL, socials = publication.socials @@ -146,7 +153,9 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, - contentTypes = publication.contentTypes, + contentTypes = publication.contentTypes.map { + ContentType.valueOf(it.uppercase()) + }, numArticles = publication.numArticles, websiteURL = publication.websiteURL, socials = publication.socials @@ -176,7 +185,9 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, - contentTypes = publication.contentTypes, + contentTypes = publication.contentTypes.map { + ContentType.valueOf(it.uppercase()) + }, numArticles = publication.numArticles, websiteURL = publication.websiteURL, socials = publication.socials @@ -206,7 +217,9 @@ class ArticleRepository @Inject constructor(private val networkApi: NetworkApi) rssURL = publication.rssURL, slug = publication.slug, shoutouts = publication.shoutouts, - contentTypes = publication.contentTypes, + contentTypes = publication.contentTypes.map { + ContentType.valueOf(it.uppercase()) + }, websiteURL = publication.websiteURL, numArticles = publication.numArticles, socials = publication.socials diff --git a/app/src/main/java/com/cornellappdev/volume/data/repositories/PublicationRepository.kt b/app/src/main/java/com/cornellappdev/volume/data/repositories/PublicationRepository.kt index 0115bcf..40a8b36 100644 --- a/app/src/main/java/com/cornellappdev/volume/data/repositories/PublicationRepository.kt +++ b/app/src/main/java/com/cornellappdev/volume/data/repositories/PublicationRepository.kt @@ -4,6 +4,7 @@ import com.cornellappdev.volume.AllPublicationsQuery import com.cornellappdev.volume.PublicationBySlugQuery import com.cornellappdev.volume.data.NetworkApi import com.cornellappdev.volume.data.models.Article +import com.cornellappdev.volume.data.models.ContentType import com.cornellappdev.volume.data.models.Publication import com.cornellappdev.volume.data.models.Social import javax.inject.Inject @@ -43,7 +44,9 @@ class PublicationRepository @Inject constructor(private val networkApi: NetworkA slug = publicationData.slug, shoutouts = publicationData.shoutouts, numArticles = publicationData.numArticles, - contentTypes = publicationData.contentTypes, + contentTypes = publicationData.contentTypes.map { + ContentType.valueOf(it.uppercase()) + }, websiteURL = publicationData.websiteURL, mostRecentArticle = publicationData.mostRecentArticle?.nsfw?.let { isNSFW -> Article( @@ -61,7 +64,9 @@ class PublicationRepository @Inject constructor(private val networkApi: NetworkA slug = publicationData.slug, shoutouts = publicationData.shoutouts, numArticles = publicationData.numArticles, - contentTypes = publicationData.contentTypes, + contentTypes = publicationData.contentTypes.map { + ContentType.valueOf(it.uppercase()) + }, websiteURL = publicationData.websiteURL, socials = publicationData.socials .map { Social(it.social, it.url) }), @@ -87,7 +92,9 @@ class PublicationRepository @Inject constructor(private val networkApi: NetworkA rssURL = publicationData.rssURL, slug = publicationData.slug, shoutouts = publicationData.shoutouts, - contentTypes = publicationData.contentTypes, + contentTypes = publicationData.contentTypes.map { + ContentType.valueOf(it.uppercase()) + }, numArticles = publicationData.numArticles, websiteURL = publicationData.websiteURL, mostRecentArticle = publicationData.mostRecentArticle?.nsfw?.let { isNSFW -> @@ -106,7 +113,9 @@ class PublicationRepository @Inject constructor(private val networkApi: NetworkA rssURL = publicationData.rssURL, slug = publicationData.slug, shoutouts = publicationData.shoutouts, - contentTypes = publicationData.contentTypes, + contentTypes = publicationData.contentTypes.map { + ContentType.valueOf(it.uppercase()) + }, websiteURL = publicationData.websiteURL, numArticles = publicationData.numArticles, socials = publicationData.socials From 44a779ecf1f57de57b2cf6c8e95648ec8b97b55f Mon Sep 17 00:00:00 2001 From: benha Date: Thu, 17 Nov 2022 12:29:35 -0500 Subject: [PATCH 16/16] Adjust publication filtering to respect content type refactor --- .../volume/ui/components/onboarding/SecondPage.kt | 3 ++- .../com/cornellappdev/volume/ui/screens/PublicationsScreen.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt b/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt index b21b3bd..4641c19 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/components/onboarding/SecondPage.kt @@ -35,6 +35,7 @@ import com.cornellappdev.volume.R import com.cornellappdev.volume.analytics.EventType import com.cornellappdev.volume.analytics.NavigationSource import com.cornellappdev.volume.analytics.VolumeEvent +import com.cornellappdev.volume.data.models.ContentType import com.cornellappdev.volume.ui.components.general.CreatePublicationRow import com.cornellappdev.volume.ui.states.PublicationsRetrievalState import com.cornellappdev.volume.ui.theme.* @@ -130,7 +131,7 @@ fun SecondPage( ) { publication -> // Clicking on row IN onboarding should not lead to IndividualPublicationScreen. They are not // an official user yet so they shouldn't be interacting with the articles. - if (!publication.contentTypes.contains("magazines")) { + if (!publication.contentTypes.contains(ContentType.MAGAZINES)) { CreatePublicationRow(publication = publication) { publicationFromCallback, isFollowing -> if (isFollowing) { onboardingViewModel.addPublicationToFollowed( diff --git a/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt b/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt index 9f3ba02..652d4df 100644 --- a/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt +++ b/app/src/main/java/com/cornellappdev/volume/ui/screens/PublicationsScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import com.cornellappdev.volume.R +import com.cornellappdev.volume.data.models.ContentType import com.cornellappdev.volume.data.models.Publication import com.cornellappdev.volume.ui.components.general.CreatePublicationColumn import com.cornellappdev.volume.ui.components.general.CreatePublicationRow @@ -158,7 +159,7 @@ fun PublicationsScreen( .padding(start = 12.dp, end = 12.dp) ) { morePublicationsState.publications.forEach { publication -> - if (!publication.contentTypes.contains("magazines")) { + if (!publication.contentTypes.contains(ContentType.MAGAZINES)) { CreatePublicationRow( publication = publication, onPublicationClick