From 45665138b2c5baf788dc06791c421a4415660719 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Mon, 6 Feb 2023 14:35:43 -0600 Subject: [PATCH] =?UTF-8?q?Adds=20=E2=80=9CContributors=E2=80=9D=20section?= =?UTF-8?q?=20to=20Credits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lists everyone who has contributed to Delta in some way besides the core team, as well as what they contributed. Moves Grant Gliner and Chris Rittenhouse to Contributors from main Credits. --- Delta.xcodeproj/project.pbxproj | 20 ++ Delta/Base.lproj/Settings.storyboard | 16 ++ Delta/Settings/Contributors/Contributor.swift | 45 ++++ .../Contributors/ContributorsView.swift | 200 ++++++++++++++++++ Delta/Settings/SettingsViewController.swift | 42 +++- Resources/Contributors.plist | 70 ++++++ 6 files changed, 389 insertions(+), 4 deletions(-) create mode 100644 Delta/Settings/Contributors/Contributor.swift create mode 100644 Delta/Settings/Contributors/ContributorsView.swift create mode 100644 Resources/Contributors.plist diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index 9210c9a..79be371 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -170,11 +170,14 @@ D524F4A3273DE9C000D500B2 /* ProcessInfo+JIT.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */; }; D524F4A5273DEBB400D500B2 /* ServerManager+Delta.swift in Sources */ = {isa = PBXBuildFile; fileRef = D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */; }; D5011C48281B6E8B00A0760B /* CharacterSet+Filename.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */; }; + D53415A5298C782A00FD67B1 /* ContributorsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D53415A4298C782A00FD67B1 /* ContributorsView.swift */; }; + D53415A7298C78BC00FD67B1 /* Contributors.plist in Resources */ = {isa = PBXBuildFile; fileRef = D53415A6298C78BC00FD67B1 /* Contributors.plist */; }; D5864970297734280081477E /* CheatMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586496F297734280081477E /* CheatMetadata.swift */; }; D586497229774ABD0081477E /* CheatBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D586497129774ABD0081477E /* CheatBase.swift */; }; D5864978297756CE0081477E /* CheatBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5864977297756CE0081477E /* CheatBaseView.swift */; }; D5A98CE2284EF14B00E023E5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A98CE1284EF14B00E023E5 /* SceneDelegate.swift */; }; D5AAF27729884F8600F21ACF /* CheatDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AAF27629884F8600F21ACF /* CheatDevice.swift */; }; + D5D797E6298D946200738869 /* Contributor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D797E5298D946200738869 /* Contributor.swift */; }; D5F82FB82981D3AC00B229AF /* LegacySearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F82FB72981D3AC00B229AF /* LegacySearchBar.swift */; }; /* End PBXBuildFile section */ @@ -373,11 +376,14 @@ D524F4A2273DE9C000D500B2 /* ProcessInfo+JIT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+JIT.swift"; sourceTree = ""; }; D524F4A4273DEBB400D500B2 /* ServerManager+Delta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ServerManager+Delta.swift"; sourceTree = ""; }; D5011C47281B6E8B00A0760B /* CharacterSet+Filename.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CharacterSet+Filename.swift"; sourceTree = ""; }; + D53415A4298C782A00FD67B1 /* ContributorsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContributorsView.swift; sourceTree = ""; }; + D53415A6298C78BC00FD67B1 /* Contributors.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Contributors.plist; sourceTree = ""; }; D586496F297734280081477E /* CheatMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatMetadata.swift; sourceTree = ""; }; D586497129774ABD0081477E /* CheatBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatBase.swift; sourceTree = ""; }; D5864977297756CE0081477E /* CheatBaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatBaseView.swift; sourceTree = ""; }; D5A98CE1284EF14B00E023E5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; D5AAF27629884F8600F21ACF /* CheatDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatDevice.swift; sourceTree = ""; }; + D5D797E5298D946200738869 /* Contributor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contributor.swift; sourceTree = ""; }; D5F82FB72981D3AC00B229AF /* LegacySearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacySearchBar.swift; sourceTree = ""; }; DC866E433B3BA9AE18ABA1EC /* libPods-Delta.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Delta.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -744,6 +750,7 @@ BF11734E1DA32CEC00047DF8 /* Controllers */, BF1DAD5B1D9F574900E752A7 /* Controller Skins */, BF48F74C219A16C100BC2FC1 /* Syncing */, + D5D797E4298D944C00738869 /* Contributors */, ); path = Settings; sourceTree = ""; @@ -795,6 +802,7 @@ BF6BB2451BB73FE800CCF94A /* Assets.xcassets */, BF02D5D91DDEBB3000A5E131 /* openvgdb.sqlite */, D5050A1B2989A84200ABE08D /* cheatbase.sqlite */, + D53415A6298C78BC00FD67B1 /* Contributors.plist */, ); path = Resources; sourceTree = ""; @@ -917,6 +925,15 @@ path = Cheats; sourceTree = ""; }; + D5D797E4298D944C00738869 /* Contributors */ = { + isa = PBXGroup; + children = ( + D5D797E5298D946200738869 /* Contributor.swift */, + D53415A4298C782A00FD67B1 /* ContributorsView.swift */, + ); + path = Contributors; + sourceTree = ""; + }; FD1E8AE87FA2DB8793F7B937 /* Pods */ = { isa = PBXGroup; children = ( @@ -1023,6 +1040,7 @@ BF3540001C5DA3C500C1184C /* PausePresentationControllerContentView.xib in Resources */, BF5E7F461B9A652600AE44F8 /* Settings.storyboard in Resources */, BF02D5DA1DDEBB3000A5E131 /* openvgdb.sqlite in Resources */, + D53415A7298C78BC00FD67B1 /* Contributors.plist in Resources */, BF71CF8A1FE904B1001F1613 /* GameTableViewCell.xib in Resources */, BFFC46461D59861000AF2CC6 /* LaunchScreen.storyboard in Resources */, D5050A1C2989A84200ABE08D /* cheatbase.sqlite in Resources */, @@ -1183,6 +1201,7 @@ BF5942951E09BD1A0051894B /* NSManagedObjectContext+Conveniences.swift in Sources */, BFC3628021ADE2BA00EF2BE6 /* UIAlertController+Error.swift in Sources */, BF353FF91C5D870B00C1184C /* MenuItem.swift in Sources */, + D5D797E6298D946200738869 /* Contributor.swift in Sources */, BFDB3418219E4B1700595A62 /* SyncStatusViewController.swift in Sources */, BF18B61F1E2985F900F70067 /* UIAlertController+Importing.swift in Sources */, D586497229774ABD0081477E /* CheatBase.swift in Sources */, @@ -1193,6 +1212,7 @@ BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */, BF6424851F5CBDC900D6AB44 /* UIView+ParentViewController.swift in Sources */, BF04E6FF1DB8625C000F35D3 /* ControllerSkinsViewController.swift in Sources */, + D53415A5298C782A00FD67B1 /* ContributorsView.swift in Sources */, BF5942891E09BC8B0051894B /* _GameCollection.swift in Sources */, BF34FA111CF1899D006624C7 /* CheatTextView.swift in Sources */, D524F4A5273DEBB400D500B2 /* ServerManager+Delta.swift in Sources */, diff --git a/Delta/Base.lproj/Settings.storyboard b/Delta/Base.lproj/Settings.storyboard index 36583b0..ded0fc2 100644 --- a/Delta/Base.lproj/Settings.storyboard +++ b/Delta/Base.lproj/Settings.storyboard @@ -643,6 +643,22 @@ + + + + + + + + + + + diff --git a/Delta/Settings/Contributors/Contributor.swift b/Delta/Settings/Contributors/Contributor.swift new file mode 100644 index 0000000..f94c388 --- /dev/null +++ b/Delta/Settings/Contributors/Contributor.swift @@ -0,0 +1,45 @@ +// +// Contributor.swift +// Delta +// +// Created by Riley Testut on 2/3/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import Foundation + +struct Contributor: Identifiable, Decodable +{ + var name: String + + var id: String { + // Use names as identifiers for now. + return self.name + } + + var url: URL? { + guard let link = self.link, let url = URL(string: link) else { return nil } + return url + } + private var link: String? + + var linkName: String? + + var contributions: [Contribution] +} + +struct Contribution: Identifiable, Decodable +{ + var name: String + + var id: String { + // Use names as identifiers for now. + return self.name + } + + var url: URL? { + guard let link = self.link, let url = URL(string: link) else { return nil } + return url + } + private var link: String? +} diff --git a/Delta/Settings/Contributors/ContributorsView.swift b/Delta/Settings/Contributors/ContributorsView.swift new file mode 100644 index 0000000..13b28d8 --- /dev/null +++ b/Delta/Settings/Contributors/ContributorsView.swift @@ -0,0 +1,200 @@ +// +// ContributionsView.swift +// Delta +// +// Created by Riley Testut on 2/2/23. +// Copyright © 2023 Riley Testut. All rights reserved. +// + +import SwiftUI +import SafariServices + +@available(iOS 14, *) +private extension NavigationLink where Label == EmptyView, Destination == EmptyView +{ + // Copied from https://stackoverflow.com/a/66891173 + static var empty: NavigationLink { + self.init(destination: EmptyView(), label: { EmptyView() }) + } +} + +@available(iOS 14, *) +extension ContributorsView +{ + fileprivate class ViewModel: ObservableObject + { + @Published + var contributors: [Contributor]? + + @Published + var error: Error? + + @Published + var webViewURL: URL? + + weak var hostingController: UIViewController? + + func loadContributors() + { + guard self.contributors == nil else { return } + + do + { + let fileURL = Bundle.main.url(forResource: "Contributors", withExtension: "plist")! + let data = try Data(contentsOf: fileURL) + + let contributors = try PropertyListDecoder().decode([Contributor].self, from: data) + self.contributors = contributors + } + catch + { + self.error = error + } + } + } + + static func makeViewController() -> UIHostingController + { + let viewModel = ViewModel() + let contributorsView = ContributorsView(viewModel: viewModel) + + let hostingController = UIHostingController(rootView: contributorsView) + hostingController.title = NSLocalizedString("Contributors", comment: "") + + viewModel.hostingController = hostingController + + return hostingController + } +} + +@available(iOS 14, *) +struct ContributorsView: View +{ + @StateObject + private var viewModel: ViewModel + + @State + private var showErrorAlert: Bool = false + + var body: some View { + List { + Section(content: {}, footer: { + Text("These individuals have contributed to the open-source Delta project on GitHub.\n\nThank you to all our contributors, your help is much appreciated 💜") + .font(.subheadline) + }) + + ForEach(viewModel.contributors ?? []) { contributor in + Section { + // First row = contributor + ContributionCell(name: Text(contributor.name).bold(), url: contributor.url, linkName: contributor.linkName) { webViewURL in + viewModel.webViewURL = webViewURL + } + + // Remaining rows = contributions + ForEach(contributor.contributions) { contribution in + ContributionCell(name: Text(contribution.name), url: contribution.url) { webViewURL in + viewModel.webViewURL = webViewURL + } + } + } + } + } + .listStyle(.grouped) // TODO: Change to .insetGrouped once we drop iOS 13 support. + .environmentObject(viewModel) + .alert(isPresented: $showErrorAlert) { + Alert(title: Text("Unable to Load Contributors"), message: Text(viewModel.error?.localizedDescription ?? ""), dismissButton: .default(Text("OK")) { + guard let hostingController = viewModel.hostingController else { return } + hostingController.navigationController?.popViewController(animated: true) + }) + } + .onReceive(viewModel.$error) { error in + guard error != nil else { return } + showErrorAlert = true + } + .onReceive(viewModel.$webViewURL) { webViewURL in + guard let webViewURL else { return } + openURL(webViewURL) + } + .onAppear { + viewModel.loadContributors() + } + } + + fileprivate init(contributors: [Contributor]? = nil, viewModel: ViewModel = ViewModel()) + { + if let contributors + { + // Don't overwrite passed-in viewModel.contributors if contributors is nil. + viewModel.contributors = contributors + } + + self._viewModel = StateObject(wrappedValue: viewModel) + } +} + +@available(iOS 14, *) +struct ContributionCell: View +{ + var name: Text + var url: URL? + var linkName: String? + + var action: (URL) -> Void + + var body: some View { + + let body = Button { + guard let url else { return } + + Task { @MainActor in + // Dispatch Task to avoid "Publishing changes from within view updates is not allowed, this will cause undefined behavior." runtime error on iOS 16. + self.action(url) + } + + } label: { + HStack { + self.name + .font(.system(size: 17)) // Match Settings screen + + Spacer() + + if let linkName + { + Text(linkName) + .font(.system(size: 17)) // Match Settings screen + .foregroundColor(.gray) + } + + if url != nil + { + NavigationLink.empty + .fixedSize() + } + } + } + .accentColor(.primary) + + if url != nil + { + body + } + else + { + // No URL to open, so disable cell highlighting. + body.buttonStyle(.plain) + } + } +} + +@available(iOS 14, *) +private extension ContributorsView +{ + func openURL(_ url: URL) + { + guard let hostingController = viewModel.hostingController else { return } + + let safariViewController = SFSafariViewController(url: url) + safariViewController.preferredControlTintColor = .deltaPurple + hostingController.present(safariViewController, animated: true) + } +} diff --git a/Delta/Settings/SettingsViewController.swift b/Delta/Settings/SettingsViewController.swift index 73c43d6..6a34705 100644 --- a/Delta/Settings/SettingsViewController.swift +++ b/Delta/Settings/SettingsViewController.swift @@ -49,6 +49,7 @@ private extension SettingsViewController case caroline case grant case litRitt + case contributors case softwareLicenses } } @@ -279,6 +280,13 @@ private extension SettingsViewController } } } + + @available(iOS 14, *) + func showContributors() + { + let hostingController = ContributorsView.makeViewController() + self.navigationController?.pushViewController(hostingController, animated: true) + } } private extension SettingsViewController @@ -418,6 +426,10 @@ extension SettingsViewController case .caroline: self.openTwitter(username: "1carolinemoore") case .grant: self.openTwitter(username: "grantgliner") case .litRitt: self.openTwitter(username: "litritt_z") + case .contributors: + guard #available(iOS 14, *) else { return } + self.showContributors() + case .softwareLicenses: break } } @@ -425,13 +437,35 @@ extension SettingsViewController override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + primary: switch Section(rawValue: indexPath.section)! { - #if !BETA - case .credits where indexPath.row == CreditsRow.litRitt.rawValue: return 0.0 - #endif - default: return super.tableView(tableView, heightForRowAt: indexPath) + case .credits: + let row = CreditsRow(rawValue: indexPath.row)! + switch row + { + case .grant: + // Hide row on iOS 14 and above + guard #available(iOS 14, *) else { break primary } + return 0.0 + + case .litRitt: + // Hide row on iOS 14 and above + guard #available(iOS 14, *) else { break primary } + return 0.0 + + case .contributors: + // Hide row on iOS 13 and below + guard #unavailable(iOS 14) else { break primary } + return 0.0 + + default: break + } + + default: break } + + return super.tableView(tableView, heightForRowAt: indexPath) } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? diff --git a/Resources/Contributors.plist b/Resources/Contributors.plist new file mode 100644 index 0000000..e35ff2b --- /dev/null +++ b/Resources/Contributors.plist @@ -0,0 +1,70 @@ + + + + + + name + Grant Gliner + link + https://twitter.com/GrantGliner + linkName + @GrantGliner + contributions + + + name + Pause Menu Icons + + + + + name + Eric Lewis + link + https://github.com/ericlewis + linkName + github.com/ericlewis + contributions + + + name + Dark Mode + link + https://github.com/rileytestut/Delta/pull/82 + + + + + name + Chris Rittenhouse + link + https://github.com/LitRitt + linkName + github.com/LitRitt + contributions + + + name + Genesis Skin + + + + + name + Noah Keck + link + https://github.com/noah978 + linkName + github.com/noah978 + contributions + + + name + CheatBase + link + https://github.com/CheatBase/CheatBase + + + + +