LazyListTranslator.kt
/*
* Copyright 2021 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 androidx.glance.appwidget.translators
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_MUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Intent
import android.content.Intent.FILL_IN_COMPONENT
import android.widget.RemoteViews
import androidx.core.widget.RemoteViewsCompat
import androidx.glance.appwidget.InsertedViewInfo
import androidx.glance.appwidget.LayoutType
import androidx.glance.appwidget.TopLevelLayoutsCount
import androidx.glance.appwidget.TranslationContext
import androidx.glance.appwidget.applyModifiers
import androidx.glance.appwidget.insertView
import androidx.glance.appwidget.lazy.EmittableLazyColumn
import androidx.glance.appwidget.lazy.EmittableLazyList
import androidx.glance.appwidget.lazy.EmittableLazyListItem
import androidx.glance.appwidget.lazy.ReservedItemIdRangeEnd
import androidx.glance.appwidget.translateChild
import androidx.glance.appwidget.translateComposition
import androidx.glance.layout.Alignment
internal fun RemoteViews.translateEmittableLazyColumn(
translationContext: TranslationContext,
element: EmittableLazyColumn,
) {
val viewDef = insertView(translationContext, LayoutType.List, element.modifier)
translateEmittableLazyList(
translationContext,
element,
viewDef,
)
}
private fun RemoteViews.translateEmittableLazyList(
translationContext: TranslationContext,
element: EmittableLazyList,
viewDef: InsertedViewInfo,
) {
check(!translationContext.isLazyCollectionDescendant) {
"Glance does not support nested list views."
}
// TODO(b/205868100): Remove [FILL_IN_COMPONENT] flag and set target component here when all
// click actions on descendants are exclusively [StartActivityAction] or exclusively not
// [StartActivityAction].
setPendingIntentTemplate(
viewDef.mainViewId,
PendingIntent.getActivity(
translationContext.context,
0,
Intent(),
FILL_IN_COMPONENT or FLAG_MUTABLE or FLAG_UPDATE_CURRENT,
)
)
val items = RemoteViewsCompat.RemoteCollectionItems.Builder().apply {
val childContext = translationContext.forLazyCollection(viewDef.mainViewId)
element.children.foldIndexed(false) { position, previous, itemEmittable ->
itemEmittable as EmittableLazyListItem
val itemId = itemEmittable.itemId
addItem(
itemId,
translateComposition(
childContext.forLazyViewItem(position, LazyListItemStartingViewId),
listOf(itemEmittable),
translationContext.layoutConfiguration?.addLayout(itemEmittable) ?: -1,
)
)
// If the user specifies any explicit ids, we assume the list to be stable
previous || (itemId > ReservedItemIdRangeEnd)
}.let { setHasStableIds(it) }
setViewTypeCount(TopLevelLayoutsCount)
}.build()
RemoteViewsCompat.setRemoteAdapter(
translationContext.context,
this,
translationContext.appWidgetId,
viewDef.mainViewId,
items
)
applyModifiers(translationContext.forAdapterView(), this, element.modifier, viewDef)
}
/**
* Translates a list item either to its immediate only child, or a column layout wrapping all its
* children.
*/
// TODO(b/202382495): Use complex generated layout instead of wrapping in an emittable box to
// support interaction animations in immediate children, e.g. checkboxes, pre-S
internal fun RemoteViews.translateEmittableLazyListItem(
translationContext: TranslationContext,
element: EmittableLazyListItem
) {
require(element.children.size == 1 && element.alignment == Alignment.CenterStart) {
"Lazy list items can only have a single child align at the center start of the view. " +
"The normalization of the composition tree failed."
}
translateChild(translationContext, element.children.first())
}
// All the lazy list items should use the same ids, to ensure the layouts can be re-used.
// Using a very high number to avoid collision with the main app widget ids.
private const val LazyListItemStartingViewId: Int = 0x00100000