Implement quote reposting

This commit is contained in:
William Casarin
2023-04-18 12:16:11 -07:00
parent 300cd87fc2
commit d1fce5054d
5 changed files with 206 additions and 91 deletions

View File

@@ -15,17 +15,15 @@ struct TimestampedProfile {
}
enum Sheets: Identifiable {
case post
case post(PostAction)
case report(ReportTarget)
case reply(NostrEvent)
case event(NostrEvent)
case filter
var id: String {
switch self {
case .report: return "report"
case .post: return "post"
case .reply(let ev): return "reply-" + ev.id
case .post(let action): return "post-" + (action.ev?.id ?? "")
case .event(let ev): return "event-" + ev.id
case .filter: return "filter"
}
@@ -115,7 +113,7 @@ struct ContentView: View {
if privkey != nil {
PostButtonContainer(is_left_handed: damus_state?.settings.left_handed ?? false) {
self.active_sheet = .post
self.active_sheet = .post(.posting)
}
}
}
@@ -311,10 +309,8 @@ struct ContentView: View {
switch item {
case .report(let target):
MaybeReportView(target: target)
case .post:
PostView(replying_to: nil, damus_state: damus_state!)
case .reply(let event):
PostView(replying_to: event, damus_state: damus_state!)
case .post(let action):
PostView(action: action, damus_state: damus_state!)
case .event:
EventDetailView()
case .filter:
@@ -354,14 +350,16 @@ struct ContentView: View {
}
.onReceive(handle_notify(.boost)) { notif in
if let ev = (notif.object as? NostrEvent) {
current_boost = ev
shouldShowBoostAlert = true
guard let ev = notif.object as? NostrEvent else {
return
}
current_boost = ev
shouldShowBoostAlert = true
}
.onReceive(handle_notify(.reply)) { notif in
let ev = notif.object as! NostrEvent
self.active_sheet = .reply(ev)
self.active_sheet = .post(.replying_to(ev))
}
.onReceive(handle_notify(.like)) { like in
}
@@ -587,17 +585,35 @@ struct ContentView: View {
Text("Could not find user to mute...", comment: "Alert message to indicate that the muted user could not be found.")
}
})
.alert(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post."), isPresented: $shouldShowBoostAlert) {
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of reposting a post.")) {
current_boost = nil
}
Button(NSLocalizedString("Repost", comment: "Button to confirm reposting a post.")) {
if let current_boost {
self.damus_state?.postbox.send(current_boost)
.confirmationDialog("Repost", isPresented: $shouldShowBoostAlert) {
Button(NSLocalizedString("Repost", comment: "Title of alert for confirming to repost a post.")) {
guard let current_boost else {
return
}
guard let privkey = self.damus_state?.keypair.privkey else {
return
}
guard let damus_state else {
return
}
let boost = make_boost_event(pubkey: damus_state.keypair.pubkey, privkey: privkey, boosted: current_boost)
damus_state.postbox.send(boost)
}
Button(NSLocalizedString("Quote", comment: "Title of alert for confirming to make a quoted post.")) {
guard let current_boost else {
return
}
self.active_sheet = .post(.quoting(current_boost))
}
}
.onChange(of: shouldShowBoostAlert) { v in
if v == false {
self.current_boost = nil
}
} message: {
Text("Are you sure you want to repost this?", comment: "Alert message to ask if user wants to repost a post.")
}
}

View File

@@ -7,8 +7,23 @@
import Foundation
class Drafts: ObservableObject {
@Published var post: NSMutableAttributedString = NSMutableAttributedString(string: "")
@Published var replies: [NostrEvent: NSMutableAttributedString] = [:]
@Published var medias: [UploadedMedia] = []
class DraftArtifacts {
var content: NSMutableAttributedString
var media: [UploadedMedia]
init() {
self.content = NSMutableAttributedString(string: "")
self.media = []
}
init(content: NSMutableAttributedString, media: [UploadedMedia]) {
self.content = content
self.media = media
}
}
class Drafts: ObservableObject {
@Published var post: DraftArtifacts? = nil
@Published var replies: [NostrEvent: DraftArtifacts] = [:]
@Published var quotes: [NostrEvent: DraftArtifacts] = [:]
}

View File

@@ -746,6 +746,15 @@ func gather_reply_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
return ids
}
func gather_quote_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
var ids: [ReferencedId] = []
ids.append(contentsOf: uniq(from.referenced_pubkeys.filter { $0.ref_id != our_pubkey }))
if from.pubkey != our_pubkey {
ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p"))
}
return ids
}
func event_from_json(dat: String) -> NostrEvent? {
return try? JSONDecoder().decode(NostrEvent.self, from: Data(dat.utf8))
}

View File

@@ -137,17 +137,7 @@ struct EventActionBar: View {
}
func send_boost() {
guard let privkey = self.damus_state.keypair.privkey else {
return
}
let boost = make_boost_event(pubkey: damus_state.keypair.pubkey, privkey: privkey, boosted: self.event)
// As we will still have to wait for the confirmation from alert for repost, we do not turn it green yet.
// However, turning green handled from EventActionBar spontaneously once reposted
// self.bar.our_boost = boost
notify(.boost, boost)
notify(.boost, self.event)
}
func send_like() {

View File

@@ -15,6 +15,23 @@ enum NostrPostResult {
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
enum PostAction {
case replying_to(NostrEvent)
case quoting(NostrEvent)
case posting
var ev: NostrEvent? {
switch self {
case .replying_to(let ev):
return ev
case .quoting(let ev):
return ev
case .posting:
return nil
}
}
}
struct PostView: View {
@State var post: NSMutableAttributedString = NSMutableAttributedString()
@FocusState var focus: Bool
@@ -31,7 +48,7 @@ struct PostView: View {
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
let replying_to: NostrEvent?
let action: PostAction
let damus_state: DamusState
@Environment(\.presentationMode) var presentationMode
@@ -51,7 +68,8 @@ struct PostView: View {
func send_post() {
var kind: NostrKind = .text
if replying_to?.known_kind == .chat {
if case .replying_to(let ev) = action, ev.known_kind == .chat {
kind = .chat
}
@@ -61,25 +79,21 @@ struct PostView: View {
}
}
var content = self.post.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let imagesString = uploadedMedias.map { $0.uploadedURL.absoluteString }.joined(separator: " ")
content.append(" " + imagesString + " ")
if case .quoting(let ev) = action, let id = bech32_note_id(ev.id) {
content.append(" nostr:" + id)
}
let new_post = NostrPost(content: content, references: references, kind: kind)
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
if let replying_to {
damus_state.drafts.replies.removeValue(forKey: replying_to)
} else {
damus_state.drafts.post = NSMutableAttributedString(string: "")
uploadedMedias = []
damus_state.drafts.medias = []
}
clear_draft()
dismiss()
}
@@ -131,17 +145,67 @@ struct PostView: View {
.clipShape(Capsule())
}
var isEmpty: Bool {
self.uploadedMedias.count == 0 &&
self.post.mutableString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
func clear_draft() {
switch action {
case .replying_to(let replying_to):
damus_state.drafts.replies.removeValue(forKey: replying_to)
case .quoting(let quoting):
damus_state.drafts.quotes.removeValue(forKey: quoting)
case .posting:
damus_state.drafts.post = nil
}
}
func load_draft() {
guard let draft = load_draft_for_post(drafts: self.damus_state.drafts, action: self.action) else {
self.post = NSMutableAttributedString("")
self.uploadedMedias = []
return
}
self.uploadedMedias = draft.media
self.post = draft.content
}
func post_changed(post: NSMutableAttributedString, media: [UploadedMedia]) {
switch action {
case .replying_to(let ev):
if let draft = damus_state.drafts.replies[ev] {
draft.content = post
draft.media = media
} else {
damus_state.drafts.replies[ev] = DraftArtifacts(content: post, media: media)
}
case .quoting(let ev):
if let draft = damus_state.drafts.quotes[ev] {
draft.content = post
draft.media = media
} else {
damus_state.drafts.quotes[ev] = DraftArtifacts(content: post, media: media)
}
case .posting:
if let draft = damus_state.drafts.post {
draft.content = post
draft.media = media
} else {
damus_state.drafts.post = DraftArtifacts(content: post, media: media)
}
}
}
var TextEntry: some View {
ZStack(alignment: .topLeading) {
TextViewWrapper(attributedText: $post)
.focused($focus)
.textInputAutocapitalization(.sentences)
.onChange(of: post) { _ in
if let replying_to {
damus_state.drafts.replies[replying_to] = post
} else {
damus_state.drafts.post = post
}
.onChange(of: post) { p in
post_changed(post: p, media: uploadedMedias)
}
if post.string.isEmpty {
@@ -207,6 +271,35 @@ struct PostView: View {
}
}
var has_artifacts: Bool {
if case .quoting = action {
return true
}
return !uploadedMedias.isEmpty
}
func Editor(deviceSize: GeometryProxy) -> some View {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .top) {
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
TextEntry
}
.frame(height: has_artifacts ? deviceSize.size.height*0.4 : deviceSize.size.height)
.id("post")
PVImageCarouselView(media: $uploadedMedias, deviceWidth: deviceSize.size.width)
.onChange(of: uploadedMedias) { media in
post_changed(post: post, media: media)
}
if case .quoting(let ev) = action {
BuilderEventView(damus: damus_state, event: ev)
}
}
.padding(.horizontal)
}
var body: some View {
GeometryReader { (deviceSize: GeometryProxy) in
VStack(alignment: .leading, spacing: 0) {
@@ -217,27 +310,11 @@ struct PostView: View {
ScrollViewReader { scroller in
ScrollView {
if let replying_to = replying_to {
if case .replying_to(let replying_to) = self.action {
ReplyView(replying_to: replying_to, damus: damus_state, originalReferences: $originalReferences, references: $references)
}
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .top) {
ProfilePicView(pubkey: damus_state.pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
TextEntry
}
.frame(height: uploadedMedias.isEmpty ? deviceSize.size.height*0.78 : deviceSize.size.height*0.2)
.id("post")
PVImageCarouselView(media: $uploadedMedias, deviceWidth: deviceSize.size.width)
.onChange(of: uploadedMedias) { _ in
if replying_to == nil {
damus_state.drafts.medias = uploadedMedias
}
}
}
.padding(.horizontal)
Editor(deviceSize: deviceSize)
}
.frame(maxHeight: searching == nil ? .infinity : 70)
.onAppear {
@@ -283,18 +360,17 @@ struct PostView: View {
}
}
.onAppear() {
if let replying_to {
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
load_draft()
switch action {
case .replying_to(let replying_to):
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
originalReferences = references
if damus_state.drafts.replies[replying_to] == nil {
damus_state.drafts.post = NSMutableAttributedString(string: "")
}
if let p = damus_state.drafts.replies[replying_to] {
post = p
}
} else {
post = damus_state.drafts.post
uploadedMedias = damus_state.drafts.medias
case .quoting(let quoting):
references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting)
originalReferences = references
case .posting:
break
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
@@ -302,11 +378,8 @@ struct PostView: View {
}
}
.onDisappear {
if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
damus_state.drafts.replies.removeValue(forKey: replying_to)
} else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
damus_state.drafts.post = NSMutableAttributedString(string : "")
damus_state.drafts.medias = uploadedMedias
if isEmpty {
clear_draft()
}
}
.alert(NSLocalizedString("Note contains \"nsec1\" private key. Are you sure?", comment: "Alert user that they might be attempting to paste a private key and ask them to confirm."), isPresented: $showPrivateKeyWarning, actions: {
@@ -344,7 +417,7 @@ func get_searching_string(_ post: String) -> String? {
struct PostView_Previews: PreviewProvider {
static var previews: some View {
PostView(replying_to: nil, damus_state: test_damus_state())
PostView(action: .posting, damus_state: test_damus_state())
}
}
@@ -429,3 +502,15 @@ struct UploadedMedia: Equatable {
let uploadedURL: URL
let representingImage: UIImage
}
func load_draft_for_post(drafts: Drafts, action: PostAction) -> DraftArtifacts? {
switch action {
case .replying_to(let ev):
return drafts.replies[ev]
case .quoting(let ev):
return drafts.quotes[ev]
case .posting:
return drafts.post
}
}