Compare commits

..

20 Commits

Author SHA1 Message Date
1767a677bb Fix profile view toolbar alignment bug in iOS 18
Changelog-Fixed: Fix profile view toolbar alignment bug in iOS 18
2024-07-29 10:21:06 -04:00
Daniel D’Aquino
dba1799df0 Merge pull request #2338 from ericholguin/move-posting-timeline
refactor: move posting timeline
2024-07-24 10:53:00 -07:00
Daniel D’Aquino
2db3d7310f Add changelog for v1.8 and 1.9 (14) App Store releases
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-07-19 13:12:52 -07:00
Daniel D’Aquino
10b1cf64ae Merge pull request #2330 from ericholguin/highlight-fixes
highlight: fixes and improvements
2024-07-19 10:50:30 -07:00
ericholguin
afdd3f1d43 refactor: move posting timeline
This patch simply moves the PostingTimelineView into its own file outside of
ContentView.

Signed-off-by: ericholguin <ericholguin@apache.org>
2024-07-15 20:10:13 -06:00
ericholguin
1b8e3fe184 highlight: fixes and improvements
This patch allows highlights to be included in posts as well as removes context
when creating a highlight. Highlights now route as the root and selecting the
highlight in root routes to the highlighted event.

Signed-off-by: ericholguin <ericholguin@apache.org>
2024-07-15 19:58:59 -06:00
William Casarin
8ab1c6a899 restore localization for custom tabs
Signed-off-by: William Casarin <jb55@jb55.com>
2024-07-15 12:27:04 -07:00
Daniel D’Aquino
e8fae19b97 Version bump to 1.11 2024-07-15 12:19:56 -07:00
William Casarin
63e70605fc Revert "Update README.md"
This reverts commit fa70c376b1.
2024-07-14 21:30:05 -07:00
Daniel D’Aquino
35df9f7ab7 Add support for OnlyZaps mode on the new chat thread
With this commit, long-presses on chat bubbles will now reveal a zap
sheet if they are on OnlyZaps mode and have zaps unlocked.

Users without OnlyZaps or with Zaps blocked will continue to see the
emoji reaction sheet

Closes: https://github.com/damus-io/damus/issues/2327
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-07-14 21:26:03 -07:00
Daniel D’Aquino
605d88add1 Fix issue where very long names would appear in two lines on the chat event view
Closes: https://github.com/damus-io/damus/issues/2329
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-07-14 21:26:03 -07:00
Daniel D’Aquino
2b0a7d126d Add missing mention view from chat event bubble view
This commit adds event mentions to the chat bubbles.

Testing
-------

PASS

Damus: This commit
Device: iPhone 15 simulator
iOS: 17.5
Coverage:
- Tested referencing an event on a thread reply. Thread reply shows up as expected
- Checked appearance on light and dark mode
- Tapping on the mentioned event takes the user to that event

Closes: https://github.com/damus-io/damus/issues/2309
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-07-14 21:26:03 -07:00
Daniel D’Aquino
6e2c133faa Truncate long text messages on chat event view
Very large messages on the chat event view cause issues with swipe and
long-press interactions, and they might be a nuisance during scrolling.

This commit adds text truncation to the chat event view. The "show
more" button causes the user to navigate to the message, which is
reasonable to avoid overloading too many interactions on the same view
and having a huge text bubble that is difficult to interact with.

Closes: https://github.com/damus-io/damus/issues/2326
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-07-14 21:26:03 -07:00
Daniel D’Aquino
9885ff1912 Reduce minimum width for chat event view
Some users have reported that there is unwanted horizontal padding on
small messages. This was due to the minimum chat event view width. To
address this feedback, the minimum width has been reduced to a very
small amount, so that small messages with no other content can more
tightly hug the inner content.

Closes: https://github.com/damus-io/damus/issues/2312
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-07-14 21:26:03 -07:00
William Casarin
abb818bbd4 Fix crash in profile related to profile updates
Changelog-Fixed: Fix crash on profile page when there are profile updates
Signed-off-by: William Casarin <jb55@jb55.com>
2024-07-14 21:26:03 -07:00
William Casarin
f1dc023e18 fix crash when adding duplicate mute items
Changelog-Fixed: Fix crash when adding duplicate mute items
Signed-off-by: William Casarin <jb55@jb55.com>
2024-07-14 21:26:03 -07:00
William Casarin
4a332c7ffa simplify CustomPicker and fix ios18 runtime error
This fixes a reflection runtime error for our custom picker

Fixes: https://github.com/damus-io/damus/issues/2332
Signed-off-by: William Casarin <jb55@jb55.com>
2024-07-14 21:26:03 -07:00
William Casarin
616f730ae5 flatbuffers: don't crash if there are flatbuffer errors
Some japanese user profiles are breaking the flatbuffer profile
builder for some reason

Changelog-Fixed: Fix pretty bad crash when building flatbuffer profiles
Signed-off-by: William Casarin <jb55@jb55.com>
2024-07-14 21:26:03 -07:00
Daniel D’Aquino
164cea96f3 Merge pull request #2325 from tyiu/reactions-view-fix
Fix reactions view to not show reactions from replies on parent note
2024-07-13 11:23:05 -07:00
alltheseas
fa70c376b1 Update README.md
Added NIP-70 to NIP list
2024-07-10 01:02:46 +03:00
23 changed files with 410 additions and 183 deletions

View File

