native_bridge.dart 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import 'package:flutter/services.dart';
  2. import '../models/app_entry.dart';
  3. import '../models/system_status.dart';
  4. class LauncherPlatformException implements Exception {
  5. LauncherPlatformException(this.message);
  6. final String message;
  7. @override
  8. String toString() => message;
  9. }
  10. /// Thin, testable boundary around Android's package manager, volume and
  11. /// connectivity APIs.
  12. abstract class LauncherPlatform {
  13. Future<List<AppEntry>> listApps();
  14. Future<void> launchApp({
  15. required String packageName,
  16. required String activityName,
  17. });
  18. Future<bool> isDefaultHome();
  19. Future<bool> requestDefaultHome();
  20. Future<SystemStatus> getStatus();
  21. Future<VolumeInfo> getVolume();
  22. Future<void> setVolume(int index);
  23. }
  24. class NativeBridge implements LauncherPlatform {
  25. static const _appsChannel = MethodChannel('com.wasp.cardash_launcher/apps');
  26. static const _systemChannel = MethodChannel(
  27. 'com.wasp.cardash_launcher/system',
  28. );
  29. @override
  30. Future<List<AppEntry>> listApps() async {
  31. try {
  32. final raw = await _appsChannel.invokeMethod<dynamic>('listApps');
  33. if (raw is Map && raw['error'] != null) {
  34. throw LauncherPlatformException(raw['error'].toString());
  35. }
  36. if (raw is! List) return const [];
  37. return raw
  38. .whereType<Map>()
  39. .map(AppEntry.fromMap)
  40. .where(
  41. (app) => app.packageName.isNotEmpty && app.activityName.isNotEmpty,
  42. )
  43. .toList(growable: false);
  44. } on PlatformException catch (error) {
  45. throw LauncherPlatformException(
  46. error.message ?? 'Could not load installed apps.',
  47. );
  48. } on MissingPluginException {
  49. throw LauncherPlatformException('The Android app bridge is unavailable.');
  50. }
  51. }
  52. @override
  53. Future<void> launchApp({
  54. required String packageName,
  55. required String activityName,
  56. }) async {
  57. try {
  58. await _appsChannel.invokeMethod<dynamic>('launchApp', {
  59. 'package': packageName,
  60. 'activity': activityName,
  61. });
  62. } on PlatformException catch (error) {
  63. throw LauncherPlatformException(
  64. error.message ?? 'Could not open the app.',
  65. );
  66. } on MissingPluginException {
  67. throw LauncherPlatformException('The Android app bridge is unavailable.');
  68. }
  69. }
  70. @override
  71. Future<bool> isDefaultHome() async {
  72. try {
  73. return await _appsChannel.invokeMethod<bool>('isDefaultHome') ?? false;
  74. } on PlatformException {
  75. return false;
  76. } on MissingPluginException {
  77. return false;
  78. }
  79. }
  80. @override
  81. Future<bool> requestDefaultHome() async {
  82. try {
  83. return await _appsChannel.invokeMethod<bool>('requestDefaultHome') ??
  84. false;
  85. } on PlatformException {
  86. return false;
  87. } on MissingPluginException {
  88. return false;
  89. }
  90. }
  91. @override
  92. Future<SystemStatus> getStatus() async {
  93. try {
  94. final raw = await _systemChannel.invokeMethod<dynamic>('getStatus');
  95. return raw is Map ? SystemStatus.fromMap(raw) : const SystemStatus();
  96. } on PlatformException {
  97. return const SystemStatus();
  98. } on MissingPluginException {
  99. return const SystemStatus();
  100. }
  101. }
  102. @override
  103. Future<VolumeInfo> getVolume() async {
  104. try {
  105. final raw = await _systemChannel.invokeMethod<dynamic>('getVolume');
  106. return raw is Map ? VolumeInfo.fromMap(raw) : const VolumeInfo();
  107. } on PlatformException {
  108. return const VolumeInfo();
  109. } on MissingPluginException {
  110. return const VolumeInfo();
  111. }
  112. }
  113. @override
  114. Future<void> setVolume(int index) async {
  115. try {
  116. await _systemChannel.invokeMethod<dynamic>('setVolume', {'index': index});
  117. } on PlatformException {
  118. // Volume can be unavailable on some emulators; the UI keeps its local
  119. // value and will reconcile on the next poll.
  120. } on MissingPluginException {
  121. // Nothing to synchronise when running outside Android.
  122. }
  123. }
  124. }