Migrate companion remote models to @JsonSerializable
This commit is contained in:
+1122
-400
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
/// Locales: 9
|
||||
/// Strings: 5121 (569 per locale)
|
||||
///
|
||||
/// Built on 2026-02-08 at 11:03 UTC
|
||||
/// Built on 2026-02-09 at 18:36 UTC
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint, unused_import
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'recent_remote_session.g.dart';
|
||||
|
||||
/// Recent Companion Remote session for quick reconnection
|
||||
@JsonSerializable()
|
||||
class RecentRemoteSession {
|
||||
final String sessionId;
|
||||
final String pin;
|
||||
final String deviceName;
|
||||
final String platform;
|
||||
final DateTime lastConnected;
|
||||
|
||||
RecentRemoteSession({
|
||||
required this.sessionId,
|
||||
required this.pin,
|
||||
required this.deviceName,
|
||||
required this.platform,
|
||||
required this.lastConnected,
|
||||
});
|
||||
|
||||
factory RecentRemoteSession.fromJson(Map<String, dynamic> json) => _$RecentRemoteSessionFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$RecentRemoteSessionToJson(this);
|
||||
|
||||
/// Create from QR code data (format: "sessionId:pin:deviceName:platform")
|
||||
factory RecentRemoteSession.fromQrData(String qrData) {
|
||||
final parts = qrData.split(':');
|
||||
if (parts.length < 2) {
|
||||
throw FormatException('Invalid QR code format');
|
||||
}
|
||||
|
||||
return RecentRemoteSession(
|
||||
sessionId: parts[0],
|
||||
pin: parts[1],
|
||||
deviceName: parts.length > 2 ? parts[2] : 'Unknown Device',
|
||||
platform: parts.length > 3 ? parts[3] : 'unknown',
|
||||
lastConnected: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '$deviceName ($platform) - Last: ${lastConnected.toLocal()}';
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'recent_remote_session.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
RecentRemoteSession _$RecentRemoteSessionFromJson(Map<String, dynamic> json) => RecentRemoteSession(
|
||||
sessionId: json['sessionId'] as String,
|
||||
pin: json['pin'] as String,
|
||||
deviceName: json['deviceName'] as String,
|
||||
platform: json['platform'] as String,
|
||||
lastConnected: DateTime.parse(json['lastConnected'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$RecentRemoteSessionToJson(RecentRemoteSession instance) => <String, dynamic>{
|
||||
'sessionId': instance.sessionId,
|
||||
'pin': instance.pin,
|
||||
'deviceName': instance.deviceName,
|
||||
'platform': instance.platform,
|
||||
'lastConnected': instance.lastConnected.toIso8601String(),
|
||||
};
|
||||
@@ -1,42 +1,24 @@
|
||||
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<String, dynamic>? data;
|
||||
|
||||
RemoteCommand({
|
||||
required this.type,
|
||||
required this.deviceId,
|
||||
required this.deviceName,
|
||||
DateTime? timestamp,
|
||||
this.data,
|
||||
}) : timestamp = timestamp ?? DateTime.now();
|
||||
RemoteCommand({required this.type, required this.deviceId, required this.deviceName, DateTime? timestamp, this.data})
|
||||
: timestamp = timestamp ?? DateTime.now();
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'type': type.name,
|
||||
'deviceId': deviceId,
|
||||
'deviceName': deviceName,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
if (data != null) 'data': data,
|
||||
};
|
||||
}
|
||||
factory RemoteCommand.fromJson(Map<String, dynamic> json) => _$RemoteCommandFromJson(json);
|
||||
|
||||
factory RemoteCommand.fromJson(Map<String, dynamic> json) {
|
||||
return RemoteCommand(
|
||||
type: RemoteCommandType.values.firstWhere(
|
||||
(e) => e.name == json['type'],
|
||||
orElse: () => RemoteCommandType.ping,
|
||||
),
|
||||
deviceId: json['deviceId'] as String,
|
||||
deviceName: json['deviceName'] as String,
|
||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||
data: json['data'] as Map<String, dynamic>?,
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() => _$RemoteCommandToJson(this);
|
||||
|
||||
RemoteCommand copyWith({
|
||||
RemoteCommandType? type,
|
||||
@@ -72,9 +54,6 @@ class RemoteCommand {
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return type.hashCode ^
|
||||
deviceId.hashCode ^
|
||||
deviceName.hashCode ^
|
||||
timestamp.hashCode;
|
||||
return type.hashCode ^ deviceId.hashCode ^ deviceName.hashCode ^ timestamp.hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'remote_command.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
RemoteCommand _$RemoteCommandFromJson(Map<String, dynamic> 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<String, dynamic>?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$RemoteCommandToJson(RemoteCommand instance) => <String, dynamic>{
|
||||
'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',
|
||||
};
|
||||
@@ -1,16 +1,12 @@
|
||||
enum RemoteSessionRole {
|
||||
host,
|
||||
remote,
|
||||
}
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
enum RemoteSessionStatus {
|
||||
disconnected,
|
||||
connecting,
|
||||
connected,
|
||||
reconnecting,
|
||||
error,
|
||||
}
|
||||
part 'remote_session.g.dart';
|
||||
|
||||
enum RemoteSessionRole { host, remote }
|
||||
|
||||
enum RemoteSessionStatus { disconnected, connecting, connected, reconnecting, error }
|
||||
|
||||
@JsonSerializable()
|
||||
class RemoteDevice {
|
||||
final String id;
|
||||
final String name;
|
||||
@@ -24,28 +20,12 @@ class RemoteDevice {
|
||||
required this.platform,
|
||||
DateTime? connectedAt,
|
||||
Map<String, bool>? capabilities,
|
||||
}) : connectedAt = connectedAt ?? DateTime.now(),
|
||||
capabilities = capabilities ?? {};
|
||||
}) : connectedAt = connectedAt ?? DateTime.now(),
|
||||
capabilities = capabilities ?? {};
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'platform': platform,
|
||||
'connectedAt': connectedAt.toIso8601String(),
|
||||
'capabilities': capabilities,
|
||||
};
|
||||
}
|
||||
factory RemoteDevice.fromJson(Map<String, dynamic> json) => _$RemoteDeviceFromJson(json);
|
||||
|
||||
factory RemoteDevice.fromJson(Map<String, dynamic> json) {
|
||||
return RemoteDevice(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
platform: json['platform'] as String,
|
||||
connectedAt: DateTime.parse(json['connectedAt'] as String),
|
||||
capabilities: Map<String, bool>.from(json['capabilities'] as Map? ?? {}),
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() => _$RemoteDeviceToJson(this);
|
||||
|
||||
RemoteDevice copyWith({
|
||||
String? id,
|
||||
@@ -74,10 +54,13 @@ class RemoteDevice {
|
||||
int get hashCode => id.hashCode;
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class RemoteSession {
|
||||
final String sessionId;
|
||||
final String pin;
|
||||
@JsonKey(unknownEnumValue: RemoteSessionRole.remote)
|
||||
final RemoteSessionRole role;
|
||||
@JsonKey(unknownEnumValue: RemoteSessionStatus.disconnected)
|
||||
final RemoteSessionStatus status;
|
||||
final RemoteDevice? connectedDevice;
|
||||
final DateTime createdAt;
|
||||
@@ -97,6 +80,10 @@ class RemoteSession {
|
||||
bool get isHost => role == RemoteSessionRole.host;
|
||||
bool get isRemote => role == RemoteSessionRole.remote;
|
||||
|
||||
factory RemoteSession.fromJson(Map<String, dynamic> json) => _$RemoteSessionFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$RemoteSessionToJson(this);
|
||||
|
||||
RemoteSession copyWith({
|
||||
String? sessionId,
|
||||
String? pin,
|
||||
@@ -116,36 +103,4 @@ class RemoteSession {
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'sessionId': sessionId,
|
||||
'pin': pin,
|
||||
'role': role.name,
|
||||
'status': status.name,
|
||||
'connectedDevice': connectedDevice?.toJson(),
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'errorMessage': errorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
factory RemoteSession.fromJson(Map<String, dynamic> json) {
|
||||
return RemoteSession(
|
||||
sessionId: json['sessionId'] as String,
|
||||
pin: json['pin'] as String,
|
||||
role: RemoteSessionRole.values.firstWhere(
|
||||
(e) => e.name == json['role'],
|
||||
orElse: () => RemoteSessionRole.remote,
|
||||
),
|
||||
status: RemoteSessionStatus.values.firstWhere(
|
||||
(e) => e.name == json['status'],
|
||||
orElse: () => RemoteSessionStatus.disconnected,
|
||||
),
|
||||
connectedDevice: json['connectedDevice'] != null
|
||||
? RemoteDevice.fromJson(json['connectedDevice'] as Map<String, dynamic>)
|
||||
: null,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
errorMessage: json['errorMessage'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'remote_session.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
RemoteDevice _$RemoteDeviceFromJson(Map<String, dynamic> json) => RemoteDevice(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
platform: json['platform'] as String,
|
||||
connectedAt: json['connectedAt'] == null ? null : DateTime.parse(json['connectedAt'] as String),
|
||||
capabilities: (json['capabilities'] as Map<String, dynamic>?)?.map((k, e) => MapEntry(k, e as bool)),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$RemoteDeviceToJson(RemoteDevice instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'platform': instance.platform,
|
||||
'connectedAt': instance.connectedAt.toIso8601String(),
|
||||
'capabilities': instance.capabilities,
|
||||
};
|
||||
|
||||
RemoteSession _$RemoteSessionFromJson(Map<String, dynamic> json) => RemoteSession(
|
||||
sessionId: json['sessionId'] as String,
|
||||
pin: json['pin'] as String,
|
||||
role: $enumDecode(_$RemoteSessionRoleEnumMap, json['role'], unknownValue: RemoteSessionRole.remote),
|
||||
status:
|
||||
$enumDecodeNullable(
|
||||
_$RemoteSessionStatusEnumMap,
|
||||
json['status'],
|
||||
unknownValue: RemoteSessionStatus.disconnected,
|
||||
) ??
|
||||
RemoteSessionStatus.disconnected,
|
||||
connectedDevice: json['connectedDevice'] == null
|
||||
? null
|
||||
: RemoteDevice.fromJson(json['connectedDevice'] as Map<String, dynamic>),
|
||||
createdAt: json['createdAt'] == null ? null : DateTime.parse(json['createdAt'] as String),
|
||||
errorMessage: json['errorMessage'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$RemoteSessionToJson(RemoteSession instance) => <String, dynamic>{
|
||||
'sessionId': instance.sessionId,
|
||||
'pin': instance.pin,
|
||||
'role': _$RemoteSessionRoleEnumMap[instance.role]!,
|
||||
'status': _$RemoteSessionStatusEnumMap[instance.status]!,
|
||||
'connectedDevice': instance.connectedDevice,
|
||||
'createdAt': instance.createdAt.toIso8601String(),
|
||||
'errorMessage': instance.errorMessage,
|
||||
};
|
||||
|
||||
const _$RemoteSessionRoleEnumMap = {RemoteSessionRole.host: 'host', RemoteSessionRole.remote: 'remote'};
|
||||
|
||||
const _$RemoteSessionStatusEnumMap = {
|
||||
RemoteSessionStatus.disconnected: 'disconnected',
|
||||
RemoteSessionStatus.connecting: 'connecting',
|
||||
RemoteSessionStatus.connected: 'connected',
|
||||
RemoteSessionStatus.reconnecting: 'reconnecting',
|
||||
RemoteSessionStatus.error: 'error',
|
||||
};
|
||||
@@ -1,3 +1,8 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'trusted_device.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class TrustedDevice {
|
||||
final String peerId;
|
||||
final String deviceName;
|
||||
@@ -13,30 +18,12 @@ class TrustedDevice {
|
||||
DateTime? firstConnected,
|
||||
DateTime? lastConnected,
|
||||
this.isApproved = false,
|
||||
}) : firstConnected = firstConnected ?? DateTime.now(),
|
||||
lastConnected = lastConnected ?? DateTime.now();
|
||||
}) : firstConnected = firstConnected ?? DateTime.now(),
|
||||
lastConnected = lastConnected ?? DateTime.now();
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'peerId': peerId,
|
||||
'deviceName': deviceName,
|
||||
'platform': platform,
|
||||
'firstConnected': firstConnected.toIso8601String(),
|
||||
'lastConnected': lastConnected.toIso8601String(),
|
||||
'isApproved': isApproved,
|
||||
};
|
||||
}
|
||||
factory TrustedDevice.fromJson(Map<String, dynamic> json) => _$TrustedDeviceFromJson(json);
|
||||
|
||||
factory TrustedDevice.fromJson(Map<String, dynamic> json) {
|
||||
return TrustedDevice(
|
||||
peerId: json['peerId'] as String,
|
||||
deviceName: json['deviceName'] as String,
|
||||
platform: json['platform'] as String,
|
||||
firstConnected: DateTime.parse(json['firstConnected'] as String),
|
||||
lastConnected: DateTime.parse(json['lastConnected'] as String),
|
||||
isApproved: json['isApproved'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
Map<String, dynamic> toJson() => _$TrustedDeviceToJson(this);
|
||||
|
||||
TrustedDevice copyWith({
|
||||
String? peerId,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'trusted_device.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
TrustedDevice _$TrustedDeviceFromJson(Map<String, dynamic> json) => TrustedDevice(
|
||||
peerId: json['peerId'] as String,
|
||||
deviceName: json['deviceName'] as String,
|
||||
platform: json['platform'] as String,
|
||||
firstConnected: json['firstConnected'] == null ? null : DateTime.parse(json['firstConnected'] as String),
|
||||
lastConnected: json['lastConnected'] == null ? null : DateTime.parse(json['lastConnected'] as String),
|
||||
isApproved: json['isApproved'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$TrustedDeviceToJson(TrustedDevice instance) => <String, dynamic>{
|
||||
'peerId': instance.peerId,
|
||||
'deviceName': instance.deviceName,
|
||||
'platform': instance.platform,
|
||||
'firstConnected': instance.firstConnected.toIso8601String(),
|
||||
'lastConnected': instance.lastConnected.toIso8601String(),
|
||||
'isApproved': instance.isApproved,
|
||||
};
|
||||
@@ -6,15 +6,23 @@ part of 'play_queue_response.dart';
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
PlayQueueResponse _$PlayQueueResponseFromJson(Map<String, dynamic> json) => PlayQueueResponse(
|
||||
playQueueID: (json['playQueueID'] as num).toInt(),
|
||||
playQueueSelectedItemID: (json['playQueueSelectedItemID'] as num?)?.toInt(),
|
||||
playQueueSelectedItemOffset: (json['playQueueSelectedItemOffset'] as num?)?.toInt(),
|
||||
playQueueSelectedMetadataItemID: json['playQueueSelectedMetadataItemID'] as String?,
|
||||
playQueueShuffled: const BoolOrIntConverter().fromJson(json['playQueueShuffled'] as Object),
|
||||
playQueueSourceURI: json['playQueueSourceURI'] as String?,
|
||||
playQueueTotalCount: (json['playQueueTotalCount'] as num?)?.toInt(),
|
||||
playQueueVersion: (json['playQueueVersion'] as num).toInt(),
|
||||
size: (json['size'] as num?)?.toInt(),
|
||||
items: (json['Metadata'] as List<dynamic>?)?.map((e) => PlexMetadata.fromJson(e as Map<String, dynamic>)).toList(),
|
||||
);
|
||||
PlayQueueResponse _$PlayQueueResponseFromJson(Map<String, dynamic> json) =>
|
||||
PlayQueueResponse(
|
||||
playQueueID: (json['playQueueID'] as num).toInt(),
|
||||
playQueueSelectedItemID: (json['playQueueSelectedItemID'] as num?)
|
||||
?.toInt(),
|
||||
playQueueSelectedItemOffset: (json['playQueueSelectedItemOffset'] as num?)
|
||||
?.toInt(),
|
||||
playQueueSelectedMetadataItemID:
|
||||
json['playQueueSelectedMetadataItemID'] as String?,
|
||||
playQueueShuffled: const BoolOrIntConverter().fromJson(
|
||||
json['playQueueShuffled'] as Object,
|
||||
),
|
||||
playQueueSourceURI: json['playQueueSourceURI'] as String?,
|
||||
playQueueTotalCount: (json['playQueueTotalCount'] as num?)?.toInt(),
|
||||
playQueueVersion: (json['playQueueVersion'] as num).toInt(),
|
||||
size: (json['size'] as num?)?.toInt(),
|
||||
items: (json['Metadata'] as List<dynamic>?)
|
||||
?.map((e) => PlexMetadata.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
@@ -19,15 +19,16 @@ PlexLibrary _$PlexLibraryFromJson(Map<String, dynamic> json) => PlexLibrary(
|
||||
hidden: (json['hidden'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PlexLibraryToJson(PlexLibrary instance) => <String, dynamic>{
|
||||
'key': instance.key,
|
||||
'title': instance.title,
|
||||
'type': instance.type,
|
||||
'agent': instance.agent,
|
||||
'scanner': instance.scanner,
|
||||
'language': instance.language,
|
||||
'uuid': instance.uuid,
|
||||
'updatedAt': instance.updatedAt,
|
||||
'createdAt': instance.createdAt,
|
||||
'hidden': instance.hidden,
|
||||
};
|
||||
Map<String, dynamic> _$PlexLibraryToJson(PlexLibrary instance) =>
|
||||
<String, dynamic>{
|
||||
'key': instance.key,
|
||||
'title': instance.title,
|
||||
'type': instance.type,
|
||||
'agent': instance.agent,
|
||||
'scanner': instance.scanner,
|
||||
'language': instance.language,
|
||||
'uuid': instance.uuid,
|
||||
'updatedAt': instance.updatedAt,
|
||||
'createdAt': instance.createdAt,
|
||||
'hidden': instance.hidden,
|
||||
};
|
||||
|
||||
@@ -41,7 +41,9 @@ PlexMetadata _$PlexMetadataFromJson(Map<String, dynamic> json) => PlexMetadata(
|
||||
leafCount: (json['leafCount'] as num?)?.toInt(),
|
||||
viewedLeafCount: (json['viewedLeafCount'] as num?)?.toInt(),
|
||||
childCount: (json['childCount'] as num?)?.toInt(),
|
||||
role: (json['Role'] as List<dynamic>?)?.map((e) => PlexRole.fromJson(e as Map<String, dynamic>)).toList(),
|
||||
role: (json['Role'] as List<dynamic>?)
|
||||
?.map((e) => PlexRole.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
audioLanguage: json['audioLanguage'] as String?,
|
||||
subtitleLanguage: json['subtitleLanguage'] as String?,
|
||||
playlistItemID: (json['playlistItemID'] as num?)?.toInt(),
|
||||
@@ -52,48 +54,49 @@ PlexMetadata _$PlexMetadataFromJson(Map<String, dynamic> json) => PlexMetadata(
|
||||
clearLogo: json['clearLogo'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PlexMetadataToJson(PlexMetadata instance) => <String, dynamic>{
|
||||
'ratingKey': instance.ratingKey,
|
||||
'key': instance.key,
|
||||
'guid': instance.guid,
|
||||
'studio': instance.studio,
|
||||
'type': instance.type,
|
||||
'title': instance.title,
|
||||
'titleSort': instance.titleSort,
|
||||
'contentRating': instance.contentRating,
|
||||
'summary': instance.summary,
|
||||
'rating': instance.rating,
|
||||
'audienceRating': instance.audienceRating,
|
||||
'year': instance.year,
|
||||
'originallyAvailableAt': instance.originallyAvailableAt,
|
||||
'thumb': instance.thumb,
|
||||
'art': instance.art,
|
||||
'duration': instance.duration,
|
||||
'addedAt': instance.addedAt,
|
||||
'updatedAt': instance.updatedAt,
|
||||
'lastViewedAt': instance.lastViewedAt,
|
||||
'grandparentTitle': instance.grandparentTitle,
|
||||
'grandparentThumb': instance.grandparentThumb,
|
||||
'grandparentArt': instance.grandparentArt,
|
||||
'grandparentRatingKey': instance.grandparentRatingKey,
|
||||
'parentTitle': instance.parentTitle,
|
||||
'parentThumb': instance.parentThumb,
|
||||
'parentRatingKey': instance.parentRatingKey,
|
||||
'parentIndex': instance.parentIndex,
|
||||
'index': instance.index,
|
||||
'grandparentTheme': instance.grandparentTheme,
|
||||
'viewOffset': instance.viewOffset,
|
||||
'viewCount': instance.viewCount,
|
||||
'leafCount': instance.leafCount,
|
||||
'viewedLeafCount': instance.viewedLeafCount,
|
||||
'childCount': instance.childCount,
|
||||
'Role': instance.role,
|
||||
'audioLanguage': instance.audioLanguage,
|
||||
'subtitleLanguage': instance.subtitleLanguage,
|
||||
'playlistItemID': instance.playlistItemID,
|
||||
'playQueueItemID': instance.playQueueItemID,
|
||||
'librarySectionID': instance.librarySectionID,
|
||||
'ratingImage': instance.ratingImage,
|
||||
'audienceRatingImage': instance.audienceRatingImage,
|
||||
'clearLogo': instance.clearLogo,
|
||||
};
|
||||
Map<String, dynamic> _$PlexMetadataToJson(PlexMetadata instance) =>
|
||||
<String, dynamic>{
|
||||
'ratingKey': instance.ratingKey,
|
||||
'key': instance.key,
|
||||
'guid': instance.guid,
|
||||
'studio': instance.studio,
|
||||
'type': instance.type,
|
||||
'title': instance.title,
|
||||
'titleSort': instance.titleSort,
|
||||
'contentRating': instance.contentRating,
|
||||
'summary': instance.summary,
|
||||
'rating': instance.rating,
|
||||
'audienceRating': instance.audienceRating,
|
||||
'year': instance.year,
|
||||
'originallyAvailableAt': instance.originallyAvailableAt,
|
||||
'thumb': instance.thumb,
|
||||
'art': instance.art,
|
||||
'duration': instance.duration,
|
||||
'addedAt': instance.addedAt,
|
||||
'updatedAt': instance.updatedAt,
|
||||
'lastViewedAt': instance.lastViewedAt,
|
||||
'grandparentTitle': instance.grandparentTitle,
|
||||
'grandparentThumb': instance.grandparentThumb,
|
||||
'grandparentArt': instance.grandparentArt,
|
||||
'grandparentRatingKey': instance.grandparentRatingKey,
|
||||
'parentTitle': instance.parentTitle,
|
||||
'parentThumb': instance.parentThumb,
|
||||
'parentRatingKey': instance.parentRatingKey,
|
||||
'parentIndex': instance.parentIndex,
|
||||
'index': instance.index,
|
||||
'grandparentTheme': instance.grandparentTheme,
|
||||
'viewOffset': instance.viewOffset,
|
||||
'viewCount': instance.viewCount,
|
||||
'leafCount': instance.leafCount,
|
||||
'viewedLeafCount': instance.viewedLeafCount,
|
||||
'childCount': instance.childCount,
|
||||
'Role': instance.role,
|
||||
'audioLanguage': instance.audioLanguage,
|
||||
'subtitleLanguage': instance.subtitleLanguage,
|
||||
'playlistItemID': instance.playlistItemID,
|
||||
'playQueueItemID': instance.playQueueItemID,
|
||||
'librarySectionID': instance.librarySectionID,
|
||||
'ratingImage': instance.ratingImage,
|
||||
'audienceRatingImage': instance.audienceRatingImage,
|
||||
'clearLogo': instance.clearLogo,
|
||||
};
|
||||
|
||||
@@ -26,22 +26,23 @@ PlexPlaylist _$PlexPlaylistFromJson(Map<String, dynamic> json) => PlexPlaylist(
|
||||
thumb: json['thumb'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$PlexPlaylistToJson(PlexPlaylist instance) => <String, dynamic>{
|
||||
'ratingKey': instance.ratingKey,
|
||||
'key': instance.key,
|
||||
'type': instance.type,
|
||||
'title': instance.title,
|
||||
'summary': instance.summary,
|
||||
'smart': instance.smart,
|
||||
'playlistType': instance.playlistType,
|
||||
'duration': instance.duration,
|
||||
'leafCount': instance.leafCount,
|
||||
'composite': instance.composite,
|
||||
'addedAt': instance.addedAt,
|
||||
'updatedAt': instance.updatedAt,
|
||||
'lastViewedAt': instance.lastViewedAt,
|
||||
'viewCount': instance.viewCount,
|
||||
'content': instance.content,
|
||||
'guid': instance.guid,
|
||||
'thumb': instance.thumb,
|
||||
};
|
||||
Map<String, dynamic> _$PlexPlaylistToJson(PlexPlaylist instance) =>
|
||||
<String, dynamic>{
|
||||
'ratingKey': instance.ratingKey,
|
||||
'key': instance.key,
|
||||
'type': instance.type,
|
||||
'title': instance.title,
|
||||
'summary': instance.summary,
|
||||
'smart': instance.smart,
|
||||
'playlistType': instance.playlistType,
|
||||
'duration': instance.duration,
|
||||
'leafCount': instance.leafCount,
|
||||
'composite': instance.composite,
|
||||
'addedAt': instance.addedAt,
|
||||
'updatedAt': instance.updatedAt,
|
||||
'lastViewedAt': instance.lastViewedAt,
|
||||
'viewCount': instance.viewCount,
|
||||
'content': instance.content,
|
||||
'guid': instance.guid,
|
||||
'thumb': instance.thumb,
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../models/companion_remote/remote_command_type.dart';
|
||||
import '../models/companion_remote/remote_session.dart';
|
||||
import '../models/companion_remote/trusted_device.dart';
|
||||
import '../services/companion_remote/companion_remote_peer_service.dart';
|
||||
import '../models/companion_remote/recent_remote_session.dart';
|
||||
import '../services/companion_remote/companion_remote_discovery_service.dart';
|
||||
import '../services/storage_service.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
@@ -99,28 +100,28 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
void _setupPeerServiceListeners() {
|
||||
_commandSubscription = _peerService!.onCommandReceived.listen((command) {
|
||||
appLogger.d('CompanionRemote: Command received: ${command.type}');
|
||||
_commandSubscription = _peerService!.onCommandReceived.listen(
|
||||
(command) {
|
||||
appLogger.d('CompanionRemote: Command received: ${command.type}');
|
||||
|
||||
if (command.type == RemoteCommandType.deviceInfo) {
|
||||
_handleDeviceInfo(command);
|
||||
} else if (command.type == RemoteCommandType.ping ||
|
||||
command.type == RemoteCommandType.pong ||
|
||||
command.type == RemoteCommandType.ack) {
|
||||
// Don't call callback for these
|
||||
} else {
|
||||
onCommandReceived?.call(command);
|
||||
}
|
||||
}, onError: (error) {
|
||||
appLogger.e('CompanionRemote: Stream error', error: error);
|
||||
});
|
||||
if (command.type == RemoteCommandType.deviceInfo) {
|
||||
_handleDeviceInfo(command);
|
||||
} else if (command.type == RemoteCommandType.ping ||
|
||||
command.type == RemoteCommandType.pong ||
|
||||
command.type == RemoteCommandType.ack) {
|
||||
// Don't call callback for these
|
||||
} else {
|
||||
onCommandReceived?.call(command);
|
||||
}
|
||||
},
|
||||
onError: (error) {
|
||||
appLogger.e('CompanionRemote: Stream error', error: error);
|
||||
},
|
||||
);
|
||||
|
||||
_deviceConnectedSubscription = _peerService!.onDeviceConnected.listen((device) async {
|
||||
appLogger.d('CompanionRemote: Device connected: ${device.name}');
|
||||
_session = _session?.copyWith(
|
||||
status: RemoteSessionStatus.connected,
|
||||
connectedDevice: device,
|
||||
);
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.connected, connectedDevice: device);
|
||||
notifyListeners();
|
||||
|
||||
await addTrustedDevice(device, requireApproval: isHost);
|
||||
@@ -129,15 +130,10 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
_deviceDisconnectedSubscription = _peerService!.onDeviceDisconnected.listen((_) {
|
||||
appLogger.d('CompanionRemote: Device disconnected (intentional: $_intentionalDisconnect)');
|
||||
if (_intentionalDisconnect) {
|
||||
_session = _session?.copyWith(
|
||||
status: RemoteSessionStatus.disconnected,
|
||||
connectedDevice: null,
|
||||
);
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.disconnected, connectedDevice: null);
|
||||
notifyListeners();
|
||||
} else {
|
||||
_session = _session?.copyWith(
|
||||
status: RemoteSessionStatus.reconnecting,
|
||||
);
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.reconnecting);
|
||||
notifyListeners();
|
||||
_scheduleReconnect();
|
||||
}
|
||||
@@ -145,10 +141,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
|
||||
_errorSubscription = _peerService!.onError.listen((error) {
|
||||
appLogger.e('CompanionRemote: Error: ${error.message}');
|
||||
_session = _session?.copyWith(
|
||||
status: RemoteSessionStatus.error,
|
||||
errorMessage: error.message,
|
||||
);
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: error.message);
|
||||
notifyListeners();
|
||||
});
|
||||
|
||||
@@ -166,11 +159,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
|
||||
appLogger.d('CompanionRemote: Device info - name: ${command.deviceName}, platform: $platform, role: $role');
|
||||
|
||||
final device = RemoteDevice(
|
||||
id: command.deviceId,
|
||||
name: command.deviceName,
|
||||
platform: platform,
|
||||
);
|
||||
final device = RemoteDevice(id: command.deviceId, name: command.deviceName, platform: platform);
|
||||
|
||||
_session = _session?.copyWith(connectedDevice: device);
|
||||
notifyListeners();
|
||||
@@ -256,10 +245,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
appLogger.d('CompanionRemote: Successfully joined session');
|
||||
} catch (e) {
|
||||
appLogger.e('CompanionRemote: Failed to join session', error: e);
|
||||
_session = _session?.copyWith(
|
||||
status: RemoteSessionStatus.error,
|
||||
errorMessage: e.toString(),
|
||||
);
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: e.toString());
|
||||
notifyListeners();
|
||||
rethrow;
|
||||
}
|
||||
@@ -305,10 +291,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
Future<void> _attemptReconnect() async {
|
||||
if (_lastSessionId == null || _lastPin == null) {
|
||||
appLogger.w('CompanionRemote: No stored credentials for reconnect');
|
||||
_session = _session?.copyWith(
|
||||
status: RemoteSessionStatus.error,
|
||||
errorMessage: 'Connection lost',
|
||||
);
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: 'Connection lost');
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
@@ -347,10 +330,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
void cancelReconnect() {
|
||||
_reconnectTimer?.cancel();
|
||||
_reconnectAttempts = 0;
|
||||
_session = _session?.copyWith(
|
||||
status: RemoteSessionStatus.disconnected,
|
||||
connectedDevice: null,
|
||||
);
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.disconnected, connectedDevice: null);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -379,9 +359,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
if (json != null) {
|
||||
final List<dynamic> list = jsonDecode(json);
|
||||
_trustedDevices.clear();
|
||||
_trustedDevices.addAll(
|
||||
list.map((e) => TrustedDevice.fromJson(e as Map<String, dynamic>)),
|
||||
);
|
||||
_trustedDevices.addAll(list.map((e) => TrustedDevice.fromJson(e as Map<String, dynamic>)));
|
||||
appLogger.d('CompanionRemote: Loaded ${_trustedDevices.length} trusted devices');
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -424,12 +402,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
_trustedDevices.add(
|
||||
TrustedDevice(
|
||||
peerId: device.id,
|
||||
deviceName: device.name,
|
||||
platform: device.platform,
|
||||
isApproved: approved,
|
||||
),
|
||||
TrustedDevice(peerId: device.id, deviceName: device.name, platform: device.platform, isApproved: approved),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../providers/companion_remote_provider.dart';
|
||||
import '../../services/companion_remote/companion_remote_discovery_service.dart';
|
||||
import '../../models/companion_remote/recent_remote_session.dart';
|
||||
import '../../utils/app_logger.dart';
|
||||
|
||||
class PairingScreen extends StatefulWidget {
|
||||
@@ -105,10 +105,7 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
|
||||
try {
|
||||
final provider = context.read<CompanionRemoteProvider>();
|
||||
await provider.joinSession(
|
||||
_sessionIdController.text.trim().toUpperCase(),
|
||||
_pinController.text.trim(),
|
||||
);
|
||||
await provider.joinSession(_sessionIdController.text.trim().toUpperCase(), _pinController.text.trim());
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
@@ -198,22 +195,10 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
children: [
|
||||
SegmentedButton<int>(
|
||||
segments: [
|
||||
const ButtonSegment(
|
||||
value: 0,
|
||||
label: Text('Recent'),
|
||||
icon: Icon(Icons.history),
|
||||
),
|
||||
const ButtonSegment(value: 0, label: Text('Recent'), icon: Icon(Icons.history)),
|
||||
if (_isMobile)
|
||||
ButtonSegment(
|
||||
value: _scanTabIndex,
|
||||
label: const Text('Scan'),
|
||||
icon: const Icon(Icons.qr_code_scanner),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: _manualTabIndex,
|
||||
label: const Text('Manual'),
|
||||
icon: const Icon(Icons.keyboard),
|
||||
),
|
||||
ButtonSegment(value: _scanTabIndex, label: const Text('Scan'), icon: const Icon(Icons.qr_code_scanner)),
|
||||
ButtonSegment(value: _manualTabIndex, label: const Text('Manual'), icon: const Icon(Icons.keyboard)),
|
||||
],
|
||||
selected: {_selectedTab},
|
||||
onSelectionChanged: (Set<int> selection) {
|
||||
@@ -222,9 +207,7 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
});
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: _buildTabContent(),
|
||||
),
|
||||
Expanded(child: _buildTabContent()),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -344,27 +327,16 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
if (_isDiscovering) ...[
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Loading...',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text('Loading...', style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center),
|
||||
] else if (sessions.isEmpty) ...[
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.devices_other,
|
||||
size: 48,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
Icon(Icons.devices_other, size: 48, color: Theme.of(context).colorScheme.outline),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'No recent connections',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
Text('No recent connections', style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Connect to a device using Manual entry to get started',
|
||||
@@ -390,11 +362,7 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
),
|
||||
isThreeLine: true,
|
||||
trailing: isThisConnecting
|
||||
? const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2))
|
||||
: const Icon(Icons.arrow_forward),
|
||||
onTap: _isConnecting ? null : () => _connectToRecentSession(session),
|
||||
onLongPress: () => _showRemoveSessionDialog(session),
|
||||
@@ -410,17 +378,12 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.onErrorContainer),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_errorMessage!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onErrorContainer),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -459,14 +422,8 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
title: const Text('Remove Recent Connection'),
|
||||
content: Text('Remove "${session.deviceName}" from recent connections?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('Remove'),
|
||||
),
|
||||
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
|
||||
TextButton(onPressed: () => Navigator.pop(context, true), child: const Text('Remove')),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -486,11 +443,7 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
children: [
|
||||
const Icon(Icons.keyboard, size: 64, color: Colors.blue),
|
||||
const SizedBox(height: 24),
|
||||
Text(
|
||||
'Pair with Desktop',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Text('Pair with Desktop', style: Theme.of(context).textTheme.headlineMedium, textAlign: TextAlign.center),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Enter the session details shown on your desktop device',
|
||||
@@ -505,17 +458,12 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.onErrorContainer),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_errorMessage!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onErrorContainer),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -571,10 +519,7 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(6),
|
||||
],
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6)],
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter a PIN';
|
||||
@@ -590,21 +535,14 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
FilledButton.icon(
|
||||
onPressed: _isConnecting ? null : _connect,
|
||||
icon: _isConnecting
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
|
||||
: const Icon(Icons.link),
|
||||
label: Text(_isConnecting ? 'Connecting...' : 'Connect'),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
const Divider(),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Tips',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
Text('Tips', style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
_buildTipCard(
|
||||
context,
|
||||
@@ -620,15 +558,11 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
_buildTipCard(
|
||||
context,
|
||||
Icons.wifi,
|
||||
'Make sure both devices are on the same WiFi network',
|
||||
),
|
||||
],
|
||||
),
|
||||
_buildTipCard(context, Icons.wifi, 'Make sure both devices are on the same WiFi network'),
|
||||
],
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTipCard(BuildContext context, IconData icon, String text) {
|
||||
@@ -639,12 +573,7 @@ class _PairingScreenState extends State<PairingScreen> {
|
||||
children: [
|
||||
Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
text,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
Expanded(child: Text(text, style: Theme.of(context).textTheme.bodySmall)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,65 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import '../../models/companion_remote/recent_remote_session.dart';
|
||||
import '../../services/storage_service.dart';
|
||||
import '../../utils/app_logger.dart';
|
||||
|
||||
/// Recent Companion Remote session for quick reconnection
|
||||
class RecentRemoteSession {
|
||||
final String sessionId;
|
||||
final String pin;
|
||||
final String deviceName;
|
||||
final String platform;
|
||||
final DateTime lastConnected;
|
||||
|
||||
RecentRemoteSession({
|
||||
required this.sessionId,
|
||||
required this.pin,
|
||||
required this.deviceName,
|
||||
required this.platform,
|
||||
required this.lastConnected,
|
||||
});
|
||||
|
||||
factory RecentRemoteSession.fromJson(Map<String, dynamic> json) {
|
||||
return RecentRemoteSession(
|
||||
sessionId: json['sessionId'] as String,
|
||||
pin: json['pin'] as String,
|
||||
deviceName: json['deviceName'] as String,
|
||||
platform: json['platform'] as String,
|
||||
lastConnected: DateTime.parse(json['lastConnected'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'sessionId': sessionId,
|
||||
'pin': pin,
|
||||
'deviceName': deviceName,
|
||||
'platform': platform,
|
||||
'lastConnected': lastConnected.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Create from QR code data (format: "sessionId:pin:deviceName:platform")
|
||||
factory RecentRemoteSession.fromQrData(String qrData) {
|
||||
final parts = qrData.split(':');
|
||||
if (parts.length < 2) {
|
||||
throw FormatException('Invalid QR code format');
|
||||
}
|
||||
|
||||
return RecentRemoteSession(
|
||||
sessionId: parts[0],
|
||||
pin: parts[1],
|
||||
deviceName: parts.length > 2 ? parts[2] : 'Unknown Device',
|
||||
platform: parts.length > 3 ? parts[3] : 'unknown',
|
||||
lastConnected: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => '$deviceName ($platform) - Last: ${lastConnected.toLocal()}';
|
||||
}
|
||||
|
||||
/// Service for managing recent Companion Remote sessions
|
||||
class CompanionRemoteDiscoveryService {
|
||||
static const String _storageKey = 'companion_remote_recent_sessions';
|
||||
@@ -87,9 +32,7 @@ class CompanionRemoteDiscoveryService {
|
||||
if (json != null) {
|
||||
final List<dynamic> list = jsonDecode(json);
|
||||
_recentSessions.clear();
|
||||
_recentSessions.addAll(
|
||||
list.map((e) => RecentRemoteSession.fromJson(e as Map<String, dynamic>)),
|
||||
);
|
||||
_recentSessions.addAll(list.map((e) => RecentRemoteSession.fromJson(e as Map<String, dynamic>)));
|
||||
|
||||
// Sort by last connected (most recent first)
|
||||
_recentSessions.sort((a, b) => b.lastConnected.compareTo(a.lastConnected));
|
||||
|
||||
+2
-2
@@ -60,7 +60,7 @@ PODS:
|
||||
- wakelock_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- WebRTC-SDK (137.7151.04)
|
||||
- window_manager (0.2.0):
|
||||
- window_manager (0.5.0):
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
@@ -146,7 +146,7 @@ SPEC CHECKSUMS:
|
||||
url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd
|
||||
wakelock_plus: 917609be14d812ddd9e9528876538b2263aaa03b
|
||||
WebRTC-SDK: 40d4f5ba05cadff14e4db5614aec402a633f007e
|
||||
window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c
|
||||
window_manager: b729e31d38fb04905235df9ea896128991cad99e
|
||||
|
||||
PODFILE CHECKSUM: d16f5e5d196d1ca9863d7a71d5885cad7ffa7d2d
|
||||
|
||||
|
||||
Reference in New Issue
Block a user