-
Notifications
You must be signed in to change notification settings - Fork 10
TV Client Application
This document covers the overall architecture, structure, and configuration of the Effective Office TV client application. This is a Compose Multiplatform application that serves as the primary interface for corporate information display on office televisions, allowing users to view employee stories, upcoming events, photo slideshows, and team statistics through automated content rotation with remote control navigation.
| Component | Technology / Library | Notes |
|---|---|---|
| Language / Runtime | Kotlin 2.1.21, JVM 17 | Aligns with shared Gradle toolchain |
| UI Framework | Jetpack Compose 1.8.1 + Material 3 | Multiplatform Compose with Material 3 components |
| Dependency Injection | Koin 4.1.0 | Dependency injection for feature screens |
| Networking | Ktor 3.1.3 + kotlinx.serialization | Fetches data from backend APIs |
| Image / Media | Coil 3.2.0, ZXing 3.5.1 | Image loading, QR code rendering, avatars |
| Navigation | Decompose 3.3.0 | Component-based navigation system |
| Concurrency | Coroutines 1.10.2 + Flow | Reactive streams and asynchronous processing |
| Build Tooling | Gradle 8.x, Kotlin Compose Compiler 2.1.21 | Project build and Compose compiler setup |
The TV application is built around an autoplay engine that continuously cycles through content categories. Each content type (Stories, Photos, Events) implements the AutoplayFeature contract, providing standardized lifecycle callbacks, navigation handling, and content rendering. The AutoplayComponent orchestrates transitions between screens, handles remote control intents (play/pause, next/previous),), and manages the overall playback state
| Source | Ingestion Path | Purpose |
|---|---|---|
| Notion databases | Ktor client + DTOs | Birthdays, anniversaries, new hires |
| Duolingo API | Ktor + serialization | Weekly XP leaderboard and streak ("shock mode") |
| Leader ID API | Ktor client | Daily/monthly event promotion |
| Synology Photos API | Ktor client | Curated photo slideshow |
| Clockify API | Ktor client | Sport activity tracking and statistics |
The data flows through a StoriesDataProvider that aggregates responses from multiple repositories, normalizes them into domain models, and emits them as Flow streams consumed by the UI components. Each pipeline includes error handling, local caching, and automatic refresh mechanisms to ensure content remains current without manual intervention
- AppActivity / RootComponent: Bootstraps Koin dependency injection, initializes the application environment, and routes between Welcome, Menu, and Autoplay surfaces using Decompose navigation. The navigation flow follows: Welcome → Menu (category selection) → Autoplay (slideshow)
- Remote Control Layer: All focusable components expose focus states for D-pad navigation using FocusRequester and onFocusChanged callbacks. The playback controls are toggled via remote control buttons (Space/Center for pause/play, Left/Right for navigation, Escape/Back for exit)
- Autoplay Manager: maintains queue definitions (e.g., Stories → Leader ID → Photos) stored in backend configuration or local overrides. Supports seamless crossfade transitions and per-screen duration settings.
The autoplay system is the core innovation that enables seamless content rotation:
Key Android configuration extracted from tvApp/build.gradle.kts:
androidTarget()
jvm("desktop")
android {
namespace = "band.effective.office.tv"
compileSdk = 34
defaultConfig {
applicationId = "band.effective.office.tv"
minSdk = 24
targetSdk = 33
buildConfigField("String", "backendApiUrl", localProperties["backendApiUrl"].toString())
buildConfigField("String", "backendApiKey", localProperties["backendApiKey"].toString())
}
buildFeatures { compose = true }
}
Build config keys are injected from local.properties so the same artifact can target staging or production signage networks without code changes.
| Module Category | Modules | Dependencies |
|---|---|---|
| Application | composeApp |
All feature and core modules |
| Features |
feature:menu, feature:photos, feature:stories, feature:events
|
Core modules, shared module |
| Core |
core:ui, core:domain, core:data
|
External libraries, no cross-dependencies |
The RootComponent serves as the application's primary coordinator, managing:
-
Navigation State: Stack navigation between Welcome, Menu, and Autoplay surfaces
-
Component Lifecycle: Creation and destruction of child components using Decompose
-
Initial Configuration: Routes to Welcome screen on first launch, then Menu for category selection
val childStack: Value<ChildStack<*, Child>> = childStack(
source = navigation,
initialConfiguration = Config.Welcome,
handleBackButton = true,
childFactory = ::createChild,
)
- Stories (Birthdays, Anniversaries, New Hires): Pulls employee attributes (name, avatar, congratulatory text) from Notion.
- Duolingo Leaderboards: Aggregates XP totals and streak lengths, highlights daily winners, and animates their avatars.
- Leader ID Events: Shows upcoming events, presenters, and an auto-generated QR linking to registration. Content refreshes daily.
- Photo Slide Show: Streams Synology albums, supports album pinning and remote-triggered updates.
- Autoplay: Operators can define ordered rotations, durations, and whether a screen is standalone or part of autoplay.
Add the following keys to local.properties (values provided via internal secrets storage):
| Key | Purpose |
|---|---|
backendApiUrl |
Effective Office backend endpoint for configs |
backendApiKey |
API key to fetch playlists, toggles, and shared assets |
Additional credentials (Notion, Duolingo, Leader ID, Synology) are stored server-side; the TV app consumes proxied APIs to avoid storing sensitive secrets on devices.