@@ -1,3 +1,79 @@
## [1.9 (14)] - 2024-07-14
### Added
- Completely new threads experience that is easier and more pleasant to use (Daniel DAquino)
- Add emoji search to emoji picker (Terry Yiu)
### Changed
- Added first aid contact damus support email (alltheseas)
- Disable mutiny wallet button (William Casarin)
- Make friends show up first when searching for profiles (Terry Yiu)
### Fixed
- Fix crash on profile page when there are profile updates (William Casarin)
- Fix crash when adding duplicate mute items (William Casarin)
- Fix pretty bad crash when building flatbuffer profiles (William Casarin)
- Fix reactions view to not show reactions from replies on parent note (Terry Yiu)
- Fix missing Mute button in profile view menu (Terry Yiu)
- Fixed wallet not disconnecting when a user logs out (ericholguin)
- Fix stale feed issue when follow list is too big (Daniel DAquino)
[1.9 (14)]: https://github.com/damus-io/damus/releases/tag/v1.9-14
## [1.8] - 2024-05-11
### Added
- Added nip10 marker replies (William Casarin)
- Add marker nip10 support when reading notes (William Casarin)
- Added title image and tags to longform events (ericholguin)
- Add First Aid solution for users who do not have a contact list created for their account (Daniel DAquino)
- Relay fees metadata (ericholguin)
- Added callbackuri for a better ux when connecting mutiny wallet nwc (ericholguin)
- Add event content preview to the full screen carousel (Daniel DAquino)
- Show list of quoted reposts in threads (William Casarin)
- Proxy Tags are now viewable on Selected Events (ericholguin)
- Connect to Mutiny Wallet Button (ericholguin)
- Add ability to mute words, add new mutelist interface (Charlie) (William Casarin)
- Add ability to mute hashtag from SearchView (Charlie Fish)
### Changed
- Change reactions to use a native looking emoji picker (Terry Yiu)
- Relay detail design (ericholguin)
- Updated Zeus logo (ericholguin)
- Improve UX around video playback (Daniel DAquino)
- Moved paste nwc button to main wallet view (ericholguin)
- Errors with an NWC will show as an alert (ericholguin)
- Relay config view user interface (ericholguin)
- Always strip GPS data from images (kernelkind)
### Fixed
- Fix thread bug where a quote isn't picked up as a reply (William Casarin)
- Fixed threads not loading sometimes (William Casarin)
- Fixed issue where some replies were including the q tag (William Casarin)
- Fixed issue where timeline was scrolling when it isn't supposed to (William Casarin)
- Fix issue where bootstrap relays would inadvertently be added to the user's list on connectivity issues (Daniel DAquino)
- Fix broken GIF uploads (Daniel DAquino)
- Fix ghost notifications caused by Purple impending expiration notifications (Daniel DAquino)
- Improve reliability of contact list creation during onboarding (Daniel DAquino)
- Fix emoji reactions being cut off (ericholguin)
- Fix image indicators to limit number of dots to not spill screen beyond visible margins (ericholguin)
- Fix bug that would cause connection issues with relays defined with a trailing slash URL, and an inability to delete them. (Daniel DAquino)
- Issue where NWC Scanner view would not dismiss after a failed scan/paste (ericholguin)
[1.8]: https://github.com/damus-io/damus/releases/tag/v1.8
## [1.7-rc2] - 2024-02-28 ## [1.7-rc2] - 2024-02-28
### Added ### Added

View File

@@ -407,6 +407,7 @@
5C7389B12B6EFA7100781E0A /* ProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B02B6EFA7100781E0A /* ProxyView.swift */; }; 5C7389B12B6EFA7100781E0A /* ProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B02B6EFA7100781E0A /* ProxyView.swift */; };
5C7389B72B9E692E00781E0A /* MutinyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B62B9E692E00781E0A /* MutinyButton.swift */; }; 5C7389B72B9E692E00781E0A /* MutinyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B62B9E692E00781E0A /* MutinyButton.swift */; };
5C7389B92B9E69ED00781E0A /* MutinyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */; }; 5C7389B92B9E69ED00781E0A /* MutinyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */; };
5C8711DE2C460C06007879C2 /* PostingTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C8711DD2C460C06007879C2 /* PostingTimelineView.swift */; };
5CC8529D2BD741CD0039FFC5 /* HighlightEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */; }; 5CC8529D2BD741CD0039FFC5 /* HighlightEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */; };
5CC8529F2BD744F60039FFC5 /* HighlightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529E2BD744F60039FFC5 /* HighlightView.swift */; }; 5CC8529F2BD744F60039FFC5 /* HighlightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529E2BD744F60039FFC5 /* HighlightView.swift */; };
5CC852A22BDED9B90039FFC5 /* HighlightDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */; }; 5CC852A22BDED9B90039FFC5 /* HighlightDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */; };
@@ -1348,6 +1349,7 @@
5C7389B02B6EFA7100781E0A /* ProxyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyView.swift; sourceTree = "<group>"; }; 5C7389B02B6EFA7100781E0A /* ProxyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyView.swift; sourceTree = "<group>"; };
5C7389B62B9E692E00781E0A /* MutinyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyButton.swift; sourceTree = "<group>"; }; 5C7389B62B9E692E00781E0A /* MutinyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyButton.swift; sourceTree = "<group>"; };
5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyGradient.swift; sourceTree = "<group>"; }; 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyGradient.swift; sourceTree = "<group>"; };
5C8711DD2C460C06007879C2 /* PostingTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostingTimelineView.swift; sourceTree = "<group>"; };
5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightEvent.swift; sourceTree = "<group>"; }; 5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightEvent.swift; sourceTree = "<group>"; };
5CC8529E2BD744F60039FFC5 /* HighlightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightView.swift; sourceTree = "<group>"; }; 5CC8529E2BD744F60039FFC5 /* HighlightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightView.swift; sourceTree = "<group>"; };
5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDescription.swift; sourceTree = "<group>"; }; 5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDescription.swift; sourceTree = "<group>"; };
@@ -2447,6 +2449,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */, 4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */,
5C8711DD2C460C06007879C2 /* PostingTimelineView.swift */,
); );
path = Timeline; path = Timeline;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -3175,6 +3178,7 @@
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */, 4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */, 4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */,
4CC14FF12A73FCDB007AEB17 /* Pubkey.swift in Sources */, 4CC14FF12A73FCDB007AEB17 /* Pubkey.swift in Sources */,
5C8711DE2C460C06007879C2 /* PostingTimelineView.swift in Sources */,
4CA9275D2A28FF630098A105 /* LongformView.swift in Sources */, 4CA9275D2A28FF630098A105 /* LongformView.swift in Sources */,
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */, 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
D7EDED1E2B11797D0018B19C /* LongformEvent.swift in Sources */, D7EDED1E2B11797D0018B19C /* LongformEvent.swift in Sources */,
@@ -4096,7 +4100,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
); );
MARKETING_VERSION = 1.10; MARKETING_VERSION = 1.11;
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2; PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -4147,7 +4151,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)", "$(PROJECT_DIR)",
); );
MARKETING_VERSION = 1.10; MARKETING_VERSION = 1.11;
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2; PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";

