Files
TZGyn 9a37e93cfc Add a native iOS client for relay-based file sharing
The SwiftUI app bundles the Go swarm engine through gomobile, so an iPhone is a real peer using the existing PAKE handshake, chunk verification, and relay fallback. XcodeGen builds the project and devicectl installs it on the paired phone.
2026-10-26 22:24:47 +08:00

174 lines
4.9 KiB
Swift

#!/usr/bin/env swift
// Draws the 1024x1024 Share app icon. Run from apps/ios:
// swift Branding/make-icon.swift Share/Assets.xcassets/AppIcon.appiconset/AppIcon.png
import AppKit
import CoreGraphics
import ImageIO
let size = 1024
let output = CommandLine.arguments.count > 1
? CommandLine.arguments[1]
: "Share/Assets.xcassets/AppIcon.appiconset/AppIcon.png"
func color(_ hex: UInt32, _ alpha: CGFloat = 1) -> NSColor {
NSColor(
calibratedRed: CGFloat((hex >> 16) & 0xFF) / 255,
green: CGFloat((hex >> 8) & 0xFF) / 255,
blue: CGFloat(hex & 0xFF) / 255,
alpha: alpha
)
}
guard let rep = NSBitmapImageRep(
bitmapDataPlanes: nil,
pixelsWide: size,
pixelsHigh: size,
bitsPerSample: 8,
samplesPerPixel: 4,
hasAlpha: true,
isPlanar: false,
colorSpaceName: .deviceRGB,
bytesPerRow: 0,
bitsPerPixel: 0
) else {
fatalError("Could not create bitmap")
}
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep)
NSGraphicsContext.current?.imageInterpolation = .high
NSGraphicsContext.current?.shouldAntialias = true
let canvas = NSRect(x: 0, y: 0, width: size, height: size)
let background = NSGradient(colors: [color(0x07111F), color(0x0B3B36)])!
background.draw(in: canvas, angle: -55)
// Soft teal glow in the upper-left and blue glow in the lower-right.
let tealGlow = NSGradient(colors: [color(0x5EEAD4, 0.35), color(0x5EEAD4, 0)])!
tealGlow.draw(
in: NSBezierPath(ovalIn: NSRect(x: -260, y: 430, width: 1020, height: 1020)),
relativeCenterPosition: NSPoint(x: 0, y: 0)
)
let blueGlow = NSGradient(colors: [color(0x60A5FA, 0.28), color(0x60A5FA, 0)])!
blueGlow.draw(
in: NSBezierPath(ovalIn: NSRect(x: 300, y: -300, width: 980, height: 980)),
relativeCenterPosition: NSPoint(x: 0, y: 0)
)
// A barely-there inset panel gives the flat icon some depth.
let panel = NSBezierPath(
roundedRect: NSRect(x: 116, y: 116, width: 792, height: 792),
xRadius: 180,
yRadius: 180
)
color(0xFFFFFF, 0.035).setFill()
panel.fill()
color(0xFFFFFF, 0.07).setStroke()
panel.lineWidth = 2
panel.stroke()
func strokeArrow(shaftFrom: NSPoint, shaftTo: NSPoint,
headLeft: NSPoint, headTip: NSPoint, headRight: NSPoint,
tint: NSColor) {
let shadow = NSShadow()
shadow.shadowColor = color(0x000000, 0.3)
shadow.shadowBlurRadius = 34
shadow.shadowOffset = NSSize(width: 0, height: -16)
shadow.set()
tint.setStroke()
let shaft = NSBezierPath()
shaft.move(to: shaftFrom)
shaft.line(to: shaftTo)
shaft.lineWidth = 74
shaft.lineCapStyle = .round
shaft.stroke()
let head = NSBezierPath()
head.move(to: headLeft)
head.line(to: headTip)
head.line(to: headRight)
head.lineWidth = 78
head.lineCapStyle = .round
head.lineJoinStyle = .round
head.stroke()
}
// Up arrow on the left.
strokeArrow(
shaftFrom: NSPoint(x: 350, y: 288),
shaftTo: NSPoint(x: 350, y: 610),
headLeft: NSPoint(x: 238, y: 490),
headTip: NSPoint(x: 350, y: 640),
headRight: NSPoint(x: 462, y: 490),
tint: color(0xF4FFFC)
)
// Down arrow on the right.
strokeArrow(
shaftFrom: NSPoint(x: 674, y: 736),
shaftTo: NSPoint(x: 674, y: 414),
headLeft: NSPoint(x: 562, y: 534),
headTip: NSPoint(x: 674, y: 384),
headRight: NSPoint(x: 786, y: 534),
tint: color(0x5EEAD4)
)
// Three small dots suggest a swarm: peers nearby, not a central server.
for (point, alpha) in [
(NSPoint(x: 250, y: 790), CGFloat(0.55)),
(NSPoint(x: 512, y: 840), CGFloat(0.35)),
(NSPoint(x: 790, y: 260), CGFloat(0.45)),
] {
let dot = NSBezierPath(ovalIn: NSRect(x: point.x - 7, y: point.y - 7, width: 14, height: 14))
color(0xFFFFFF, alpha).setFill()
dot.fill()
}
NSGraphicsContext.restoreGraphicsState()
guard let cgImage = rep.cgImage else {
fatalError("Could not make a CGImage")
}
// App Store icons must not contain an alpha channel. Drawing the rendered
// image into an opaque context and then encoding that image gives us an
// RGB PNG without changing a single visible pixel.
let width = cgImage.width
let height = cgImage.height
let colorSpace = CGColorSpaceCreateDeviceRGB()
guard let context = CGContext(
data: nil,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: 0,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue
) else {
fatalError("Could not create an opaque context")
}
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
guard let opaque = context.makeImage() else {
fatalError("Could not render the opaque icon")
}
let url = URL(fileURLWithPath: output)
try FileManager.default.createDirectory(
at: url.deletingLastPathComponent(),
withIntermediateDirectories: true
)
guard let destination = CGImageDestinationCreateWithURL(
url as CFURL,
"public.png" as CFString,
1,
nil
) else {
fatalError("Could not create a PNG destination")
}
CGImageDestinationAddImage(destination, opaque, nil)
guard CGImageDestinationFinalize(destination) else {
fatalError("Could not write the PNG")
}
print("Wrote \(url.path)")