Files
kryz-go-flutter/PROJECT_OVERVIEW.md
T

10 KiB

KRYZ Go! — Project Overview

Mobile streaming client for KRYZ LP-FM 98.5 Mariposa community radio station.

Tech Stack

Layer Technology
Framework Flutter (Dart SDK ^3.11.5)
Audio playback just_audio + just_audio_background + audio_service
Network http (HTTP client for live-metadata API)
Date/time intl + timezone
Casting (Android) Google Cast framework (play-services-cast-framework:22.1.0) via Kotlin platform channel
Casting (iOS) AirPlay via Swift platform channel
Android Auto MediaBrowserService (Kotlin) + androidx.media:1.7.0
iOS CarPlay Custom CarPlaySceneDelegate (Swift)
Whitelabel build Python 3 + Pillow + cairosvg (build_whitelabel.py)
Development VS Code Dev Container (Docker), flutter_lints
Testing flutter_test (widget + unit tests)

Target platforms: Android, iOS, macOS, Windows, Linux, Web.

Key Dependencies

  • just_audio (^0.10.5) — core audio playback engine
  • audio_service (^0.18.18) — media session integration (lock screen, notifications)
  • audio_session (^0.2.2) — audio focus / duck configuration
  • just_audio_background (^0.0.1-beta.17) — background audio on mobile
  • http (^1.5.0) — HTTP client for live-info metadata polling
  • timezone (^0.10.1) — timezone-aware datetime handling
  • intl (^0.20.2) — date/time formatting utilities
  • webview_flutter (^4.13.0) — embedded WebView (declared but unused in current code path)
  • cupertino_icons (^1.0.8) — iOS-style icon font

Native Dependencies (Android)

  • play-services-cast-framework:22.1.0
  • androidx.mediarouter:1.7.0
  • androidx.media:1.7.0
  • Java/Kotlin target: Java 17

Directory Structure

kryz-go-flutter/
├── lib/                              # Dart application source
│   ├── main.dart                     # Entry point + MaterialApp + MainPage UI
│   ├── kryz_audio_handler.dart       # Background audio handler (audio_service)
│   ├── streaming_audio_business_object.dart  # Audio playback controller
│   ├── casting_business_object.dart  # Cast/AirPlay state machine (platform channels)
│   ├── live_info_business_object.dart # Live metadata poller + data models
│   ├── live_info_panel.dart          # "Live Studio Feed" widget (now/next cards)
│   ├── track_card.dart               # Individual track display widget
│   ├── show_card.dart                # Show schedule display widget
│   ├── recently_played_list.dart     # Recently-played list widget (unused in current UI)
│   ├── app_theme_business_object.dart # Theme colors + light/dark ThemeData
│   ├── time_formatter.dart           # Timestamp parsing + timezone conversion
│   └── generated/                    # Auto-generated by build_whitelabel.py (not committed)
│       ├── app_config.dart           # Compile-time constants
│       └── theme_colors.dart         # Generated AppThemeColors
├── android/                          # Android native project
│   └── app/src/main/
│       ├── kotlin/.../MainActivity.kt      # Cast framework + MethodChannel bridge
│       ├── kotlin/.../KryzAutoMediaBrowserService.kt  # Android Auto media browser
│       ├── kotlin/.../CastOptionsProvider.kt          # Cast receiver config
│       ├── AndroidManifest.xml                   # App manifest + permissions
│       └── res/values/automotive_colors.xml      # Android Auto theming
├── ios/                              # iOS native project
│   └── Runner/
│       ├── AppDelegate.swift                     # Flutter engine + CarPlay setup
│       ├── SceneDelegate.swift                   # Scene lifecycle
│       └── CarPlaySceneDelegate.swift            # CarPlay template
├── macos/                            # macOS native project (Swift)
├── linux/                            # Linux native project (C++)
├── windows/                          # Windows native project (C++)
├── web/                              # Web entry point (index.html, manifest.json)
├── assets/icon/                      # App icon source
│   └── app_icon.png
├── test/                             # Dart tests
│   ├── widget_test.dart              # Basic widget smoke test
│   └── time_formatter_test.dart      # Timezone + formatting unit tests
├── build_whitelabel.py               # Whitelabel build script (Python)
├── requirements.txt                  # Python deps (Pillow, cairosvg)
├── pubspec.yaml                      # Flutter package manifest + Dart deps
├── analysis_options.yaml             # Dart linter config
├── README.md                         # Brief project docs
├── PROJECT_STRUCTURE.md              # Detailed architecture analysis
├── PROJECT_OVERVIEW.md               # This file — setup & onboarding guide
├── .devcontainer/                    # VS Code Dev Container config
└── theme.json                        # Station theme config (colors, images, URLs)

