| 1 | import 'dart:io'; |
| 2 | import 'package:flutter/services.dart'; |
| 3 | import 'package:wakelock_plus/wakelock_plus.dart'; |
| 4 | import '../logger/app_logger.dart'; |
| 5 | |
| 6 | class ChatBackgroundService { |
| 7 | static const _channel = MethodChannel('localmind/chat_background'); |
| 8 | bool _isActive = false; |
| 9 | bool _isMicActive = false; |
| 10 | |
| 11 | Future<void> start() async { |
| 12 | if (_isActive) return; |
| 13 | try { |
| 14 | Log.info('Starting background chat service'); |
| 15 | if (Platform.isAndroid) { |
| 16 | await _channel.invokeMethod('startForeground'); |
| 17 | } |
| 18 | await WakelockPlus.enable(); |
| 19 | _isActive = true; |
| 20 | } catch (e) { |
| 21 | Log.error('Failed to start background chat service: $e'); |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | Future<void> stop() async { |
| 26 | if (!_isActive) return; |
| 27 | try { |
| 28 | Log.info('Stopping background chat service'); |
| 29 | if (Platform.isAndroid) { |
| 30 | await _channel.invokeMethod('stopForeground'); |
| 31 | } |
| 32 | await WakelockPlus.disable(); |
| 33 | _isActive = false; |
| 34 | } catch (e) { |
| 35 | Log.error('Failed to stop background chat service: $e'); |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | /// Start a microphone-typed foreground service so that voice capture |
| 40 | /// survives when the app is backgrounded (Android 14+ requirement). |
| 41 | /// Does not toggle Wakelock — STT is short-lived and the overlay is |
| 42 | /// already on-screen when listening starts. |
| 43 | Future<bool> startMic() async { |
| 44 | if (_isMicActive) return true; |
| 45 | try { |
| 46 | Log.info('Starting background mic service'); |
| 47 | if (Platform.isAndroid) { |
| 48 | await _channel.invokeMethod('startForegroundMic'); |
| 49 | } |
| 50 | _isMicActive = true; |
| 51 | return true; |
| 52 | } catch (e) { |
| 53 | Log.error('Failed to start background mic service: $e'); |
| 54 | return false; |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | /// Stop the microphone-typed foreground service. Safe to call multiple |
| 59 | /// times and from `endSession()`. |
| 60 | Future<void> stopMic() async { |
| 61 | if (!_isMicActive) return; |
| 62 | try { |
| 63 | Log.info('Stopping background mic service'); |
| 64 | if (Platform.isAndroid) { |
| 65 | await _channel.invokeMethod('stopForegroundMic'); |
| 66 | } |
| 67 | _isMicActive = false; |
| 68 | } catch (e) { |
| 69 | Log.error('Failed to stop background mic service: $e'); |
| 70 | } |
| 71 | } |
| 72 | } |