Skip to content

Commit

Permalink
Merge pull request #1672 from planetary-social/bdm/1458-more-observable
Browse files Browse the repository at this point in the history
ObservableObject to @observable migrations #1458
  • Loading branch information
bryanmontz authored Oct 29, 2024
2 parents 1f59978 + 4edf2ee commit 63c3e2e
Show file tree
Hide file tree
Showing 25 changed files with 97 additions and 114 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added the Account Success onboarding screen. Currently behind the “New Onboarding Flow” feature flag. [#1599](https://github.com/planetary-social/nos/issues/1599)
- Updated the Age Verification onboarding screen. Currently behind the “New Onboarding Flow” feature flag. [#1651](https://github.com/planetary-social/nos/issues/1651)
- Track opening mentions with Posthog. [#1480](https://github.com/planetary-social/nos/issues/1480)
- More ObservableObject to @Observable migrations [#1458](https://github.com/planetary-social/nos/issues/1458)
- Avoid crash and print extra debugging details when a reposted note that has not finished loading is clicked. [#1669](https://github.com/planetary-social/nos/issues/1669)
- Changed the term "mute" to "block". [#1681](https://github.com/planetary-social/nos/pull/1681)

Expand Down
4 changes: 4 additions & 0 deletions Nos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,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 */; };
Expand Down Expand Up @@ -736,6 +737,7 @@
5044546D2C90726A00251A7E /* Event+Fetching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Event+Fetching.swift"; sourceTree = "<group>"; };
5044546F2C90728500251A7E /* Event+Hydration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Event+Hydration.swift"; sourceTree = "<group>"; };
5045540C2C81E10C0044ECAE /* EditableAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableAvatarView.swift; sourceTree = "<group>"; };
506102872CC3D29B003DC0E3 /* TextDebouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextDebouncer.swift; sourceTree = "<group>"; };
508133CA2C79F78500DFBF75 /* AttributedString+Quotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+Quotation.swift"; sourceTree = "<group>"; };
508133DA2C7A003600DFBF75 /* AttributedString+QuotationsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AttributedString+QuotationsTests.swift"; sourceTree = "<group>"; };
508B2B602C9EF65300C14034 /* NSPersistentContainer+Nos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPersistentContainer+Nos.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1921,6 +1923,7 @@
C94A5E172A72C84200B6EC5D /* ReportCategory.swift */,
C9E37E142A1E8143003D4B0A /* ReportTarget.swift */,
C992B3292B3613CC00704A9C /* SubscriptionCancellable.swift */,
506102872CC3D29B003DC0E3 /* TextDebouncer.swift */,
5BFBB28A2BD9D79F002E909F /* URLParser.swift */,
659B27232BD9CB4500BEA6CC /* VerifiableEvent.swift */,
030742C32B4769F90073839D /* CoreData */,
Expand Down Expand Up @@ -2495,6 +2498,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 */,
Expand Down
27 changes: 16 additions & 11 deletions Nos/Controller/SearchController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,37 @@ 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<String, Never>(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()

/// 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
Expand All @@ -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 {
Expand Down
27 changes: 27 additions & 0 deletions Nos/Models/TextDebouncer.swift
Original file line number Diff line number Diff line change
@@ -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<String, Never>(text)

private var subscriptions = Set<AnyCancellable>()

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)
}
}
4 changes: 2 additions & 2 deletions Nos/NosApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ struct NosApp: App {
WindowGroup {
AppView()
.environment(\.managedObjectContext, persistenceController.viewContext)
.environmentObject(relayService)
.environment(relayService)
.environmentObject(router)
.environment(appController)
.environment(currentUser)
.environmentObject(pushNotificationService)
.environment(pushNotificationService)
.onOpenURL { DeepLinkService.handle($0, router: router) }
.onChange(of: scenePhase) { _, newPhase in
switch newPhase {
Expand Down
26 changes: 12 additions & 14 deletions Nos/Service/PushNotificationService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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<Event>?
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

Expand Down
18 changes: 5 additions & 13 deletions Nos/Service/Relay/RelayService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
14 changes: 7 additions & 7 deletions Nos/Views/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -226,26 +226,26 @@ struct AppView_Previews: PreviewProvider {
static var previews: some View {
AppView()
.environment(\.managedObjectContext, previewContext)
.environmentObject(relayService)
.environment(relayService)
.environmentObject(router)
.environment(loggedInAppController)
.environment(currentUser)
.environmentObject(pushNotificationService)
.environment(pushNotificationService)

AppView()
.environment(\.managedObjectContext, previewContext)
.environmentObject(relayService)
.environment(relayService)
.environmentObject(router)
.environment(AppController())
.environment(currentUser)
.environmentObject(pushNotificationService)
.environment(pushNotificationService)

AppView()
.environment(\.managedObjectContext, previewContext)
.environmentObject(relayService)
.environment(relayService)
.environmentObject(routerWithSideMenuOpened)
.environment(AppController())
.environment(currentUser)
.environmentObject(pushNotificationService)
.environment(pushNotificationService)
}
}
2 changes: 1 addition & 1 deletion Nos/Views/Components/Author/AuthorListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
@State private var filteredAuthors: [Author] = []
Expand Down
2 changes: 1 addition & 1 deletion Nos/Views/Components/Author/CompactAuthorCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion Nos/Views/Components/Button/LikeButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Nos/Views/Components/Button/NoteButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion Nos/Views/Components/Button/RepostButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ struct RepostButton: View {
let showsCount: Bool

@FetchRequest private var reposts: FetchedResults<Event>
@EnvironmentObject private var relayService: RelayService
@Environment(RelayService.self) private var relayService
@Environment(CurrentUser.self) private var currentUser
@Environment(\.managedObjectContext) private var viewContext

Expand Down
2 changes: 1 addition & 1 deletion Nos/Views/Components/ThreadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion Nos/Views/Discover/DiscoverContentsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import SwiftUI
import Dependencies

struct DiscoverContentsView: View {
@ObservedObject var searchController: SearchController
private var searchController: SearchController

@EnvironmentObject private var router: Router

Expand Down
2 changes: 1 addition & 1 deletion Nos/Views/Discover/DiscoverTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
@FocusState private var isSearching: Bool
Expand Down
2 changes: 1 addition & 1 deletion Nos/Views/Fixtures/PreviewData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ struct InjectPreviewData: ViewModifier {
content
.environment(\.managedObjectContext, previewData.persistenceController.viewContext)
.environmentObject(previewData.router)
.environmentObject(previewData.relayService)
.environment(previewData.relayService)
.environment(previewData.currentUser)
}
}
Expand Down
2 changes: 1 addition & 1 deletion Nos/Views/Note/NoteCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions Nos/Views/Note/NoteView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -257,7 +257,7 @@ struct RepliesView_Previews: PreviewProvider {
}
}
.environment(\.managedObjectContext, previewContext)
.environmentObject(emptyRelayService)
.environment(emptyRelayService)
.environmentObject(router)
.environment(currentUser)
.padding()
Expand Down
Loading

0 comments on commit 63c3e2e

Please sign in to comment.