Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 372b27540b | |||
| db4d15bf8b | |||
| 8e24ea9ed7 | |||
| 73c0125843 |
@@ -207,10 +207,20 @@ jobs:
|
||||
# (Config/Punktfunk-macOS.entitlements) — mandatory for the Mac App Store.
|
||||
continue-on-error: true
|
||||
run: |
|
||||
# Separate archive from the Developer ID one above: App Store needs a profile-signed
|
||||
# archive (manual signing), not the unsigned-then-codesign DMG path. Same App-Manager
|
||||
# ASC-key constraint as iOS/tvOS — MANUAL signing, NOT -allowProvisioningUpdates
|
||||
# (cloud signing the key can't do). Quit Xcode so it can't prune the dropped profile.
|
||||
# Separate archive from the Developer ID one above: App Store needs a signed, entitled
|
||||
# archive that -exportArchive can re-sign for distribution, not the unsigned-then-codesign
|
||||
# DMG path. Archive with AUTOMATIC signing (development). Why not a manually-specified
|
||||
# profile (as this step used to do): the in-app license screens added a SwiftPM resource
|
||||
# bundle (PunktfunkKit_PunktfunkKit), and a resource bundle is a product type that cannot
|
||||
# carry a provisioning profile — a global PROVISIONING_PROFILE_SPECIFIER (here) or an
|
||||
# sdk-scoped one (iOS/tvOS) lands on it and fails the archive ("does not support
|
||||
# provisioning profiles"). Automatic signing assigns a profile only to the app and leaves
|
||||
# the resource bundle (and the macOS-host macro plugins) alone, and bakes the sandbox
|
||||
# entitlements in. No -allowProvisioningUpdates → it stays OFFLINE and never cloud-signs
|
||||
# (the App-Manager ASC key can't), so the runner must have a macOS *development* profile
|
||||
# for io.unom.punktfunk installed. DISTRIBUTION signing happens in the export step below
|
||||
# (manual, via the plist). Quit Xcode so it can't prune the manually-installed App Store
|
||||
# distribution profile that export needs.
|
||||
osascript -e 'tell application "Xcode" to quit' >/dev/null 2>&1 || true
|
||||
pkill -x Xcode 2>/dev/null || true
|
||||
PROFILE="Punktfunk macOS App Store Distribution"
|
||||
@@ -218,11 +228,10 @@ jobs:
|
||||
-project "$PROJECT" -scheme Punktfunk \
|
||||
-destination 'generic/platform=macOS' \
|
||||
-archivePath "$RUNNER_TEMP/Punktfunk-macos-appstore.xcarchive" \
|
||||
-skipMacroValidation -skipPackagePluginValidation \
|
||||
MARKETING_VERSION="$VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUM" \
|
||||
CODE_SIGN_STYLE=Manual \
|
||||
CODE_SIGN_IDENTITY="Apple Distribution" \
|
||||
DEVELOPMENT_TEAM="$TEAM_ID" \
|
||||
PROVISIONING_PROFILE_SPECIFIER="$PROFILE"
|
||||
CODE_SIGN_STYLE=Automatic \
|
||||
DEVELOPMENT_TEAM="$TEAM_ID"
|
||||
cat > "$RUNNER_TEMP/export-macos-appstore.plist" <<EOF
|
||||
<?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">
|
||||
@@ -252,35 +261,27 @@ jobs:
|
||||
# Best-effort until the App Store Connect app record for io.unom.punktfunk exists.
|
||||
continue-on-error: true
|
||||
run: |
|
||||
# MANUAL App Store signing: the local (valid) Apple Distribution identity + the App
|
||||
# Store provisioning profile. NOT -allowProvisioningUpdates — with an App-Manager-role
|
||||
# ASC key that forces Xcode's CLOUD-managed signing, which the role can't do ("Cloud
|
||||
# signing permission error"). The profile must be installed on the runner under
|
||||
# ~/Library/Developer/Xcode/UserData/Provisioning Profiles/ (install it once with
|
||||
# Xcode.app quit, or it prunes the manually-dropped distribution profile).
|
||||
# A running Xcode.app prunes unrecognized profiles from that dir — quit it so the App
|
||||
# Store profile survives this build; headless xcodebuild doesn't need the GUI app.
|
||||
# Archive with AUTOMATIC signing (development) — see the macOS App Store step for the full
|
||||
# rationale. The SwiftPM resource bundle (PunktfunkKit_PunktfunkKit, added with the in-app
|
||||
# license screens) builds for iphoneos, so even the sdk-scoped PROVISIONING_PROFILE_SPECIFIER
|
||||
# this step used to set matched it and failed the archive ("does not support provisioning
|
||||
# profiles"). Automatic signing profiles only the app and leaves the resource bundle (and
|
||||
# the macOS-host macro plugins) alone. No -allowProvisioningUpdates → OFFLINE, never
|
||||
# cloud-signs (the App-Manager ASC key can't), so the runner needs an iOS *development*
|
||||
# profile for io.unom.punktfunk installed. DISTRIBUTION signing is the export step below
|
||||
# (manual, via the plist). A running Xcode.app prunes unrecognized profiles — quit it so the
|
||||
# manually-installed App Store distribution profile survives for export.
|
||||
osascript -e 'tell application "Xcode" to quit' >/dev/null 2>&1 || true
|
||||
pkill -x Xcode 2>/dev/null || true
|
||||
PROFILE="Punktfunk iOS App Store Distribution"
|
||||
# Scope signing to the iOS device SDK via an xcconfig — see the tvOS step below for the
|
||||
# full rationale. A global (CLI) profile specifier would also be forced onto the shared
|
||||
# macOS-host SwiftPM macro plugins, which reject it and fail the archive; [sdk=iphoneos*]
|
||||
# in an xcconfig lands it on the app/framework slices only.
|
||||
SIGN_XCCONFIG="$RUNNER_TEMP/sign-ios.xcconfig"
|
||||
cat > "$SIGN_XCCONFIG" <<XCCONF
|
||||
CODE_SIGN_STYLE = Manual
|
||||
DEVELOPMENT_TEAM = $TEAM_ID
|
||||
CODE_SIGN_IDENTITY[sdk=iphoneos*] = Apple Distribution
|
||||
PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*] = $PROFILE
|
||||
XCCONF
|
||||
DEVELOPER_DIR="$XCODE_DEV_DIR" xcodebuild archive \
|
||||
-project "$PROJECT" -scheme Punktfunk-iOS \
|
||||
-destination 'generic/platform=iOS' \
|
||||
-archivePath "$RUNNER_TEMP/Punktfunk-ios.xcarchive" \
|
||||
-skipMacroValidation -skipPackagePluginValidation \
|
||||
-xcconfig "$SIGN_XCCONFIG" \
|
||||
MARKETING_VERSION="$VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUM"
|
||||
MARKETING_VERSION="$VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUM" \
|
||||
CODE_SIGN_STYLE=Automatic \
|
||||
DEVELOPMENT_TEAM="$TEAM_ID"
|
||||
cat > "$RUNNER_TEMP/export-appstore.plist" <<EOF
|
||||
<?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">
|
||||
@@ -312,33 +313,24 @@ jobs:
|
||||
# on the runner (xcodebuild -downloadPlatform tvOS).
|
||||
continue-on-error: true
|
||||
run: |
|
||||
# Same manual App Store signing as iOS (the App-Manager ASC key can't cloud-sign).
|
||||
# Archive with AUTOMATIC signing (development) — see the macOS App Store step. The SwiftPM
|
||||
# resource bundle (PunktfunkKit_PunktfunkKit) builds for appletvos and rejected the
|
||||
# sdk-scoped profile this step used to set; Automatic signing profiles only the app and
|
||||
# leaves the resource bundle + the macOS-host macro plugins (OnceMacro/SwizzlingMacro/
|
||||
# AssociationMacro) alone. No -allowProvisioningUpdates → OFFLINE, never cloud-signs (the
|
||||
# App-Manager ASC key can't), so the runner needs a tvOS *development* profile for
|
||||
# io.unom.punktfunk installed. DISTRIBUTION signing is the export step below (manual, plist).
|
||||
osascript -e 'tell application "Xcode" to quit' >/dev/null 2>&1 || true
|
||||
pkill -x Xcode 2>/dev/null || true
|
||||
PROFILE="Punktfunk tvOS App Store Distribution"
|
||||
# Scope signing to the tvOS device SDK via an xcconfig. A global (CLI) profile specifier
|
||||
# hits EVERY target, including the shared SwiftPM macro plugins (OnceMacro/SwizzlingMacro/
|
||||
# AssociationMacro) which build for the macOS host and reject a provisioning profile
|
||||
# ("<macro> does not support provisioning profiles"), failing the archive. Conditionals
|
||||
# work only in an xcconfig (xcodebuild mis-parses a CLI "SETTING[sdk=..]=val"), and a
|
||||
# command-line -xcconfig outranks target settings, so [sdk=appletvos*] puts the profile on
|
||||
# the app/framework slices only — the macosx-host macros get nothing. (The macOS archive
|
||||
# above is immune: its host-SDK macros are CODE_SIGNING_ALLOWED=NO, so a global specifier
|
||||
# is ignored there.)
|
||||
SIGN_XCCONFIG="$RUNNER_TEMP/sign-tvos.xcconfig"
|
||||
cat > "$SIGN_XCCONFIG" <<XCCONF
|
||||
CODE_SIGN_STYLE = Manual
|
||||
DEVELOPMENT_TEAM = $TEAM_ID
|
||||
CODE_SIGN_IDENTITY[sdk=appletvos*] = Apple Distribution
|
||||
PROVISIONING_PROFILE_SPECIFIER[sdk=appletvos*] = $PROFILE
|
||||
XCCONF
|
||||
DEVELOPER_DIR="$XCODE_DEV_DIR" xcodebuild archive \
|
||||
-project "$PROJECT" -scheme Punktfunk-tvOS \
|
||||
-destination 'generic/platform=tvOS' \
|
||||
-archivePath "$RUNNER_TEMP/Punktfunk-tvos.xcarchive" \
|
||||
-skipMacroValidation -skipPackagePluginValidation \
|
||||
-xcconfig "$SIGN_XCCONFIG" \
|
||||
MARKETING_VERSION="$VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUM"
|
||||
MARKETING_VERSION="$VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUM" \
|
||||
CODE_SIGN_STYLE=Automatic \
|
||||
DEVELOPMENT_TEAM="$TEAM_ID"
|
||||
cat > "$RUNNER_TEMP/export-tvos.plist" <<EOF
|
||||
<?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">
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
"name": "MIT OR Apache-2.0",
|
||||
"identifier": "MIT OR Apache-2.0"
|
||||
},
|
||||
"version": "0.0.1"
|
||||
"version": "0.3.0"
|
||||
},
|
||||
"paths": {
|
||||
"/api/v1/clients": {
|
||||
|
||||
@@ -10,32 +10,44 @@ struct AcknowledgementsView: View {
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
Text("punktfunk")
|
||||
.font(.title2).bold()
|
||||
if let version {
|
||||
Text("Version \(version)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
// Top-level LazyVStack so the third-party-notices chunks (Licenses.thirdPartyNoticesChunks,
|
||||
// ~885 KB total) load lazily as they scroll into view — a single Text that large overshoots
|
||||
// the text-rendering height limit (blank below the limit + very slow). spacing 0 keeps the
|
||||
// notice chunks visually continuous; the header block carries its own spacing + bottom pad.
|
||||
LazyVStack(alignment: .leading, spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
Text("punktfunk")
|
||||
.font(.title2).bold()
|
||||
if let version {
|
||||
Text("Version \(version)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Text(Licenses.appLicense)
|
||||
.font(.caption.monospaced())
|
||||
.modifier(SelectableText())
|
||||
|
||||
Divider()
|
||||
|
||||
Text("Third-party software")
|
||||
.font(.headline)
|
||||
Text(
|
||||
"punktfunk uses the open-source components below, each under its own license. "
|
||||
+ "On some platforms FFmpeg is additionally bundled under the LGPL v2.1+ "
|
||||
+ "(dynamically linked, replaceable)."
|
||||
)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
Text(Licenses.appLicense)
|
||||
.font(.caption.monospaced())
|
||||
.modifier(SelectableText())
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.bottom, 18)
|
||||
|
||||
Divider()
|
||||
|
||||
Text("Third-party software")
|
||||
.font(.headline)
|
||||
Text(
|
||||
"punktfunk uses the open-source components below, each under its own license. "
|
||||
+ "On some platforms FFmpeg is additionally bundled under the LGPL v2.1+ "
|
||||
+ "(dynamically linked, replaceable)."
|
||||
)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
Text(Licenses.thirdPartyNotices)
|
||||
.font(.caption2.monospaced())
|
||||
.modifier(SelectableText())
|
||||
ForEach(Licenses.thirdPartyNoticesChunks.indices, id: \.self) { i in
|
||||
Text(Licenses.thirdPartyNoticesChunks[i])
|
||||
.font(.caption2.monospaced())
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.modifier(SelectableText())
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: 900, alignment: .leading)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
@@ -81,6 +81,11 @@ struct AddHostSheet: View {
|
||||
#if !os(tvOS)
|
||||
.formStyle(.grouped)
|
||||
#endif
|
||||
#if os(iOS)
|
||||
// The detent below is sized to fit all 3 rows + the action button exactly, so the
|
||||
// Form must NOT scroll/bounce inside it — lock it. (iOS 16+; safe at iOS 17.)
|
||||
.scrollDisabled(true)
|
||||
#endif
|
||||
#if os(macOS)
|
||||
// macOS: UNCHANGED — Cancel + Spacer + Add in an HStack, both wired to the
|
||||
// window's default/cancel keyboard actions. The 380-wide .fixedSize panel below
|
||||
@@ -120,8 +125,8 @@ struct AddHostSheet: View {
|
||||
// Form + the full-width action row, instead of the half-screen .medium it used to rest
|
||||
// at. A single fixed detent is enough: the system keeps the content above the keyboard
|
||||
// when Address/Port is focused, and on iPadOS this renders as a short bottom sheet (not a
|
||||
// centered formSheet card). If Dynamic Type grows the rows past this height the Form just
|
||||
// scrolls inside the detent — nothing is clipped. (.height(_:) is iOS 16+, safe at iOS 17.)
|
||||
// centered formSheet card). The Form itself is .scrollDisabled (above) so it can't
|
||||
// bounce/scroll inside this fixed detent. (.height(_:) is iOS 16+, safe at iOS 17.)
|
||||
.presentationDetents([.height(320)])
|
||||
.presentationDragIndicator(.visible)
|
||||
#endif
|
||||
|
||||
@@ -33,4 +33,18 @@ public enum Licenses {
|
||||
let text = resource("THIRD-PARTY-NOTICES")
|
||||
return text.isEmpty ? "Third-party notices unavailable." : text
|
||||
}
|
||||
|
||||
/// `thirdPartyNotices` pre-split into render-sized line chunks. The full notices are ~885 KB /
|
||||
/// 16k lines; a single SwiftUI `Text` that large overshoots CoreText/CoreAnimation's max
|
||||
/// renderable height — it lays out for ages and draws blank past the limit — so the
|
||||
/// Acknowledgements screen renders these chunks in a `LazyVStack` (only on-screen chunks lay
|
||||
/// out, and no chunk is tall enough to clip). Split at line boundaries and joined with "\n";
|
||||
/// the inter-chunk break is the `LazyVStack` row boundary, so no text is lost. Computed once.
|
||||
public static let thirdPartyNoticesChunks: [String] = {
|
||||
let lines = thirdPartyNotices.split(separator: "\n", omittingEmptySubsequences: false)
|
||||
let chunkSize = 200
|
||||
return stride(from: 0, to: lines.count, by: chunkSize).map { start in
|
||||
lines[start..<min(start + chunkSize, lines.count)].joined(separator: "\n")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user