Skip to content

Commit 8933a6d

Browse files
committed
Add ProgressStyle notification sample
1 parent b78fc7a commit 8933a6d

File tree

20 files changed

+671
-5
lines changed

20 files changed

+671
-5
lines changed

app/build.gradle.kts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ plugins {
2323

2424
android {
2525
namespace = "com.example.platform"
26-
compileSdk = 35
26+
compileSdk = 36
2727

2828
defaultConfig {
2929
applicationId = "com.example.platform"
30-
minSdk = 21
31-
targetSdk = 35
30+
minSdk = 26
31+
targetSdk = 36
3232
versionCode = 1
3333
versionName = "1.0"
3434

@@ -92,6 +92,7 @@ dependencies {
9292
implementation(project(":samples:user-interface:constraintlayout"))
9393
implementation(project(":samples:user-interface:draganddrop"))
9494
implementation(project(":samples:user-interface:haptics"))
95+
implementation(project(":samples:user-interface:live-updates"))
9596
implementation(project(":samples:user-interface:picture-in-picture"))
9697
implementation(project(":samples:user-interface:predictiveback"))
9798
implementation(project(":samples:user-interface:quicksettings"))

app/src/main/java/com/example/platform/app/ApiSurface.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ val UserInterfaceHapticsApiSurface = ApiSurface(
141141
null,
142142
)
143143

144+
val UserInterfaceLiveUpdatesApiSurface = ApiSurface(
145+
"live-updates",
146+
"User Interface - Live Updates",
147+
null,
148+
)
149+
144150
val UserInterfacePictureInPictureApiSurface = ApiSurface(
145151
"user-interface-picture-in-picture",
146152
"User Interface - Picture In Picture",
@@ -204,6 +210,7 @@ val API_SURFACES = listOf(
204210
UserInterfaceConstraintLayoutApiSurface,
205211
UserInterfaceDragAndDropApiSurface,
206212
UserInterfaceHapticsApiSurface,
213+
UserInterfaceLiveUpdatesApiSurface,
207214
UserInterfacePictureInPictureApiSurface,
208215
UserInterfacePredictiveBackApiSurface,
209216
UserInterfaceQuickSettingsApiSurface,

app/src/main/java/com/example/platform/app/SampleDemo.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ import com.example.platform.ui.haptics.Resist
111111
import com.example.platform.ui.haptics.Wobble
112112
import com.example.platform.ui.insets.ImmersiveMode
113113
import com.example.platform.ui.insets.WindowInsetsAnimationActivity
114+
import com.example.platform.ui.live_updates.LiveUpdateActivity
114115
import com.example.platform.ui.predictiveback.PBHostingActivity
115116
import com.example.platform.ui.quicksettings.QuickSettings
116117
import com.example.platform.ui.share.receiver.ShareReceiverActivity
@@ -993,6 +994,14 @@ val SAMPLE_DEMOS by lazy {
993994
tags = listOf("Haptics"),
994995
content = { Wobble() }
995996
),
997+
ActivitySampleDemo(
998+
id = "live-updates",
999+
name = "Live Updates - ProgressStyle implementation",
1000+
description = "Usage of ProgressStyle with Live update treatment",
1001+
documentation = "https://developer.android.com/about/versions/16/features/progress-centric-notifications",
1002+
apiSurface = UserInterfaceLiveUpdatesApiSurface,
1003+
content = LiveUpdateActivity::class.java
1004+
),
9961005
ActivitySampleDemo(
9971006
id = "picture-in-picture-video-playback",
9981007
name = "Picture in Picture (PiP) - Video playback",

gradle/libs.versions.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# limitations under the License.
1515
#
1616
[versions]
17-
agp = "8.8.1"
17+
agp = "8.9.1"
1818
fragmentCompose = "1.8.6"
1919
kotlin = "2.1.10"
2020
coreKtx = "1.15.0"
@@ -51,6 +51,7 @@ androidxTestExtTruth = "1.5.0"
5151
androidxTestRules = "1.5.0"
5252
androidxTestRunner = "1.5.2"
5353
androidxUiAutomator = "2.2.0"
54+
material3Android = "1.3.2"
5455
media3 = "1.5.0"
5556
constraintlayout = "2.1.4"
5657
glide-compose = "1.0.0-beta01"
@@ -162,6 +163,7 @@ androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4"
162163
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
163164
androidx-draganddrop = "androidx.draganddrop:draganddrop:1.0.0"
164165
androidx-dynamicanimation = "androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03"
166+
androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" }
165167
androidx-media3-common = { module = "androidx.media3:media3-common", version.ref = "media3" }
166168
androidx-media3-effect = { module = "androidx.media3:media3-effect", version.ref = "media3" }
167169
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
@@ -185,6 +187,7 @@ android-application = { id = "com.android.application", version.ref = "agp" }
185187
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
186188
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
187189
android-library = { id = "com.android.library", version.ref = "agp" }
190+
compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
188191

189192
affectedmoduledetector = { id = "com.dropbox.affectedmoduledetector", version = "0.2.0" }
190193
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version = "0.7.0" }
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#Fri Feb 14 20:10:12 KST 2025
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
4-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
55
zipStoreBase=GRADLE_USER_HOME
66
zipStorePath=wrapper/dists
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
plugins {
2+
alias(libs.plugins.android.library)
3+
alias(libs.plugins.kotlin.android)
4+
alias(libs.plugins.compose)
5+
}
6+
7+
android {
8+
namespace = "com.example.platform.ui.live_updates"
9+
compileSdk = 36
10+
11+
defaultConfig {
12+
minSdk = 21
13+
targetSdk = 36
14+
}
15+
kotlinOptions {
16+
jvmTarget = "1.8"
17+
}
18+
19+
buildFeatures {
20+
viewBinding = true
21+
}
22+
}
23+
24+
dependencies {
25+
implementation(libs.androidx.core.ktx)
26+
implementation(libs.androidx.appcompat)
27+
implementation(libs.androidx.activity.compose)
28+
implementation(libs.material)
29+
implementation(libs.accompanist.permissions)
30+
implementation(libs.androidx.constraintlayout)
31+
implementation(libs.androidx.material3.android)
32+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright 2025 The Android Open Source Project
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ https://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
18+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
19+
20+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
21+
22+
<application>
23+
<activity
24+
android:name=".LiveUpdateActivity"
25+
android:exported="true">
26+
</activity>
27+
</application>
28+
29+
</manifest>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package com.example.platform.ui.live_updates
2+
3+
import android.annotation.SuppressLint
4+
import android.app.NotificationManager
5+
import android.content.Context
6+
import android.os.Bundle
7+
import androidx.activity.enableEdgeToEdge
8+
import androidx.activity.ComponentActivity
9+
import androidx.activity.compose.setContent
10+
import androidx.compose.foundation.layout.Box
11+
import androidx.compose.foundation.layout.Column
12+
import androidx.compose.foundation.layout.Spacer
13+
import androidx.compose.foundation.layout.fillMaxSize
14+
import androidx.compose.foundation.layout.fillMaxWidth
15+
import androidx.compose.foundation.layout.height
16+
import androidx.compose.foundation.layout.padding
17+
import androidx.compose.material3.Button
18+
import androidx.compose.material3.Card
19+
import androidx.compose.material3.Scaffold
20+
import androidx.compose.material3.SnackbarHost
21+
import androidx.compose.material3.SnackbarHostState
22+
import androidx.compose.material3.Text
23+
import androidx.compose.runtime.Composable
24+
import androidx.compose.runtime.remember
25+
import androidx.compose.runtime.rememberCoroutineScope
26+
import androidx.compose.ui.Alignment
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.res.stringResource
29+
import androidx.compose.ui.unit.dp
30+
import com.google.accompanist.permissions.ExperimentalPermissionsApi
31+
import com.google.accompanist.permissions.isGranted
32+
import com.google.accompanist.permissions.rememberPermissionState
33+
import com.google.accompanist.permissions.shouldShowRationale
34+
import kotlinx.coroutines.launch
35+
36+
class LiveUpdateActivity : ComponentActivity() {
37+
override fun onCreate(savedInstanceState: Bundle?) {
38+
super.onCreate(savedInstanceState)
39+
enableEdgeToEdge()
40+
val notificationManager =
41+
this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
42+
SnackbarNotificationManager.initialize(this, notificationManager)
43+
setContent {
44+
Initialize()
45+
}
46+
}
47+
}
48+
49+
@Composable
50+
fun Initialize() {
51+
val scope = rememberCoroutineScope()
52+
val snackbarHostState = remember { SnackbarHostState() }
53+
Scaffold(
54+
snackbarHost = {
55+
SnackbarHost(hostState = snackbarHostState)
56+
},
57+
) { contentPadding ->
58+
Column(
59+
modifier = Modifier
60+
.fillMaxSize()
61+
.padding(10.dp, 100.dp),
62+
) {
63+
Text(stringResource( R.string.live_update_summary_text))
64+
Spacer(modifier = Modifier.height(4.dp))
65+
NotificationPermission()
66+
Button(onClick = {
67+
onCheckout()
68+
scope.launch {
69+
snackbarHostState.showSnackbar("Order placed")
70+
}
71+
}) {
72+
Text("Checkout")
73+
}
74+
}
75+
}
76+
}
77+
78+
fun onCheckout() {
79+
SnackbarNotificationManager.start()
80+
}
81+
82+
@OptIn(ExperimentalPermissionsApi::class)
83+
@Composable
84+
fun NotificationPermission() {
85+
@SuppressLint("InlinedApi") // Granted at install time on API <33.
86+
val notificationPermissionState = rememberPermissionState(
87+
android.Manifest.permission.POST_NOTIFICATIONS,
88+
)
89+
if (!notificationPermissionState.status.isGranted) {
90+
NotificationPermissionCard(
91+
shouldShowRationale = notificationPermissionState.status.shouldShowRationale,
92+
onGrantClick = {
93+
notificationPermissionState.launchPermissionRequest()
94+
},
95+
modifier = Modifier
96+
.fillMaxWidth()
97+
)
98+
}
99+
}
100+
101+
@Composable
102+
private fun NotificationPermissionCard(
103+
shouldShowRationale: Boolean,
104+
onGrantClick: () -> Unit,
105+
modifier: Modifier = Modifier,
106+
) {
107+
Card(
108+
modifier = modifier,
109+
) {
110+
Text(
111+
text = stringResource(R.string.permission_message),
112+
modifier = Modifier.padding(16.dp),
113+
)
114+
if (shouldShowRationale) {
115+
Text(
116+
text = stringResource(R.string.permission_rationale),
117+
modifier = Modifier.padding(horizontal = 10.dp),
118+
)
119+
}
120+
Box(
121+
modifier = Modifier
122+
.fillMaxWidth()
123+
.padding(10.dp),
124+
contentAlignment = Alignment.BottomEnd,
125+
) {
126+
Button(onClick = onGrantClick) {
127+
Text(text = stringResource(R.string.permission_grant))
128+
}
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)