PrimaryLayout.java
/*
* Copyright 2022 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.wear.tiles.material.layouts;
import static androidx.wear.tiles.DimensionBuilders.dp;
import static androidx.wear.tiles.DimensionBuilders.expand;
import static androidx.wear.tiles.DimensionBuilders.wrap;
import static androidx.wear.tiles.material.ChipDefaults.COMPACT_HEIGHT;
import static androidx.wear.tiles.material.Helper.checkNotNull;
import static androidx.wear.tiles.material.Helper.isRoundDevice;
import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_BOTTOM_ROUND_PERCENT;
import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_BOTTOM_SQUARE_PERCENT;
import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_HORIZONTAL_ROUND_PERCENT;
import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_HORIZONTAL_SQUARE_PERCENT;
import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_TOP_ROUND_PERCENT;
import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_MARGIN_TOP_SQUARE_PERCENT;
import static androidx.wear.tiles.material.layouts.LayoutDefaults.PRIMARY_LAYOUT_SPACER_HEIGHT;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.wear.tiles.DeviceParametersBuilders.DeviceParameters;
import androidx.wear.tiles.DimensionBuilders.DpProp;
import androidx.wear.tiles.LayoutElementBuilders;
import androidx.wear.tiles.LayoutElementBuilders.Box;
import androidx.wear.tiles.LayoutElementBuilders.Column;
import androidx.wear.tiles.LayoutElementBuilders.Layout;
import androidx.wear.tiles.LayoutElementBuilders.LayoutElement;
import androidx.wear.tiles.LayoutElementBuilders.Spacer;
import androidx.wear.tiles.ModifiersBuilders.Modifiers;
import androidx.wear.tiles.ModifiersBuilders.Padding;
import androidx.wear.tiles.TimelineBuilders.Timeline;
import androidx.wear.tiles.TimelineBuilders.TimelineEntry;
import androidx.wear.tiles.proto.LayoutElementProto;
/**
* Tiles layout that represents a suggested layout style for Material Tiles with the primary
* (compact) chip at the bottom with the given content in a center and the recommended margin and
* padding applied.
*/
// TODO(b/215323986)
public class PrimaryLayout implements LayoutElement {
@NonNull private final LayoutElement mElement;
PrimaryLayout(@NonNull LayoutElement layoutElement) {
this.mElement = layoutElement;
}
/** Builder class for {@link PrimaryLayout}. */
public static final class Builder implements LayoutElement.Builder {
@NonNull private final DeviceParameters mDeviceParameters;
@Nullable private LayoutElement mPrimaryChip = null;
@NonNull private LayoutElement mContent = new Box.Builder().build();
/**
* Creates a builder for the {@link PrimaryLayout} from the given content. Custom
* content inside of it can later be set with ({@link #setContent}.
*/
public Builder(@NonNull DeviceParameters deviceParameters) {
this.mDeviceParameters = deviceParameters;
}
/** Sets the primary compact chip which will be at the bottom. */
@NonNull
@SuppressWarnings("MissingGetterMatchingBuilder")
// There is no direct matching getter for this setter as the serialized format of the
// ProtoLayouts do not allow for a direct reconstruction of the arguments. Instead there are
// methods to get the contents a whole for rendering.
public Builder setCompactChipContent(@NonNull LayoutElement compactChip) {
this.mPrimaryChip = compactChip;
return this;
}
/** Sets the additional content to this layout, above the primary chip. */
@NonNull
public Builder setContent(@NonNull LayoutElement content) {
this.mContent = content;
return this;
}
/** Constructs and returns {@link PrimaryLayout} with the provided content and look. */
@NonNull
@Override
public PrimaryLayout build() {
float topPadding =
mDeviceParameters.getScreenHeightDp()
* (isRoundDevice(mDeviceParameters)
? PRIMARY_LAYOUT_MARGIN_TOP_ROUND_PERCENT
: PRIMARY_LAYOUT_MARGIN_TOP_SQUARE_PERCENT);
float bottomPadding =
mPrimaryChip != null
? (mDeviceParameters.getScreenHeightDp()
* (isRoundDevice(mDeviceParameters)
? PRIMARY_LAYOUT_MARGIN_BOTTOM_ROUND_PERCENT
: PRIMARY_LAYOUT_MARGIN_BOTTOM_SQUARE_PERCENT))
: topPadding;
float horizontalPadding =
mDeviceParameters.getScreenWidthDp()
* (isRoundDevice(mDeviceParameters)
? PRIMARY_LAYOUT_MARGIN_HORIZONTAL_ROUND_PERCENT
: PRIMARY_LAYOUT_MARGIN_HORIZONTAL_SQUARE_PERCENT);
float primaryChipHeight =
mPrimaryChip != null
? (COMPACT_HEIGHT.getValue()
+ PRIMARY_LAYOUT_SPACER_HEIGHT.getValue())
: 0;
DpProp mainContentHeight =
dp(
mDeviceParameters.getScreenHeightDp()
- primaryChipHeight
- bottomPadding
- topPadding);
Modifiers modifiers =
new Modifiers.Builder()
.setPadding(
new Padding.Builder()
.setTop(dp(topPadding))
.setBottom(dp(bottomPadding))
.setStart(dp(horizontalPadding))
.setEnd(dp(horizontalPadding))
.build())
.build();
Column.Builder columnBuilder =
new Column.Builder()
.setModifiers(modifiers)
.setWidth(expand())
.setHeight(expand())
.setHorizontalAlignment(LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER)
.addContent(
new Box.Builder()
.setVerticalAlignment(
LayoutElementBuilders.VERTICAL_ALIGN_CENTER)
.setHeight(mainContentHeight)
.setWidth(expand())
.addContent(mContent)
.build());
if (mPrimaryChip != null) {
columnBuilder
.addContent(
new Spacer.Builder()
.setHeight(PRIMARY_LAYOUT_SPACER_HEIGHT)
.build())
.addContent(
new Box.Builder()
.setVerticalAlignment(
LayoutElementBuilders.VERTICAL_ALIGN_BOTTOM)
.setHeight(wrap())
.addContent(mPrimaryChip)
.build());
}
LayoutElement.Builder element =
new Box.Builder()
.setWidth(expand())
.setHeight(expand())
.setVerticalAlignment(LayoutElementBuilders.VERTICAL_ALIGN_BOTTOM)
.addContent(columnBuilder.build());
return new PrimaryLayout(element.build());
}
}
/** Returns the {@link Layout} object containing this layout template. */
@NonNull
public Layout toLayout() {
return toLayoutBuilder().build();
}
/** Returns the {@link Layout.Builder} object containing this layout template. */
@NonNull
public Layout.Builder toLayoutBuilder() {
return new Layout.Builder().setRoot(mElement);
}
/** Returns the {@link TimelineEntry.Builder} object containing this layout template. */
@NonNull
public TimelineEntry.Builder toTimelineEntryBuilder() {
return new TimelineEntry.Builder().setLayout(toLayout());
}
/** Returns the {@link TimelineEntry} object containing this layout template. */
@NonNull
public TimelineEntry toTimelineEntry() {
return toTimelineEntryBuilder().build();
}
/** Returns the {@link Timeline.Builder} object containing this layout template. */
@NonNull
public Timeline.Builder toTimelineBuilder() {
return new Timeline.Builder().addTimelineEntry(toTimelineEntry());
}
/** Returns the {@link Timeline} object containing this layout template. */
@NonNull
public Timeline toTimeline() {
return toTimelineBuilder().build();
}
@NonNull
public LayoutElement getContent() {
return checkNotNull(
((Box) ((Column) ((Box) mElement).getContents().get(0)).getContents().get(0))
.getContents()
.get(0));
}
/** @hide */
@NonNull
@Override
@RestrictTo(Scope.LIBRARY_GROUP)
public LayoutElementProto.LayoutElement toLayoutElementProto() {
return mElement.toLayoutElementProto();
}
}