Skip to content

Commit

Permalink
[JetChat] Add glance widget for JetChat App #1424 (#1425)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mlykotom authored Jan 10, 2025
2 parents ed1677e + 5038653 commit 8abb7d2
Show file tree
Hide file tree
Showing 17 changed files with 318 additions and 9 deletions.
4 changes: 4 additions & 0 deletions Jetchat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ This sample showcases:

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

<img src="screenshots/widget.png" width="300"/>

<img src="screenshots/widget_discoverability.png" width="300"/>

### Status: 🚧 In progress

Jetchat is still in under development, and some features are not yet implemented.
Expand Down
2 changes: 2 additions & 0 deletions Jetchat/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ dependencies {
implementation(composeBom)
androidTestImplementation(composeBom)

implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.glance.material3)
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.android)

Expand Down
10 changes: 9 additions & 1 deletion Jetchat/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<receiver android:name=".widget.WidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_unread_messages_info" />
</receiver>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@

package com.example.compose.jetchat.components

import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.os.Build
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
Expand All @@ -33,7 +39,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsTopHeight
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Divider
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
Expand All @@ -44,13 +50,16 @@ import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.compose.jetchat.R
import com.example.compose.jetchat.data.colleagueProfile
import com.example.compose.jetchat.data.meProfile
import com.example.compose.jetchat.theme.JetchatTheme
import com.example.compose.jetchat.widget.WidgetReceiver

@Composable
fun JetchatDrawerContent(
Expand All @@ -73,6 +82,11 @@ fun JetchatDrawerContent(
ProfileItem("Taylor Brooks", colleagueProfile.photo) {
onProfileClicked(colleagueProfile.userId)
}
if (widgetAddingIsSupported(LocalContext.current)) {
DividerItem(modifier = Modifier.padding(horizontal = 28.dp))
DrawerItemHeader("Settings")
WidgetDiscoverability()
}
}
}

Expand All @@ -90,6 +104,7 @@ private fun DrawerHeader() {
)
}
}

