Refactor tile layout to show recent cases
The tile layout has been refactored to display a list of recent cases instead of the currently active case. This change includes: - Removing the "current case" view and its associated logic. - Implementing a new `caseListContent` function to render a list of up to 8 recent cases. - Modifying the main `buildTileLayout` to integrate the new case list and a prominent "Neu" (New) button. - Updating the styling and click handling to reflect the new layout and functionality.
This commit is contained in:
@@ -14,8 +14,8 @@ private const val RESOURCES_VERSION = "1"
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Serves the single Doctate Tile. Reads a non-blocking snapshot from
|
* Serves the single Doctate Tile. Reads a non-blocking snapshot from
|
||||||
* [DoctateApp.caseStore] and renders the "glance at current case" view with
|
* [DoctateApp.caseStore] and renders the recent-cases list with an
|
||||||
* three tap regions. Refreshes are triggered externally via
|
* edge-style "Neu" button. Refreshes are triggered externally via
|
||||||
* `TileService.getUpdater(ctx).requestUpdate(...)` — the App wires that up
|
* `TileService.getUpdater(ctx).requestUpdate(...)` — the App wires that up
|
||||||
* to the case-store flow.
|
* to the case-store flow.
|
||||||
*/
|
*/
|
||||||
@@ -24,9 +24,9 @@ class DoctateTileService : TileService() {
|
|||||||
requestParams: RequestBuilders.TileRequest,
|
requestParams: RequestBuilders.TileRequest,
|
||||||
): ListenableFuture<TileBuilders.Tile> {
|
): ListenableFuture<TileBuilders.Tile> {
|
||||||
val app = applicationContext as DoctateApp
|
val app = applicationContext as DoctateApp
|
||||||
val current = app.caseStore.currentCase
|
val cases = app.caseStore.casesFlow.value
|
||||||
val layout = LayoutElementBuilders.Layout.Builder()
|
val layout = LayoutElementBuilders.Layout.Builder()
|
||||||
.setRoot(buildTileLayout(packageName, current))
|
.setRoot(buildTileLayout(packageName, cases))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val timeline = TimelineBuilders.Timeline.Builder()
|
val timeline = TimelineBuilders.Timeline.Builder()
|
||||||
|
|||||||
@@ -4,15 +4,11 @@ import androidx.wear.protolayout.ActionBuilders
|
|||||||
import androidx.wear.protolayout.ColorBuilders.argb
|
import androidx.wear.protolayout.ColorBuilders.argb
|
||||||
import androidx.wear.protolayout.DimensionBuilders.dp
|
import androidx.wear.protolayout.DimensionBuilders.dp
|
||||||
import androidx.wear.protolayout.DimensionBuilders.expand
|
import androidx.wear.protolayout.DimensionBuilders.expand
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders
|
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders.Box
|
import androidx.wear.protolayout.LayoutElementBuilders.Box
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders.Column
|
import androidx.wear.protolayout.LayoutElementBuilders.Column
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders.FontStyle
|
import androidx.wear.protolayout.LayoutElementBuilders.FontStyle
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER
|
import androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_CENTER
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_END
|
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders.HORIZONTAL_ALIGN_START
|
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
|
import androidx.wear.protolayout.LayoutElementBuilders.LayoutElement
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders.Row
|
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders.Spacer
|
import androidx.wear.protolayout.LayoutElementBuilders.Spacer
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders.Text
|
import androidx.wear.protolayout.LayoutElementBuilders.Text
|
||||||
import androidx.wear.protolayout.LayoutElementBuilders.VERTICAL_ALIGN_BOTTOM
|
import androidx.wear.protolayout.LayoutElementBuilders.VERTICAL_ALIGN_BOTTOM
|
||||||
@@ -22,7 +18,6 @@ import androidx.wear.protolayout.ModifiersBuilders.Clickable
|
|||||||
import androidx.wear.protolayout.ModifiersBuilders.Corner
|
import androidx.wear.protolayout.ModifiersBuilders.Corner
|
||||||
import androidx.wear.protolayout.ModifiersBuilders.Modifiers
|
import androidx.wear.protolayout.ModifiersBuilders.Modifiers
|
||||||
import androidx.wear.protolayout.ModifiersBuilders.Padding
|
import androidx.wear.protolayout.ModifiersBuilders.Padding
|
||||||
import androidx.wear.protolayout.TypeBuilders.StringProp
|
|
||||||
import com.doctate.watch.domain.CaseEntry
|
import com.doctate.watch.domain.CaseEntry
|
||||||
import com.doctate.watch.domain.displayText
|
import com.doctate.watch.domain.displayText
|
||||||
import com.doctate.watch.domain.isManual
|
import com.doctate.watch.domain.isManual
|
||||||
@@ -30,101 +25,98 @@ import com.doctate.watch.presentation.NavCommand
|
|||||||
|
|
||||||
private const val CLICK_ID_LIST = "list"
|
private const val CLICK_ID_LIST = "list"
|
||||||
private const val CLICK_ID_NEW = "new"
|
private const val CLICK_ID_NEW = "new"
|
||||||
private const val CLICK_ID_CONTINUE = "continue"
|
|
||||||
|
|
||||||
/** Build the root layout element for the Tile. Returns the "current case" view or an
|
private const val MAX_VISIBLE_CASES = 8
|
||||||
* empty-state view depending on whether any case exists in [CaseStoreStub]. */
|
|
||||||
internal fun buildTileLayout(packageName: String, current: CaseEntry?): LayoutElement {
|
/**
|
||||||
val column = Column.Builder()
|
* Build the root Tile layout: a vertical stack with the most recent cases
|
||||||
|
* pinned just above an edge-style "Neu" button. The whole list region is a
|
||||||
|
* single tap target — every line routes to the case list, regardless of
|
||||||
|
* which case was tapped. Up to [MAX_VISIBLE_CASES] cases are shown; the
|
||||||
|
* newest sits at the bottom (next to the edge button).
|
||||||
|
*/
|
||||||
|
internal fun buildTileLayout(packageName: String, cases: List<CaseEntry>): LayoutElement {
|
||||||
|
return Column.Builder()
|
||||||
.setWidth(expand())
|
.setWidth(expand())
|
||||||
.setHeight(expand())
|
.setHeight(expand())
|
||||||
.setHorizontalAlignment(HORIZONTAL_ALIGN_CENTER)
|
.setHorizontalAlignment(HORIZONTAL_ALIGN_CENTER)
|
||||||
.addContent(topRow(packageName))
|
.addContent(Spacer.Builder().setHeight(dp(10f)).build())
|
||||||
.addContent(Spacer.Builder().setHeight(dp(8f)).build())
|
.addContent(caseListContent(packageName, cases))
|
||||||
.addContent(middleContent(packageName, current))
|
.addContent(Spacer.Builder().setHeight(dp(4f)).build())
|
||||||
.addContent(Spacer.Builder().setHeight(dp(8f)).build())
|
|
||||||
.addContent(bottomNewButton(packageName))
|
.addContent(bottomNewButton(packageName))
|
||||||
.build()
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun caseListContent(packageName: String, cases: List<CaseEntry>): LayoutElement {
|
||||||
|
val column = Column.Builder()
|
||||||
|
.setWidth(expand())
|
||||||
|
.setHorizontalAlignment(HORIZONTAL_ALIGN_CENTER)
|
||||||
|
|
||||||
|
if (cases.isEmpty()) {
|
||||||
|
column.addContent(
|
||||||
|
Text.Builder()
|
||||||
|
.setText("Noch kein Fall heute")
|
||||||
|
.setFontStyle(FontStyle.Builder().setSize(sp(14f)).build())
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val visible = cases.take(MAX_VISIBLE_CASES).asReversed()
|
||||||
|
visible.forEachIndexed { index, case ->
|
||||||
|
if (index > 0) {
|
||||||
|
column.addContent(Spacer.Builder().setHeight(dp(2f)).build())
|
||||||
|
}
|
||||||
|
column.addContent(
|
||||||
|
Text.Builder()
|
||||||
|
.setText(formatLine(case))
|
||||||
|
.setMaxLines(1)
|
||||||
|
.setFontStyle(FontStyle.Builder().setSize(sp(13f)).build())
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Box.Builder()
|
return Box.Builder()
|
||||||
.setWidth(expand())
|
.setWidth(expand())
|
||||||
.setHeight(expand())
|
.setHeight(expand())
|
||||||
.setVerticalAlignment(VERTICAL_ALIGN_CENTER)
|
.setHorizontalAlignment(HORIZONTAL_ALIGN_CENTER)
|
||||||
.addContent(column)
|
.setVerticalAlignment(VERTICAL_ALIGN_BOTTOM)
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun topRow(packageName: String): LayoutElement {
|
|
||||||
val faelle = Text.Builder()
|
|
||||||
.setText("☰ Fälle")
|
|
||||||
.setFontStyle(FontStyle.Builder().setSize(sp(14f)).build())
|
|
||||||
.setModifiers(
|
.setModifiers(
|
||||||
Modifiers.Builder()
|
Modifiers.Builder()
|
||||||
.setClickable(launchClickable(CLICK_ID_LIST, packageName, NavCommand.OPEN_LIST))
|
.setClickable(launchClickable(CLICK_ID_LIST, packageName, NavCommand.OPEN_LIST))
|
||||||
.setPadding(
|
|
||||||
Padding.Builder().setStart(dp(8f)).setEnd(dp(8f))
|
|
||||||
.setTop(dp(4f)).setBottom(dp(4f)).build(),
|
|
||||||
)
|
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.build()
|
.addContent(column.build())
|
||||||
// Row has no horizontal-alignment prop; use a Box with END alignment instead.
|
|
||||||
return Box.Builder()
|
|
||||||
.setWidth(expand())
|
|
||||||
.setHorizontalAlignment(HORIZONTAL_ALIGN_END)
|
|
||||||
.setVerticalAlignment(VERTICAL_ALIGN_CENTER)
|
|
||||||
.addContent(faelle)
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun middleContent(packageName: String, current: CaseEntry?): LayoutElement {
|
/** "👤 text" if doctor-authored, plain text if auto-generated, "…" placeholder otherwise. */
|
||||||
val label = when {
|
private fun formatLine(case: CaseEntry): String {
|
||||||
current == null -> "Noch kein Fall heute"
|
val text = case.oneliner.displayText() ?: return "…"
|
||||||
else -> current.oneliner.displayText()
|
return if (case.oneliner.isManual()) "👤 $text" else text
|
||||||
?.let { if (current.oneliner.isManual()) "👤 $it" else it }
|
|
||||||
?: "…"
|
|
||||||
}
|
|
||||||
val textBuilder = Text.Builder()
|
|
||||||
.setText(label)
|
|
||||||
.setMaxLines(3)
|
|
||||||
.setFontStyle(FontStyle.Builder().setSize(sp(16f)).build())
|
|
||||||
|
|
||||||
val modifiersBuilder = Modifiers.Builder()
|
|
||||||
.setPadding(
|
|
||||||
Padding.Builder().setStart(dp(12f)).setEnd(dp(12f))
|
|
||||||
.setTop(dp(6f)).setBottom(dp(6f)).build(),
|
|
||||||
)
|
|
||||||
if (current != null) {
|
|
||||||
modifiersBuilder.setClickable(
|
|
||||||
launchClickable(
|
|
||||||
CLICK_ID_CONTINUE,
|
|
||||||
packageName,
|
|
||||||
NavCommand.OPEN_CONTINUE,
|
|
||||||
caseId = current.caseId,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
textBuilder.setModifiers(modifiersBuilder.build())
|
|
||||||
return textBuilder.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bottomNewButton(packageName: String): LayoutElement {
|
private fun bottomNewButton(packageName: String): LayoutElement {
|
||||||
val text = Text.Builder()
|
val text = Text.Builder()
|
||||||
.setText("● Neu")
|
.setText("● Neu")
|
||||||
.setFontStyle(FontStyle.Builder().setSize(sp(14f)).build())
|
.setFontStyle(
|
||||||
|
FontStyle.Builder()
|
||||||
|
.setSize(sp(14f))
|
||||||
|
.setColor(argb(0xFF0A305F.toInt()))
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
.build()
|
.build()
|
||||||
return Box.Builder()
|
return Box.Builder()
|
||||||
.setWidth(expand())
|
.setWidth(expand())
|
||||||
.setHeight(dp(36f))
|
.setHeight(dp(48f))
|
||||||
.setVerticalAlignment(VERTICAL_ALIGN_BOTTOM)
|
.setVerticalAlignment(VERTICAL_ALIGN_CENTER)
|
||||||
.setHorizontalAlignment(HORIZONTAL_ALIGN_CENTER)
|
.setHorizontalAlignment(HORIZONTAL_ALIGN_CENTER)
|
||||||
.setModifiers(
|
.setModifiers(
|
||||||
Modifiers.Builder()
|
Modifiers.Builder()
|
||||||
.setClickable(launchClickable(CLICK_ID_NEW, packageName, NavCommand.OPEN_NEW))
|
.setClickable(launchClickable(CLICK_ID_NEW, packageName, NavCommand.OPEN_NEW))
|
||||||
.setBackground(
|
.setBackground(
|
||||||
Background.Builder()
|
Background.Builder()
|
||||||
.setColor(argb(0xFF1F1F1F.toInt()))
|
.setColor(argb(0xFFAAC7FF.toInt()))
|
||||||
.setCorner(Corner.Builder().setRadius(dp(18f)).build())
|
.setCorner(Corner.Builder().setRadius(dp(24f)).build())
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.setPadding(
|
.setPadding(
|
||||||
|
|||||||
Reference in New Issue
Block a user