native_bridge.dart 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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. Future<bool> isMusicPlaying();
  24. }
  25. class NativeBridge implements LauncherPlatform {
  26. static const _appsChannel = MethodChannel('com.wasp.cardash_launcher/apps');
  27. static const _systemChannel = MethodChannel(
  28. 'com.wasp.cardash_launcher/system',
  29. );
  30. @override
  31. Future<List<AppEntry>> listApps() async {
  32. try {
  33. final raw = await _appsChannel.invokeMethod<dynamic>('listApps');
  34. if (raw is Map && raw['error'] != null) {
  35. throw LauncherPlatformException(raw['error'].toString());
  36. }
  37. if (raw is! List) return const [];
  38. return raw
  39. .whereType<Map>()
  40. .map(AppEntry.fromMap)
  41. .where(
  42. (app) => app.packageName.isNotEmpty && app.activityName.isNotEmpty,
  43. )
  44. .toList(growable: false);
  45. } on PlatformException catch (error) {
  46. throw LauncherPlatformException(
  47. error.message ?? 'Could not load installed apps.',
  48. );
  49. } on MissingPluginException {
  50. throw LauncherPlatformException('The Android app bridge is unavailable.');
  51. }
  52. }
  53. @override
  54. Future<void> launchApp({
  55. required String packageName,
  56. required String activityName,
  57. }) async {
  58. try {
  59. await _appsChannel.invokeMethod<dynamic>('launchApp', {
  60. 'package': packageName,
  61. 'activity': activityName,
  62. });
  63. } on PlatformException catch (error) {
  64. throw LauncherPlatformException(
  65. error.message ?? 'Could not open the app.',
  66. );
  67. } on MissingPluginException {
  68. throw LauncherPlatformException('The Android app bridge is unavailable.');
  69. }
  70. }
  71. @override
  72. Future<bool> isDefaultHome() async {
  73. try {
  74. return await _appsChannel.invokeMethod<bool>('isDefaultHome') ?? false;
  75. } on PlatformException {
  76. return false;
  77. } on MissingPluginException {
  78. return false;
  79. }
  80. }
  81. @override
  82. Future<bool> requestDefaultHome() async {
  83. try {
  84. return await _appsChannel.invokeMethod<bool>('requestDefaultHome') ??
  85. false;
  86. } on PlatformException {
  87. return false;
  88. } on MissingPluginException {
  89. return false;
  90. }
  91. }
  92. @override
  93. Future<SystemStatus> getStatus() async {
  94. try {
  95. final raw = await _systemChannel.invokeMethod<dynamic>('getStatus');
  96. return raw is Map ? SystemStatus.fromMap(raw) : const SystemStatus();
  97. } on PlatformException {
  98. return const SystemStatus();
  99. } on MissingPluginException {
  100. return const SystemStatus();
  101. }
  102. }
  103. @override
  104. Future<VolumeInfo> getVolume() async {
  105. try {
  106. final raw = await _systemChannel.invokeMethod<dynamic>('getVolume');
  107. return raw is Map ? VolumeInfo.fromMap(raw) : const VolumeInfo();
  108. } on PlatformException {
  109. return const VolumeInfo();
  110. } on MissingPluginException {
  111. return const VolumeInfo();
  112. }
  113. }
  114. @override
  115. Future<bool> isMusicPlaying() async {
  116. try {
  117. return await _systemChannel.invokeMethod<bool>('isMusicPlaying') ?? false;
  118. } on PlatformException {
  119. return false;
  120. } on MissingPluginException {
  121. return false;
  122. }
  123. }
  124. @override
  125. Future<void> setVolume(int index) async {
  126. try {
  127. await _systemChannel.invokeMethod<dynamic>('setVolume', {'index': index});
  128. } on PlatformException {
  129. // Volume can be unavailable on some emulators; the UI keeps its local
  130. // value and will reconcile on the next poll.
  131. } on MissingPluginException {
  132. // Nothing to synchronise when running outside Android.
  133. }
  134. }
  135. }