Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ProfileView.onAppear code #1748

Merged
merged 3 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Improved naming of a couple list-related classes.
- Track TestFlight vs AppStore installations in Posthog. [#130](https://github.com/verse-pbc/issues/issues/130)
- Track breadcrumbs in Sentry for all analytics events. [#125](https://github.com/verse-pbc/issues/issues/125)
- Refactored the way the ProfileView downloads data and logs analytics events. [#1748](https://github.com/planetary-social/nos/pull/1748)

## [1.1] - 2025-01-03Z

Expand Down
31 changes: 17 additions & 14 deletions Nos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@
C98298332ADD7F9A0096C5B5 /* DeepLinkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98298322ADD7F9A0096C5B5 /* DeepLinkService.swift */; };
C98298342ADD7F9A0096C5B5 /* DeepLinkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98298322ADD7F9A0096C5B5 /* DeepLinkService.swift */; };
C98651102B0BD49200597B68 /* PagedNoteListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C986510F2B0BD49200597B68 /* PagedNoteListView.swift */; };
C987153D2D4D198200EA2F56 /* OnTabAppearModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C987153C2D4D198200EA2F56 /* OnTabAppearModifier.swift */; };
C987F81729BA4C6A00B44E7A /* BigActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C987F81629BA4C6900B44E7A /* BigActionButton.swift */; };
C987F81A29BA4D0E00B44E7A /* ActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C987F81929BA4D0E00B44E7A /* ActionButton.swift */; };
C987F81D29BA6D9A00B44E7A /* ProfileTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = C987F81C29BA6D9A00B44E7A /* ProfileTab.swift */; };
Expand Down Expand Up @@ -951,6 +952,7 @@
C98298312ADD7EDB0096C5B5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
C98298322ADD7F9A0096C5B5 /* DeepLinkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkService.swift; sourceTree = "<group>"; };
C986510F2B0BD49200597B68 /* PagedNoteListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedNoteListView.swift; sourceTree = "<group>"; };
C987153C2D4D198200EA2F56 /* OnTabAppearModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnTabAppearModifier.swift; sourceTree = "<group>"; };
C987F81629BA4C6900B44E7A /* BigActionButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BigActionButton.swift; sourceTree = "<group>"; };
C987F81929BA4D0E00B44E7A /* ActionButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionButton.swift; sourceTree = "<group>"; };
C987F81C29BA6D9A00B44E7A /* ProfileTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTab.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1868,12 +1870,13 @@
03C7E7A12CB9CD0B0054624C /* PointDownEmojiTipViewStyle.swift */,
C9A25B3C29F174D200B39534 /* ReadabilityPadding.swift */,
C9E37E0E2A1E7C32003D4B0A /* ReportMenuModifier.swift */,
C987153C2D4D198200EA2F56 /* OnTabAppearModifier.swift */,
C9A0DAE629C69FA000466635 /* Text+Gradient.swift */,
04C9D7262CBF09C200EAAD4D /* TextField+PlaceHolderStyle.swift */,
C9DC6CB92C1739AD00E1CFB3 /* View+HandleURLsInRouter.swift */,
50089A162C98678600834588 /* View+ListRowGradientBackground.swift */,
C93EC2FC29C3785C0012EE2A /* View+RoundedCorner.swift */,
50DE6B1A2C6B88FE0065665D /* View+StyledBorder.swift */,
04C9D7262CBF09C200EAAD4D /* TextField+PlaceHolderStyle.swift */,
);
path = Modifiers;
sourceTree = "<group>";
Expand Down Expand Up @@ -2261,7 +2264,7 @@
C9B737702AB24D5F00398BE7 /* XCRemoteSwiftPackageReference "SwiftGenPlugin" */,
C91565BF2B2368FA0068EECA /* XCRemoteSwiftPackageReference "ViewInspector" */,
3AD3185B2B294E6200026B07 /* XCRemoteSwiftPackageReference "xcstrings-tool-plugin" */,
C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1.swift" */,
C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1" */,
C9FD35112BCED5A6008F8D95 /* XCRemoteSwiftPackageReference "nostr-sdk-ios" */,
03C49ABE2C938A9C00502321 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
039389212CA4985C00698978 /* XCRemoteSwiftPackageReference "SDWebImageWebPCoder" */,
Expand Down Expand Up @@ -2575,7 +2578,6 @@
C98CA9042B14FA3D00929141 /* PagedRelaySubscription.swift in Sources */,
5B0D99032A94090A0039F0C5 /* DoubleTapToPopModifier.swift in Sources */,
030024192CC00DFC0073ED56 /* SplashScreenView.swift in Sources */,
50CBD8102D3A8B7000BF8A0B /* ListCircle.swift in Sources */,
0357299B2BE415E5005FEE85 /* ContentWarningController.swift in Sources */,
5BFF66B42A58853D00AA79DD /* PublishedEventsView.swift in Sources */,
03D1B42C2C3C1B0D001778CD /* TLVElement.swift in Sources */,
Expand Down Expand Up @@ -2626,6 +2628,7 @@
65BD8DC22BDAF2C300802039 /* DiscoverTab.swift in Sources */,
65BD8DC32BDAF2C300802039 /* FeaturedAuthorCategory.swift in Sources */,
C93F045E2B9B7A7000AD5872 /* ReplyPreview.swift in Sources */,
C987153D2D4D198200EA2F56 /* OnTabAppearModifier.swift in Sources */,
C97465312A3B89140031226F /* AuthorLabel.swift in Sources */,
C9C547592A4F1D8C006B0741 /* NosNotification+CoreDataClass.swift in Sources */,
030AE4292BE3D63C004DEE02 /* FeaturedAuthor.swift in Sources */,
Expand Down Expand Up @@ -2928,11 +2931,11 @@
/* Begin PBXTargetDependency section */
3AD3185D2B294E9000026B07 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = 3AD3185C2B294E9000026B07 /* plugin:XCStringsToolPlugin */;
productRef = 3AD3185C2B294E9000026B07 /* XCStringsToolPlugin */;
};
3AEABEF32B2BF806001BC933 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = 3AEABEF22B2BF806001BC933 /* plugin:XCStringsToolPlugin */;
productRef = 3AEABEF22B2BF806001BC933 /* XCStringsToolPlugin */;
};
C90862C229E9804B00C35A71 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
Expand All @@ -2941,11 +2944,11 @@
};
C9A6C7442AD83F7A001F9500 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = C9A6C7432AD83F7A001F9500 /* plugin:SwiftGenPlugin */;
productRef = C9A6C7432AD83F7A001F9500 /* SwiftGenPlugin */;
};
C9D573402AB24A3700E06BB4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
productRef = C9D5733F2AB24A3700E06BB4 /* plugin:SwiftGenPlugin */;
productRef = C9D5733F2AB24A3700E06BB4 /* SwiftGenPlugin */;
};
C9DEBFE6298941020078B43A /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
Expand Down Expand Up @@ -3819,7 +3822,7 @@
minimumVersion = 4.0.0;
};
};
C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1.swift" */ = {
C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/GigaBitcoin/secp256k1.swift";
requirement = {
Expand Down Expand Up @@ -3858,12 +3861,12 @@
package = 03C49ABE2C938A9C00502321 /* XCRemoteSwiftPackageReference "SwiftSoup" */;
productName = SwiftSoup;
};
3AD3185C2B294E9000026B07 /* plugin:XCStringsToolPlugin */ = {
3AD3185C2B294E9000026B07 /* XCStringsToolPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = 3AD3185B2B294E6200026B07 /* XCRemoteSwiftPackageReference "xcstrings-tool-plugin" */;
productName = "plugin:XCStringsToolPlugin";
};
3AEABEF22B2BF806001BC933 /* plugin:XCStringsToolPlugin */ = {
3AEABEF22B2BF806001BC933 /* XCStringsToolPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = 3AD3185B2B294E6200026B07 /* XCRemoteSwiftPackageReference "xcstrings-tool-plugin" */;
productName = "plugin:XCStringsToolPlugin";
Expand Down Expand Up @@ -3942,7 +3945,7 @@
package = C99DBF7C2A9E81CF00F7068F /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */;
productName = SDWebImageSwiftUI;
};
C9A6C7432AD83F7A001F9500 /* plugin:SwiftGenPlugin */ = {
C9A6C7432AD83F7A001F9500 /* SwiftGenPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = C9B737702AB24D5F00398BE7 /* XCRemoteSwiftPackageReference "SwiftGenPlugin" */;
productName = "plugin:SwiftGenPlugin";
Expand All @@ -3962,7 +3965,7 @@
package = C9B71DBC2A8E9BAD0031ED9F /* XCRemoteSwiftPackageReference "sentry-cocoa" */;
productName = Sentry;
};
C9D5733F2AB24A3700E06BB4 /* plugin:SwiftGenPlugin */ = {
C9D5733F2AB24A3700E06BB4 /* SwiftGenPlugin */ = {
isa = XCSwiftPackageProductDependency;
package = C9C8450C2AB249DB00654BC1 /* XCRemoteSwiftPackageReference "SwiftGenPlugin" */;
productName = "plugin:SwiftGenPlugin";
Expand All @@ -3974,12 +3977,12 @@
};
C9FD34F52BCEC89C008F8D95 /* secp256k1 */ = {
isa = XCSwiftPackageProductDependency;
package = C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1.swift" */;
package = C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1" */;
productName = secp256k1;
};
C9FD34F72BCEC8B5008F8D95 /* secp256k1 */ = {
isa = XCSwiftPackageProductDependency;
package = C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1.swift" */;
package = C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1" */;
productName = secp256k1;
};
C9FD35122BCED5A6008F8D95 /* NostrSDK */ = {
Expand Down
2 changes: 2 additions & 0 deletions Nos/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import Dependencies
@Dependency(\.persistenceController) private var persistenceController
@Dependency(\.relayService) private var relayService
@Dependency(\.crashReporting) private var crashReporting
@Dependency(\.analytics) private var analytics

/// The `NavigationPath` of the tab (or side menu) the user currently has open.
/// This has to be a two-way binding, but really the only things that should be modifying it are the `Router`
Expand Down Expand Up @@ -104,6 +105,7 @@ import Dependencies

/// Pushes a profile view for the given author.
func push(_ author: Author) {
analytics.showedProfile()
push(.author(author.hexadecimalPublicKey))
}

Expand Down
10 changes: 7 additions & 3 deletions Nos/Service/Analytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,29 @@ class Analytics {
}

func showedHome() {
track("Home Tab Tapped")
track("Home Tab Opened")
}

func showedDiscover() {
track("Discover Tab Tapped")
track("Discover Tab Opened")
}

func showedNoteComposer() {
track("New Note Tapped")
}

func showedNotifications() {
track("Notifications Tab Tapped")
track("Notifications Tab Opened")
}

func showedProfile() {
track("Profile View Opened")
}

func showedProfileTab() {
track("Profile Tab Opened")
}

func showedThread() {
track("Thread View Opened")
}
Expand Down
2 changes: 1 addition & 1 deletion Nos/Views/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ struct AppView: View {
.badge(pushNotificationService.badgeCount)

if let author = currentUser.author {
ProfileTab(author: author, path: $router.profilePath)
ProfileTab(author: author)
.tabItem {
VStack {
let text = Text("profileTitle")
Expand Down
16 changes: 2 additions & 14 deletions Nos/Views/Discover/DiscoverTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ struct DiscoverTab: View {

@State var columns: Int = 0

@State private var isVisible = false

@State private var searchController = SearchController()

@FocusState private var isSearching: Bool
Expand Down Expand Up @@ -56,18 +54,8 @@ struct DiscoverTab: View {
}
.background(Color.appBg)
.animation(.easeInOut, value: columns)
.onAppear {
if router.selectedTab == .discover {
isVisible = true
}
}
.onDisappear {
isVisible = false
}
.onChange(of: isVisible) {
if isVisible {
analytics.showedDiscover()
}
.onTabAppear(.discover) {
analytics.showedDiscover()
}
.nosNavigationBar("discover")
.toolbarBackground(.visible, for: .navigationBar)
Expand Down
17 changes: 4 additions & 13 deletions Nos/Views/Home/HomeFeedView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ struct HomeFeedView: View {

@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject private var router: Router
@ObservationIgnored @Dependency(\.analytics) private var analytics
@Dependency(\.analytics) private var analytics
@ObserveInjection var inject

@State private var refreshController = RefreshController(lastRefreshDate: Date.now + Self.staticLoadTime)
@State private var isVisible = false
@State private var feedController: FeedController

/// When set to true this will display a fullscreen progress wheel for a set amount of time to give us a chance
Expand Down Expand Up @@ -188,17 +187,9 @@ struct HomeFeedView: View {
.navigationBarTitle("", displayMode: .inline)
.padding(.top, 1)
.environment(feedController)
.onAppear {
if router.selectedTab == .home {
isVisible = true
}
}
.onDisappear { isVisible = false }
.onChange(of: isVisible) {
if isVisible {
analytics.showedHome()
GoToFeedTip.viewedFeed.sendDonation()
}
.onTabAppear(.home) {
analytics.showedHome()
GoToFeedTip.viewedFeed.sendDonation()
}
.onChange(of: shouldNavigateToListsOnAppear) {
if shouldNavigateToListsOnAppear {
Expand Down
46 changes: 46 additions & 0 deletions Nos/Views/Modifiers/OnTabAppearModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import SwiftUI

/// A view modifier that helps track when a tab becomes visible or invisible in a TabView.
struct OnTabAppearModifier: ViewModifier {
@EnvironmentObject private var router: Router
let tab: AppDestination
let onAppear: (() async -> Void)?
let onDisappear: (() async -> Void)?

@State private var isVisible = false

func body(content: Content) -> some View {
content
.onAppear {
if router.selectedTab == tab {
isVisible = true
}
}
.onDisappear { isVisible = false }
.onChange(of: isVisible) {
if isVisible {
Task { await onAppear?() }
} else {
Task { await onDisappear?() }
}
}
}
}

extension View {
/// Executes an action when a specific tab becomes visible
/// - Parameters:
/// - tab: The tab to monitor for visibility
/// - action: The action to perform when the tab becomes visible
func onTabAppear(_ tab: AppDestination, perform action: @escaping () async -> Void) -> some View {
modifier(OnTabAppearModifier(tab: tab, onAppear: action, onDisappear: nil))
}

/// Executes an action when a specific tab is navigated away from
/// - Parameters:
/// - tab: The tab to monitor for visibility
/// - action: The action to perform when the tab becomes invisible
func onTabDisappear(_ tab: AppDestination, perform action: @escaping () async -> Void) -> some View {
modifier(OnTabAppearModifier(tab: tab, onAppear: nil, onDisappear: action))
}
}
24 changes: 7 additions & 17 deletions Nos/Views/Notifications/NotificationsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,25 +94,15 @@ struct NotificationsView: View {
.refreshable {
await subscribeToNewEvents()
}
.onAppear {
if router.selectedTab == .notifications {
isVisible = true
}
.onTabAppear(.notifications) {
pushNotificationService.requestNotificationPermissionsFromUser()
analytics.showedNotifications()
await subscribeToNewEvents()
await markAllNotificationsRead()
}
.onDisappear {
isVisible = false
}
.onChange(of: isVisible) {
Task { await markAllNotificationsRead() }
if isVisible {
analytics.showedNotifications()
Task {
await subscribeToNewEvents()
}
} else {
Task { await cancelSubscriptions() }
}
.onTabDisappear(.notifications) {
await cancelSubscriptions()
await markAllNotificationsRead()
}
.doubleTapToPop(tab: .notifications) { proxy in
if let firstEvent = events.first {
Expand Down
10 changes: 7 additions & 3 deletions Nos/Views/Profile/ProfileTab.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import Combine
import SwiftUI
import Dependencies

/// A version of the ProfileView that is displayed in the main tab bar
struct ProfileTab: View {

@Environment(CurrentUser.self) var currentUser
@EnvironmentObject private var router: Router
@Dependency(\.analytics) private var analytics
@ObservedObject var author: Author

@Binding var path: NavigationPath

var body: some View {
NosNavigationStack(path: $path) {
NosNavigationStack(path: $router.profilePath) {
ProfileView(author: author, addDoubleTapToPop: true)
.navigationBarItems(leading: SideMenuButton())
.onTabAppear(.profile) {
analytics.showedProfileTab()
}
}
}
}
10 changes: 2 additions & 8 deletions Nos/Views/Profile/ProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,8 @@ struct ProfileView: View {
}
)
.alert(unwrapping: $alert)
.onAppear {
Task {
await downloadAuthorData()
}
analytics.showedProfile()
}
.onDisappear {
relaySubscriptions.removeAll()
.task {
await downloadAuthorData()
}
}

Expand Down