feat: tvos support
@@ -0,0 +1,33 @@
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
Flutter/GeneratedPluginRegistrant.*
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>9.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,2 @@
|
||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,12 @@
|
||||
platform :tvos, '14.0'
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
target 'Runner' do
|
||||
# No pods.
|
||||
end
|
||||
@@ -0,0 +1,749 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0D69E2DEA6EA0538722BC7F0 /* ConnectivityProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20D0E84BD711F2744BA0D136 /* ConnectivityProvider.swift */; };
|
||||
12E72ED3EFA35A8715DE2A52 /* GamepadStreamHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D82813FF8CCC8320A8C32FC7 /* GamepadStreamHandler.swift */; };
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
30E568153D56A59EA5EADCBB /* PathProviderPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87984F20624EC6CA45E20790 /* PathProviderPlugin.swift */; };
|
||||
32F0056AF154FC4EA76CBEFC /* GamepadPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD57BBEE1B72584C9C7D44B9 /* GamepadPlugin.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
477FB0F520224870902B9FD7 /* messages.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FF71CD2416844A1FC485D77 /* messages.g.swift */; };
|
||||
4A6DE7BDEF20A6242A3C6365 /* GCControllerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F2071908D08983E6A6A1C1E /* GCControllerManager.swift */; };
|
||||
5689B077C6EA57D53BBE8D0D /* OsMediaControlsPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8663A3A20A0D6F446B6B37 /* OsMediaControlsPlugin.swift */; };
|
||||
5C71F5F7B33075F2B007825B /* MpvPlayerPluginShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73645904F226A24585A092CE /* MpvPlayerPluginShared.swift */; };
|
||||
5CDBEB7E3FE968FC131E964A /* ConnectivityPlusPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C667C1287EFBDF4DF45C5313 /* ConnectivityPlusPlugin.swift */; };
|
||||
691577F0EB3F4EB1A5160280 /* MpvPlayerCoreBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D52A3BDA46E79969EA1DF3AC /* MpvPlayerCoreBase.swift */; };
|
||||
6A0510802F9B91CB0090B5FC /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = EA0F4263E7B912702C490108 /* MPVKit */; };
|
||||
6F3C0DD6F2F8DA14E7E2F386 /* MpvPlayerCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A12B8610AE5D580077264851 /* MpvPlayerCore.swift */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
8E5EED3DDAC9455D4DAA9776 /* MpvPlayerPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D7A998830EDC8F77BF521D1 /* MpvPlayerPlugin.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
A7ED1D92E7B15D3F7562F93E /* SharedPreferencesPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = E09D436FC3E6CD994DC1B488 /* SharedPreferencesPlugin.swift */; };
|
||||
AA34E3E5872B3792D6959EAA /* MpvPipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2635E12EB9322B151EE5127 /* MpvPipController.swift */; };
|
||||
B494441CBB8FB05D04F55DD9 /* DeviceInfoPlusPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4BADB53B8D59E031563AA9 /* DeviceInfoPlusPlugin.swift */; };
|
||||
BF599A1C25382688DD7F4505 /* ButtonMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FBF4BBA5D82316059225B32 /* ButtonMapping.swift */; };
|
||||
C1E0D7252DE079EDFA234F90 /* PathMonitorConnectivityProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4716B74C5238614844BCA224 /* PathMonitorConnectivityProvider.swift */; };
|
||||
C330A1087AA135CCFC0C6203 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F435CB4590A55958615A7AD /* libPods-Runner.a */; };
|
||||
F36688C4E8DF44BC6BDD99A5 /* PackageInfoPlusPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 300783F8CE83D2530D2EDD97 /* PackageInfoPlusPlugin.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
04DD35536DEE7C27FFA53862 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
0C25F4F2367A30B47E945AD6 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
0FF71CD2416844A1FC485D77 /* messages.g.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = messages.g.swift; sourceTree = "<group>"; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
20D0E84BD711F2744BA0D136 /* ConnectivityProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConnectivityProvider.swift; sourceTree = "<group>"; };
|
||||
300783F8CE83D2530D2EDD97 /* PackageInfoPlusPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PackageInfoPlusPlugin.swift; sourceTree = "<group>"; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
420881FB6A648A2AFD39FFF2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
4716B74C5238614844BCA224 /* PathMonitorConnectivityProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PathMonitorConnectivityProvider.swift; sourceTree = "<group>"; };
|
||||
5C4BADB53B8D59E031563AA9 /* DeviceInfoPlusPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DeviceInfoPlusPlugin.swift; sourceTree = "<group>"; };
|
||||
5F435CB4590A55958615A7AD /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5FBF4BBA5D82316059225B32 /* ButtonMapping.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ButtonMapping.swift; sourceTree = "<group>"; };
|
||||
73645904F226A24585A092CE /* MpvPlayerPluginShared.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvPlayerPluginShared.swift; path = ../shared/apple/MpvPlayer/MpvPlayerPluginShared.swift; sourceTree = "<source_root>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
7B8663A3A20A0D6F446B6B37 /* OsMediaControlsPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OsMediaControlsPlugin.swift; sourceTree = "<group>"; };
|
||||
87984F20624EC6CA45E20790 /* PathProviderPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PathProviderPlugin.swift; sourceTree = "<group>"; };
|
||||
8F2071908D08983E6A6A1C1E /* GCControllerManager.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GCControllerManager.swift; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
9D7A998830EDC8F77BF521D1 /* MpvPlayerPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvPlayerPlugin.swift; path = ../ios/Runner/MpvPlayer/MpvPlayerPlugin.swift; sourceTree = "<source_root>"; };
|
||||
A12B8610AE5D580077264851 /* MpvPlayerCore.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvPlayerCore.swift; path = ../ios/Runner/MpvPlayer/MpvPlayerCore.swift; sourceTree = "<source_root>"; };
|
||||
A2635E12EB9322B151EE5127 /* MpvPipController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvPipController.swift; path = ../ios/Runner/MpvPlayer/MpvPipController.swift; sourceTree = "<source_root>"; };
|
||||
C667C1287EFBDF4DF45C5313 /* ConnectivityPlusPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConnectivityPlusPlugin.swift; sourceTree = "<group>"; };
|
||||
D52A3BDA46E79969EA1DF3AC /* MpvPlayerCoreBase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = MpvPlayerCoreBase.swift; path = ../shared/apple/MpvPlayer/MpvPlayerCoreBase.swift; sourceTree = "<source_root>"; };
|
||||
D82813FF8CCC8320A8C32FC7 /* GamepadStreamHandler.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GamepadStreamHandler.swift; sourceTree = "<group>"; };
|
||||
DD57BBEE1B72584C9C7D44B9 /* GamepadPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = GamepadPlugin.swift; sourceTree = "<group>"; };
|
||||
E09D436FC3E6CD994DC1B488 /* SharedPreferencesPlugin.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SharedPreferencesPlugin.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6A0510802F9B91CB0090B5FC /* MPVKit in Frameworks */,
|
||||
C330A1087AA135CCFC0C6203 /* libPods-Runner.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
0524C4A636652321237B0884 /* path_provider */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
87984F20624EC6CA45E20790 /* PathProviderPlugin.swift */,
|
||||
);
|
||||
path = path_provider;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
08F5845B7875697970629696 /* package_info_plus */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
300783F8CE83D2530D2EDD97 /* PackageInfoPlusPlugin.swift */,
|
||||
);
|
||||
path = package_info_plus;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
19DB3FBCF738894832DA07EF /* device_info_plus */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C4BADB53B8D59E031563AA9 /* DeviceInfoPlusPlugin.swift */,
|
||||
);
|
||||
path = device_info_plus;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
53CDD29681161166962B9FA5 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0C25F4F2367A30B47E945AD6 /* Pods-Runner.debug.xcconfig */,
|
||||
04DD35536DEE7C27FFA53862 /* Pods-Runner.release.xcconfig */,
|
||||
420881FB6A648A2AFD39FFF2 /* Pods-Runner.profile.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5815557FAD15751414018354 /* shared_preferences_foundation */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E09D436FC3E6CD994DC1B488 /* SharedPreferencesPlugin.swift */,
|
||||
0FF71CD2416844A1FC485D77 /* messages.g.swift */,
|
||||
);
|
||||
path = shared_preferences_foundation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5833EC5B503BBB4E370BA1B7 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5F435CB4590A55958615A7AD /* libPods-Runner.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
737C2C621E266B83D4011221 /* connectivity_plus */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C667C1287EFBDF4DF45C5313 /* ConnectivityPlusPlugin.swift */,
|
||||
20D0E84BD711F2744BA0D136 /* ConnectivityProvider.swift */,
|
||||
4716B74C5238614844BCA224 /* PathMonitorConnectivityProvider.swift */,
|
||||
);
|
||||
path = connectivity_plus;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
53CDD29681161166962B9FA5 /* Pods */,
|
||||
5833EC5B503BBB4E370BA1B7 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
BF0B7DFE378943E9D7865D18 /* MpvPlayer */,
|
||||
E6BAF734A5201F83A1B61DCC /* Plugins */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
AD5C822C9001042A47B99FBD /* universal_gamepad */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DD57BBEE1B72584C9C7D44B9 /* GamepadPlugin.swift */,
|
||||
D82813FF8CCC8320A8C32FC7 /* GamepadStreamHandler.swift */,
|
||||
8F2071908D08983E6A6A1C1E /* GCControllerManager.swift */,
|
||||
5FBF4BBA5D82316059225B32 /* ButtonMapping.swift */,
|
||||
);
|
||||
path = universal_gamepad;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BF0B7DFE378943E9D7865D18 /* MpvPlayer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D52A3BDA46E79969EA1DF3AC /* MpvPlayerCoreBase.swift */,
|
||||
73645904F226A24585A092CE /* MpvPlayerPluginShared.swift */,
|
||||
A12B8610AE5D580077264851 /* MpvPlayerCore.swift */,
|
||||
9D7A998830EDC8F77BF521D1 /* MpvPlayerPlugin.swift */,
|
||||
A2635E12EB9322B151EE5127 /* MpvPipController.swift */,
|
||||
);
|
||||
name = MpvPlayer;
|
||||
path = Runner/MpvPlayer;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
C538704E1F003452B22FE0C7 /* os_media_controls */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7B8663A3A20A0D6F446B6B37 /* OsMediaControlsPlugin.swift */,
|
||||
);
|
||||
path = os_media_controls;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E6BAF734A5201F83A1B61DCC /* Plugins */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5815557FAD15751414018354 /* shared_preferences_foundation */,
|
||||
08F5845B7875697970629696 /* package_info_plus */,
|
||||
0524C4A636652321237B0884 /* path_provider */,
|
||||
AD5C822C9001042A47B99FBD /* universal_gamepad */,
|
||||
19DB3FBCF738894832DA07EF /* device_info_plus */,
|
||||
737C2C621E266B83D4011221 /* connectivity_plus */,
|
||||
C538704E1F003452B22FE0C7 /* os_media_controls */,
|
||||
);
|
||||
path = Plugins;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
570D41C54A87D72AB27F6F15 /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
packageProductDependencies = (
|
||||
EA0F4263E7B912702C490108 /* MPVKit */,
|
||||
);
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
packageReferences = (
|
||||
CEE842A5A25923620300AB90 /* XCRemoteSwiftPackageReference "MPVKit" */,
|
||||
);
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "echo no-op thin binary\n";
|
||||
};
|
||||
570D41C54A87D72AB27F6F15 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "#/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n/bin/sh \"$SOURCE_ROOT/scripts/xcode_appletv.sh\" build\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
691577F0EB3F4EB1A5160280 /* MpvPlayerCoreBase.swift in Sources */,
|
||||
5C71F5F7B33075F2B007825B /* MpvPlayerPluginShared.swift in Sources */,
|
||||
6F3C0DD6F2F8DA14E7E2F386 /* MpvPlayerCore.swift in Sources */,
|
||||
8E5EED3DDAC9455D4DAA9776 /* MpvPlayerPlugin.swift in Sources */,
|
||||
AA34E3E5872B3792D6959EAA /* MpvPipController.swift in Sources */,
|
||||
A7ED1D92E7B15D3F7562F93E /* SharedPreferencesPlugin.swift in Sources */,
|
||||
477FB0F520224870902B9FD7 /* messages.g.swift in Sources */,
|
||||
F36688C4E8DF44BC6BDD99A5 /* PackageInfoPlusPlugin.swift in Sources */,
|
||||
30E568153D56A59EA5EADCBB /* PathProviderPlugin.swift in Sources */,
|
||||
32F0056AF154FC4EA76CBEFC /* GamepadPlugin.swift in Sources */,
|
||||
12E72ED3EFA35A8715DE2A52 /* GamepadStreamHandler.swift in Sources */,
|
||||
4A6DE7BDEF20A6242A3C6365 /* GCControllerManager.swift in Sources */,
|
||||
BF599A1C25382688DD7F4505 /* ButtonMapping.swift in Sources */,
|
||||
B494441CBB8FB05D04F55DD9 /* DeviceInfoPlusPlugin.swift in Sources */,
|
||||
5CDBEB7E3FE968FC131E964A /* ConnectivityPlusPlugin.swift in Sources */,
|
||||
0D69E2DEA6EA0538722BC7F0 /* ConnectivityProvider.swift in Sources */,
|
||||
C1E0D7252DE079EDFA234F90 /* PathMonitorConnectivityProvider.swift in Sources */,
|
||||
5689B077C6EA57D53BBE8D0D /* OsMediaControlsPlugin.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = appletvos;
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator";
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 13.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = G88U5B5783;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Plezy;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.edde746.plezy;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = appletvos;
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 13.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = appletvos;
|
||||
SUPPORTED_PLATFORMS = "appletvos appletvsimulator";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = 3;
|
||||
TVOS_DEPLOYMENT_TARGET = 13.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = G88U5B5783;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Plezy;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.edde746.plezy;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = G88U5B5783;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Plezy;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.edde746.plezy;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
CEE842A5A25923620300AB90 /* XCRemoteSwiftPackageReference "MPVKit" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/edde746/MPVKit";
|
||||
requirement = {
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
EA0F4263E7B912702C490108 /* MPVKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CEE842A5A25923620300AB90 /* XCRemoteSwiftPackageReference "MPVKit" */;
|
||||
productName = MPVKit;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"originHash" : "c535d3cdb62bd4e11e67f3c9e68290fa7ee9306634c5f56d2b0396eb75ed41ee",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "mpvkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/edde746/MPVKit",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "29803ccbfa631997d496f34c2c1ed191cc8fd2c3"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"originHash" : "c535d3cdb62bd4e11e67f3c9e68290fa7ee9306634c5f56d2b0396eb75ed41ee",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "mpvkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/edde746/MPVKit",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "29803ccbfa631997d496f34c2c1ed191cc8fd2c3"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
do {
|
||||
let session = AVAudioSession.sharedInstance()
|
||||
try session.setCategory(.playback, mode: .default)
|
||||
try session.setActive(true)
|
||||
} catch {
|
||||
print("Failed to configure audio session: \(error)")
|
||||
}
|
||||
|
||||
application.beginReceivingRemoteControlEvents()
|
||||
|
||||
NSLog("[tvos] AppDelegate didFinishLaunching — registering plugins")
|
||||
|
||||
if let spRegistrar = self.registrar(forPlugin: "SharedPreferencesPlugin") {
|
||||
NSLog("[tvos] Registering SharedPreferencesPlugin (Swift-direct)")
|
||||
SharedPreferencesPlugin.register(with: spRegistrar)
|
||||
} else {
|
||||
NSLog("[tvos] SharedPreferencesPlugin registrar nil")
|
||||
}
|
||||
|
||||
if let mpvRegistrar = self.registrar(forPlugin: "MpvPlayerPlugin") {
|
||||
NSLog("[tvos] Registering MpvPlayerPlugin")
|
||||
MpvPlayerPlugin.register(with: mpvRegistrar)
|
||||
}
|
||||
|
||||
if let r = self.registrar(forPlugin: "PackageInfoPlusPlugin") {
|
||||
NSLog("[tvos] Registering PackageInfoPlusPlugin")
|
||||
PackageInfoPlusPlugin.register(with: r)
|
||||
}
|
||||
|
||||
if let r = self.registrar(forPlugin: "PathProviderPlugin") {
|
||||
NSLog("[tvos] Registering PathProviderPlugin")
|
||||
PathProviderPlugin.register(with: r)
|
||||
}
|
||||
|
||||
if let r = self.registrar(forPlugin: "GamepadPlugin") {
|
||||
NSLog("[tvos] Registering GamepadPlugin")
|
||||
GamepadPlugin.register(with: r)
|
||||
}
|
||||
|
||||
if let r = self.registrar(forPlugin: "DeviceInfoPlusPlugin") {
|
||||
NSLog("[tvos] Registering DeviceInfoPlusPlugin")
|
||||
DeviceInfoPlusPlugin.register(with: r)
|
||||
}
|
||||
|
||||
if let r = self.registrar(forPlugin: "ConnectivityPlusPlugin") {
|
||||
NSLog("[tvos] Registering ConnectivityPlusPlugin")
|
||||
ConnectivityPlusPlugin.register(with: r)
|
||||
}
|
||||
|
||||
if let r = self.registrar(forPlugin: "OsMediaControlsPlugin") {
|
||||
NSLog("[tvos] Registering OsMediaControlsPlugin")
|
||||
OsMediaControlsPlugin.register(with: r)
|
||||
}
|
||||
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 528 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "Back.png", "idiom" : "tv" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"info" : { "author" : "xcode", "version" : 1 },
|
||||
"layers" : [
|
||||
{ "filename" : "Front.imagestacklayer" },
|
||||
{ "filename" : "Middle.imagestacklayer" },
|
||||
{ "filename" : "Back.imagestacklayer" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "Front.png", "idiom" : "tv" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 42 KiB |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "Middle.png", "idiom" : "tv" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 441 B |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 239 KiB |
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "Back.png", "idiom" : "tv", "scale" : "1x" },
|
||||
{ "filename" : "Back@2x.png", "idiom" : "tv", "scale" : "2x" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"info" : { "author" : "xcode", "version" : 1 },
|
||||
"layers" : [
|
||||
{ "filename" : "Front.imagestacklayer" },
|
||||
{ "filename" : "Middle.imagestacklayer" },
|
||||
{ "filename" : "Back.imagestacklayer" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "Front.png", "idiom" : "tv", "scale" : "1x" },
|
||||
{ "filename" : "Front@2x.png", "idiom" : "tv", "scale" : "2x" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "Middle.png", "idiom" : "tv", "scale" : "1x" },
|
||||
{ "filename" : "Middle@2x.png", "idiom" : "tv", "scale" : "2x" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 333 B |
|
After Width: | Height: | Size: 367 B |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"assets" : [
|
||||
{ "filename" : "App Icon.imagestack", "idiom" : "tv", "role" : "primary-app-icon", "size" : "400x240" },
|
||||
{ "filename" : "App Icon - App Store.imagestack","idiom" : "tv", "role" : "primary-app-icon", "size" : "1280x768" },
|
||||
{ "filename" : "Top Shelf Image.imageset", "idiom" : "tv", "role" : "top-shelf-image", "size" : "1920x720" },
|
||||
{ "filename" : "Top Shelf Image Wide.imageset", "idiom" : "tv", "role" : "top-shelf-image-wide", "size" : "2320x720" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "top-shelf-wide.png", "idiom" : "tv" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"images" : [
|
||||
{ "filename" : "top-shelf.png", "idiom" : "tv" }
|
||||
],
|
||||
"info" : { "author" : "xcode", "version" : 1 }
|
||||
}
|
||||
|
After Width: | Height: | Size: 983 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
@@ -0,0 +1,5 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="16096" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" ulaunchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="appleTV" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
<rect key="frame" x="959.5" y="539.5" width="1" height="1"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="1" height="1"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="15702" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="appleTV" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<viewLayoutGuide key="safeArea" id="wu6-TO-1qx"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Plezy</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Plezy</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright 2019 The Chromium Authors. All rights reserved.
|
||||
// Use of this source is governed by a BSD-style license that can
|
||||
// be found in the LICENSE file.
|
||||
|
||||
import Flutter
|
||||
|
||||
public class ConnectivityPlusPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
|
||||
private let connectivityProvider: ConnectivityProvider
|
||||
private var eventSink: FlutterEventSink?
|
||||
|
||||
init(connectivityProvider: ConnectivityProvider) {
|
||||
self.connectivityProvider = connectivityProvider
|
||||
super.init()
|
||||
self.connectivityProvider.connectivityUpdateHandler = connectivityUpdateHandler
|
||||
}
|
||||
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let binaryMessenger = registrar.messenger()
|
||||
|
||||
let channel = FlutterMethodChannel(
|
||||
name: "dev.fluttercommunity.plus/connectivity",
|
||||
binaryMessenger: binaryMessenger)
|
||||
|
||||
let streamChannel = FlutterEventChannel(
|
||||
name: "dev.fluttercommunity.plus/connectivity_status",
|
||||
binaryMessenger: binaryMessenger)
|
||||
|
||||
let connectivityProvider = PathMonitorConnectivityProvider()
|
||||
let instance = ConnectivityPlusPlugin(connectivityProvider: connectivityProvider)
|
||||
streamChannel.setStreamHandler(instance)
|
||||
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func detachFromEngine(for registrar: FlutterPluginRegistrar) {
|
||||
eventSink = nil
|
||||
connectivityProvider.stop()
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "check":
|
||||
result(statusFrom(connectivityTypes: connectivityProvider.currentConnectivityTypes))
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
private func statusFrom(connectivityType: ConnectivityType) -> String {
|
||||
switch connectivityType {
|
||||
case .wifi:
|
||||
return "wifi"
|
||||
case .cellular:
|
||||
return "mobile"
|
||||
case .wiredEthernet:
|
||||
return "ethernet"
|
||||
case .other:
|
||||
return "other"
|
||||
case .none:
|
||||
return "none"
|
||||
}
|
||||
}
|
||||
|
||||
private func statusFrom(connectivityTypes: [ConnectivityType]) -> [String] {
|
||||
return connectivityTypes.map {
|
||||
self.statusFrom(connectivityType: $0)
|
||||
}
|
||||
}
|
||||
|
||||
public func onListen(
|
||||
withArguments _: Any?,
|
||||
eventSink events: @escaping FlutterEventSink
|
||||
) -> FlutterError? {
|
||||
eventSink = events
|
||||
connectivityProvider.start()
|
||||
// Update this to handle a list
|
||||
connectivityUpdateHandler(connectivityTypes: connectivityProvider.currentConnectivityTypes)
|
||||
return nil
|
||||
}
|
||||
|
||||
private func connectivityUpdateHandler(connectivityTypes: [ConnectivityType]) {
|
||||
DispatchQueue.main.async {
|
||||
self.eventSink?(self.statusFrom(connectivityTypes: connectivityTypes))
|
||||
}
|
||||
}
|
||||
|
||||
public func onCancel(withArguments _: Any?) -> FlutterError? {
|
||||
connectivityProvider.stop()
|
||||
eventSink = nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
|
||||
public enum ConnectivityType {
|
||||
case none
|
||||
case wiredEthernet
|
||||
case wifi
|
||||
case cellular
|
||||
case other
|
||||
}
|
||||
|
||||
public protocol ConnectivityProvider: NSObjectProtocol {
|
||||
typealias ConnectivityUpdateHandler = ([ConnectivityType]) -> Void
|
||||
|
||||
var currentConnectivityTypes: [ConnectivityType] { get }
|
||||
|
||||
var connectivityUpdateHandler: ConnectivityUpdateHandler? { get set }
|
||||
|
||||
func start()
|
||||
|
||||
func stop()
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
public class PathMonitorConnectivityProvider: NSObject, ConnectivityProvider {
|
||||
|
||||
// Use .utility, as it is intended for tasks that the user does not track actively.
|
||||
// See: https://developer.apple.com/documentation/dispatch/dispatchqos
|
||||
private let queue = DispatchQueue.global(qos: .utility)
|
||||
|
||||
private var pathMonitor: NWPathMonitor?
|
||||
|
||||
private func connectivityFrom(path: NWPath) -> [ConnectivityType] {
|
||||
var types: [ConnectivityType] = []
|
||||
|
||||
// Check for connectivity and append to types array as necessary
|
||||
if path.status == .satisfied {
|
||||
if path.usesInterfaceType(.wifi) {
|
||||
types.append(.wifi)
|
||||
}
|
||||
if path.usesInterfaceType(.cellular) {
|
||||
types.append(.cellular)
|
||||
}
|
||||
if path.usesInterfaceType(.wiredEthernet) {
|
||||
types.append(.wiredEthernet)
|
||||
}
|
||||
if path.usesInterfaceType(.other) {
|
||||
types.append(.other)
|
||||
}
|
||||
}
|
||||
|
||||
return types.isEmpty ? [.none] : types
|
||||
}
|
||||
|
||||
public var currentConnectivityTypes: [ConnectivityType] {
|
||||
let path = ensurePathMonitor().currentPath
|
||||
return connectivityFrom(path: path)
|
||||
}
|
||||
|
||||
public var connectivityUpdateHandler: ConnectivityUpdateHandler?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
_ = ensurePathMonitor()
|
||||
}
|
||||
|
||||
public func start() {
|
||||
_ = ensurePathMonitor()
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
pathMonitor?.cancel()
|
||||
pathMonitor = nil
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func ensurePathMonitor() -> NWPathMonitor {
|
||||
if (pathMonitor == nil) {
|
||||
let pathMonitor = NWPathMonitor()
|
||||
pathMonitor.start(queue: queue)
|
||||
pathMonitor.pathUpdateHandler = pathUpdateHandler
|
||||
self.pathMonitor = pathMonitor
|
||||
}
|
||||
return self.pathMonitor!
|
||||
}
|
||||
|
||||
private func pathUpdateHandler(path: NWPath) {
|
||||
connectivityUpdateHandler?(connectivityFrom(path: path))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// Pure-Swift tvOS port of fluttercommunity.plus/device_info. Mirrors the
|
||||
// Objective-C FPPDeviceInfoPlusPlugin on iOS so the Dart IosDeviceInfo
|
||||
// parser finds all the keys it expects.
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
import Flutter
|
||||
|
||||
public class DeviceInfoPlusPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(
|
||||
name: "dev.fluttercommunity.plus/device_info",
|
||||
binaryMessenger: registrar.messenger()
|
||||
)
|
||||
let instance = DeviceInfoPlusPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
guard call.method == "getDeviceInfo" else {
|
||||
result(FlutterMethodNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
let device = UIDevice.current
|
||||
var uts = utsname()
|
||||
uname(&uts)
|
||||
|
||||
let processInfo = ProcessInfo.processInfo
|
||||
let isSimulator: Bool = {
|
||||
#if targetEnvironment(simulator)
|
||||
return true
|
||||
#else
|
||||
return false
|
||||
#endif
|
||||
}()
|
||||
let isPhysicalDevice = !isSimulator
|
||||
|
||||
let machine: String
|
||||
if isPhysicalDevice {
|
||||
machine = Self.utsnameString(&uts.machine)
|
||||
} else {
|
||||
machine = processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] ?? ""
|
||||
}
|
||||
|
||||
let freeDisk: Int64
|
||||
let totalDisk: Int64
|
||||
if let attrs = try? FileManager.default.attributesOfFileSystem(
|
||||
forPath: NSHomeDirectory())
|
||||
{
|
||||
freeDisk = (attrs[.systemFreeSize] as? NSNumber)?.int64Value ?? -1
|
||||
totalDisk = (attrs[.systemSize] as? NSNumber)?.int64Value ?? -1
|
||||
} else {
|
||||
freeDisk = -1
|
||||
totalDisk = -1
|
||||
}
|
||||
|
||||
var isiOSAppOnMac = false
|
||||
if #available(iOS 14.0, tvOS 14.0, *) {
|
||||
isiOSAppOnMac = processInfo.isiOSAppOnMac
|
||||
}
|
||||
|
||||
let physicalRam = Int64(processInfo.physicalMemory) / 1_048_576
|
||||
|
||||
let info: [String: Any] = [
|
||||
"name": device.name,
|
||||
"systemName": device.systemName,
|
||||
"systemVersion": device.systemVersion,
|
||||
"model": device.model,
|
||||
"localizedModel": device.localizedModel,
|
||||
"modelName": machine,
|
||||
"identifierForVendor": device.identifierForVendor?.uuidString as Any,
|
||||
"freeDiskSize": freeDisk,
|
||||
"totalDiskSize": totalDisk,
|
||||
"isPhysicalDevice": isPhysicalDevice,
|
||||
"isiOSAppOnMac": isiOSAppOnMac,
|
||||
"physicalRamSize": physicalRam,
|
||||
"availableRamSize": Self.availableMemoryMB(),
|
||||
"utsname": [
|
||||
"sysname": Self.utsnameString(&uts.sysname),
|
||||
"nodename": Self.utsnameString(&uts.nodename),
|
||||
"release": Self.utsnameString(&uts.release),
|
||||
"version": Self.utsnameString(&uts.version),
|
||||
"machine": machine,
|
||||
],
|
||||
]
|
||||
|
||||
result(info)
|
||||
}
|
||||
|
||||
private static func utsnameString<T>(_ tupled: inout T) -> String {
|
||||
withUnsafePointer(to: &tupled) { ptr in
|
||||
ptr.withMemoryRebound(to: CChar.self, capacity: MemoryLayout<T>.size) {
|
||||
String(cString: $0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func availableMemoryMB() -> Int {
|
||||
var hostInfo = vm_statistics_data_t()
|
||||
var count = mach_msg_type_number_t(
|
||||
MemoryLayout<vm_statistics_data_t>.size / MemoryLayout<integer_t>.size)
|
||||
let host = mach_host_self()
|
||||
var pageSize: vm_size_t = 0
|
||||
host_page_size(host, &pageSize)
|
||||
|
||||
let result: kern_return_t = withUnsafeMutablePointer(to: &hostInfo) { ptr in
|
||||
ptr.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { reboundPtr in
|
||||
host_statistics(host, HOST_VM_INFO, reboundPtr, &count)
|
||||
}
|
||||
}
|
||||
guard result == KERN_SUCCESS else { return -1 }
|
||||
let memFree = UInt64(hostInfo.free_count) * UInt64(pageSize)
|
||||
return Int(memFree / 1_048_576)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,370 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import MediaPlayer
|
||||
import AVFoundation
|
||||
|
||||
public class OsMediaControlsPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
|
||||
private var eventSink: FlutterEventSink?
|
||||
private let nowPlayingCenter = MPNowPlayingInfoCenter.default()
|
||||
private let commandCenter = MPRemoteCommandCenter.shared()
|
||||
|
||||
private var currentMetadata: [String: Any] = [:]
|
||||
|
||||
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let methodChannel = FlutterMethodChannel(
|
||||
name: "com.edde746.os_media_controls/methods",
|
||||
binaryMessenger: registrar.messenger()
|
||||
)
|
||||
let eventChannel = FlutterEventChannel(
|
||||
name: "com.edde746.os_media_controls/events",
|
||||
binaryMessenger: registrar.messenger()
|
||||
)
|
||||
|
||||
let instance = OsMediaControlsPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: methodChannel)
|
||||
eventChannel.setStreamHandler(instance)
|
||||
}
|
||||
|
||||
public override init() {
|
||||
super.init()
|
||||
|
||||
// Ensure app receives remote control events for Now Playing controls
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.beginReceivingRemoteControlEvents()
|
||||
}
|
||||
|
||||
setupRemoteCommandCenter()
|
||||
}
|
||||
|
||||
private func setupRemoteCommandCenter() {
|
||||
// Play command
|
||||
commandCenter.playCommand.isEnabled = true
|
||||
commandCenter.playCommand.addTarget { [weak self] event in
|
||||
self?.sendEvent(["type": "play"])
|
||||
return .success
|
||||
}
|
||||
|
||||
// Pause command
|
||||
commandCenter.pauseCommand.isEnabled = true
|
||||
commandCenter.pauseCommand.addTarget { [weak self] event in
|
||||
self?.sendEvent(["type": "pause"])
|
||||
return .success
|
||||
}
|
||||
|
||||
// Toggle play/pause command
|
||||
commandCenter.togglePlayPauseCommand.isEnabled = true
|
||||
commandCenter.togglePlayPauseCommand.addTarget { [weak self] event in
|
||||
self?.sendEvent(["type": "togglePlayPause"])
|
||||
return .success
|
||||
}
|
||||
|
||||
// Next track command
|
||||
commandCenter.nextTrackCommand.isEnabled = true
|
||||
commandCenter.nextTrackCommand.addTarget { [weak self] event in
|
||||
self?.sendEvent(["type": "next"])
|
||||
return .success
|
||||
}
|
||||
|
||||
// Previous track command
|
||||
commandCenter.previousTrackCommand.isEnabled = true
|
||||
commandCenter.previousTrackCommand.addTarget { [weak self] event in
|
||||
self?.sendEvent(["type": "previous"])
|
||||
return .success
|
||||
}
|
||||
|
||||
// Change playback position command (seek)
|
||||
commandCenter.changePlaybackPositionCommand.isEnabled = true
|
||||
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] event in
|
||||
if let positionEvent = event as? MPChangePlaybackPositionCommandEvent {
|
||||
self?.sendEvent([
|
||||
"type": "seek",
|
||||
"position": positionEvent.positionTime
|
||||
])
|
||||
}
|
||||
return .success
|
||||
}
|
||||
|
||||
// Skip forward command
|
||||
commandCenter.skipForwardCommand.isEnabled = false // Disabled by default
|
||||
commandCenter.skipForwardCommand.addTarget { [weak self] event in
|
||||
if let skipEvent = event as? MPSkipIntervalCommandEvent {
|
||||
self?.sendEvent([
|
||||
"type": "skipForward",
|
||||
"interval": skipEvent.interval
|
||||
])
|
||||
} else {
|
||||
self?.sendEvent(["type": "skipForward"])
|
||||
}
|
||||
return .success
|
||||
}
|
||||
|
||||
// Skip backward command
|
||||
commandCenter.skipBackwardCommand.isEnabled = false // Disabled by default
|
||||
commandCenter.skipBackwardCommand.addTarget { [weak self] event in
|
||||
if let skipEvent = event as? MPSkipIntervalCommandEvent {
|
||||
self?.sendEvent([
|
||||
"type": "skipBackward",
|
||||
"interval": skipEvent.interval
|
||||
])
|
||||
} else {
|
||||
self?.sendEvent(["type": "skipBackward"])
|
||||
}
|
||||
return .success
|
||||
}
|
||||
|
||||
// Change playback rate command
|
||||
commandCenter.changePlaybackRateCommand.isEnabled = true
|
||||
commandCenter.changePlaybackRateCommand.supportedPlaybackRates = [0.5, 1.0, 1.5, 2.0]
|
||||
commandCenter.changePlaybackRateCommand.addTarget { [weak self] event in
|
||||
if let rateEvent = event as? MPChangePlaybackRateCommandEvent {
|
||||
self?.sendEvent([
|
||||
"type": "setSpeed",
|
||||
"speed": rateEvent.playbackRate
|
||||
])
|
||||
}
|
||||
return .success
|
||||
}
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "setMetadata":
|
||||
setMetadata(arguments: call.arguments as? [String: Any])
|
||||
result(nil)
|
||||
|
||||
case "setPlaybackState":
|
||||
setPlaybackState(arguments: call.arguments as? [String: Any])
|
||||
result(nil)
|
||||
|
||||
case "enableControls":
|
||||
enableControls(arguments: call.arguments as? [String])
|
||||
result(nil)
|
||||
|
||||
case "disableControls":
|
||||
disableControls(arguments: call.arguments as? [String])
|
||||
result(nil)
|
||||
|
||||
case "setSkipIntervals":
|
||||
setSkipIntervals(arguments: call.arguments as? [String: Any])
|
||||
result(nil)
|
||||
|
||||
case "setQueueInfo":
|
||||
setQueueInfo(arguments: call.arguments as? [String: Any])
|
||||
result(nil)
|
||||
|
||||
case "clear":
|
||||
clear()
|
||||
result(nil)
|
||||
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
private func setMetadata(arguments: [String: Any]?) {
|
||||
guard let args = arguments else { return }
|
||||
|
||||
// Reactivate audio session to ensure media controls work after being cleared
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setActive(true)
|
||||
} catch {
|
||||
print("Failed to reactivate audio session: \(error)")
|
||||
}
|
||||
|
||||
// Store metadata for later use
|
||||
for (key, value) in args {
|
||||
if key != "artwork" {
|
||||
currentMetadata[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
var nowPlayingInfo = nowPlayingCenter.nowPlayingInfo ?? [:]
|
||||
|
||||
if let title = args["title"] as? String {
|
||||
nowPlayingInfo[MPMediaItemPropertyTitle] = title
|
||||
}
|
||||
if let artist = args["artist"] as? String {
|
||||
nowPlayingInfo[MPMediaItemPropertyArtist] = artist
|
||||
}
|
||||
if let album = args["album"] as? String {
|
||||
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = album
|
||||
}
|
||||
if let albumArtist = args["albumArtist"] as? String {
|
||||
nowPlayingInfo[MPMediaItemPropertyAlbumArtist] = albumArtist
|
||||
}
|
||||
if let duration = args["duration"] as? Double {
|
||||
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = duration
|
||||
}
|
||||
// Handle artwork from bytes (takes precedence over URL)
|
||||
if let artworkData = args["artwork"] as? FlutterStandardTypedData {
|
||||
if let image = UIImage(data: artworkData.data) {
|
||||
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
|
||||
}
|
||||
} else if let artworkUrlString = args["artworkUrl"] as? String, let artworkUrl = URL(string: artworkUrlString) {
|
||||
// Handle artwork from URL - download asynchronously
|
||||
URLSession.shared.dataTask(with: artworkUrl) { [weak self] data, response, error in
|
||||
guard let self = self else { return }
|
||||
guard error == nil, let data = data, let image = UIImage(data: data) else {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
var updatedInfo = self.nowPlayingCenter.nowPlayingInfo ?? [:]
|
||||
updatedInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: image.size) { _ in image }
|
||||
self.nowPlayingCenter.nowPlayingInfo = updatedInfo
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
nowPlayingCenter.nowPlayingInfo = nowPlayingInfo
|
||||
}
|
||||
|
||||
private func setPlaybackState(arguments: [String: Any]?) {
|
||||
guard let args = arguments,
|
||||
let stateString = args["state"] as? String,
|
||||
let position = args["position"] as? Double,
|
||||
let speed = args["speed"] as? Double else { return }
|
||||
|
||||
// Reactivate audio session to ensure media controls work after being cleared
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setActive(true)
|
||||
} catch {
|
||||
print("Failed to reactivate audio session: \(error)")
|
||||
}
|
||||
|
||||
var nowPlayingInfo = nowPlayingCenter.nowPlayingInfo ?? [:]
|
||||
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = position
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] =
|
||||
stateString == "playing" ? speed : 0.0
|
||||
|
||||
nowPlayingCenter.nowPlayingInfo = nowPlayingInfo
|
||||
}
|
||||
|
||||
private func enableControls(arguments: [String]?) {
|
||||
guard let controls = arguments else { return }
|
||||
|
||||
for control in controls {
|
||||
switch control {
|
||||
case "play":
|
||||
commandCenter.playCommand.isEnabled = true
|
||||
case "pause":
|
||||
commandCenter.pauseCommand.isEnabled = true
|
||||
case "stop":
|
||||
// iOS doesn't have a dedicated stop command
|
||||
break
|
||||
case "next":
|
||||
commandCenter.nextTrackCommand.isEnabled = true
|
||||
case "previous":
|
||||
commandCenter.previousTrackCommand.isEnabled = true
|
||||
case "seek":
|
||||
commandCenter.changePlaybackPositionCommand.isEnabled = true
|
||||
case "skipForward":
|
||||
commandCenter.skipForwardCommand.isEnabled = true
|
||||
case "skipBackward":
|
||||
commandCenter.skipBackwardCommand.isEnabled = true
|
||||
case "changeSpeed":
|
||||
commandCenter.changePlaybackRateCommand.isEnabled = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func disableControls(arguments: [String]?) {
|
||||
guard let controls = arguments else { return }
|
||||
|
||||
for control in controls {
|
||||
switch control {
|
||||
case "play":
|
||||
commandCenter.playCommand.isEnabled = false
|
||||
case "pause":
|
||||
commandCenter.pauseCommand.isEnabled = false
|
||||
case "next":
|
||||
commandCenter.nextTrackCommand.isEnabled = false
|
||||
case "previous":
|
||||
commandCenter.previousTrackCommand.isEnabled = false
|
||||
case "seek":
|
||||
commandCenter.changePlaybackPositionCommand.isEnabled = false
|
||||
case "skipForward":
|
||||
commandCenter.skipForwardCommand.isEnabled = false
|
||||
case "skipBackward":
|
||||
commandCenter.skipBackwardCommand.isEnabled = false
|
||||
case "changeSpeed":
|
||||
commandCenter.changePlaybackRateCommand.isEnabled = false
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setSkipIntervals(arguments: [String: Any]?) {
|
||||
guard let args = arguments else { return }
|
||||
|
||||
if let forward = args["forward"] as? Int {
|
||||
commandCenter.skipForwardCommand.isEnabled = true
|
||||
commandCenter.skipForwardCommand.preferredIntervals = [NSNumber(value: forward)]
|
||||
}
|
||||
|
||||
if let backward = args["backward"] as? Int {
|
||||
commandCenter.skipBackwardCommand.isEnabled = true
|
||||
commandCenter.skipBackwardCommand.preferredIntervals = [NSNumber(value: backward)]
|
||||
}
|
||||
}
|
||||
|
||||
private func setQueueInfo(arguments: [String: Any]?) {
|
||||
guard let args = arguments,
|
||||
let currentIndex = args["currentIndex"] as? Int,
|
||||
let queueLength = args["queueLength"] as? Int else { return }
|
||||
|
||||
var nowPlayingInfo = nowPlayingCenter.nowPlayingInfo ?? [:]
|
||||
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackQueueIndex] = currentIndex
|
||||
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackQueueCount] = queueLength
|
||||
|
||||
nowPlayingCenter.nowPlayingInfo = nowPlayingInfo
|
||||
}
|
||||
|
||||
private func clear() {
|
||||
nowPlayingCenter.nowPlayingInfo = nil
|
||||
currentMetadata.removeAll()
|
||||
|
||||
// Deactivate audio session to force iOS to remove controls from Control Center
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
|
||||
} catch {
|
||||
// Audio session deactivation failed, but continue with cleanup
|
||||
}
|
||||
|
||||
// Disable all command center buttons
|
||||
let commandCenter = MPRemoteCommandCenter.shared()
|
||||
commandCenter.playCommand.isEnabled = false
|
||||
commandCenter.pauseCommand.isEnabled = false
|
||||
commandCenter.togglePlayPauseCommand.isEnabled = false
|
||||
commandCenter.nextTrackCommand.isEnabled = false
|
||||
commandCenter.previousTrackCommand.isEnabled = false
|
||||
commandCenter.changePlaybackPositionCommand.isEnabled = false
|
||||
commandCenter.skipForwardCommand.isEnabled = false
|
||||
commandCenter.skipBackwardCommand.isEnabled = false
|
||||
commandCenter.changePlaybackRateCommand.isEnabled = false
|
||||
}
|
||||
|
||||
|
||||
private func sendEvent(_ event: [String: Any]) {
|
||||
eventSink?(event)
|
||||
}
|
||||
|
||||
// MARK: - FlutterStreamHandler
|
||||
|
||||
public func onListen(withArguments arguments: Any?,
|
||||
eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
||||
self.eventSink = events
|
||||
return nil
|
||||
}
|
||||
|
||||
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
||||
self.eventSink = nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Pure-Swift tvOS port of fluttercommunity.plus/package_info.
|
||||
// Matches the Objective-C FPPPackageInfoPlusPlugin on iOS exactly enough to
|
||||
// satisfy PackageInfo.fromPlatform() in Dart.
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
import Flutter
|
||||
|
||||
public class PackageInfoPlusPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(
|
||||
name: "dev.fluttercommunity.plus/package_info",
|
||||
binaryMessenger: registrar.messenger()
|
||||
)
|
||||
let instance = PackageInfoPlusPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
guard call.method == "getAll" else {
|
||||
result(FlutterMethodNotImplemented)
|
||||
return
|
||||
}
|
||||
|
||||
let bundle = Bundle.main
|
||||
let appStoreReceipt = bundle.appStoreReceiptURL?.path ?? ""
|
||||
let installerStore: String
|
||||
if appStoreReceipt.contains("CoreSimulator") {
|
||||
installerStore = "com.apple.simulator"
|
||||
} else if appStoreReceipt.contains("sandboxReceipt") {
|
||||
installerStore = "com.apple.testflight"
|
||||
} else {
|
||||
installerStore = "com.apple"
|
||||
}
|
||||
|
||||
let appName =
|
||||
(bundle.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String)
|
||||
?? (bundle.object(forInfoDictionaryKey: "CFBundleName") as? String)
|
||||
?? ""
|
||||
let packageName = bundle.bundleIdentifier ?? ""
|
||||
let version =
|
||||
(bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String) ?? ""
|
||||
let buildNumber = (bundle.object(forInfoDictionaryKey: "CFBundleVersion") as? String) ?? ""
|
||||
|
||||
let installTime = Self.timeMillisString(from: Self.installDate())
|
||||
let updateTime = Self.timeMillisString(from: Self.updateDate())
|
||||
|
||||
result([
|
||||
"appName": appName,
|
||||
"packageName": packageName,
|
||||
"version": version,
|
||||
"buildNumber": buildNumber,
|
||||
"installerStore": installerStore,
|
||||
"installTime": installTime as Any,
|
||||
"updateTime": updateTime as Any,
|
||||
])
|
||||
}
|
||||
|
||||
private static func installDate() -> Date? {
|
||||
guard
|
||||
let docsURL = FileManager.default.urls(
|
||||
for: .documentDirectory, in: .userDomainMask
|
||||
).last
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
let attrs = try? FileManager.default.attributesOfItem(atPath: docsURL.path)
|
||||
return attrs?[.creationDate] as? Date
|
||||
}
|
||||
|
||||
private static func updateDate() -> Date? {
|
||||
let attrs = try? FileManager.default.attributesOfItem(atPath: Bundle.main.bundlePath)
|
||||
return attrs?[.modificationDate] as? Date
|
||||
}
|
||||
|
||||
private static func timeMillisString(from date: Date?) -> String? {
|
||||
guard let date = date else { return nil }
|
||||
return String(Int64(date.timeIntervalSince1970 * 1000))
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,85 @@
|
||||
// Pure-Swift tvOS implementation of the legacy plugins.flutter.io/path_provider
|
||||
// channel. Replaces path_provider_foundation's FFI-based Dart impl, which
|
||||
// requires the objective_c package's dylib and isn't linked on tvOS.
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
import Flutter
|
||||
|
||||
public class PathProviderPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(
|
||||
name: "plugins.flutter.io/path_provider",
|
||||
binaryMessenger: registrar.messenger()
|
||||
)
|
||||
let instance = PathProviderPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
// On tvOS apps have no persistent writable storage; only the Caches
|
||||
// directory is writable (and can be evicted anytime). Docs, Support, and
|
||||
// Library all return dummy paths that fail on write. Route them into
|
||||
// Caches subdirectories so plugins expecting a writable path work.
|
||||
#if os(tvOS)
|
||||
let tvosCache = pathIn(.cachesDirectory)
|
||||
func tvosSubdir(_ name: String) -> String? {
|
||||
guard let base = tvosCache else { return nil }
|
||||
let p = (base as NSString).appendingPathComponent(name)
|
||||
try? FileManager.default.createDirectory(
|
||||
atPath: p, withIntermediateDirectories: true)
|
||||
return p
|
||||
}
|
||||
|
||||
switch call.method {
|
||||
case "getTemporaryDirectory":
|
||||
result(tvosCache)
|
||||
case "getApplicationSupportDirectory":
|
||||
result(tvosSubdir("ApplicationSupport"))
|
||||
case "getApplicationDocumentsDirectory":
|
||||
result(tvosSubdir("Documents"))
|
||||
case "getApplicationCacheDirectory":
|
||||
result(tvosSubdir("AppCache"))
|
||||
case "getLibraryDirectory":
|
||||
result(tvosSubdir("Library"))
|
||||
case "getDownloadsDirectory":
|
||||
result(tvosSubdir("Downloads"))
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
#else
|
||||
switch call.method {
|
||||
case "getTemporaryDirectory":
|
||||
result(pathIn(.cachesDirectory))
|
||||
case "getApplicationSupportDirectory":
|
||||
let path = pathIn(.applicationSupportDirectory)
|
||||
if let p = path {
|
||||
try? FileManager.default.createDirectory(
|
||||
atPath: p, withIntermediateDirectories: true)
|
||||
}
|
||||
result(path)
|
||||
case "getApplicationDocumentsDirectory":
|
||||
result(pathIn(.documentDirectory))
|
||||
case "getApplicationCacheDirectory":
|
||||
let path = pathIn(.cachesDirectory)
|
||||
if let p = path {
|
||||
try? FileManager.default.createDirectory(
|
||||
atPath: p, withIntermediateDirectories: true)
|
||||
}
|
||||
result(path)
|
||||
case "getLibraryDirectory":
|
||||
result(pathIn(.libraryDirectory))
|
||||
case "getDownloadsDirectory":
|
||||
result(pathIn(.downloadsDirectory))
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func pathIn(_ directory: FileManager.SearchPathDirectory) -> String? {
|
||||
NSSearchPathForDirectoriesInDomains(directory, .userDomainMask, true).first
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,194 @@
|
||||
// Copyright 2013 The Flutter Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
import Flutter
|
||||
#elseif os(macOS)
|
||||
import FlutterMacOS
|
||||
#endif
|
||||
|
||||
let argumentError: String = "Argument Error"
|
||||
|
||||
public class LegacySharedPreferencesPlugin: NSObject, FlutterPlugin, LegacyUserDefaultsApi {
|
||||
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let instance = LegacySharedPreferencesPlugin()
|
||||
// Workaround for https://github.com/flutter/flutter/issues/118103.
|
||||
#if os(iOS) || os(tvOS)
|
||||
let messenger = registrar.messenger()
|
||||
#else
|
||||
let messenger = registrar.messenger
|
||||
#endif
|
||||
LegacyUserDefaultsApiSetup.setUp(binaryMessenger: messenger, api: instance)
|
||||
}
|
||||
|
||||
func getAll(prefix: String, allowList: [String]?) -> [String: Any] {
|
||||
return getAllPrefs(prefix: prefix, allowList: allowList)
|
||||
}
|
||||
|
||||
func setBool(key: String, value: Bool) {
|
||||
UserDefaults.standard.set(value, forKey: key)
|
||||
}
|
||||
|
||||
func setDouble(key: String, value: Double) {
|
||||
UserDefaults.standard.set(value, forKey: key)
|
||||
}
|
||||
|
||||
func setValue(key: String, value: Any) {
|
||||
UserDefaults.standard.set(value, forKey: key)
|
||||
}
|
||||
|
||||
func remove(key: String) {
|
||||
UserDefaults.standard.removeObject(forKey: key)
|
||||
}
|
||||
|
||||
func clear(prefix: String, allowList: [String]?) -> Bool {
|
||||
let defaults = UserDefaults.standard
|
||||
for (key, _) in getAllPrefs(prefix: prefix, allowList: allowList) {
|
||||
defaults.removeObject(forKey: key)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/// Returns all preferences stored with specified prefix.
|
||||
/// If [allowList] is included, only items included will be returned.
|
||||
func getAllPrefs(prefix: String, allowList: [String]?) -> [String: Any] {
|
||||
var filteredPrefs: [String: Any] = [:]
|
||||
|
||||
let prefs = try! SharedPreferencesPlugin.getAllPrefs(
|
||||
allowList: allowList, options: SharedPreferencesPigeonOptions())
|
||||
|
||||
for (key, value) in prefs where (key.hasPrefix(prefix)) {
|
||||
filteredPrefs[key] = value
|
||||
}
|
||||
|
||||
return filteredPrefs
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class SharedPreferencesPlugin: NSObject, FlutterPlugin, UserDefaultsApi {
|
||||
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let instance = SharedPreferencesPlugin()
|
||||
// Workaround for https://github.com/flutter/flutter/issues/118103.
|
||||
#if os(iOS) || os(tvOS)
|
||||
let messenger = registrar.messenger()
|
||||
#else
|
||||
let messenger = registrar.messenger
|
||||
#endif
|
||||
UserDefaultsApiSetup.setUp(binaryMessenger: messenger, api: instance)
|
||||
LegacySharedPreferencesPlugin.register(with: registrar)
|
||||
}
|
||||
|
||||
static private func getUserDefaults(options: SharedPreferencesPigeonOptions) throws
|
||||
-> UserDefaults
|
||||
{
|
||||
#if os(iOS)
|
||||
if !(options.suiteName?.starts(with: "group.") ?? true) {
|
||||
throw FlutterError(
|
||||
code: argumentError,
|
||||
message:
|
||||
"The provided Suite Name '\(options.suiteName!)' does not follow the predefined requirements",
|
||||
details: "") as! Error
|
||||
}
|
||||
#endif
|
||||
let prefs = UserDefaults(suiteName: options.suiteName)
|
||||
|
||||
if prefs == nil {
|
||||
throw FlutterError(
|
||||
code: argumentError,
|
||||
message: "The provided Suite Name '\(options.suiteName!)' does not exist",
|
||||
details: "") as! Error
|
||||
}
|
||||
return prefs!
|
||||
}
|
||||
|
||||
func getKeys(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws -> [String] {
|
||||
return Array(try getAll(allowList: allowList, options: options).keys)
|
||||
}
|
||||
|
||||
func getAll(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws -> [String: Any]
|
||||
{
|
||||
return try SharedPreferencesPlugin.getAllPrefs(allowList: allowList, options: options)
|
||||
}
|
||||
|
||||
func set(key: String, value: Any, options: SharedPreferencesPigeonOptions) throws {
|
||||
try SharedPreferencesPlugin.getUserDefaults(options: options).set(value, forKey: key)
|
||||
}
|
||||
|
||||
func getValue(key: String, options: SharedPreferencesPigeonOptions) throws -> Any? {
|
||||
let preference = try SharedPreferencesPlugin.getUserDefaults(options: options).object(
|
||||
forKey: key)
|
||||
return SharedPreferencesPlugin.isTypeCompatible(value: preference as Any) ? preference : nil
|
||||
}
|
||||
|
||||
func remove(key: String, options: SharedPreferencesPigeonOptions) throws {
|
||||
try SharedPreferencesPlugin.getUserDefaults(options: options).removeObject(forKey: key)
|
||||
}
|
||||
|
||||
func clear(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws {
|
||||
let defaults = try SharedPreferencesPlugin.getUserDefaults(options: options)
|
||||
if let allowList = allowList {
|
||||
for (key) in allowList {
|
||||
defaults.removeObject(forKey: key)
|
||||
}
|
||||
} else {
|
||||
for key in defaults.dictionaryRepresentation().keys {
|
||||
defaults.removeObject(forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all preferences stored with specified prefix.
|
||||
/// If [allowList] is included, only items included will be returned.
|
||||
/// If no [allowList], returns supported types only.
|
||||
static func getAllPrefs(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws
|
||||
-> [String: Any]
|
||||
{
|
||||
var filteredPrefs: [String: Any] = [:]
|
||||
var compatiblePrefs: [String: Any] = [:]
|
||||
let allowSet = allowList.map { Set($0) }
|
||||
|
||||
// Since `getUserDefaults` is initialized with the suite name, it seems redundant to call
|
||||
// `persistentDomain` with the suite name again. However, it is necessary because
|
||||
// `dictionaryRepresentation` returns keys from the global domain.
|
||||
// Also, Apple's docs on `persistentDomain` are incorrect,
|
||||
// see: https://github.com/feedback-assistant/reports/issues/165
|
||||
if let appDomain = options.suiteName ?? Bundle.main.bundleIdentifier,
|
||||
let prefs = try getUserDefaults(options: options).persistentDomain(forName: appDomain)
|
||||
{
|
||||
if let allowSet = allowSet {
|
||||
filteredPrefs = prefs.filter { allowSet.contains($0.key) }
|
||||
} else {
|
||||
filteredPrefs = prefs
|
||||
}
|
||||
compatiblePrefs = filteredPrefs.filter { isTypeCompatible(value: $0.value) }
|
||||
}
|
||||
return compatiblePrefs
|
||||
}
|
||||
|
||||
static func isTypeCompatible(value: Any) -> Bool {
|
||||
switch value {
|
||||
case is Bool:
|
||||
return true
|
||||
case is Double:
|
||||
return true
|
||||
case is String:
|
||||
return true
|
||||
case is Int:
|
||||
return true
|
||||
case is [Any]:
|
||||
if let value = value as? [Any] {
|
||||
return value.allSatisfy(isTypeCompatible)
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
// Copyright 2013 The Flutter Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
// Autogenerated from Pigeon (v26.1.0), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
import Flutter
|
||||
#elseif os(macOS)
|
||||
import FlutterMacOS
|
||||
#else
|
||||
#error("Unsupported platform.")
|
||||
#endif
|
||||
|
||||
/// Error class for passing custom error details to Dart side.
|
||||
final class PigeonError: Error {
|
||||
let code: String
|
||||
let message: String?
|
||||
let details: Sendable?
|
||||
|
||||
init(code: String, message: String?, details: Sendable?) {
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.details = details
|
||||
}
|
||||
|
||||
var localizedDescription: String {
|
||||
return
|
||||
"PigeonError(code: \(code), message: \(message ?? "<nil>"), details: \(details ?? "<nil>")"
|
||||
}
|
||||
}
|
||||
|
||||
private func wrapResult(_ result: Any?) -> [Any?] {
|
||||
return [result]
|
||||
}
|
||||
|
||||
private func wrapError(_ error: Any) -> [Any?] {
|
||||
if let pigeonError = error as? PigeonError {
|
||||
return [
|
||||
pigeonError.code,
|
||||
pigeonError.message,
|
||||
pigeonError.details,
|
||||
]
|
||||
}
|
||||
if let flutterError = error as? FlutterError {
|
||||
return [
|
||||
flutterError.code,
|
||||
flutterError.message,
|
||||
flutterError.details,
|
||||
]
|
||||
}
|
||||
return [
|
||||
"\(error)",
|
||||
"\(type(of: error))",
|
||||
"Stacktrace: \(Thread.callStackSymbols)",
|
||||
]
|
||||
}
|
||||
|
||||
private func isNullish(_ value: Any?) -> Bool {
|
||||
return value is NSNull || value == nil
|
||||
}
|
||||
|
||||
private func nilOrValue<T>(_ value: Any?) -> T? {
|
||||
if value is NSNull { return nil }
|
||||
return value as! T?
|
||||
}
|
||||
|
||||
func deepEqualsmessages(_ lhs: Any?, _ rhs: Any?) -> Bool {
|
||||
let cleanLhs = nilOrValue(lhs) as Any?
|
||||
let cleanRhs = nilOrValue(rhs) as Any?
|
||||
switch (cleanLhs, cleanRhs) {
|
||||
case (nil, nil):
|
||||
return true
|
||||
|
||||
case (nil, _), (_, nil):
|
||||
return false
|
||||
|
||||
case is (Void, Void):
|
||||
return true
|
||||
|
||||
case let (cleanLhsHashable, cleanRhsHashable) as (AnyHashable, AnyHashable):
|
||||
return cleanLhsHashable == cleanRhsHashable
|
||||
|
||||
case let (cleanLhsArray, cleanRhsArray) as ([Any?], [Any?]):
|
||||
guard cleanLhsArray.count == cleanRhsArray.count else { return false }
|
||||
for (index, element) in cleanLhsArray.enumerated() {
|
||||
if !deepEqualsmessages(element, cleanRhsArray[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case let (cleanLhsDictionary, cleanRhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]):
|
||||
guard cleanLhsDictionary.count == cleanRhsDictionary.count else { return false }
|
||||
for (key, cleanLhsValue) in cleanLhsDictionary {
|
||||
guard cleanRhsDictionary.index(forKey: key) != nil else { return false }
|
||||
if !deepEqualsmessages(cleanLhsValue, cleanRhsDictionary[key]!) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
default:
|
||||
// Any other type shouldn't be able to be used with pigeon. File an issue if you find this to be untrue.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func deepHashmessages(value: Any?, hasher: inout Hasher) {
|
||||
if let valueList = value as? [AnyHashable] {
|
||||
for item in valueList { deepHashmessages(value: item, hasher: &hasher) }
|
||||
return
|
||||
}
|
||||
|
||||
if let valueDict = value as? [AnyHashable: AnyHashable] {
|
||||
for key in valueDict.keys {
|
||||
hasher.combine(key)
|
||||
deepHashmessages(value: valueDict[key]!, hasher: &hasher)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if let hashableValue = value as? AnyHashable {
|
||||
hasher.combine(hashableValue.hashValue)
|
||||
}
|
||||
|
||||
return hasher.combine(String(describing: value))
|
||||
}
|
||||
|
||||
/// Generated class from Pigeon that represents data sent in messages.
|
||||
struct SharedPreferencesPigeonOptions: Hashable {
|
||||
var suiteName: String? = nil
|
||||
|
||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||
static func fromList(_ pigeonVar_list: [Any?]) -> SharedPreferencesPigeonOptions? {
|
||||
let suiteName: String? = nilOrValue(pigeonVar_list[0])
|
||||
|
||||
return SharedPreferencesPigeonOptions(
|
||||
suiteName: suiteName
|
||||
)
|
||||
}
|
||||
func toList() -> [Any?] {
|
||||
return [
|
||||
suiteName
|
||||
]
|
||||
}
|
||||
static func == (lhs: SharedPreferencesPigeonOptions, rhs: SharedPreferencesPigeonOptions) -> Bool
|
||||
{
|
||||
return deepEqualsmessages(lhs.toList(), rhs.toList())
|
||||
}
|
||||
func hash(into hasher: inout Hasher) {
|
||||
deepHashmessages(value: toList(), hasher: &hasher)
|
||||
}
|
||||
}
|
||||
|
||||
private class MessagesPigeonCodecReader: FlutterStandardReader {
|
||||
override func readValue(ofType type: UInt8) -> Any? {
|
||||
switch type {
|
||||
case 129:
|
||||
return SharedPreferencesPigeonOptions.fromList(self.readValue() as! [Any?])
|
||||
default:
|
||||
return super.readValue(ofType: type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MessagesPigeonCodecWriter: FlutterStandardWriter {
|
||||
override func writeValue(_ value: Any) {
|
||||
if let value = value as? SharedPreferencesPigeonOptions {
|
||||
super.writeByte(129)
|
||||
super.writeValue(value.toList())
|
||||
} else {
|
||||
super.writeValue(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter {
|
||||
override func reader(with data: Data) -> FlutterStandardReader {
|
||||
return MessagesPigeonCodecReader(data: data)
|
||||
}
|
||||
|
||||
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
|
||||
return MessagesPigeonCodecWriter(data: data)
|
||||
}
|
||||
}
|
||||
|
||||
class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
|
||||
static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter())
|
||||
}
|
||||
|
||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||
protocol LegacyUserDefaultsApi {
|
||||
func remove(key: String) throws
|
||||
func setBool(key: String, value: Bool) throws
|
||||
func setDouble(key: String, value: Double) throws
|
||||
func setValue(key: String, value: Any) throws
|
||||
func getAll(prefix: String, allowList: [String]?) throws -> [String: Any]
|
||||
func clear(prefix: String, allowList: [String]?) throws -> Bool
|
||||
}
|
||||
|
||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||
class LegacyUserDefaultsApiSetup {
|
||||
static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared }
|
||||
/// Sets up an instance of `LegacyUserDefaultsApi` to handle messages through the `binaryMessenger`.
|
||||
static func setUp(
|
||||
binaryMessenger: FlutterBinaryMessenger, api: LegacyUserDefaultsApi?,
|
||||
messageChannelSuffix: String = ""
|
||||
) {
|
||||
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
|
||||
let removeChannel = FlutterBasicMessageChannel(
|
||||
name:
|
||||
"dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.remove\(channelSuffix)",
|
||||
binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
removeChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let keyArg = args[0] as! String
|
||||
do {
|
||||
try api.remove(key: keyArg)
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
removeChannel.setMessageHandler(nil)
|
||||
}
|
||||
let setBoolChannel = FlutterBasicMessageChannel(
|
||||
name:
|
||||
"dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setBool\(channelSuffix)",
|
||||
binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
setBoolChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let keyArg = args[0] as! String
|
||||
let valueArg = args[1] as! Bool
|
||||
do {
|
||||
try api.setBool(key: keyArg, value: valueArg)
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setBoolChannel.setMessageHandler(nil)
|
||||
}
|
||||
let setDoubleChannel = FlutterBasicMessageChannel(
|
||||
name:
|
||||
"dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setDouble\(channelSuffix)",
|
||||
binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
setDoubleChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let keyArg = args[0] as! String
|
||||
let valueArg = args[1] as! Double
|
||||
do {
|
||||
try api.setDouble(key: keyArg, value: valueArg)
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setDoubleChannel.setMessageHandler(nil)
|
||||
}
|
||||
let setValueChannel = FlutterBasicMessageChannel(
|
||||
name:
|
||||
"dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.setValue\(channelSuffix)",
|
||||
binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
setValueChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let keyArg = args[0] as! String
|
||||
let valueArg = args[1]!
|
||||
do {
|
||||
try api.setValue(key: keyArg, value: valueArg)
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setValueChannel.setMessageHandler(nil)
|
||||
}
|
||||
let getAllChannel = FlutterBasicMessageChannel(
|
||||
name:
|
||||
"dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.getAll\(channelSuffix)",
|
||||
binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
getAllChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let prefixArg = args[0] as! String
|
||||
let allowListArg: [String]? = nilOrValue(args[1])
|
||||
do {
|
||||
let result = try api.getAll(prefix: prefixArg, allowList: allowListArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getAllChannel.setMessageHandler(nil)
|
||||
}
|
||||
let clearChannel = FlutterBasicMessageChannel(
|
||||
name:
|
||||
"dev.flutter.pigeon.shared_preferences_foundation.LegacyUserDefaultsApi.clear\(channelSuffix)",
|
||||
binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
clearChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let prefixArg = args[0] as! String
|
||||
let allowListArg: [String]? = nilOrValue(args[1])
|
||||
do {
|
||||
let result = try api.clear(prefix: prefixArg, allowList: allowListArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clearChannel.setMessageHandler(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||
protocol UserDefaultsApi {
|
||||
/// Adds property to shared preferences data set of type String.
|
||||
func set(key: String, value: Any, options: SharedPreferencesPigeonOptions) throws
|
||||
/// Removes all properties from shared preferences data set with matching prefix.
|
||||
func clear(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws
|
||||
/// Gets all properties from shared preferences data set with matching prefix.
|
||||
func getAll(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws -> [String: Any]
|
||||
/// Gets individual value stored with [key], if any.
|
||||
func getValue(key: String, options: SharedPreferencesPigeonOptions) throws -> Any?
|
||||
/// Gets all properties from shared preferences data set with matching prefix.
|
||||
func getKeys(allowList: [String]?, options: SharedPreferencesPigeonOptions) throws -> [String]
|
||||
}
|
||||
|
||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||
class UserDefaultsApiSetup {
|
||||
static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared }
|
||||
/// Sets up an instance of `UserDefaultsApi` to handle messages through the `binaryMessenger`.
|
||||
static func setUp(
|
||||
binaryMessenger: FlutterBinaryMessenger, api: UserDefaultsApi?,
|
||||
messageChannelSuffix: String = ""
|
||||
) {
|
||||
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
|
||||
/// Adds property to shared preferences data set of type String.
|
||||
let setChannel = FlutterBasicMessageChannel(
|
||||
name: "dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.set\(channelSuffix)",
|
||||
binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
setChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let keyArg = args[0] as! String
|
||||
let valueArg = args[1]!
|
||||
let optionsArg = args[2] as! SharedPreferencesPigeonOptions
|
||||
do {
|
||||
try api.set(key: keyArg, value: valueArg, options: optionsArg)
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setChannel.setMessageHandler(nil)
|
||||
}
|
||||
/// Removes all properties from shared preferences data set with matching prefix.
|
||||
let clearChannel = FlutterBasicMessageChannel(
|
||||
name:
|
||||
"dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.clear\(channelSuffix)",
|
||||
binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
clearChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let allowListArg: [String]? = nilOrValue(args[0])
|
||||
let optionsArg = args[1] as! SharedPreferencesPigeonOptions
|
||||
do {
|
||||
try api.clear(allowList: allowListArg, options: optionsArg)
|
||||
reply(wrapResult(nil))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
clearChannel.setMessageHandler(nil)
|
||||
}
|
||||
/// Gets all properties from shared preferences data set with matching prefix.
|
||||
let getAllChannel = FlutterBasicMessageChannel(
|
||||
name:
|
||||
"dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getAll\(channelSuffix)",
|
||||
binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
getAllChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let allowListArg: [String]? = nilOrValue(args[0])
|
||||
let optionsArg = args[1] as! SharedPreferencesPigeonOptions
|
||||
do {
|
||||
let result = try api.getAll(allowList: allowListArg, options: optionsArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getAllChannel.setMessageHandler(nil)
|
||||
}
|
||||
/// Gets individual value stored with [key], if any.
|
||||
let getValueChannel = FlutterBasicMessageChannel(
|
||||
name:
|
||||
"dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getValue\(channelSuffix)",
|
||||
binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
getValueChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let keyArg = args[0] as! String
|
||||
let optionsArg = args[1] as! SharedPreferencesPigeonOptions
|
||||
do {
|
||||
let result = try api.getValue(key: keyArg, options: optionsArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getValueChannel.setMessageHandler(nil)
|
||||
}
|
||||
/// Gets all properties from shared preferences data set with matching prefix.
|
||||
let getKeysChannel = FlutterBasicMessageChannel(
|
||||
name:
|
||||
"dev.flutter.pigeon.shared_preferences_foundation.UserDefaultsApi.getKeys\(channelSuffix)",
|
||||
binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
getKeysChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let allowListArg: [String]? = nilOrValue(args[0])
|
||||
let optionsArg = args[1] as! SharedPreferencesPigeonOptions
|
||||
do {
|
||||
let result = try api.getKeys(allowList: allowListArg, options: optionsArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
getKeysChannel.setMessageHandler(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import GameController
|
||||
|
||||
/// Maps GCController elements to W3C Standard Gamepad indices.
|
||||
///
|
||||
/// W3C Standard Gamepad button mapping:
|
||||
/// 0 = A (South) 8 = Back/View
|
||||
/// 1 = B (East) 9 = Start/Menu
|
||||
/// 2 = X (West) 10 = Left Stick Button
|
||||
/// 3 = Y (North) 11 = Right Stick Button
|
||||
/// 4 = Left Shoulder 12 = D-pad Up
|
||||
/// 5 = Right Shoulder 13 = D-pad Down
|
||||
/// 6 = Left Trigger 14 = D-pad Left
|
||||
/// 7 = Right Trigger 15 = D-pad Right
|
||||
/// 16 = Guide/Home
|
||||
///
|
||||
/// W3C Standard Gamepad axis mapping:
|
||||
/// 0 = Left Stick X (-1 left, +1 right)
|
||||
/// 1 = Left Stick Y (-1 up, +1 down)
|
||||
/// 2 = Right Stick X (-1 left, +1 right)
|
||||
/// 3 = Right Stick Y (-1 up, +1 down)
|
||||
///
|
||||
/// Note: GCController reports Y-axis with up = +1, which is the opposite of
|
||||
/// the W3C convention (up = -1). The axis mapping inverts Y values.
|
||||
enum ButtonMapping {
|
||||
|
||||
/// Returns the W3C button index and the corresponding `GCControllerButtonInput`
|
||||
/// if `element` matches a known button in `gamepad`.
|
||||
static func buttonIndex(for element: GCControllerElement,
|
||||
in gamepad: GCExtendedGamepad) -> (Int, GCControllerButtonInput)? {
|
||||
// Face buttons
|
||||
if element == gamepad.buttonA {
|
||||
return (0, gamepad.buttonA)
|
||||
}
|
||||
if element == gamepad.buttonB {
|
||||
return (1, gamepad.buttonB)
|
||||
}
|
||||
if element == gamepad.buttonX {
|
||||
return (2, gamepad.buttonX)
|
||||
}
|
||||
if element == gamepad.buttonY {
|
||||
return (3, gamepad.buttonY)
|
||||
}
|
||||
|
||||
// Shoulders
|
||||
if element == gamepad.leftShoulder {
|
||||
return (4, gamepad.leftShoulder)
|
||||
}
|
||||
if element == gamepad.rightShoulder {
|
||||
return (5, gamepad.rightShoulder)
|
||||
}
|
||||
|
||||
// Triggers
|
||||
if element == gamepad.leftTrigger {
|
||||
return (6, gamepad.leftTrigger)
|
||||
}
|
||||
if element == gamepad.rightTrigger {
|
||||
return (7, gamepad.rightTrigger)
|
||||
}
|
||||
|
||||
// Menu buttons
|
||||
if let buttonOptions = gamepad.buttonOptions, element == buttonOptions {
|
||||
return (8, buttonOptions)
|
||||
}
|
||||
if element == gamepad.buttonMenu {
|
||||
return (9, gamepad.buttonMenu)
|
||||
}
|
||||
|
||||
// Thumbstick buttons
|
||||
if let leftThumbstickButton = gamepad.leftThumbstickButton, element == leftThumbstickButton {
|
||||
return (10, leftThumbstickButton)
|
||||
}
|
||||
if let rightThumbstickButton = gamepad.rightThumbstickButton, element == rightThumbstickButton {
|
||||
return (11, rightThumbstickButton)
|
||||
}
|
||||
|
||||
// D-pad individual buttons
|
||||
if element == gamepad.dpad.up {
|
||||
return (12, gamepad.dpad.up)
|
||||
}
|
||||
if element == gamepad.dpad.down {
|
||||
return (13, gamepad.dpad.down)
|
||||
}
|
||||
if element == gamepad.dpad.left {
|
||||
return (14, gamepad.dpad.left)
|
||||
}
|
||||
if element == gamepad.dpad.right {
|
||||
return (15, gamepad.dpad.right)
|
||||
}
|
||||
|
||||
// Guide / Home button
|
||||
if let buttonHome = gamepad.buttonHome, element == buttonHome {
|
||||
return (16, buttonHome)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Returns all W3C axis events for the given `element`.
|
||||
///
|
||||
/// The `valueChangedHandler` on `GCExtendedGamepad` may fire with the
|
||||
/// entire `GCControllerDirectionPad` (thumbstick) as the element, or with
|
||||
/// an individual axis sub-element. When the full thumbstick fires we
|
||||
/// return both X and Y axis entries. When a single axis fires we return
|
||||
/// one entry.
|
||||
///
|
||||
/// Y-axis values are inverted: GCController uses +1 for up, but the W3C
|
||||
/// standard uses -1 for up.
|
||||
static func axisIndices(for element: GCControllerElement,
|
||||
in gamepad: GCExtendedGamepad) -> [(Int, Double)] {
|
||||
// Left stick (full thumbstick element)
|
||||
if element == gamepad.leftThumbstick {
|
||||
return [
|
||||
(0, Double(gamepad.leftThumbstick.xAxis.value)),
|
||||
(1, Double(-gamepad.leftThumbstick.yAxis.value)),
|
||||
]
|
||||
}
|
||||
|
||||
// Left stick individual axes
|
||||
if element == gamepad.leftThumbstick.xAxis {
|
||||
return [(0, Double(gamepad.leftThumbstick.xAxis.value))]
|
||||
}
|
||||
if element == gamepad.leftThumbstick.yAxis {
|
||||
// Invert Y: GC up is +1, W3C up is -1
|
||||
return [(1, Double(-gamepad.leftThumbstick.yAxis.value))]
|
||||
}
|
||||
|
||||
// Right stick (full thumbstick element)
|
||||
if element == gamepad.rightThumbstick {
|
||||
return [
|
||||
(2, Double(gamepad.rightThumbstick.xAxis.value)),
|
||||
(3, Double(-gamepad.rightThumbstick.yAxis.value)),
|
||||
]
|
||||
}
|
||||
|
||||
// Right stick individual axes
|
||||
if element == gamepad.rightThumbstick.xAxis {
|
||||
return [(2, Double(gamepad.rightThumbstick.xAxis.value))]
|
||||
}
|
||||
if element == gamepad.rightThumbstick.yAxis {
|
||||
// Invert Y: GC up is +1, W3C up is -1
|
||||
return [(3, Double(-gamepad.rightThumbstick.yAxis.value))]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
import GameController
|
||||
|
||||
/// Manages GCController connections and input, translating events into the
|
||||
/// compact wire-format arrays expected by the Dart side.
|
||||
class GCControllerManager {
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let streamHandler: GamepadStreamHandler
|
||||
|
||||
/// Maps a GCController instance to its stable int gamepad ID.
|
||||
private var controllerIds: [ObjectIdentifier: Int] = [:]
|
||||
|
||||
/// Counter for generating unique IDs.
|
||||
private var nextId: Int = 0
|
||||
|
||||
/// Notification observers so we can remove them on dispose.
|
||||
private var connectObserver: NSObjectProtocol?
|
||||
private var disconnectObserver: NSObjectProtocol?
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(streamHandler: GamepadStreamHandler) {
|
||||
self.streamHandler = streamHandler
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
/// Begin listening for GCController connect/disconnect notifications and
|
||||
/// register input handlers on any controllers that are already connected.
|
||||
func startObserving() {
|
||||
connectObserver = NotificationCenter.default.addObserver(
|
||||
forName: .GCControllerDidConnect,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] notification in
|
||||
guard let self = self,
|
||||
let controller = notification.object as? GCController else { return }
|
||||
self.controllerDidConnect(controller)
|
||||
}
|
||||
|
||||
disconnectObserver = NotificationCenter.default.addObserver(
|
||||
forName: .GCControllerDidDisconnect,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] notification in
|
||||
guard let self = self,
|
||||
let controller = notification.object as? GCController else { return }
|
||||
self.controllerDidDisconnect(controller)
|
||||
}
|
||||
|
||||
// Handle controllers that were already connected before we started
|
||||
// observing (e.g. wired controllers).
|
||||
for controller in GCController.controllers() {
|
||||
controllerDidConnect(controller)
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove all observers and clear input handlers.
|
||||
func stopObserving() {
|
||||
if let observer = connectObserver {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
connectObserver = nil
|
||||
}
|
||||
if let observer = disconnectObserver {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
disconnectObserver = nil
|
||||
}
|
||||
|
||||
// Remove value changed handlers from all tracked controllers.
|
||||
for controller in GCController.controllers() {
|
||||
controller.extendedGamepad?.valueChangedHandler = nil
|
||||
}
|
||||
|
||||
controllerIds.removeAll()
|
||||
}
|
||||
|
||||
// MARK: - listGamepads
|
||||
|
||||
/// Returns an array of dictionaries describing currently connected
|
||||
/// controllers, suitable for returning over a MethodChannel.
|
||||
func listGamepads() -> [[String: Any]] {
|
||||
var result: [[String: Any]] = []
|
||||
for controller in GCController.controllers() {
|
||||
let key = ObjectIdentifier(controller)
|
||||
guard let gamepadId = controllerIds[key] else { continue }
|
||||
|
||||
var info: [String: Any] = [
|
||||
"id": gamepadId,
|
||||
"name": controllerName(controller),
|
||||
]
|
||||
|
||||
if let vendorId = vendorId(for: controller) {
|
||||
info["vendorId"] = vendorId
|
||||
}
|
||||
if let productId = productId(for: controller) {
|
||||
info["productId"] = productId
|
||||
}
|
||||
|
||||
result.append(info)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - Connection events
|
||||
|
||||
private func controllerDidConnect(_ controller: GCController) {
|
||||
let gamepadId = assignId(for: controller)
|
||||
|
||||
// Register input handlers for the extended gamepad profile.
|
||||
registerInputHandlers(for: controller, gamepadId: gamepadId)
|
||||
|
||||
// Wire format: [0, gamepadId, timestamp, connected, name, vendorId, productId]
|
||||
let event: [Any] = [
|
||||
0,
|
||||
gamepadId,
|
||||
currentTimestamp(),
|
||||
true,
|
||||
controllerName(controller),
|
||||
vendorId(for: controller) as Any,
|
||||
productId(for: controller) as Any,
|
||||
]
|
||||
streamHandler.send(event: event)
|
||||
}
|
||||
|
||||
private func controllerDidDisconnect(_ controller: GCController) {
|
||||
let key = ObjectIdentifier(controller)
|
||||
guard let gamepadId = controllerIds.removeValue(forKey: key) else { return }
|
||||
|
||||
controller.extendedGamepad?.valueChangedHandler = nil
|
||||
|
||||
// Wire format: [0, gamepadId, timestamp, connected, name, vendorId, productId]
|
||||
let event: [Any] = [
|
||||
0,
|
||||
gamepadId,
|
||||
currentTimestamp(),
|
||||
false,
|
||||
controllerName(controller),
|
||||
vendorId(for: controller) as Any,
|
||||
productId(for: controller) as Any,
|
||||
]
|
||||
streamHandler.send(event: event)
|
||||
}
|
||||
|
||||
// MARK: - Input handlers
|
||||
|
||||
private func registerInputHandlers(for controller: GCController, gamepadId: Int) {
|
||||
guard let extendedGamepad = controller.extendedGamepad else { return }
|
||||
|
||||
extendedGamepad.valueChangedHandler = { [weak self] (gamepad, element) in
|
||||
guard let self = self else { return }
|
||||
self.handleValueChanged(gamepad: gamepad, element: element, gamepadId: gamepadId)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleValueChanged(gamepad: GCExtendedGamepad,
|
||||
element: GCControllerElement,
|
||||
gamepadId: Int) {
|
||||
let timestamp = currentTimestamp()
|
||||
|
||||
// D-pad fires as a single GCControllerDirectionPad element, not as
|
||||
// individual direction buttons. Expand it into 4 button events.
|
||||
if element === gamepad.dpad {
|
||||
sendButtonEvent(gamepadId: gamepadId, index: 12,
|
||||
pressed: gamepad.dpad.up.isPressed,
|
||||
value: Double(gamepad.dpad.up.value), timestamp: timestamp)
|
||||
sendButtonEvent(gamepadId: gamepadId, index: 13,
|
||||
pressed: gamepad.dpad.down.isPressed,
|
||||
value: Double(gamepad.dpad.down.value), timestamp: timestamp)
|
||||
sendButtonEvent(gamepadId: gamepadId, index: 14,
|
||||
pressed: gamepad.dpad.left.isPressed,
|
||||
value: Double(gamepad.dpad.left.value), timestamp: timestamp)
|
||||
sendButtonEvent(gamepadId: gamepadId, index: 15,
|
||||
pressed: gamepad.dpad.right.isPressed,
|
||||
value: Double(gamepad.dpad.right.value), timestamp: timestamp)
|
||||
return
|
||||
}
|
||||
|
||||
// Check buttons.
|
||||
if let (index, button) = ButtonMapping.buttonIndex(for: element, in: gamepad) {
|
||||
sendButtonEvent(gamepadId: gamepadId, index: index,
|
||||
pressed: button.isPressed,
|
||||
value: Double(button.value), timestamp: timestamp)
|
||||
return
|
||||
}
|
||||
|
||||
// Check axes (may produce multiple events when a full thumbstick fires).
|
||||
let axisEvents = ButtonMapping.axisIndices(for: element, in: gamepad)
|
||||
for (index, axisValue) in axisEvents {
|
||||
// Wire format: [2, gamepadId, timestamp, axisIndex, value]
|
||||
streamHandler.send(event: [2, gamepadId, timestamp, index, axisValue])
|
||||
}
|
||||
}
|
||||
|
||||
private func sendButtonEvent(gamepadId: Int, index: Int,
|
||||
pressed: Bool, value: Double, timestamp: Int) {
|
||||
// Wire format: [1, gamepadId, timestamp, buttonIndex, pressed, value]
|
||||
streamHandler.send(event: [1, gamepadId, timestamp, index, pressed, value])
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func assignId(for controller: GCController) -> Int {
|
||||
let key = ObjectIdentifier(controller)
|
||||
if let existing = controllerIds[key] {
|
||||
return existing
|
||||
}
|
||||
let id = nextId
|
||||
nextId += 1
|
||||
controllerIds[key] = id
|
||||
return id
|
||||
}
|
||||
|
||||
private func controllerName(_ controller: GCController) -> String {
|
||||
let category = controller.productCategory
|
||||
if !category.isEmpty {
|
||||
return category
|
||||
}
|
||||
return controller.vendorName ?? "Unknown Controller"
|
||||
}
|
||||
|
||||
/// Returns the USB vendor ID for the controller, if available.
|
||||
///
|
||||
/// GCController does not directly expose numeric vendor/product IDs
|
||||
/// through the public API. We attempt to obtain them from the
|
||||
/// underlying physical device description on iOS 16+.
|
||||
private func vendorId(for controller: GCController) -> Int? {
|
||||
// GCController does not publicly expose numeric vendor IDs on iOS.
|
||||
// Return nil; the Dart side treats this as optional.
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Returns the USB product ID for the controller, if available.
|
||||
private func productId(for controller: GCController) -> Int? {
|
||||
// GCController does not publicly expose numeric product IDs on iOS.
|
||||
// Return nil; the Dart side treats this as optional.
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Returns the current timestamp in milliseconds since epoch.
|
||||
private func currentTimestamp() -> Int {
|
||||
return Int(Date().timeIntervalSince1970 * 1000)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
/// Entry point for the gamepad iOS plugin.
|
||||
///
|
||||
/// Registers a MethodChannel (`dev.universal_gamepad/methods`) and an
|
||||
/// EventChannel (`dev.universal_gamepad/events`) with the Flutter engine.
|
||||
public class GamepadPlugin: NSObject, FlutterPlugin {
|
||||
|
||||
private let controllerManager: GCControllerManager
|
||||
private let streamHandler: GamepadStreamHandler
|
||||
|
||||
init(controllerManager: GCControllerManager, streamHandler: GamepadStreamHandler) {
|
||||
self.controllerManager = controllerManager
|
||||
self.streamHandler = streamHandler
|
||||
super.init()
|
||||
}
|
||||
|
||||
// MARK: - FlutterPlugin
|
||||
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let streamHandler = GamepadStreamHandler()
|
||||
let controllerManager = GCControllerManager(streamHandler: streamHandler)
|
||||
|
||||
let methodChannel = FlutterMethodChannel(
|
||||
name: "dev.universal_gamepad/methods",
|
||||
binaryMessenger: registrar.messenger()
|
||||
)
|
||||
|
||||
let eventChannel = FlutterEventChannel(
|
||||
name: "dev.universal_gamepad/events",
|
||||
binaryMessenger: registrar.messenger()
|
||||
)
|
||||
|
||||
let instance = GamepadPlugin(
|
||||
controllerManager: controllerManager,
|
||||
streamHandler: streamHandler
|
||||
)
|
||||
|
||||
registrar.addMethodCallDelegate(instance, channel: methodChannel)
|
||||
eventChannel.setStreamHandler(streamHandler)
|
||||
|
||||
// Start observing controllers immediately so we capture connections
|
||||
// that happen before the Dart side listens.
|
||||
controllerManager.startObserving()
|
||||
}
|
||||
|
||||
// MARK: - MethodChannel
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "listGamepads":
|
||||
result(controllerManager.listGamepads())
|
||||
case "dispose":
|
||||
controllerManager.stopObserving()
|
||||
result(nil)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import Flutter
|
||||
|
||||
/// FlutterStreamHandler that forwards native gamepad events to Dart.
|
||||
///
|
||||
/// Events are queued if no sink is attached yet; once a listener subscribes
|
||||
/// all queued events are flushed immediately.
|
||||
public class GamepadStreamHandler: NSObject, FlutterStreamHandler {
|
||||
|
||||
/// The active event sink provided by Flutter's EventChannel.
|
||||
private var eventSink: FlutterEventSink?
|
||||
|
||||
/// Events received before the Dart side starts listening.
|
||||
private var pendingEvents: [[Any]] = []
|
||||
|
||||
/// Thread-safety lock for sink / queue access.
|
||||
private let lock = NSLock()
|
||||
|
||||
// MARK: - FlutterStreamHandler
|
||||
|
||||
public func onListen(withArguments arguments: Any?,
|
||||
eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
||||
lock.lock()
|
||||
eventSink = events
|
||||
|
||||
// Flush any events that arrived before the listener was attached.
|
||||
for event in pendingEvents {
|
||||
events(event)
|
||||
}
|
||||
pendingEvents.removeAll()
|
||||
lock.unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
||||
lock.lock()
|
||||
eventSink = nil
|
||||
lock.unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - Internal API
|
||||
|
||||
/// Sends a gamepad event array to Dart.
|
||||
///
|
||||
/// If the sink is not yet available the event is queued.
|
||||
func send(event: [Any]) {
|
||||
lock.lock()
|
||||
if let sink = eventSink {
|
||||
lock.unlock()
|
||||
// Dispatch on the main thread to satisfy Flutter platform channel
|
||||
// requirements.
|
||||
DispatchQueue.main.async {
|
||||
sink(event)
|
||||
}
|
||||
} else {
|
||||
pendingEvents.append(event)
|
||||
lock.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@@ -0,0 +1 @@
|
||||
3.41.6
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>13.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
# Exit on error
|
||||
set -e
|
||||
|
||||
|
||||
#determine ios folder
|
||||
if [[ -d ./_ios ]]; then
|
||||
IOS_DIR=./_ios
|
||||
|
||||
if [[ -d ./ios ]]; then
|
||||
TVOS_DIR=./ios
|
||||
else
|
||||
echo "Error: could not find './ios' folder with tvos content"
|
||||
exit 1
|
||||
fi
|
||||
elif [[ -d ./ios ]]; then
|
||||
IOS_DIR=./ios
|
||||
|
||||
if [[ -d ./tvos ]]; then
|
||||
TVOS_DIR=./tvos
|
||||
else
|
||||
echo "Error: could not find './tvos' folder with tvos content"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Error: could not find './ios' or './_ios' folder"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
IOS_FLUTTER_ASSETS_DIR=$IOS_DIR/Flutter/App.framework/flutter_assets
|
||||
TVOS_FLUTTER_ASSETS_DIR=$TVOS_DIR/tvos_flutter_assets/flutter_assets
|
||||
|
||||
|
||||
if [[ -d $IOS_FLUTTER_ASSETS_DIR ]]; then
|
||||
|
||||
echo "Copying flutter_assets from IOS compiled target"
|
||||
|
||||
|
||||
# better approuch would to copy complete assets folder and remove files that are generated by compilation step
|
||||
# isolate_snapshot_data, isolate_snapshot_instr, kernel_blob, vm_snapshot_data
|
||||
# note, this list might be different for debug (jit) and release (aot)??????
|
||||
|
||||
rm -rf "$TVOS_FLUTTER_ASSETS_DIR"
|
||||
mkdir -p "$TVOS_FLUTTER_ASSETS_DIR"
|
||||
|
||||
echo " └─App.framework/flutter_assets/AssetManifest.json"
|
||||
cp -v "$IOS_FLUTTER_ASSETS_DIR/AssetManifest.json" "$TVOS_FLUTTER_ASSETS_DIR/AssetManifest.json"
|
||||
|
||||
echo " └─App.framework/flutter_assets/FontManifest.json"
|
||||
cp -v "$IOS_FLUTTER_ASSETS_DIR/FontManifest.json" "$TVOS_FLUTTER_ASSETS_DIR/FontManifest.json"
|
||||
|
||||
set +e
|
||||
# the content has changed from flutter v 1.15.19 to 1.19.0-4.3pre
|
||||
echo " └─App.framework/flutter_assets/LICENSE"
|
||||
cp -v "$IOS_FLUTTER_ASSETS_DIR/LICENSE" "$TVOS_FLUTTER_ASSETS_DIR/LICENSE"
|
||||
|
||||
echo " └─App.framework/flutter_assets/NOTICES"
|
||||
cp -v "$IOS_FLUTTER_ASSETS_DIR/NOTICES" "$TVOS_FLUTTER_ASSETS_DIR/NOTICES"
|
||||
|
||||
echo " └─App.framework/flutter_assets/assets/"
|
||||
mkdir -p "$TVOS_FLUTTER_ASSETS_DIR/assets"
|
||||
cp -v -R "$IOS_FLUTTER_ASSETS_DIR/assets" "$TVOS_FLUTTER_ASSETS_DIR"
|
||||
|
||||
echo " └─App.framework/flutter_assets/fonts/"
|
||||
mkdir -p "$TVOS_FLUTTER_ASSETS_DIR/fonts"
|
||||
cp -v -R "$IOS_FLUTTER_ASSETS_DIR/fonts" "$TVOS_FLUTTER_ASSETS_DIR"
|
||||
|
||||
echo " └─App.framework/flutter_assets/packages/"
|
||||
mkdir -p "$TVOS_FLUTTER_ASSETS_DIR/packages"
|
||||
cp -v -R "$IOS_FLUTTER_ASSETS_DIR/packages" "$TVOS_FLUTTER_ASSETS_DIR"
|
||||
|
||||
set -e
|
||||
echo " └─Done"
|
||||
|
||||
else
|
||||
echo "'./ios/Flutter/App.framework/flutter_assets' folder does not not exist or IOS target not compiled yet, build any ios buiild first!"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# based on scripts in /engine/src/flutter/testing/scenario_app
|
||||
# and xcode_backend.sh script for integration in xcode
|
||||
|
||||
|
||||
# Exit on error
|
||||
set -e
|
||||
|
||||
if [[ $(uname -m) == "arm64" ]]; then
|
||||
TARGET_POSTFIX='_arm64'
|
||||
CLANG_POSTFIX='_arm64'
|
||||
else
|
||||
TARGET_POSTFIX=''
|
||||
CLANG_POSTFIX='_X86'
|
||||
fi
|
||||
|
||||
|
||||
if [ -z "$FLUTTER_LOCAL_ENGINE" ]; then
|
||||
echo " └─ERROR: FLUTTER_LOCAL_ENGINE not set!"
|
||||
return 1;
|
||||
fi
|
||||
|
||||
if [ "$1" == "release" ]; then
|
||||
echo "Coping Flutter.framework (release)..."
|
||||
DEVICE_TOOLS=$FLUTTER_LOCAL_ENGINE/out/ios_release$TARGET_POSTFIX
|
||||
elif [[ "$1" == "debug_sim" ]] ; then
|
||||
echo "Coping Flutter.framework (debug-simulator)..."
|
||||
DEVICE_TOOLS=$FLUTTER_LOCAL_ENGINE/out/ios_debug_sim_unopt$TARGET_POSTFIX
|
||||
else
|
||||
#debug
|
||||
echo "Coping Flutter.framework (debug)..."
|
||||
DEVICE_TOOLS=$FLUTTER_LOCAL_ENGINE/out/ios_debug_unopt$TARGET_POSTFIX
|
||||
fi
|
||||
|
||||
|
||||
OUTDIR=$PWD/Flutter
|
||||
|
||||
rm -rf "$OUTDIR/Flutter.framework"
|
||||
cp -R "$DEVICE_TOOLS/Flutter.framework" "$OUTDIR"
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download the prebuilt flutter-tvos engine tarball from GitHub Releases,
|
||||
# extract it into a shared cache, and write tvos/Flutter/Generated.xcconfig
|
||||
# so Xcode picks it up via FLUTTER_LOCAL_ENGINE.
|
||||
#
|
||||
# Reads the engine version from tvos/engine.version. Re-runs are cheap —
|
||||
# skips download if the cache already has the matching version.
|
||||
#
|
||||
# Usage:
|
||||
# tvos/scripts/fetch_engine.sh
|
||||
#
|
||||
# Env overrides:
|
||||
# FLUTTER_TVOS_ENGINE_CACHE — root dir for cached engines (default: ~/.cache/flutter-tvos-engine)
|
||||
# FLUTTER_TVOS_RELEASES_URL — base URL for release tarballs (default: github.com/edde746/flutter-tvos)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TVOS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
REPO_ROOT="$(cd "${TVOS_DIR}/.." && pwd)"
|
||||
|
||||
VERSION_FILE="${TVOS_DIR}/engine.version"
|
||||
if [[ ! -f "$VERSION_FILE" ]]; then
|
||||
echo "error: $VERSION_FILE missing" >&2
|
||||
exit 1
|
||||
fi
|
||||
VERSION="$(tr -d '[:space:]' < "$VERSION_FILE")"
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo "error: tvos/engine.version is empty" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
CACHE_ROOT="${FLUTTER_TVOS_ENGINE_CACHE:-$HOME/.cache/flutter-tvos-engine}"
|
||||
RELEASES_URL="${FLUTTER_TVOS_RELEASES_URL:-https://github.com/edde746/flutter-tvos}"
|
||||
|
||||
ENGINE_DIR="${CACHE_ROOT}/v${VERSION}"
|
||||
TARBALL_URL="${RELEASES_URL}/releases/download/v${VERSION}/flutter-tvos-${VERSION}.tar.gz"
|
||||
STAMP="${ENGINE_DIR}/.installed-${VERSION}"
|
||||
|
||||
if [[ ! -f "$STAMP" ]]; then
|
||||
echo "[fetch_engine] downloading ${TARBALL_URL}"
|
||||
mkdir -p "$ENGINE_DIR"
|
||||
TMP_TAR="$(mktemp -t flutter-tvos-engine.XXXXXX.tar.gz)"
|
||||
trap 'rm -f "$TMP_TAR"' EXIT
|
||||
curl -fL --progress-bar -o "$TMP_TAR" "$TARBALL_URL"
|
||||
echo "[fetch_engine] extracting to $ENGINE_DIR"
|
||||
tar -xzf "$TMP_TAR" -C "$ENGINE_DIR"
|
||||
touch "$STAMP"
|
||||
rm -f "$TMP_TAR"
|
||||
trap - EXIT
|
||||
else
|
||||
echo "[fetch_engine] using cached engine at $ENGINE_DIR"
|
||||
fi
|
||||
|
||||
# Locate a host Flutter SDK for flutter CLI invocation during the build.
|
||||
if [[ -n "${FLUTTER_ROOT:-}" ]]; then
|
||||
FLUTTER_ROOT_RESOLVED="$FLUTTER_ROOT"
|
||||
elif command -v flutter >/dev/null 2>&1; then
|
||||
FLUTTER_ROOT_RESOLVED="$(dirname "$(dirname "$(command -v flutter)")")"
|
||||
else
|
||||
echo "error: couldn't find Flutter SDK — set FLUTTER_ROOT or put flutter on PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Read version name/number from pubspec so Generated.xcconfig matches the app.
|
||||
PUBSPEC="${REPO_ROOT}/pubspec.yaml"
|
||||
PUB_NAME=""
|
||||
PUB_NUMBER=""
|
||||
if [[ -f "$PUBSPEC" ]]; then
|
||||
VER_LINE="$(awk '/^version:/ {print $2; exit}' "$PUBSPEC" | tr -d '"')"
|
||||
if [[ -n "$VER_LINE" ]]; then
|
||||
PUB_NAME="${VER_LINE%+*}"
|
||||
if [[ "$VER_LINE" == *+* ]]; then
|
||||
PUB_NUMBER="${VER_LINE#*+}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
PUB_NAME="${PUB_NAME:-1.0.0}"
|
||||
PUB_NUMBER="${PUB_NUMBER:-1}"
|
||||
|
||||
# tvOS deployment target is hard-coded at the Podfile / project level to 14.0.
|
||||
# Write Generated.xcconfig so Xcode (and the Run Script phase) see the engine.
|
||||
GEN_XC="${TVOS_DIR}/Flutter/Generated.xcconfig"
|
||||
mkdir -p "$(dirname "$GEN_XC")"
|
||||
cat > "$GEN_XC" <<EOF
|
||||
FLUTTER_ROOT=${FLUTTER_ROOT_RESOLVED}
|
||||
FLUTTER_APPLICATION_PATH=${REPO_ROOT}
|
||||
FLUTTER_TARGET=lib/main.dart
|
||||
FLUTTER_BUILD_NAME=${PUB_NAME}
|
||||
FLUTTER_BUILD_NUMBER=${PUB_NUMBER}
|
||||
TVOS_DEPLOYMENT_TARGET=14.0
|
||||
FLUTTER_LOCAL_ENGINE=${ENGINE_DIR}
|
||||
PODS_ROOT=${TVOS_DIR}/Pods
|
||||
EOF
|
||||
|
||||
echo
|
||||
echo "Engine ready. Wrote $GEN_XC with:"
|
||||
echo " FLUTTER_LOCAL_ENGINE=$ENGINE_DIR"
|
||||
echo " FLUTTER_BUILD_NAME=$PUB_NAME"
|
||||
echo " FLUTTER_BUILD_NUMBER=$PUB_NUMBER"
|
||||
echo
|
||||
echo "Next: open tvos/Runner.xcworkspace in Xcode and build."
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
RED='\033[0;31m'
|
||||
NOCOLOR='\033[0m'
|
||||
|
||||
if [[ $(uname -m) == "arm64" ]]; then
|
||||
echo "Host: arm64"
|
||||
GN_SIM_ARGS="--simulator-cpu=arm64"
|
||||
GN_ARGS="--mac-cpu=arm64"
|
||||
GN_ARGS_HOST="--mac-cpu=arm64 "
|
||||
OUTPUT_POSTFIX="_arm64"
|
||||
fi
|
||||
|
||||
if [[ "$1" == "clean" ]]; then
|
||||
echo "Clean build ..."
|
||||
rm -irf ./out/ios_debug_sim_unopt$OUTPUT_POSTFIX
|
||||
rm -rf ./out/ios_debug_unopt$OUTPUT_POSTFIX
|
||||
rm -rf ./out/ios_release$OUTPUT_POSTFIX
|
||||
rm -rf ./out/host_debug_unopt$OUTPUT_POSTFIX
|
||||
rm -rf ./out/host_release$OUTPUT_POSTFIX
|
||||
fi
|
||||
|
||||
if [[ $(uname -m) != "arm64" ]]; then
|
||||
echo "Host: x64"
|
||||
GN_ARGS_HOST="--mac-cpu=x64"
|
||||
OUTPUT_POSTFIX=""
|
||||
fi
|
||||
|
||||
if [[ "$1" == "clean" ]] || [[ ! -d ./out/host_release$OUTPUT_POSTFIX ]]; then
|
||||
./flutter/tools/gn --no-goma --no-lto --runtime-mode=release --no-prebuilt-dart-sdk $GN_ARGS_HOST
|
||||
fi
|
||||
ninja -C out/host_release$OUTPUT_POSTFIX
|
||||
|
||||
if [[ "$1" == "clean" ]] || [[ ! -d ./out/host_debug_unopt$OUTPUT_POSTFIX ]]; then
|
||||
./flutter/tools/gn --no-goma --unoptimized --no-prebuilt-dart-sdk $GN_ARGS_HOST
|
||||
fi
|
||||
ninja -C out/host_debug_unopt$OUTPUT_POSTFIX
|
||||
|
||||
if [[ $(uname -m) != "arm64" ]]; then
|
||||
GN_SIM_ARGS="--simulator-cpu=x64"
|
||||
GN_ARGS="--mac-cpu=x64"
|
||||
fi
|
||||
|
||||
if [[ "$1" == "clean" ]] || [[ ! -d ./out/ios_debug_sim_unopt$OUTPUT_POSTFIX ]]; then
|
||||
./flutter/tools/gn --ios --no-goma --simulator --unoptimized $GN_SIM_ARGS
|
||||
fi
|
||||
ninja -C out/ios_debug_sim_unopt$OUTPUT_POSTFIX
|
||||
|
||||
if [[ "$1" == "clean" ]] || [[ ! -d ./out/ios_debug_unopt$OUTPUT_POSTFIX ]]; then
|
||||
./flutter/tools/gn --ios --no-goma --unoptimized $GN_ARGS
|
||||
fi
|
||||
ninja -C out/ios_debug_unopt$OUTPUT_POSTFIX
|
||||
|
||||
if [[ "$1" == "clean" ]] || [[ ! -d ./out/ios_release$OUTPUT_POSTFIX ]]; then
|
||||
./flutter/tools/gn --ios --no-goma --runtime-mode=release $GN_ARGS
|
||||
fi
|
||||
ninja -C out/ios_release$OUTPUT_POSTFIX
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
# Note: sed tool only works when going in to the actual folder of the package, specifying the direct full path to the project.pbxproj does not work!
|
||||
|
||||
cd Pods/Pods.xcodeproj
|
||||
echo "pathching $PWD/project.pbxproj ..."
|
||||
find . -name 'project.pbxproj' -print0 | xargs -0 sed -i '' -e 's/TARGETED_DEVICE_FAMILY[[:space:]]=[[:space:]]\"1,2\"/TARGETED_DEVICE_FAMILY = \"3\"/g'
|
||||
find . -name 'project.pbxproj' -print0 | xargs -0 sed -i '' -e 's/SDKROOT[[:space:]]=[[:space:]]iphoneos/SDKROOT = appletvos/g'
|
||||
find . -name 'project.pbxproj' -print0 | xargs -0 sed -i '' -e 's/SUPPORTED_PLATFORMS[[:space:]]=[[:space:]]iphoneos/SUPPORTED_PLATFORMS = appletvos/g'
|
||||
find . -name 'project.pbxproj' -print0 | xargs -0 sed -i '' -e 's/IPHONEOS_DEPLOYMENT_TARGET[[:space:]]=[[:space:]]9.0/TVOS_DEPLOYMENT_TARGET = 13.0/g'
|
||||
# TODO: 8.0 --> should work with any version!
|
||||
|
||||
cd ../..
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
|
||||
# Note: sed tool only works when going in to the actual folder of the package, specifying the direct full path to the project.pbxproj does not work!
|
||||
|
||||
cd Runner.xcodeproj
|
||||
echo "pathching $PWD/project.pbxproj ..."
|
||||
find . -name 'project.pbxproj' -print0 | xargs -0 sed -i '' -e 's/TARGETED_DEVICE_FAMILY[[:space:]]=[[:space:]]\"1,2\"/TARGETED_DEVICE_FAMILY = \"3\"/g'
|
||||
find . -name 'project.pbxproj' -print0 | xargs -0 sed -i '' -e 's/SDKROOT[[:space:]]=[[:space:]]iphoneos/SDKROOT = appletvos/g'
|
||||
find . -name 'project.pbxproj' -print0 | xargs -0 sed -i '' -e 's/SUPPORTED_PLATFORMS[[:space:]]=[[:space:]]iphoneos/SUPPORTED_PLATFORMS = appletvos/g'
|
||||
find . -name 'project.pbxproj' -print0 | xargs -0 sed -i '' -e 's/IPHONEOS_DEPLOYMENT_TARGET[[:space:]]=[[:space:]][0-9].[0-9]/TVOS_DEPLOYMENT_TARGET = 13.0/g'
|
||||
# TODO: 8.0 --> should work with any version!
|
||||
|
||||
cd ..
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$1" == "ios" ]; then
|
||||
if [[ ! -d ./tvos ]] && [[ -d ./_ios ]] && [[ -d ./ios ]]; then
|
||||
echo "Renaming 'ios' to 'tvos'"
|
||||
mv ./ios ./tvos
|
||||
mv ./_ios ./ios
|
||||
else
|
||||
echo "'tvos' already exists or '_ios'/'ios' does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
elif [[ "$1" == "tvos" ]] ; then
|
||||
if [[ ! -d ./_ios ]] && [[ -d ./tvos ]] && [[ -d ./ios ]]; then
|
||||
echo "Renaming 'tvos' to 'ios'"
|
||||
mv ./ios ./_ios
|
||||
mv ./tvos ./ios
|
||||
else
|
||||
echo "'_ios' already exists or 'tvos'/'ios' does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
else
|
||||
echo "invalid arguments:"
|
||||
echo " Usage: $0 ios or $0 tvos"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env ruby
|
||||
# Adds the Plezy MpvPlayer Swift sources and the MPVKit Swift Package
|
||||
# dependency to tvos/Runner.xcodeproj so it matches the iOS project's
|
||||
# linkage. Idempotent: re-running skips already-added entries.
|
||||
|
||||
require 'xcodeproj'
|
||||
|
||||
PROJECT_PATH = File.expand_path('../Runner.xcodeproj', __dir__)
|
||||
project = Xcodeproj::Project.open(PROJECT_PATH)
|
||||
runner_target = project.targets.find { |t| t.name == 'Runner' }
|
||||
raise "Runner target not found" unless runner_target
|
||||
|
||||
# Find or create the MpvPlayer group under Runner.
|
||||
main_group = project.main_group['Runner']
|
||||
raise "Runner group not found" unless main_group
|
||||
mpv_group = main_group['MpvPlayer'] || main_group.new_group('MpvPlayer', 'Runner/MpvPlayer')
|
||||
|
||||
# File references.
|
||||
# path is relative to the group's path (Runner/MpvPlayer → ../..).
|
||||
# For files elsewhere in the repo, use SOURCE_ROOT with the absolute-ish path.
|
||||
sources = [
|
||||
{ name: 'MpvPlayerCoreBase.swift', path: '../shared/apple/MpvPlayer/MpvPlayerCoreBase.swift', tree: '<source_root>' },
|
||||
{ name: 'MpvPlayerPluginShared.swift', path: '../shared/apple/MpvPlayer/MpvPlayerPluginShared.swift', tree: '<source_root>' },
|
||||
{ name: 'MpvPlayerCore.swift', path: '../ios/Runner/MpvPlayer/MpvPlayerCore.swift', tree: '<source_root>' },
|
||||
{ name: 'MpvPlayerPlugin.swift', path: '../ios/Runner/MpvPlayer/MpvPlayerPlugin.swift', tree: '<source_root>' },
|
||||
{ name: 'MpvPipController.swift', path: '../ios/Runner/MpvPlayer/MpvPipController.swift', tree: '<source_root>' },
|
||||
]
|
||||
|
||||
sources_phase = runner_target.source_build_phase
|
||||
sources.each do |src|
|
||||
existing = mpv_group.files.find { |f| f.display_name == src[:name] }
|
||||
if existing
|
||||
puts "[skip] #{src[:name]} already present"
|
||||
next
|
||||
end
|
||||
ref = mpv_group.new_file(src[:path])
|
||||
ref.name = src[:name]
|
||||
ref.source_tree = src[:tree]
|
||||
sources_phase.add_file_reference(ref, true)
|
||||
puts "[add ] #{src[:name]}"
|
||||
end
|
||||
|
||||
# Swift Package: MPVKit.
|
||||
pkg_url = 'https://github.com/edde746/MPVKit'
|
||||
existing_pkg = project.root_object.package_references.find do |p|
|
||||
p.repositoryURL == pkg_url rescue false
|
||||
end
|
||||
|
||||
if existing_pkg
|
||||
puts "[skip] MPVKit SPM package already present"
|
||||
else
|
||||
pkg = project.new(Xcodeproj::Project::Object::XCRemoteSwiftPackageReference)
|
||||
pkg.repositoryURL = pkg_url
|
||||
pkg.requirement = { 'kind' => 'branch', 'branch' => 'main' }
|
||||
project.root_object.package_references << pkg
|
||||
|
||||
product = project.new(Xcodeproj::Project::Object::XCSwiftPackageProductDependency)
|
||||
product.package = pkg
|
||||
product.product_name = 'MPVKit'
|
||||
runner_target.package_product_dependencies << product
|
||||
|
||||
frameworks_phase = runner_target.frameworks_build_phase
|
||||
build_file = project.new(Xcodeproj::Project::Object::PBXBuildFile)
|
||||
build_file.product_ref = product
|
||||
frameworks_phase.files << build_file
|
||||
puts "[add ] MPVKit SPM package + framework linkage"
|
||||
end
|
||||
|
||||
project.save
|
||||
puts "Saved #{PROJECT_PATH}"
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env ruby
|
||||
# Adds pure-Swift Flutter plugin sources to Runner target (no pods needed).
|
||||
# Cleans up stale plugin file references left from earlier failed attempts.
|
||||
|
||||
require 'xcodeproj'
|
||||
|
||||
PROJECT_PATH = File.expand_path('../Runner.xcodeproj', __dir__)
|
||||
project = Xcodeproj::Project.open(PROJECT_PATH)
|
||||
runner = project.targets.find { |t| t.name == 'Runner' }
|
||||
raise "Runner target not found" unless runner
|
||||
|
||||
# Names we want to own (anything with these basenames that's NOT part of
|
||||
# MpvPlayer is a stale duplicate from earlier failed runs).
|
||||
PLUGIN_BASENAMES = %w[
|
||||
SharedPreferencesPlugin.swift
|
||||
messages.g.swift
|
||||
PackageInfoPlusPlugin.swift
|
||||
PathProviderPlugin.swift
|
||||
GamepadPlugin.swift
|
||||
GamepadStreamHandler.swift
|
||||
GCControllerManager.swift
|
||||
ButtonMapping.swift
|
||||
DeviceInfoPlusPlugin.swift
|
||||
ConnectivityPlusPlugin.swift
|
||||
ConnectivityProvider.swift
|
||||
PathMonitorConnectivityProvider.swift
|
||||
OsMediaControlsPlugin.swift
|
||||
]
|
||||
|
||||
# Remove any existing file refs for these basenames from the entire project.
|
||||
project.files.select { |f| PLUGIN_BASENAMES.include?(f.display_name) }.each do |f|
|
||||
f.remove_from_project
|
||||
end
|
||||
|
||||
# Drop any build files whose file_ref is now nil.
|
||||
project.targets.each do |t|
|
||||
t.build_phases.each do |phase|
|
||||
next unless phase.respond_to?(:files)
|
||||
phase.files.delete_if { |bf| bf.file_ref.nil? }
|
||||
end
|
||||
end
|
||||
|
||||
# Remove any empty Plugins groups.
|
||||
runner_group = project.main_group['Runner']
|
||||
if (prev = runner_group['Plugins'])
|
||||
prev.remove_from_project
|
||||
end
|
||||
|
||||
# Fresh Plugins group, filesystem-aligned under tvos/Runner/Plugins.
|
||||
plugins_group = runner_group.new_group('Plugins', 'Plugins')
|
||||
|
||||
plugins = {
|
||||
'shared_preferences_foundation' => %w[
|
||||
SharedPreferencesPlugin.swift
|
||||
messages.g.swift
|
||||
],
|
||||
'package_info_plus' => %w[
|
||||
PackageInfoPlusPlugin.swift
|
||||
],
|
||||
'path_provider' => %w[
|
||||
PathProviderPlugin.swift
|
||||
],
|
||||
'universal_gamepad' => %w[
|
||||
GamepadPlugin.swift
|
||||
GamepadStreamHandler.swift
|
||||
GCControllerManager.swift
|
||||
ButtonMapping.swift
|
||||
],
|
||||
'device_info_plus' => %w[
|
||||
DeviceInfoPlusPlugin.swift
|
||||
],
|
||||
'connectivity_plus' => %w[
|
||||
ConnectivityPlusPlugin.swift
|
||||
ConnectivityProvider.swift
|
||||
PathMonitorConnectivityProvider.swift
|
||||
],
|
||||
'os_media_controls' => %w[
|
||||
OsMediaControlsPlugin.swift
|
||||
],
|
||||
}
|
||||
|
||||
sources_phase = runner.source_build_phase
|
||||
plugins.each do |plugin_name, files|
|
||||
sub = plugins_group.new_group(plugin_name, plugin_name)
|
||||
files.each do |fname|
|
||||
ref = sub.new_file(fname)
|
||||
sources_phase.add_file_reference(ref, true)
|
||||
puts "[add ] Runner/Plugins/#{plugin_name}/#{fname}"
|
||||
end
|
||||
end
|
||||
|
||||
project.save
|
||||
puts "Saved"
|
||||
@@ -0,0 +1,364 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# based on scripts in /engine/src/flutter/testing/scenario_app
|
||||
# and xcode_backend.sh script for integration in xcode
|
||||
|
||||
|
||||
# Exit on error
|
||||
set -e
|
||||
|
||||
debug_sim=""
|
||||
|
||||
|
||||
if [[ $(uname -m) == "arm64" ]]; then
|
||||
TARGET_POSTFIX='_arm64'
|
||||
CLANG_POSTFIX='_arm64'
|
||||
SIM_ARCH='arm64'
|
||||
else
|
||||
TARGET_POSTFIX=''
|
||||
CLANG_POSTFIX='_X86'
|
||||
SIM_ARCH='x86_64'
|
||||
fi
|
||||
|
||||
BuildAppDebug() {
|
||||
# Host tools (frontend_server, patched SDK, dartaotruntime) ship in
|
||||
# host_release for both debug and release consumers — the frontend_server
|
||||
# compiles debug kernels regardless of the host build flavor.
|
||||
HOST_TOOLS=$FLUTTER_LOCAL_ENGINE/out/host_release
|
||||
if [[ "$debug_sim" == "true" ]]; then
|
||||
DEVICE_TOOLS=$FLUTTER_LOCAL_ENGINE/out/tvos_debug_sim_unopt$TARGET_POSTFIX
|
||||
else
|
||||
# Device build is always arm64; gn outputs `tvos_debug_unopt` without suffix.
|
||||
DEVICE_TOOLS=$FLUTTER_LOCAL_ENGINE/out/tvos_debug_unopt
|
||||
fi
|
||||
|
||||
ROOTDIR=$(dirname "$PROJECT_DIR")
|
||||
OUTDIR=$ROOTDIR/build/ios/Release-iphoneos
|
||||
mkdir -p $OUTDIR
|
||||
echo " └─OUTDIR: $OUTDIR"
|
||||
echo " └─BUILT_PRODUCTS_DIR: $BUILT_PRODUCTS_DIR"
|
||||
|
||||
|
||||
echo " └─Copying Flutter.framework"
|
||||
rm -rf "$OUTDIR/Flutter.framework"
|
||||
cp -R "$DEVICE_TOOLS/Flutter.framework" "$OUTDIR"
|
||||
|
||||
|
||||
tvos_deployment_target="$TVOS_DEPLOYMENT_TARGET"
|
||||
|
||||
echo " └─Generate flutter_assets via flutter build bundle"
|
||||
mkdir -p "$OUTDIR/App.framework/flutter_assets"
|
||||
# Resolve the flutter CLI: prefer FLUTTER_ROOT from Generated.xcconfig, else
|
||||
# fall back to the `flutter` on PATH.
|
||||
FLUTTER_BIN=""
|
||||
if [ -n "$FLUTTER_ROOT" ] && [ -x "$FLUTTER_ROOT/bin/flutter" ]; then
|
||||
FLUTTER_BIN="$FLUTTER_ROOT/bin/flutter"
|
||||
elif command -v flutter >/dev/null 2>&1; then
|
||||
FLUTTER_BIN="$(command -v flutter)"
|
||||
fi
|
||||
|
||||
if [ -z "$FLUTTER_BIN" ]; then
|
||||
echo " └─ERROR: flutter CLI not found (set FLUTTER_ROOT or add flutter to PATH)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# flutter build bundle produces: AssetManifest, FontManifest, NOTICES,
|
||||
# shaders, fonts, assets, packages, plus a kernel_blob.bin and
|
||||
# isolate_snapshot_data compiled against the stock flutter engine. We
|
||||
# overwrite kernel_blob.bin and both snapshot blobs below with versions
|
||||
# from our tvOS engine so the tvOS VM can load them.
|
||||
(
|
||||
cd "$FLUTTER_APPLICATION_PATH" && \
|
||||
"$FLUTTER_BIN" build bundle \
|
||||
--asset-dir="$OUTDIR/App.framework/flutter_assets" \
|
||||
--no-tree-shake-icons \
|
||||
--suppress-analytics
|
||||
) || {
|
||||
echo " └─ERROR: flutter build bundle failed"
|
||||
return 1
|
||||
}
|
||||
|
||||
echo " └─Compiling tvOS kernel via local engine frontend_server"
|
||||
FRONTEND_SERVER="$HOST_TOOLS/dart-sdk/bin/snapshots/frontend_server_aot.dart.snapshot"
|
||||
if [ ! -f "$FRONTEND_SERVER" ]; then
|
||||
FRONTEND_SERVER="$HOST_TOOLS/gen/frontend_server_aot.dart.snapshot"
|
||||
fi
|
||||
"$HOST_TOOLS/dart-sdk/bin/dartaotruntime" \
|
||||
"$FRONTEND_SERVER" \
|
||||
--sdk-root "$HOST_TOOLS/flutter_patched_sdk" \
|
||||
--tfa --target=flutter \
|
||||
-DTVOS_BUILD=true \
|
||||
--output-dill "$OUTDIR/App.framework/flutter_assets/kernel_blob.bin" \
|
||||
"$FLUTTER_APPLICATION_PATH/lib/main.dart"
|
||||
|
||||
echo " └─Copying tvOS VM + isolate snapshots (pure JIT, no gen_snapshot)"
|
||||
cp "$DEVICE_TOOLS/gen/flutter/lib/snapshot/vm_isolate_snapshot.bin" \
|
||||
"$OUTDIR/App.framework/flutter_assets/vm_snapshot_data"
|
||||
cp "$DEVICE_TOOLS/gen/flutter/lib/snapshot/isolate_snapshot.bin" \
|
||||
"$OUTDIR/App.framework/flutter_assets/isolate_snapshot_data"
|
||||
|
||||
|
||||
if [[ "$debug_sim" == "true" ]]; then
|
||||
SYSROOT=$(xcrun --sdk appletvsimulator --show-sdk-path)
|
||||
else
|
||||
SYSROOT=$(xcrun --sdk appletvos --show-sdk-path)
|
||||
fi
|
||||
|
||||
echo " └─Creating stub App using $SYSROOT"
|
||||
|
||||
|
||||
if [[ "$debug_sim" == "true" ]]; then
|
||||
echo "static const int Moo = 88;" | xcrun clang -x c \
|
||||
-arch $SIM_ARCH \
|
||||
-L"$SYSROOT/usr/lib" \
|
||||
-isysroot "$SYSROOT" \
|
||||
-mappletvsimulator-version-min=$tvos_deployment_target \
|
||||
-dynamiclib \
|
||||
-Xlinker -rpath -Xlinker '@executable_path/Frameworks' \
|
||||
-Xlinker -rpath -Xlinker '@loader_path/Frameworks' \
|
||||
-install_name '@rpath/App.framework/App' \
|
||||
-o "$OUTDIR/App.framework/App" -
|
||||
|
||||
else
|
||||
echo "static const int Moo = 88;" | xcrun clang -x c \
|
||||
-arch arm64 \
|
||||
-isysroot "$SYSROOT" \
|
||||
-mtvos-version-min=$tvos_deployment_target \
|
||||
-dynamiclib \
|
||||
-Xlinker -rpath -Xlinker '@executable_path/Frameworks' \
|
||||
-Xlinker -rpath -Xlinker '@loader_path/Frameworks' \
|
||||
-install_name '@rpath/App.framework/App' \
|
||||
-o "$OUTDIR/App.framework/App" -
|
||||
fi
|
||||
|
||||
strip "$OUTDIR/App.framework/App"
|
||||
|
||||
echo " └─copy frameworks"
|
||||
cp "$PROJECT_DIR/scripts/Info.plist" "$OUTDIR/App.framework/Info.plist"
|
||||
|
||||
# For Archive builds BUILT_PRODUCTS_DIR differs from TARGET_BUILD_DIR:
|
||||
# Swift compile looks up frameworks under BUILT_PRODUCTS_DIR, but the
|
||||
# embedded copy needs to end up inside TARGET_BUILD_DIR/Runner.app.
|
||||
cp -R "${OUTDIR}/"{App.framework,Flutter.framework} "$TARGET_BUILD_DIR"
|
||||
if [ -n "${BUILT_PRODUCTS_DIR:-}" ] && [ "$BUILT_PRODUCTS_DIR" != "$TARGET_BUILD_DIR" ]; then
|
||||
rm -rf "$BUILT_PRODUCTS_DIR/App.framework" "$BUILT_PRODUCTS_DIR/Flutter.framework"
|
||||
cp -R "${OUTDIR}/"{App.framework,Flutter.framework} "$BUILT_PRODUCTS_DIR"
|
||||
fi
|
||||
|
||||
# Also embed into Runner.app/Frameworks so the dylib @rpath resolves at launch.
|
||||
APP_FRAMEWORKS_DIR="$TARGET_BUILD_DIR/$WRAPPER_NAME/Frameworks"
|
||||
mkdir -p "$APP_FRAMEWORKS_DIR"
|
||||
rm -rf "$APP_FRAMEWORKS_DIR/App.framework" "$APP_FRAMEWORKS_DIR/Flutter.framework"
|
||||
cp -R "${OUTDIR}/"{App.framework,Flutter.framework} "$APP_FRAMEWORKS_DIR"
|
||||
|
||||
# Sign the binaries we moved. Both the flat copy (used by linker/embedder)
|
||||
# and the embedded copy inside Runner.app/Frameworks need signatures.
|
||||
# Skip when signing is disabled (CODE_SIGNING_ALLOWED=NO, unit-test builds,
|
||||
# or sim builds where Xcode doesn't sign frameworks). Xcode's own CodeSign
|
||||
# phase will sign the final app + frameworks in proper archive builds.
|
||||
echo " └─Sign"
|
||||
if [[ "$debug_sim" != "true" && -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" && "${CODE_SIGNING_ALLOWED:-YES}" != "NO" ]]; then
|
||||
codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${TARGET_BUILD_DIR}/App.framework/App"
|
||||
codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${TARGET_BUILD_DIR}/Flutter.framework/Flutter"
|
||||
codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${APP_FRAMEWORKS_DIR}/App.framework/App"
|
||||
codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${APP_FRAMEWORKS_DIR}/Flutter.framework/Flutter"
|
||||
else
|
||||
echo " (skipped — no code sign identity or signing disabled)"
|
||||
fi
|
||||
|
||||
echo " └─Done"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
BuildAppRelease() {
|
||||
HOST_TOOLS=$FLUTTER_LOCAL_ENGINE/out/host_release
|
||||
DEVICE_TOOLS=$FLUTTER_LOCAL_ENGINE/out/tvos_release
|
||||
|
||||
# Locate gen_snapshot. Use the cross-compile variant that targets iOS/tvOS
|
||||
# arm64 (emits arm64-ios assembly). The plain host gen_snapshot targets
|
||||
# macOS and its snapshots are rejected at runtime by the iOS/tvOS VM.
|
||||
GEN_SNAPSHOT=""
|
||||
for cand in \
|
||||
"$DEVICE_TOOLS/artifacts_arm64/gen_snapshot_arm64" \
|
||||
"$DEVICE_TOOLS/universal/gen_snapshot_arm64" \
|
||||
"$DEVICE_TOOLS/artifacts_x64/gen_snapshot_arm64" \
|
||||
"$DEVICE_TOOLS/gen_snapshot_arm64"; do
|
||||
if [ -x "$cand" ]; then
|
||||
GEN_SNAPSHOT="$cand"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -z "$GEN_SNAPSHOT" ]; then
|
||||
echo " └─ERROR: gen_snapshot not found under $DEVICE_TOOLS or $HOST_TOOLS"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ROOTDIR=$(dirname "$PROJECT_DIR")
|
||||
OUTDIR=$ROOTDIR/build/ios/Release-iphoneos
|
||||
mkdir -p "$OUTDIR"
|
||||
|
||||
echo " └─OUTDIR: $OUTDIR"
|
||||
echo " └─gen_snapshot: $GEN_SNAPSHOT"
|
||||
|
||||
echo " └─Copying Flutter.framework"
|
||||
rm -rf "$OUTDIR/Flutter.framework"
|
||||
cp -R "$DEVICE_TOOLS/Flutter.framework" "$OUTDIR"
|
||||
|
||||
tvos_deployment_target="$TVOS_DEPLOYMENT_TARGET"
|
||||
|
||||
# Resolve flutter CLI.
|
||||
FLUTTER_BIN=""
|
||||
if [ -n "$FLUTTER_ROOT" ] && [ -x "$FLUTTER_ROOT/bin/flutter" ]; then
|
||||
FLUTTER_BIN="$FLUTTER_ROOT/bin/flutter"
|
||||
elif command -v flutter >/dev/null 2>&1; then
|
||||
FLUTTER_BIN="$(command -v flutter)"
|
||||
fi
|
||||
if [ -z "$FLUTTER_BIN" ]; then
|
||||
echo " └─ERROR: flutter CLI not found (set FLUTTER_ROOT or add flutter to PATH)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " └─Generate flutter_assets via flutter build bundle (release)"
|
||||
mkdir -p "$OUTDIR/App.framework/flutter_assets"
|
||||
(
|
||||
cd "$FLUTTER_APPLICATION_PATH" && \
|
||||
"$FLUTTER_BIN" build bundle \
|
||||
--release \
|
||||
--asset-dir="$OUTDIR/App.framework/flutter_assets" \
|
||||
--no-tree-shake-icons \
|
||||
--suppress-analytics
|
||||
) || {
|
||||
echo " └─ERROR: flutter build bundle failed"
|
||||
return 1
|
||||
}
|
||||
# AOT builds don't need kernel_blob.bin in flutter_assets — the compiled
|
||||
# arm64 code lives in App.framework/App itself.
|
||||
rm -f "$OUTDIR/App.framework/flutter_assets/kernel_blob.bin"
|
||||
|
||||
echo " └─Compiling AOT kernel via local engine frontend_server"
|
||||
# The snapshot under dart-sdk/bin/snapshots/ is the actual AOT-compiled one;
|
||||
# the one under gen/ is a stale/placeholder kernel.
|
||||
FRONTEND_SERVER="$HOST_TOOLS/dart-sdk/bin/snapshots/frontend_server_aot.dart.snapshot"
|
||||
if [ ! -f "$FRONTEND_SERVER" ]; then
|
||||
FRONTEND_SERVER="$HOST_TOOLS/gen/frontend_server_aot.dart.snapshot"
|
||||
fi
|
||||
"$HOST_TOOLS/dart-sdk/bin/dartaotruntime" \
|
||||
"$FRONTEND_SERVER" \
|
||||
--sdk-root "$HOST_TOOLS/flutter_patched_sdk" \
|
||||
--aot --tfa --target=flutter \
|
||||
-Ddart.vm.product=true \
|
||||
-Ddart.vm.profile=false \
|
||||
-DFLUTTER_BUILD_MODE=release \
|
||||
-DTARGET_PLATFORM=TVOS \
|
||||
-DTVOS_BUILD=true \
|
||||
--output-dill "$OUTDIR/app.dill" \
|
||||
"$FLUTTER_APPLICATION_PATH/lib/main.dart"
|
||||
|
||||
echo " └─Compiling AOT Assembly"
|
||||
"$GEN_SNAPSHOT" \
|
||||
--deterministic \
|
||||
--snapshot_kind=app-aot-assembly \
|
||||
--assembly="$OUTDIR/snapshot_assembly.S" \
|
||||
--strip \
|
||||
"$OUTDIR/app.dill"
|
||||
|
||||
echo " └─Compiling Assembly"
|
||||
SYSROOT=$(xcrun --sdk appletvos --show-sdk-path)
|
||||
cc -arch arm64 \
|
||||
-isysroot "$SYSROOT" \
|
||||
-mtvos-version-min=$tvos_deployment_target \
|
||||
-c "$OUTDIR/snapshot_assembly.S" \
|
||||
-o "$OUTDIR/snapshot_assembly.o"
|
||||
|
||||
echo " └─Linking app"
|
||||
clang -arch arm64 \
|
||||
-isysroot "$SYSROOT" \
|
||||
-mtvos-version-min=$tvos_deployment_target \
|
||||
-dynamiclib \
|
||||
-Xlinker -rpath -Xlinker @executable_path/Frameworks \
|
||||
-Xlinker -rpath -Xlinker @loader_path/Frameworks \
|
||||
-install_name @rpath/App.framework/App \
|
||||
-o "$OUTDIR/App.framework/App" \
|
||||
"$OUTDIR/snapshot_assembly.o"
|
||||
|
||||
strip "$OUTDIR/App.framework/App"
|
||||
|
||||
cp "$PROJECT_DIR/scripts/Info.plist" "$OUTDIR/App.framework/Info.plist"
|
||||
|
||||
echo " └─copy frameworks"
|
||||
# Archive builds point BUILT_PRODUCTS_DIR at a separate products path that
|
||||
# Swift compile scans for frameworks — copy there too so downstream
|
||||
# compilation in the same target can resolve Flutter.h.
|
||||
cp -R "${OUTDIR}/"{App.framework,Flutter.framework} "$TARGET_BUILD_DIR"
|
||||
if [ -n "${BUILT_PRODUCTS_DIR:-}" ] && [ "$BUILT_PRODUCTS_DIR" != "$TARGET_BUILD_DIR" ]; then
|
||||
rm -rf "$BUILT_PRODUCTS_DIR/App.framework" "$BUILT_PRODUCTS_DIR/Flutter.framework"
|
||||
cp -R "${OUTDIR}/"{App.framework,Flutter.framework} "$BUILT_PRODUCTS_DIR"
|
||||
fi
|
||||
|
||||
APP_FRAMEWORKS_DIR="$TARGET_BUILD_DIR/$WRAPPER_NAME/Frameworks"
|
||||
mkdir -p "$APP_FRAMEWORKS_DIR"
|
||||
rm -rf "$APP_FRAMEWORKS_DIR/App.framework" "$APP_FRAMEWORKS_DIR/Flutter.framework"
|
||||
cp -R "${OUTDIR}/"{App.framework,Flutter.framework} "$APP_FRAMEWORKS_DIR"
|
||||
|
||||
echo " └─Sign"
|
||||
if [[ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" && "${CODE_SIGNING_ALLOWED:-YES}" != "NO" ]]; then
|
||||
codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${TARGET_BUILD_DIR}/App.framework/App"
|
||||
codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${TARGET_BUILD_DIR}/Flutter.framework/Flutter"
|
||||
codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${APP_FRAMEWORKS_DIR}/App.framework/App"
|
||||
codesign --force --verbose --sign "${EXPANDED_CODE_SIGN_IDENTITY}" -- "${APP_FRAMEWORKS_DIR}/Flutter.framework/Flutter"
|
||||
else
|
||||
echo " (skipped — no code sign identity or signing disabled)"
|
||||
fi
|
||||
|
||||
echo " └─Done"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
BuildApp() {
|
||||
|
||||
local build_mode="$(echo "${FLUTTER_BUILD_MODE:-${CONFIGURATION}}" | tr "[:upper:]" "[:lower:]")"
|
||||
|
||||
echo "Compiling Flutter/App.Framework"
|
||||
|
||||
if [ -z "$FLUTTER_LOCAL_ENGINE" ]; then
|
||||
echo " └─ERROR: FLUTTER_LOCAL_ENGINE not set!"
|
||||
return 1;
|
||||
fi
|
||||
|
||||
echo " └─engine $FLUTTER_LOCAL_ENGINE"
|
||||
|
||||
|
||||
if [[ "$PLATFORM_NAME" == "appletvsimulator" && "$build_mode" =~ "debug" ]]; then
|
||||
debug_sim="true"
|
||||
BuildAppDebug
|
||||
elif [[ "$build_mode" =~ "debug" ]]; then
|
||||
BuildAppDebug
|
||||
elif [[ "$build_mode" =~ "release" ]]; then
|
||||
# release/archive (archive: build mode == "release" && ${ACTION} == "install")
|
||||
BuildAppRelease
|
||||
else
|
||||
echo " └─ERROR: unknown target: ${build_mode}"
|
||||
return 1;
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# Main entry point.
|
||||
if [[ $# == 0 ]]; then
|
||||
# Backwards-compatibility: if no args are provided, build and embed.
|
||||
BuildApp
|
||||
EmbedFlutterFrameworks
|
||||
else
|
||||
case $1 in
|
||||
"build")
|
||||
BuildApp ;;
|
||||
# "embed_and_thin")
|
||||
# "Not needed, used from flutter xcode_backend.sh script"
|
||||
esac
|
||||
fi
|
||||