damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 35df9f7ab74e777e40d60f93ea2b7d526303ca7e
parent 605d88add16e60f517bf2296051e7617a7202c9f
Author: Daniel D’Aquino <daniel@daquino.me>
Date:   Sat, 13 Jul 2024 00:31:44 +0000

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>

Diffstat:
Mdamus/Views/Chat/ChatEventView.swift | 39+++++++++++++++++++++++++++++++++------
Mdamus/Views/Zaps/CustomizeZapView.swift | 84++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 112 insertions(+), 11 deletions(-)

diff --git a/damus/Views/Chat/ChatEventView.swift b/damus/Views/Chat/ChatEventView.swift @@ -31,7 +31,7 @@ struct ChatEventView: View { @State var long_press_bounce_work_item: DispatchWorkItem? @State var popover_state: PopoverState = .closed { 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() } } @@ -43,6 +43,11 @@ struct ChatEventView: View { enum PopoverState: String { case closed 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 { @@ -90,9 +95,22 @@ struct ChatEventView: View { var by_other_user: Bool { return event.pubkey != damus_state.pubkey } - + 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 { ChatBubble( direction: is_ours ? .right : .left, @@ -170,6 +188,14 @@ struct ChatEventView: View { EmojiPickerView(selectedEmoji: $selected_emoji, emojiProvider: damus_state.emoji_provider) }.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 if let newSelectedEmoji { send_like(emoji: newSelectedEmoji.value) @@ -177,8 +203,8 @@ struct ChatEventView: View { } } } - .scaleEffect(self.popover_state == .open_emoji_selector ? 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) + .scaleEffect(self.popover_state.some_sheet_open() ? 1.08 : is_pressing ? 1.02 : 1) + .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: { long_press_bounce_work_item?.cancel() }, onPressingChanged: { is_pressing in @@ -192,7 +218,8 @@ struct ChatEventView: View { // Ensure the action is performed only if the condition is still valid if self.is_pressing { 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 } } } diff --git a/damus/Views/Zaps/CustomizeZapView.swift b/damus/Views/Zaps/CustomizeZapView.swift @@ -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 { func hideKeyboard() { 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: "") - .frame(width: 400, height: 600) - } + + +fileprivate func test_zap_sheet() -> ZapSheet { + 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) }