passepartout-apple/Passepartout/Library/Sources/CommonUtils/Views/GenericCreditsView.swift

245 lines
6.5 KiB
Swift
Raw Permalink Normal View History

2022-04-12 13:09:14 +00:00
//
// GenericCreditsView.swift
// Passepartout
//
// Created by Davide De Rosa on 2/27/22.
2024-01-14 13:34:21 +00:00
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
2022-04-12 13:09:14 +00:00
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import SwiftUI
2024-09-23 13:02:26 +00:00
public struct Credits: Decodable {
public struct License: Decodable {
public let name: String
2024-09-23 13:02:26 +00:00
public let licenseName: String
2024-09-23 13:02:26 +00:00
public let licenseURL: URL
}
2024-09-23 13:02:26 +00:00
public struct Notice: Decodable {
public let name: String
2024-09-23 13:02:26 +00:00
public let message: String
}
2024-09-23 13:02:26 +00:00
public let author: String
2024-09-23 13:02:26 +00:00
public let licenses: [License]
2024-09-23 13:02:26 +00:00
public let notices: [Notice]
public let translations: [String: [String]]
}
2024-09-23 13:02:26 +00:00
public struct GenericCreditsView: View {
private let credits: Credits
2023-03-17 20:55:47 +00:00
2024-09-23 13:02:26 +00:00
private var licensesHeader: String?
2023-03-19 15:10:40 +00:00
2024-09-23 13:02:26 +00:00
private var noticesHeader: String?
2023-03-19 15:10:40 +00:00
2024-09-23 13:02:26 +00:00
private var translationsHeader: String?
2023-03-19 15:10:40 +00:00
2024-09-23 13:02:26 +00:00
private let errorDescription: (Error) -> String
2023-03-19 15:10:40 +00:00
2024-09-23 13:02:26 +00:00
@State
private var contentForLicense: [String: String] = [:]
2023-03-19 15:10:40 +00:00
2024-09-23 13:02:26 +00:00
public init(
credits: Credits,
licensesHeader: String? = nil,
noticesHeader: String? = nil,
translationsHeader: String? = nil,
errorDescription: @escaping (Error) -> String
) {
self.credits = credits
self.licensesHeader = licensesHeader
self.noticesHeader = noticesHeader
self.translationsHeader = translationsHeader
self.errorDescription = errorDescription
}
public var body: some View {
Form {
if !credits.licenses.isEmpty {
licensesSection
}
if !credits.notices.isEmpty {
noticesSection
}
if !credits.translations.isEmpty {
translationsSection
}
2023-03-19 15:10:40 +00:00
}
}
}
2023-03-17 20:55:47 +00:00
private extension GenericCreditsView {
struct LicenseView: View {
let url: URL
2023-03-17 20:55:47 +00:00
2024-09-23 13:02:26 +00:00
let errorDescription: (Error) -> String
@Binding
var content: String?
2023-03-17 20:55:47 +00:00
var body: some View {
ZStack {
content.map { unwrapped in
ScrollView {
Text(unwrapped)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.monospaced()
.padding()
}
}
if content == nil {
ProgressView()
2024-09-23 13:02:26 +00:00
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
2024-09-23 13:02:26 +00:00
}
.onAppear(perform: loadURL)
2022-04-12 13:09:14 +00:00
}
}
}
// MARK: -
2023-03-17 20:55:47 +00:00
private extension GenericCreditsView {
2024-09-23 13:02:26 +00:00
var sortedLicenses: [Credits.License] {
credits.licenses.sorted {
2023-03-19 15:10:40 +00:00
$0.name.lowercased() < $1.name.lowercased()
2022-04-12 13:09:14 +00:00
}
}
2023-03-17 20:55:47 +00:00
2024-09-23 13:02:26 +00:00
var sortedNotices: [Credits.Notice] {
credits.notices.sorted {
2023-03-19 15:10:40 +00:00
$0.name.lowercased() < $1.name.lowercased()
2022-04-12 13:09:14 +00:00
}
}
2023-03-17 20:55:47 +00:00
var sortedLanguages: [String] {
2024-09-23 13:02:26 +00:00
credits.translations.keys.sorted {
($0.localizedAsLanguageCode ?? $0) < ($1.localizedAsLanguageCode ?? $1)
2022-04-12 13:09:14 +00:00
}
}
2023-03-17 20:55:47 +00:00
var licensesSection: some View {
2024-09-23 13:02:26 +00:00
Section {
2023-03-19 15:10:40 +00:00
ForEach(sortedLicenses, id: \.name) { license in
2022-04-12 13:09:14 +00:00
NavigationLink {
LicenseView(
2023-03-19 15:10:40 +00:00
url: license.licenseURL,
2024-09-23 13:02:26 +00:00
errorDescription: errorDescription,
2023-03-19 15:10:40 +00:00
content: $contentForLicense[license.name]
2024-09-23 13:02:26 +00:00
)
.navigationTitle(license.name)
2022-04-12 13:09:14 +00:00
} label: {
HStack {
2023-03-19 15:10:40 +00:00
Text(license.name)
2022-04-12 13:09:14 +00:00
Spacer()
2023-03-19 15:10:40 +00:00
Text(license.licenseName)
2022-04-12 13:09:14 +00:00
}
}
}
2024-09-23 13:02:26 +00:00
} header: {
licensesHeader.map(Text.init)
2022-04-12 13:09:14 +00:00
}
}
var noticesSection: some View {
2024-09-23 13:02:26 +00:00
Section {
2023-03-19 15:10:40 +00:00
ForEach(sortedNotices, id: \.name) { notice in
NavigationLink(notice.name, destination: noticeView(notice))
2022-04-12 13:09:14 +00:00
}
2024-09-23 13:02:26 +00:00
} header: {
noticesHeader.map(Text.init)
2022-04-12 13:09:14 +00:00
}
}
var translationsSection: some View {
2024-09-23 13:02:26 +00:00
Section {
2022-04-12 13:09:14 +00:00
ForEach(sortedLanguages, id: \.self) { code in
#if os(tvOS)
Button {
//
} label: {
translationLabel(code)
2022-04-12 13:09:14 +00:00
}
#else
translationLabel(code)
#endif
2022-04-12 13:09:14 +00:00
}
2024-09-23 13:02:26 +00:00
} header: {
translationsHeader.map(Text.init)
2022-04-12 13:09:14 +00:00
}
}
2023-03-17 20:55:47 +00:00
2024-09-23 13:02:26 +00:00
func noticeView(_ content: Credits.Notice) -> some View {
2022-04-12 13:09:14 +00:00
VStack {
2024-09-23 13:02:26 +00:00
Text(content.message)
2022-04-12 13:09:14 +00:00
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.padding()
2024-09-23 13:02:26 +00:00
}
.navigationTitle(content.name)
2022-04-12 13:09:14 +00:00
}
func translationLabel(_ code: String) -> some View {
HStack {
Text(code.localizedAsLanguageCode ?? code)
Spacer()
credits.translations[code].map { authors in
VStack(spacing: 4) {
ForEach(authors, id: \.self) {
Text($0)
.frame(maxWidth: .infinity, alignment: .trailing)
}
}
}
}
}
2022-04-12 13:09:14 +00:00
}
// MARK: -
2023-03-17 20:55:47 +00:00
@MainActor
private extension GenericCreditsView.LicenseView {
func loadURL() {
guard content == nil else {
return
2022-04-12 13:09:14 +00:00
}
Task {
2024-09-23 13:02:26 +00:00
do {
let session = URLSession(configuration: .ephemeral)
let response = try await session.data(from: url)
let string = String(data: response.0, encoding: .utf8)
2024-09-23 13:02:26 +00:00
withAnimation {
content = string
}
} catch {
withAnimation {
content = errorDescription(error)
2022-04-12 13:09:14 +00:00
}
}
}
}
}