import 'package:flutter/services.dart'; import '../models/app_entry.dart'; import '../models/media_playback.dart'; import '../models/system_status.dart'; class LauncherPlatformException implements Exception { LauncherPlatformException(this.message); final String message; @override String toString() => message; } /// Thin, testable boundary around Android's package manager, volume and /// connectivity APIs. abstract class LauncherPlatform { Future> listApps(); Future launchApp({ required String packageName, required String activityName, }); Future isDefaultHome(); Future requestDefaultHome(); Future getStatus(); Future getVolume(); Future setVolume(int index); Future isMusicPlaying(); /// Sends a standard media key to the current media session. Future sendMediaKey(String action); Future getMediaState(); Future openNotificationAccessSettings(); Future setKeepScreenOn(bool enabled); } class NativeBridge implements LauncherPlatform { static const _appsChannel = MethodChannel('com.wasp.cardash_launcher/apps'); static const _systemChannel = MethodChannel( 'com.wasp.cardash_launcher/system', ); static const _mediaChannel = MethodChannel('com.wasp.cardash_launcher/media'); @override Future> listApps() async { try { final raw = await _appsChannel.invokeMethod('listApps'); if (raw is Map && raw['error'] != null) { throw LauncherPlatformException(raw['error'].toString()); } if (raw is! List) return const []; return raw .whereType() .map(AppEntry.fromMap) .where( (app) => app.packageName.isNotEmpty && app.activityName.isNotEmpty, ) .toList(growable: false); } on PlatformException catch (error) { throw LauncherPlatformException( error.message ?? 'Could not load installed apps.', ); } on MissingPluginException { throw LauncherPlatformException('The Android app bridge is unavailable.'); } } @override Future launchApp({ required String packageName, required String activityName, }) async { try { await _appsChannel.invokeMethod('launchApp', { 'package': packageName, 'activity': activityName, }); } on PlatformException catch (error) { throw LauncherPlatformException( error.message ?? 'Could not open the app.', ); } on MissingPluginException { throw LauncherPlatformException('The Android app bridge is unavailable.'); } } @override Future isDefaultHome() async { try { return await _appsChannel.invokeMethod('isDefaultHome') ?? false; } on PlatformException { return false; } on MissingPluginException { return false; } } @override Future requestDefaultHome() async { try { return await _appsChannel.invokeMethod('requestDefaultHome') ?? false; } on PlatformException { return false; } on MissingPluginException { return false; } } @override Future getStatus() async { try { final raw = await _systemChannel.invokeMethod('getStatus'); return raw is Map ? SystemStatus.fromMap(raw) : const SystemStatus(); } on PlatformException { return const SystemStatus(); } on MissingPluginException { return const SystemStatus(); } } @override Future getVolume() async { try { final raw = await _systemChannel.invokeMethod('getVolume'); return raw is Map ? VolumeInfo.fromMap(raw) : const VolumeInfo(); } on PlatformException { return const VolumeInfo(); } on MissingPluginException { return const VolumeInfo(); } } @override Future isMusicPlaying() async { try { return await _systemChannel.invokeMethod('isMusicPlaying') ?? false; } on PlatformException { return false; } on MissingPluginException { return false; } } @override Future getMediaState() async { try { final raw = await _mediaChannel.invokeMethod('getMediaState'); return raw is Map ? MediaPlayback.fromMap(raw) : const MediaPlayback(); } on PlatformException { return const MediaPlayback(); } on MissingPluginException { return const MediaPlayback(); } } @override Future openNotificationAccessSettings() async { try { return await _mediaChannel.invokeMethod( 'openNotificationAccessSettings', ) ?? false; } on PlatformException { return false; } on MissingPluginException { return false; } } @override Future sendMediaKey(String action) async { try { return await _systemChannel.invokeMethod('mediaKey', { 'action': action, }) ?? false; } on PlatformException { return false; } on MissingPluginException { return false; } } @override Future setKeepScreenOn(bool enabled) async { try { await _systemChannel.invokeMethod('setKeepScreenOn', { 'enabled': enabled, }); } on PlatformException { // Older builds simply keep the window behaviour unchanged. } on MissingPluginException { // Nothing to apply outside Android. } } @override Future setVolume(int index) async { try { await _systemChannel.invokeMethod('setVolume', {'index': index}); } on PlatformException { // Volume can be unavailable on some emulators; the UI keeps its local // value and will reconcile on the next poll. } on MissingPluginException { // Nothing to synchronise when running outside Android. } } }