commit b3d9ee3fc096aae23e02cbfd6f1e8d4b8fd28323
parent e65219ee3e942ab3ab359ed8a2d59c36919ba665
Author: Terry Yiu <git@tyiu.xyz>
Date: Wed, 4 Jun 2025 00:14:34 -0400
Add tip in threads to inform users what trusted network means
Changelog-Added: Added tip in threads to inform users what trusted network means
Signed-off-by: Terry Yiu <git@tyiu.xyz>
Diffstat:
8 files changed, 88 insertions(+), 1 deletion(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -27,6 +27,9 @@
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4647CE2A413ADC00386AD8 /* CondensedProfilePicturesView.swift */; };
3A48E7B029DFBE9D006E787E /* MutedThreadsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */; };
+ 3A515C502DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C4F2DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift */; };
+ 3A515C512DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C4F2DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift */; };
+ 3A515C522DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A515C4F2DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift */; };
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */; };
3A92C0FE2DE16E9800CEEBAC /* FaviconCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A92C0FD2DE16E9800CEEBAC /* FaviconCache.swift */; };
3A92C0FF2DE16E9800CEEBAC /* FaviconCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A92C0FD2DE16E9800CEEBAC /* FaviconCache.swift */; };
@@ -1870,6 +1873,7 @@
3A47CB782BDA05A200728A7C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
3A47CB792BDA05A200728A7C /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fi; path = fi.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
3A48E7AF29DFBE9D006E787E /* MutedThreadsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutedThreadsManager.swift; sourceTree = "<group>"; };
+ 3A515C4F2DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedNetworkRepliesTip.swift; sourceTree = "<group>"; };
3A5C4575296A879E0032D398 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-419"; path = "es-419.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A5CAE1D298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3A5CAE1E298DC0DB00B5334F /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
@@ -2748,6 +2752,14 @@
path = "Empty Views";
sourceTree = "<group>";
};
+ 3A515C4E2DF4E0E6002D3B34 /* Tips */ = {
+ isa = PBXGroup;
+ children = (
+ 3A515C4F2DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift */,
+ );
+ path = Tips;
+ sourceTree = "<group>";
+ };
3AA24800297E3DAE0090C62D /* Reposts */ = {
isa = PBXGroup;
children = (
@@ -3245,6 +3257,7 @@
4C190F232A547D1700027FD5 /* NostrScript */,
4C7D095A2A098C5C00943473 /* Wallet */,
4C8D1A6D29F31E4100ACDF75 /* Buttons */,
+ 3A515C4E2DF4E0E6002D3B34 /* Tips */,
4C1A9A2829DDF53B00516EAC /* Video */,
4C1A9A1B29DDCF8B00516EAC /* Settings */,
4CFF8F6129CC9A80008DB934 /* Images */,
@@ -4766,6 +4779,7 @@
D7100C5A2B76FD5100C59298 /* LogoView.swift in Sources */,
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
4C3AC79F2833115300E1F516 /* FollowButtonView.swift in Sources */,
+ 3A515C502DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */,
D7CB5D3B2B112FBB00AD4105 /* NotificationFormatter.swift in Sources */,
4C4E137B2A76D5FB00BDD832 /* MuteThreadNotify.swift in Sources */,
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
@@ -5416,6 +5430,7 @@
82D6FB9D2CD99F7900C925F4 /* ZapButtonModel.swift in Sources */,
5C09FD142DF283D700823661 /* FollowPackModel.swift in Sources */,
82D6FB9E2CD99F7900C925F4 /* ContentFilters.swift in Sources */,
+ 3A515C512DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */,
82D6FB9F2CD99F7900C925F4 /* DamusCacheManager.swift in Sources */,
82D6FBA02CD99F7900C925F4 /* NotificationsManager.swift in Sources */,
D755B28E2D3E7D8800BBEEFA /* NIP37Draft.swift in Sources */,
@@ -5671,6 +5686,7 @@
D73E5E2B2C6A97F4007EB227 /* PostNotify.swift in Sources */,
D73E5E2C2C6A97F4007EB227 /* PresentSheetNotify.swift in Sources */,
D73E5E2D2C6A97F4007EB227 /* ProfileUpdatedNotify.swift in Sources */,
+ 3A515C522DF4E100002D3B34 /* TrustedNetworkRepliesTip.swift in Sources */,
D73E5E2E2C6A97F4007EB227 /* ReportNotify.swift in Sources */,
D73E5E2F2C6A97F4007EB227 /* ScrollToTopNotify.swift in Sources */,
D73E5E302C6A97F4007EB227 /* SwitchedTimelineNotify.swift in Sources */,
diff --git a/damus/ContentView.swift b/damus/ContentView.swift
@@ -10,6 +10,10 @@ import AVKit
import MediaPlayer
import EmojiPicker
+#if canImport(TipKit)
+import TipKit
+#endif
+
struct ZapSheet {
let target: ZapTarget
let lnurl: String
@@ -705,6 +709,21 @@ struct ContentView: View {
damus_state.nostrNetwork.pool.register_handler(sub_id: sub_id, handler: home.handle_event)
damus_state.nostrNetwork.connect()
+
+ if #available(iOS 17, *) {
+ if damus_state.settings.developer_mode && damus_state.settings.reset_tips_on_launch {
+ do {
+ try Tips.resetDatastore()
+ } catch {
+ Log.error("Failed to reset tips datastore: %s", for: .tips, error.localizedDescription)
+ }
+ }
+ do {
+ try Tips.configure()
+ } catch {
+ Log.error("Failed to configure tips: %s", for: .tips, error.localizedDescription)
+ }
+ }
}
func music_changed(_ state: MusicState) {
diff --git a/damus/Models/UserSettingsStore.swift b/damus/Models/UserSettingsStore.swift
@@ -131,6 +131,9 @@ class UserSettingsStore: ObservableObject {
@Setting(key: "show_trusted_replies_first", default_value: true)
var show_trusted_replies_first: Bool
+ @Setting(key: "reset_tips_on_launch", default_value: false)
+ var reset_tips_on_launch: Bool
+
@Setting(key: "hide_nsfw_tagged_content", default_value: false)
var hide_nsfw_tagged_content: Bool
diff --git a/damus/Util/Log.swift b/damus/Util/Log.swift
@@ -21,6 +21,7 @@ enum LogCategory: String {
case damus_purple
case image_uploading
case video_coordination
+ case tips
}
/// Damus structured logger
diff --git a/damus/Views/Chat/ChatroomThreadView.swift b/damus/Views/Chat/ChatroomThreadView.swift
@@ -7,6 +7,7 @@
import SwiftUI
import SwipeActions
+import TipKit
struct ChatroomThreadView: View {
@Environment(\.dismiss) var dismiss
@@ -159,6 +160,12 @@ struct ChatroomThreadView: View {
// MARK: - Children view - outside trusted network
if !untrusted_events.isEmpty {
+ if #available(iOS 17, *) {
+ TipView(TrustedNetworkRepliesTip.shared, arrowEdge: .bottom)
+ .padding(.top, 10)
+ .padding(.horizontal)
+ }
+
VStack(alignment: .leading, spacing: 0) {
// Track this section's position
Color.clear
@@ -184,6 +191,10 @@ struct ChatroomThreadView: View {
withAnimation {
untrusted_network_expanded.toggle()
+ if #available(iOS 17, *) {
+ TrustedNetworkRepliesTip.shared.invalidate(reason: .actionPerformed)
+ }
+
scroll_to_event(scroller: scroller, id: ChatroomThreadView.untrusted_network_section_id, delay: 0.1, animate: true, anchor: ChatroomThreadView.sticky_header_adjusted_anchor)
}
}) {
diff --git a/damus/Views/Notifications/NotificationsView.swift b/damus/Views/Notifications/NotificationsView.swift
@@ -7,6 +7,10 @@
import SwiftUI
+#if canImport(TipKit)
+import TipKit
+#endif
+
class NotificationFilter: ObservableObject, Equatable {
@Published var state: NotificationFilterState
@Published var friend_filter: FriendFilter
@@ -75,7 +79,9 @@ struct NotificationsView: View {
@StateObject var filter = NotificationFilter()
@SceneStorage("NotificationsView.filter_state") var filter_state: NotificationFilterState = .all
@Binding var subtitle: String?
-
+
+ @State var showTip: Bool = true
+
@Environment(\.colorScheme) var colorScheme
var body: some View {
diff --git a/damus/Views/Settings/DeveloperSettingsView.swift b/damus/Views/Settings/DeveloperSettingsView.swift
@@ -96,6 +96,11 @@ struct DeveloperSettingsView: View {
Toggle(NSLocalizedString("Enable experimental Purple In-app purchase support", comment: "Developer mode setting to enable experimental Purple In-app purchase support."), isOn: $settings.enable_experimental_purple_iap_support)
.toggleStyle(.switch)
+
+ if #available(iOS 17, *) {
+ Toggle(NSLocalizedString("Reset tips on launch", comment: "Developer mode setting to reset tips upon app first launch. Tips are visual contextual hints that highlight new, interesting, or unused features users have not discovered yet."), isOn: $settings.reset_tips_on_launch)
+ .toggleStyle(.switch)
+ }
}
}
}
diff --git a/damus/Views/Tips/TrustedNetworkRepliesTip.swift b/damus/Views/Tips/TrustedNetworkRepliesTip.swift
@@ -0,0 +1,26 @@
+//
+// TrustedNetworkRepliesTip.swift
+// damus
+//
+// Created by Terry Yiu on 6/7/25.
+//
+
+import Foundation
+import TipKit
+
+@available(iOS 17, *)
+struct TrustedNetworkRepliesTip: Tip {
+ static let shared = TrustedNetworkRepliesTip()
+
+ var title: Text {
+ Text("Toggle visibility of replies from outside your trusted network", comment: "Title of tip that informs users what trusted network means and that they can toggle the visibility of threaded replies from outside their trusted network.")
+ }
+
+ var message: Text? {
+ Text("Your trusted network is comprised of profiles you follow and profiles that they follow.", comment: "Description of the tip that informs users what trusted network means.")
+ }
+
+ var image: Image? {
+ Image(systemName: "network.badge.shield.half.filled")
+ }
+}