Skip to content

Commit 8abb7d2

Browse files
authored
[JetChat] Add glance widget for JetChat App #1424 (#1425)
Added an Unreads widget for the JetChat app using Glance. Also has a option to add widget to home screen on the app. <img src="https://github.com/android/compose-samples/assets/9254310/6ba94b27-724f-4ff3-90ca-1a2447cb66e7" width="300"> [Glance Widget Recording.webm](https://github.com/android/compose-samples/assets/9254310/c8b80701-d2a5-4eee-b95a-cf38842945db) Fixes #1424
2 parents ed1677e + 5038653 commit 8abb7d2

File tree

17 files changed

+318
-9
lines changed

17 files changed

+318
-9
lines changed

Jetchat/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ This sample showcases:
2525

2626
<img src="screenshots/screenshots.png"/>
2727

28+
<img src="screenshots/widget.png" width="300"/>
29+
30+
<img src="screenshots/widget_discoverability.png" width="300"/>
31+
2832
### Status: 🚧 In progress
2933

3034
Jetchat is still in under development, and some features are not yet implemented.

Jetchat/app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ dependencies {
8787
implementation(composeBom)
8888
androidTestImplementation(composeBom)
8989

90+
implementation(libs.androidx.glance.appwidget)
91+
implementation(libs.androidx.glance.material3)
9092
implementation(libs.kotlin.stdlib)
9193
implementation(libs.kotlinx.coroutines.android)
9294

Jetchat/app/src/main/AndroidManifest.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@
3535
<category android:name="android.intent.category.LAUNCHER" />
3636
</intent-filter>
3737
</activity>
38-
38+
<receiver android:name=".widget.WidgetReceiver"
39+
android:exported="true">
40+
<intent-filter>
41+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
42+
</intent-filter>
43+
<meta-data
44+
android:name="android.appwidget.provider"
45+
android:resource="@xml/widget_unread_messages_info" />
46+
</receiver>
3947
</application>
4048
</manifest>

Jetchat/app/src/main/java/com/example/compose/jetchat/components/JetchatDrawer.kt

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@
1616

1717
package com.example.compose.jetchat.components
1818

19+
import android.appwidget.AppWidgetManager
20+
import android.content.ComponentName
21+
import android.content.Context
22+
import android.os.Build
23+
import androidx.annotation.ChecksSdkIntAtLeast
1924
import androidx.annotation.DrawableRes
25+
import androidx.annotation.RequiresApi
2026
import androidx.compose.foundation.Image
2127
import androidx.compose.foundation.background
2228
import androidx.compose.foundation.clickable
@@ -33,7 +39,7 @@ import androidx.compose.foundation.layout.size
3339
import androidx.compose.foundation.layout.statusBars
3440
import androidx.compose.foundation.layout.windowInsetsTopHeight
3541
import androidx.compose.foundation.shape.CircleShape
36-
import androidx.compose.material3.Divider
42+
import androidx.compose.material3.HorizontalDivider
3743
import androidx.compose.material3.Icon
3844
import androidx.compose.material3.MaterialTheme
3945
import androidx.compose.material3.Surface
@@ -44,13 +50,16 @@ import androidx.compose.ui.Alignment.Companion.CenterVertically
4450
import androidx.compose.ui.Modifier
4551
import androidx.compose.ui.draw.clip
4652
import androidx.compose.ui.layout.ContentScale
53+
import androidx.compose.ui.platform.LocalContext
4754
import androidx.compose.ui.res.painterResource
55+
import androidx.compose.ui.res.stringResource
4856
import androidx.compose.ui.tooling.preview.Preview
4957
import androidx.compose.ui.unit.dp
5058
import com.example.compose.jetchat.R
5159
import com.example.compose.jetchat.data.colleagueProfile
5260
import com.example.compose.jetchat.data.meProfile
5361
import com.example.compose.jetchat.theme.JetchatTheme
62+
import com.example.compose.jetchat.widget.WidgetReceiver
5463

5564
@Composable
5665
fun JetchatDrawerContent(
@@ -73,6 +82,11 @@ fun JetchatDrawerContent(
7382
ProfileItem("Taylor Brooks", colleagueProfile.photo) {
7483
onProfileClicked(colleagueProfile.userId)
7584
}
85+
if (widgetAddingIsSupported(LocalContext.current)) {
86+
DividerItem(modifier = Modifier.padding(horizontal = 28.dp))
87+
DrawerItemHeader("Settings")
88+
WidgetDiscoverability()
89+
}
7690
}
7791
}
7892

@@ -90,6 +104,7 @@ private fun DrawerHeader() {
90104
)
91105
}
92106
}
107+
93108
@Composable
94109
private fun DrawerItemHeader(text: String) {
95110
Box(
@@ -182,7 +197,7 @@ private fun ProfileItem(text: String, @DrawableRes profilePic: Int?, onProfileCl
182197

183198
@Composable
184199
fun DividerItem(modifier: Modifier = Modifier) {
185-
Divider(
200+
HorizontalDivider(
186201
modifier = modifier,
187202
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
188203
)
@@ -199,6 +214,7 @@ fun DrawerPreview() {
199214
}
200215
}
201216
}
217+
202218
@Composable
203219
@Preview
204220
fun DrawerPreviewDark() {
@@ -210,3 +226,42 @@ fun DrawerPreviewDark() {
210226
}
211227
}
212228
}
229+
230+
@RequiresApi(Build.VERSION_CODES.O)
231+
@Composable
232+
private fun WidgetDiscoverability() {
233+
val context = LocalContext.current
234+
Row(
235+
modifier = Modifier
236+
.height(56.dp)
237+
.fillMaxWidth()
238+
.padding(horizontal = 12.dp)
239+
.clip(CircleShape)
240+
.clickable(onClick = {
241+
addWidgetToHomeScreen(context)
242+
}),
243+
verticalAlignment = CenterVertically
244+
) {
245+
Text(
246+
stringResource(id = R.string.add_widget_to_home_page),
247+
style = MaterialTheme.typography.bodyMedium,
248+
color = MaterialTheme.colorScheme.onSurface,
249+
modifier = Modifier.padding(start = 12.dp)
250+
)
251+
}
252+
}
253+
254+
@RequiresApi(Build.VERSION_CODES.O)
255+
private fun addWidgetToHomeScreen(context: Context) {
256+
val appWidgetManager = AppWidgetManager.getInstance(context)
257+
val myProvider = ComponentName(context, WidgetReceiver::class.java)
258+
if (widgetAddingIsSupported(context)) {
259+
appWidgetManager.requestPinAppWidget(myProvider, null, null)
260+
}
261+
}
262+
263+
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
264+
private fun widgetAddingIsSupported(context: Context): Boolean {
265+
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
266+
AppWidgetManager.getInstance(context).isRequestPinAppWidgetSupported
267+
}

Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/ConversationUiState.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ data class Message(
3939
val content: String,
4040
val timestamp: String,
4141
val image: Int? = null,
42-
val authorImage: Int = if (author == "me") R.drawable.ali else R.drawable.someone_else
42+
val authorImage: Int = if (author == "me") R.drawable.ali else R.drawable.someone_else,
4343
)

Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import com.example.compose.jetchat.data.EMOJIS.EMOJI_PINK_HEART
2626
import com.example.compose.jetchat.data.EMOJIS.EMOJI_POINTS
2727
import com.example.compose.jetchat.profile.ProfileScreenState
2828

29-
private val initialMessages = listOf(
29+
val initialMessages = listOf(
3030
Message(
3131
"me",
3232
"Check it out!",
@@ -62,9 +62,26 @@ private val initialMessages = listOf(
6262
"loading but haven’t found any good ones $EMOJI_MELTING $EMOJI_CLOUDS. " +
6363
"What’s the recommended way to load async data and emit composable widgets?",
6464
"8:03 PM"
65-
)
65+
),
66+
Message(
67+
"Shangeeth Sivan",
68+
"Does anyone know about Glance Widgets its the new way to build widgets in Android!",
69+
"8:08 PM"
70+
),
71+
Message(
72+
"Taylor Brooks",
73+
"Wow! I never knew about Glance Widgets when was this added to the android ecosystem",
74+
"8:10 PM"
75+
),
76+
Message(
77+
"John Glenn",
78+
"Yeah its seems to be pretty new!",
79+
"8:12 PM"
80+
),
6681
)
6782

83+
val unreadMessages = initialMessages.filter { it.author != "me" }
84+
6885
val exampleUiState = ConversationUiState(
6986
initialMessages = initialMessages,
7087
channelName = "#composers",

Jetchat/app/src/main/java/com/example/compose/jetchat/theme/Themes.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import androidx.compose.runtime.Composable
2828
import androidx.compose.ui.graphics.Color
2929
import androidx.compose.ui.platform.LocalContext
3030

31-
private val JetchatDarkColorScheme = darkColorScheme(
31+
val JetchatDarkColorScheme = darkColorScheme(
3232
primary = Blue80,
3333
onPrimary = Blue20,
3434
primaryContainer = Blue30,
@@ -57,7 +57,7 @@ private val JetchatDarkColorScheme = darkColorScheme(
5757
outline = BlueGrey60
5858
)
5959

60-
private val JetchatLightColorScheme = lightColorScheme(
60+
val JetchatLightColorScheme = lightColorScheme(
6161
primary = Blue40,
6262
onPrimary = Color.White,
6363
primaryContainer = Blue90,
@@ -83,7 +83,7 @@ private val JetchatLightColorScheme = lightColorScheme(
8383
inverseOnSurface = Grey95,
8484
surfaceVariant = BlueGrey90,
8585
onSurfaceVariant = BlueGrey30,
86-
outline = BlueGrey50
86+
outline = BlueGrey50,
8787
)
8888

8989
@SuppressLint("NewApi")
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2024 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.compose.jetchat.widget
18+
19+
import android.content.Context
20+
import androidx.glance.GlanceId
21+
import androidx.glance.GlanceTheme
22+
import androidx.glance.appwidget.GlanceAppWidget
23+
import androidx.glance.appwidget.provideContent
24+
import com.example.compose.jetchat.data.unreadMessages
25+
import com.example.compose.jetchat.widget.composables.MessagesWidget
26+
27+
class JetChatWidget : GlanceAppWidget() {
28+
29+
override suspend fun provideGlance(context: Context, id: GlanceId) {
30+
provideContent {
31+
GlanceTheme {
32+
MessagesWidget(unreadMessages.toList())
33+
}
34+
}
35+
}
36+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2024 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.compose.jetchat.widget
18+
19+
import androidx.glance.appwidget.GlanceAppWidget
20+
import androidx.glance.appwidget.GlanceAppWidgetReceiver
21+
22+
class WidgetReceiver : GlanceAppWidgetReceiver() {
23+
24+
override val glanceAppWidget: GlanceAppWidget
25+
get() = JetChatWidget()
26+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2024 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.compose.jetchat.widget.composables
18+
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.ui.tooling.preview.Preview
21+
import androidx.compose.ui.unit.dp
22+
import androidx.glance.GlanceModifier
23+
import androidx.glance.ImageProvider
24+
import androidx.glance.LocalContext
25+
import androidx.glance.action.actionStartActivity
26+
import androidx.glance.action.clickable
27+
import androidx.glance.appwidget.components.Scaffold
28+
import androidx.glance.appwidget.components.TitleBar
29+
import androidx.glance.appwidget.lazy.LazyColumn
30+
import androidx.glance.layout.Column
31+
import androidx.glance.layout.Spacer
32+
import androidx.glance.layout.fillMaxWidth
33+
import androidx.glance.layout.height
34+
import androidx.glance.text.Text
35+
import com.example.compose.jetchat.NavActivity
36+
import com.example.compose.jetchat.R
37+
import com.example.compose.jetchat.conversation.Message
38+
import com.example.compose.jetchat.widget.theme.JetChatGlanceTextStyles
39+
import com.example.compose.jetchat.widget.theme.JetchatGlanceColorScheme
40+
41+
@Composable
42+
fun MessagesWidget(messages: List<Message>) {
43+
Scaffold(titleBar = {
44+
TitleBar(
45+
startIcon = ImageProvider(R.drawable.ic_jetchat),
46+
iconColor = null,
47+
title = LocalContext.current.getString(R.string.messages_widget_title),
48+
)
49+
}, backgroundColor = JetchatGlanceColorScheme.colors.background) {
50+
LazyColumn(modifier = GlanceModifier.fillMaxWidth()) {
51+
messages.forEach {
52+
item {
53+
Column(modifier = GlanceModifier.fillMaxWidth()) {
54+
MessageItem(it)
55+
Spacer(modifier = GlanceModifier.height(10.dp))
56+
}
57+
}
58+
}
59+
}
60+
}
61+
}
62+
63+
@Composable
64+
fun MessageItem(message: Message) {
65+
Column(modifier = GlanceModifier.clickable(actionStartActivity<NavActivity>()).fillMaxWidth()) {
66+
Text(
67+
text = message.author,
68+
style = JetChatGlanceTextStyles.titleMedium
69+
)
70+
Text(
71+
text = message.content,
72+
style = JetChatGlanceTextStyles.bodyMedium,
73+
)
74+
}
75+
}
76+
77+
@Preview
78+
@Composable
79+
fun MessageItemPreview() {
80+
MessageItem(Message("John", "This is a preview of the message Item", "8:02PM"))
81+
}
82+
83+
@Preview
84+
@Composable
85+
fun WidgetPreview() {
86+
MessagesWidget(listOf(Message("John", "This is a preview of the message Item", "8:02PM")))
87+
}

0 commit comments

Comments
 (0)