From d73feea7da20e2f25667e0525d97fb98b079e39f Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 27 Nov 2024 17:50:14 -0500 Subject: [PATCH 01/11] #1570: Fix warnings and previews in Xcode 16 --- CHANGELOG.md | 3 + Nos.xcodeproj/project.pbxproj | 20 ++--- .../xcshareddata/xcschemes/Nos.xcscheme | 2 +- .../xcschemes/NosDiagnostics.xcscheme | 2 +- .../xcschemes/NosLocalization.xcscheme | 2 +- .../xcschemes/NosPerformance Tests.xcscheme | 2 +- .../xcschemes/NosProduction.xcscheme | 2 +- .../xcschemes/NosStaging.xcscheme | 2 +- .../xcshareddata/xcschemes/NosTests.xcscheme | 2 +- .../xcschemes/NosUITests.xcscheme | 2 +- Nos/AppDelegate.swift | 1 + Nos/Controller/RawEventController.swift | 2 +- Nos/Extensions/Foundation+Sendable.swift | 3 +- Nos/Models/CoreData/Event+CoreDataClass.swift | 2 +- Nos/Models/JSONEvent.swift | 7 +- Nos/Models/KeyPair.swift | 4 +- .../OpenGraph/SoupOpenGraphParser.swift | 6 +- Nos/Models/RawNostrID.swift | 2 +- Nos/Service/CurrentUser+PublishEvents.swift | 5 +- Nos/Service/CurrentUser.swift | 8 +- Nos/Service/DependencyInjection.swift | 8 +- Nos/Service/PushNotificationRegistrar.swift | 5 +- Nos/Service/Relay/RelayService.swift | 8 +- .../Relay/RelaySubscriptionManager.swift | 4 +- Nos/Service/ReportPublisher.swift | 5 +- Nos/Views/Components/Author/AuthorCard.swift | 2 +- Nos/Views/Components/KnownFollowersView.swift | 2 +- Nos/Views/Components/NosNavigationStack.swift | 2 +- Nos/Views/Components/NosToggle.swift | 2 +- Nos/Views/Components/NoteTextEditor.swift | 7 +- Nos/Views/Components/PagedNoteListView.swift | 10 +-- Nos/Views/Components/RelayPicker.swift | 14 ++- Nos/Views/Components/WarningView.swift | 2 +- Nos/Views/Discover/DiscoverContentsView.swift | 2 +- Nos/Views/Fixtures/PreviewData.swift | 88 +++++++++---------- Nos/Views/Home/HomeFeedView.swift | 4 +- Nos/Views/Note/NoteCard.swift | 2 +- .../NoteComposer/ComposerActionBar.swift | 2 +- Nos/Views/NoteComposer/NoteComposer.swift | 2 +- Nos/Views/NoteComposer/ReplyPreview.swift | 2 +- .../Notifications/NotificationCard.swift | 13 ++- .../Notifications/NotificationsView.swift | 2 +- .../Onboarding/Components/CopyKeyView.swift | 8 +- Nos/Views/Onboarding/DisplayNameView.swift | 2 +- Nos/Views/Onboarding/UsernameView.swift | 2 +- Nos/Views/Profile/ActivityPubBadgeView.swift | 2 +- Nos/Views/Profile/BioSheet.swift | 2 +- .../ConfirmUsernameDeletionSheet.swift | 2 +- .../DeleteUsernameWizard.swift | 2 +- Nos/Views/Profile/ProfileHeader.swift | 19 ++-- Nos/Views/Profile/ProfileView.swift | 2 +- .../ContentWarningControllerTests.swift | 2 +- NosTests/Models/CoreData/EventTests.swift | 4 +- 53 files changed, 165 insertions(+), 149 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59a5e6a65..c8b39eea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed galleries expanding past the width of the screen when there are lots of links or images. [#24](https://github.com/verse-pbc/issues/issues/24) - Fix quoted note composer does not expand to fit mention. [#25](https://github.com/verse-pbc/issues/issues/25) +### Internal Changes +- Upgraded to Xcode 16. [#1570](https://github.com/planetary-social/nos/issues/1570) + ## [1.0.2] - 2024-11-26Z ### Release Notes diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index 680d715e8..e48dc0dff 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -132,6 +132,8 @@ 03ED93472C46C48400C8D443 /* JSONEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03ED93462C46C48400C8D443 /* JSONEventTests.swift */; }; 03F7C4F32C10DF79006FF613 /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F7C4F22C10DF79006FF613 /* URLSessionProtocol.swift */; }; 03F7C4F42C10E05B006FF613 /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F7C4F22C10DF79006FF613 /* URLSessionProtocol.swift */; }; + 03F866692CF7D39900527C39 /* Foundation+Sendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F866682CF7D39900527C39 /* Foundation+Sendable.swift */; }; + 03F8666A2CF7D39900527C39 /* Foundation+Sendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03F866682CF7D39900527C39 /* Foundation+Sendable.swift */; }; 03FE3F792C87A9D900D25810 /* EventError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FE3F782C87A9D900D25810 /* EventError.swift */; }; 03FE3F7A2C87A9DC00D25810 /* EventError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FE3F782C87A9D900D25810 /* EventError.swift */; }; 03FE3F7C2C87AC9900D25810 /* Event+InlineMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FE3F7B2C87AC9900D25810 /* Event+InlineMetadata.swift */; }; @@ -467,8 +469,6 @@ C9B5C78E2C24AF650070445B /* MockRelaySubscriptionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0320C1142BFE63DC00C4C080 /* MockRelaySubscriptionManager.swift */; }; C9B678DB29EEBF3B00303F33 /* DependencyInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B678DA29EEBF3B00303F33 /* DependencyInjection.swift */; }; C9B678DC29EEBF3B00303F33 /* DependencyInjection.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B678DA29EEBF3B00303F33 /* DependencyInjection.swift */; }; - C9B678DE29EEC35B00303F33 /* Foundation+Sendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B678DD29EEC35B00303F33 /* Foundation+Sendable.swift */; }; - C9B678DF29EEC35B00303F33 /* Foundation+Sendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B678DD29EEC35B00303F33 /* Foundation+Sendable.swift */; }; C9B678E129EEC41000303F33 /* SocialGraphCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B678E029EEC41000303F33 /* SocialGraphCache.swift */; }; C9B678E229EEC41000303F33 /* SocialGraphCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B678E029EEC41000303F33 /* SocialGraphCache.swift */; }; C9B678E729F01A8500303F33 /* FullscreenProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B678E629F01A8500303F33 /* FullscreenProgressView.swift */; }; @@ -693,6 +693,7 @@ 03EB47062CB080110004FF35 /* BrokenLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenLinkView.swift; sourceTree = ""; }; 03ED93462C46C48400C8D443 /* JSONEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONEventTests.swift; sourceTree = ""; }; 03F7C4F22C10DF79006FF613 /* URLSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionProtocol.swift; sourceTree = ""; }; + 03F866682CF7D39900527C39 /* Foundation+Sendable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Sendable.swift"; sourceTree = ""; }; 03FE3F782C87A9D900D25810 /* EventError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventError.swift; sourceTree = ""; }; 03FE3F7B2C87AC9900D25810 /* Event+InlineMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Event+InlineMetadata.swift"; sourceTree = ""; }; 03FE3F8A2C87BC9500D25810 /* text_note_multiple_media.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = text_note_multiple_media.json; sourceTree = ""; }; @@ -943,7 +944,6 @@ C9ADB14029951CB10075E7F8 /* NSManagedObject+Nos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Nos.swift"; sourceTree = ""; }; C9B597642BBC8300002EC76A /* ImagePickerUIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagePickerUIViewController.swift; sourceTree = ""; }; C9B678DA29EEBF3B00303F33 /* DependencyInjection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyInjection.swift; sourceTree = ""; }; - C9B678DD29EEC35B00303F33 /* Foundation+Sendable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Sendable.swift"; sourceTree = ""; }; C9B678E029EEC41000303F33 /* SocialGraphCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialGraphCache.swift; sourceTree = ""; }; C9B678E629F01A8500303F33 /* FullscreenProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenProgressView.swift; sourceTree = ""; }; C9B708BA2A13BE41006C613A /* NoteTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteTextEditor.swift; sourceTree = ""; }; @@ -1872,7 +1872,7 @@ C9F84C1B298DBBF400C6714D /* Data+Sha.swift */, C942566829B66A2800C4202C /* Date+Elapsed.swift */, C987F85729BA981800B44E7A /* Font+Clarity.swift */, - C9B678DD29EEC35B00303F33 /* Foundation+Sendable.swift */, + 03F866682CF7D39900527C39 /* Foundation+Sendable.swift */, C9F0BB6829A5039D000547FC /* Int+Bool.swift */, C9ADB14029951CB10075E7F8 /* NSManagedObject+Nos.swift */, C97A1C8D29E58EC7009D9E8D /* NSManagedObjectContext+Nos.swift */, @@ -1880,6 +1880,7 @@ C93EC2F329C34C860012EE2A /* NSPredicate+Bool.swift */, 50E2EB712C86175900D4B360 /* NSRegularExpression+Replacement.swift */, C93EC2F629C351470012EE2A /* Optional+Unwrap.swift */, + 045EDD042CAC025700B67964 /* ScrollViewProxy+Animate.swift */, C99721CA2AEBED26004EBEAB /* String+Empty.swift */, C9ADB13729928CC30075E7F8 /* String+Hex.swift */, C9DEC002298945150078B43A /* String+Lorem.swift */, @@ -1887,7 +1888,6 @@ DC2E54C72A700F1400C2CAAB /* UIDevice+Simulator.swift */, C92DF80429C25DE900400561 /* URL+Extensions.swift */, C9C2B77B29E072E400548B4A /* WebSocket+Nos.swift */, - 045EDD042CAC025700B67964 /* ScrollViewProxy+Animate.swift */, ); path = Extensions; sourceTree = ""; @@ -2121,7 +2121,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1530; + LastUpgradeCheck = 1610; TargetAttributes = { C90862BA29E9804B00C35A71 = { CreatedOnToolsVersion = 14.2; @@ -2333,7 +2333,6 @@ 50089A0C2C97182200834588 /* CurrentUser+PublishEvents.swift in Sources */, C97A1C8E29E58EC7009D9E8D /* NSManagedObjectContext+Nos.swift in Sources */, 5BBA5E9C2BAE052F00D57D76 /* NiceWorkSheet.swift in Sources */, - C9B678DE29EEC35B00303F33 /* Foundation+Sendable.swift in Sources */, 5B88051A2A21027C00E21F06 /* SHA256Key.swift in Sources */, C9B71DC22A9003670031ED9F /* CrashReporting.swift in Sources */, C987F81729BA4C6A00B44E7A /* BigActionButton.swift in Sources */, @@ -2451,6 +2450,7 @@ C9F84C27298DC98800C6714D /* KeyPair.swift in Sources */, 5B8C96B629DDD3B200B73AEC /* NoteUITextViewRepresentable.swift in Sources */, C93EC2F129C337EB0012EE2A /* RelayPicker.swift in Sources */, + 03F866692CF7D39900527C39 /* Foundation+Sendable.swift in Sources */, 5BBA5E912BADF98E00D57D76 /* AlreadyHaveANIP05View.swift in Sources */, C9F0BB6F29A50437000547FC /* NostrIdentifierPrefix.swift in Sources */, C96D39272B61B6D200D3D0A1 /* RawNostrID.swift in Sources */, @@ -2690,7 +2690,6 @@ 03E711672C935114000B6F96 /* SoupOpenGraphParserTests.swift in Sources */, C9C5475C2A4F1D8C006B0741 /* NosNotification+CoreDataProperties.swift in Sources */, 03C49AC32C938DE100502321 /* SoupOpenGraphParser.swift in Sources */, - C9B678DF29EEC35B00303F33 /* Foundation+Sendable.swift in Sources */, 03EB470C2CB080180004FF35 /* BrokenLinkView.swift in Sources */, A3B943D7299D6DB700A15A08 /* Follow+CoreDataClass.swift in Sources */, C9ADB13E29929EEF0075E7F8 /* Bech32.swift in Sources */, @@ -2748,6 +2747,7 @@ C9DEC06F2989668E0078B43A /* Relay+CoreDataClass.swift in Sources */, C9ADB13F29929F1F0075E7F8 /* String+Hex.swift in Sources */, 500899F32C95C1F900834588 /* PushNotificationRegistrar.swift in Sources */, + 03F8666A2CF7D39900527C39 /* Foundation+Sendable.swift in Sources */, C973AB622A323167002AED16 /* Author+CoreDataProperties.swift in Sources */, C93EC2F529C34C860012EE2A /* NSPredicate+Bool.swift in Sources */, C9DEC05B298950A90078B43A /* String+Lorem.swift in Sources */, @@ -2947,7 +2947,6 @@ 5B7888CD2B5A0FB800B6761F /* Staging */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO; CODE_SIGN_STYLE = Automatic; @@ -3121,7 +3120,6 @@ 5BE460732BAB3028004B83ED /* Dev */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO; CODE_SIGN_STYLE = Automatic; @@ -3449,7 +3447,6 @@ C9DEBFFC298941020078B43A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO; CODE_SIGN_STYLE = Automatic; @@ -3479,7 +3476,6 @@ C9DEBFFD298941020078B43A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO; CODE_SIGN_STYLE = Automatic; diff --git a/Nos.xcodeproj/xcshareddata/xcschemes/Nos.xcscheme b/Nos.xcodeproj/xcshareddata/xcschemes/Nos.xcscheme index 69d8b3ad3..0726952d7 100644 --- a/Nos.xcodeproj/xcshareddata/xcschemes/Nos.xcscheme +++ b/Nos.xcodeproj/xcshareddata/xcschemes/Nos.xcscheme @@ -1,6 +1,6 @@ String? { let encoder = JSONEncoder() let data = try encoder.encode(self) - return String(decoding: data, as: UTF8.self) + return String(data: data, encoding: .utf8) } mutating func sign(withKey privateKey: KeyPair) throws { @@ -164,7 +164,10 @@ struct JSONEvent: Codable, Hashable, VerifiableEvent { func buildPublishRequest() throws -> String { let request: [Any] = ["EVENT", dictionary] let requestData = try JSONSerialization.data(withJSONObject: request) - return String(decoding: requestData, as: UTF8.self) + guard let requestString = String(data: requestData, encoding: .utf8) else { + throw RelayError.parseError + } + return requestString } } diff --git a/Nos/Models/KeyPair.swift b/Nos/Models/KeyPair.swift index b53fafc8d..c298c7bf1 100644 --- a/Nos/Models/KeyPair.swift +++ b/Nos/Models/KeyPair.swift @@ -116,9 +116,9 @@ extension KeyPair: RawRepresentable { } public var rawValue: String { - guard let data = try? JSONEncoder().encode(self) else { + guard let data = try? JSONEncoder().encode(self), let string = String(data: data, encoding: .utf8) else { return "{}" } - return String(decoding: data, as: UTF8.self) + return string } } diff --git a/Nos/Models/OpenGraph/SoupOpenGraphParser.swift b/Nos/Models/OpenGraph/SoupOpenGraphParser.swift index 280ce0888..2a9ebdff8 100644 --- a/Nos/Models/OpenGraph/SoupOpenGraphParser.swift +++ b/Nos/Models/OpenGraph/SoupOpenGraphParser.swift @@ -4,8 +4,10 @@ import SwiftSoup /// Parses the Open Graph metadata from an HTML document using SwiftSoup. struct SoupOpenGraphParser: OpenGraphParser { func metadata(html: Data) -> OpenGraphMetadata? { - let htmlString = String(decoding: html, as: UTF8.self) - guard let document = try? SwiftSoup.parse(htmlString) else { return nil } + guard + let htmlString = String(data: html, encoding: .utf8), + let document = try? SwiftSoup.parse(htmlString) + else { return nil } let title = stringValue(.title, from: document) let type = typeMetadata(from: document) diff --git a/Nos/Models/RawNostrID.swift b/Nos/Models/RawNostrID.swift index 63391f9da..a2d979640 100644 --- a/Nos/Models/RawNostrID.swift +++ b/Nos/Models/RawNostrID.swift @@ -20,7 +20,7 @@ public typealias RawEventID = RawNostrID /// An alias for a RawNostrID that we know is for an Author. See docs for `RawNostrID`. public typealias RawAuthorID = RawNostrID -extension RawNostrID: Identifiable { +extension RawNostrID { public var id: String { self diff --git a/Nos/Service/CurrentUser+PublishEvents.swift b/Nos/Service/CurrentUser+PublishEvents.swift index e24268695..f513966b9 100644 --- a/Nos/Service/CurrentUser+PublishEvents.swift +++ b/Nos/Service/CurrentUser+PublishEvents.swift @@ -61,7 +61,10 @@ extension CurrentUser { let jsonObject = buildMetadataJSONObject(author: author) let data = try JSONSerialization.data(withJSONObject: jsonObject) - let content = String(decoding: data, as: UTF8.self) + guard let content = String(data: data, encoding: .utf8) else { + Log.error("Error: Couldn't convert data to string") + throw CurrentUserError.errorWhilePublishingToRelays + } let jsonEvent = JSONEvent( pubKey: pubKey, diff --git a/Nos/Service/CurrentUser.swift b/Nos/Service/CurrentUser.swift index 4ff0ffd07..d3e37f7e8 100644 --- a/Nos/Service/CurrentUser.swift +++ b/Nos/Service/CurrentUser.swift @@ -83,7 +83,7 @@ import Dependencies self.socialGraph = SocialGraphCache(userKey: nil, context: persistenceController.newBackgroundContext()) if let privateKeyData = keychain.load(key: keychain.keychainPrivateKey) { Log.info("CurrentUser loaded a private key from keychain") - let hexString = String(decoding: privateKeyData, as: UTF8.self) + let hexString = String(data: privateKeyData, encoding: .utf8) _privateKeyHex = hexString setUp() if let keyPair { @@ -286,8 +286,10 @@ import Dependencies // MARK: - NSFetchedResultsControllerDelegate - @MainActor func controllerDidChangeContent(_ controller: NSFetchedResultsController) { - author = controller.fetchedObjects?.first as? Author + func controllerDidChangeContent(_ controller: NSFetchedResultsController) { + Task { @MainActor in + author = controller.fetchedObjects?.first as? Author + } } } // swiftlint:enable type_body_length diff --git a/Nos/Service/DependencyInjection.swift b/Nos/Service/DependencyInjection.swift index 0d6c37979..5cef90ab2 100644 --- a/Nos/Service/DependencyInjection.swift +++ b/Nos/Service/DependencyInjection.swift @@ -102,7 +102,7 @@ fileprivate enum AnalyticsKey: DependencyKey { static let previewValue = Analytics(mock: true) } -fileprivate enum CurrentUserKey: DependencyKey { +fileprivate enum CurrentUserKey: @preconcurrency DependencyKey { @MainActor static let liveValue = CurrentUser() @MainActor static let testValue = CurrentUser() @MainActor static let previewValue = CurrentUser() @@ -112,7 +112,7 @@ fileprivate enum FileStorageAPIClientKey: DependencyKey { static var liveValue: any FileStorageAPIClient = NostrBuildAPIClient() } -fileprivate enum RouterKey: DependencyKey { +fileprivate enum RouterKey: @preconcurrency DependencyKey { @MainActor static let liveValue = Router() @MainActor static let testValue = Router() @MainActor static let previewValue = Router() @@ -124,7 +124,7 @@ private enum RelayServiceKey: DependencyKey { static let previewValue: RelayService = MockRelayService() } -fileprivate enum PushNotificationServiceKey: DependencyKey { +fileprivate enum PushNotificationServiceKey: @preconcurrency DependencyKey { @MainActor static let liveValue = PushNotificationService() @MainActor static let testValue: PushNotificationService = MockPushNotificationService() @MainActor static let previewValue: PushNotificationService = MockPushNotificationService() @@ -177,7 +177,7 @@ fileprivate enum FeatureFlagsKey: DependencyKey { static let previewValue: any FeatureFlags = MockFeatureFlags() } -fileprivate enum KeychainKey: DependencyKey { +fileprivate enum KeychainKey: @preconcurrency DependencyKey { @MainActor static let liveValue: Keychain = SystemKeychain() @MainActor static let testValue: Keychain = InMemoryKeychain() @MainActor static let previewValue: Keychain = InMemoryKeychain() diff --git a/Nos/Service/PushNotificationRegistrar.swift b/Nos/Service/PushNotificationRegistrar.swift index a730dda0a..f15d639d8 100644 --- a/Nos/Service/PushNotificationRegistrar.swift +++ b/Nos/Service/PushNotificationRegistrar.swift @@ -93,7 +93,10 @@ final class PushNotificationRegistrar { publicKey: publicKeyHex, relays: relays ) - return String(decoding: try JSONEncoder().encode(content), as: UTF8.self) + guard let string = String(data: try JSONEncoder().encode(content), encoding: .utf8) else { + throw PushNotificationError.unexpected + } + return string } } diff --git a/Nos/Service/Relay/RelayService.swift b/Nos/Service/Relay/RelayService.swift index 56bdfd07e..2d218841d 100644 --- a/Nos/Service/Relay/RelayService.swift +++ b/Nos/Service/Relay/RelayService.swift @@ -113,7 +113,9 @@ extension RelayService { await subscriptionManager.forceCloseSubscriptionCount(for: subscriptionID) let request: [Any] = ["CLOSE", subscriptionID] let requestData = try JSONSerialization.data(withJSONObject: request) - let requestString = String(decoding: requestData, as: UTF8.self) + guard let requestString = String(data: requestData, encoding: .utf8) else { + throw RelayError.parseError + } client.write(string: requestString) } catch { Log.error("Error: Could not send close \(error.localizedDescription)") @@ -511,7 +513,9 @@ extension RelayService { let request: [Any] = ["AUTH", jsonEvent.dictionary] let requestData = try JSONSerialization.data(withJSONObject: request) - let string = String(decoding: requestData, as: UTF8.self) + guard let string = String(data: requestData, encoding: .utf8) else { + throw RelayError.parseError + } socket.write(string: string) } catch { Log.error("Error authenticating with \(relayAddress)", error.localizedDescription) diff --git a/Nos/Service/Relay/RelaySubscriptionManager.swift b/Nos/Service/Relay/RelaySubscriptionManager.swift index 5cfe64843..64aa3849f 100644 --- a/Nos/Service/Relay/RelaySubscriptionManager.swift +++ b/Nos/Service/Relay/RelaySubscriptionManager.swift @@ -308,7 +308,9 @@ actor RelaySubscriptionManagerActor: RelaySubscriptionManager { // Track this so we can close requests if needed let request: [Any] = ["REQ", subscription.id, subscription.filter.dictionary] let requestData = try JSONSerialization.data(withJSONObject: request) - let requestString = String(decoding: requestData, as: UTF8.self) + guard let requestString = String(data: requestData, encoding: .utf8) else { + throw RelayError.parseError + } socket.write(string: requestString) } catch { Log.error("Error: Could not send request \(error.localizedDescription)") diff --git a/Nos/Service/ReportPublisher.swift b/Nos/Service/ReportPublisher.swift index 09a430bb5..b4c8f10a7 100644 --- a/Nos/Service/ReportPublisher.swift +++ b/Nos/Service/ReportPublisher.swift @@ -145,6 +145,9 @@ struct ReportRequest { } let data = try JSONSerialization.data(withJSONObject: dictionary) - return String(decoding: data, as: UTF8.self) + guard let string = String(data: data, encoding: .utf8) else { + throw ReportError.encodingFailed("Could not convert JSON data to a String") + } + return string } } diff --git a/Nos/Views/Components/Author/AuthorCard.swift b/Nos/Views/Components/Author/AuthorCard.swift index 22c1c94ae..3004c9e9a 100644 --- a/Nos/Views/Components/Author/AuthorCard.swift +++ b/Nos/Views/Components/Author/AuthorCard.swift @@ -93,7 +93,7 @@ struct AuthorCard: View { } #Preview { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() return VStack { AuthorCard(author: previewData.alice) diff --git a/Nos/Views/Components/KnownFollowersView.swift b/Nos/Views/Components/KnownFollowersView.swift index b10a9f778..2690bdfaf 100644 --- a/Nos/Views/Components/KnownFollowersView.swift +++ b/Nos/Views/Components/KnownFollowersView.swift @@ -94,7 +94,7 @@ struct KnownFollowersView: View { } #Preview { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() return VStack { KnownFollowersView(source: previewData.currentUser.author, destination: previewData.alice) diff --git a/Nos/Views/Components/NosNavigationStack.swift b/Nos/Views/Components/NosNavigationStack.swift index baf3c4082..f23771b39 100644 --- a/Nos/Views/Components/NosNavigationStack.swift +++ b/Nos/Views/Components/NosNavigationStack.swift @@ -44,7 +44,7 @@ struct NosNavigationStack: View { } #Preview { - @State var path = NavigationPath() + @Previewable @State var path = NavigationPath() return NosNavigationStack(path: $path) { Text("hello world") diff --git a/Nos/Views/Components/NosToggle.swift b/Nos/Views/Components/NosToggle.swift index 71c1b5c9c..e17eb274d 100644 --- a/Nos/Views/Components/NosToggle.swift +++ b/Nos/Views/Components/NosToggle.swift @@ -24,7 +24,7 @@ struct NosToggle: View { } #Preview { - @State var isOn = true + @Previewable @State var isOn = true return NosToggle( "useReportsFromFollows", diff --git a/Nos/Views/Components/NoteTextEditor.swift b/Nos/Views/Components/NoteTextEditor.swift index 4c6716244..dee356d75 100644 --- a/Nos/Views/Components/NoteTextEditor.swift +++ b/Nos/Views/Components/NoteTextEditor.swift @@ -50,10 +50,9 @@ struct NoteTextEditor: View { } #Preview { - - var previewData = PreviewData() - @State var controller = NoteEditorController() - + @Previewable @State var controller = NoteEditorController() + @Previewable @State var previewData = PreviewData() + return NavigationStack { NoteTextEditor( controller: $controller, diff --git a/Nos/Views/Components/PagedNoteListView.swift b/Nos/Views/Components/PagedNoteListView.swift index 6dac7b442..1eb085b88 100644 --- a/Nos/Views/Components/PagedNoteListView.swift +++ b/Nos/Views/Components/PagedNoteListView.swift @@ -238,15 +238,15 @@ extension Notification.Name { } #Preview { - var previewData = PreviewData() - @State var refreshController = RefreshController() - + @Previewable @State var refreshController = RefreshController() + @Previewable @State var previewData = PreviewData() + return PagedNoteListView( refreshController: $refreshController, databaseFilter: previewData.alice.allPostsRequest(onlyRootPosts: false), relayFilter: Filter(), relay: nil, - managedObjectContext: previewData.previewContext, + managedObjectContext: previewData.context, tab: .home, header: { ProfileHeader(author: previewData.alice, selectedTab: .constant(.activity)) @@ -262,7 +262,7 @@ extension Notification.Name { .inject(previewData: previewData) .onAppear { for i in 0..<100 { - let note = Event(context: previewData.previewContext) + let note = Event(context: previewData.context) note.identifier = "ProfileNotesView-\(i)" note.kind = EventKind.text.rawValue note.content = "\(i)" diff --git a/Nos/Views/Components/RelayPicker.swift b/Nos/Views/Components/RelayPicker.swift index f24b7e413..f01bb5abc 100644 --- a/Nos/Views/Components/RelayPicker.swift +++ b/Nos/Views/Components/RelayPicker.swift @@ -145,15 +145,14 @@ struct RelayPickerRow: View { } #Preview("Without ScrollView") { + @Previewable @State var selectedRelay: Relay? + @Previewable @State var previewData = PreviewData() - @State var selectedRelay: Relay? - var previewData = PreviewData() - func createTestData() { let user = previewData.alice let addresses = ["wss://nostr.com", "wss://nos.social", "wss://alongdomainnametoseewhathappens.com"] addresses.forEach { address in - let relay = try? Relay.findOrCreate(by: address, context: previewData.previewContext) + let relay = try? Relay.findOrCreate(by: address, context: previewData.context) relay?.relayDescription = "A Nostr relay that aims to cultivate a healthy community." relay?.addToAuthors(user) } @@ -171,15 +170,14 @@ struct RelayPickerRow: View { } #Preview("With ScrollView") { - - @State var selectedRelay: Relay? - var previewData = PreviewData() + @Previewable @State var selectedRelay: Relay? + @Previewable @State var previewData = PreviewData() func createTestData() { let user = previewData.alice let addresses = Relay.allKnown addresses.forEach { address in - let relay = try? Relay.findOrCreate(by: address, context: previewData.previewContext) + let relay = try? Relay.findOrCreate(by: address, context: previewData.context) relay?.relayDescription = "A Nostr relay that aims to cultivate a healthy community." relay?.addToAuthors(user) } diff --git a/Nos/Views/Components/WarningView.swift b/Nos/Views/Components/WarningView.swift index 408750076..0e19e590b 100644 --- a/Nos/Views/Components/WarningView.swift +++ b/Nos/Views/Components/WarningView.swift @@ -165,7 +165,7 @@ struct ContentWarningMessage: View { } #Preview { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() return VStack { ContentWarningMessage(reports: [previewData.shortNoteReportOne], type: .author) diff --git a/Nos/Views/Discover/DiscoverContentsView.swift b/Nos/Views/Discover/DiscoverContentsView.swift index ff17274ac..a0c49c434 100644 --- a/Nos/Views/Discover/DiscoverContentsView.swift +++ b/Nos/Views/Discover/DiscoverContentsView.swift @@ -82,7 +82,7 @@ struct DiscoverContentsView: View { LazyVStack { categoryPicker - ForEach(featuredAuthorIDs) { authorID in + ForEach(featuredAuthorIDs, id: \.self) { authorID in AuthorObservationView(authorID: authorID) { author in VStack { if author.lastUpdatedMetadata != nil { diff --git a/Nos/Views/Fixtures/PreviewData.swift b/Nos/Views/Fixtures/PreviewData.swift index 9e1de7760..036034473 100644 --- a/Nos/Views/Fixtures/PreviewData.swift +++ b/Nos/Views/Fixtures/PreviewData.swift @@ -27,7 +27,7 @@ struct PreviewData { @Dependency(\.relayService) var relayService @Dependency(\.currentUser) var currentUser - lazy var previewContext: NSManagedObjectContext = { + lazy var context: NSManagedObjectContext = { persistenceController.viewContext }() @@ -38,7 +38,7 @@ struct PreviewData { }() lazy var alice: Author = { - let author = try! Author.findOrCreate(by: KeyFixture.alice.publicKeyHex, context: previewContext) + let author = try! Author.findOrCreate(by: KeyFixture.alice.publicKeyHex, context: context) author.name = "Alice" author.nip05 = "alice@nos.social" author.profilePhotoURL = URL(string: "https://github.com/planetary-social/nos/assets/1165004/07f83f00-4555-4db3-85fc-f1a05b1908a2") @@ -49,7 +49,7 @@ struct PreviewData { }() lazy var bob: Author = { - let author = try! Author.findOrCreate(by: KeyFixture.bob.publicKeyHex, context: previewContext) + let author = try! Author.findOrCreate(by: KeyFixture.bob.publicKeyHex, context: context) author.hexadecimalPublicKey = KeyFixture.bob.publicKeyHex author.name = "Bob" author.profilePhotoURL = URL(string: "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.r1ZOH5E3M6WiK6aw5GRdlAHaEK%26pid%3DApi&f=1&ipt=42ae9de7730da3bda152c5980cd64b14ccef37d8f55b8791e41b4667fc38ddf1&ipo=images") @@ -58,7 +58,7 @@ struct PreviewData { }() lazy var eve: Author = { - let author = try! Author.findOrCreate(by: KeyFixture.eve.publicKeyHex, context: previewContext) + let author = try! Author.findOrCreate(by: KeyFixture.eve.publicKeyHex, context: context) author.name = "Eve" author.nip05 = "eve@nos.social" @@ -66,7 +66,7 @@ struct PreviewData { }() lazy var blockedUser: Author = { - let author = try! Author.findOrCreate(by: KeyFixture.eve.publicKeyHex, context: previewContext) + let author = try! Author.findOrCreate(by: KeyFixture.eve.publicKeyHex, context: context) author.name = "Sam" author.about = "I am a terrible person" author.muted = true @@ -78,18 +78,18 @@ struct PreviewData { // MARK: - Notes lazy var shortNote: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "1" note.kind = EventKind.text.rawValue note.content = "Hello, world!" note.author = previewAuthor note.createdAt = .now - try? previewContext.save() + try? context.save() return note }() lazy var expiringNote: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "10" note.kind = EventKind.text.rawValue note.content = "Hello, world!" @@ -101,56 +101,56 @@ struct PreviewData { dateComponents.day = 1 let tomorrow = calendar.date(byAdding: dateComponents, to: currentDate)! note.expirationDate = tomorrow - try? previewContext.save() + try? context.save() return note }() lazy var imageNote: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "2" note.kind = EventKind.text.rawValue note.content = "Hello, world!https://cdn.ymaws.com/nacfm.com/resource/resmgr/images/blog_photos/footprints.jpg" note.author = previewAuthor note.createdAt = .now - try? previewContext.save() + try? context.save() return note }() lazy var verticalImageNote: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "3" note.kind = EventKind.text.rawValue note.content = "Hello, world!https://nostr.build/i/nostr.build_1b958a2af7a2c3fcb2758dd5743912e697ba34d3a6199bfb1300fa6be1dc62ee.jpeg" note.author = previewAuthor note.createdAt = .now - try? previewContext.save() + try? context.save() return note }() lazy var veryWideImageNote: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "4" note.kind = EventKind.text.rawValue note.content = "Hello, world! https://nostr.build/i/nostr.build_db8287dde9aedbc65df59972386fde14edf9e1afc210e80c764706e61cd1cdfa.png" note.author = previewAuthor note.createdAt = .now - try? previewContext.save() + try? context.save() return note }() lazy var longNote: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "5" note.kind = EventKind.text.rawValue note.createdAt = .now note.content = .loremIpsum(5) note.author = previewAuthor - try? previewContext.save() + try? context.save() return note }() lazy var longFormNote: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "6" note.createdAt = .now note.kind = EventKind.longFormContent.rawValue @@ -164,12 +164,12 @@ struct PreviewData { And it has a link to [nos.social](https://nos.social). """ note.author = previewAuthor - try? previewContext.save() + try? context.save() return note }() lazy var doubleImageNote: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "7" note.kind = EventKind.text.rawValue note.content = """ @@ -182,12 +182,12 @@ struct PreviewData { """ note.author = previewAuthor note.createdAt = .now - try? previewContext.save() + try? context.save() return note }() lazy var linkNote: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "8" note.kind = EventKind.text.rawValue note.content = """ @@ -209,17 +209,17 @@ struct PreviewData { """ note.author = previewAuthor note.createdAt = .now - try? previewContext.save() + try? context.save() return note }() lazy var repost: Event = { - let originalPostAuthor = Author(context: previewContext) + let originalPostAuthor = Author(context: context) originalPostAuthor.hexadecimalPublicKey = KeyFixture.bob.publicKeyHex originalPostAuthor.name = "Bob" originalPostAuthor.profilePhotoURL = URL(string: "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.r1ZOH5E3M6WiK6aw5GRdlAHaEK%26pid%3DApi&f=1&ipt=42ae9de7730da3bda152c5980cd64b14ccef37d8f55b8791e41b4667fc38ddf1&ipo=images") - let repostedNote = Event(context: previewContext) + let repostedNote = Event(context: context) repostedNote.identifier = "3" repostedNote.kind = EventKind.text.rawValue repostedNote.createdAt = .now @@ -228,81 +228,81 @@ struct PreviewData { let reference: EventReference do { - reference = try EventReference(jsonTag: ["e", "3", ""], context: previewContext) + reference = try EventReference(jsonTag: ["e", "3", ""], context: context) } catch { print(error) - reference = EventReference(context: previewContext) + reference = EventReference(context: context) } - let repost = Event(context: previewContext) + let repost = Event(context: context) repost.identifier = "4" repost.kind = EventKind.repost.rawValue repost.createdAt = .now repost.author = previewAuthor repost.eventReferences = NSOrderedSet(array: [reference]) - try? previewContext.save() + try? context.save() return repost }() lazy var reply: Event = { let rootNote = longNote - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "11" note.kind = EventKind.text.rawValue note.content = "Well that's pretty neat" note.author = bob note.createdAt = .now - let rootReference = EventReference(context: previewContext) + let rootReference = EventReference(context: context) rootReference.eventId = rootNote.identifier rootReference.marker = "root" rootReference.referencedEvent = rootNote note.insertIntoEventReferences(rootReference, at: 0) - try? previewContext.save() + try? context.save() return note }() // MARK: Reports lazy var shortNoteReportOne: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "r1" note.kind = EventKind.report.rawValue note.content = "impersonation" note.author = bob note.createdAt = .now - let reference = EventReference(context: previewContext) + let reference = EventReference(context: context) reference.eventId = "1" reference.referencedEvent = shortNote note.insertIntoEventReferences(reference, at: 0) - try? previewContext.save() + try? context.save() return note }() lazy var reportBobTwo: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "r4" note.kind = EventKind.report.rawValue note.content = "harassment" note.author = bob note.createdAt = .now - let reference = EventReference(context: previewContext) + let reference = EventReference(context: context) reference.eventId = "1" reference.referencedEvent = shortNote note.insertIntoEventReferences(reference, at: 0) - try? previewContext.save() + try? context.save() return note }() lazy var shortNoteReportTwo: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "r2" note.kind = EventKind.report.rawValue note.content = "harassment" @@ -312,17 +312,17 @@ struct PreviewData { note.author = eve note.createdAt = .now - let reference = EventReference(context: previewContext) + let reference = EventReference(context: context) reference.eventId = "1" reference.referencedEvent = shortNote note.insertIntoEventReferences(reference, at: 0) - try? previewContext.save() + try? context.save() return note }() lazy var shortNoteReportThree: Event = { - let note = Event(context: previewContext) + let note = Event(context: context) note.identifier = "r3" note.kind = EventKind.report.rawValue note.content = "this person is spammy" @@ -333,12 +333,12 @@ struct PreviewData { note.author = alice note.createdAt = .now - let reference = EventReference(context: previewContext) + let reference = EventReference(context: context) reference.eventId = "1" reference.referencedEvent = shortNote note.insertIntoEventReferences(reference, at: 0) - try? previewContext.save() + try? context.save() return note }() } diff --git a/Nos/Views/Home/HomeFeedView.swift b/Nos/Views/Home/HomeFeedView.swift index dd866310f..d36e73fc1 100644 --- a/Nos/Views/Home/HomeFeedView.swift +++ b/Nos/Views/Home/HomeFeedView.swift @@ -168,13 +168,13 @@ struct HomeFeedView: View { } #Preview { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() func createTestData() { let user = previewData.alice let addresses = Relay.recommended addresses.forEach { address in - let relay = try? Relay.findOrCreate(by: address, context: previewData.previewContext) + let relay = try? Relay.findOrCreate(by: address, context: previewData.context) relay?.relayDescription = "A Nostr relay that aims to cultivate a healthy community." relay?.addToAuthors(user) } diff --git a/Nos/Views/Note/NoteCard.swift b/Nos/Views/Note/NoteCard.swift index 30f844bd3..afb62cf24 100644 --- a/Nos/Views/Note/NoteCard.swift +++ b/Nos/Views/Note/NoteCard.swift @@ -225,7 +225,7 @@ struct NoteCard_Previews: PreviewProvider { } } } - .environment(\.managedObjectContext, previewData.previewContext) + .environment(\.managedObjectContext, previewData.context) .environment(previewData.relayService) .environmentObject(previewData.router) .environment(previewData.currentUser) diff --git a/Nos/Views/NoteComposer/ComposerActionBar.swift b/Nos/Views/NoteComposer/ComposerActionBar.swift index dd9e5da5a..55adedc71 100644 --- a/Nos/Views/NoteComposer/ComposerActionBar.swift +++ b/Nos/Views/NoteComposer/ComposerActionBar.swift @@ -180,7 +180,7 @@ struct ComposerActionBar: View { do { startUploadingImage() let url = try await fileStorageAPIClient.upload(fileAt: imageURL, isProfilePhoto: false) - await editingController.append(url) + editingController.append(url) endUploadingImage() } catch { endUploadingImage() diff --git a/Nos/Views/NoteComposer/NoteComposer.swift b/Nos/Views/NoteComposer/NoteComposer.swift index b94c87ff0..e4013085c 100644 --- a/Nos/Views/NoteComposer/NoteComposer.swift +++ b/Nos/Views/NoteComposer/NoteComposer.swift @@ -399,7 +399,7 @@ struct NoteComposer: View { } #Preview { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() return NoteComposer(replyTo: previewData.longNote, isPresented: .constant(true)) .inject(previewData: previewData) diff --git a/Nos/Views/NoteComposer/ReplyPreview.swift b/Nos/Views/NoteComposer/ReplyPreview.swift index f66c8c551..3278f80af 100644 --- a/Nos/Views/NoteComposer/ReplyPreview.swift +++ b/Nos/Views/NoteComposer/ReplyPreview.swift @@ -38,7 +38,7 @@ struct ReplyPreview: View { } #Preview { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() return ReplyPreview(note: previewData.longNote) .background(Color.appBg) diff --git a/Nos/Views/Notifications/NotificationCard.swift b/Nos/Views/Notifications/NotificationCard.swift index 82ffc2d49..0b1482a08 100644 --- a/Nos/Views/Notifications/NotificationCard.swift +++ b/Nos/Views/Notifications/NotificationCard.swift @@ -92,10 +92,9 @@ struct NotificationCard: View { } #Preview { - var previewData = PreviewData() - - let previewContext = previewData.previewContext - + @Previewable @State var previewData = PreviewData() + let context = previewData.context + var alice: Author { previewData.alice } @@ -105,16 +104,16 @@ struct NotificationCard: View { } let note: Event = { - let mentionNote = Event(context: previewContext) + let mentionNote = Event(context: context) mentionNote.content = "Hello, bob!" mentionNote.kind = 1 mentionNote.createdAt = .now mentionNote.author = alice - let authorRef = AuthorReference(context: previewContext) + let authorRef = AuthorReference(context: context) authorRef.pubkey = bob.hexadecimalPublicKey mentionNote.authorReferences = NSMutableOrderedSet(array: [authorRef]) try? mentionNote.sign(withKey: KeyFixture.alice) - try? previewContext.save() + try? context.save() return mentionNote }() diff --git a/Nos/Views/Notifications/NotificationsView.swift b/Nos/Views/Notifications/NotificationsView.swift index ce962c4a8..296c2cb38 100644 --- a/Nos/Views/Notifications/NotificationsView.swift +++ b/Nos/Views/Notifications/NotificationsView.swift @@ -127,7 +127,7 @@ struct NotificationsView_Previews: PreviewProvider { static var previewData = PreviewData() - static var previewContext = previewData.previewContext + static var previewContext = previewData.context static var alice: Author { previewData.alice diff --git a/Nos/Views/Onboarding/Components/CopyKeyView.swift b/Nos/Views/Onboarding/Components/CopyKeyView.swift index 457ec1296..14700c1fc 100644 --- a/Nos/Views/Onboarding/Components/CopyKeyView.swift +++ b/Nos/Views/Onboarding/Components/CopyKeyView.swift @@ -45,11 +45,11 @@ struct CopyKeyView: View { } #Preview { - @State var privateKey = KeyFixture.nsec - @State var privateCopyButtonState = CopyButtonState.copy + @Previewable @State var privateKey = KeyFixture.nsec + @Previewable @State var privateCopyButtonState = CopyButtonState.copy - @State var publicKey = KeyFixture.npub - @State var publicCopyButtonState = CopyButtonState.copied + @Previewable @State var publicKey = KeyFixture.npub + @Previewable @State var publicCopyButtonState = CopyButtonState.copied return VStack(spacing: 40) { CopyKeyView("copyPrivateKey", keyString: $privateKey, copyButtonState: $privateCopyButtonState) diff --git a/Nos/Views/Onboarding/DisplayNameView.swift b/Nos/Views/Onboarding/DisplayNameView.swift index 92af67a85..52396983c 100644 --- a/Nos/Views/Onboarding/DisplayNameView.swift +++ b/Nos/Views/Onboarding/DisplayNameView.swift @@ -73,7 +73,7 @@ struct DisplayNameView: View { /// Saves the display name locally and publishes the event to relays. Sets `showError` if it fails. func save() async { - guard let author = await currentUser.author else { + guard let author = currentUser.author else { showError = true return } diff --git a/Nos/Views/Onboarding/UsernameView.swift b/Nos/Views/Onboarding/UsernameView.swift index df8235d8d..1b44238cc 100644 --- a/Nos/Views/Onboarding/UsernameView.swift +++ b/Nos/Views/Onboarding/UsernameView.swift @@ -152,7 +152,7 @@ struct UsernameView: View { func save() async { usernameState = .loading - guard let author = await currentUser.author, + guard let author = currentUser.author, let keyPair = currentUser.keyPair else { usernameState = .errorAlert return diff --git a/Nos/Views/Profile/ActivityPubBadgeView.swift b/Nos/Views/Profile/ActivityPubBadgeView.swift index bc17bf278..934fa4582 100644 --- a/Nos/Views/Profile/ActivityPubBadgeView.swift +++ b/Nos/Views/Profile/ActivityPubBadgeView.swift @@ -29,7 +29,7 @@ struct ActivityPubBadgeView: View { } #Preview { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() return VStack { ActivityPubBadgeView(author: previewData.alice) diff --git a/Nos/Views/Profile/BioSheet.swift b/Nos/Views/Profile/BioSheet.swift index 9bc0c31aa..c160dc45e 100644 --- a/Nos/Views/Profile/BioSheet.swift +++ b/Nos/Views/Profile/BioSheet.swift @@ -154,7 +154,7 @@ struct BioSheet: View { } #Preview("BioSheet") { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() return Group { BioSheet(author: previewData.eve) diff --git a/Nos/Views/Profile/Edit/DeleteUsernameWizard/ConfirmUsernameDeletionSheet.swift b/Nos/Views/Profile/Edit/DeleteUsernameWizard/ConfirmUsernameDeletionSheet.swift index 4ba32e58f..10cd3c186 100644 --- a/Nos/Views/Profile/Edit/DeleteUsernameWizard/ConfirmUsernameDeletionSheet.swift +++ b/Nos/Views/Profile/Edit/DeleteUsernameWizard/ConfirmUsernameDeletionSheet.swift @@ -166,7 +166,7 @@ fileprivate enum DeleteError: LocalizedError { } #Preview { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() return Color.clear.sheet(isPresented: .constant(true)) { ConfirmUsernameDeletionSheet( author: previewData.alice, diff --git a/Nos/Views/Profile/Edit/DeleteUsernameWizard/DeleteUsernameWizard.swift b/Nos/Views/Profile/Edit/DeleteUsernameWizard/DeleteUsernameWizard.swift index 52cfe4261..3f8d2d009 100644 --- a/Nos/Views/Profile/Edit/DeleteUsernameWizard/DeleteUsernameWizard.swift +++ b/Nos/Views/Profile/Edit/DeleteUsernameWizard/DeleteUsernameWizard.swift @@ -16,7 +16,7 @@ struct DeleteUsernameWizard: View { } #Preview { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() return Color.clear.sheet(isPresented: .constant(true)) { DeleteUsernameWizard( author: previewData.alice, diff --git a/Nos/Views/Profile/ProfileHeader.swift b/Nos/Views/Profile/ProfileHeader.swift index e916c9f10..92bc28ff7 100644 --- a/Nos/Views/Profile/ProfileHeader.swift +++ b/Nos/Views/Profile/ProfileHeader.swift @@ -278,7 +278,7 @@ struct ProfileHeader: View { } #Preview { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() return Group { // ProfileHeader(author: author) @@ -290,28 +290,28 @@ struct ProfileHeader: View { } #Preview { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() var author: Author { - let previewContext = previewData.previewContext - let author = Author(context: previewContext) + let context = previewData.context + let author = Author(context: context) author.hexadecimalPublicKey = KeyFixture.pubKeyHex - author.add(relay: Relay(context: previewContext)) + author.add(relay: Relay(context: context)) author.name = "Sebastian Heit" author.nip05 = "chardot@nostr.fan" author.about = "Go programmer working on Nos/Planetary. You can find me at various European events related to" + " Chaos Computer Club, the hacker community and free software." - let first = Author(context: previewContext) + let first = Author(context: context) first.name = "Craig Nichols" - let second = Author(context: previewContext) + let second = Author(context: context) second.name = "Justin Pool" - let firstFollow = Follow(context: previewContext) + let firstFollow = Follow(context: context) firstFollow.source = first firstFollow.destination = author - let secondFollow = Follow(context: previewContext) + let secondFollow = Follow(context: context) secondFollow.source = second secondFollow.destination = author @@ -324,7 +324,6 @@ struct ProfileHeader: View { ProfileHeader(author: author, selectedTab: .constant(.activity)) } .inject(previewData: previewData) - .previewDevice("iPhone SE (2nd generation)") .padding() .background(Color.previewBg) } diff --git a/Nos/Views/Profile/ProfileView.swift b/Nos/Views/Profile/ProfileView.swift index 7c836140b..c233b4a99 100644 --- a/Nos/Views/Profile/ProfileView.swift +++ b/Nos/Views/Profile/ProfileView.swift @@ -244,7 +244,7 @@ struct ProfileView: View { } #Preview("Generic user") { - var previewData = PreviewData() + @Previewable @State var previewData = PreviewData() return NavigationStack { ProfileView(author: previewData.previewAuthor) diff --git a/NosTests/Controller/ContentWarningControllerTests.swift b/NosTests/Controller/ContentWarningControllerTests.swift index 36c7e6520..11f7518fc 100644 --- a/NosTests/Controller/ContentWarningControllerTests.swift +++ b/NosTests/Controller/ContentWarningControllerTests.swift @@ -10,7 +10,7 @@ final class ContentWarningControllerTests: CoreDataTestCase { override func setUp() async throws { try await super.setUp() fixture = PreviewData() - fixture.previewContext = testContext + fixture.context = testContext } func test_noReports() throws { diff --git a/NosTests/Models/CoreData/EventTests.swift b/NosTests/Models/CoreData/EventTests.swift index 85fb72a20..178d758c3 100644 --- a/NosTests/Models/CoreData/EventTests.swift +++ b/NosTests/Models/CoreData/EventTests.swift @@ -20,8 +20,8 @@ final class EventTests: CoreDataTestCase { // Act let serializedData = try JSONSerialization.data(withJSONObject: event.serializedEventForSigning) - let actualString = String(decoding: serializedData, as: UTF8.self) - + let actualString = try XCTUnwrap(String(data: serializedData, encoding: .utf8)) + // Assert XCTAssertEqual(actualString, expectedString) } From 74ba9b55d0fdbbe4cee7339680371bb7f54d8c02 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 27 Nov 2024 18:07:53 -0500 Subject: [PATCH 02/11] fix the tab bar issue in iPad builds from Xcode 16 --- Nos/Views/AppView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Nos/Views/AppView.swift b/Nos/Views/AppView.swift index f6ec3abf1..718e7b065 100644 --- a/Nos/Views/AppView.swift +++ b/Nos/Views/AppView.swift @@ -167,6 +167,7 @@ struct AppView: View { .environment(relayService) .interactiveDismissDisabled() } + .environment(\.horizontalSizeClass, .compact) } } From ba155571920fa6903edd48bfae227f095080dcd3 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 27 Nov 2024 18:12:58 -0500 Subject: [PATCH 03/11] upgrade .xcode-version to 16.1 to make it official --- .xcode-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.xcode-version b/.xcode-version index 232a7fc1a..c32b0ec5a 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -15.4 +16.1 From 5347199da5a9a7ba07ac79379db724aa8a3f03a5 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Mon, 2 Dec 2024 16:44:57 -0500 Subject: [PATCH 04/11] update Test & Lint action to use latest versions --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 003c403c0..dc9f75fb7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ on: push: branches: main env: - SIMULATOR: platform=iOS Simulator,name=iPhone 15,OS=17.4 + SIMULATOR: platform=iOS Simulator,name=iPhone 16,OS=18.1 jobs: unit_test: @@ -38,6 +38,6 @@ jobs: steps: - uses: actions/checkout@v1 - name: SwiftLint - uses: docker://norionomura/swiftlint:0.55.1 + uses: docker://norionomura/swiftlint:0.57.1 with: args: swiftlint --strict --reporter github-actions-logging From ba0e4afa5fb4bade58b770b188250c3fbd32a4be Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 3 Dec 2024 17:25:15 -0500 Subject: [PATCH 05/11] refactor to UITabBarController to work around macOS Sequoia issue --- Nos.xcodeproj/project.pbxproj | 4 + .../newPostButton.imageset/Contents.json | 3 + Nos/Models/AppDestination.swift | 14 ++ Nos/Views/AppView.swift | 190 +++++++----------- Nos/Views/TabBarController.swift | 66 ++++++ 5 files changed, 165 insertions(+), 112 deletions(-) create mode 100644 Nos/Views/TabBarController.swift diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index e48dc0dff..2448050ea 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 030036852C5D39DD002C71F5 /* RefreshController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030036842C5D39DD002C71F5 /* RefreshController.swift */; }; 030036942C5D3AD3002C71F5 /* RefreshController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030036842C5D39DD002C71F5 /* RefreshController.swift */; }; 030036AB2C5D872B002C71F5 /* NewNotesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030036AA2C5D872B002C71F5 /* NewNotesButton.swift */; }; + 0301495C2CFFA8B7000A0152 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0301495B2CFFA8B7000A0152 /* TabBarController.swift */; }; 0304D0A72C9B4BF2001D16C7 /* OpenGraphMetatdata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0304D0A62C9B4BF2001D16C7 /* OpenGraphMetatdata.swift */; }; 0304D0A82C9B4BF2001D16C7 /* OpenGraphMetatdata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0304D0A62C9B4BF2001D16C7 /* OpenGraphMetatdata.swift */; }; 0304D0B22C9B731F001D16C7 /* MockOpenGraphService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0304D0B12C9B731F001D16C7 /* MockOpenGraphService.swift */; }; @@ -603,6 +604,7 @@ 030024182CC00DF70073ED56 /* SplashScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenView.swift; sourceTree = ""; }; 030036842C5D39DD002C71F5 /* RefreshController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshController.swift; sourceTree = ""; }; 030036AA2C5D872B002C71F5 /* NewNotesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNotesButton.swift; sourceTree = ""; }; + 0301495B2CFFA8B7000A0152 /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; 0304D0A62C9B4BF2001D16C7 /* OpenGraphMetatdata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGraphMetatdata.swift; sourceTree = ""; }; 0304D0B12C9B731F001D16C7 /* MockOpenGraphService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOpenGraphService.swift; sourceTree = ""; }; 030AE4282BE3D63C004DEE02 /* FeaturedAuthor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedAuthor.swift; sourceTree = ""; }; @@ -2001,6 +2003,7 @@ children = ( C9F84C20298DC36800C6714D /* AppView.swift */, 030024182CC00DF70073ED56 /* SplashScreenView.swift */, + 0301495B2CFFA8B7000A0152 /* TabBarController.swift */, 5B79F6402BA11618002DA9BE /* Components */, 65BD8DC12BDAF2C300802039 /* Discover */, 03618B112C825D8700BCBC55 /* Fixtures */, @@ -2450,6 +2453,7 @@ C9F84C27298DC98800C6714D /* KeyPair.swift in Sources */, 5B8C96B629DDD3B200B73AEC /* NoteUITextViewRepresentable.swift in Sources */, C93EC2F129C337EB0012EE2A /* RelayPicker.swift in Sources */, + 0301495C2CFFA8B7000A0152 /* TabBarController.swift in Sources */, 03F866692CF7D39900527C39 /* Foundation+Sendable.swift in Sources */, 5BBA5E912BADF98E00D57D76 /* AlreadyHaveANIP05View.swift in Sources */, C9F0BB6F29A50437000547FC /* NostrIdentifierPrefix.swift in Sources */, diff --git a/Nos/Assets/Assets.xcassets/newPostButton.imageset/Contents.json b/Nos/Assets/Assets.xcassets/newPostButton.imageset/Contents.json index 3cda4b0eb..9f2c6ffea 100644 --- a/Nos/Assets/Assets.xcassets/newPostButton.imageset/Contents.json +++ b/Nos/Assets/Assets.xcassets/newPostButton.imageset/Contents.json @@ -19,5 +19,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" } } diff --git a/Nos/Models/AppDestination.swift b/Nos/Models/AppDestination.swift index 4483d8f43..21c578f60 100644 --- a/Nos/Models/AppDestination.swift +++ b/Nos/Models/AppDestination.swift @@ -9,6 +9,10 @@ enum AppDestination: Hashable, Equatable { case noteComposer(String?) case profile + static var tabDestinations: [AppDestination] { + [.home, .discover, .noteComposer(nil), .notifications, .profile] + } + var destinationString: String { switch self { case .home: @@ -27,4 +31,14 @@ enum AppDestination: Hashable, Equatable { func hash(into hasher: inout Hasher) { hasher.combine(destinationString) } + + var tabIndex: Int { + switch self { + case .home: return 0 + case .discover: return 1 + case .noteComposer: return 2 + case .notifications: return 3 + case .profile: return 4 + } + } } diff --git a/Nos/Views/AppView.swift b/Nos/Views/AppView.swift index 718e7b065..5bd1d21e0 100644 --- a/Nos/Views/AppView.swift +++ b/Nos/Views/AppView.swift @@ -50,124 +50,90 @@ struct AppView: View { } private var tabView: some View { - TabView(selection: $router.selectedTab) { - if let author = currentUser.author { - HomeTab(user: author) - .tabItem { - VStack { - let text = Text("homeFeed") - if $router.selectedTab.wrappedValue == .home { - Image.tabIconHomeSelected - text - } else { - Image.tabIconHome - text.foregroundColor(.secondaryTxt) - } - } - } - .toolbarBackground(.visible, for: .tabBar) - .toolbarBackground(Color.cardBgBottom, for: .tabBar) - .tag(AppDestination.home) - .onAppear { - // TODO: Move this somewhere better like CurrentUser when it becomes the source of truth - // for who is logged in - if let keyPair = currentUser.keyPair { - analytics.identify( - with: keyPair, - nip05: currentUser.author?.nip05 - ) - crashReporting.identify(with: keyPair) - } - } - } - - DiscoverTab() - .tabItem { - VStack { - let text = Text("discover") - if $router.selectedTab.wrappedValue == .discover { - Image.tabIconEveryoneSelected - text.foregroundColor(.primaryTxt) - } else { - Image.tabIconEveryone - text.foregroundColor(.secondaryTxt) - } - } + TabBarController(selectedIndex: $router.selectedTab, viewControllers: makeViewControllers()) + .onChange(of: router.selectedTab) { _, newTab in + if case let AppDestination.noteComposer(contents) = newTab { + newPostContents = contents + showNewPost = true + router.selectedTab = lastSelectedTab + } else if !showNewPost { + lastSelectedTab = newTab } - .toolbarBackground(.visible, for: .tabBar) - .toolbarBackground(Color.cardBgBottom, for: .tabBar) - .tag(AppDestination.discover) - - VStack {} - .tabItem { - VStack { - Image.newPostButton - Text("post") - } - } - .tag(AppDestination.noteComposer(nil)) - - NotificationsView(user: currentUser.author) - .tabItem { - VStack { - let text = Text("notifications") - if $router.selectedTab.wrappedValue == .notifications { - Image.tabIconNotificationsSelected - text.foregroundColor(.primaryTxt) - } else { - Image.tabIconNotifications - text.foregroundColor(.secondaryTxt) - } + } + .overlay { + if router.isLoading { + ZStack { + Rectangle().fill(.black.opacity(0.4)) + ProgressView() } } - .toolbarBackground(.visible, for: .tabBar) - .toolbarBackground(Color.cardBgBottom, for: .tabBar) - .tag(AppDestination.notifications) - .badge(pushNotificationService.badgeCount) - - if let author = currentUser.author { - ProfileTab(author: author, path: $router.profilePath) - .tabItem { - VStack { - let text = Text("profileTitle") - if $router.selectedTab.wrappedValue == .profile { - Image.tabProfileSelected - text.foregroundColor(.primaryTxt) - } else { - Image.tabProfile - text.foregroundColor(.secondaryTxt) - } - } - } - .toolbarBackground(.visible, for: .tabBar) - .toolbarBackground(Color.cardBgBottom, for: .tabBar) - .tag(AppDestination.profile) } - } - .onChange(of: router.selectedTab) { _, newTab in - if case let AppDestination.noteComposer(contents) = newTab { - newPostContents = contents - showNewPost = true - router.selectedTab = lastSelectedTab - } else if !showNewPost { - lastSelectedTab = newTab + .sheet(isPresented: $showNewPost) { + NoteComposer(initialContents: newPostContents, isPresented: $showNewPost) + .environment(currentUser) + .environment(relayService) + .interactiveDismissDisabled() } - } - .overlay { - if router.isLoading { - ZStack { - Rectangle().fill(.black.opacity(0.4)) - ProgressView() + .ignoresSafeArea() + } + + private func makeViewControllers() -> [UIViewController] { + guard let author = currentUser.author else { return [] } + + var controllers: [UIViewController] = [] + + let homeVC = UIHostingController(rootView: HomeTab(user: author) + .onAppear { + if let keyPair = currentUser.keyPair { + analytics.identify( + with: keyPair, + nip05: currentUser.author?.nip05 + ) + crashReporting.identify(with: keyPair) } - } - } - .sheet(isPresented: $showNewPost) { - NoteComposer(initialContents: newPostContents, isPresented: $showNewPost) - .environment(currentUser) - .environment(relayService) - .interactiveDismissDisabled() - } - .environment(\.horizontalSizeClass, .compact) + }) + homeVC.tabBarItem = UITabBarItem( + title: String(localized: "homeFeed"), + image: UIImage.tabIconHome, + selectedImage: UIImage.tabIconHomeSelected + ) + controllers.append(homeVC) + + let discoverVC = UIHostingController(rootView: DiscoverTab()) + discoverVC.tabBarItem = UITabBarItem( + title: String(localized: "discover"), + image: UIImage.tabIconEveryone, + selectedImage: UIImage.tabIconEveryoneSelected + ) + controllers.append(discoverVC) + + let composerVC = UIViewController() + composerVC.tabBarItem = UITabBarItem( + title: String(localized: "post"), + image: UIImage.newPostButton, + selectedImage: UIImage.newPostButton + ) + controllers.append(composerVC) + + let notificationsVC = UIHostingController(rootView: NotificationsView(user: currentUser.author)) + notificationsVC.tabBarItem = UITabBarItem( + title: String(localized: "notifications"), + image: UIImage.tabIconNotifications, + selectedImage: UIImage.tabIconNotificationsSelected + ) + notificationsVC.tabBarItem.badgeValue = + pushNotificationService.badgeCount > 0 ? "\(pushNotificationService.badgeCount)" : nil + controllers.append(notificationsVC) + + let profileVC = UIHostingController(rootView: ProfileTab(author: author, path: $router.profilePath)) + profileVC.tabBarItem = UITabBarItem( + title: String(localized: "profileTitle"), + image: UIImage.tabProfile, + selectedImage: UIImage.tabProfileSelected + ) + controllers.append(profileVC) + + return controllers } } diff --git a/Nos/Views/TabBarController.swift b/Nos/Views/TabBarController.swift new file mode 100644 index 000000000..5c9b61094 --- /dev/null +++ b/Nos/Views/TabBarController.swift @@ -0,0 +1,66 @@ +import SwiftUI +import UIKit + +struct TabBarController: UIViewControllerRepresentable { + @Binding var selectedIndex: AppDestination + let viewControllers: [UIViewController] + + func makeUIViewController(context: Context) -> UITabBarController { + let tabController = WorkaroundTabBarController() + tabController.viewControllers = viewControllers + tabController.delegate = context.coordinator + + tabController.tabBar.backgroundColor = .cardBgBottom + tabController.tabBar.isTranslucent = true + tabController.tabBar.tintColor = .primaryTxt + tabController.tabBar.unselectedItemTintColor = .secondaryTxt + + // removes the translucency to match the design + tabController.tabBar.backgroundImage = UIImage() + tabController.tabBar.shadowImage = UIImage() + + return tabController + } + + func updateUIViewController(_ tabController: UITabBarController, context: Context) { + if case .noteComposer = selectedIndex { + // Don't update tab selection for composer + return + } + tabController.selectedIndex = selectedIndex.tabIndex + } + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + class Coordinator: NSObject, UITabBarControllerDelegate { + var parent: TabBarController + + init(_ parent: TabBarController) { + self.parent = parent + } + + func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { + parent.selectedIndex = AppDestination.tabDestinations[tabBarController.selectedIndex] + } + } +} + +class WorkaroundTabBarController: UITabBarController { + init() { + super.init(nibName: nil, bundle: nil) + traitOverrides.horizontalSizeClass = .compact + + // Fix for macOS Sequoia: without this, the tabs appear twice and the view crashes regularly. + if #available(iOS 18, *), ProcessInfo.processInfo.isiOSAppOnMac { + // Hides the top tabs + self.mode = .tabSidebar + self.sidebar.isHidden = true + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} From df8ee5f30c6263f2457b0321638b43ae165e63a0 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 3 Dec 2024 17:44:42 -0500 Subject: [PATCH 06/11] fix lint warning --- Nos/Views/TabBarController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Nos/Views/TabBarController.swift b/Nos/Views/TabBarController.swift index 5c9b61094..34ef510ce 100644 --- a/Nos/Views/TabBarController.swift +++ b/Nos/Views/TabBarController.swift @@ -59,7 +59,8 @@ class WorkaroundTabBarController: UITabBarController { self.sidebar.isHidden = true } } - + + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } From 873ee54e229c40f3e498ebd2b7d8eb647fcdb03a Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 4 Dec 2024 17:11:30 -0500 Subject: [PATCH 07/11] move changelog entry to the right place after merge --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a72c1725..544199b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Release Notes + +### Internal Changes +- Upgraded to Xcode 16. [#1570](https://github.com/planetary-social/nos/issues/1570) + ## [1.0.3] - 2024-12-04Z ### Release Notes @@ -20,7 +25,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix quoted note composer does not expand to fit mention. [#25](https://github.com/verse-pbc/issues/issues/25) ### Internal Changes -- Upgraded to Xcode 16. [#1570](https://github.com/planetary-social/nos/issues/1570) ## [1.0.2] - 2024-11-26Z From 8feab1dbeadfd926176f42965ebd4f84589f92b6 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 4 Dec 2024 17:12:03 -0500 Subject: [PATCH 08/11] remove unnecessary line from CHANGELOG --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 544199b19..bb2bdc44b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,8 +24,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed galleries expanding past the width of the screen when there are lots of links or images. [#24](https://github.com/verse-pbc/issues/issues/24) - Fix quoted note composer does not expand to fit mention. [#25](https://github.com/verse-pbc/issues/issues/25) -### Internal Changes - ## [1.0.2] - 2024-11-26Z ### Release Notes From dbc69593566b722cbc84d6646cfdf4e3da794b76 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 10 Dec 2024 10:27:57 -0500 Subject: [PATCH 09/11] fix profile icon brightness on tab bar --- Nos/Assets/Assets.xcassets/tab-profile.imageset/Contents.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Nos/Assets/Assets.xcassets/tab-profile.imageset/Contents.json b/Nos/Assets/Assets.xcassets/tab-profile.imageset/Contents.json index 594dc266a..39cdbe304 100644 --- a/Nos/Assets/Assets.xcassets/tab-profile.imageset/Contents.json +++ b/Nos/Assets/Assets.xcassets/tab-profile.imageset/Contents.json @@ -52,5 +52,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" } } From f207c13d6f540edda5ff0bc8cd54c1d5896238df Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 10 Dec 2024 10:38:46 -0500 Subject: [PATCH 10/11] fix tab bar size on macOS Sonoma and hopefully Sequoia --- Nos/Views/TabBarController.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Nos/Views/TabBarController.swift b/Nos/Views/TabBarController.swift index 34ef510ce..4e9b112a6 100644 --- a/Nos/Views/TabBarController.swift +++ b/Nos/Views/TabBarController.swift @@ -50,13 +50,14 @@ struct TabBarController: UIViewControllerRepresentable { class WorkaroundTabBarController: UITabBarController { init() { super.init(nibName: nil, bundle: nil) - traitOverrides.horizontalSizeClass = .compact // Fix for macOS Sequoia: without this, the tabs appear twice and the view crashes regularly. if #available(iOS 18, *), ProcessInfo.processInfo.isiOSAppOnMac { // Hides the top tabs - self.mode = .tabSidebar - self.sidebar.isHidden = true + mode = .tabSidebar + sidebar.isHidden = true + traitOverrides.horizontalSizeClass = .compact + additionalSafeAreaInsets.bottom = 10 } } From 920ad6b3c70bc3660a6d0ad0831a5377a12d212a Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Fri, 13 Dec 2024 10:00:10 -0500 Subject: [PATCH 11/11] try to fix iPad tab bar without affecting Mac or iPhone --- Nos/Views/TabBarController.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Nos/Views/TabBarController.swift b/Nos/Views/TabBarController.swift index 4e9b112a6..8d30f4302 100644 --- a/Nos/Views/TabBarController.swift +++ b/Nos/Views/TabBarController.swift @@ -56,8 +56,11 @@ class WorkaroundTabBarController: UITabBarController { // Hides the top tabs mode = .tabSidebar sidebar.isHidden = true + additionalSafeAreaInsets.bottom = 10 traitOverrides.horizontalSizeClass = .compact + } else if traitCollection.userInterfaceIdiom == .pad { additionalSafeAreaInsets.bottom = 10 + traitOverrides.horizontalSizeClass = .compact } }