| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 |
- import 'package:flutter/services.dart';
- import '../models/app_entry.dart';
- import '../models/media_playback.dart';
- import '../models/update_info.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<List<AppEntry>> listApps();
- Future<void> launchApp({
- required String packageName,
- required String activityName,
- });
- Future<bool> isDefaultHome();
- Future<bool> requestDefaultHome();
- Future<SystemStatus> getStatus();
- Future<VolumeInfo> getVolume();
- Future<void> setVolume(int index);
- Future<bool> isMusicPlaying();
- /// Sends a standard media key to the current media session.
- Future<bool> sendMediaKey(String action);
- Future<MediaPlayback> getMediaState();
- Future<bool> openNotificationAccessSettings();
- Future<void> setKeepScreenOn(bool enabled);
- Future<bool> openUrl(String url);
- Future<UpdateInstallResult> downloadAndInstallUpdate(String url);
- }
- 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<List<AppEntry>> listApps() async {
- try {
- final raw = await _appsChannel.invokeMethod<dynamic>('listApps');
- if (raw is Map && raw['error'] != null) {
- throw LauncherPlatformException(raw['error'].toString());
- }
- if (raw is! List) return const [];
- return raw
- .whereType<Map>()
- .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<void> launchApp({
- required String packageName,
- required String activityName,
- }) async {
- try {
- await _appsChannel.invokeMethod<dynamic>('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<bool> isDefaultHome() async {
- try {
- return await _appsChannel.invokeMethod<bool>('isDefaultHome') ?? false;
- } on PlatformException {
- return false;
- } on MissingPluginException {
- return false;
- }
- }
- @override
- Future<bool> requestDefaultHome() async {
- try {
- return await _appsChannel.invokeMethod<bool>('requestDefaultHome') ??
- false;
- } on PlatformException {
- return false;
- } on MissingPluginException {
- return false;
- }
- }
- @override
- Future<SystemStatus> getStatus() async {
- try {
- final raw = await _systemChannel.invokeMethod<dynamic>('getStatus');
- return raw is Map ? SystemStatus.fromMap(raw) : const SystemStatus();
- } on PlatformException {
- return const SystemStatus();
- } on MissingPluginException {
- return const SystemStatus();
- }
- }
- @override
- Future<VolumeInfo> getVolume() async {
- try {
- final raw = await _systemChannel.invokeMethod<dynamic>('getVolume');
- return raw is Map ? VolumeInfo.fromMap(raw) : const VolumeInfo();
- } on PlatformException {
- return const VolumeInfo();
- } on MissingPluginException {
- return const VolumeInfo();
- }
- }
- @override
- Future<bool> isMusicPlaying() async {
- try {
- return await _systemChannel.invokeMethod<bool>('isMusicPlaying') ?? false;
- } on PlatformException {
- return false;
- } on MissingPluginException {
- return false;
- }
- }
- @override
- Future<MediaPlayback> getMediaState() async {
- try {
- final raw = await _mediaChannel.invokeMethod<dynamic>('getMediaState');
- return raw is Map ? MediaPlayback.fromMap(raw) : const MediaPlayback();
- } on PlatformException {
- return const MediaPlayback();
- } on MissingPluginException {
- return const MediaPlayback();
- }
- }
- @override
- Future<bool> openNotificationAccessSettings() async {
- try {
- return await _mediaChannel.invokeMethod<bool>(
- 'openNotificationAccessSettings',
- ) ??
- false;
- } on PlatformException {
- return false;
- } on MissingPluginException {
- return false;
- }
- }
- @override
- Future<bool> sendMediaKey(String action) async {
- try {
- return await _systemChannel.invokeMethod<bool>('mediaKey', {
- 'action': action,
- }) ??
- false;
- } on PlatformException {
- return false;
- } on MissingPluginException {
- return false;
- }
- }
- @override
- Future<UpdateInstallResult> downloadAndInstallUpdate(String url) async {
- try {
- final raw = await _appsChannel.invokeMethod<dynamic>(
- 'downloadAndInstallUpdate',
- {'url': url},
- );
- if (raw is Map) return UpdateInstallResult.fromMap(raw);
- return const UpdateInstallResult(
- started: false,
- permissionRequired: false,
- message: 'The update installer did not start.',
- );
- } on PlatformException catch (error) {
- return UpdateInstallResult(
- started: false,
- permissionRequired: false,
- message: error.message ?? 'Could not download the update.',
- );
- } on MissingPluginException {
- return const UpdateInstallResult(
- started: false,
- permissionRequired: false,
- message: 'The Android update bridge is unavailable.',
- );
- }
- }
- @override
- Future<bool> openUrl(String url) async {
- try {
- return await _appsChannel.invokeMethod<bool>('openUrl', {'url': url}) ??
- false;
- } on PlatformException {
- return false;
- } on MissingPluginException {
- return false;
- }
- }
- @override
- Future<void> setKeepScreenOn(bool enabled) async {
- try {
- await _systemChannel.invokeMethod<dynamic>('setKeepScreenOn', {
- 'enabled': enabled,
- });
- } on PlatformException {
- // Older builds simply keep the window behaviour unchanged.
- } on MissingPluginException {
- // Nothing to apply outside Android.
- }
- }
- @override
- Future<void> setVolume(int index) async {
- try {
- await _systemChannel.invokeMethod<dynamic>('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.
- }
- }
- }
|