@Composable
private fun DrawerItemHeader(text: String) {
Box(
Expand Down Expand Up @@ -182,7 +197,7 @@ private fun ProfileItem(text: String, @DrawableRes profilePic: Int?, onProfileCl

@Composable
fun DividerItem(modifier: Modifier = Modifier) {
Divider(
HorizontalDivider(
modifier = modifier,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
)
Expand All @@ -199,6 +214,7 @@ fun DrawerPreview() {
}
}
}

@Composable
@Preview
fun DrawerPreviewDark() {
Expand All @@ -210,3 +226,42 @@ fun DrawerPreviewDark() {
}
}
}

@RequiresApi(Build.VERSION_CODES.O)
@Composable
private fun WidgetDiscoverability() {
val context = LocalContext.current
Row(
modifier = Modifier
.height(56.dp)
.fillMaxWidth()
.padding(horizontal = 12.dp)
.clip(CircleShape)
.clickable(onClick = {
addWidgetToHomeScreen(context)
}),
verticalAlignment = CenterVertically
) {
Text(
stringResource(id = R.string.add_widget_to_home_page),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(start = 12.dp)
)
}
}

@RequiresApi(Build.VERSION_CODES.O)
private fun addWidgetToHomeScreen(context: Context) {
val appWidgetManager = AppWidgetManager.getInstance(context)
val myProvider = ComponentName(context, WidgetReceiver::class.java)
if (widgetAddingIsSupported(context)) {
appWidgetManager.requestPinAppWidget(myProvider, null, null)
}
}

@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
private fun widgetAddingIsSupported(context: Context): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
AppWidgetManager.getInstance(context).isRequestPinAppWidgetSupported
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ data class Message(
val content: String,
val timestamp: String,
val image: Int? = null,
val authorImage: Int = if (author == "me") R.drawable.ali else R.drawable.someone_else
val authorImage: Int = if (author == "me") R.drawable.ali else R.drawable.someone_else,
)
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import com.example.compose.jetchat.data.EMOJIS.EMOJI_PINK_HEART
import com.example.compose.jetchat.data.EMOJIS.EMOJI_POINTS
import com.example.compose.jetchat.profile.ProfileScreenState

private val initialMessages = listOf(
val initialMessages = listOf(
Message(
"me",
"Check it out!",
Expand Down Expand Up @@ -62,9 +62,26 @@ private val initialMessages = listOf(
"loading but haven’t found any good ones $EMOJI_MELTING $EMOJI_CLOUDS. " +
"What’s the recommended way to load async data and emit composable widgets?",
"8:03 PM"
)
),
Message(
"Shangeeth Sivan",
"Does anyone know about Glance Widgets its the new way to build widgets in Android!",
"8:08 PM"
),
Message(
"Taylor Brooks",
"Wow! I never knew about Glance Widgets when was this added to the android ecosystem",
"8:10 PM"
),
Message(
"John Glenn",
"Yeah its seems to be pretty new!",
"8:12 PM"
),
)

val unreadMessages = initialMessages.filter { it.author != "me" }

val exampleUiState = ConversationUiState(
initialMessages = initialMessages,
channelName = "#composers",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext

private val JetchatDarkColorScheme = darkColorScheme(
val JetchatDarkColorScheme = darkColorScheme(
primary = Blue80,
onPrimary = Blue20,
primaryContainer = Blue30,
Expand Down Expand Up @@ -57,7 +57,7 @@ private val JetchatDarkColorScheme = darkColorScheme(
outline = BlueGrey60
)

private val JetchatLightColorScheme = lightColorScheme(
val JetchatLightColorScheme = lightColorScheme(
primary = Blue40,
onPrimary = Color.White,
primaryContainer = Blue90,
Expand All @@ -83,7 +83,7 @@ private val JetchatLightColorScheme = lightColorScheme(
inverseOnSurface = Grey95,
surfaceVariant = BlueGrey90,
onSurfaceVariant = BlueGrey30,
outline = BlueGrey50
outline = BlueGrey50,
)

@SuppressLint("NewApi")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.compose.jetchat.widget

import android.content.Context
import androidx.glance.GlanceId
import androidx.glance.GlanceTheme
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent
import com.example.compose.jetchat.data.unreadMessages
import com.example.compose.jetchat.widget.composables.MessagesWidget

class JetChatWidget : GlanceAppWidget() {

override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
GlanceTheme {
MessagesWidget(unreadMessages.toList())
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.compose.jetchat.widget

import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver

class WidgetReceiver : GlanceAppWidgetReceiver() {

override val glanceAppWidget: GlanceAppWidget
get() = JetChatWidget()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.compose.jetchat.widget.composables

import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.glance.GlanceModifier
import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.action.actionStartActivity
import androidx.glance.action.clickable
import androidx.glance.appwidget.components.Scaffold
import androidx.glance.appwidget.components.TitleBar
import androidx.glance.appwidget.lazy.LazyColumn
import androidx.glance.layout.Column
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.height
import androidx.glance.text.Text
import com.example.compose.jetchat.NavActivity
import com.example.compose.jetchat.R
import com.example.compose.jetchat.conversation.Message
import com.example.compose.jetchat.widget.theme.JetChatGlanceTextStyles
import com.example.compose.jetchat.widget.theme.JetchatGlanceColorScheme

@Composable
fun MessagesWidget(messages: List<Message>) {
Scaffold(titleBar = {
TitleBar(
startIcon = ImageProvider(R.drawable.ic_jetchat),
iconColor = null,
title = LocalContext.current.getString(R.string.messages_widget_title),
)
}, backgroundColor = JetchatGlanceColorScheme.colors.background) {
LazyColumn(modifier = GlanceModifier.fillMaxWidth()) {
messages.forEach {
item {
Column(modifier = GlanceModifier.fillMaxWidth()) {
MessageItem(it)
Spacer(modifier = GlanceModifier.height(10.dp))
}
}
}
}
}
}

@Composable
fun MessageItem(message: Message) {
Column(modifier = GlanceModifier.clickable(actionStartActivity<NavActivity>()).fillMaxWidth()) {
Text(
text = message.author,
style = JetChatGlanceTextStyles.titleMedium
)
Text(
text = message.content,
style = JetChatGlanceTextStyles.bodyMedium,
)
}
}

@Preview
@Composable
fun MessageItemPreview() {
MessageItem(Message("John", "This is a preview of the message Item", "8:02PM"))
}

@Preview
@Composable
fun WidgetPreview() {
MessagesWidget(listOf(Message("John", "This is a preview of the message Item", "8:02PM")))
}
Loading

0 comments on commit 8abb7d2

Please sign in to comment.