Files
kryz-go-flutter/PROJECT_STRUCTURE.md
T
robot f3393348f7 docs: add project structure and architecture analysis report
- Map out complete folder structure with descriptions
- Identify Flutter+Dart as primary stack (no Go backend)
- Document all Dart source modules and their responsibilities
- Document native platform code (Android Cast, iOS CarPlay)
- Describe whitelabel build system (build_whitelabel.py + theme.json)
- Document data flow between modules
- List dependencies, dev environment, and test coverage
2026-07-31 23:59:33 +00:00

10 KiB

KRYZ Go! — Project Structure & Architecture

Analysis of kryz-go-flutter on the setup/project-onboarding branch.

1. Overview

KRYZ Go! is a multi-platform Flutter mobile application for the KRYZ LP-FM 98.5 Mariposa community radio station. It streams live audio from the station, displays real-time metadata (now playing, upcoming show), and supports casting to external displays (Chromecast, Android Auto, iOS CarPlay/AirPlay).

Primary stack: Flutter + Dart (frontend only). There is no Go backend in this repository — the project name "kryz-go-flutter" refers to the app name "KRYZ Go!", not a Go language backend.

Target platforms: Android (including Android Auto), iOS (including CarPlay), macOS, Windows, Linux, and Web.

2. Folder Structure

kryz-go-flutter/
├── lib/                              # Dart application source
│   ├── main.dart                     # App 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 (station name, stream URL, etc.)
│       └── theme_colors.dart         # Generated AppThemeColors (light + dark)
├── 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 for whitelabel script (Pillow, cairosvg)
├── pubspec.yaml                      # Flutter package manifest + Dart deps
├── analysis_options.yaml             # Dart linter config (flutter_lints)
├── README.md                         # Brief project docs
├── .devcontainer/                    # VS Code Dev Container (Dockerfile + JSON)
└── theme.json                        # Station theme config (colors, images, stream URLs)

3. Key Configuration Files

pubspec.yaml

  • Package: kryz_go_flutter
  • SDK: Dart ^3.11.5
  • Version: 1.0.0+1
  • Core dependencies:
    • just_audio (^0.10.5) — audio playback engine
    • just_audio_background (^0.0.1-beta.17) — background audio support
    • audio_service (^0.18.18) — media session integration (notifications, lock screen)
    • audio_session (^0.2.2) — audio focus/Duck configuration
    • webview_flutter (^4.13.0) — embedded WebView (unused in current code path)
    • http (^1.5.0) — HTTP client for live-info API
    • intl (^0.20.2) — date/time formatting
    • timezone (^0.10.1) — timezone-aware datetime handling
  • Dev dependencies:
    • flutter_name_manager — rename app across platforms
    • flutter_launcher_icons — generate platform-specific icons
    • flutter_lints (^6.0.0) — recommended lint rules

Android build.gradle.kts

  • Namespace: com.example.kryz_go_flutter
  • Java/Kotlin target: Java 17
  • Cast dependency: play-services-cast-framework:22.1.0
  • Media router: androidx.mediarouter:1.7.0
  • Media: androidx.media:1.7.0

4. Architecture

Entry Point

lib/main.dart initializes timezone data, then runs MyApp which creates two top-level business objects: StreamingAudioBusinessObject and CastingBusinessObject. The MainPage widget composes the full UI (header card + live info panel + playback controls).

Core Modules

Audio Playback (kryz_audio_handler.dart, streaming_audio_business_object.dart)

  • KryzAudioHandler extends BaseAudioHandler (from audio_service). It manages a just_audio.AudioPlayer instance that streams from a hard-coded streamUrl (loaded from AppConfig).
  • Supports play/stop only (no seek — live stream). Keeps a persistent metadata refresh timer that polls the live-info API every 20 seconds.
  • StreamingAudioBusinessObject wraps the handler, exposing a simplified StreamingPlaybackStatus enum and current track title/artist to the UI.

Casting / Remote Playback (casting_business_object.dart)

  • Communicates with native Android/iOS code through Flutter platform channels (MethodChannel + EventChannel).
  • On Android: integrates with the Google Cast framework (Chromecast) and Android Auto.
  • On iOS: integrates with AirPlay.
  • Supports showing a device picker, starting a cast stream, stopping playback, and disconnecting.
  • Handles the handoff from local playback to remote playback (stops audio_service when cast loads).

Live Metadata (live_info_business_object.dart)

  • Polls a metadata endpoint (configurable via theme.json or KRYZ_LIVE_INFO_URL env var) every 20 seconds.
  • Parses the JSON response into LiveInfoSnapshot containing:
    • current, next, previous track info (title, artist, album, timestamps)
    • currentShow, nextShow (name, description, start/end times)
    • timezone, schedulerTime, sourceEnabled
  • Maintains a deduplicated "recently played" list (max 40 items) built at runtime.

Theme / Whitelabeling (build_whitelabel.py, app_theme_business_object.dart)

  • theme.json (not committed) defines station name, callsign, frequency, stream URL, metadata URL, color palette, and embedded images (logo, hero icon).
  • build_whitelabel.py reads theme.json and generates:
    • lib/generated/app_config.dart — compile-time constants
    • lib/generated/theme_colors.dart — light + dark AppThemeColors
    • assets/icon/app_icon.png — from embedded logo
    • Patches AndroidManifest.xml, Info.plist, automotive_colors.xml, and Kotlin source files
  • This design enables re-branding the entire app for different stations by changing a single JSON file.

Native Platform Code

  • Android (MainActivity.kt): Full Google Cast framework integration with session management, retry logic, media browser service for Android Auto, and platform channel bridging to Flutter.
  • iOS (AppDelegate.swift, CarPlaySceneDelegate.swift): Flutter engine setup and CarPlay template scene delegate.
  • macOS/Linux/Windows: Standard Flutter shell templates.

Data Flow

[Radio Station API] ──HTTP──> [LiveInfoBusinessObject] ──poll──> [UI: LiveInfoPanel]
                                                          └─> [KryzAudioHandler: updateMetadata]
                                                          └─> [RecentlyPlayedList (runtime)]

[Station Stream URL] ──stream──> [just_audio Player] ──state──> [StreamingAudioBusinessObject] ──> [UI: Play/Stop buttons]

[Cast/AirPlay] ◄──platform channels──> [CastingBusinessObject] ──> [UI: Cast button]
     │                                                │
     └──native──> MainActivity.kt / iOS native        └──> [stops local audio on handoff]

5. Business Object Pattern

The codebase uses a "Business Object" naming convention for stateful singletons:

  • StreamingAudioBusinessObject — audio playback state
  • CastingBusinessObject — cast/AirPlay connection state
  • LiveInfoBusinessObject — live metadata + recently-played history
  • AppThemeBusinessObject — theme color definitions

All extend ChangeNotifier, providing reactive state updates to Flutter widgets via addListener/notifyListeners.

6. Development Environment

  • Dev Container: VS Code remote container with Flutter + Python venv. Extensions: Flutter, TypeScript.
  • Post-create: flutter pub get, Python venv + deps install, Claude CLI install.
  • Build: flutter build <platform> or flutter run <platform> for debug.
  • Whitelabel build: python3 build_whitelabel.py [--theme theme.json] [--dry-run] [--no-tools]

7. Test Coverage

  • test/widget_test.dart — basic smoke test verifying app title and playback buttons render.
  • test/time_formatter_test.dart — comprehensive tests for timezone conversion, naive vs. explicit UTC timestamps, and time-window formatting.

No integration tests, mock API tests, or casting-specific tests currently exist.