Setup Instructions

Prerequisites

  • Flutter SDK (compatible with Dart ^3.11.5)
  • Python 3.10+ (for whitelabel builds)
  • Android Studio / Xcode for mobile builds (as applicable)
  • Docker (optional, for VS Code Dev Container workflow)

Quick Start

# 1. Clone the repository
git clone <repo-url> kryz-go-flutter
cd kryz-go-flutter

# 2. Check out the development branch
git checkout setup/project-onboarding

# 3. Install Flutter dependencies
flutter pub get

# 4. Generate whitelabel config (required before running)
#    You must have a theme.json in the project root.
pip install -r requirements.txt
python3 build_whitelabel.py

# 5. Run the app
flutter run                    # on connected device
flutter run -d chrome          # for web debugging
flutter run -d macos           # for macOS desktop

Whitelabel Build

The app is fully re-brandable via a single theme.json file. This defines station name, callsign, frequency, stream URL, metadata endpoint, color palette, and embedded images.

# Full build (generates Dart configs, patches native manifests, generates icons)
python3 build_whitelabel.py --theme theme.json

# Dry run (preview what will change)
python3 build_whitelabel.py --theme theme.json --dry-run

# Skip native tool steps (Pillow/cairosvg not installed)
python3 build_whitelabel.py --no-tools

The script generates:

  • lib/generated/app_config.dart — compile-time constants
  • lib/generated/theme_colors.dart — light + dark theme colors
  • assets/icon/app_icon.png — from embedded logo
  • Patches to AndroidManifest.xml, Info.plist, automotive_colors.xml, and Kotlin sources

Dev Container

Open the project in VS Code with the Dev Container extension. It provisions a Docker environment with Flutter and Python pre-installed. Post-create steps run flutter pub get, set up the Python venv, and install dependencies.

Naming and Icons

# Rename the app across all platforms
dart run flutter_name_manager:rename_app --name "KRYZ Go!"

# Regenerate launcher icons
dart run flutter_launcher_icons

Known Issues

  1. No Go backend despite repo name — The repository is named kryz-go-flutter but contains only Flutter/Dart frontend code. The "Go" refers to the app brand name "KRYZ Go!", not the Go programming language.

  2. webview_flutter declared but unused — The webview_flutter dependency is in pubspec.yaml but is not referenced anywhere in the current code path. Safe to remove if not planned for future use.

  3. recently_played_list.dart exists but is not wired into the UI — The widget is built but not currently used in the main page layout.

  4. Minimal test coverage — Only two test files exist (widget_test.dart, time_formatter_test.dart). No integration tests, mock API tests, or casting-specific tests are present.

  5. Hardcoded stream URL in audio handler — The stream URL is loaded from AppConfig (generated by whitelabel), but the fallback and error-handling paths for stream disconnection are limited.

  6. Beta dependencyjust_audio_background is on a beta version (^0.0.1-beta.17), which may have stability issues on certain Android/iOS versions.

  7. Generated files not committedlib/generated/ is not in the repository. Developers must run build_whitelabel.py with a theme.json before the app will compile.

  8. Android Auto cold-start race condition — The startup sequence uses unawaited() calls that can cause issues if background media services initialize after an Android Auto cold start. A comment in main.dart acknowledges this.

Architecture Overview

Business Object Pattern

The codebase uses a "Business Object" naming convention for reactive state singletons (all extend ChangeNotifier):

Business Object Responsibility
StreamingAudioBusinessObject Audio playback state (play/stop/buffering)
CastingBusinessObject Cast/AirPlay connection state
LiveInfoBusinessObject Live metadata + recently-played history
AppThemeBusinessObject Theme color definitions

Data Flow

[Radio Station API] --HTTP--> [LiveInfoBusinessObject] --poll--> [UI: LiveInfoPanel]
                                                     --> [AudioHandler: updateMetadata]
                                                     --> [RecentlyPlayedList (runtime)]

[Station Stream URL] --stream--> [just_audio Player] --state--> [StreamingAudioBO] --> [UI: Play/Stop]

[Cast/AirPlay] <--platform channels--> [CastingBO] --> [UI: Cast button]
     |                                         |
     +--native--> MainActivity.kt / iOS native  +--> [stops local audio on handoff]

Metadata Polling

  • Polls the live-info API every 20 seconds
  • Endpoint configured via theme.json or KRYZ_LIVE_INFO_URL env var
  • Response parsed into LiveInfoSnapshot: current/next/previous tracks, show schedule, timezone
  • Maintains a deduplicated "recently played" list (max 40 items) at runtime