feat: player-aware companion remote with syncState, track cycling
This commit is contained in:
@@ -21,6 +21,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>13.0</string>
|
<string>15.5</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
platform :ios, '14.0'
|
platform :ios, '15.5'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|||||||
+76
-1
@@ -6,8 +6,56 @@ PODS:
|
|||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
|
- GoogleDataTransport (10.1.0):
|
||||||
|
- nanopb (~> 3.30910.0)
|
||||||
|
- PromisesObjC (~> 2.4)
|
||||||
|
- GoogleMLKit/BarcodeScanning (7.0.0):
|
||||||
|
- GoogleMLKit/MLKitCore
|
||||||
|
- MLKitBarcodeScanning (~> 6.0.0)
|
||||||
|
- GoogleMLKit/MLKitCore (7.0.0):
|
||||||
|
- MLKitCommon (~> 12.0.0)
|
||||||
|
- GoogleToolboxForMac/Defines (4.2.1)
|
||||||
|
- GoogleToolboxForMac/Logger (4.2.1):
|
||||||
|
- GoogleToolboxForMac/Defines (= 4.2.1)
|
||||||
|
- "GoogleToolboxForMac/NSData+zlib (4.2.1)":
|
||||||
|
- GoogleToolboxForMac/Defines (= 4.2.1)
|
||||||
|
- GoogleUtilities/Environment (8.1.0):
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Logger (8.1.0):
|
||||||
|
- GoogleUtilities/Environment
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GoogleUtilities/Privacy (8.1.0)
|
||||||
|
- GoogleUtilities/UserDefaults (8.1.0):
|
||||||
|
- GoogleUtilities/Logger
|
||||||
|
- GoogleUtilities/Privacy
|
||||||
|
- GTMSessionFetcher/Core (3.5.0)
|
||||||
- in_app_review (2.0.0):
|
- in_app_review (2.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- MLImage (1.0.0-beta6)
|
||||||
|
- MLKitBarcodeScanning (6.0.0):
|
||||||
|
- MLKitCommon (~> 12.0)
|
||||||
|
- MLKitVision (~> 8.0)
|
||||||
|
- MLKitCommon (12.0.0):
|
||||||
|
- GoogleDataTransport (~> 10.0)
|
||||||
|
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
||||||
|
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
||||||
|
- GoogleUtilities/Logger (~> 8.0)
|
||||||
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
|
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
||||||
|
- MLKitVision (8.0.0):
|
||||||
|
- GoogleToolboxForMac/Logger (< 5.0, >= 4.2.1)
|
||||||
|
- "GoogleToolboxForMac/NSData+zlib (< 5.0, >= 4.2.1)"
|
||||||
|
- GTMSessionFetcher/Core (< 4.0, >= 3.3.2)
|
||||||
|
- MLImage (= 1.0.0-beta6)
|
||||||
|
- MLKitCommon (~> 12.0)
|
||||||
|
- mobile_scanner (6.0.2):
|
||||||
|
- Flutter
|
||||||
|
- GoogleMLKit/BarcodeScanning (~> 7.0.0)
|
||||||
|
- nanopb (3.30910.0):
|
||||||
|
- nanopb/decode (= 3.30910.0)
|
||||||
|
- nanopb/encode (= 3.30910.0)
|
||||||
|
- nanopb/decode (3.30910.0)
|
||||||
|
- nanopb/encode (3.30910.0)
|
||||||
- os_media_controls (0.0.1):
|
- os_media_controls (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
@@ -15,6 +63,7 @@ PODS:
|
|||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- PromisesObjC (2.4.0)
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@@ -61,6 +110,7 @@ DEPENDENCIES:
|
|||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
||||||
|
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
|
||||||
- os_media_controls (from `.symlinks/plugins/os_media_controls/ios`)
|
- os_media_controls (from `.symlinks/plugins/os_media_controls/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
@@ -74,6 +124,17 @@ DEPENDENCIES:
|
|||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
|
- GoogleDataTransport
|
||||||
|
- GoogleMLKit
|
||||||
|
- GoogleToolboxForMac
|
||||||
|
- GoogleUtilities
|
||||||
|
- GTMSessionFetcher
|
||||||
|
- MLImage
|
||||||
|
- MLKitBarcodeScanning
|
||||||
|
- MLKitCommon
|
||||||
|
- MLKitVision
|
||||||
|
- nanopb
|
||||||
|
- PromisesObjC
|
||||||
- sqlite3
|
- sqlite3
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
@@ -87,6 +148,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
in_app_review:
|
in_app_review:
|
||||||
:path: ".symlinks/plugins/in_app_review/ios"
|
:path: ".symlinks/plugins/in_app_review/ios"
|
||||||
|
mobile_scanner:
|
||||||
|
:path: ".symlinks/plugins/mobile_scanner/ios"
|
||||||
os_media_controls:
|
os_media_controls:
|
||||||
:path: ".symlinks/plugins/os_media_controls/ios"
|
:path: ".symlinks/plugins/os_media_controls/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
@@ -113,10 +176,22 @@ SPEC CHECKSUMS:
|
|||||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||||
file_picker: 8fc6fe5e42585a217d44d22f79ec046cb8d81140
|
file_picker: 8fc6fe5e42585a217d44d22f79ec046cb8d81140
|
||||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
|
GoogleMLKit: eff9e23ec1d90ea4157a1ee2e32a4f610c5b3318
|
||||||
|
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
|
||||||
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
|
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
||||||
in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937
|
in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937
|
||||||
|
MLImage: 0ad1c5f50edd027672d8b26b0fee78a8b4a0fc56
|
||||||
|
MLKitBarcodeScanning: 0a3064da0a7f49ac24ceb3cb46a5bc67496facd2
|
||||||
|
MLKitCommon: 07c2c33ae5640e5380beaaa6e4b9c249a205542d
|
||||||
|
MLKitVision: 45e79d68845a2de77e2dd4d7f07947f0ed157b0e
|
||||||
|
mobile_scanner: af8f71879eaba2bbcb4d86c6a462c3c0e7f23036
|
||||||
|
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||||
os_media_controls: 86dceab6245a5325af90fc0fdebe243c42d789b4
|
os_media_controls: 86dceab6245a5325af90fc0fdebe243c42d789b4
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||||
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
||||||
@@ -126,6 +201,6 @@ SPEC CHECKSUMS:
|
|||||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||||
workmanager_apple: 904529ae31e97fc5be632cf628507652294a0778
|
workmanager_apple: 904529ae31e97fc5be632cf628507652294a0778
|
||||||
|
|
||||||
PODFILE CHECKSUM: faeab82d8b6e3c9d039ddf62b7666988a3dea540
|
PODFILE CHECKSUM: 16bb7f67e16d8aaa2bfb97666803fbfb291b6559
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -214,6 +214,7 @@
|
|||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
2E10BAE564A4AFFCFF5B4EF0 /* [CP] Embed Pods Frameworks */,
|
2E10BAE564A4AFFCFF5B4EF0 /* [CP] Embed Pods Frameworks */,
|
||||||
|
BE3E47DA555D39F032C6A1DB /* [CP] Copy Pods Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@@ -380,6 +381,23 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
};
|
};
|
||||||
|
BE3E47DA555D39F032C6A1DB /* [CP] Copy Pods Resources */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Copy Pods Resources";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
@@ -475,7 +493,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -496,7 +514,7 @@
|
|||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Plezy;
|
INFOPLIST_KEY_CFBundleDisplayName = Plezy;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -609,7 +627,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -660,7 +678,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SUPPORTED_PLATFORMS = iphoneos;
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
@@ -683,7 +701,7 @@
|
|||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Plezy;
|
INFOPLIST_KEY_CFBundleDisplayName = Plezy;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -710,7 +728,7 @@
|
|||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Plezy;
|
INFOPLIST_KEY_CFBundleDisplayName = Plezy;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
|||||||
@@ -49,4 +49,5 @@ enum RemoteCommandType {
|
|||||||
deviceInfo,
|
deviceInfo,
|
||||||
disconnect,
|
disconnect,
|
||||||
ack,
|
ack,
|
||||||
|
syncState,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
String _platform = 'unknown';
|
String _platform = 'unknown';
|
||||||
final List<TrustedDevice> _trustedDevices = [];
|
final List<TrustedDevice> _trustedDevices = [];
|
||||||
final List<RecentRemoteSession> _recentSessions = [];
|
final List<RecentRemoteSession> _recentSessions = [];
|
||||||
|
bool _isPlayerActive = false;
|
||||||
|
|
||||||
static const String _storageKey = 'companion_remote_trusted_devices';
|
static const String _storageKey = 'companion_remote_trusted_devices';
|
||||||
static const String _lastDeviceKey = 'companion_remote_last_device';
|
static const String _lastDeviceKey = 'companion_remote_last_device';
|
||||||
@@ -61,6 +62,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
RemoteDevice? get connectedDevice => _session?.connectedDevice;
|
RemoteDevice? get connectedDevice => _session?.connectedDevice;
|
||||||
List<TrustedDevice> get trustedDevices => List.unmodifiable(_trustedDevices);
|
List<TrustedDevice> get trustedDevices => List.unmodifiable(_trustedDevices);
|
||||||
List<RecentRemoteSession> get recentSessions => List.unmodifiable(_recentSessions);
|
List<RecentRemoteSession> get recentSessions => List.unmodifiable(_recentSessions);
|
||||||
|
bool get isPlayerActive => _isPlayerActive;
|
||||||
|
|
||||||
CompanionRemoteProvider() {
|
CompanionRemoteProvider() {
|
||||||
_initializeDeviceInfo();
|
_initializeDeviceInfo();
|
||||||
@@ -108,6 +110,8 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
|
|
||||||
if (command.type == RemoteCommandType.deviceInfo) {
|
if (command.type == RemoteCommandType.deviceInfo) {
|
||||||
_handleDeviceInfo(command);
|
_handleDeviceInfo(command);
|
||||||
|
} else if (command.type == RemoteCommandType.syncState) {
|
||||||
|
_handleSyncState(command);
|
||||||
} else if (command.type == RemoteCommandType.ping ||
|
} else if (command.type == RemoteCommandType.ping ||
|
||||||
command.type == RemoteCommandType.pong ||
|
command.type == RemoteCommandType.pong ||
|
||||||
command.type == RemoteCommandType.ack) {
|
command.type == RemoteCommandType.ack) {
|
||||||
@@ -185,6 +189,14 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handleSyncState(RemoteCommand command) {
|
||||||
|
final playerActive = command.data?['playerActive'] as bool? ?? false;
|
||||||
|
if (_isPlayerActive != playerActive) {
|
||||||
|
_isPlayerActive = playerActive;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _cleanupSubscriptions() {
|
void _cleanupSubscriptions() {
|
||||||
_commandSubscription?.cancel();
|
_commandSubscription?.cancel();
|
||||||
_commandSubscription = null;
|
_commandSubscription = null;
|
||||||
@@ -363,6 +375,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
_cleanupSubscriptions();
|
_cleanupSubscriptions();
|
||||||
|
|
||||||
_session = null;
|
_session = null;
|
||||||
|
_isPlayerActive = false;
|
||||||
_intentionalDisconnect = false;
|
_intentionalDisconnect = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,10 +211,11 @@ class _RemoteControlContentState extends State<_RemoteControlContent> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
SegmentedButton<int>(
|
SegmentedButton<int>(
|
||||||
|
showSelectedIcon: false,
|
||||||
segments: const [
|
segments: const [
|
||||||
ButtonSegment(value: 0, label: Text('Navigate'), icon: Icon(Icons.navigation)),
|
ButtonSegment(value: 0, label: Text('Remote'), icon: Icon(Icons.navigation)),
|
||||||
ButtonSegment(value: 1, label: Text('Playback'), icon: Icon(Icons.play_arrow)),
|
ButtonSegment(value: 1, label: Text('Play'), icon: Icon(Icons.play_arrow)),
|
||||||
ButtonSegment(value: 2, label: Text('Quick'), icon: Icon(Icons.flash_on)),
|
ButtonSegment(value: 2, label: Text('More'), icon: Icon(Icons.flash_on)),
|
||||||
],
|
],
|
||||||
selected: {_selectedTab},
|
selected: {_selectedTab},
|
||||||
onSelectionChanged: (Set<int> selection) {
|
onSelectionChanged: (Set<int> selection) {
|
||||||
@@ -236,6 +237,8 @@ class _RemoteControlContentState extends State<_RemoteControlContent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildNavigationTab() {
|
Widget _buildNavigationTab() {
|
||||||
|
final isPlayerActive = context.watch<CompanionRemoteProvider>().isPlayerActive;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -253,6 +256,7 @@ class _RemoteControlContentState extends State<_RemoteControlContent> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
Center(child: _DPad(onCommand: _sendCommand)),
|
Center(child: _DPad(onCommand: _sendCommand)),
|
||||||
|
if (!isPlayerActive) ...[
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
Text('Tab Navigation', style: Theme.of(context).textTheme.titleMedium),
|
Text('Tab Navigation', style: Theme.of(context).textTheme.titleMedium),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -289,6 +293,7 @@ class _RemoteControlContentState extends State<_RemoteControlContent> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,6 +374,8 @@ class _RemoteControlContentState extends State<_RemoteControlContent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildQuickActionsTab() {
|
Widget _buildQuickActionsTab() {
|
||||||
|
final isPlayerActive = context.watch<CompanionRemoteProvider>().isPlayerActive;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -377,12 +384,25 @@ class _RemoteControlContentState extends State<_RemoteControlContent> {
|
|||||||
runSpacing: 12,
|
runSpacing: 12,
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
if (!isPlayerActive)
|
||||||
_RemoteCard(icon: Icons.search, label: 'Search', onPressed: _showSearchSheet),
|
_RemoteCard(icon: Icons.search, label: 'Search', onPressed: _showSearchSheet),
|
||||||
|
if (isPlayerActive) ...[
|
||||||
_RemoteCard(
|
_RemoteCard(
|
||||||
icon: Icons.fullscreen,
|
icon: Icons.fullscreen,
|
||||||
label: 'Fullscreen',
|
label: 'Fullscreen',
|
||||||
onPressed: () => _sendCommand(RemoteCommandType.fullscreen),
|
onPressed: () => _sendCommand(RemoteCommandType.fullscreen),
|
||||||
),
|
),
|
||||||
|
_RemoteCard(
|
||||||
|
icon: Icons.subtitles,
|
||||||
|
label: 'Subtitles',
|
||||||
|
onPressed: () => _sendCommand(RemoteCommandType.subtitles),
|
||||||
|
),
|
||||||
|
_RemoteCard(
|
||||||
|
icon: Icons.audiotrack,
|
||||||
|
label: 'Audio',
|
||||||
|
onPressed: () => _sendCommand(RemoteCommandType.audioTracks),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -397,16 +417,17 @@ class _DPad extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const size = 80.0;
|
const size = 72.0;
|
||||||
const centerSize = 60.0;
|
const gap = 8.0;
|
||||||
|
const total = size * 3 + gap * 2;
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: size * 3,
|
width: total,
|
||||||
height: size * 3,
|
height: total,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned(
|
||||||
left: size,
|
left: size + gap,
|
||||||
top: 0,
|
top: 0,
|
||||||
child: _DPadButton(
|
child: _DPadButton(
|
||||||
icon: Icons.arrow_drop_up,
|
icon: Icons.arrow_drop_up,
|
||||||
@@ -415,7 +436,7 @@ class _DPad extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
left: size,
|
left: size + gap,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: _DPadButton(
|
child: _DPadButton(
|
||||||
icon: Icons.arrow_drop_down,
|
icon: Icons.arrow_drop_down,
|
||||||
@@ -425,7 +446,7 @@ class _DPad extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
top: size,
|
top: size + gap,
|
||||||
child: _DPadButton(
|
child: _DPadButton(
|
||||||
icon: Icons.arrow_left,
|
icon: Icons.arrow_left,
|
||||||
onPressed: () => onCommand(RemoteCommandType.dpadLeft),
|
onPressed: () => onCommand(RemoteCommandType.dpadLeft),
|
||||||
@@ -434,7 +455,7 @@ class _DPad extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 0,
|
right: 0,
|
||||||
top: size,
|
top: size + gap,
|
||||||
child: _DPadButton(
|
child: _DPadButton(
|
||||||
icon: Icons.arrow_right,
|
icon: Icons.arrow_right,
|
||||||
onPressed: () => onCommand(RemoteCommandType.dpadRight),
|
onPressed: () => onCommand(RemoteCommandType.dpadRight),
|
||||||
@@ -442,13 +463,13 @@ class _DPad extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
left: (size * 3 - centerSize) / 2,
|
left: size + gap,
|
||||||
top: (size * 3 - centerSize) / 2,
|
top: size + gap,
|
||||||
child: _DPadButton(
|
child: _DPadButton(
|
||||||
icon: Icons.check,
|
icon: Icons.check,
|
||||||
label: 'OK',
|
label: 'OK',
|
||||||
onPressed: () => onCommand(RemoteCommandType.select),
|
onPressed: () => onCommand(RemoteCommandType.select),
|
||||||
size: centerSize,
|
size: size,
|
||||||
isPrimary: true,
|
isPrimary: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ import '../models/plex_media_info.dart';
|
|||||||
import '../providers/download_provider.dart';
|
import '../providers/download_provider.dart';
|
||||||
import '../providers/multi_server_provider.dart';
|
import '../providers/multi_server_provider.dart';
|
||||||
import '../providers/playback_state_provider.dart';
|
import '../providers/playback_state_provider.dart';
|
||||||
|
import '../models/companion_remote/remote_command_type.dart';
|
||||||
|
import '../providers/companion_remote_provider.dart';
|
||||||
import '../services/companion_remote/companion_remote_receiver.dart';
|
import '../services/companion_remote/companion_remote_receiver.dart';
|
||||||
|
import '../services/macos_window_service.dart';
|
||||||
import '../services/discord_rpc_service.dart';
|
import '../services/discord_rpc_service.dart';
|
||||||
import '../services/episode_navigation_service.dart';
|
import '../services/episode_navigation_service.dart';
|
||||||
import '../services/media_controls_manager.dart';
|
import '../services/media_controls_manager.dart';
|
||||||
@@ -45,6 +48,7 @@ import '../utils/platform_detector.dart';
|
|||||||
import '../utils/provider_extensions.dart';
|
import '../utils/provider_extensions.dart';
|
||||||
import '../utils/language_codes.dart';
|
import '../utils/language_codes.dart';
|
||||||
import '../utils/snackbar_helper.dart';
|
import '../utils/snackbar_helper.dart';
|
||||||
|
import '../utils/track_label_builder.dart' as tlb;
|
||||||
import '../utils/plex_url_helper.dart';
|
import '../utils/plex_url_helper.dart';
|
||||||
import '../utils/video_player_navigation.dart';
|
import '../utils/video_player_navigation.dart';
|
||||||
import '../widgets/video_controls/video_controls.dart';
|
import '../widgets/video_controls/video_controls.dart';
|
||||||
@@ -134,6 +138,10 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
|||||||
// Watch Together provider reference (stored early to use in dispose)
|
// Watch Together provider reference (stored early to use in dispose)
|
||||||
WatchTogetherProvider? _watchTogetherProvider;
|
WatchTogetherProvider? _watchTogetherProvider;
|
||||||
|
|
||||||
|
// Companion remote state (stored early for use in dispose)
|
||||||
|
CompanionRemoteProvider? _companionRemoteProvider;
|
||||||
|
VoidCallback? _savedOnHome;
|
||||||
|
|
||||||
/// Get the correct PlexClient for this metadata's server
|
/// Get the correct PlexClient for this metadata's server
|
||||||
PlexClient _getClientForMetadata(BuildContext context) {
|
PlexClient _getClientForMetadata(BuildContext context) {
|
||||||
return context.getClientForServer(widget.metadata.serverId!);
|
return context.getClientForServer(widget.metadata.serverId!);
|
||||||
@@ -1174,6 +1182,24 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
|||||||
player!.setVolume(newVolume);
|
player!.setVolume(newVolume);
|
||||||
settings.setVolume(newVolume);
|
settings.setVolume(newVolume);
|
||||||
};
|
};
|
||||||
|
receiver.onSubtitles = _cycleSubtitleTrack;
|
||||||
|
receiver.onAudioTracks = _cycleAudioTrack;
|
||||||
|
receiver.onFullscreen = _toggleFullscreen;
|
||||||
|
|
||||||
|
// Override home to exit the player first (main screen handler runs after pop)
|
||||||
|
_savedOnHome = receiver.onHome;
|
||||||
|
receiver.onHome = () {
|
||||||
|
if (mounted) _handleBackButton();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Store provider reference for use in dispose and notify remote
|
||||||
|
try {
|
||||||
|
_companionRemoteProvider = context.read<CompanionRemoteProvider>();
|
||||||
|
_companionRemoteProvider!.sendCommand(
|
||||||
|
RemoteCommandType.syncState,
|
||||||
|
data: {'playerActive': true},
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _cleanupCompanionRemoteCallbacks() {
|
void _cleanupCompanionRemoteCallbacks() {
|
||||||
@@ -1186,6 +1212,84 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
|||||||
receiver.onVolumeUp = null;
|
receiver.onVolumeUp = null;
|
||||||
receiver.onVolumeDown = null;
|
receiver.onVolumeDown = null;
|
||||||
receiver.onVolumeMute = null;
|
receiver.onVolumeMute = null;
|
||||||
|
receiver.onSubtitles = null;
|
||||||
|
receiver.onAudioTracks = null;
|
||||||
|
receiver.onFullscreen = null;
|
||||||
|
receiver.onHome = _savedOnHome;
|
||||||
|
_savedOnHome = null;
|
||||||
|
|
||||||
|
// Notify remote that player is no longer active
|
||||||
|
_companionRemoteProvider?.sendCommand(
|
||||||
|
RemoteCommandType.syncState,
|
||||||
|
data: {'playerActive': false},
|
||||||
|
);
|
||||||
|
_companionRemoteProvider = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cycleSubtitleTrack() {
|
||||||
|
if (player == null) return;
|
||||||
|
final tracks = player!.state.tracks.subtitle.where((t) => t.id != 'auto').toList();
|
||||||
|
if (tracks.isEmpty) return;
|
||||||
|
|
||||||
|
final current = player!.state.track.subtitle;
|
||||||
|
// tracks includes 'no' (off). Find current index and advance.
|
||||||
|
final currentIndex = tracks.indexWhere((t) => t.id == current?.id);
|
||||||
|
final nextIndex = (currentIndex + 1) % tracks.length;
|
||||||
|
final next = tracks[nextIndex];
|
||||||
|
player!.selectSubtitleTrack(next);
|
||||||
|
_onSubtitleTrackChanged(next);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
final label = next.id == 'no'
|
||||||
|
? 'Subtitles: Off'
|
||||||
|
: 'Subtitles: ${tlb.TrackLabelBuilder.buildSubtitleLabel(title: next.title, language: next.language, codec: next.codec, index: nextIndex)}';
|
||||||
|
showAppSnackBar(context, label, duration: const Duration(seconds: 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cycleAudioTrack() {
|
||||||
|
if (player == null) return;
|
||||||
|
final tracks = player!.state.tracks.audio.where((t) => t.id != 'auto' && t.id != 'no').toList();
|
||||||
|
if (tracks.length <= 1) return;
|
||||||
|
|
||||||
|
final current = player!.state.track.audio;
|
||||||
|
final currentIndex = tracks.indexWhere((t) => t.id == current?.id);
|
||||||
|
final nextIndex = (currentIndex + 1) % tracks.length;
|
||||||
|
final next = tracks[nextIndex];
|
||||||
|
player!.selectAudioTrack(next);
|
||||||
|
_onAudioTrackChanged(next);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
final label = 'Audio: ${tlb.TrackLabelBuilder.buildAudioLabel(title: next.title, language: next.language, codec: next.codec, channelsCount: next.channelsCount, index: nextIndex)}';
|
||||||
|
showAppSnackBar(context, label, duration: const Duration(seconds: 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _toggleFullscreen() async {
|
||||||
|
if (PlatformDetector.isMobile(context)) return;
|
||||||
|
final isCurrentlyFullscreen = await windowManager.isFullScreen();
|
||||||
|
if (Platform.isMacOS) {
|
||||||
|
if (isCurrentlyFullscreen) {
|
||||||
|
await MacOSWindowService.exitFullscreen();
|
||||||
|
} else {
|
||||||
|
await MacOSWindowService.enterFullscreen();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await windowManager.setFullScreen(!isCurrentlyFullscreen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Exit fullscreen before leaving the player (Windows/Linux only).
|
||||||
|
/// macOS is excluded because we can't distinguish native fullscreen
|
||||||
|
/// from maximized state, so we leave the window state unchanged.
|
||||||
|
Future<void> _exitFullscreenIfNeeded() async {
|
||||||
|
if (Platform.isWindows || Platform.isLinux) {
|
||||||
|
final isFullscreen = await windowManager.isFullScreen();
|
||||||
|
if (isFullscreen) {
|
||||||
|
await windowManager.setFullScreen(false);
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle back button press
|
/// Handle back button press
|
||||||
@@ -1207,16 +1311,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
|||||||
if (confirmed && mounted) {
|
if (confirmed && mounted) {
|
||||||
await _watchTogetherProvider!.leaveSession();
|
await _watchTogetherProvider!.leaveSession();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
// Exit fullscreen before leaving player (Windows/Linux only)
|
await _exitFullscreenIfNeeded();
|
||||||
if (Platform.isWindows || Platform.isLinux) {
|
|
||||||
final isFullscreen = await windowManager.isFullScreen();
|
|
||||||
if (isFullscreen) {
|
|
||||||
await windowManager.setFullScreen(false);
|
|
||||||
// Wait for a frame to allow window manager to process the fullscreen exit
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
if (!mounted) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_isExiting.value = true;
|
_isExiting.value = true;
|
||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(true);
|
||||||
@@ -1225,16 +1320,7 @@ class VideoPlayerScreenState extends State<VideoPlayerScreen> with WidgetsBindin
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit fullscreen before leaving player (Windows/Linux only)
|
await _exitFullscreenIfNeeded();
|
||||||
if (Platform.isWindows || Platform.isLinux) {
|
|
||||||
final isFullscreen = await windowManager.isFullScreen();
|
|
||||||
if (isFullscreen) {
|
|
||||||
await windowManager.setFullScreen(false);
|
|
||||||
// Wait for a frame to allow window manager to process the fullscreen exit
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
if (!mounted) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default behavior for hosts or non-session users
|
// Default behavior for hosts or non-session users
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ class CompanionRemoteReceiver {
|
|||||||
VoidCallback? onVolumeUp;
|
VoidCallback? onVolumeUp;
|
||||||
VoidCallback? onVolumeDown;
|
VoidCallback? onVolumeDown;
|
||||||
VoidCallback? onVolumeMute;
|
VoidCallback? onVolumeMute;
|
||||||
|
VoidCallback? onSubtitles;
|
||||||
|
VoidCallback? onAudioTracks;
|
||||||
|
VoidCallback? onFullscreen;
|
||||||
|
|
||||||
void handleCommand(RemoteCommand command, BuildContext? context) {
|
void handleCommand(RemoteCommand command, BuildContext? context) {
|
||||||
appLogger.d('CompanionRemoteReceiver: Handling command: ${command.type}');
|
appLogger.d('CompanionRemoteReceiver: Handling command: ${command.type}');
|
||||||
@@ -109,17 +112,23 @@ class CompanionRemoteReceiver {
|
|||||||
onPreviousTrack?.call();
|
onPreviousTrack?.call();
|
||||||
|
|
||||||
case RemoteCommandType.subtitles:
|
case RemoteCommandType.subtitles:
|
||||||
|
onSubtitles?.call();
|
||||||
case RemoteCommandType.audioTracks:
|
case RemoteCommandType.audioTracks:
|
||||||
break; // No-op: track cycling not yet implemented
|
onAudioTracks?.call();
|
||||||
|
|
||||||
case RemoteCommandType.fullscreen:
|
case RemoteCommandType.fullscreen:
|
||||||
|
if (onFullscreen != null) {
|
||||||
|
onFullscreen!.call();
|
||||||
|
} else {
|
||||||
simulateKeyPress(LogicalKeyboardKey.keyF);
|
simulateKeyPress(LogicalKeyboardKey.keyF);
|
||||||
|
}
|
||||||
|
|
||||||
case RemoteCommandType.ping:
|
case RemoteCommandType.ping:
|
||||||
case RemoteCommandType.pong:
|
case RemoteCommandType.pong:
|
||||||
case RemoteCommandType.ack:
|
case RemoteCommandType.ack:
|
||||||
case RemoteCommandType.deviceInfo:
|
case RemoteCommandType.deviceInfo:
|
||||||
case RemoteCommandType.disconnect:
|
case RemoteCommandType.disconnect:
|
||||||
|
case RemoteCommandType.syncState:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -258,6 +258,7 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
|||||||
_initAlwaysOnTopState();
|
_initAlwaysOnTopState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Focus play/pause button on first frame if in keyboard mode
|
// Focus play/pause button on first frame if in keyboard mode
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_focusPlayPauseIfKeyboardMode();
|
_focusPlayPauseIfKeyboardMode();
|
||||||
@@ -733,7 +734,12 @@ class _PlexVideoControlsState extends State<PlexVideoControls> with WindowListen
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updateTrafficLightVisibility() async {
|
void _updateTrafficLightVisibility() async {
|
||||||
await MacOSWindowService.setTrafficLightsVisible(_showControls);
|
// When maximized or fullscreen, always keep traffic lights visible so the
|
||||||
|
// user can reach them without the controls-hide-on-mouse-leave race.
|
||||||
|
// In normal windowed mode, toggle with controls as before.
|
||||||
|
final isMaximizedOrFullscreen = await windowManager.isMaximized() || await windowManager.isFullScreen();
|
||||||
|
final visible = isMaximizedOrFullscreen ? true : _showControls;
|
||||||
|
await MacOSWindowService.setTrafficLightsVisible(visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether PiP is supported on this device
|
/// Check whether PiP is supported on this device
|
||||||
|
|||||||
Reference in New Issue
Block a user