chore (iOS): Adhere to iOS development guidelines
105
scripts/ios-opaque-app-icons.swift
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env swift
|
||||
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
import ImageIO
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct HexColor {
|
||||
let red: CGFloat
|
||||
let green: CGFloat
|
||||
let blue: CGFloat
|
||||
|
||||
init(_ hex: String) {
|
||||
let sanitized = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
||||
guard sanitized.count == 6, let value = UInt64(sanitized, radix: 16) else {
|
||||
fatalError("Invalid hex color: \(hex)")
|
||||
}
|
||||
|
||||
red = CGFloat((value >> 16) & 0xFF) / 255.0
|
||||
green = CGFloat((value >> 8) & 0xFF) / 255.0
|
||||
blue = CGFloat(value & 0xFF) / 255.0
|
||||
}
|
||||
}
|
||||
|
||||
func flattenPNG(at url: URL, background: HexColor) throws {
|
||||
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil),
|
||||
let image = CGImageSourceCreateImageAtIndex(source, 0, nil) else {
|
||||
throw NSError(domain: "ios-opaque-app-icons", code: 1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "Failed to load image at \(url.path)"
|
||||
])
|
||||
}
|
||||
|
||||
switch image.alphaInfo {
|
||||
case .alphaOnly, .first, .last, .premultipliedFirst, .premultipliedLast:
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
let bitmapInfo = CGImageAlphaInfo.noneSkipLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
|
||||
guard let context = CGContext(
|
||||
data: nil,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
bitsPerComponent: 8,
|
||||
bytesPerRow: 0,
|
||||
space: CGColorSpaceCreateDeviceRGB(),
|
||||
bitmapInfo: bitmapInfo
|
||||
) else {
|
||||
throw NSError(domain: "ios-opaque-app-icons", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: "Failed to create drawing context for \(url.path)"
|
||||
])
|
||||
}
|
||||
|
||||
context.setFillColor(CGColor(red: background.red, green: background.green, blue: background.blue, alpha: 1))
|
||||
context.fill(CGRect(x: 0, y: 0, width: image.width, height: image.height))
|
||||
context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))
|
||||
|
||||
guard let flattened = context.makeImage() else {
|
||||
throw NSError(domain: "ios-opaque-app-icons", code: 3, userInfo: [
|
||||
NSLocalizedDescriptionKey: "Failed to finalize image for \(url.path)"
|
||||
])
|
||||
}
|
||||
|
||||
let tempURL = url.deletingLastPathComponent().appendingPathComponent(".\(url.lastPathComponent).tmp")
|
||||
guard let destination = CGImageDestinationCreateWithURL(tempURL as CFURL, UTType.png.identifier as CFString, 1, nil) else {
|
||||
throw NSError(domain: "ios-opaque-app-icons", code: 4, userInfo: [
|
||||
NSLocalizedDescriptionKey: "Failed to create output file for \(url.path)"
|
||||
])
|
||||
}
|
||||
|
||||
CGImageDestinationAddImage(destination, flattened, nil)
|
||||
guard CGImageDestinationFinalize(destination) else {
|
||||
throw NSError(domain: "ios-opaque-app-icons", code: 5, userInfo: [
|
||||
NSLocalizedDescriptionKey: "Failed to write PNG data for \(url.path)"
|
||||
])
|
||||
}
|
||||
|
||||
_ = try FileManager.default.replaceItemAt(url, withItemAt: tempURL)
|
||||
}
|
||||
|
||||
let arguments = CommandLine.arguments.dropFirst()
|
||||
guard let appIconSetArgument = arguments.first else {
|
||||
fputs("usage: ios-opaque-app-icons.swift <AppIcon.appiconset> [background_hex]\n", stderr)
|
||||
exit(64)
|
||||
}
|
||||
|
||||
let background = HexColor(arguments.dropFirst().first ?? "FFF3D6")
|
||||
let appIconSetURL = URL(fileURLWithPath: appIconSetArgument, isDirectory: true)
|
||||
let fileManager = FileManager.default
|
||||
|
||||
guard fileManager.fileExists(atPath: appIconSetURL.path) else {
|
||||
exit(0)
|
||||
}
|
||||
|
||||
let pngURLs = try fileManager.contentsOfDirectory(
|
||||
at: appIconSetURL,
|
||||
includingPropertiesForKeys: nil,
|
||||
options: [.skipsHiddenFiles]
|
||||
)
|
||||
.filter { $0.pathExtension.lowercased() == "png" }
|
||||
|
||||
for pngURL in pngURLs {
|
||||
try flattenPNG(at: pngURL, background: background)
|
||||
}
|
||||
|
|
@ -55,6 +55,16 @@ run_tauri_ios_xcode_script() {
|
|||
${ARCHS:?}
|
||||
}
|
||||
|
||||
normalize_ios_app_icons() {
|
||||
local app_icon_set="$REPO_ROOT/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset"
|
||||
xcrun --sdk macosx swift "$SCRIPT_DIR/ios-opaque-app-icons.swift" "$app_icon_set"
|
||||
}
|
||||
|
||||
build_tauri_ios_target() {
|
||||
run_tauri_ios_xcode_script "$@"
|
||||
normalize_ios_app_icons
|
||||
}
|
||||
|
||||
if ! ensure_node_on_path; then
|
||||
echo "error: node was not found in Xcode's PATH." >&2
|
||||
echo "error: install Node in a stable location, or configure fnm/volta so GUI apps can resolve it." >&2
|
||||
|
|
@ -63,12 +73,12 @@ if ! ensure_node_on_path; then
|
|||
fi
|
||||
|
||||
if command -v pnpm >/dev/null 2>&1; then
|
||||
run_tauri_ios_xcode_script pnpm
|
||||
build_tauri_ios_target pnpm
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if command -v corepack >/dev/null 2>&1; then
|
||||
run_tauri_ios_xcode_script corepack pnpm
|
||||
build_tauri_ios_target corepack pnpm
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
|
|
|||
10
src-tauri/Info.ios.plist
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?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>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>TauriTavern uses the camera only to scan LAN Sync pairing QR codes between your devices.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
Before Width: | Height: | Size: 758 B After Width: | Height: | Size: 790 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 300 KiB After Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
|
@ -30,7 +30,6 @@ targets:
|
|||
sources:
|
||||
- path: Sources
|
||||
- path: Assets.xcassets
|
||||
- path: Externals
|
||||
- path: tauritavern_iOS
|
||||
- path: assets
|
||||
buildPhase: resources
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
AA3812A8AB49B932DA7D1FE2 /* libapp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C11991D4BBA9ED50B2CBD16A /* libapp.a */; };
|
||||
BE9048F0BE508CFBCBAAE845 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 87CD80661131CB327DEBC646 /* LaunchScreen.storyboard */; };
|
||||
CD0EE93A72D73C678ED1D675 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 581C420CD09C276B28F81159 /* assets */; };
|
||||
EA65CA1E6500175D7775DBA9 /* libapp.a in Resources */ = {isa = PBXBuildFile; fileRef = 3DF579298F528439D611A8DE /* libapp.a */; };
|
||||
F64A8E669FCED0F94A1592A1 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = E494B2830348E4D8EB0AEEAD /* main.mm */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
|
@ -847,7 +846,6 @@
|
|||
88452944535A8DB9D80683ED /* Assets.xcassets in Resources */,
|
||||
BE9048F0BE508CFBCBAAE845 /* LaunchScreen.storyboard in Resources */,
|
||||
CD0EE93A72D73C678ED1D675 /* assets in Resources */,
|
||||
EA65CA1E6500175D7775DBA9 /* libapp.a in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -15,9 +15,13 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.5.1</string>
|
||||
<string>1.5.2</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.5.1</string>
|
||||
<string>1.5.2</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>TauriTavern uses the camera only to scan LAN Sync pairing QR codes between your devices.</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
|
|
@ -41,4 +45,4 @@
|
|||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
|
|
|||