feat(clients): in-app OSS / third-party-license screens
Surface THIRD-PARTY-NOTICES.txt in every GUI client (the desktop packages already
ship it as a file; this adds the on-glass screen):
- Linux: Preferences -> About -> Third-party licenses (adw::AboutDialog with the app
license + Legal sections; include_str! the root notices).
- Apple: macOS About tab / iOS+tvOS Acknowledgements link; notices bundled as
PunktfunkKit SPM resources, read via Bundle.module (the Xcode app links the SPM
product, so they ride along - no .pbxproj edit).
- Android: Settings -> About -> Open-source licenses (reads the bundled asset).
- (Windows landed earlier in d1d2ca2: Settings -> About -> Third-party licenses.)
gen-third-party-notices.sh now copies the generated file into the Apple Resources/
and Android assets/ trees so the in-tree copies never drift.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import PunktfunkKit
|
||||
import SwiftUI
|
||||
|
||||
/// Open-source acknowledgements: punktfunk's own license (MIT OR Apache-2.0) followed by the
|
||||
/// third-party software notices. Used as a pushed view on iOS/tvOS and a preferences tab on macOS.
|
||||
struct AcknowledgementsView: View {
|
||||
private var version: String? {
|
||||
Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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.thirdPartyNotices)
|
||||
.font(.caption2.monospaced())
|
||||
.modifier(SelectableText())
|
||||
}
|
||||
.frame(maxWidth: 900, alignment: .leading)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
#if os(tvOS)
|
||||
.padding(40)
|
||||
#endif
|
||||
}
|
||||
.navigationTitle("Acknowledgements")
|
||||
}
|
||||
}
|
||||
|
||||
/// `textSelection(.enabled)` is unavailable on tvOS, so apply it only where it exists.
|
||||
private struct SelectableText: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
#if os(tvOS)
|
||||
content
|
||||
#else
|
||||
content.textSelection(.enabled)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,9 @@ struct SettingsView: View {
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.tabItem { Label("Advanced", systemImage: "slider.horizontal.3") }
|
||||
|
||||
AcknowledgementsView()
|
||||
.tabItem { Label("About", systemImage: "info.circle") }
|
||||
}
|
||||
.frame(width: 480, height: 460)
|
||||
}
|
||||
@@ -115,6 +118,9 @@ struct SettingsView: View {
|
||||
statisticsSection
|
||||
experimentalSection
|
||||
controllersSection
|
||||
Section {
|
||||
NavigationLink("Acknowledgements") { AcknowledgementsView() }
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.onAppear {
|
||||
@@ -217,6 +223,8 @@ struct SettingsView: View {
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.top, 8)
|
||||
NavigationLink("Acknowledgements") { AcknowledgementsView() }
|
||||
.padding(.top, 8)
|
||||
}
|
||||
.frame(maxWidth: 1000)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Reference in New Issue
Block a user