video wallpapers
This commit is contained in:
1 parent
35e58b88eb
commit
aaf5051eeb
11 files changed
+479
-7
No files matched your search
@@ -17,6 +17,9 @@
|
|||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
6F09B79F2DEB694B00CDE89C /* Pocket Poster */ = {
|
6F09B79F2DEB694B00CDE89C /* Pocket Poster */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
explicitFolders = (
|
||||||
|
Resources/VideoCAML,
|
||||||
|
);
|
||||||
path = "Pocket Poster";
|
path = "Pocket Poster";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -261,7 +264,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 0;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 2P4CP47H76;
|
DEVELOPMENT_TEAM = 2P4CP47H76;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -278,7 +281,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 2.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.lemin.Pocket-Poster";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.lemin.Pocket-Poster";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -293,7 +296,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 0;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 2P4CP47H76;
|
DEVELOPMENT_TEAM = 2P4CP47H76;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -310,7 +313,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 2.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.lemin.Pocket-Poster";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.lemin.Pocket-Poster";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|||||||
@@ -161,9 +161,18 @@ class PosterBoardManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyTendies(appHash: String) throws {
|
func applyTendies(appHash: String, videoURL: URL?) throws {
|
||||||
// organize the descriptors into their respective extensions
|
// organize the descriptors into their respective extensions
|
||||||
var extList: [String: [URL]] = [:]
|
var extList: [String: [URL]] = [:]
|
||||||
|
// create the video first
|
||||||
|
if let videoURL = videoURL {
|
||||||
|
do {
|
||||||
|
let newVideo = try VideoHandler.createCaml(from: videoURL, autoReverses: false)
|
||||||
|
extList["com.apple.WallpaperKit.CollectionsPoster"] = [newVideo]
|
||||||
|
} catch {
|
||||||
|
print(error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
for url in selectedTendies {
|
for url in selectedTendies {
|
||||||
let unzippedDir = try unzipFile(at: url)
|
let unzippedDir = try unzipFile(at: url)
|
||||||
guard let descriptors = try getDescriptorsFromTendie(unzippedDir) else { continue } // TODO: Add error handling
|
guard let descriptors = try getDescriptorsFromTendie(unzippedDir) else { continue } // TODO: Add error handling
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
//
|
||||||
|
// VideoHandler.swift
|
||||||
|
// Pocket Poster
|
||||||
|
//
|
||||||
|
// Created by lemin on 6/8/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension CGImage {
|
||||||
|
var png: Data? {
|
||||||
|
guard let mutableData = CFDataCreateMutable(nil, 0),
|
||||||
|
let destination = CGImageDestinationCreateWithData(mutableData, "public.png" as CFString, 1, nil) else { return nil }
|
||||||
|
CGImageDestinationAddImage(destination, self, nil)
|
||||||
|
guard CGImageDestinationFinalize(destination) else { return nil }
|
||||||
|
return mutableData as Data
|
||||||
|
}
|
||||||
|
var jpg: Data? {
|
||||||
|
let uiimg = UIImage(cgImage: self)
|
||||||
|
return uiimg.jpegData(compressionQuality: 0.7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VideoHandler {
|
||||||
|
static func createCaml(from url: URL, autoReverses: Bool) throws -> URL {
|
||||||
|
// copy the resources
|
||||||
|
let descrURL = SymHandler.getDocumentsDirectory().appendingPathComponent(UUID().uuidString, conformingTo: .directory)
|
||||||
|
try FileManager.default.createDirectory(at: descrURL, withIntermediateDirectories: false)
|
||||||
|
|
||||||
|
print("pre resource")
|
||||||
|
guard let rc = Bundle.main.url(forResource: "VideoCAML", withExtension: nil) else { throw URLError(.fileDoesNotExist) }
|
||||||
|
print("post resource")
|
||||||
|
try FileManager.default.copyItem(at: rc, to: descrURL.appendingPathComponent("videoCAML"))
|
||||||
|
print("post copy")
|
||||||
|
|
||||||
|
let coreAnimDir = descrURL.appending(path: "videoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/9183.Custom_Background-810w-1080h@2x~ipad.ca")
|
||||||
|
let assetsFolder = coreAnimDir.appendingPathComponent("assets", conformingTo: .directory)
|
||||||
|
if !FileManager.default.fileExists(atPath: assetsFolder.path()) {
|
||||||
|
try FileManager.default.createDirectory(at: assetsFolder, withIntermediateDirectories: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load the video
|
||||||
|
let asset = AVURLAsset(url: url)
|
||||||
|
|
||||||
|
let assetImageGenerator = AVAssetImageGenerator(asset: asset)
|
||||||
|
assetImageGenerator.appliesPreferredTrackTransform = true
|
||||||
|
assetImageGenerator.apertureMode = AVAssetImageGenerator.ApertureMode.encodedPixels
|
||||||
|
|
||||||
|
// create the caml
|
||||||
|
guard let track = asset.tracks(withMediaType: .video).first else {
|
||||||
|
print("Failed to get video track")
|
||||||
|
throw AVError(.decodeFailed)
|
||||||
|
}
|
||||||
|
let size = track.naturalSize.applying(track.preferredTransform)
|
||||||
|
let width = Int(size.width)
|
||||||
|
let height = Int(size.height)
|
||||||
|
let fps = track.nominalFrameRate
|
||||||
|
let duration = CMTimeGetSeconds(asset.duration)
|
||||||
|
let totalFrames = Int(fps * Float(duration))
|
||||||
|
let animDur = Double(totalFrames) / Double(fps)
|
||||||
|
var camlData = """
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<caml xmlns="http://www.apple.com/CoreAnimation/1.0">
|
||||||
|
<CALayer allowsEdgeAntialiasing="1" allowsGroupOpacity="1" bounds="0 0 \(width) \(height)" contentsFormat="RGBA8" cornerCurve="circular" hidden="0" name="_FLOATING" position="\(Int(width/2)) \(Int(height/2))">
|
||||||
|
<sublayers>
|
||||||
|
<CATransformLayer allowsEdgeAntialiasing="1" allowsGroupOpacity="1" allowsHitTesting="1" bounds="0 0 \(width) \(height)" contentsFormat="RGBA8" cornerCurve="circular" name="Chip" position="\(Int(width/2)) \(Int(height/2))">
|
||||||
|
<sublayers>
|
||||||
|
<CALayer allowsEdgeAntialiasing="1" allowsGroupOpacity="1" bounds="0 0 \(width) \(height)" contentsFormat="RGBA8" cornerCurve="circular" name="CALayer1" position="\(Int(width/2)) \(Int(height/2))">
|
||||||
|
<contents type="CGImage" src="assets/0.jpg"/>
|
||||||
|
<animations>
|
||||||
|
<animation type="CAKeyframeAnimation" calculationMode="linear" keyPath="contents" beginTime="1e-100" duration="\(animDur)" removedOnCompletion="0" repeatCount="inf" repeatDuration="0" speed="1" timeOffset="0" autoreverses="\(autoReverses ? 1 : 0)">
|
||||||
|
<values>\n
|
||||||
|
"""
|
||||||
|
|
||||||
|
// go through every frame and add it to the caml
|
||||||
|
do {
|
||||||
|
let reader = try AVAssetReader(asset: asset)
|
||||||
|
let readerOutputSettings: [String: Any] = [
|
||||||
|
kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)
|
||||||
|
]
|
||||||
|
|
||||||
|
let readerOutput = AVAssetReaderTrackOutput(track: track, outputSettings: readerOutputSettings)
|
||||||
|
reader.add(readerOutput)
|
||||||
|
|
||||||
|
reader.startReading()
|
||||||
|
|
||||||
|
var frameCount = 0
|
||||||
|
|
||||||
|
while let sampleBuffer = readerOutput.copyNextSampleBuffer(),
|
||||||
|
let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
|
||||||
|
|
||||||
|
let ciImage = CIImage(cvPixelBuffer: imageBuffer)
|
||||||
|
let context = CIContext()
|
||||||
|
if let cgImage = context.createCGImage(ciImage, from: ciImage.extent) {
|
||||||
|
let name = "\(frameCount).jpg"
|
||||||
|
if let img = cgImage.jpg {
|
||||||
|
try img.write(to: assetsFolder.appendingPathComponent(name))
|
||||||
|
camlData += "\t\t\t<CGImage src=\"assets/\(name)\"/>\n"
|
||||||
|
frameCount += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
print("Error reading asset: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
camlData += """
|
||||||
|
</values>
|
||||||
|
</animation>
|
||||||
|
</animations>
|
||||||
|
</CALayer>
|
||||||
|
</sublayers>
|
||||||
|
</CATransformLayer>
|
||||||
|
</sublayers>
|
||||||
|
<states>
|
||||||
|
<LKState name="Locked">
|
||||||
|
<elements/>
|
||||||
|
</LKState>
|
||||||
|
<LKState name="Unlock">
|
||||||
|
<elements/>
|
||||||
|
</LKState>
|
||||||
|
<LKState name="Sleep">
|
||||||
|
<elements/>
|
||||||
|
</LKState>
|
||||||
|
</states>
|
||||||
|
<stateTransitions>
|
||||||
|
<LKStateTransition fromState="*" toState="Unlock">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
<LKStateTransition fromState="Unlock" toState="*">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
<LKStateTransition fromState="*" toState="Locked">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
<LKStateTransition fromState="Locked" toState="*">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
<LKStateTransition fromState="*" toState="Sleep">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
<LKStateTransition fromState="Sleep" toState="*">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
</stateTransitions>
|
||||||
|
</CALayer>
|
||||||
|
</caml>
|
||||||
|
"""
|
||||||
|
// write the caml
|
||||||
|
guard let caData = camlData.data(using: .utf8) else { throw URLError(.unknown) }
|
||||||
|
try caData.write(to: coreAnimDir.appendingPathComponent("main.caml"))
|
||||||
|
|
||||||
|
// write the other info xml
|
||||||
|
camlData = """
|
||||||
|
<?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>assetManifest</key>
|
||||||
|
<string>assetManifest.caml</string>
|
||||||
|
<key>documentHeight</key>
|
||||||
|
<real>\(height)</real>
|
||||||
|
<key>documentResizesToView</key>
|
||||||
|
<true/>
|
||||||
|
<key>documentWidth</key>
|
||||||
|
<real>\(width)</real>
|
||||||
|
<key>dynamicGuidesEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>geometryFlipped</key>
|
||||||
|
<false/>
|
||||||
|
<key>guidesEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>interactiveMouseEventsEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>interactiveShowsCursor</key>
|
||||||
|
<true/>
|
||||||
|
<key>interactiveTouchEventsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
<key>loopEnd</key>
|
||||||
|
<real>0.0</real>
|
||||||
|
<key>loopStart</key>
|
||||||
|
<real>0.0</real>
|
||||||
|
<key>loopingEnabled</key>
|
||||||
|
<false/>
|
||||||
|
<key>multitouchDisablesMouse</key>
|
||||||
|
<false/>
|
||||||
|
<key>multitouchEnabled</key>
|
||||||
|
<false/>
|
||||||
|
<key>presentationMouseEventsEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>presentationShowsCursor</key>
|
||||||
|
<true/>
|
||||||
|
<key>presentationTouchEventsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
<key>rootDocument</key>
|
||||||
|
<string>main.caml</string>
|
||||||
|
<key>savesWindowFrame</key>
|
||||||
|
<false/>
|
||||||
|
<key>scalesToFitInPlayer</key>
|
||||||
|
<true/>
|
||||||
|
<key>showsTouches</key>
|
||||||
|
<true/>
|
||||||
|
<key>snappingEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>timelineMarkers</key>
|
||||||
|
<string>[(null)]</string>
|
||||||
|
<key>touchesColor</key>
|
||||||
|
<string>1 1 0 0.8</string>
|
||||||
|
<key>unitsInPixelsInPlayer</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
"""
|
||||||
|
|
||||||
|
guard let xmlData = camlData.data(using: .utf8) else { throw URLError(.unknown) }
|
||||||
|
try xmlData.write(to: coreAnimDir.appendingPathComponent("index.xml", conformingTo: .xml))
|
||||||
|
|
||||||
|
return descrURL
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
9183
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
PRPosterRoleLockScreen
|
||||||
Binary file not shown.
+58
@@ -0,0 +1,58 @@
|
|||||||
|
<?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>assetManifest</key>
|
||||||
|
<string>assetManifest.caml</string>
|
||||||
|
<key>documentHeight</key>
|
||||||
|
<real>3176</real>
|
||||||
|
<key>documentResizesToView</key>
|
||||||
|
<false/>
|
||||||
|
<key>documentWidth</key>
|
||||||
|
<real>3176</real>
|
||||||
|
<key>dynamicGuidesEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>geometryFlipped</key>
|
||||||
|
<false/>
|
||||||
|
<key>guidesEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>interactiveMouseEventsEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>interactiveShowsCursor</key>
|
||||||
|
<true/>
|
||||||
|
<key>interactiveTouchEventsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
<key>loopEnd</key>
|
||||||
|
<real>0.0</real>
|
||||||
|
<key>loopStart</key>
|
||||||
|
<real>0.0</real>
|
||||||
|
<key>loopingEnabled</key>
|
||||||
|
<false/>
|
||||||
|
<key>multitouchDisablesMouse</key>
|
||||||
|
<false/>
|
||||||
|
<key>multitouchEnabled</key>
|
||||||
|
<false/>
|
||||||
|
<key>presentationMouseEventsEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>presentationShowsCursor</key>
|
||||||
|
<true/>
|
||||||
|
<key>presentationTouchEventsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
<key>rootDocument</key>
|
||||||
|
<string>main.caml</string>
|
||||||
|
<key>savesWindowFrame</key>
|
||||||
|
<false/>
|
||||||
|
<key>scalesToFitInPlayer</key>
|
||||||
|
<false/>
|
||||||
|
<key>showsTouches</key>
|
||||||
|
<true/>
|
||||||
|
<key>snappingEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>timelineMarkers</key>
|
||||||
|
<string>[(null)]</string>
|
||||||
|
<key>touchesColor</key>
|
||||||
|
<string>1 1 0 0.8</string>
|
||||||
|
<key>unitsInPixelsInPlayer</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<caml xmlns="http://www.apple.com/CoreAnimation/1.0">
|
||||||
|
<CALayer allowsEdgeAntialiasing="1" allowsGroupOpacity="1" bounds="0 0 3176 3176" contentsFormat="RGBA8" cornerCurve="circular" hidden="0" name="_BACKGROUND" position="1588 1588">
|
||||||
|
<sublayers>
|
||||||
|
<CALayer allowsEdgeAntialiasing="1" allowsGroupOpacity="1" anchorPoint="0 0" bounds="0 0 0 0" contentsFormat="RGBA8" cornerCurve="circular" name="_CENTER_BACKGROUND" position="1588 1588"/>
|
||||||
|
</sublayers>
|
||||||
|
<states>
|
||||||
|
<LKState name="Locked">
|
||||||
|
<elements/>
|
||||||
|
</LKState>
|
||||||
|
<LKState name="Unlock">
|
||||||
|
<elements/>
|
||||||
|
</LKState>
|
||||||
|
<LKState name="Sleep">
|
||||||
|
<elements/>
|
||||||
|
</LKState>
|
||||||
|
</states>
|
||||||
|
<stateTransitions>
|
||||||
|
<LKStateTransition fromState="*" toState="Unlock">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
<LKStateTransition fromState="Unlock" toState="*">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
<LKStateTransition fromState="*" toState="Locked">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
<LKStateTransition fromState="Locked" toState="*">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
<LKStateTransition fromState="*" toState="Sleep">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
<LKStateTransition fromState="Sleep" toState="*">
|
||||||
|
<elements/>
|
||||||
|
</LKStateTransition>
|
||||||
|
</stateTransitions>
|
||||||
|
</CALayer>
|
||||||
|
</caml>
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
<?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>appearanceAware</key>
|
||||||
|
<true/>
|
||||||
|
<key>assets</key>
|
||||||
|
<dict>
|
||||||
|
<key>lockAndHome</key>
|
||||||
|
<dict>
|
||||||
|
<key>default</key>
|
||||||
|
<dict>
|
||||||
|
<key>backgroundAnimationFileName</key>
|
||||||
|
<string>9183.Custom_Background-810w-1080h@2x~ipad.ca</string>
|
||||||
|
<key>floatingAnimationFileNameKey</key>
|
||||||
|
<string>9183.Custom_Floating-810w-1080h@2x~ipad.ca</string>
|
||||||
|
<key>identifier</key>
|
||||||
|
<integer>9183</integer>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Chip</string>
|
||||||
|
<key>type</key>
|
||||||
|
<string>LayeredAnimation</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>contentVersion</key>
|
||||||
|
<real>2.01</real>
|
||||||
|
<key>family</key>
|
||||||
|
<string>Chip</string>
|
||||||
|
<key>identifier</key>
|
||||||
|
<integer>9183</integer>
|
||||||
|
<key>logicalScreenClass</key>
|
||||||
|
<string>810w-1080h@2x~ipad</string>
|
||||||
|
<key>name</key>
|
||||||
|
<string>Chip</string>
|
||||||
|
<key>preferredProminentColor</key>
|
||||||
|
<dict>
|
||||||
|
<key>dark</key>
|
||||||
|
<string>#00000</string>
|
||||||
|
<key>default</key>
|
||||||
|
<string>#FFFFFF</string>
|
||||||
|
</dict>
|
||||||
|
<key>version</key>
|
||||||
|
<integer>1</integer>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
<?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>posterEnvironmentOverrides</key>
|
||||||
|
<data>
|
||||||
|
e30=
|
||||||
|
</data>
|
||||||
|
<key>wallpaperRepresentingFileName</key>
|
||||||
|
<string>9183.Custom-810w-1080h@2x~ipad.wallpaper</string>
|
||||||
|
<key>wallpaperRepresentingIdentifier</key>
|
||||||
|
<string>9999</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import UniformTypeIdentifiers
|
import UniformTypeIdentifiers
|
||||||
|
import PhotosUI
|
||||||
|
|
||||||
extension UIDocumentPickerViewController {
|
extension UIDocumentPickerViewController {
|
||||||
@objc func fix_init(forOpeningContentTypes contentTypes: [UTType], asCopy: Bool) -> UIDocumentPickerViewController {
|
@objc func fix_init(forOpeningContentTypes contentTypes: [UTType], asCopy: Bool) -> UIDocumentPickerViewController {
|
||||||
@@ -14,6 +15,25 @@ extension UIDocumentPickerViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Movie: Transferable {
|
||||||
|
let url: URL
|
||||||
|
|
||||||
|
static var transferRepresentation: some TransferRepresentation {
|
||||||
|
FileRepresentation(contentType: .movie) { movie in
|
||||||
|
SentTransferredFile(movie.url)
|
||||||
|
} importing: { received in
|
||||||
|
let copy = URL.documentsDirectory.appending(path: "movie.mp4")
|
||||||
|
|
||||||
|
if FileManager.default.fileExists(atPath: copy.path()) {
|
||||||
|
try FileManager.default.removeItem(at: copy)
|
||||||
|
}
|
||||||
|
|
||||||
|
try FileManager.default.copyItem(at: received.file, to: copy)
|
||||||
|
return Self.init(url: copy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
// Prefs
|
// Prefs
|
||||||
@AppStorage("pbHash") var pbHash: String = ""
|
@AppStorage("pbHash") var pbHash: String = ""
|
||||||
@@ -22,6 +42,12 @@ struct ContentView: View {
|
|||||||
|
|
||||||
@State var showTendiesImporter: Bool = false
|
@State var showTendiesImporter: Bool = false
|
||||||
|
|
||||||
|
enum LoadState {
|
||||||
|
case unknown, loading, loaded(Movie), failed
|
||||||
|
}
|
||||||
|
@State var selectedVideo: PhotosPickerItem?
|
||||||
|
@State private var loadState = LoadState.unknown
|
||||||
|
|
||||||
@State var showErrorAlert = false
|
@State var showErrorAlert = false
|
||||||
@State var lastError: String?
|
@State var lastError: String?
|
||||||
@State var hideResetHelp: Bool = true
|
@State var hideResetHelp: Bool = true
|
||||||
@@ -35,6 +61,49 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
|
VStack {
|
||||||
|
switch loadState {
|
||||||
|
case .unknown:
|
||||||
|
EmptyView()
|
||||||
|
case .loading:
|
||||||
|
ProgressView()
|
||||||
|
case .loaded(let movie):
|
||||||
|
Text("Video imported")
|
||||||
|
case .failed:
|
||||||
|
Text("Import failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch loadState {
|
||||||
|
case .loaded(let movie):
|
||||||
|
Button(action: {
|
||||||
|
selectedVideo = nil
|
||||||
|
try? FileManager.default.removeItem(at: movie.url)
|
||||||
|
loadState = .unknown
|
||||||
|
}) {
|
||||||
|
Text("Remove Selected Video")
|
||||||
|
}
|
||||||
|
.buttonStyle(TintedButton(color: .red, fullwidth: true))
|
||||||
|
default:
|
||||||
|
PhotosPicker("Select Video", selection: $selectedVideo, matching: .videos)
|
||||||
|
.buttonStyle(TintedButton(color: .yellow, fullwidth: true))
|
||||||
|
.onChange(of: selectedVideo) { _ in
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
loadState = .loading
|
||||||
|
|
||||||
|
if let movie = try await selectedVideo?.loadTransferable(type: Movie.self) {
|
||||||
|
loadState = .loaded(movie)
|
||||||
|
} else {
|
||||||
|
loadState = .failed
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
loadState = .failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
||||||
showTendiesImporter.toggle()
|
showTendiesImporter.toggle()
|
||||||
@@ -62,14 +131,21 @@ struct ContentView: View {
|
|||||||
Text("Enter your PosterBoard app hash in Settings.")
|
Text("Enter your PosterBoard app hash in Settings.")
|
||||||
} else {
|
} else {
|
||||||
VStack {
|
VStack {
|
||||||
if !PosterBoardManager.shared.selectedTendies.isEmpty {
|
if !PosterBoardManager.shared.selectedTendies.isEmpty || selectedVideo != nil {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
UIImpactFeedbackGenerator(style: .soft).impactOccurred()
|
||||||
UIApplication.shared.alert(title: NSLocalizedString("Applying Tendies...", comment: ""), body: NSLocalizedString("Please wait", comment: ""), animated: false, withButton: false)
|
UIApplication.shared.alert(title: NSLocalizedString("Applying Tendies...", comment: ""), body: NSLocalizedString("Please wait", comment: ""), animated: false, withButton: false)
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||||
do {
|
do {
|
||||||
try PosterBoardManager.shared.applyTendies(appHash: pbHash)
|
var videoURL: URL? = nil
|
||||||
|
switch loadState {
|
||||||
|
case .loaded(let movie):
|
||||||
|
videoURL = movie.url
|
||||||
|
default:
|
||||||
|
videoURL = nil
|
||||||
|
}
|
||||||
|
try PosterBoardManager.shared.applyTendies(appHash: pbHash, videoURL: videoURL)
|
||||||
PosterBoardManager.shared.selectedTendies.removeAll()
|
PosterBoardManager.shared.selectedTendies.removeAll()
|
||||||
SymHandler.cleanup() // just to be extra sure
|
SymHandler.cleanup() // just to be extra sure
|
||||||
try? FileManager.default.removeItem(at: PosterBoardManager.shared.getTendiesStoreURL())
|
try? FileManager.default.removeItem(at: PosterBoardManager.shared.getTendiesStoreURL())
|
||||||
|
|||||||
Reference in new issue
Block a user