diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2737713f..38d754ac 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -57,7 +57,7 @@ material3Android = "1.3.2" media3 = "1.5.0" constraintlayout = "2.1.4" glide-compose = "1.0.0-beta01" -glance = "1.1.0" +glance = "1.3.0-alpha01" tensorflowLite = "2.9.0" tensorflowLiteGpuDelegatePlugin = "0.4.4" tensorflowLiteSupport = "0.4.2" diff --git a/samples/user-interface/appwidgets/build.gradle.kts b/samples/user-interface/appwidgets/build.gradle.kts index 8be71454..b7d7b28f 100644 --- a/samples/user-interface/appwidgets/build.gradle.kts +++ b/samples/user-interface/appwidgets/build.gradle.kts @@ -23,10 +23,10 @@ plugins { android { namespace = "com.example.platform.ui.appwidgets" - compileSdk = 36 + compileSdk = 37 defaultConfig { - minSdk = 21 + minSdk = 23 } compileOptions { diff --git a/samples/user-interface/appwidgets/src/main/AndroidManifest.xml b/samples/user-interface/appwidgets/src/main/AndroidManifest.xml index 2baf4e0a..c24d88cc 100644 --- a/samples/user-interface/appwidgets/src/main/AndroidManifest.xml +++ b/samples/user-interface/appwidgets/src/main/AndroidManifest.xml @@ -104,6 +104,19 @@ android:name="android.appwidget.provider" android:resource="@xml/sample_text_with_image_widget_info" /> + + + + + + + = Build.VERSION_CODES.VANILLA_ICE_CREAM) { + lifecycleScope.launch { + try { + val context = this@CanonicalLayoutActivity + val receiver = FullBleedImageAppWidgetReceiver::class.java + val glanceAppWidgetManager = GlanceAppWidgetManager(context) + val appWidgetManager = context.getSystemService(AppWidgetManager::class.java) + + val providerInfo = appWidgetManager?.installedProviders?.firstOrNull { + it.provider.className == receiver.name + } + + if (providerInfo?.generatedPreviewCategories == 0) { + val result = glanceAppWidgetManager.setWidgetPreviews(FullBleedImageAppWidgetReceiver::class) + val status = when (result) { + GlanceAppWidgetManager.SET_WIDGET_PREVIEWS_RESULT_SUCCESS -> "Success" + GlanceAppWidgetManager.SET_WIDGET_PREVIEWS_RESULT_RATE_LIMITED -> "Rate-Limited" + else -> "Error ($result)" + } + Log.i("CanonicalLayoutActivity", "Published previews for ${receiver.simpleName}: $status") + } + } catch (e: Exception) { + Log.e("CanonicalLayoutActivity", "Failed to set widget previews", e) + } + } + } + setContent { MaterialTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> @@ -195,7 +246,7 @@ private fun CanonicalLayoutRow(widget: CanonicalLayoutRowData, modifier: Modifie painter = painterResource(id = widget.imageRes), contentDescription = "Screenshot of ${widget.rowTitle}", contentScale = ContentScale.FillWidth, - modifier = modifier, + modifier = modifier.fillMaxWidth(), ) } @@ -287,6 +338,12 @@ private val canonicalLayoutWidgets = listOf( imageRes = R.drawable.cl_activity_row_text_image, receiver = TextWithImageAppWidgetReceiver::class.java, ), + CanonicalLayoutRowData( + rowTitle = R.string.cl_title_full_bleed_image, + rowDescription = R.string.cl_description_full_bleed_image, + imageRes = R.drawable.cl_activity_row_full_bleed_image, + receiver = FullBleedImageAppWidgetReceiver::class.java, + ), CanonicalLayoutRowData( rowTitle = R.string.cl_title_grid, rowDescription = R.string.cl_description_grid, diff --git a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/collections/data/FakeImageGridDataRepository.kt b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/collections/data/FakeImageGridDataRepository.kt index e8e7518e..03e72789 100644 --- a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/collections/data/FakeImageGridDataRepository.kt +++ b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/collections/data/FakeImageGridDataRepository.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 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.example.platform.ui.appwidgets.glance.layout.collections.data import android.content.Context @@ -16,11 +32,11 @@ import com.example.platform.ui.appwidgets.glance.layout.collections.layout.Image import com.example.platform.ui.appwidgets.glance.layout.utils.ImageUtils.getMaxPossibleImageSize import com.example.platform.ui.appwidgets.glance.layout.utils.ImageUtils.getMaxWidgetMemoryAllowedSizeInBytes import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.runBlocking import com.example.platform.ui.appwidgets.glance.layout.computeIfAbsent as computeIfAbsentExt /** * A fake in-memory implementation of repository that produces a list of @@ -74,7 +90,7 @@ class FakeImageGridDataRepository { val width = IMAGE_SIZE.coerceAtMost(imageSizeLimit.width) val height = width * 9 / 16 - val mappedItems = runBlocking { + val mappedItems = coroutineScope { items.map { item -> async(Dispatchers.IO) { var bitmap: Bitmap? = null diff --git a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/FullBleedImageAppWidget.kt b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/FullBleedImageAppWidget.kt new file mode 100644 index 00000000..8f26e61a --- /dev/null +++ b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/FullBleedImageAppWidget.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2026 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 details; and limitations under the License. + */ + +package com.example.platform.ui.appwidgets.glance.layout.text + +import android.content.Context +import android.util.Log +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.glance.GlanceId +import androidx.glance.GlanceTheme +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetManager +import androidx.glance.appwidget.GlanceAppWidgetReceiver +import androidx.glance.appwidget.SizeMode +import androidx.glance.appwidget.provideContent +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import com.example.platform.ui.appwidgets.glance.layout.collections.data.FakeImageGridDataRepository +import com.example.platform.ui.appwidgets.glance.layout.collections.data.FakeImageGridDataRepository.Companion.getImageGridDataRepo +import com.example.platform.ui.appwidgets.glance.layout.text.layout.FullBleedImageLayout +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * Glance widget showcasing Full Bleed Snap Scrolling, powered by [FakeImageGridDataRepository]. + */ +class FullBleedImageAppWidget : GlanceAppWidget() { + override val sizeMode: SizeMode = SizeMode.Exact + + override val previewSizeMode = SizeMode.Responsive( + setOf( + DpSize(109.dp, 115.dp) + ) + ) + + override suspend fun provideGlance(context: Context, id: GlanceId) { + val repo = getImageGridDataRepo(id) + + val initialData = withContext(Dispatchers.IO) { + repo.load(context) + } + + provideContent { + val data by repo.data().collectAsState(initial = initialData) + + GlanceTheme { + FullBleedImageLayout( + data = data + ) + } + } + } + + override suspend fun providePreview(context: Context, widgetCategory: Int) { + val repo = FakeImageGridDataRepository() + + val initialData = withContext(Dispatchers.IO) { + repo.load(context) + } + + provideContent { + GlanceTheme { + FullBleedImageLayout( + data = initialData.take(1) + ) + } + } + } +} + +/** + * Receiver for the Full Bleed Snap Scrolling widget. + */ +class FullBleedImageAppWidgetReceiver : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget = FullBleedImageAppWidget() + + override fun onDeleted(context: Context, appWidgetIds: IntArray) { + val glanceAppWidgetManager = GlanceAppWidgetManager(context) + appWidgetIds.forEach { id -> + try { + val glanceId = glanceAppWidgetManager.getGlanceIdBy(id) + FakeImageGridDataRepository.cleanUp(glanceId) + } catch (e: IllegalArgumentException) { + Log.w("FullBleedImageReceiver", "Skipping cleanup for invalid AppWidget ID: $id", e) + } + } + super.onDeleted(context, appWidgetIds) + } +} diff --git a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/FullBleedImageLayout.kt b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/FullBleedImageLayout.kt new file mode 100644 index 00000000..df8c63e3 --- /dev/null +++ b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/FullBleedImageLayout.kt @@ -0,0 +1,240 @@ +/* + * Copyright 2026 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.example.platform.ui.appwidgets.glance.layout.text.layout + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.DpSize +import androidx.glance.GlanceModifier +import androidx.glance.Image +import androidx.glance.ImageProvider +import androidx.glance.LocalContext +import androidx.glance.LocalSize +import androidx.glance.appwidget.cornerRadius +import androidx.glance.appwidget.lazy.LazyColumn +import androidx.glance.appwidget.lazy.VerticalScrollMode +import androidx.glance.appwidget.lazy.items +import androidx.glance.background +import androidx.glance.layout.Alignment +import androidx.glance.layout.Box +import androidx.glance.layout.Column +import androidx.glance.layout.ContentScale +import androidx.glance.layout.fillMaxSize +import androidx.glance.layout.fillMaxWidth +import androidx.glance.layout.height +import androidx.glance.layout.padding +import androidx.glance.layout.size +import androidx.glance.layout.width +import androidx.glance.text.FontWeight +import androidx.glance.text.Text +import androidx.glance.text.TextStyle +import androidx.glance.unit.ColorProvider +import com.example.platform.ui.appwidgets.R +import com.example.platform.ui.appwidgets.glance.layout.collections.layout.ImageGridItemData +import com.example.platform.ui.appwidgets.glance.layout.collections.layout.NoDataContent +import com.example.platform.ui.appwidgets.glance.layout.utils.ActionUtils.actionStartDemoActivity +import com.example.platform.ui.appwidgets.glance.layout.utils.MediumWidgetPreview +import com.example.platform.ui.appwidgets.glance.layout.utils.SmallWidgetPreview + +/** + * A full bleed snap scrolling gallery canonical layout using [ImageGridItemData]. + * + * Each item displays an edge-to-edge background photo with overlaid + * title and caption details that auto-scale to fit the current widget size. + */ +@Composable +fun FullBleedImageLayout( + data: List? = null, +) { + val size = LocalSize.current + val isSmall = size.height <= 110.dp + val appName = LocalContext.current.getString(R.string.sample_full_bleed_image_app_widget_name) + + Box( + modifier = GlanceModifier + .fillMaxSize() + .cornerRadius(16.dp) + ) { + if (data.isNullOrEmpty()) { + val context = LocalContext.current + NoDataContent( + noDataText = context.getString(R.string.sample_no_data_text), + noDataIconRes = R.drawable.sample_no_data_icon, + actionButtonText = context.getString(R.string.sample_learn_more_button_text), + actionButtonIcon = R.drawable.sample_info_icon, + actionButtonOnClick = actionStartDemoActivity("on-click of info button in no data view") + ) + } else if (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.BAKLAVA_1) { + SnapScrollingGallery( + data = data, + isSmall = isSmall, + appName = appName, + size = size + ) + } else { + // Show a standard scrolling list of items without Snap Scrolling + // TODO: Remove once Snap Scrolling gracefully degrades + GalleryList( + data = data, + isSmall = isSmall, + appName = appName, + size = size + ) + } + } +} + +@RequiresApi(Build.VERSION_CODES_FULL.BAKLAVA_1) +@Composable +private fun SnapScrollingGallery( + data: List, + isSmall: Boolean, + appName: String, + size: DpSize, +) { + GalleryList( + data = data, + isSmall = isSmall, + appName = appName, + size = size, + verticalScrollMode = VerticalScrollMode.SnapScrollMatchHeight(size.height) + ) +} + +@Composable +private fun GalleryList( + data: List, + isSmall: Boolean, + appName: String, + size: DpSize, + verticalScrollMode: VerticalScrollMode = VerticalScrollMode.Normal +) { + if (data.size == 1) { + // If there's only 1 item (like in the widget preview), render with fillMaxSize to + // bypass LazyColumn measurement issues where the generated widget preview item doesn't + // fill the widget bounds. + GalleryItemCard( + item = data[0], + isSmall = isSmall, + appName = appName, + modifier = GlanceModifier.fillMaxSize() + ) + } else { + val limitedData = data.take(5) + LazyColumn( + modifier = GlanceModifier.fillMaxSize(), + verticalScrollMode = verticalScrollMode + ) { + items(limitedData, itemId = { item -> item.key.hashCode().toLong() }) { item -> + GalleryItemCard( + item = item, + isSmall = isSmall, + appName = appName, + modifier = GlanceModifier.width(size.width).height(size.height) + ) + } + } + } +} + +@Composable +private fun GalleryItemCard( + item: ImageGridItemData, + isSmall: Boolean, + appName: String, + modifier: GlanceModifier = GlanceModifier, +) { + val itemTitle = item.title ?: "" + + Box( + modifier = modifier, + contentAlignment = Alignment.BottomStart + ) { + val imageProvider = item.image?.let { ImageProvider(it) } ?: ImageProvider(R.drawable.sample_placeholder_image) + + Image( + provider = imageProvider, + contentDescription = item.imageContentDescription ?: itemTitle, + contentScale = ContentScale.Crop, + modifier = GlanceModifier.fillMaxSize() + ) + + val titleFontSize = if (isSmall) { + WidgetTextDimensions.primaryTextFontSizeAndMaxLines(itemTitle).first + } else { + WidgetTextDimensions.maxPrimaryTextFontSize + } + + Column( + modifier = GlanceModifier + .fillMaxWidth() + // Implementing a partial gradient scrim by applying a background modifier directly + // to a text Column results in the gradient stretching to fill the entire widget. + .background(ImageProvider(R.drawable.sample_scrim_gradient)) + .padding(WidgetTextDimensions.widgetPadding), + verticalAlignment = Alignment.Bottom, + ) { + // App Logo Icon styled as a fixed size monochrome asset above the Title as the Caption + Image( + provider = ImageProvider(R.drawable.sample_app_logo), + contentDescription = appName, + contentScale = ContentScale.Fit, + modifier = GlanceModifier.size(24.dp) + ) + + if (itemTitle.isNotEmpty()) { + Text( + text = itemTitle, + style = TextStyle( + color = ColorProvider(Color.White), + fontWeight = FontWeight.Bold, + fontSize = titleFontSize + ), + modifier = GlanceModifier.fillMaxWidth().padding(top = 4.dp) + ) + } + } + } +} + +@RequiresApi(Build.VERSION_CODES_FULL.BAKLAVA_1) +@SmallWidgetPreview +@MediumWidgetPreview +@Composable +private fun FullBleedImageLayoutPreview() { + val previewItems = listOf( + ImageGridItemData( + key = "0", + title = "Yosemite Valley under clear blue skies", + image = null, + imageContentDescription = null + ), + ImageGridItemData( + key = "1", + title = "Mystical forest lake reflection at sunrise", + image = null, + imageContentDescription = null + ) + ) + + FullBleedImageLayout( + data = previewItems + ) +} diff --git a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/LongTextLayout.kt b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/LongTextLayout.kt index 4adb7121..417e2b9a 100644 --- a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/LongTextLayout.kt +++ b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/text/layout/LongTextLayout.kt @@ -40,9 +40,9 @@ import androidx.glance.layout.padding import androidx.glance.text.Text import androidx.glance.text.TextStyle import com.example.platform.ui.appwidgets.R -import com.example.platform.ui.appwidgets.glance.layout.text.layout.LongTextLayoutDimensions.captionFontSizeAndMaxLines -import com.example.platform.ui.appwidgets.glance.layout.text.layout.LongTextLayoutDimensions.widgetPadding -import com.example.platform.ui.appwidgets.glance.layout.text.layout.LongTextLayoutDimensions.primaryTextFontSizeAndMaxLines +import com.example.platform.ui.appwidgets.glance.layout.text.layout.WidgetTextDimensions.captionFontSizeAndMaxLines +import com.example.platform.ui.appwidgets.glance.layout.text.layout.WidgetTextDimensions.widgetPadding +import com.example.platform.ui.appwidgets.glance.layout.text.layout.WidgetTextDimensions.primaryTextFontSizeAndMaxLines import com.example.platform.ui.appwidgets.glance.layout.utils.ActionUtils.actionStartDemoActivity import com.example.platform.ui.appwidgets.glance.layout.utils.FontUtils.calculateFontSizeAndMaxLines import com.example.platform.ui.appwidgets.glance.layout.utils.MediumWidgetPreview @@ -221,7 +221,7 @@ data class LongTextLayoutData( val caption: String, ) -private object LongTextLayoutDimensions { +internal object WidgetTextDimensions { val widgetPadding = 16.dp private val titleBarHeight: Dp @Composable get() = if (LongTextLayoutSize.fromLocalSize() == LongTextLayoutSize.XSmall) { @@ -242,14 +242,14 @@ private object LongTextLayoutDimensions { } // Upper and lower bounds for the caption. - private val minCaptionFontSize = 12.sp // low - GM3 Label Medium - private val maxCaptionFontSize = 14.sp // high - GM3 Label Large + internal val minCaptionFontSize = 12.sp // low - GM3 Label Medium + internal val maxCaptionFontSize = 14.sp // high - GM3 Label Large // Upper bound for primary text. - private val maxPrimaryTextFontSize = 28.sp // GM3 Headline Medium + internal val maxPrimaryTextFontSize = 28.sp // GM3 Headline Medium // For a font size 16 of primary text, we want caption to be of size 14. - private const val captionToPrimaryTextRatio = 0.875f + internal const val captionToPrimaryTextRatio = 0.875f @Composable fun primaryTextFontSizeAndMaxLines(text: String): Pair { diff --git a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/utils/FontUtils.kt b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/utils/FontUtils.kt index 3ad49989..f472c638 100644 --- a/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/utils/FontUtils.kt +++ b/samples/user-interface/appwidgets/src/main/java/com/example/platform/ui/appwidgets/glance/layout/utils/FontUtils.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 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.example.platform.ui.appwidgets.glance.layout.utils import android.content.Context diff --git a/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/cl_activity_row_full_bleed_image.png b/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/cl_activity_row_full_bleed_image.png new file mode 100644 index 00000000..7fda3a9c Binary files /dev/null and b/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/cl_activity_row_full_bleed_image.png differ diff --git a/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/sample_full_bleed_image_preview.png b/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/sample_full_bleed_image_preview.png new file mode 100644 index 00000000..265b91f2 Binary files /dev/null and b/samples/user-interface/appwidgets/src/main/res/drawable-nodpi/sample_full_bleed_image_preview.png differ diff --git a/samples/user-interface/appwidgets/src/main/res/drawable/sample_scrim_gradient.xml b/samples/user-interface/appwidgets/src/main/res/drawable/sample_scrim_gradient.xml new file mode 100644 index 00000000..5c79e094 --- /dev/null +++ b/samples/user-interface/appwidgets/src/main/res/drawable/sample_scrim_gradient.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + diff --git a/samples/user-interface/appwidgets/src/main/res/values-xlarge/dimens.xml b/samples/user-interface/appwidgets/src/main/res/values-xlarge/dimens.xml index 19ba7de3..3fafe678 100644 --- a/samples/user-interface/appwidgets/src/main/res/values-xlarge/dimens.xml +++ b/samples/user-interface/appwidgets/src/main/res/values-xlarge/dimens.xml @@ -1,4 +1,19 @@ + diff --git a/samples/user-interface/appwidgets/src/main/res/values/dimens.xml b/samples/user-interface/appwidgets/src/main/res/values/dimens.xml index 1df1cc6a..50d6bdbe 100644 --- a/samples/user-interface/appwidgets/src/main/res/values/dimens.xml +++ b/samples/user-interface/appwidgets/src/main/res/values/dimens.xml @@ -134,5 +134,13 @@ 624dp 422dp + + 2 + 2 + 120dp + 48dp + 120dp + 48dp + \ No newline at end of file diff --git a/samples/user-interface/appwidgets/src/main/res/values/strings.xml b/samples/user-interface/appwidgets/src/main/res/values/strings.xml index f2518f03..e53e8022 100644 --- a/samples/user-interface/appwidgets/src/main/res/values/strings.xml +++ b/samples/user-interface/appwidgets/src/main/res/values/strings.xml @@ -89,4 +89,8 @@ Search Toolbar Ideal for apps where search is paramount, this layout provides a dedicated search entry point while allowing for additional shortcuts based on available widget space. + Full Bleed Snap Scrolling + Full Bleed Snap Scrolling + A full bleed gallery widget demonstrating snap scrolling. Scroll vertically between featured items with an overlaid title and caption. + \ No newline at end of file diff --git a/samples/user-interface/appwidgets/src/main/res/xml/sample_full_bleed_image_widget_info.xml b/samples/user-interface/appwidgets/src/main/res/xml/sample_full_bleed_image_widget_info.xml new file mode 100644 index 00000000..6f076990 --- /dev/null +++ b/samples/user-interface/appwidgets/src/main/res/xml/sample_full_bleed_image_widget_info.xml @@ -0,0 +1,26 @@ + +