Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JetChat] Add glance widget for JetChat App #1424 #1425

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/my_app_widget_info" />
shangeethsivan marked this conversation as resolved.
Show resolved Hide resolved
</receiver>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@

package com.example.compose.jetchat.components

import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.os.Build
import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
Expand Down Expand Up @@ -44,13 +49,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,9 +81,13 @@ fun JetchatDrawerContent(
ProfileItem("Taylor Brooks", colleagueProfile.photo) {
onProfileClicked(colleagueProfile.userId)
}
DividerItem(modifier = Modifier.padding(horizontal = 28.dp))
DrawerItemHeader("Settings")
WidgetDiscoverability()
}
}


@Composable
private fun DrawerHeader() {
Row(modifier = Modifier.padding(16.dp), verticalAlignment = CenterVertically) {
Expand All @@ -90,6 +102,7 @@ private fun DrawerHeader() {
)
}
}

@Composable
private fun DrawerItemHeader(text: String) {
Box(
Expand Down Expand Up @@ -199,6 +212,7 @@ fun DrawerPreview() {
}
}
}

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


@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)
)
}
}

private fun addWidgetToHomeScreen(context: Context) {
val appWidgetManager = AppWidgetManager.getInstance(context)
val myProvider = ComponentName(context, WidgetReceiver::class.java)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of showing the error only when user clicks the button, can we prevent showing the drawer item at all?
Maybe you can abstract the method to something like widgetAddingIsSupported() that uses the SDK check and uses the isRequestPinAppWidgetSupported and check for that before showing the UI.

if (appWidgetManager.isRequestPinAppWidgetSupported) {
appWidgetManager.requestPinAppWidget(myProvider, null, null)
} else {
Toast.makeText(
context,
"Unable to add widget, please add from home screen",
Toast.LENGTH_LONG
).show()
}
} else {
Toast.makeText(
context,
"Unable to add widget, please add from home screen",
Toast.LENGTH_LONG
).show()
}
shangeethsivan marked this conversation as resolved.
Show resolved Hide resolved
}
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 @@ -64,6 +64,58 @@ private val initialMessages = listOf(
"8:03 PM"
)
)
val unreadMessages = listOf(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest instead of adding bunch of copy pastes, to use something like the initialMessages and filter it for the unread messages. Maybe filter the ones not from the current author?
val unreadMessages = initialMessages.filter { it.author != "me" }

If you don't have enough messages, you can add more (meaningful-ish) messages to the initialMessages

Message(
"Taylor Brooks",
"You can use all the same stuff",
"8:05 PM"
),
Message(
"Taylor Brooks",
"@aliconors Take a look at the `Flow.collectAsStateWithLifecycle()` APIs",
"8:05 PM"
),
Message(
"Taylor Brooks",
"You can use all the same stuff",
"8:05 PM"
),
Message(
"Taylor Brooks",
"@aliconors Take a look at the `Flow.collectAsStateWithLifecycle()` APIs",
"8:05 PM"
),
Message(
"Taylor Brooks",
"You can use all the same stuff",
"8:05 PM"
),
Message(
"Taylor Brooks",
"@aliconors Take a look at the `Flow.collectAsStateWithLifecycle()` APIs",
"8:05 PM"
),
Message(
"Taylor Brooks",
"You can use all the same stuff",
"8:05 PM"
),
Message(
"Taylor Brooks",
"@aliconors Take a look at the `Flow.collectAsStateWithLifecycle()` APIs",
"8:05 PM"
),
Message(
"Taylor Brooks",
"You can use all the same stuff",
"8:05 PM"
),
Message(
"Taylor Brooks",
"@aliconors Take a look at the `Flow.collectAsStateWithLifecycle()` APIs",
"8:05 PM"
),
)

val exampleUiState = ConversationUiState(
initialMessages = initialMessages,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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.widget.composables.MessagesWidget

class JetChatWidget : GlanceAppWidget() {

override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
GlanceTheme {
MessagesWidget()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.compose.jetchat.widget

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

class WidgetReceiver : GlanceAppWidgetReceiver() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have a widget error layout but do not use it. Consider referencing it here with the errorUiLayout property or implementing onCompositionError


override val glanceAppWidget: GlanceAppWidget
get() = JetChatWidget()

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.example.compose.jetchat.widget.composables

import androidx.compose.runtime.Composable
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.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import com.example.compose.jetchat.NavActivity
import com.example.compose.jetchat.R
import com.example.compose.jetchat.conversation.Message
import com.example.compose.jetchat.data.unreadMessages

@Composable
fun MessagesWidget() {
Scaffold(titleBar = {
TitleBar(
startIcon = ImageProvider(R.drawable.ic_jetchat),
iconColor = null,
title = LocalContext.current.getString(R.string.app_name_unreads),
)
}) {
val unreadMessage = unreadMessages

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unreadMessages should be hoisted and passed into MessagesWidget from provideGlance.

Usually in a Glance Widget you will pass to your widget a repository and the widget can access flows in the composable function. Even though this is fakeData we should try to keep as much as possible to this pattern.

LazyColumn(modifier = GlanceModifier.fillMaxWidth()) {
unreadMessage.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 = TextStyle(fontWeight = FontWeight.Bold)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add color = GlanceTheme.colors.onSurface to the TextStyle. This will make the colors update when you switch to dark mode.

)
Text(
text = message.content,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of us not being able to format the text in any form. Showing the unformatted font looks unstyled :(

style = TextStyle(fontWeight = FontWeight.Normal)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add color = GlanceTheme.colors.onSurface to the TextStyle. This will make the colors update when you switch to dark mode.

)
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions Jetchat/app/src/main/res/layout/widget_error_layout.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:gravity="center">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black30"
Comment on lines +5 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try not to use direct colors, but use the tokens from the Theme

android:text="@string/widget_loading_error" />
</RelativeLayout>
Comment on lines +1 to +13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this used? 🤔

11 changes: 11 additions & 0 deletions Jetchat/app/src/main/res/layout/widget_loading_layout.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
shangeethsivan marked this conversation as resolved.
Show resolved Hide resolved
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff">

<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
3 changes: 3 additions & 0 deletions Jetchat/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,8 @@
<string name="more_options">More options</string>
<string name="touch_and_hold_to_record">Touch and hold to record</string>
<string name="record_message">Record voice message</string>
<string name="widget_loading_error">Cannot load Widget</string>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<string name="widget_loading_error">Cannot load Widget</string>
<string name="widget_loading_error">Problem loading the widget</string>

<string name="app_name_unreads">JetChat (Un-Reads)</string>
shangeethsivan marked this conversation as resolved.
Show resolved Hide resolved
<string name="add_widget_to_home_page">Add Widget to Home Page</string>

</resources>
9 changes: 9 additions & 0 deletions Jetchat/app/src/main/res/xml/my_app_widget_info.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_loading_layout"
android:minWidth="276dp"
android:minHeight="102dp"
android:previewImage="@drawable/widget_icon"
android:resizeMode="none"
android:targetCellWidth="4"
android:targetCellHeight="3" />
Binary file added Jetchat/screenshots/widget.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading