Adds app whitelabling feature from kmountainflower site dumps

This commit is contained in:
Your Name
2026-07-07 10:00:16 +00:00
parent b8d624343b
commit ea1bc88a70
54 changed files with 585 additions and 71 deletions
+4 -31
View File
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'generated/theme_colors.dart';
class AppThemeColors {
const AppThemeColors({
required this.primarySeed,
@@ -33,37 +35,8 @@ class AppThemeColors {
}
class AppThemeBusinessObject {
static const AppThemeColors lightColors = AppThemeColors(
primarySeed: Color(0xFFE36A18),
secondarySeed: Color(0xFF1663C7),
scaffoldGradientStart: Color(0xFFFFF3E3),
scaffoldGradientEnd: Color(0xFFE6F2FF),
shellSurface: Color(0xF7FFFFFF),
shellBorder: Color(0x1A1A4D86),
accent: Color(0xFF0F5DB6),
webViewBorder: Color(0x261A4D86),
webViewBackground: Color(0xFFFFFFFF),
controlsGradientStart: Color(0xFFFFB465),
controlsGradientEnd: Color(0xFF69ADFF),
controlsShadow: Color(0x24114B7A),
statusText: Color(0xFF13243D),
);
static const AppThemeColors darkColors = AppThemeColors(
primarySeed: Color(0xFFFF9D59),
secondarySeed: Color(0xFF76B8FF),
scaffoldGradientStart: Color(0xFF0F1C30),
scaffoldGradientEnd: Color(0xFF31190C),
shellSurface: Color(0xD9162438),
shellBorder: Color(0x3366AAFF),
accent: Color(0xFFFFBC7E),
webViewBorder: Color(0x3378B7FF),
webViewBackground: Color(0xFF0C1420),
controlsGradientStart: Color(0xFF275792),
controlsGradientEnd: Color(0xFF9A4F1F),
controlsShadow: Color(0x44000000),
statusText: Color(0xFFF4F7FF),
);
static const AppThemeColors lightColors = GeneratedThemeColors.lightColors;
static const AppThemeColors darkColors = GeneratedThemeColors.darkColors;
static bool isDarkMode(BuildContext context) {
return MediaQuery.platformBrightnessOf(context) == Brightness.dark;
+6 -5
View File
@@ -6,6 +6,7 @@ import 'package:audio_session/audio_session.dart';
import 'package:http/http.dart' as http;
import 'package:just_audio/just_audio.dart';
import 'generated/app_config.dart';
import 'live_info_business_object.dart';
class KryzAudioHandler extends BaseAudioHandler with SeekHandler {
@@ -16,16 +17,16 @@ class KryzAudioHandler extends BaseAudioHandler with SeekHandler {
);
}
static const String streamUrl = 'https://kryz.out.airtime.pro/kryz_a';
static const String streamUrl = AppConfig.streamUrl;
static const String mediaId = 'kryz-live-stream';
static const String _liveInfoEndpoint = 'http://kryz.airtime.pro/api/live-info';
static const String _liveInfoEndpoint = AppConfig.metadataUrl ?? '';
static const Duration _liveInfoTimeout = Duration(seconds: 8);
static const Duration _metadataRefreshInterval = Duration(seconds: 20);
static const MediaItem _defaultMediaItem = MediaItem(
id: mediaId,
title: 'KRYZ Live Stream',
artist: 'KRYZ Radio',
title: AppConfig.defaultTitle,
artist: AppConfig.defaultArtist,
album: 'Live',
extras: <String, dynamic>{
'isLive': true,
@@ -52,7 +53,7 @@ class KryzAudioHandler extends BaseAudioHandler with SeekHandler {
MediaItem(
id: mediaId,
title: current.displayTitle,
artist: subtitle.isEmpty ? 'KRYZ Radio' : subtitle,
artist: subtitle.isEmpty ? AppConfig.defaultArtist : subtitle,
album: 'Live',
extras: <String, dynamic>{
'isLive': true,
+24 -6
View File
@@ -4,17 +4,31 @@ import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'generated/app_config.dart';
class LiveInfoBusinessObject extends ChangeNotifier {
LiveInfoBusinessObject({
this.endpoint = const String.fromEnvironment(
'KRYZ_LIVE_INFO_URL',
defaultValue: 'http://kryz.airtime.pro/api/live-info',
),
String? endpoint,
int refreshIntervalSeconds = 20,
this.requestTimeout = const Duration(seconds: 8),
}) : _refreshIntervalSeconds = refreshIntervalSeconds;
}) : _endpointOverride = endpoint,
_refreshIntervalSeconds = refreshIntervalSeconds;
final String endpoint;
/// Explicit endpoint passed at construction, if any.
final String? _endpointOverride;
/// The effective polling endpoint: construction override > env var > AppConfig.
String get endpoint {
final override = _endpointOverride;
if (override != null && override.isNotEmpty) {
return override;
}
const envUrl = String.fromEnvironment('KRYZ_LIVE_INFO_URL', defaultValue: '');
if (envUrl.isNotEmpty) {
return envUrl;
}
return AppConfig.metadataUrl ?? '';
}
final Duration requestTimeout;
Timer? _pollTimer;
@@ -34,6 +48,10 @@ class LiveInfoBusinessObject extends ChangeNotifier {
List<RecentPlayedItem> get recentPlayed => List.unmodifiable(_recentPlayed);
Future<void> start() async {
// Skip polling if no endpoint is configured
if (endpoint.isEmpty && (AppConfig.metadataUrl ?? '').isEmpty) {
return;
}
await refresh();
_restartTimer();
}
+3 -2
View File
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'generated/app_config.dart';
import 'app_theme_business_object.dart';
import 'casting_business_object.dart';
import 'live_info_business_object.dart';
@@ -53,7 +54,7 @@ class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'KRYZ Go!',
title: AppConfig.appName,
theme: AppThemeBusinessObject.lightTheme(),
darkTheme: AppThemeBusinessObject.darkTheme(),
themeMode: ThemeMode.system,
@@ -394,7 +395,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
const SizedBox(width: 14),
Expanded(
child: Text(
'KRYZ Go!',
AppConfig.appName,
textAlign: TextAlign.center,
style: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.w900,
+6 -5
View File
@@ -3,6 +3,7 @@ import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/foundation.dart';
import 'generated/app_config.dart';
import 'kryz_audio_handler.dart';
import 'live_info_business_object.dart';
@@ -38,7 +39,7 @@ class StreamingAudioBusinessObject extends ChangeNotifier {
builder: () => KryzAudioHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.kryzgoflutter.audio.channel',
androidNotificationChannelName: 'KRYZ Audio Playback',
androidNotificationChannelName: '${AppConfig.appName} Audio Playback',
androidNotificationOngoing: true,
androidStopForegroundOnPause: true,
),
@@ -60,8 +61,8 @@ class StreamingAudioBusinessObject extends ChangeNotifier {
StreamingPlaybackStatus _playbackStatus = StreamingPlaybackStatus.stopped;
bool _isDisposed = false;
bool _isAttached = false;
String _currentTitle = 'KRYZ Live Stream';
String _currentSubtitle = 'KRYZ Radio';
String _currentTitle = AppConfig.defaultTitle;
String _currentSubtitle = AppConfig.defaultArtist;
StreamingPlaybackStatus get playbackStatus => _playbackStatus;
bool get isPlaying => _playbackStatus == StreamingPlaybackStatus.playing;
@@ -145,9 +146,9 @@ class StreamingAudioBusinessObject extends ChangeNotifier {
return;
}
final title = item.title.trim().isEmpty ? 'KRYZ Live Stream' : item.title;
final title = item.title.trim().isEmpty ? AppConfig.defaultTitle : item.title;
final subtitle =
(item.artist ?? '').trim().isEmpty ? 'KRYZ Radio' : item.artist!.trim();
(item.artist ?? '').trim().isEmpty ? AppConfig.defaultArtist : item.artist!.trim();
if (title == _currentTitle && subtitle == _currentSubtitle) {
return;