Skip to content

Commit

Permalink
Glance Widget Solution
Browse files Browse the repository at this point in the history
Change-Id: Ie75625aa5ca568f41f94ef58b98a583c79aebe0d
  • Loading branch information
secondsun committed Mar 25, 2024
1 parent 4e032af commit 83a0fa4
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 10 deletions.
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ dependencies {
androidTestImplementation(libs.espresso.core)
baselineProfile(project(":baselineprofile"))

implementation(libs.glance.appwidget)
implementation(libs.glance.material)

val composeBom = platform(libs.compose.bom)
implementation(composeBom)
androidTestImplementation(composeBom)
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@
android:enableOnBackInvokedCallback="true"
android:theme="@style/Theme.Social"
tools:targetApi="34">

<receiver
android:name=".widget.SociaLiteAppWidgetReceiver"
android:exported="true"
android:label="SociaLite">


<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>


<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/socialite_widget_info" />
</receiver>


<activity
android:name=".MainActivity"
android:exported="true"
Expand All @@ -48,6 +66,7 @@
android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize"
android:supportsPictureInPicture="true">


<!-- This activity is the one that's shown in the launcher. -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import com.google.android.samples.socialite.data.MessageDao
import com.google.android.samples.socialite.data.RoomDatabaseManager
import com.google.android.samples.socialite.data.populateInitialData
import com.google.android.samples.socialite.widget.model.WidgetModelDao
import com.google.android.samples.socialite.widget.model.WidgetModelRepository
import dagger.Binds
import dagger.Module
import dagger.Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.google.android.samples.socialite.widget


import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
Expand All @@ -27,10 +26,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.net.toUri
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.GlanceTheme
import androidx.core.net.toUri
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.LocalContext
Expand Down Expand Up @@ -67,7 +66,146 @@ import kotlinx.coroutines.runBlocking

class SociaLiteAppWidget : GlanceAppWidget() {
override suspend fun provideGlance(context: Context, id: GlanceId) {
TODO("Not yet implemented")
provideContent {
GlanceTheme {
Content(context, id)
}
}
}

@Composable
private fun Content(context: Context, id: GlanceId) {
val widgetId = GlanceAppWidgetManager(context).getAppWidgetId(id)
val repository = WidgetModelRepository.get(context)
val model = repository.loadModel(widgetId).collectAsState(null).value

Scaffold(
titleBar = {
TitleBar(
textColor = GlanceTheme.colors.onSurface,
startIcon = ImageProvider(R.mipmap.ic_launcher),
title = "SociaLite",
)
},
backgroundColor = GlanceTheme.colors.widgetBackground,
modifier = GlanceModifier.fillMaxSize(),
) {
when (model) {
null -> ZeroState(repository, widgetId, context)
else -> {
FavoriteContact(model)
}
}
}
}

@Composable
private fun FavoriteContact(model: WidgetModel) {
val appContext = LocalContext.current.applicationContext
Box(
modifier = GlanceModifier.fillMaxSize().then(

if (model.unreadMessages) {
GlanceModifier.clickable(
actionStartActivity(
Intent(appContext, MainActivity::class.java)
.setAction(Intent.ACTION_VIEW)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.setData("https://socialite.google.com/chat/${model.contactId}".toUri()),
),
)
} else {
GlanceModifier
},
),
contentAlignment = Alignment.Center,
) {
Column {
Image(
provider = ImageProvider(model.photo.toUri()),
contentDescription = model.displayName,
)
Text(
text = model.displayName,
style = TextStyle(
fontWeight = FontWeight.Bold,
color = GlanceTheme.colors.onBackground,
),
)
Text(
text = if (model.unreadMessages) "New Message!" else "No messages",
style = TextStyle(color = GlanceTheme.colors.onBackground),
)
}
}
}

@Composable
fun ZeroState(repository: WidgetModelRepository, widgetId: Int, context: Context) {
Box(modifier = GlanceModifier.fillMaxSize(), contentAlignment = Alignment.TopStart) {
LazyColumn {
items(Contact.CONTACTS.size) { contactIndex ->
val contact = Contact.CONTACTS[contactIndex]
val profileImage = remember(contact.iconUri) { mutableStateOf<Bitmap?>(null) }

LaunchedEffect(contact.iconUri) {
profileImage.value = repository.getImage(contact.iconUri, false, context)
}

Row(
modifier = GlanceModifier.fillMaxWidth().clickable {
runBlocking {
repository.create(
WidgetModel(
widgetId,
contact.id,
contact.name,
contact.iconUri.toString(),
false,
),
)
}
}.wrapContentHeight().padding(4.dp),
verticalAlignment = Alignment.Vertical.CenterVertically,
) {
Image(
modifier = GlanceModifier.size(48.dp).padding(end = 8.dp),
contentScale = ContentScale.Fit,
provider = if (profileImage.value == null) {
ImageProvider(R.drawable.ic_launcher_background)
} else {
ImageProvider(
profileImage.value!!,
)
},
contentDescription = "Avatar",

)
Column(
modifier = GlanceModifier.defaultWeight(),
horizontalAlignment = Alignment.Horizontal.Start,
) {
Text(
text = contact.name,
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 16.sp,
color = GlanceTheme.colors.onBackground,
),
)
Text(
text = "Click to select as favorite contact",
style = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
fontFamily = FontFamily.Monospace,
color = GlanceTheme.colors.onBackground,
),
)
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 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
*
* http://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.google.android.samples.socialite.widget

import android.content.Context
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import com.google.android.samples.socialite.widget.model.WidgetModelRepository
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class SociaLiteAppWidgetReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget = SociaLiteAppWidget()

@Inject
lateinit var repository: WidgetModelRepository

override fun onDeleted(context: Context, appWidgetIds: IntArray) {
super.onDeleted(context, appWidgetIds)
repository.delete(appWidgetIds)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,13 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch


@Singleton
class WidgetModelRepository @Inject internal constructor(private val widgetModelDao: WidgetModelDao, @AppCoroutineScope private val coroutineScope: CoroutineScope, @ApplicationContext private val appContext: Context) {


@EntryPoint
@InstallIn(SingletonComponent::class)
interface WidgetModelRepositoryEntryoint {
fun widgetModelRepository() : WidgetModelRepository;
fun widgetModelRepository(): WidgetModelRepository
}

companion object {
Expand All @@ -61,9 +59,8 @@ class WidgetModelRepository @Inject internal constructor(private val widgetModel
applicationContext,
WidgetModelRepositoryEntryoint::class.java,
)
return widgetModelRepositoryEntryoint.widgetModelRepository();
return widgetModelRepositoryEntryoint.widgetModelRepository()
}

}

suspend fun create(model: WidgetModel): WidgetModel {
Expand Down Expand Up @@ -96,7 +93,6 @@ class WidgetModelRepository @Inject internal constructor(private val widgetModel
}
}


suspend fun getImage(url: Uri, force: Boolean = false, context: Context): Bitmap? {
val request =
ImageRequest.Builder(context).transformations(CircleCropTransformation()).data(url)
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/res/xml/socialite_widget_info.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:resizeMode="horizontal|vertical"
android:minHeight="128dp"
android:minWidth="128dp"
android:updatePeriodMillis="3600000"
android:minResizeHeight="64dp"
android:minResizeWidth="64dp"
android:initialLayout="@layout/glance_default_loading_layout">
</appwidget-provider>

0 comments on commit 83a0fa4

Please sign in to comment.