diff --git a/CHANGELOG.md b/CHANGELOG.md index a46ada2a7..54e447075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Release Notes +- Added a tip to Discover to prompt first-time users to go to their Feed. [#1601](https://github.com/planetary-social/nos/issues/1601) - Added a tip to the Feed to welcome first-time users and explain how the Feed works. [#1602](https://github.com/planetary-social/nos/issues/1602) - Added a tag to published contact lists to help us detect the source of lost contact lists. [cleanstr#51](https://github.com/planetary-social/cleanstr/issues/51) - Removed integration with Universal Name Space [#1636](https://github.com/planetary-social/nos/issues/1636) diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index 3ae447dd8..c986d5075 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -92,6 +92,7 @@ 039C961F2C480F4100A8EB39 /* unsupported_kinds.json in Resources */ = {isa = PBXBuildFile; fileRef = 039C961E2C480F4100A8EB39 /* unsupported_kinds.json */; }; 039C96292C48321E00A8EB39 /* long_form_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 039C96282C48321E00A8EB39 /* long_form_data.json */; }; 03A3AA3B2C5028FF008FE153 /* PublicKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A3AA3A2C5028FF008FE153 /* PublicKeyTests.swift */; }; + 03A743452CC048C700893CAE /* GoToFeedTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A743442CC048C700893CAE /* GoToFeedTip.swift */; }; 03B4E6A22C125CA1006E5F59 /* nostr_build_nip96_upload_response.json in Resources */ = {isa = PBXBuildFile; fileRef = 03B4E6A12C125CA1006E5F59 /* nostr_build_nip96_upload_response.json */; }; 03B4E6AC2C125D13006E5F59 /* FileStorageUploadResponseJSONTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B4E6AB2C125D13006E5F59 /* FileStorageUploadResponseJSONTests.swift */; }; 03B4E6AE2C125D61006E5F59 /* FileStorageUploadResponseJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B4E6AD2C125D61006E5F59 /* FileStorageUploadResponseJSON.swift */; }; @@ -102,6 +103,7 @@ 03C68C522C94D8400037DC59 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 03C68C512C94D8400037DC59 /* SwiftSoup */; }; 03C7E7922CB9C0B30054624C /* WelcomeToFeedTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C7E7912CB9C0AF0054624C /* WelcomeToFeedTip.swift */; }; 03C7E7982CB9C16C0054624C /* InlineTipViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C7E7972CB9C1600054624C /* InlineTipViewStyle.swift */; }; + 03C7E7A22CB9CD150054624C /* PointDownEmojiTipViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C7E7A12CB9CD0B0054624C /* PointDownEmojiTipViewStyle.swift */; }; 03C8B4962C6D065900A07CCD /* ImageViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03C8B4952C6D065900A07CCD /* ImageViewer.swift */; }; 03D1B4282C3C1A5D001778CD /* NostrIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D1B4272C3C1A5D001778CD /* NostrIdentifier.swift */; }; 03D1B4292C3C1AC9001778CD /* NostrIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03D1B4272C3C1A5D001778CD /* NostrIdentifier.swift */; }; @@ -638,6 +640,7 @@ 039C961E2C480F4100A8EB39 /* unsupported_kinds.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = unsupported_kinds.json; sourceTree = ""; }; 039C96282C48321E00A8EB39 /* long_form_data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = long_form_data.json; sourceTree = ""; }; 03A3AA3A2C5028FF008FE153 /* PublicKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicKeyTests.swift; sourceTree = ""; }; + 03A743442CC048C700893CAE /* GoToFeedTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoToFeedTip.swift; sourceTree = ""; }; 03AB2F7D2BF6609500B73DB1 /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; 03B4E6A12C125CA1006E5F59 /* nostr_build_nip96_upload_response.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = nostr_build_nip96_upload_response.json; sourceTree = ""; }; 03B4E6AB2C125D13006E5F59 /* FileStorageUploadResponseJSONTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileStorageUploadResponseJSONTests.swift; sourceTree = ""; }; @@ -645,6 +648,7 @@ 03C49AC12C938DE100502321 /* SoupOpenGraphParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoupOpenGraphParser.swift; sourceTree = ""; }; 03C7E7912CB9C0AF0054624C /* WelcomeToFeedTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeToFeedTip.swift; sourceTree = ""; }; 03C7E7972CB9C1600054624C /* InlineTipViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineTipViewStyle.swift; sourceTree = ""; }; + 03C7E7A12CB9CD0B0054624C /* PointDownEmojiTipViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointDownEmojiTipViewStyle.swift; sourceTree = ""; }; 03C8B4952C6D065900A07CCD /* ImageViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewer.swift; sourceTree = ""; }; 03D1B4272C3C1A5D001778CD /* NostrIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrIdentifier.swift; sourceTree = ""; }; 03D1B42B2C3C1B0D001778CD /* TLVElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TLVElement.swift; sourceTree = ""; }; @@ -1535,6 +1539,7 @@ 65BD8DBE2BDAF2C300802039 /* DiscoverTab.swift */, 030AE4282BE3D63C004DEE02 /* FeaturedAuthor.swift */, 65BD8DBF2BDAF2C300802039 /* FeaturedAuthorCategory.swift */, + 03A743442CC048C700893CAE /* GoToFeedTip.swift */, ); path = Discover; sourceTree = ""; @@ -1691,10 +1696,11 @@ C9CFF6D02AB241EB00D4B368 /* Modifiers */ = { isa = PBXGroup; children = ( - 03C7E7972CB9C1600054624C /* InlineTipViewStyle.swift */, 5B0D99022A94090A0039F0C5 /* DoubleTapToPopModifier.swift */, + 03C7E7972CB9C1600054624C /* InlineTipViewStyle.swift */, C95D68A8299E709800429F86 /* LinearGradient+Planetary.swift */, C95D68A4299E6E1E00429F86 /* PlaceholderModifier.swift */, + 03C7E7A12CB9CD0B0054624C /* PointDownEmojiTipViewStyle.swift */, C9A25B3C29F174D200B39534 /* ReadabilityPadding.swift */, C9E37E0E2A1E7C32003D4B0A /* ReportMenuModifier.swift */, C9A0DAE629C69FA000466635 /* Text+Gradient.swift */, @@ -2331,6 +2337,7 @@ C9AC31AD2A55E0BD00A94E5A /* NotificationViewModel.swift in Sources */, 0326347A2C10C57A00E489B5 /* FileStorageAPIClient.swift in Sources */, C9EE3E632A053910008A7491 /* ExpirationTimeOption.swift in Sources */, + 03C7E7A22CB9CD150054624C /* PointDownEmojiTipViewStyle.swift in Sources */, C9A0DAE029C697A100466635 /* AboutView.swift in Sources */, 0496D6312C975E6900D29375 /* FlagOptionPicker.swift in Sources */, A351E1A229BA92240009B7F6 /* ProfileEditView.swift in Sources */, @@ -2381,6 +2388,7 @@ C9671D73298DB94C00EE7E12 /* Data+Encoding.swift in Sources */, 03C7E7922CB9C0B30054624C /* WelcomeToFeedTip.swift in Sources */, C9646EA129B7A22C007239A4 /* Analytics.swift in Sources */, + 03A743452CC048C700893CAE /* GoToFeedTip.swift in Sources */, 5045540D2C81E10C0044ECAE /* EditableAvatarView.swift in Sources */, DC4AB2F62A4475B800D1478A /* AppDelegate.swift in Sources */, 5B8C96B229DB313300B73AEC /* AuthorCard.swift in Sources */, diff --git a/Nos/Assets/Localization/Localizable.xcstrings b/Nos/Assets/Localization/Localizable.xcstrings index 6e6b52ef6..cd458535c 100644 --- a/Nos/Assets/Localization/Localizable.xcstrings +++ b/Nos/Assets/Localization/Localizable.xcstrings @@ -8920,6 +8920,18 @@ } } }, + "goToYourFeed" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Go to your Feed to see notes from your connections." + } + } + } + }, + "homeFeed" : { "extractionState" : "manual", "localizations" : { diff --git a/Nos/Service/Analytics.swift b/Nos/Service/Analytics.swift index bbef658f7..320350e15 100644 --- a/Nos/Service/Analytics.swift +++ b/Nos/Service/Analytics.swift @@ -43,7 +43,7 @@ class Analytics { func completedOnboarding() { track("Completed Onboarding") } - + func showedHome() { track("Home Tab Tapped") } @@ -232,7 +232,7 @@ class Analytics { track("Push Notification Registration Failed", properties: ["reason": reason]) } - // MARK: NIP-05 Usernames + // MARK: - NIP-05 Usernames func showedNIP05Wizard() { track("Showed NIP-05 Wizard") @@ -250,8 +250,8 @@ class Analytics { track("Deleted NIP-05 Username") } - // MARK: Message Actions - + // MARK: - Message Actions + func copiedNoteIdentifier() { track("Copied Note Identifier") } @@ -276,7 +276,8 @@ class Analytics { track("Liked Note") } - // MARK: Uploads + // MARK: - Uploads + func selectedUploadFromCamera() { track("Selected Upload From Camera") } diff --git a/Nos/Views/Components/Button/CircularFollowButton.swift b/Nos/Views/Components/Button/CircularFollowButton.swift index 30c565d88..b3f249bda 100644 --- a/Nos/Views/Components/Button/CircularFollowButton.swift +++ b/Nos/Views/Components/Button/CircularFollowButton.swift @@ -32,6 +32,7 @@ struct CircularFollowButton: View { } else { try await currentUser.follow(author: author) analytics.followed(author) + GoToFeedTip.followedAccount.sendDonation() } } catch { crashReporting.report(error) diff --git a/Nos/Views/Components/Button/FollowButton.swift b/Nos/Views/Components/Button/FollowButton.swift index 2a55a5d7e..a194a840d 100644 --- a/Nos/Views/Components/Button/FollowButton.swift +++ b/Nos/Views/Components/Button/FollowButton.swift @@ -39,6 +39,7 @@ struct FollowButton: View { } else { try await currentUser.follow(author: author) analytics.followed(author) + GoToFeedTip.followedAccount.sendDonation() } } catch { crashReporting.report(error) diff --git a/Nos/Views/Discover/DiscoverTab.swift b/Nos/Views/Discover/DiscoverTab.swift index 603f6f5bc..f7f2f814e 100644 --- a/Nos/Views/Discover/DiscoverTab.swift +++ b/Nos/Views/Discover/DiscoverTab.swift @@ -2,8 +2,9 @@ import SwiftUI import Combine import CoreData import Dependencies +import TipKit -struct DiscoverTab: View { +struct DiscoverTab: View { // MARK: - Properties @EnvironmentObject private var router: Router @@ -20,6 +21,8 @@ struct DiscoverTab: View { @State var predicate: NSPredicate = .false + let goToFeedTip = GoToFeedTip() + // MARK: - View var body: some View { @@ -29,6 +32,17 @@ struct DiscoverTab: View { featuredAuthorCategory: .all, searchController: searchController ) + + VStack { + Spacer() + + TipView(goToFeedTip) + .padding(.horizontal, 12) + .padding(.bottom, 8) + .readabilityPadding() + .tipBackground(LinearGradient.horizontalAccentReversed) + .tipViewStyle(.pointDownEmoji) + } } .searchable( text: $searchController.query, diff --git a/Nos/Views/Discover/GoToFeedTip.swift b/Nos/Views/Discover/GoToFeedTip.swift new file mode 100644 index 000000000..87600398d --- /dev/null +++ b/Nos/Views/Discover/GoToFeedTip.swift @@ -0,0 +1,26 @@ +import TipKit + +/// A tip that's displayed on the Discover view after the user has followed three accounts. +struct GoToFeedTip: Tip { + /// A TipKit Event that tracks the number of accounts that have been followed. + static let followedAccount = Tips.Event(id: "followedAccount") + + /// A TipKit Event that tracks how many times the Feed has been displayed. + static let viewedFeed = Tips.Event(id: "viewedFeed") + + var title: Text { + Text("goToYourFeed") + } + + var rules: [Rule] { + // Each rule here is combined using the logical AND, so all rules must return true for the tip to display. + + #Rule(Self.followedAccount) { + $0.donations.count >= 3 + } + + #Rule(Self.viewedFeed) { + $0.donations.count < 1 + } + } +} diff --git a/Nos/Views/Home/HomeFeedView.swift b/Nos/Views/Home/HomeFeedView.swift index 1a435739f..fd501a3fa 100644 --- a/Nos/Views/Home/HomeFeedView.swift +++ b/Nos/Views/Home/HomeFeedView.swift @@ -154,13 +154,14 @@ struct HomeFeedView: View { .nosNavigationBar(title: navigationBarTitle) .onAppear { if router.selectedTab == .home { - isVisible = true + isVisible = true } } .onDisappear { isVisible = false } .onChange(of: isVisible) { if isVisible { analytics.showedHome() + GoToFeedTip.viewedFeed.sendDonation() } } } diff --git a/Nos/Views/Modifiers/PointDownEmojiTipViewStyle.swift b/Nos/Views/Modifiers/PointDownEmojiTipViewStyle.swift new file mode 100644 index 000000000..7abd3e25c --- /dev/null +++ b/Nos/Views/Modifiers/PointDownEmojiTipViewStyle.swift @@ -0,0 +1,34 @@ +import TipKit + +/// A popover tip view style that has a point down emoji (👇) in the bottom left in addition to the title and +/// close button. +struct PointDownEmojiTipViewStyle: TipViewStyle { + func makeBody(configuration: TipViewStyle.Configuration) -> some View { + VStack(alignment: .leading, spacing: 10) { + HStack(alignment: .top) { + configuration.title + .font(.body.bold()) + .foregroundStyle(Color.primaryTxt) + .shadow(color: .lightShadow, radius: 2, x: 0, y: 1) + + Spacer() + + Button { + configuration.tip.invalidate(reason: .tipClosed) + } label: { + Image(systemName: "xmark").scaledToFit() + .foregroundStyle(Color.primaryTxt) + .font(.system(size: 20).bold()) + } + } + + Text("👇") + } + .padding(.vertical, 20) + .padding(.horizontal, 16) + } +} + +extension TipViewStyle where Self == PointDownEmojiTipViewStyle { + static var pointDownEmoji: Self { Self() } +}