Search code examples
wear-os

How to add a clickable modifier to EdgeContentLayout (tiles)


What's the problem

I'm trying to build a tile with a progress bar (showing the user's sleep time) and that when a user clicks it, it opens the main activity (app), but I can't figure out how to add the clickable modifier to my EdgeContentLayout and when I add the clickable modifier to my text it doesn't even open the app.

My Code

Here's my MainTileService:

/**
 * Skeleton for a tile with no images.
 */
@OptIn(ExperimentalHorologistApi::class)
class MainTileService : SuspendingTileService() {
    override suspend fun resourcesRequest(
        requestParams: RequestBuilders.ResourcesRequest
    ): ResourceBuilders.Resources {
        return ResourceBuilders.Resources.Builder()
            .setVersion(RESOURCES_VERSION)
            .addIdToImageMapping(
                "sleep_icon",
                ResourceBuilders.ImageResource.Builder()
                    .setAndroidResourceByResId(
                        ResourceBuilders.AndroidImageResourceByResId.Builder()
                        .setResourceId(R.drawable.sleep)
                        .build()
                    ).build()
            ).build()
    }

    override suspend fun tileRequest(
        requestParams: RequestBuilders.TileRequest
    ): TileBuilders.Tile {
        val lastClickableId = requestParams.currentState.lastClickableId
        if (lastClickableId === LAUNCH_APP_ID) {
            Log.d("Tile", "Launching main activity...")
            startActivity(packageManager.getLaunchIntentForPackage(packageName))
        }
        
        val sharedPreferences = getSharedPreferences(
            SettingsBasics.SHARED_PREFERENCES.getKey(),
            SettingsBasics.SHARED_PREFERENCES.getMode()
        )
        // Initialize managers
        val timeManager = TimeManager()
        val alarmManager = AlarmsManager()
        // Get preferences
        val useAlarm = sharedPreferences.getBoolean(Settings.ALARM.getKey(), Settings.ALARM.getDefaultAsBoolean())
        val wakeTimeString = sharedPreferences.getString(Settings.WAKE_TIME.getKey(), Settings.WAKE_TIME.getDefault())
        // Get next alarm
        val nextAlarm = alarmManager.fetchAlarms(this);
        // Calculate wake time
        // between alarm and wake time
        val wakeTime = timeManager.getWakeTime(
            useAlarm,
            nextAlarm,
            wakeTimeString,
            Settings.WAKE_TIME.getDefaultAsLocalTime()
        );
        // Calculate time difference
        val sleepTime = timeManager.calculateTimeDifference(wakeTime.first)
        // Calculate sleep quality from time diff
        val sleepQuality = timeManager.calculateSleepQuality(sleepTime)

        val singleTileTimeline = TimelineBuilders.Timeline.Builder().addTimelineEntry(
            TimelineBuilders.TimelineEntry.Builder().setLayout(
                LayoutElementBuilders.Layout.Builder().setRoot(
                    tileLayout(this, sleepTime, sleepQuality).build()
                ).build()
            ).build()
        ).build()


        return TileBuilders.Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setTileTimeline(singleTileTimeline)
            .setFreshnessIntervalMillis(
                // Every 5 minutes (60000 = 1m)
                60000 * 5
            )
            .build()
    }
}

Here's my tile layout:

private fun tileLayout(
    context: Context,
    sleepTime: TimeDifference,
    sleepQuality: SleepQuality
): EdgeContentLayout.Builder {
    val deviceParameters = buildDeviceParameters(context.resources)
    return EdgeContentLayout.Builder(deviceParameters)
        .setEdgeContent(
            CircularProgressIndicator.Builder()
                .setProgress(sleepTime.hours.toFloat() / DEFAULT_GOAL)
                .setStartAngle(-165f)
                .setEndAngle(165f)
                .setCircularProgressIndicatorColors(
                    ProgressIndicatorColors(
                        TileColors.PrimaryColor,
                        TileColors.TrackColor
                    )
                )
                .build()
        )
        .setPrimaryLabelTextContent(
            Text.Builder(context, "Sleep")
                .setTypography(6.toInt())
                .setColor(argb(TileColors.LightText))
                .build()
        )
        .setSecondaryLabelTextContent(
            Text.Builder(context, sleepQuality.getTitle())
                .setTypography(Typography.TYPOGRAPHY_CAPTION1)
                .setColor(argb(TileColors.White))
                .build()
        )
        .setContent(
            Spannable.Builder()
                .addSpan(
                    SpanText.Builder()
                        .setText(sleepTime.hours.toString())
                        .setFontStyle(
                            FontStyle.PrimaryFontSize.getBuilder()
                        )
                        .build()
                )
                .addSpan(
                    SpanText.Builder()
                        .setText("h")
                        .setFontStyle(
                            FontStyle.SecondaryFontSize.getBuilder()
                        )
                        .build()
                )
                .addSpan(
                    SpanText.Builder()
                        .setText(" ")
                        .build()
                )
                .addSpan(
                    SpanText.Builder()
                        .setText(sleepTime.minutes.toString())
                        .setFontStyle(
                            FontStyle.PrimaryFontSize.getBuilder()
                        )
                        .build()
                )
                .addSpan(
                    SpanText.Builder()
                        .setText("m")
                        .setFontStyle(
                            FontStyle.SecondaryFontSize.getBuilder()
                        )
                        .build()
                )
                .build()
        )
}

full code available on github

What I've tried

  • Wrapping the tile layout in a LayoutElementBuilders.Box.Builder, then adding the clickable modifier, but that makes the tile blank

What I want

I want a tile with a progress bar that when a user clicks on the tile, it launches the main activity (app)


Solution

  • This part that you've tried:

    Wrapping the tile layout in a LayoutElementBuilders.Box.Builder, then adding the clickable modifier, but that makes the tile blank

    is good. You're seeing a blank tile because Box by default has width and height set to wrap, but EdgeContentLayout's dimension is expand (i.e. when setting an expand child to a parent that wraps, that element won't be shown), to fix that you could do something like:

    import androidx.wear.protolayout.DimensionBuilders.expand
    
    Box.Builder()
      .setWidth(expand())
      .setHeight(expand())
      .addContent(yourEdgeContentLayout)
      .build()
    

    Hope this helps!