From 36ec9d40988a9dfa9e79545e1287ff52a33ad7d7 Mon Sep 17 00:00:00 2001 From: Bryan Montz Date: Fri, 18 Oct 2024 07:21:21 -0500 Subject: [PATCH 1/6] migrate RelayService to @Observable --- Nos/NosApp.swift | 2 +- Nos/Service/Relay/RelayService.swift | 18 +++++------------- Nos/Views/AppView.swift | 6 +++--- .../Components/Author/CompactAuthorCard.swift | 2 +- Nos/Views/Components/Button/LikeButton.swift | 2 +- Nos/Views/Components/Button/NoteButton.swift | 2 +- Nos/Views/Components/Button/RepostButton.swift | 2 +- Nos/Views/Components/ThreadView.swift | 2 +- Nos/Views/Fixtures/PreviewData.swift | 2 +- Nos/Views/Note/NoteCard.swift | 2 +- Nos/Views/Note/NoteView.swift | 4 ++-- Nos/Views/NoteComposer/NoteComposer.swift | 8 ++++---- Nos/Views/Notifications/NotificationCard.swift | 2 +- .../Notifications/NotificationsView.swift | 2 +- Nos/Views/Relay/RelayView.swift | 2 +- 15 files changed, 25 insertions(+), 33 deletions(-) diff --git a/Nos/NosApp.swift b/Nos/NosApp.swift index 8dee730ca..fc9ccd2ec 100644 --- a/Nos/NosApp.swift +++ b/Nos/NosApp.swift @@ -30,7 +30,7 @@ struct NosApp: App { WindowGroup { AppView() .environment(\.managedObjectContext, persistenceController.viewContext) - .environmentObject(relayService) + .environment(relayService) .environmentObject(router) .environment(appController) .environment(currentUser) diff --git a/Nos/Service/Relay/RelayService.swift b/Nos/Service/Relay/RelayService.swift index 98c3330ca..56bdfd07e 100644 --- a/Nos/Service/Relay/RelayService.swift +++ b/Nos/Service/Relay/RelayService.swift @@ -9,7 +9,7 @@ import UIKit /// A service that maintains connections to Nostr Relay servers and executes requests for data from those relays /// in the form of `Filters` and `RelaySubscription`s. -class RelayService: ObservableObject { +@Observable class RelayService { private var subscriptionManager: RelaySubscriptionManager private var processSubscriptionQueueTimer: AsyncTimer? @@ -19,11 +19,10 @@ class RelayService: ObservableObject { private var processingQueue = DispatchQueue(label: "RelayService-processing", qos: .userInitiated) private var parseQueue = ParseQueue() - @Dependency(\.persistenceController) var persistenceController - @Dependency(\.analytics) private var analytics - @Dependency(\.crashReporting) private var crashReporting - @MainActor @Dependency(\.currentUser) private var currentUser - @Published var numberOfConnectedRelays: Int = 0 + @ObservationIgnored @Dependency(\.persistenceController) var persistenceController + @ObservationIgnored @Dependency(\.analytics) private var analytics + @ObservationIgnored @Dependency(\.crashReporting) private var crashReporting + @MainActor @ObservationIgnored @Dependency(\.currentUser) private var currentUser init(subscriptionManager: RelaySubscriptionManager = RelaySubscriptionManagerActor()) { self.subscriptionManager = subscriptionManager @@ -312,13 +311,6 @@ extension RelayService { await clearStaleSubscriptions() await subscriptionManager.processSubscriptionQueue() - - let socketsCount = await subscriptionManager.sockets().count - Task { @MainActor in - if numberOfConnectedRelays != socketsCount { - numberOfConnectedRelays = socketsCount - } - } } private func clearStaleSubscriptions() async { diff --git a/Nos/Views/AppView.swift b/Nos/Views/AppView.swift index fcdd2d0a6..939f66861 100644 --- a/Nos/Views/AppView.swift +++ b/Nos/Views/AppView.swift @@ -226,7 +226,7 @@ struct AppView_Previews: PreviewProvider { static var previews: some View { AppView() .environment(\.managedObjectContext, previewContext) - .environmentObject(relayService) + .environment(relayService) .environmentObject(router) .environment(loggedInAppController) .environment(currentUser) @@ -234,7 +234,7 @@ struct AppView_Previews: PreviewProvider { AppView() .environment(\.managedObjectContext, previewContext) - .environmentObject(relayService) + .environment(relayService) .environmentObject(router) .environment(AppController()) .environment(currentUser) @@ -242,7 +242,7 @@ struct AppView_Previews: PreviewProvider { AppView() .environment(\.managedObjectContext, previewContext) - .environmentObject(relayService) + .environment(relayService) .environmentObject(routerWithSideMenuOpened) .environment(AppController()) .environment(currentUser) diff --git a/Nos/Views/Components/Author/CompactAuthorCard.swift b/Nos/Views/Components/Author/CompactAuthorCard.swift index ec36293be..f65f034fe 100644 --- a/Nos/Views/Components/Author/CompactAuthorCard.swift +++ b/Nos/Views/Components/Author/CompactAuthorCard.swift @@ -7,7 +7,7 @@ struct CompactAuthorCard: View { let author: Author @EnvironmentObject private var router: Router - @EnvironmentObject private var relayService: RelayService + @Environment(RelayService.self) private var relayService @State private var relaySubscriptions = SubscriptionCancellables() diff --git a/Nos/Views/Components/Button/LikeButton.swift b/Nos/Views/Components/Button/LikeButton.swift index af323a68b..b0861831a 100644 --- a/Nos/Views/Components/Button/LikeButton.swift +++ b/Nos/Views/Components/Button/LikeButton.swift @@ -17,7 +17,7 @@ struct LikeButton: View { /// Whether a "like" or "delete like" event is currently being published. @State private var isPublishing = false - @EnvironmentObject private var relayService: RelayService + @Environment(RelayService.self) private var relayService @Environment(CurrentUser.self) private var currentUser @Environment(\.managedObjectContext) private var viewContext @ObservationIgnored @Dependency(\.analytics) private var analytics diff --git a/Nos/Views/Components/Button/NoteButton.swift b/Nos/Views/Components/Button/NoteButton.swift index 4b9f84489..86f852a07 100644 --- a/Nos/Views/Components/Button/NoteButton.swift +++ b/Nos/Views/Components/Button/NoteButton.swift @@ -31,7 +31,7 @@ struct NoteButton: View { private let tapAction: ((Event) -> Void)? @State private var relaySubscriptions = SubscriptionCancellables() - @EnvironmentObject private var relayService: RelayService + @Environment(RelayService.self) private var relayService @EnvironmentObject private var router: Router /// Initializes a NoteButton object. diff --git a/Nos/Views/Components/Button/RepostButton.swift b/Nos/Views/Components/Button/RepostButton.swift index 88c41596b..2376a8d55 100644 --- a/Nos/Views/Components/Button/RepostButton.swift +++ b/Nos/Views/Components/Button/RepostButton.swift @@ -10,7 +10,7 @@ struct RepostButton: View { let showsCount: Bool @FetchRequest private var reposts: FetchedResults - @EnvironmentObject private var relayService: RelayService + @Environment(RelayService.self) private var relayService @Environment(CurrentUser.self) private var currentUser @Environment(\.managedObjectContext) private var viewContext diff --git a/Nos/Views/Components/ThreadView.swift b/Nos/Views/Components/ThreadView.swift index 97986d6e1..dffc1e611 100644 --- a/Nos/Views/Components/ThreadView.swift +++ b/Nos/Views/Components/ThreadView.swift @@ -151,7 +151,7 @@ struct ThreadView_Previews: PreviewProvider { static var previews: some View { ThreadView(root: rootNote, allReplies: [replyNote, secondReply]) .environment(\.managedObjectContext, emptyPreviewContext) - .environmentObject(emptyRelayService) + .environment(emptyRelayService) .environmentObject(router) .environment(currentUser) } diff --git a/Nos/Views/Fixtures/PreviewData.swift b/Nos/Views/Fixtures/PreviewData.swift index cac451732..48ffa45f1 100644 --- a/Nos/Views/Fixtures/PreviewData.swift +++ b/Nos/Views/Fixtures/PreviewData.swift @@ -341,7 +341,7 @@ struct InjectPreviewData: ViewModifier { content .environment(\.managedObjectContext, previewData.persistenceController.viewContext) .environmentObject(previewData.router) - .environmentObject(previewData.relayService) + .environment(previewData.relayService) .environment(previewData.currentUser) } } diff --git a/Nos/Views/Note/NoteCard.swift b/Nos/Views/Note/NoteCard.swift index 8ef2c36d5..d24ffdb17 100644 --- a/Nos/Views/Note/NoteCard.swift +++ b/Nos/Views/Note/NoteCard.swift @@ -223,7 +223,7 @@ struct NoteCard_Previews: PreviewProvider { } } .environment(\.managedObjectContext, previewData.previewContext) - .environmentObject(previewData.relayService) + .environment(previewData.relayService) .environmentObject(previewData.router) .environment(previewData.currentUser) .padding() diff --git a/Nos/Views/Note/NoteView.swift b/Nos/Views/Note/NoteView.swift index 535e5124a..e978919a0 100644 --- a/Nos/Views/Note/NoteView.swift +++ b/Nos/Views/Note/NoteView.swift @@ -4,7 +4,7 @@ import SwiftUINavigation import Dependencies struct NoteView: View { - @EnvironmentObject private var relayService: RelayService + @Environment(RelayService.self) private var relayService @EnvironmentObject private var router: Router @Environment(CurrentUser.self) private var currentUser @Dependency(\.analytics) private var analytics @@ -210,7 +210,7 @@ struct RepliesView_Previews: PreviewProvider { } } .environment(\.managedObjectContext, previewContext) - .environmentObject(emptyRelayService) + .environment(emptyRelayService) .environmentObject(router) .environment(currentUser) .padding() diff --git a/Nos/Views/NoteComposer/NoteComposer.swift b/Nos/Views/NoteComposer/NoteComposer.swift index 7d777c574..b07153f00 100644 --- a/Nos/Views/NoteComposer/NoteComposer.swift +++ b/Nos/Views/NoteComposer/NoteComposer.swift @@ -7,8 +7,8 @@ import SwiftUINavigation struct NoteComposer: View { @Environment(\.managedObjectContext) private var viewContext - @EnvironmentObject private var relayService: RelayService - @Environment(CurrentUser.self) var currentUser + @Environment(RelayService.self) private var relayService + @Dependency(\.currentUser) private var currentUser @Dependency(\.analytics) private var analytics @Dependency(\.noteParser) private var noteParser @Dependency(\.persistenceController) private var persistenceController @@ -262,7 +262,7 @@ struct NoteComposer: View { .zIndex(1) } - private func postAction() async { + @MainActor private func postAction() async { guard currentUser.keyPair != nil, let author = currentUser.author else { alert = AlertState(title: { TextState(String(localized: .localizable.error)) @@ -345,7 +345,7 @@ struct NoteComposer: View { ) } - private func publishPost() async { + @MainActor private func publishPost() async { guard let keyPair = currentUser.keyPair, let author = currentUser.author else { Log.error("Cannot post without a keypair") return diff --git a/Nos/Views/Notifications/NotificationCard.swift b/Nos/Views/Notifications/NotificationCard.swift index c02de43e3..82ffc2d49 100644 --- a/Nos/Views/Notifications/NotificationCard.swift +++ b/Nos/Views/Notifications/NotificationCard.swift @@ -6,7 +6,7 @@ struct NotificationCard: View { @Environment(\.managedObjectContext) private var viewContext @EnvironmentObject private var router: Router - @EnvironmentObject private var relayService: RelayService + @Environment(RelayService.self) private var relayService @Dependency(\.persistenceController) private var persistenceController let viewModel: NotificationViewModel diff --git a/Nos/Views/Notifications/NotificationsView.swift b/Nos/Views/Notifications/NotificationsView.swift index 6429eed63..cea37baaf 100644 --- a/Nos/Views/Notifications/NotificationsView.swift +++ b/Nos/Views/Notifications/NotificationsView.swift @@ -7,7 +7,7 @@ import Logger /// Displays a list of cells that let the user know when other users interact with their notes. struct NotificationsView: View { - @EnvironmentObject private var relayService: RelayService + @Environment(RelayService.self) private var relayService @EnvironmentObject private var router: Router @Dependency(\.analytics) private var analytics @Dependency(\.pushNotificationService) private var pushNotificationService diff --git a/Nos/Views/Relay/RelayView.swift b/Nos/Views/Relay/RelayView.swift index d5f8d9248..26d07c1a1 100644 --- a/Nos/Views/Relay/RelayView.swift +++ b/Nos/Views/Relay/RelayView.swift @@ -10,7 +10,7 @@ struct RelaysDestination: Hashable { struct RelayView: View { @Environment(\.managedObjectContext) private var viewContext - @EnvironmentObject private var relayService: RelayService + @Environment(RelayService.self) private var relayService @Environment(CurrentUser.self) private var currentUser @ObservedObject var author: Author From 8b44931397217ce2cdd5e7d98852f405ddfebdd6 Mon Sep 17 00:00:00 2001 From: Bryan Montz Date: Fri, 18 Oct 2024 07:35:39 -0500 Subject: [PATCH 2/6] migrate PushNotificationService to @Observable --- Nos/NosApp.swift | 2 +- Nos/Service/PushNotificationService.swift | 26 +++++++++++------------ Nos/Views/AppView.swift | 8 +++---- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/Nos/NosApp.swift b/Nos/NosApp.swift index fc9ccd2ec..48ef27900 100644 --- a/Nos/NosApp.swift +++ b/Nos/NosApp.swift @@ -34,7 +34,7 @@ struct NosApp: App { .environmentObject(router) .environment(appController) .environment(currentUser) - .environmentObject(pushNotificationService) + .environment(pushNotificationService) .onOpenURL { DeepLinkService.handle($0, router: router) } .onChange(of: scenePhase) { _, newPhase in switch newPhase { diff --git a/Nos/Service/PushNotificationService.swift b/Nos/Service/PushNotificationService.swift index 6b173ab3c..8d68e1546 100644 --- a/Nos/Service/PushNotificationService.swift +++ b/Nos/Service/PushNotificationService.swift @@ -8,13 +8,13 @@ import Combine /// A class that abstracts our interactions with push notification infrastructure and iOS permissions. It can handle /// UNUserNotificationCenterDelegate callbacks for receiving and displaying notifications, and it watches the db for /// all new events and creates `NosNotification`s and displays them when appropriate. -@MainActor class PushNotificationService: - NSObject, ObservableObject, NSFetchedResultsControllerDelegate, UNUserNotificationCenterDelegate { +@MainActor @Observable class PushNotificationService: + NSObject, NSFetchedResultsControllerDelegate, UNUserNotificationCenterDelegate { // MARK: - Public Properties /// The number of unread notifications that should be displayed as a badge - @Published var badgeCount = 0 + private(set) var badgeCount = 0 private let showPushNotificationsAfterKey = "PushNotificationService.notificationCutoff" @@ -40,22 +40,20 @@ import Combine // MARK: - Private Properties - @Dependency(\.relayService) private var relayService - @Dependency(\.persistenceController) private var persistenceController - @Dependency(\.router) private var router - @Dependency(\.analytics) private var analytics - @Dependency(\.crashReporting) private var crashReporting - @Dependency(\.userDefaults) private var userDefaults - @Dependency(\.currentUser) private var currentUser + @ObservationIgnored @Dependency(\.relayService) private var relayService + @ObservationIgnored @Dependency(\.persistenceController) private var persistenceController + @ObservationIgnored @Dependency(\.router) private var router + @ObservationIgnored @Dependency(\.analytics) private var analytics + @ObservationIgnored @Dependency(\.crashReporting) private var crashReporting + @ObservationIgnored @Dependency(\.userDefaults) private var userDefaults + @ObservationIgnored @Dependency(\.currentUser) private var currentUser private var notificationWatcher: NSFetchedResultsController? private var relaySubscription: SubscriptionCancellable? private var currentAuthor: Author? - private lazy var modelContext: NSManagedObjectContext = { - persistenceController.newBackgroundContext() - }() + @ObservationIgnored private lazy var modelContext = persistenceController.newBackgroundContext() - private lazy var registrar = PushNotificationRegistrar() + @ObservationIgnored private lazy var registrar = PushNotificationRegistrar() // MARK: - Setup diff --git a/Nos/Views/AppView.swift b/Nos/Views/AppView.swift index 939f66861..3de448ae3 100644 --- a/Nos/Views/AppView.swift +++ b/Nos/Views/AppView.swift @@ -8,7 +8,7 @@ struct AppView: View { @Environment(AppController.self) var appController @EnvironmentObject private var router: Router - @EnvironmentObject var pushNotificationService: PushNotificationService + @Environment(PushNotificationService.self) private var pushNotificationService @Dependency(\.analytics) private var analytics @Dependency(\.crashReporting) private var crashReporting @Dependency(\.userDefaults) private var userDefaults @@ -230,7 +230,7 @@ struct AppView_Previews: PreviewProvider { .environmentObject(router) .environment(loggedInAppController) .environment(currentUser) - .environmentObject(pushNotificationService) + .environment(pushNotificationService) AppView() .environment(\.managedObjectContext, previewContext) @@ -238,7 +238,7 @@ struct AppView_Previews: PreviewProvider { .environmentObject(router) .environment(AppController()) .environment(currentUser) - .environmentObject(pushNotificationService) + .environment(pushNotificationService) AppView() .environment(\.managedObjectContext, previewContext) @@ -246,6 +246,6 @@ struct AppView_Previews: PreviewProvider { .environmentObject(routerWithSideMenuOpened) .environment(AppController()) .environment(currentUser) - .environmentObject(pushNotificationService) + .environment(pushNotificationService) } } From 10511bab316be6b3711bc443d9a0d2f976a6c4ed Mon Sep 17 00:00:00 2001 From: Bryan Montz Date: Sat, 19 Oct 2024 06:31:35 -0500 Subject: [PATCH 3/6] migrate SearchController to @Observable --- Nos/Controller/SearchController.swift | 27 +++++++++++-------- .../Components/Author/AuthorListView.swift | 2 +- Nos/Views/Discover/DiscoverContentsView.swift | 2 +- Nos/Views/Discover/DiscoverTab.swift | 2 +- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Nos/Controller/SearchController.swift b/Nos/Controller/SearchController.swift index 1c84b6d79..f149977e2 100644 --- a/Nos/Controller/SearchController.swift +++ b/Nos/Controller/SearchController.swift @@ -32,24 +32,29 @@ enum SearchOrigin { } /// Manages a search query and list of results. -class SearchController: ObservableObject { +@Observable final class SearchController { // MARK: - Properties /// The search query string. - @Published var query: String = "" + var query: String = "" { + didSet { + queryPublisher.send(query) + } + } + @ObservationIgnored private lazy var queryPublisher = CurrentValueSubject(query) /// Any and all authors in the search results. As of this writing, _only_ authors appear in search results, /// so this contains all search results, period. - @Published var authorResults = [Author]() + var authorResults = [Author]() - @Published var state: SearchState = .noQuery + var state: SearchState = .noQuery - @Dependency(\.router) private var router - @Dependency(\.relayService) private var relayService - @Dependency(\.persistenceController) private var persistenceController - @Dependency(\.currentUser) var currentUser - @Dependency(\.analytics) private var analytics + @ObservationIgnored @Dependency(\.router) private var router + @ObservationIgnored @Dependency(\.relayService) private var relayService + @ObservationIgnored @Dependency(\.persistenceController) private var persistenceController + @ObservationIgnored @Dependency(\.currentUser) var currentUser + @ObservationIgnored @Dependency(\.analytics) private var analytics private var cancellables = [AnyCancellable]() private var searchSubscriptions = SubscriptionCancellables() @@ -57,7 +62,7 @@ class SearchController: ObservableObject { /// The timer for showing the "not finding results" view. Resets any time the query is changed. private var timer: Timer? - private lazy var context = persistenceController.viewContext + @ObservationIgnored private lazy var context = persistenceController.viewContext /// The amount of time, in seconds, to remain in the `.loading` state until switching to `.stillLoading`. private let stillLoadingTime: TimeInterval = 10 @@ -70,7 +75,7 @@ class SearchController: ObservableObject { init(searchOrigin: SearchOrigin = .discover) { self.searchOrigin = searchOrigin - $query + queryPublisher .removeDuplicates() .map { [weak self] query in if query.isEmpty { diff --git a/Nos/Views/Components/Author/AuthorListView.swift b/Nos/Views/Components/Author/AuthorListView.swift index a4734c4c3..94dbd5448 100644 --- a/Nos/Views/Components/Author/AuthorListView.swift +++ b/Nos/Views/Components/Author/AuthorListView.swift @@ -7,7 +7,7 @@ struct AuthorListView: View { @Environment(\.managedObjectContext) private var viewContext - @StateObject private var searchController = SearchController(searchOrigin: .mentions) + @State private var searchController = SearchController(searchOrigin: .mentions) @FocusState private var isSearching: Bool diff --git a/Nos/Views/Discover/DiscoverContentsView.swift b/Nos/Views/Discover/DiscoverContentsView.swift index 6fe912772..f154f601d 100644 --- a/Nos/Views/Discover/DiscoverContentsView.swift +++ b/Nos/Views/Discover/DiscoverContentsView.swift @@ -3,7 +3,7 @@ import SwiftUI import Dependencies struct DiscoverContentsView: View { - @ObservedObject var searchController: SearchController + @State private var searchController: SearchController @EnvironmentObject private var router: Router diff --git a/Nos/Views/Discover/DiscoverTab.swift b/Nos/Views/Discover/DiscoverTab.swift index f7f2f814e..152dd3ad6 100644 --- a/Nos/Views/Discover/DiscoverTab.swift +++ b/Nos/Views/Discover/DiscoverTab.swift @@ -17,7 +17,7 @@ struct DiscoverTab: View { @State private var isVisible = false - @StateObject private var searchController = SearchController() + @State private var searchController = SearchController() @State var predicate: NSPredicate = .false From b9a4060e3402a2a3a068a7b0bad5c95096446b23 Mon Sep 17 00:00:00 2001 From: Bryan Montz Date: Sat, 19 Oct 2024 06:51:43 -0500 Subject: [PATCH 4/6] migrate UsernameObserver to @Observable, rename to TextDebouncer --- Nos.xcodeproj/project.pbxproj | 4 +++ Nos/Models/TextDebouncer.swift | 27 +++++++++++++++++++ .../AlreadyHaveANIP05View.swift | 26 ++---------------- .../PickYourUsernameSheet.swift | 26 ++---------------- 4 files changed, 35 insertions(+), 48 deletions(-) create mode 100644 Nos/Models/TextDebouncer.swift diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index dd473f33e..e406983a4 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -175,6 +175,7 @@ 504454712C90728E00251A7E /* Event+Fetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044546D2C90726A00251A7E /* Event+Fetching.swift */; }; 504454722C90729100251A7E /* Event+Hydration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044546F2C90728500251A7E /* Event+Hydration.swift */; }; 5045540D2C81E10C0044ECAE /* EditableAvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5045540C2C81E10C0044ECAE /* EditableAvatarView.swift */; }; + 506102882CC3D29B003DC0E3 /* TextDebouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 506102872CC3D29B003DC0E3 /* TextDebouncer.swift */; }; 508133CB2C79F78500DFBF75 /* AttributedString+Quotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508133CA2C79F78500DFBF75 /* AttributedString+Quotation.swift */; }; 508133DB2C7A003600DFBF75 /* AttributedString+QuotationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508133DA2C7A003600DFBF75 /* AttributedString+QuotationsTests.swift */; }; 508133DC2C7A007700DFBF75 /* AttributedString+Quotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 508133CA2C79F78500DFBF75 /* AttributedString+Quotation.swift */; }; @@ -703,6 +704,7 @@ 5044546D2C90726A00251A7E /* Event+Fetching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Event+Fetching.swift"; sourceTree = ""; }; 5044546F2C90728500251A7E /* Event+Hydration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Event+Hydration.swift"; sourceTree = ""; }; 5045540C2C81E10C0044ECAE /* EditableAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableAvatarView.swift; sourceTree = ""; }; + 506102872CC3D29B003DC0E3 /* TextDebouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextDebouncer.swift; sourceTree = ""; }; 508133CA2C79F78500DFBF75 /* AttributedString+Quotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Quotation.swift"; sourceTree = ""; }; 508133DA2C7A003600DFBF75 /* AttributedString+QuotationsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+QuotationsTests.swift"; sourceTree = ""; }; 508B2B602C9EF65300C14034 /* NSPersistentContainer+Nos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPersistentContainer+Nos.swift"; sourceTree = ""; }; @@ -1858,6 +1860,7 @@ C94A5E172A72C84200B6EC5D /* ReportCategory.swift */, C9E37E142A1E8143003D4B0A /* ReportTarget.swift */, C992B3292B3613CC00704A9C /* SubscriptionCancellable.swift */, + 506102872CC3D29B003DC0E3 /* TextDebouncer.swift */, 5BFBB28A2BD9D79F002E909F /* URLParser.swift */, 659B27232BD9CB4500BEA6CC /* VerifiableEvent.swift */, 030742C32B4769F90073839D /* CoreData */, @@ -2421,6 +2424,7 @@ C95D68A1299E6D3E00429F86 /* BioView.swift in Sources */, 5BE281CA2AE2CCEB00880466 /* HomeTab.swift in Sources */, 03C49AC22C938DE100502321 /* SoupOpenGraphParser.swift in Sources */, + 506102882CC3D29B003DC0E3 /* TextDebouncer.swift in Sources */, C94D14812A12B3F70014C906 /* SearchBar.swift in Sources */, C92E7F672C4EFF3D00B80638 /* WebSocketErrorEvent.swift in Sources */, 0317263C2C7935220030EDCA /* AspectRatioContainer.swift in Sources */, diff --git a/Nos/Models/TextDebouncer.swift b/Nos/Models/TextDebouncer.swift new file mode 100644 index 000000000..47c4fd987 --- /dev/null +++ b/Nos/Models/TextDebouncer.swift @@ -0,0 +1,27 @@ +import Combine +import SwiftUI + +@Observable final class TextDebouncer { + + private(set) var debouncedText = "" + + var text = "" { + didSet { + textPublisher.send(text) + } + } + @ObservationIgnored private lazy var textPublisher = CurrentValueSubject(text) + + private var subscriptions = Set() + + init() { + textPublisher + .removeDuplicates() + .filter { $0.count >= 3 } + .debounce(for: .milliseconds(200), scheduler: RunLoop.main) + .sink { [weak self] value in + self?.debouncedText = value + } + .store(in: &subscriptions) + } +} diff --git a/Nos/Views/Profile/Edit/CreateUsernameWizard/AlreadyHaveANIP05View.swift b/Nos/Views/Profile/Edit/CreateUsernameWizard/AlreadyHaveANIP05View.swift index 175c24e29..b52d64811 100644 --- a/Nos/Views/Profile/Edit/CreateUsernameWizard/AlreadyHaveANIP05View.swift +++ b/Nos/Views/Profile/Edit/CreateUsernameWizard/AlreadyHaveANIP05View.swift @@ -5,7 +5,7 @@ import SwiftUI struct AlreadyHaveANIP05View: View { @Binding var isPresented: Bool - @StateObject private var usernameObserver = UsernameObserver() + @State private var usernameObserver = TextDebouncer() @State private var verified: Bool? @State private var isVerifying = false @State private var verifyTask: Task? @@ -124,31 +124,9 @@ struct AlreadyHaveANIP05View: View { } } -fileprivate class UsernameObserver: ObservableObject { - - @Published - var debouncedText = "" - - @Published - var text = "" - - private var subscriptions = Set() - - init() { - $text - .removeDuplicates() - .filter { $0.count >= 3 } - .debounce(for: .milliseconds(200), scheduler: RunLoop.main) - .sink { [weak self] value in - self?.debouncedText = value - } - .store(in: &subscriptions) - } -} - fileprivate struct UsernameTextField: View { - @StateObject var usernameObserver: UsernameObserver + @State var usernameObserver: TextDebouncer @FocusState private var usernameFieldIsFocused: Bool var body: some View { diff --git a/Nos/Views/Profile/Edit/CreateUsernameWizard/PickYourUsernameSheet.swift b/Nos/Views/Profile/Edit/CreateUsernameWizard/PickYourUsernameSheet.swift index 8c8aa3cbb..97be3dbb3 100644 --- a/Nos/Views/Profile/Edit/CreateUsernameWizard/PickYourUsernameSheet.swift +++ b/Nos/Views/Profile/Edit/CreateUsernameWizard/PickYourUsernameSheet.swift @@ -6,7 +6,7 @@ import SwiftUI struct PickYourUsernameSheet: View { @Binding var isPresented: Bool - @StateObject private var usernameObserver = UsernameObserver() + @State private var usernameObserver = TextDebouncer() @State private var verified: Bool? @State private var isVerifying = false @Dependency(\.namesAPI) private var namesAPI @@ -114,31 +114,9 @@ struct PickYourUsernameSheet: View { } } -fileprivate class UsernameObserver: ObservableObject { - - @Published - var debouncedText = "" - - @Published - var text = "" - - private var subscriptions = Set() - - init() { - $text - .removeDuplicates() - .filter { $0.count >= 3 } - .debounce(for: .milliseconds(200), scheduler: RunLoop.main) - .sink { [weak self] value in - self?.debouncedText = value - } - .store(in: &subscriptions) - } -} - fileprivate struct UsernameTextField: View { - @StateObject var usernameObserver: UsernameObserver + @State var usernameObserver: TextDebouncer @FocusState private var usernameFieldIsFocused: Bool var body: some View { From 5258000cca56b2fe283357404a65bf93cc12a4e1 Mon Sep 17 00:00:00 2001 From: Bryan Montz Date: Sat, 19 Oct 2024 06:56:41 -0500 Subject: [PATCH 5/6] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5febe2bf..379ed550f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Internal Changes - Migrate ObservableObject to @Observable where possible [#1458](https://github.com/planetary-social/nos/issues/1458) - Added the Create Account onboarding screen. Currently behind the “New Onboarding Flow” feature flag. [#1594](https://github.com/planetary-social/nos/issues/1594) +- More ObservableObject to @Observable migrations [#1458](https://github.com/planetary-social/nos/issues/1458) ## [0.2.2] - 2024-10-11Z From 4edf2ee6ea63b514416e1be4b930907b31a249df Mon Sep 17 00:00:00 2001 From: Bryan Montz Date: Thu, 24 Oct 2024 08:07:18 -0500 Subject: [PATCH 6/6] addressed feedback --- Nos/Views/Discover/DiscoverContentsView.swift | 2 +- .../Edit/CreateUsernameWizard/AlreadyHaveANIP05View.swift | 4 ++-- .../Edit/CreateUsernameWizard/PickYourUsernameSheet.swift | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Nos/Views/Discover/DiscoverContentsView.swift b/Nos/Views/Discover/DiscoverContentsView.swift index 45948d8c2..ff17274ac 100644 --- a/Nos/Views/Discover/DiscoverContentsView.swift +++ b/Nos/Views/Discover/DiscoverContentsView.swift @@ -3,7 +3,7 @@ import SwiftUI import Dependencies struct DiscoverContentsView: View { - @State private var searchController: SearchController + private var searchController: SearchController @EnvironmentObject private var router: Router diff --git a/Nos/Views/Profile/Edit/CreateUsernameWizard/AlreadyHaveANIP05View.swift b/Nos/Views/Profile/Edit/CreateUsernameWizard/AlreadyHaveANIP05View.swift index 2bbf03466..821317bea 100644 --- a/Nos/Views/Profile/Edit/CreateUsernameWizard/AlreadyHaveANIP05View.swift +++ b/Nos/Views/Profile/Edit/CreateUsernameWizard/AlreadyHaveANIP05View.swift @@ -5,7 +5,7 @@ import SwiftUI struct AlreadyHaveANIP05View: View { @Binding var isPresented: Bool - @State private var usernameObserver = TextDebouncer() + @State var usernameObserver = TextDebouncer() @State private var verified: Bool? @State private var isVerifying = false @State private var verifyTask: Task? @@ -126,7 +126,7 @@ struct AlreadyHaveANIP05View: View { fileprivate struct UsernameTextField: View { - @State var usernameObserver: TextDebouncer + @Bindable var usernameObserver: TextDebouncer @FocusState private var usernameFieldIsFocused: Bool var body: some View { diff --git a/Nos/Views/Profile/Edit/CreateUsernameWizard/PickYourUsernameSheet.swift b/Nos/Views/Profile/Edit/CreateUsernameWizard/PickYourUsernameSheet.swift index 86cdc7a3f..913b58f9e 100644 --- a/Nos/Views/Profile/Edit/CreateUsernameWizard/PickYourUsernameSheet.swift +++ b/Nos/Views/Profile/Edit/CreateUsernameWizard/PickYourUsernameSheet.swift @@ -6,7 +6,7 @@ import SwiftUI struct PickYourUsernameSheet: View { @Binding var isPresented: Bool - @State private var usernameObserver = TextDebouncer() + @State var usernameObserver = TextDebouncer() @State private var verified: Bool? @State private var isVerifying = false @Dependency(\.namesAPI) private var namesAPI @@ -116,7 +116,7 @@ struct PickYourUsernameSheet: View { fileprivate struct UsernameTextField: View { - @State var usernameObserver: TextDebouncer + @Bindable var usernameObserver: TextDebouncer @FocusState private var usernameFieldIsFocused: Bool var body: some View {