mirror of
https://github.com/narrrl/proton-stream.git
synced 2026-11-03 16:41:26 +01:00
A SwiftUI app that drives the same UniFFI engine as Android: Rust owns the Proton sessions, SQLite and every decrypted byte, and Swift asks it questions off the main thread. Playback goes through MPVKit's libmpv, with the pstr:// stream protocol ported from the Android JNI half, and secrets live in the Keychain. scripts/build-ios.sh builds the static library for aarch64-apple-ios, writes the Swift bindings (module PstrBridge), generates the Xcode project with XcodeGen and packages an unsigned .ipa for SideStore or AltStore, which re-sign it with the viewer's own Apple ID. The generated project, Info.plist and build output are ignored.
50 lines
1.6 KiB
Swift
50 lines
1.6 KiB
Swift
import AVFoundation
|
|
import SwiftUI
|
|
|
|
@main
|
|
struct ProtonStreamApp: App {
|
|
@State private var model = AppModel()
|
|
|
|
init() {
|
|
// `.playback` keeps the audio going with the screen locked or the app
|
|
// in the background, which `UIBackgroundModes: audio` then permits.
|
|
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
|
|
}
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
RootView()
|
|
.environment(model)
|
|
.preferredColorScheme(.dark)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct RootView: View {
|
|
@Environment(AppModel.self) private var model
|
|
|
|
var body: some View {
|
|
if let failure = model.startupError {
|
|
ContentUnavailableView("Could not start", systemImage: "exclamationmark.triangle", description: Text(failure))
|
|
} else {
|
|
TabView {
|
|
NavigationStack { LibraryView() }
|
|
.tabItem { Label("Library", systemImage: "play.rectangle.on.rectangle") }
|
|
NavigationStack { SharesView() }
|
|
.tabItem { Label("Shares", systemImage: "link") }
|
|
NavigationStack { SettingsView() }
|
|
.tabItem { Label("Settings", systemImage: "gearshape") }
|
|
}
|
|
.task { await model.reload() }
|
|
.alert("Something went wrong", isPresented: .init(
|
|
get: { model.error != nil },
|
|
set: { if !$0 { model.error = nil } }
|
|
)) {
|
|
Button("OK", role: .cancel) {}
|
|
} message: {
|
|
Text(model.error ?? "")
|
|
}
|
|
}
|
|
}
|
|
}
|