Adds an opt-in native HLS video player (iOS-only, off by default) that uses AVPlayerViewController for true automatic picture-in-picture on swipe-home, replacing the WebView-based player when enabled. Key changes: - GQL API client for fetching playback access tokens and building HLS URLs - NativeVideoStore (MobX) managing player lifecycle, quality selection, PiP - VideoPlayerInterface abstraction so both stores drive the same overlay - Settings toggle gated on iOS with "Native player (experimental)" label - Auth token fallback: tries authenticated GQL for ad-free, falls back to anonymous if rejected (app OAuth token != web Client-ID token) - Scoped UnauthorizedInterceptor to Helix/OAuth URLs only - iOS Info.plist background audio mode for PiP audio continuity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
109 lines
3.7 KiB
Dart
109 lines
3.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:frosty/apis/bttv_api.dart';
|
|
import 'package:frosty/apis/ffz_api.dart';
|
|
import 'package:frosty/apis/seventv_api.dart';
|
|
import 'package:frosty/apis/twitch_api.dart';
|
|
import 'package:frosty/apis/twitch_gql_api.dart';
|
|
import 'package:frosty/screens/settings/stores/auth_store.dart';
|
|
import 'package:frosty/screens/settings/stores/settings_store.dart';
|
|
import 'package:frosty/stores/global_assets_store.dart';
|
|
import 'package:frosty/theme.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
/// Static utility class for orientation-related operations
|
|
class OrientationUtils {
|
|
/// Gets the current orientation from the given context
|
|
static Orientation getCurrentOrientation(BuildContext context) {
|
|
return MediaQuery.of(context).orientation;
|
|
}
|
|
|
|
/// Returns true if the current orientation is portrait
|
|
static bool isPortrait(BuildContext context) {
|
|
return MediaQuery.of(context).orientation == Orientation.portrait;
|
|
}
|
|
|
|
/// Returns true if the current orientation is landscape
|
|
static bool isLandscape(BuildContext context) {
|
|
return MediaQuery.of(context).orientation == Orientation.landscape;
|
|
}
|
|
}
|
|
|
|
/// Comprehensive context extensions for common Flutter patterns
|
|
extension ContextExtensions on BuildContext {
|
|
// ===== THEME & STYLING =====
|
|
|
|
/// Gets the current theme
|
|
ThemeData get theme => Theme.of(this);
|
|
|
|
/// Gets the current color scheme
|
|
ColorScheme get colorScheme => Theme.of(this).colorScheme;
|
|
|
|
/// Gets the current text theme
|
|
TextTheme get textTheme => Theme.of(this).textTheme;
|
|
|
|
/// Gets the scaffold background color
|
|
Color get scaffoldColor => Theme.of(this).scaffoldBackgroundColor;
|
|
|
|
/// Gets body small text color (commonly used for secondary text)
|
|
Color? get bodySmallColor => Theme.of(this).textTheme.bodySmall?.color;
|
|
|
|
/// Gets the app's custom semantic colors
|
|
FrostyColors get frostyColors => Theme.of(this).extension<FrostyColors>()!;
|
|
|
|
/// Gets the default text style
|
|
TextStyle get defaultTextStyle => DefaultTextStyle.of(this).style;
|
|
|
|
// ===== SCREEN & LAYOUT =====
|
|
|
|
/// Gets the screen width
|
|
double get screenWidth => MediaQuery.of(this).size.width;
|
|
|
|
/// Gets the screen height
|
|
double get screenHeight => MediaQuery.of(this).size.height;
|
|
|
|
/// Gets the safe area padding top (commonly used)
|
|
double get safePaddingTop => MediaQuery.of(this).padding.top;
|
|
|
|
/// Gets the safe area padding bottom (commonly used)
|
|
double get safePaddingBottom => MediaQuery.of(this).padding.bottom;
|
|
|
|
// ===== PROVIDER ACCESS =====
|
|
|
|
/// Gets TwitchApi from provider
|
|
TwitchApi get twitchApi => read<TwitchApi>();
|
|
|
|
/// Gets TwitchGqlApi from provider
|
|
TwitchGqlApi get twitchGqlApi => read<TwitchGqlApi>();
|
|
|
|
/// Gets AuthStore from provider
|
|
AuthStore get authStore => read<AuthStore>();
|
|
|
|
/// Gets SettingsStore from provider
|
|
SettingsStore get settingsStore => read<SettingsStore>();
|
|
|
|
/// Gets FFZApi from provider
|
|
FFZApi get ffzApi => read<FFZApi>();
|
|
|
|
/// Gets BTTVApi from provider
|
|
BTTVApi get bttvApi => read<BTTVApi>();
|
|
|
|
/// Gets SevenTVApi from provider
|
|
SevenTVApi get sevenTVApi => read<SevenTVApi>();
|
|
|
|
/// Gets GlobalAssetsStore from provider
|
|
GlobalAssetsStore get globalAssetsStore => read<GlobalAssetsStore>();
|
|
|
|
// ===== ORIENTATION =====
|
|
|
|
/// Returns true if currently in portrait orientation
|
|
bool get isPortrait => OrientationUtils.isPortrait(this);
|
|
|
|
/// Returns true if currently in landscape orientation
|
|
bool get isLandscape => OrientationUtils.isLandscape(this);
|
|
}
|
|
|
|
/// TextScaler extensions for common scaling operations
|
|
extension TextScalerExtensions on double {
|
|
/// Creates a linear text scaler with this scale factor
|
|
TextScaler get textScaler => TextScaler.linear(this);
|
|
}
|