import 'dart:io'; import 'dart:math'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; /// Service for detecting if the app is running on Android TV class TvDetectionService { static TvDetectionService? _instance; bool _detected = false; bool _forceTv = false; bool _isTV = false; bool _initialized = false; TvDetectionService._(); /// Get the singleton instance, initializing if needed. /// Pass [forceTv] to combine a user override with the system-feature check. static Future getInstance({bool forceTv = false}) async { if (_instance == null) { _instance = TvDetectionService._(); await _instance!._detect(forceTv); } return _instance!; } Future _detect(bool forceTv) async { if (_initialized) return; if (Platform.isAndroid) { final deviceInfo = DeviceInfoPlugin(); final androidInfo = await deviceInfo.androidInfo; // Check for android.software.leanback feature (standard Android TV detection) _detected = androidInfo.systemFeatures.contains('android.software.leanback'); } _forceTv = forceTv; _isTV = _detected || _forceTv; _initialized = true; } bool get isTV => _isTV; /// Update the user force-TV override and recompute the effective flag. void setForceTv(bool value) { _forceTv = value; _isTV = _detected || _forceTv; } /// Synchronous access after initialization (returns false if not initialized) static bool isTVSync() => _instance?._isTV ?? false; /// Convenience setter that forwards to the singleton if available. static void setForceTVSync(bool value) => _instance?.setForceTv(value); } /// Utility class for platform detection class PlatformDetector { /// Detects if running on Android TV (requires TvDetectionService to be initialized) static bool isTV() { return TvDetectionService.isTVSync(); } /// Detects if the app should use side navigation (Desktop or TV) static bool shouldUseSideNavigation(BuildContext context) { return isDesktop(context) || isTV(); } /// Whether this device should act as a companion remote host (receiver). /// Desktop platforms and Android TV are hosts; phones/tablets are controllers. static bool shouldActAsRemoteHost(BuildContext context) { return isDesktop(context) || isTV(); } /// Detects if running on a mobile platform (iOS or Android) /// Uses Theme for consistent platform detection across the app static bool isMobile(BuildContext context) { final platform = Theme.of(context).platform; return platform == TargetPlatform.iOS || platform == TargetPlatform.android; } /// Detects if running on a handheld mobile device (phone or tablet, not TV). static bool isHandheld(BuildContext context) { return isMobile(context) && !isTV(); } /// Detects if running on a desktop platform (Windows, macOS, or Linux) static bool isDesktop(BuildContext context) { return !isMobile(context); } /// True on the desktop OS (Windows / macOS / Linux), without needing a /// BuildContext. Use for OS-level capability checks (window state, native /// keyboard, etc.); use [isDesktop] for layout decisions. static bool isDesktopOS() { return Platform.isWindows || Platform.isMacOS || Platform.isLinux; } /// Detects if the device is likely a tablet based on screen size /// Uses diagonal screen size to determine if device is a tablet static bool isTablet(BuildContext context) { final data = MediaQuery.of(context); final size = data.size; final diagonal = sqrt(size.width * size.width + size.height * size.height); final devicePixelRatio = data.devicePixelRatio; // Convert diagonal from logical pixels to inches (assuming 160 DPI as baseline) final diagonalInches = diagonal / (devicePixelRatio * 160 / 2.54); // Consider devices with diagonal >= 7 inches as tablets return diagonalInches >= 7.0; } /// Detects if the device is a phone (mobile but not tablet) static bool isPhone(BuildContext context) { return isHandheld(context) && !isTablet(context); } }