From 5c3fbfdff8e7abf15a0d1bebe2b7044bd4a52912 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Tue, 10 Feb 2026 09:17:55 +0100 Subject: [PATCH] fix: remote dialog issues and optimize command protocol --- .../companion_remote/remote_command.dart | 55 ++----- .../companion_remote/remote_command.g.dart | 67 --------- .../companion_remote/remote_command_type.dart | 141 +----------------- lib/providers/companion_remote_provider.dart | 15 +- .../companion_remote_peer_service.dart | 35 ++--- .../companion_remote_receiver.dart | 2 - .../remote_session_dialog.dart | 5 +- 7 files changed, 30 insertions(+), 290 deletions(-) delete mode 100644 lib/models/companion_remote/remote_command.g.dart diff --git a/lib/models/companion_remote/remote_command.dart b/lib/models/companion_remote/remote_command.dart index b9c16726..d301da2c 100644 --- a/lib/models/companion_remote/remote_command.dart +++ b/lib/models/companion_remote/remote_command.dart @@ -1,59 +1,26 @@ -import 'package:json_annotation/json_annotation.dart'; - import 'remote_command_type.dart'; -part 'remote_command.g.dart'; - -@JsonSerializable() class RemoteCommand { - @JsonKey(unknownEnumValue: RemoteCommandType.ping) final RemoteCommandType type; - final String deviceId; - final String deviceName; - final DateTime timestamp; final Map? data; - RemoteCommand({required this.type, required this.deviceId, required this.deviceName, DateTime? timestamp, this.data}) - : timestamp = timestamp ?? DateTime.now(); + const RemoteCommand({required this.type, this.data}); - factory RemoteCommand.fromJson(Map json) => _$RemoteCommandFromJson(json); - - Map toJson() => _$RemoteCommandToJson(this); - - RemoteCommand copyWith({ - RemoteCommandType? type, - String? deviceId, - String? deviceName, - DateTime? timestamp, - Map? data, - }) { + factory RemoteCommand.fromJson(Map json) { + final index = json['t'] as int; return RemoteCommand( - type: type ?? this.type, - deviceId: deviceId ?? this.deviceId, - deviceName: deviceName ?? this.deviceName, - timestamp: timestamp ?? this.timestamp, - data: data ?? this.data, + type: index < RemoteCommandType.values.length ? RemoteCommandType.values[index] : RemoteCommandType.ping, + data: json['d'] as Map?, ); } - @override - String toString() { - return 'RemoteCommand(type: ${type.name}, device: $deviceName, data: $data)'; + Map toJson() { + return { + 't': type.index, + if (data != null) 'd': data, + }; } @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is RemoteCommand && - other.type == type && - other.deviceId == deviceId && - other.deviceName == deviceName && - other.timestamp == timestamp; - } - - @override - int get hashCode { - return type.hashCode ^ deviceId.hashCode ^ deviceName.hashCode ^ timestamp.hashCode; - } + String toString() => 'RemoteCommand(${type.name}, data: $data)'; } diff --git a/lib/models/companion_remote/remote_command.g.dart b/lib/models/companion_remote/remote_command.g.dart deleted file mode 100644 index db7cab90..00000000 --- a/lib/models/companion_remote/remote_command.g.dart +++ /dev/null @@ -1,67 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'remote_command.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -RemoteCommand _$RemoteCommandFromJson(Map json) => RemoteCommand( - type: $enumDecode(_$RemoteCommandTypeEnumMap, json['type'], unknownValue: RemoteCommandType.ping), - deviceId: json['deviceId'] as String, - deviceName: json['deviceName'] as String, - timestamp: json['timestamp'] == null ? null : DateTime.parse(json['timestamp'] as String), - data: json['data'] as Map?, -); - -Map _$RemoteCommandToJson(RemoteCommand instance) => { - 'type': _$RemoteCommandTypeEnumMap[instance.type]!, - 'deviceId': instance.deviceId, - 'deviceName': instance.deviceName, - 'timestamp': instance.timestamp.toIso8601String(), - 'data': instance.data, -}; - -const _$RemoteCommandTypeEnumMap = { - RemoteCommandType.dpadUp: 'dpadUp', - RemoteCommandType.dpadDown: 'dpadDown', - RemoteCommandType.dpadLeft: 'dpadLeft', - RemoteCommandType.dpadRight: 'dpadRight', - RemoteCommandType.select: 'select', - RemoteCommandType.back: 'back', - RemoteCommandType.contextMenu: 'contextMenu', - RemoteCommandType.play: 'play', - RemoteCommandType.pause: 'pause', - RemoteCommandType.playPause: 'playPause', - RemoteCommandType.stop: 'stop', - RemoteCommandType.seekForward: 'seekForward', - RemoteCommandType.seekBackward: 'seekBackward', - RemoteCommandType.nextTrack: 'nextTrack', - RemoteCommandType.previousTrack: 'previousTrack', - RemoteCommandType.skipIntro: 'skipIntro', - RemoteCommandType.skipCredits: 'skipCredits', - RemoteCommandType.volumeUp: 'volumeUp', - RemoteCommandType.volumeDown: 'volumeDown', - RemoteCommandType.volumeMute: 'volumeMute', - RemoteCommandType.volumeSet: 'volumeSet', - RemoteCommandType.tabNext: 'tabNext', - RemoteCommandType.tabPrevious: 'tabPrevious', - RemoteCommandType.tabDiscover: 'tabDiscover', - RemoteCommandType.tabLibraries: 'tabLibraries', - RemoteCommandType.tabSearch: 'tabSearch', - RemoteCommandType.tabDownloads: 'tabDownloads', - RemoteCommandType.tabSettings: 'tabSettings', - RemoteCommandType.home: 'home', - RemoteCommandType.search: 'search', - RemoteCommandType.subtitles: 'subtitles', - RemoteCommandType.audioTracks: 'audioTracks', - RemoteCommandType.qualitySettings: 'qualitySettings', - RemoteCommandType.fullscreen: 'fullscreen', - RemoteCommandType.ping: 'ping', - RemoteCommandType.pong: 'pong', - RemoteCommandType.deviceInfo: 'deviceInfo', - RemoteCommandType.capabilitiesRequest: 'capabilitiesRequest', - RemoteCommandType.capabilitiesResponse: 'capabilitiesResponse', - RemoteCommandType.disconnect: 'disconnect', - RemoteCommandType.ack: 'ack', -}; diff --git a/lib/models/companion_remote/remote_command_type.dart b/lib/models/companion_remote/remote_command_type.dart index ca5febb1..dcf0f3d5 100644 --- a/lib/models/companion_remote/remote_command_type.dart +++ b/lib/models/companion_remote/remote_command_type.dart @@ -47,145 +47,6 @@ enum RemoteCommandType { ping, pong, deviceInfo, - capabilitiesRequest, - capabilitiesResponse, disconnect, - ack, // Acknowledgment of received command -} - -extension RemoteCommandTypeExtension on RemoteCommandType { - String get displayName { - switch (this) { - case RemoteCommandType.dpadUp: - return 'Up'; - case RemoteCommandType.dpadDown: - return 'Down'; - case RemoteCommandType.dpadLeft: - return 'Left'; - case RemoteCommandType.dpadRight: - return 'Right'; - case RemoteCommandType.select: - return 'Select'; - case RemoteCommandType.back: - return 'Back'; - case RemoteCommandType.contextMenu: - return 'Menu'; - case RemoteCommandType.play: - return 'Play'; - case RemoteCommandType.pause: - return 'Pause'; - case RemoteCommandType.playPause: - return 'Play/Pause'; - case RemoteCommandType.stop: - return 'Stop'; - case RemoteCommandType.seekForward: - return 'Seek Forward'; - case RemoteCommandType.seekBackward: - return 'Seek Backward'; - case RemoteCommandType.nextTrack: - return 'Next'; - case RemoteCommandType.previousTrack: - return 'Previous'; - case RemoteCommandType.skipIntro: - return 'Skip Intro'; - case RemoteCommandType.skipCredits: - return 'Skip Credits'; - case RemoteCommandType.volumeUp: - return 'Volume Up'; - case RemoteCommandType.volumeDown: - return 'Volume Down'; - case RemoteCommandType.volumeMute: - return 'Mute'; - case RemoteCommandType.volumeSet: - return 'Set Volume'; - case RemoteCommandType.tabNext: - return 'Next Tab'; - case RemoteCommandType.tabPrevious: - return 'Previous Tab'; - case RemoteCommandType.tabDiscover: - return 'Discover'; - case RemoteCommandType.tabLibraries: - return 'Libraries'; - case RemoteCommandType.tabSearch: - return 'Search'; - case RemoteCommandType.tabDownloads: - return 'Downloads'; - case RemoteCommandType.tabSettings: - return 'Settings'; - case RemoteCommandType.home: - return 'Home'; - case RemoteCommandType.search: - return 'Search'; - case RemoteCommandType.subtitles: - return 'Subtitles'; - case RemoteCommandType.audioTracks: - return 'Audio'; - case RemoteCommandType.qualitySettings: - return 'Quality'; - case RemoteCommandType.fullscreen: - return 'Fullscreen'; - case RemoteCommandType.ping: - return 'Ping'; - case RemoteCommandType.pong: - return 'Pong'; - case RemoteCommandType.deviceInfo: - return 'Device Info'; - case RemoteCommandType.capabilitiesRequest: - return 'Capabilities Request'; - case RemoteCommandType.capabilitiesResponse: - return 'Capabilities Response'; - case RemoteCommandType.disconnect: - return 'Disconnect'; - case RemoteCommandType.ack: - return 'Acknowledgment'; - } - } - - bool get isNavigationCommand { - return [ - RemoteCommandType.dpadUp, - RemoteCommandType.dpadDown, - RemoteCommandType.dpadLeft, - RemoteCommandType.dpadRight, - RemoteCommandType.select, - RemoteCommandType.back, - RemoteCommandType.contextMenu, - ].contains(this); - } - - bool get isPlaybackCommand { - return [ - RemoteCommandType.play, - RemoteCommandType.pause, - RemoteCommandType.playPause, - RemoteCommandType.stop, - RemoteCommandType.seekForward, - RemoteCommandType.seekBackward, - RemoteCommandType.nextTrack, - RemoteCommandType.previousTrack, - RemoteCommandType.skipIntro, - RemoteCommandType.skipCredits, - ].contains(this); - } - - bool get isVolumeCommand { - return [ - RemoteCommandType.volumeUp, - RemoteCommandType.volumeDown, - RemoteCommandType.volumeMute, - RemoteCommandType.volumeSet, - ].contains(this); - } - - bool get isTabCommand { - return [ - RemoteCommandType.tabNext, - RemoteCommandType.tabPrevious, - RemoteCommandType.tabDiscover, - RemoteCommandType.tabLibraries, - RemoteCommandType.tabSearch, - RemoteCommandType.tabDownloads, - RemoteCommandType.tabSettings, - ].contains(this); - } + ack, } diff --git a/lib/providers/companion_remote_provider.dart b/lib/providers/companion_remote_provider.dart index 0adf1f97..1879874b 100644 --- a/lib/providers/companion_remote_provider.dart +++ b/lib/providers/companion_remote_provider.dart @@ -168,12 +168,14 @@ class CompanionRemoteProvider with ChangeNotifier { Future _handleDeviceInfo(RemoteCommand command) async { if (command.data != null) { + final id = command.data!['id'] as String? ?? 'unknown'; + final name = command.data!['name'] as String? ?? 'Unknown Device'; final platform = command.data!['platform'] as String? ?? 'unknown'; final role = command.data!['role'] as String?; - appLogger.d('CompanionRemote: Device info - name: ${command.deviceName}, platform: $platform, role: $role'); + appLogger.d('CompanionRemote: Device info - name: $name, platform: $platform, role: $role'); - final device = RemoteDevice(id: command.deviceId, name: command.deviceName, platform: platform); + final device = RemoteDevice(id: id, name: name, platform: platform); _session = _session?.copyWith(connectedDevice: device); notifyListeners(); @@ -275,14 +277,7 @@ class CompanionRemoteProvider with ChangeNotifier { } appLogger.d('CompanionRemote: Sending command $type'); - final command = RemoteCommand( - type: type, - deviceId: _peerService!.myPeerId ?? 'unknown', - deviceName: _deviceName, - data: data, - ); - - _peerService!.sendCommand(command); + _peerService!.sendCommand(RemoteCommand(type: type, data: data)); } void _scheduleReconnect() { diff --git a/lib/services/companion_remote/companion_remote_peer_service.dart b/lib/services/companion_remote/companion_remote_peer_service.dart index e91684ed..40dc6817 100644 --- a/lib/services/companion_remote/companion_remote_peer_service.dart +++ b/lib/services/companion_remote/companion_remote_peer_service.dart @@ -268,13 +268,13 @@ class CompanionRemotePeerService { appLogger.d('CompanionRemote: Received command: ${command.type}'); if (_shouldSendAck(command)) { - _sendAck(command, hostDeviceName); + _sendAck(command); } _commandReceivedController.add(command); if (command.type == RemoteCommandType.ping) { - _sendPong(hostDeviceName, hostPlatform); + _sendPong(); } } } catch (e) { @@ -375,13 +375,13 @@ class CompanionRemotePeerService { appLogger.d('CompanionRemote: Received command: ${command.type}'); if (_shouldSendAck(command)) { - _sendAck(command, deviceName); + _sendAck(command); } _commandReceivedController.add(command); if (command.type == RemoteCommandType.ping) { - _sendPong(deviceName, platform); + _sendPong(); } } } catch (e) { @@ -441,7 +441,7 @@ class CompanionRemotePeerService { _stopPingTimer(); _pingTimer = Timer.periodic(const Duration(seconds: 5), (_) { if (isConnected) { - sendCommand(RemoteCommand(type: RemoteCommandType.ping, deviceId: _myPeerId ?? 'unknown', deviceName: 'local')); + sendCommand(const RemoteCommand(type: RemoteCommandType.ping)); } }); } @@ -458,34 +458,19 @@ class CompanionRemotePeerService { command.type != RemoteCommandType.deviceInfo; } - void _sendAck(RemoteCommand command, String deviceName) { - final ackCommand = RemoteCommand( - type: RemoteCommandType.ack, - deviceId: _myPeerId ?? 'unknown', - deviceName: deviceName, - data: {'originalCommand': command.type.toString()}, - ); - sendCommand(ackCommand); + void _sendAck(RemoteCommand command) { + sendCommand(const RemoteCommand(type: RemoteCommandType.ack)); } - void _sendPong(String deviceName, String platform) { - sendCommand( - RemoteCommand( - type: RemoteCommandType.pong, - deviceId: _myPeerId ?? 'unknown', - deviceName: deviceName, - data: {'platform': platform}, - ), - ); + void _sendPong() { + sendCommand(const RemoteCommand(type: RemoteCommandType.pong)); } void sendDeviceInfo(String deviceName, String platform) { sendCommand( RemoteCommand( type: RemoteCommandType.deviceInfo, - deviceId: _myPeerId ?? 'unknown', - deviceName: deviceName, - data: {'platform': platform, 'role': _role?.name}, + data: {'id': _myPeerId, 'name': deviceName, 'platform': platform, 'role': _role?.name}, ), ); } diff --git a/lib/services/companion_remote/companion_remote_receiver.dart b/lib/services/companion_remote/companion_remote_receiver.dart index fd2caee2..e9d16c78 100644 --- a/lib/services/companion_remote/companion_remote_receiver.dart +++ b/lib/services/companion_remote/companion_remote_receiver.dart @@ -119,8 +119,6 @@ class CompanionRemoteReceiver { case RemoteCommandType.pong: case RemoteCommandType.ack: case RemoteCommandType.deviceInfo: - case RemoteCommandType.capabilitiesRequest: - case RemoteCommandType.capabilitiesResponse: case RemoteCommandType.disconnect: break; diff --git a/lib/widgets/companion_remote/remote_session_dialog.dart b/lib/widgets/companion_remote/remote_session_dialog.dart index 7fcc0bd4..59c22497 100644 --- a/lib/widgets/companion_remote/remote_session_dialog.dart +++ b/lib/widgets/companion_remote/remote_session_dialog.dart @@ -29,7 +29,8 @@ class _RemoteSessionDialogState extends State { @override void initState() { super.initState(); - _createSession(); + // Defer to avoid notifyListeners() during build phase + WidgetsBinding.instance.addPostFrameCallback((_) => _createSession()); } Future _createSession() async { @@ -122,7 +123,7 @@ class _RemoteSessionDialogState extends State { return Dialog( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 500), - child: Padding( + child: SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( mainAxisSize: MainAxisSize.min,