View File

@@ -12,31 +12,25 @@ let RECTANGLE_GRADIENT = LinearGradient(gradient: Gradient(colors: [
DamusColors.blue DamusColors.blue
]), startPoint: .leading, endPoint: .trailing) ]), startPoint: .leading, endPoint: .trailing)
struct CustomPicker<SelectionValue: Hashable, Content: View>: View { struct CustomPicker<SelectionValue: Hashable>: View {
let tabs: [(String, SelectionValue)]
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
@Namespace var picker @Namespace var picker
@Binding var selection: SelectionValue @Binding var selection: SelectionValue
@ViewBuilder let content: Content
public var body: some View { public var body: some View {
let contentMirror = Mirror(reflecting: content)
let blocksCount = Mirror(reflecting: contentMirror.descendant("value")!).children.count
HStack { HStack {
ForEach(0..<blocksCount, id: \.self) { index in ForEach(tabs, id: \.1) { (text, tag) in
let tupleBlock = contentMirror.descendant("value", ".\(index)")
let text = Mirror(reflecting: tupleBlock!).descendant("content") as! Text
let tag = Mirror(reflecting: tupleBlock!).descendant("modifier", "value", "tagged") as! SelectionValue
Button { Button {
withAnimation(.spring()) { withAnimation(.spring()) {
selection = tag selection = tag
} }
} label: { } label: {
text Text(text).padding(EdgeInsets(top: 15, leading: 0, bottom: 10, trailing: 0))
.padding(EdgeInsets(top: 15, leading: 0, bottom: 10, trailing: 0))
.font(.system(size: 14, weight: .heavy)) .font(.system(size: 14, weight: .heavy))
.tag(tag)
} }
.background( .background(
Group { Group {

View File

@@ -79,71 +79,15 @@ struct ContentView: View {
@State var hide_bar: Bool = false @State var hide_bar: Bool = false
@State var user_muted_confirm: Bool = false @State var user_muted_confirm: Bool = false
@State var confirm_overwrite_mutelist: Bool = false @State var confirm_overwrite_mutelist: Bool = false
@SceneStorage("ContentView.filter_state") var filter_state : FilterState = .posts_and_replies
@State private var isSideBarOpened = false @State private var isSideBarOpened = false
var home: HomeModel = HomeModel() var home: HomeModel = HomeModel()
@StateObject var navigationCoordinator: NavigationCoordinator = NavigationCoordinator() @StateObject var navigationCoordinator: NavigationCoordinator = NavigationCoordinator()
@AppStorage("has_seen_suggested_users") private var hasSeenOnboardingSuggestions = false @AppStorage("has_seen_suggested_users") private var hasSeenOnboardingSuggestions = false
let sub_id = UUID().description let sub_id = UUID().description
@Environment(\.colorScheme) var colorScheme
// connect retry timer // connect retry timer
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var mystery: some View {
Text("Are you lost?", comment: "Text asking the user if they are lost in the app.")
.id("what")
}
func content_filter(_ fstate: FilterState) -> ((NostrEvent) -> Bool) {
var filters = ContentFilters.defaults(damus_state: damus_state!)
filters.append(fstate.filter)
return ContentFilters(filters: filters).filter
}
var PostingTimelineView: some View {
VStack {
ZStack {
TabView(selection: $filter_state) {
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
mystery
contentTimelineView(filter: content_filter(.posts))
.tag(FilterState.posts)
.id(FilterState.posts)
contentTimelineView(filter: content_filter(.posts_and_replies))
.tag(FilterState.posts_and_replies)
.id(FilterState.posts_and_replies)
}
.tabViewStyle(.page(indexDisplayMode: .never))
if privkey != nil {
PostButtonContainer(is_left_handed: damus_state?.settings.left_handed ?? false) {
self.active_sheet = .post(.posting(.none))
}
}
}
}
.safeAreaInset(edge: .top, spacing: 0) {
VStack(spacing: 0) {
CustomPicker(selection: $filter_state, content: {
Text("Notes", comment: "Label for filter for seeing only notes (instead of notes and replies).").tag(FilterState.posts)
Text("Notes & Replies", comment: "Label for filter for seeing notes and replies (instead of only notes).").tag(FilterState.posts_and_replies)
})
Divider()
.frame(height: 1)
}
.background(colorScheme == .dark ? Color.black : Color.white)
}
}
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
TimelineView(events: home.events, loading: .constant(false), damus: damus_state, show_friend_icon: false, filter: filter) {
PullDownSearchView(state: damus_state, on_cancel: {})
}
}
func navIsAtRoot() -> Bool { func navIsAtRoot() -> Bool {
return navigationCoordinator.isAtRoot() return navigationCoordinator.isAtRoot()
} }
@@ -171,7 +115,7 @@ struct ContentView: View {
} }
case .home: case .home:
PostingTimelineView PostingTimelineView(damus_state: damus_state!, home: home, active_sheet: $active_sheet)
case .notifications: case .notifications:
NotificationsView(state: damus, notifications: home.notifications) NotificationsView(state: damus, notifications: home.notifications)

View File

@@ -16,7 +16,7 @@ enum FilterState : Int {
func filter(ev: NostrEvent) -> Bool { func filter(ev: NostrEvent) -> Bool {
switch self { switch self {
case .posts: case .posts:
return ev.known_kind == .boost || !ev.is_reply() return ev.known_kind == .boost || ev.known_kind == .highlight || !ev.is_reply()
case .posts_and_replies: case .posts_and_replies:
return true return true
} }

View File

@@ -111,12 +111,16 @@ class MutelistManager {
private func add_mute_item(_ item: MuteItem) { private func add_mute_item(_ item: MuteItem) {
switch item { switch item {
case .user(_, _): case .user(_, _):
guard !users.contains(item) else { return }
users.insert(item) users.insert(item)
case .hashtag(_, _): case .hashtag(_, _):
guard !hashtags.contains(item) else { return }
hashtags.insert(item) hashtags.insert(item)
case .word(_, _): case .word(_, _):
guard !words.contains(item) else { return }
words.insert(item) words.insert(item)
case .thread(_, _): case .thread(_, _):
guard !threads.contains(item) else { return }
threads.insert(item) threads.insert(item)
} }
} }

View File

@@ -11,13 +11,15 @@ import Foundation
class ThreadModel: ObservableObject { class ThreadModel: ObservableObject {
@Published var event: NostrEvent @Published var event: NostrEvent
let original_event: NostrEvent let original_event: NostrEvent
let highlight: String?
var event_map: Set<NostrEvent> var event_map: Set<NostrEvent>
init(event: NostrEvent, damus_state: DamusState) { init(event: NostrEvent, damus_state: DamusState, highlight: String? = nil) {
self.damus_state = damus_state self.damus_state = damus_state
self.event_map = Set() self.event_map = Set()
self.event = event self.event = event
self.original_event = event self.original_event = event
self.highlight = highlight
add_event(event, keypair: damus_state.keypair) add_event(event, keypair: damus_state.keypair)
} }

View File

@@ -36,6 +36,13 @@ let test_short_note =
createdAt: UInt32(Date().timeIntervalSince1970 - 100) createdAt: UInt32(Date().timeIntervalSince1970 - 100)
)! )!
let test_super_short_note =
NostrEvent(
content: "A",
keypair: jack_keypair,
createdAt: UInt32(Date().timeIntervalSince1970 - 100)
)!
let test_note_json_with_escaped_slash = "{\"tags\":[],\"pubkey\":\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\",\"content\":\"https:\\/\\/cdn.nostr.build\\/i\\/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg\",\"created_at\":1691864981,\"kind\":1,\"sig\":\"fc0033aa3d4df50b692a5b346fa816fdded698de2045e36e0642a021391468c44ca69c2471adc7e92088131872d4aaa1e90ea6e1ad97f3cc748f4aed96dfae18\",\"id\":\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\"}" let test_note_json_with_escaped_slash = "{\"tags\":[],\"pubkey\":\"f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9\",\"content\":\"https:\\/\\/cdn.nostr.build\\/i\\/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg\",\"created_at\":1691864981,\"kind\":1,\"sig\":\"fc0033aa3d4df50b692a5b346fa816fdded698de2045e36e0642a021391468c44ca69c2471adc7e92088131872d4aaa1e90ea6e1ad97f3cc748f4aed96dfae18\",\"id\":\"e8f6eca3b161abba034dac9a02bb6930ecde9fd2fb5d6c5f22a05526e11382cb\"}"
let test_encoded_note_with_image = NostrEvent.owned_from_json(json: test_note_json_with_escaped_slash) let test_encoded_note_with_image = NostrEvent.owned_from_json(json: test_note_json_with_escaped_slash)

View File

@@ -31,7 +31,7 @@ struct ChatEventView: View {
@State var long_press_bounce_work_item: DispatchWorkItem? @State var long_press_bounce_work_item: DispatchWorkItem?
@State var popover_state: PopoverState = .closed { @State var popover_state: PopoverState = .closed {
didSet { didSet {
let generator = UIImpactFeedbackGenerator(style: popover_state == .open_emoji_selector ? .heavy : .light) let generator = UIImpactFeedbackGenerator(style: popover_state.some_sheet_open() ? .heavy : .light)
generator.impactOccurred() generator.impactOccurred()
} }
} }
@@ -43,6 +43,11 @@ struct ChatEventView: View {
enum PopoverState: String { enum PopoverState: String {
case closed case closed
case open_emoji_selector case open_emoji_selector
case open_zap_sheet
func some_sheet_open() -> Bool {
return self == .open_zap_sheet || self == .open_emoji_selector
}
} }
var just_started: Bool { var just_started: Bool {
@@ -93,6 +98,19 @@ struct ChatEventView: View {
var is_ours: Bool { return !by_other_user } var is_ours: Bool { return !by_other_user }
// MARK: Zapping properties
var lnurl: String? {
damus_state.profiles.lookup_with_timestamp(event.pubkey)?.map({ pr in
pr?.lnurl
}).value
}
var zap_target: ZapTarget {
ZapTarget.note(id: event.id, author: event.pubkey)
}
// MARK: Views
var event_bubble: some View { var event_bubble: some View {
ChatBubble( ChatBubble(
direction: is_ours ? .right : .left, direction: is_ours ? .right : .left,
@@ -107,6 +125,7 @@ struct ChatEventView: View {
.onTapGesture { .onTapGesture {
show_profile_action_sheet_if_enabled(damus_state: damus_state, pubkey: event.pubkey) show_profile_action_sheet_if_enabled(damus_state: damus_state, pubkey: event.pubkey)
} }
.lineLimit(1)
Text(verbatim: "\(format_relative_time(event.created_at))") Text(verbatim: "\(format_relative_time(event.created_at))")
.foregroundColor(.gray) .foregroundColor(.gray)
} }
@@ -124,13 +143,18 @@ struct ChatEventView: View {
} }
let blur_images = should_blur_images(settings: damus_state.settings, contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey) let blur_images = should_blur_images(settings: damus_state.settings, contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
NoteContentView(damus_state: damus_state, event: event, blur_images: blur_images, size: .normal, options: []) NoteContentView(damus_state: damus_state, event: event, blur_images: blur_images, size: .normal, options: [.truncate_content])
.padding(2) .padding(2)
if let mention = first_eref_mention(ev: event, keypair: damus_state.keypair) {
MentionView(damus_state: damus_state, mention: mention)
.background(DamusColors.adaptableWhite)
.clipShape(RoundedRectangle(cornerSize: CGSize(width: 10, height: 10)))
}
} }
.frame(minWidth: 150, alignment: is_ours ? .trailing : .leading) .frame(minWidth: 5, alignment: is_ours ? .trailing : .leading)
.padding(10) .padding(10)
} }
.tint(is_ours ? Color.white : Color.accentColor) .tint(Color.accentColor)
.overlay( .overlay(
ZStack(alignment: is_ours ? .bottomLeading : .bottomTrailing) { ZStack(alignment: is_ours ? .bottomLeading : .bottomTrailing) {
VStack { VStack {
@@ -164,6 +188,14 @@ struct ChatEventView: View {
EmojiPickerView(selectedEmoji: $selected_emoji, emojiProvider: damus_state.emoji_provider) EmojiPickerView(selectedEmoji: $selected_emoji, emojiProvider: damus_state.emoji_provider)
}.presentationDetents([.medium, .large]) }.presentationDetents([.medium, .large])
} }
.sheet(isPresented: Binding(get: { popover_state == .open_zap_sheet }, set: { new_state in
withAnimation(new_state == true ? .easeIn(duration: 0.5) : .easeOut(duration: 0.1)) {
popover_state = new_state == true ? .open_zap_sheet : .closed
}
})) {
ZapSheetViewIfPossible(damus_state: damus_state, target: zap_target, lnurl: lnurl)
.presentationDetents([.medium, .large])
}
.onChange(of: selected_emoji) { newSelectedEmoji in .onChange(of: selected_emoji) { newSelectedEmoji in
if let newSelectedEmoji { if let newSelectedEmoji {
send_like(emoji: newSelectedEmoji.value) send_like(emoji: newSelectedEmoji.value)
@@ -171,8 +203,8 @@ struct ChatEventView: View {
} }
} }
} }
.scaleEffect(self.popover_state == .open_emoji_selector ? 1.08 : is_pressing ? 1.02 : 1) .scaleEffect(self.popover_state.some_sheet_open() ? 1.08 : is_pressing ? 1.02 : 1)
.shadow(color: (is_pressing || self.popover_state == .open_emoji_selector) ? .black.opacity(0.1) : .black.opacity(0.3), radius: (is_pressing || self.popover_state == .open_emoji_selector) ? 8 : 0, y: (is_pressing || self.popover_state == .open_emoji_selector) ? 15 : 0) .shadow(color: (is_pressing || self.popover_state.some_sheet_open()) ? .black.opacity(0.1) : .black.opacity(0.3), radius: (is_pressing || self.popover_state.some_sheet_open()) ? 8 : 0, y: (is_pressing || self.popover_state.some_sheet_open()) ? 15 : 0)
.onLongPressGesture(minimumDuration: 0.5, maximumDistance: 10, perform: { .onLongPressGesture(minimumDuration: 0.5, maximumDistance: 10, perform: {
long_press_bounce_work_item?.cancel() long_press_bounce_work_item?.cancel()
}, onPressingChanged: { is_pressing in }, onPressingChanged: { is_pressing in
@@ -186,7 +218,8 @@ struct ChatEventView: View {
// Ensure the action is performed only if the condition is still valid // Ensure the action is performed only if the condition is still valid
if self.is_pressing { if self.is_pressing {
withAnimation(.bouncy(duration: 0.2, extraBounce: 0.35)) { withAnimation(.bouncy(duration: 0.2, extraBounce: 0.35)) {
popover_state = .open_emoji_selector let should_show_zap_sheet = !damus_state.settings.nozaps && damus_state.settings.onlyzaps_mode
popover_state = should_show_zap_sheet ? .open_zap_sheet : .open_emoji_selector
} }
} }
} }
@@ -254,13 +287,25 @@ struct ChatEventView: View {
SwipeView { SwipeView {
self.event_bubble_with_long_press_interaction self.event_bubble_with_long_press_interaction
} leadingActions: { context in } leadingActions: { context in
EventActionBar( if !is_ours {
damus_state: damus_state, EventActionBar(
event: event, damus_state: damus_state,
bar: bar, event: event,
options: is_ours ? [.swipe_action_menu_reverse] : [.swipe_action_menu], bar: bar,
swipe_context: context options: is_ours ? [.swipe_action_menu_reverse] : [.swipe_action_menu],
) swipe_context: context
)
}
} trailingActions: { context in
if is_ours {
EventActionBar(
damus_state: damus_state,
event: event,
bar: bar,
options: is_ours ? [.swipe_action_menu_reverse] : [.swipe_action_menu],
swipe_context: context
)
}
} }
.swipeSpacing(-20) .swipeSpacing(-20)
.swipeActionsStyle(.mask) .swipeActionsStyle(.mask)
@@ -322,3 +367,8 @@ extension Notification.Name {
let bar = make_actionbar_model(ev: test_note.id, damus: test_damus_state) let bar = make_actionbar_model(ev: test_note.id, damus: test_damus_state)
return ChatEventView(event: test_short_note, selected_event: test_note, prev_ev: nil, next_ev: nil, damus_state: test_damus_state, thread: ThreadModel(event: test_note, damus_state: test_damus_state), scroll_to_event: nil, focus_event: nil, highlight_bubble: true, bar: bar) return ChatEventView(event: test_short_note, selected_event: test_note, prev_ev: nil, next_ev: nil, damus_state: test_damus_state, thread: ThreadModel(event: test_note, damus_state: test_damus_state), scroll_to_event: nil, focus_event: nil, highlight_bubble: true, bar: bar)
} }
#Preview {
let bar = make_actionbar_model(ev: test_note.id, damus: test_damus_state)
return ChatEventView(event: test_super_short_note, selected_event: test_note, prev_ev: nil, next_ev: nil, damus_state: test_damus_state, thread: ThreadModel(event: test_note, damus_state: test_damus_state), scroll_to_event: nil, focus_event: nil, highlight_bubble: false, bar: bar)
}

View File

@@ -72,12 +72,10 @@ struct DirectMessagesView: View {
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
CustomPicker(selection: $dm_type, content: { CustomPicker(tabs: [
Text("DMs", comment: "Picker option for DM selector for seeing only DMs that have been responded to. DM is the English abbreviation for Direct Message.") (NSLocalizedString("DMs", comment: "Picker option for DM selector for seeing only DMs that have been responded to. DM is the English abbreviation for Direct Message."), DMType.friend),
.tag(DMType.friend) (NSLocalizedString("Requests", comment: "Picker option for DM selector for seeing only message requests (DMs that someone else sent the user which has not been responded to yet"), DMType.rando),
Text("Requests", comment: "Picker option for DM selector for seeing only message requests (DMs that someone else sent the user which has not been responded to yet). DM is the English abbreviation for Direct Message.") ], selection: $dm_type)
.tag(DMType.rando)
})
Divider() Divider()
.frame(height: 1) .frame(height: 1)

View File

@@ -15,18 +15,12 @@ struct ReplyPart: View {
var body: some View { var body: some View {
Group { Group {
if let reply_ref = event.thread_reply()?.reply { if event.known_kind == .highlight {
let highlighted_note = event.highlighted_note_id().flatMap { events.lookup($0) }
HighlightDescription(event: event, highlighted_event: highlighted_note, ndb: ndb)
} else if let reply_ref = event.thread_reply()?.reply {
let replying_to = events.lookup(reply_ref.note_id) let replying_to = events.lookup(reply_ref.note_id)
if event.known_kind != .highlight { ReplyDescription(event: event, replying_to: replying_to, ndb: ndb)
ReplyDescription(event: event, replying_to: replying_to, ndb: ndb)
} else if event.known_kind == .highlight {
HighlightDescription(event: event, highlighted_event: replying_to, ndb: ndb)
}
else {
EmptyView()
}
} else {
EmptyView()
} }
} }
} }

View File

@@ -37,6 +37,12 @@ struct EventBody: View {
} }
} else if event.known_kind == .highlight { } else if event.known_kind == .highlight {
HighlightBodyView(state: damus_state, ev: event, options: options) HighlightBodyView(state: damus_state, ev: event, options: options)
.onTapGesture {
if let highlighted_note = event.highlighted_note_id().flatMap({ damus_state.events.lookup($0) }) {
let thread = ThreadModel(event: highlighted_note, damus_state: damus_state, highlight: event.content)
damus_state.nav.push(route: Route.Thread(thread: thread))
}
}
} else { } else {
note_content note_content
} }

View File

@@ -29,8 +29,7 @@ struct HighlightPostView: View {
Spacer() Spacer()
Button(NSLocalizedString("Post", comment: "Button to post a highlight.")) { Button(NSLocalizedString("Post", comment: "Button to post a highlight.")) {
var tags: [[String]] = [ ["e", "\(self.event.id)"] ] let tags: [[String]] = [ ["e", "\(self.event.id)"] ]
tags.append(["context", self.event.content])
let kind = NostrKind.highlight.rawValue let kind = NostrKind.highlight.rawValue
guard let ev = NostrEvent(content: selectedText, keypair: damus_state.keypair, kind: kind, tags: tags) else { guard let ev = NostrEvent(content: selectedText, keypair: damus_state.keypair, kind: kind, tags: tags) else {
@@ -53,7 +52,7 @@ struct HighlightPostView: View {
HStack { HStack {
var attributedString: AttributedString { var attributedString: AttributedString {
var attributedString = AttributedString(self.event.content) var attributedString = AttributedString(selectedText)
if let range = attributedString.range(of: selectedText) { if let range = attributedString.range(of: selectedText) {
attributedString[range].backgroundColor = DamusColors.highlight attributedString[range].backgroundColor = DamusColors.highlight

View File

@@ -40,19 +40,8 @@ struct SelectedEventView: View {
.minimumScaleFactor(0.75) .minimumScaleFactor(0.75)
.lineLimit(1) .lineLimit(1)
if let reply_ref = event.thread_reply()?.reply { ReplyPart(events: damus.events, event: event, keypair: damus.keypair, ndb: damus.ndb)
let replying_to = damus.events.lookup(reply_ref.note_id) .padding(.horizontal)
if event.known_kind == .highlight {
HighlightDescription(event: event, highlighted_event: replying_to, ndb: damus.ndb)
.padding(.horizontal)
} else {
ReplyDescription(event: event, replying_to: replying_to, ndb: damus.ndb)
.padding(.horizontal)
}
} else if event.known_kind == .highlight {
HighlightDescription(event: event, highlighted_event: nil, ndb: damus.ndb)
.padding(.horizontal)
}
ProxyView(event: event) ProxyView(event: event)
.padding(.top, 5) .padding(.top, 5)

View File

@@ -160,10 +160,10 @@ struct FollowingView: View {
.navigationBarTitle(NSLocalizedString("Following", comment: "Navigation bar title for view that shows who a user is following.")) .navigationBarTitle(NSLocalizedString("Following", comment: "Navigation bar title for view that shows who a user is following."))
.safeAreaInset(edge: .top, spacing: 0) { .safeAreaInset(edge: .top, spacing: 0) {
VStack(spacing: 0) { VStack(spacing: 0) {
CustomPicker(selection: $tab_selection, content: { CustomPicker(tabs: [
Text("People", comment: "Label for filter for seeing only people follows.").tag(FollowingViewTabSelection.people) (NSLocalizedString("People", comment: "Label for filter for seeing only people follows."), FollowingViewTabSelection.people),
Text("Hashtags", comment: "Label for filter for seeing only hashtag follows.").tag(FollowingViewTabSelection.hashtags) (NSLocalizedString("Hashtags", comment: "Label for filter for seeing only hashtag follows."), FollowingViewTabSelection.hashtags)
}) ], selection: $tab_selection)
Divider() Divider()
.frame(height: 1) .frame(height: 1)
} }

View File

@@ -391,6 +391,11 @@ struct NoteContentView_Previews: PreviewProvider {
} }
.previewDisplayName("Short note") .previewDisplayName("Short note")
VStack {
NoteContentView(damus_state: state, event: test_super_short_note, blur_images: true, size: .normal, options: [])
}
.previewDisplayName("Super short note")
VStack { VStack {
NoteContentView(damus_state: state, event: test_encoded_note_with_image!, blur_images: false, size: .normal, options: []) NoteContentView(damus_state: state, event: test_encoded_note_with_image!, blur_images: false, size: .normal, options: [])
} }

View File

@@ -117,17 +117,11 @@ struct NotificationsView: View {
} }
.safeAreaInset(edge: .top, spacing: 0) { .safeAreaInset(edge: .top, spacing: 0) {
VStack(spacing: 0) { VStack(spacing: 0) {
CustomPicker(selection: $filter_state, content: { CustomPicker(tabs: [
Text("All", comment: "Label for filter for all notifications.") (NSLocalizedString("All", comment: "Label for filter for all notifications."), NotificationFilterState.all),
.tag(NotificationFilterState.all) (NSLocalizedString("Zaps", comment: "Label for filter for zap notifications."), NotificationFilterState.zaps),
(NSLocalizedString("Mentions", comment: "Label for filter for seeing mention notifications (replies, etc)."), NotificationFilterState.replies),
Text("Zaps", comment: "Label for filter for zap notifications.") ], selection: $filter_state)
.tag(NotificationFilterState.zaps)
Text("Mentions", comment: "Label for filter for seeing mention notifications (replies, etc).")
.tag(NotificationFilterState.replies)
})
Divider() Divider()
.frame(height: 1) .frame(height: 1)
} }

View File

@@ -125,31 +125,34 @@ struct ProfileName: View {
return return
} }
var profile: Profile!
var profile_txn: NdbTxn<Profile?>!
switch update { switch update {
case .remote(let pubkey): case .remote(let pubkey):
profile_txn = damus_state.profiles.lookup(id: pubkey) guard let profile_txn = damus_state.profiles.lookup(id: pubkey),
guard let prof = profile_txn.unsafeUnownedValue else { return } let prof = profile_txn.unsafeUnownedValue else {
profile = prof return
}
handle_profile_update(profile: prof)
case .manual(_, let prof): case .manual(_, let prof):
profile = prof handle_profile_update(profile: prof)
} }
let display_name = Profile.displayName(profile: profile, pubkey: pubkey) }
if self.display_name != display_name { }
self.display_name = display_name
}
let nip05 = damus_state.profiles.is_validated(pubkey) @MainActor
if nip05 != self.nip05 { func handle_profile_update(profile: Profile) {
self.nip05 = nip05 let display_name = Profile.displayName(profile: profile, pubkey: pubkey)
} if self.display_name != display_name {
self.display_name = display_name
}
if donation != profile.damus_donation { let nip05 = damus_state.profiles.is_validated(pubkey)
donation = profile.damus_donation if nip05 != self.nip05 {
} self.nip05 = nip05
}
if donation != profile.damus_donation {
donation = profile.damus_donation
} }
} }
} }

View File

@@ -217,16 +217,6 @@ struct ProfileView: View {
} }
} }
var customNavbar: some View {
HStack {
navBackButton
Spacer()
navActionSheetButton
}
.padding(.top, 5)
.accentColor(DamusColors.white)
}
func lnButton(unownedProfile: Profile?, record: ProfileRecord?) -> some View { func lnButton(unownedProfile: Profile?, record: ProfileRecord?) -> some View {
return ProfileZapLinkView(unownedProfileRecord: record, profileModel: self.profile) { reactions_enabled, lud16, lnurl in return ProfileZapLinkView(unownedProfileRecord: record, profileModel: self.profile) { reactions_enabled, lud16, lnurl in
Image(reactions_enabled ? "zap.fill" : "zap") Image(reactions_enabled ? "zap.fill" : "zap")
@@ -434,10 +424,10 @@ struct ProfileView: View {
aboutSection aboutSection
VStack(spacing: 0) { VStack(spacing: 0) {
CustomPicker(selection: $filter_state, content: { CustomPicker(tabs: [
Text("Notes", comment: "Label for filter for seeing only your notes (instead of notes and replies).").tag(FilterState.posts) (NSLocalizedString("Notes", comment: "Label for filter for seeing only notes (instead of notes and replies)."), FilterState.posts),
Text("Notes & Replies", comment: "Label for filter for seeing your notes and replies (instead of only your notes).").tag(FilterState.posts_and_replies) (NSLocalizedString("Notes & Replies", comment: "Label for filter for seeing notes and replies (instead of only notes)."), FilterState.posts_and_replies)
}) ], selection: $filter_state)
Divider() Divider()
.frame(height: 1) .frame(height: 1)
} }
@@ -458,8 +448,15 @@ struct ProfileView: View {
.navigationTitle("") .navigationTitle("")
.navigationBarBackButtonHidden() .navigationBarBackButtonHidden()
.toolbar { .toolbar {
ToolbarItem(placement: .principal) { ToolbarItem(placement: .topBarLeading) {
customNavbar navBackButton
.padding(.top, 5)
.accentColor(DamusColors.white)
}
ToolbarItem(placement: .topBarTrailing) {
navActionSheetButton
.padding(.top, 5)
.accentColor(DamusColors.white)
} }
} }
.toolbarBackground(.hidden) .toolbarBackground(.hidden)

View File

@@ -0,0 +1,80 @@
//
// PostingTimelineView.swift
// damus
//
// Created by eric on 7/15/24.
//
import SwiftUI
struct PostingTimelineView: View {
let damus_state: DamusState
var home: HomeModel
@State var search: String = ""
@State var results: [NostrEvent] = []
@State var initialOffset: CGFloat?
@State var offset: CGFloat?
@State var showSearch: Bool = true
@Binding var active_sheet: Sheets?
@FocusState private var isSearchFocused: Bool
@State private var contentOffset: CGFloat = 0
@State private var indicatorWidth: CGFloat = 0
@State private var indicatorPosition: CGFloat = 0
@SceneStorage("PostingTimelineView.filter_state") var filter_state : FilterState = .posts_and_replies
var mystery: some View {
Text("Are you lost?", comment: "Text asking the user if they are lost in the app.")
.id("what")
}
func content_filter(_ fstate: FilterState) -> ((NostrEvent) -> Bool) {
var filters = ContentFilters.defaults(damus_state: damus_state)
filters.append(fstate.filter)
return ContentFilters(filters: filters).filter
}
func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
TimelineView(events: home.events, loading: .constant(false), damus: damus_state, show_friend_icon: false, filter: filter) {
PullDownSearchView(state: damus_state, on_cancel: {})
}
}
var body: some View {
VStack {
ZStack {
TabView(selection: $filter_state) {
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
mystery
contentTimelineView(filter: content_filter(.posts))
.tag(FilterState.posts)
.id(FilterState.posts)
contentTimelineView(filter: content_filter(.posts_and_replies))
.tag(FilterState.posts_and_replies)
.id(FilterState.posts_and_replies)
}
.tabViewStyle(.page(indexDisplayMode: .never))
if damus_state.keypair.privkey != nil {
PostButtonContainer(is_left_handed: damus_state.settings.left_handed) {
self.active_sheet = .post(.posting(.none))
}
}
}
}
.safeAreaInset(edge: .top, spacing: 0) {
VStack(spacing: 0) {
CustomPicker(tabs: [
(NSLocalizedString("Notes", comment: "Label for filter for seeing only notes (instead of notes and replies)."), FilterState.posts),
(NSLocalizedString("Notes & Replies", comment: "Label for filter for seeing notes and replies (instead of only notes)."), FilterState.posts_and_replies)
],
selection: $filter_state)
Divider()
.frame(height: 1)
}
.background(DamusColors.adaptableWhite)
}
}
}

View File

@@ -295,6 +295,64 @@ struct CustomizeZapView: View {
} }
} }
struct ZapSheetViewIfPossible: View {
let damus_state: DamusState
let target: ZapTarget
let lnurl: String?
var zap_sheet: ZapSheet? {
guard let lnurl else { return nil }
return ZapSheet(target: target, lnurl: lnurl)
}
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
var body: some View {
if let zap_sheet {
CustomizeZapView(state: damus_state, target: zap_sheet.target, lnurl: zap_sheet.lnurl)
}
else {
zap_sheet_not_possible
}
}
var zap_sheet_not_possible: some View {
VStack(alignment: .center, spacing: 20) {
Image(systemName: "bolt.trianglebadge.exclamationmark.fill")
.resizable()
.scaledToFit()
.frame(width: 70)
Text("User not zappable", comment: "Headline indicating a user cannot be zapped")
.font(.headline)
Text("This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?", comment: "Comment explaining why a user cannot be zapped.")
.multilineTextAlignment(.center)
.opacity(0.6)
self.dm_button
}
.padding()
}
var dm_button: some View {
let dm_model = damus_state.dms.lookup_or_create(target.pubkey)
return VStack(alignment: .center, spacing: 10) {
Button(
action: {
damus_state.nav.push(route: Route.DMChat(dms: dm_model))
dismiss()
},
label: {
Image("messages")
.profile_button_style(scheme: colorScheme)
}
)
.buttonStyle(NeutralButtonShape.circle.style)
Text("Orange-pill", comment: "Button label that allows the user to start a direct message conversation with the user shown on-screen, to orange-pill them (i.e. help them to setup zaps)")
.foregroundStyle(.secondary)
.font(.caption)
}
}
}
extension View { extension View {
func hideKeyboard() { func hideKeyboard() {
let resign = #selector(UIResponder.resignFirstResponder) let resign = #selector(UIResponder.resignFirstResponder)
@@ -302,9 +360,25 @@ extension View {
} }
} }
struct CustomizeZapView_Previews: PreviewProvider {
static var previews: some View {
CustomizeZapView(state: test_damus_state, target: ZapTarget.note(id: test_note.id, author: test_note.pubkey), lnurl: "") fileprivate func test_zap_sheet() -> ZapSheet {
.frame(width: 400, height: 600) let zap_target = ZapTarget.note(id: test_note.id, author: test_note.pubkey)
} let lnurl = ""
return ZapSheet(target: zap_target, lnurl: lnurl)
}
#Preview {
CustomizeZapView(state: test_damus_state, target: test_zap_sheet().target, lnurl: test_zap_sheet().lnurl)
.frame(width: 400, height: 600)
}
#Preview {
ZapSheetViewIfPossible(damus_state: test_damus_state, target: test_zap_sheet().target, lnurl: test_zap_sheet().lnurl)
.frame(width: 400, height: 600)
}
#Preview {
ZapSheetViewIfPossible(damus_state: test_damus_state, target: test_zap_sheet().target, lnurl: nil)
.frame(width: 400, height: 600)
} }

View File

@@ -341,7 +341,14 @@ extension NdbNote {
} }
func thread_reply() -> ThreadReply? { func thread_reply() -> ThreadReply? {
ThreadReply(tags: self.tags) if self.known_kind != .highlight {
return ThreadReply(tags: self.tags)
}
return nil
}
func highlighted_note_id() -> NoteId? {
return ThreadReply(tags: self.tags)?.reply.note_id
} }
func get_content(_ keypair: Keypair) -> String { func get_content(_ keypair: Keypair) -> String {

View File

@@ -82,7 +82,7 @@ extern "C" {
* Note: some internal assertion will remain if disabled. * Note: some internal assertion will remain if disabled.
*/ */
#ifndef FLATCC_BUILDER_ASSERT_ON_ERROR #ifndef FLATCC_BUILDER_ASSERT_ON_ERROR
#define FLATCC_BUILDER_ASSERT_ON_ERROR 1 #define FLATCC_BUILDER_ASSERT_ON_ERROR 0
#endif #endif
/* /*