Notify users when news are updated

pull/712/head
TJ Dahunsi 2 years ago
parent 553f55f978
commit 2499c0a0bd

@ -24,6 +24,7 @@ android {
}
dependencies {
implementation(project(":core:common"))
implementation(project(":core:model"))
implementation(libs.kotlinx.coroutines.android)

@ -14,4 +14,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"/>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
</manifest>

@ -16,17 +16,126 @@
package com.google.samples.apps.nowinandroid.core.notifications
import android.Manifest.permission
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.InboxStyle
import androidx.core.app.NotificationManagerCompat
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
private const val NEWS_NOTIFICATION_SUMMARY_ID = 1
private const val NEWS_NOTIFICATION_CHANNEL_ID = ""
private const val NEWS_NOTIFICATION_GROUP = "NEWS_NOTIFICATIONS"
/**
* Implementation of [Notifier] that displays notifications in the system tray.
*/
@Singleton
class AndroidSystemNotifier @Inject constructor() : Notifier {
class AndroidSystemNotifier @Inject constructor(
@ApplicationContext private val context: Context,
) : Notifier {
override fun onNewsAdded(
newsResources: List<NewsResource>,
) = with(context) {
if (ActivityCompat.checkSelfPermission(
this,
permission.POST_NOTIFICATIONS,
) != PackageManager.PERMISSION_GRANTED
) {
return
}
val newsNotifications = newsResources.map { newsResource ->
newsNotification {
setSmallIcon(
com.google.samples.apps.nowinandroid.core.common.R.drawable.ic_nia_notification,
)
.setContentTitle(newsResource.title)
.setContentText(newsResource.content)
.setGroup(NEWS_NOTIFICATION_GROUP)
}
}
val summaryNotification = newsNotification {
val title = getString(
R.string.news_notification_group_summary,
newsNotifications.size,
)
setContentTitle(title)
.setContentText(title)
.setSmallIcon(
com.google.samples.apps.nowinandroid.core.common.R.drawable.ic_nia_notification,
)
// Build summary info into InboxStyle template.
.setStyle(newsInboxStyle(newsResources, title))
.setGroup(NEWS_NOTIFICATION_GROUP)
.setGroupSummary(true)
.build()
}
with(NotificationManagerCompat.from(this)) {
newsNotifications.forEachIndexed { index, notification ->
notify(newsResources[index].id.hashCode(), notification)
}
notify(NEWS_NOTIFICATION_SUMMARY_ID, summaryNotification)
}
}
/**
* Creates an inbox style summary notification for news updates
*/
private fun newsInboxStyle(
newsResources: List<NewsResource>,
title: String,
): InboxStyle = newsResources
// Show at most 5 lines
.take(5)
.fold(InboxStyle()) { inboxStyle, newsResource ->
inboxStyle.addLine(newsResource.title)
}
.setBigContentTitle(title)
.setSummaryText(title)
}
/**
* Creates a notification for configured for news updates
*/
private fun Context.newsNotification(
block: NotificationCompat.Builder.() -> Unit,
): Notification {
ensureNotificationChannel()
return NotificationCompat.Builder(
this,
NEWS_NOTIFICATION_CHANNEL_ID,
)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.apply(block)
.build()
}
/**
* Ensures the a notification channel is is present if applicable
*/
private fun Context.ensureNotificationChannel() {
if (VERSION.SDK_INT < VERSION_CODES.O) return
override fun onNewsAdded(newsResources: List<NewsResource>) {
// TODO, create notification and display to the user
val channel = NotificationChannel(
NEWS_NOTIFICATION_CHANNEL_ID,
getString(R.string.news_notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT,
).apply {
description = getString(R.string.news_notification_channel_description)
}
// Register the channel with the system
NotificationManagerCompat.from(this).createNotificationChannel(channel)
}

@ -15,8 +15,8 @@
limitations under the License.
-->
<resources>
<string name="sync_notification_title">Now in Android</string>
<string name="sync_notification_channel_name">Sync</string>
<string name="sync_notification_channel_description">Background tasks for Now in Android</string>
<string name="news_notification_title">Now in Android</string>
<string name="news_notification_channel_name">News updates</string>
<string name="news_notification_channel_description">The latest updates on what\'s new in Android</string>
<string name="news_notification_group_summary">%1$d news updates</string>
</resources>

@ -29,4 +29,5 @@ android {
dependencies {
implementation(libs.kotlinx.datetime)
implementation(libs.androidx.activity.compose)
implementation(libs.accompanist.permissions)
}

@ -16,6 +16,8 @@
package com.google.samples.apps.nowinandroid.feature.foryou
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.activity.compose.ReportDrawnWhen
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
@ -57,6 +59,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -73,6 +76,9 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.trace
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus.Denied
import com.google.accompanist.permissions.rememberPermissionState
import com.google.samples.apps.nowinandroid.core.designsystem.component.DynamicAsyncImage
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaButton
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton
@ -199,6 +205,7 @@ internal fun ForYouScreen(
}
}
TrackScreenViewEvent(screenName = "ForYou")
requestNotificationsPermission()
}
/**
@ -383,6 +390,21 @@ fun TopicIcon(
)
}
@Composable
@OptIn(ExperimentalPermissionsApi::class)
private fun requestNotificationsPermission() {
if (VERSION.SDK_INT < VERSION_CODES.TIRAMISU) return
val notificationsPermissionState = rememberPermissionState(
android.Manifest.permission.POST_NOTIFICATIONS,
)
LaunchedEffect(notificationsPermissionState) {
val status = notificationsPermissionState.status
if (status is Denied && !status.shouldShowRationale) {
notificationsPermissionState.launchPermissionRequest()
}
}
}
@DevicePreviews
@Composable
fun ForYouScreenPopulatedFeed(

@ -55,6 +55,7 @@ turbine = "0.12.1"
[libraries]
accompanist-systemuicontroller = { group = "com.google.accompanist", name = "accompanist-systemuicontroller", version.ref = "accompanist" }
accompanist-testharness = { group = "com.google.accompanist", name = "accompanist-testharness", version.ref = "accompanist" }
accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist" }
android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" }

Loading…
Cancel
Save