Skip to content

Commit a75d875

Browse files
authored
Merge pull request #296 from alabiaga/main
Add ProgressStyle notification sample
2 parents b78fc7a + 894a7e7 commit a75d875

File tree

20 files changed

+520
-5
lines changed

20 files changed

+520
-5
lines changed

app/build.gradle.kts

Lines changed: 3 additions & 2 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"
3030
minSdk = 21
31-
targetSdk = 35
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: 15 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.LiveUpdateSample
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,20 @@ val SAMPLE_DEMOS by lazy {
993994
tags = listOf("Haptics"),
994995
content = { Wobble() }
995996
),
997+
ComposableSampleDemo(
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+
minSdk = Build.VERSION_CODES.BAKLAVA,
1003+
apiSurface = UserInterfaceLiveUpdatesApiSurface,
1004+
content = {
1005+
MinSdkBox(minSdk = Build.VERSION_CODES.BAKLAVA) {
1006+
//noinspection NewApi
1007+
LiveUpdateSample()
1008+
}
1009+
},
1010+
),
9961011
ActivitySampleDemo(
9971012
id = "picture-in-picture-video-playback",
9981013
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" }

gradle/wrapper/gradle-wrapper.jar

-15.3 KB
Binary file not shown.
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
#Fri Feb 14 20:10:12 KST 2025
21
distributionBase=GRADLE_USER_HOME
32
distributionPath=wrapper/dists
4-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
4+
networkTimeout=10000
5+
validateDistributionUrl=true
56
zipStoreBase=GRADLE_USER_HOME
67
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: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
</manifest>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.platform.ui.live_updates
18+
19+
import android.annotation.SuppressLint
20+
import android.app.NotificationManager
21+
import android.content.Context
22+
import android.os.Build
23+
import androidx.annotation.RequiresApi
24+
import androidx.compose.foundation.layout.Box
25+
import androidx.compose.foundation.layout.Column
26+
import androidx.compose.foundation.layout.Spacer
27+
import androidx.compose.foundation.layout.fillMaxSize
28+
import androidx.compose.foundation.layout.fillMaxWidth
29+
import androidx.compose.foundation.layout.height
30+
import androidx.compose.foundation.layout.padding
31+
import androidx.compose.material3.Button
32+
import androidx.compose.material3.Card
33+
import androidx.compose.material3.Scaffold
34+
import androidx.compose.material3.SnackbarHost
35+
import androidx.compose.material3.SnackbarHostState
36+
import androidx.compose.material3.Text
37+
import androidx.compose.runtime.Composable
38+
import androidx.compose.runtime.remember
39+
import androidx.compose.runtime.rememberCoroutineScope
40+
import androidx.compose.ui.Alignment
41+
import androidx.compose.ui.Modifier
42+
import androidx.compose.ui.platform.LocalContext
43+
import androidx.compose.ui.res.stringResource
44+
import androidx.compose.ui.unit.dp
45+
import com.google.accompanist.permissions.ExperimentalPermissionsApi
46+
import com.google.accompanist.permissions.isGranted
47+
import com.google.accompanist.permissions.rememberPermissionState
48+
import com.google.accompanist.permissions.shouldShowRationale
49+
import kotlinx.coroutines.launch
50+
51+
@RequiresApi(Build.VERSION_CODES.O)
52+
@Composable
53+
fun LiveUpdateSample() {
54+
val notificationManager =
55+
LocalContext.current.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
56+
SnackbarNotificationManager.initialize(LocalContext.current.applicationContext, notificationManager)
57+
val scope = rememberCoroutineScope()
58+
val snackbarHostState = remember { SnackbarHostState() }
59+
Scaffold(
60+
snackbarHost = {
61+
SnackbarHost(hostState = snackbarHostState)
62+
},
63+
) { contentPadding ->
64+
Column(
65+
modifier = Modifier
66+
.fillMaxSize()
67+
.padding(contentPadding),
68+
) {
69+
Text(stringResource( R.string.live_update_summary_text))
70+
Spacer(modifier = Modifier.height(4.dp))
71+
NotificationPermission()
72+
Button(onClick = {
73+
onCheckout()
74+
scope.launch {
75+
snackbarHostState.showSnackbar("Order placed")
76+
}
77+
}) {
78+
Text("Checkout")
79+
}
80+
}
81+
}
82+
}
83+
84+
fun onCheckout() {
85+
SnackbarNotificationManager.start()
86+
}
87+
88+
@OptIn(ExperimentalPermissionsApi::class)
89+
@Composable
90+
fun NotificationPermission() {
91+
@SuppressLint("InlinedApi") // Granted at install time on API <33.
92+
val notificationPermissionState = rememberPermissionState(
93+
android.Manifest.permission.POST_NOTIFICATIONS,
94+
)
95+
if (!notificationPermissionState.status.isGranted) {
96+
NotificationPermissionCard(
97+
shouldShowRationale = notificationPermissionState.status.shouldShowRationale,
98+
onGrantClick = {
99+
notificationPermissionState.launchPermissionRequest()
100+
},
101+
modifier = Modifier
102+
.fillMaxWidth()
103+
)
104+
}
105+
}
106+
107+
@Composable
108+
private fun NotificationPermissionCard(
109+
shouldShowRationale: Boolean,
110+
onGrantClick: () -> Unit,
111+
modifier: Modifier = Modifier,
112+
) {
113+
Card(
114+
modifier = modifier,
115+
) {
116+
Text(
117+
text = stringResource(R.string.permission_message),
118+
modifier = Modifier.padding(16.dp),
119+
)
120+
if (shouldShowRationale) {
121+
Text(
122+
text = stringResource(R.string.permission_rationale),
123+
modifier = Modifier.padding(horizontal = 10.dp),
124+
)
125+
}
126+
Box(
127+
modifier = Modifier
128+
.fillMaxWidth()
129+
.padding(10.dp),
130+
contentAlignment = Alignment.BottomEnd,
131+
) {
132+
Button(onClick = onGrantClick) {
133+
Text(text = stringResource(R.string.permission_grant))
134+
}
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)