Catalyst: ohne Netz, ohne Übersetzung, ohne Fenstergröße

Drei Fehler auf einmal, und alle drei meine.

**Die App kam gar nicht ins Netz.** Ein Catalyst-Programm läuft in der Sandbox
und braucht eine ausdrückliche Erlaubnis für ausgehende Verbindungen. Ohne sie
gibt es keine Fehlermeldung — der Ladekreis dreht sich einfach für immer, und
genau so sah es aus.

**In der Seitenleiste standen Schlüssel statt Wörtern** — „sidebar.downloads"
statt „Downloads". Ich hatte zwei Fassungen derselben Beschriftung gebaut, eine
mit `LocalizedStringKey` und eine mit `String` für Titel, die vom Server
kommen. Bei einem Literal darf Swift sich für beide entscheiden, und es
entschied sich nicht überall gleich. Jetzt gibt es eine Fassung; wer einen
fertigen Text hat, baut sein `Text` selbst.

**Das Fenster öffnete wieder in Notizzettelgröße.** Meine Größenangabe hing an
`os(macOS)` — unter Catalyst gilt aber `os(iOS)`, also griff sie nicht. Dort
hängt die Größe ohnehin an der Szene, nicht am Fenster.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-13 16:22:42 +02:00
co-authored by Claude Opus 5
parent f296d7ae1a
commit 39d9138911
6 changed files with 3777 additions and 3730 deletions
+13
View File
@@ -0,0 +1,13 @@
<?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>
<!-- Catalyst-Programme laufen in der Sandbox. Ohne die Erlaubnis für
ausgehende Verbindungen erreicht die App keinen Server — und zwar
ohne Fehlermeldung: der Ladekreis dreht sich einfach für immer. -->
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
+31 -1
View File
@@ -70,9 +70,18 @@ struct FlixyApp: App {
// Eine Untergrenze, unter der die Oberfläche nicht mehr // Eine Untergrenze, unter der die Oberfläche nicht mehr
// aufgeht: schmaler als das hier klappt die Seitenleiste zu // aufgeht: schmaler als das hier klappt die Seitenleiste zu
// und die Reihen zeigen anderthalb Kacheln. // und die Reihen zeigen anderthalb Kacheln.
#if os(macOS) //
// `os(macOS)` allein reichte nicht — unter Catalyst gilt
// `os(iOS)`, und genau deshalb öffnete das Fenster wieder in
// Notizzettelgröße.
#if os(macOS) || targetEnvironment(macCatalyst)
.frame(minWidth: 900, minHeight: 620) .frame(minWidth: 900, minHeight: 620)
#endif #endif
#if targetEnvironment(macCatalyst)
// Unter Catalyst gibt es `defaultSize` nicht; die Größe hängt
// an der Szene.
.task { Self.widenWindow() }
#endif
.environment(session) .environment(session)
.environment(playback) .environment(playback)
.environment(playbackSettings) .environment(playbackSettings)
@@ -123,6 +132,27 @@ struct FlixyApp: App {
} }
#if os(iOS) #if os(iOS)
#if targetEnvironment(macCatalyst)
extension FlixyApp {
/// Gibt dem Fenster eine vernünftige Mindestgröße.
///
/// Unter Catalyst öffnet macOS sonst ein Fenster von der Größe eines
/// Notizzettels — und darin fällt der Aufmacher in sich zusammen, weil
/// seine Abstände dehnbar sind.
@MainActor
static func widenWindow() {
for scene in UIApplication.shared.connectedScenes {
guard let windowScene = scene as? UIWindowScene else { continue }
windowScene.sizeRestrictions?.minimumSize = CGSize(width: 900, height: 620)
windowScene.sizeRestrictions?.maximumSize = CGSize(
width: CGFloat.greatestFiniteMagnitude,
height: CGFloat.greatestFiniteMagnitude
)
}
}
}
#endif
/// Der klassische App-Delegat, für das eine, was SwiftUI nicht abdeckt. /// Der klassische App-Delegat, für das eine, was SwiftUI nicht abdeckt.
@MainActor @MainActor
final class AppDelegate: NSObject, UIApplicationDelegate { final class AppDelegate: NSObject, UIApplicationDelegate {
+2
View File
@@ -40,6 +40,8 @@
<key>NSAllowsLocalNetworking</key> <key>NSAllowsLocalNetworking</key>
<true/> <true/>
</dict> </dict>
<key>NSLocalNetworkUsageDescription</key>
<string>Flixy benötigt Zugriff auf das lokale Netzwerk, um deinen Medienserver zu finden und Filme direkt von ihm zu laden.</string>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>
<array> <array>
<string>audio</string> <string>audio</string>
+42 -41
View File
@@ -1,10 +1,6 @@
{ {
"sourceLanguage" : "en", "sourceLanguage" : "en",
"strings" : { "strings" : {
"%@ GB": {
"comment": "A picker option in the settings view that lets the user select the maximum amount of storage space that can be used for downloaded content. The value is given in gigabytes.",
"isCommentAutoGenerated": true
},
"%@ → %@" : { "%@ → %@" : {
"comment" : "A line in a diagnostic trace that shows a network request path and its result. The first argument is the network request path. The second argument is the network response result.", "comment" : "A line in a diagnostic trace that shows a network request path and its result. The first argument is the network request path. The second argument is the network response result.",
"isCommentAutoGenerated" : true, "isCommentAutoGenerated" : true,
@@ -17,8 +13,8 @@
} }
} }
}, },
"1080p": { "%@ GB" : {
"comment": "A label for a 1080p video.", "comment" : "A picker option in the settings view that lets the user select the maximum amount of storage space that can be used for downloaded content. The value is given in gigabytes.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
}, },
"4K" : { "4K" : {
@@ -29,8 +25,8 @@
"comment" : "A label for a 720p video.", "comment" : "A label for a 720p video.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
}, },
"SD": { "1080p" : {
"comment": "A label for a 720p movie.", "comment" : "A label for a 1080p video.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
}, },
"account.hint" : { "account.hint" : {
@@ -3212,23 +3208,6 @@
} }
} }
}, },
"player.subtitleTracks": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Untertitel"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Subtitles"
}
}
}
},
"player.subtitlesOff" : { "player.subtitlesOff" : {
"comment" : "Eintrag, der die Untertitel abschaltet", "comment" : "Eintrag, der die Untertitel abschaltet",
"extractionState" : "stale", "extractionState" : "stale",
@@ -3247,7 +3226,25 @@
} }
} }
}, },
"player.subtitleTracks" : {
"extractionState" : "stale",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Untertitel"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Subtitles"
}
}
}
},
"player.tracks" : { "player.tracks" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"de" : { "de" : {
"stringUnit" : { "stringUnit" : {
@@ -3329,22 +3326,6 @@
} }
} }
}, },
"quality.autoShort": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Auto"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Auto"
}
}
}
},
"quality.automatic" : { "quality.automatic" : {
"extractionState" : "stale", "extractionState" : "stale",
"localizations" : { "localizations" : {
@@ -3362,6 +3343,22 @@
} }
} }
}, },
"quality.autoShort" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Auto"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Auto"
}
}
}
},
"quality.current" : { "quality.current" : {
"localizations" : { "localizations" : {
"de" : { "de" : {
@@ -3685,6 +3682,10 @@
} }
} }
}, },
"SD" : {
"comment" : "A label for a 720p movie.",
"isCommentAutoGenerated" : true
},
"search.hint" : { "search.hint" : {
"localizations" : { "localizations" : {
"de" : { "de" : {
+11 -14
View File
@@ -191,7 +191,7 @@ struct RootView: View {
) { ) {
mediaStack { LibraryView(library: library) } mediaStack { LibraryView(library: library) }
} label: { } label: {
tabLabel(library.title, symbol: symbol(for: library.kind)) sidebarLabel(Text(library.title), symbol: symbol(for: library.kind))
} }
.customizationID("flixy.tab.library.\(library.id)") .customizationID("flixy.tab.library.\(library.id)")
} }
@@ -292,22 +292,19 @@ struct RootView: View {
#endif #endif
/// Die Beschriftung eines Eintrags. /// Die Beschriftung eines Eintrags.
///
/// **Ein** Aufruf, nicht zwei. Es gab hier eine zweite Fassung für Titel,
/// die vom Server kommen — mit `String` statt `LocalizedStringKey`. Swift
/// darf sich bei einem Literal für beide entscheiden, und es entschied sich
/// nicht überall gleich: in der Seitenleiste stand plötzlich
/// „sidebar.downloads" statt „Downloads". Wer einen fertigen Text hat,
/// baut sein `Text` jetzt selbst.
private func tabLabel(_ title: LocalizedStringKey, symbol: String) -> some View { private func tabLabel(_ title: LocalizedStringKey, symbol: String) -> some View {
let label = Label(title, systemImage: symbol) sidebarLabel(Text(title), symbol: symbol)
#if os(macOS)
return label.frame(
minWidth: Self.sidebarWidth.min - 40,
maxWidth: Self.sidebarWidth.max - 40,
alignment: .leading
)
#else
return label
#endif
} }
/// Dieselbe Beschriftung, wenn der Titel erst vom Server kommt. private func sidebarLabel(_ title: Text, symbol: String) -> some View {
private func tabLabel(_ title: String, symbol: String) -> some View { let label = Label { title } icon: { Image(systemName: symbol) }
let label = Label(title, systemImage: symbol)
#if os(macOS) #if os(macOS)
return label.frame( return label.frame(
minWidth: Self.sidebarWidth.min - 40, minWidth: Self.sidebarWidth.min - 40,
+4
View File
@@ -201,6 +201,7 @@ targets:
CODE_SIGN_IDENTITY: "-" CODE_SIGN_IDENTITY: "-"
DEVELOPMENT_TEAM: "" DEVELOPMENT_TEAM: ""
ENABLE_HARDENED_RUNTIME: NO ENABLE_HARDENED_RUNTIME: NO
CODE_SIGN_ENTITLEMENTS: Flixy/Flixy-macCatalyst.entitlements
info: info:
path: Flixy/Info-macOS.plist path: Flixy/Info-macOS.plist
properties: properties:
@@ -222,6 +223,9 @@ targets:
ITSAppUsesNonExemptEncryption: false ITSAppUsesNonExemptEncryption: false
NSAppTransportSecurity: NSAppTransportSecurity:
NSAllowsLocalNetworking: true NSAllowsLocalNetworking: true
NSLocalNetworkUsageDescription: >-
Flixy benötigt Zugriff auf das lokale Netzwerk, um deinen
Medienserver zu finden und Filme direkt von ihm zu laden.
DownloadWidget: DownloadWidget:
type: app-extension type: app-extension