Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

restore chatroom view (wip) #2179

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions damus.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
3AE45AF6297BB2E700C1D842 /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
3AFE89C32BD4156F00AD31EF /* MCEmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 3AFE89C22BD4156F00AD31EF /* MCEmojiPicker */; };
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */; };
4C011B5E2BD0A56A002F2F9B /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5C2BD0A56A002F2F9B /* ChatView.swift */; };
4C011B5F2BD0A56A002F2F9B /* ChatroomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B5D2BD0A56A002F2F9B /* ChatroomView.swift */; };
4C011B612BD0B25C002F2F9B /* ReplyQuoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C011B602BD0B25C002F2F9B /* ReplyQuoteView.swift */; };
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670028FC7C5900038D2A /* RelayView.swift */; };
4C06670428FC7EC500038D2A /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 4C06670328FC7EC500038D2A /* Kingfisher */; };
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C06670528FCB08600038D2A /* ImageCarousel.swift */; };
Expand Down Expand Up @@ -98,6 +101,7 @@
4C2B10282A7B0F5C008AA43E /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
4C2B7BF22A71B6540049DEE7 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B7BF12A71B6540049DEE7 /* Id.swift */; };
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */; };
4C2D34412BDAF1B300F9FB44 /* NIP10Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */; };
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */; };
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7329A5680900E2BD5A /* EventGroupView.swift */; };
4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */; };
Expand Down Expand Up @@ -822,6 +826,9 @@
3AF6336929884C6B0005672A /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
3AF6336A29884C6B0005672A /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-PT"; path = "pt-PT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3CCD1E692A874C4E0099A953 /* Nip98HTTPAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nip98HTTPAuth.swift; sourceTree = "<group>"; };
4C011B5C2BD0A56A002F2F9B /* ChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
4C011B5D2BD0A56A002F2F9B /* ChatroomView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatroomView.swift; sourceTree = "<group>"; };
4C011B602BD0B25C002F2F9B /* ReplyQuoteView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyQuoteView.swift; sourceTree = "<group>"; };
4C06670028FC7C5900038D2A /* RelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayView.swift; sourceTree = "<group>"; };
4C06670528FCB08600038D2A /* ImageCarousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarousel.swift; sourceTree = "<group>"; };
4C06670828FDE64700038D2A /* damus-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "damus-Bridging-Header.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -885,6 +892,7 @@
4C2B10272A7B0F5C008AA43E /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
4C2B7BF12A71B6540049DEE7 /* Id.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Id.swift; sourceTree = "<group>"; };
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = "<group>"; };
4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP10Tests.swift; sourceTree = "<group>"; };
4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
4C30AC7329A5680900E2BD5A /* EventGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroupView.swift; sourceTree = "<group>"; };
4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1986,6 +1994,9 @@
4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup;
children = (
4C011B602BD0B25C002F2F9B /* ReplyQuoteView.swift */,
4C011B5D2BD0A56A002F2F9B /* ChatroomView.swift */,
4C011B5C2BD0A56A002F2F9B /* ChatView.swift */,
D71AC4CA2BA8E3320076268E /* Extensions */,
BA3759952ABCCF360018D73B /* Camera */,
F71694E82A66221E001F4053 /* Onboarding */,
Expand Down Expand Up @@ -2555,6 +2566,7 @@
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */,
D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */,
D7831AF72BBE11E2005DA780 /* VideoCacheTests.swift */,
4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */,
);
path = damusTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -3226,6 +3238,7 @@
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */,
4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */,
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */,
4C011B612BD0B25C002F2F9B /* ReplyQuoteView.swift in Sources */,
D71AC4CC2BA8E3480076268E /* VisibilityTracker.swift in Sources */,
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
Expand Down Expand Up @@ -3313,6 +3326,7 @@
4C64305C2A945AFF00B0C0E9 /* MusicController.swift in Sources */,
5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */,
F79C7FAD29D5E9620000F946 /* EditPictureControl.swift in Sources */,
4C011B5F2BD0A56A002F2F9B /* ChatroomView.swift in Sources */,
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */,
4C1A9A2A29DDF54400516EAC /* DamusVideoPlayer.swift in Sources */,
Expand Down Expand Up @@ -3483,6 +3497,7 @@
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
4C1A9A2129DDD3E100516EAC /* KeySettingsView.swift in Sources */,
D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */,
4C011B5E2BD0A56A002F2F9B /* ChatView.swift in Sources */,
4C32B95A2A9AD44700DC3548 /* Verifiable.swift in Sources */,
4C73C5142A4437C10062CAC0 /* ZapUserView.swift in Sources */,
501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */,
Expand Down Expand Up @@ -3517,6 +3532,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4C2D34412BDAF1B300F9FB44 /* NIP10Tests.swift in Sources */,
4C684A572A7FFAE6005E6031 /* UrlTests.swift in Sources */,
3A90B1832A4EA3C600000D94 /* UserSearchCacheTests.swift in Sources */,
4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */,
Expand Down
32 changes: 19 additions & 13 deletions damus/ContentParsing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,27 +76,33 @@ func interpret_event_refs_ndb(blocks: [Block], tags: TagsSequence) -> [EventRef]
}

func interp_event_refs_without_mentions_ndb(_ ev_tags: References<NoteRef>) -> [EventRef] {

var count = 0
var evrefs: [EventRef] = []
var first: Bool = true
var first_ref: NoteRef? = nil
var root_id: NoteRef? = nil

for ref in ev_tags {
if first {
first_ref = ref
evrefs.append(.thread_id(ref))
first = false
if let marker = ref.marker {
switch marker {
case .root: root_id = ref
case .reply: evrefs.append(.reply(ref))
case .mention: evrefs.append(.mention(.noteref(ref)))
}
} else {

evrefs.append(.reply(ref))
if first {
root_id = ref
first = false
} else {
evrefs.append(.reply(ref))
}
}
count += 1
}

if let first_ref, count == 1 {
let r = first_ref
return [.reply_to_root(r)]
if let root_id {
if evrefs.count == 0 {
return [.reply_to_root(root_id)]
} else {
evrefs.append(.thread_id(root_id))
}
}

return evrefs
Expand Down
9 changes: 9 additions & 0 deletions damus/Models/EventRef.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ enum EventRef: Equatable {
case reply(NoteRef)
case reply_to_root(NoteRef)

var note_ref: NoteRef {
switch self {
case .mention(let mnref): return mnref.ref
case .thread_id(let ref): return ref
case .reply(let ref): return ref
case .reply_to_root(let ref): return ref
}
}

var is_mention: NoteRef? {
if case .mention(let m) = self { return m.ref }
return nil
Expand Down
13 changes: 12 additions & 1 deletion damus/Models/ThreadModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,18 @@ class ThreadModel: ObservableObject {
self.original_event = event
add_event(event, keypair: damus_state.keypair)
}


func events() -> [NostrEvent] {
return Array(event_map).sorted(by: { a, b in
if a.created_at == b.created_at {
return false
} else if a.created_at < b.created_at {
return true
}
return false
})
}

var is_original: Bool {
return original_event.id == event.id
}
Expand Down
4 changes: 2 additions & 2 deletions damus/Models/VideoCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import Foundation
import CryptoKit
import AVKit

// Default expiry time of only 1 day to prevent using too much storage
fileprivate let DEFAULT_EXPIRY_TIME: TimeInterval = 60*60*24
// Default expiry time of only 2 hours to prevent using too much storage
fileprivate let DEFAULT_EXPIRY_TIME: TimeInterval = 60*60*2
// Default cache directory is in the system-provided caches directory, so that the operating system can delete files when it needs storage space
// (https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html)
fileprivate let DEFAULT_CACHE_DIRECTORY_PATH: URL? = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("video_cache")
Expand Down
4 changes: 2 additions & 2 deletions damus/Util/EventCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ class EventCache {
// TODO: remove me and change code to use ndb directly
private let ndb: Ndb
private var events: [NoteId: NostrEvent] = [:]
private var replies = ReplyMap()
private var cancellable: AnyCancellable?
private var image_metadata: [String: ImageMetadataState] = [:] // lowercased URL key
private var event_data: [NoteId: EventData] = [:]
var replies = ReplyMap()

//private var thread_latest: [String: Int64]

Expand Down Expand Up @@ -187,7 +187,7 @@ class EventCache {
replies.add(id: reply, reply_id: ev.id)
}
}

func child_events(event: NostrEvent) -> [NostrEvent] {
guard let xs = replies.lookup(event.id) else {
return []
Expand Down
3 changes: 2 additions & 1 deletion damus/Util/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ enum Route: Hashable {
case .FirstAidSettings(settings: let settings):
FirstAidSettingsView(damus_state: damusState, settings: settings)
case .Thread(let thread):
ThreadView(state: damusState, thread: thread)
ChatroomView(damus: damusState, thread: thread)
//ThreadView(state: damusState, thread: thread)
case .Reposts(let reposts):
RepostsView(damus_state: damusState, model: reposts)
case .QuoteReposts(let quote_reposts):
Expand Down
177 changes: 177 additions & 0 deletions damus/Views/ChatView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//
// ChatView.swift
// damus
//
// Created by William Casarin on 2022-04-19.
//

import SwiftUI

struct ChatView: View {
let event: NostrEvent
let prev_ev: NostrEvent?
let next_ev: NostrEvent?

let damus_state: DamusState
var thread: ThreadModel

@State var expand_reply: Bool = false

var just_started: Bool {
return prev_ev == nil || prev_ev!.pubkey != event.pubkey
}

func next_replies_to_this() -> Bool {
guard let next = next_ev else {
return false
}

return damus_state.events.replies.lookup(next.id) != nil
}

func is_reply_to_prev(ref_id: NoteId) -> Bool {
guard let prev = prev_ev else {
return true
}

if let rep = damus_state.events.replies.lookup(event.id) {
return rep.contains(prev.id)
}

return false
}

var is_active: Bool {
return thread.event.id == event.id
}

func prev_reply_is_same() -> NoteId? {
return damus.prev_reply_is_same(event: event, prev_ev: prev_ev, replies: damus_state.events.replies)
}

func reply_is_new() -> NoteId? {
guard let prev = self.prev_ev else {
// if they are both null they are the same?
return nil
}

if damus_state.events.replies.lookup(prev.id) != damus_state.events.replies.lookup(event.id) {
return prev.id
}

return nil
}

@Environment(\.colorScheme) var colorScheme

var disable_animation: Bool {
self.damus_state.settings.disable_animation
}

var options: EventViewOptions {
if expand_reply {
return [.no_previews, .no_action_bar]
} else {
return [.no_previews, .no_action_bar, .truncate_content]
}
}

var body: some View {
HStack {
VStack {
if is_active || just_started {
ProfilePicView(pubkey: event.pubkey, size: 32, highlight: is_active ? .main : .none, profiles: damus_state.profiles, disable_animation: disable_animation)
}

Spacer()
}
.frame(maxWidth: 32)

Group {
VStack(alignment: .leading) {
HStack {
ProfileName(pubkey: event.pubkey, damus: damus_state)
.foregroundColor(colorScheme == .dark ? id_to_color(event.pubkey) : Color.black)
//.shadow(color: Color.black, radius: 2)
Text(verbatim: "\(format_relative_time(event.created_at))")
.foregroundColor(.gray)
}

if let replying_to = event.direct_replies(damus_state.keypair).first,
let prev = self.prev_ev,
replying_to != prev.id
{
//if !is_reply_to_prev(ref_id) {
ReplyQuoteView(keypair: damus_state.keypair, quoter: event, event_id: replying_to, state: damus_state, thread: thread, options: options)
.onTapGesture {
expand_reply = !expand_reply
}
}

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: [])

if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
let bar = make_actionbar_model(ev: event.id, damus: damus_state)
EventActionBar(damus_state: damus_state, event: event, bar: bar)
}

//Spacer()
}
.padding(6)
}
.background(Color.secondary.opacity(0.1))
.cornerRadius(8.0)

//.border(Color.red)
}
.contentShape(Rectangle())
.id(event.id)
//.frame(minHeight: just_started ? PFP_SIZE : 0)
.padding([.bottom], 6)
//.border(Color.green)

}
}

extension Notification.Name {
static var toggle_thread_view: Notification.Name {
return Notification.Name("convert_to_thread")
}
}


/*
struct ChatView_Previews: PreviewProvider {
static var previews: some View {
ChatView()
}
}

*/


func prev_reply_is_same(event: NostrEvent, prev_ev: NostrEvent?, replies: ReplyMap) -> NoteId? {
if let prev = prev_ev {
if let prev_reply_id = replies.lookup(prev.id) {
if let cur_reply_id = replies.lookup(event.id) {
if prev_reply_id != cur_reply_id {
return cur_reply_id.first
}
}
}
}
return nil
}


func id_to_color(_ pubkey: Pubkey) -> Color {
return Color(
.sRGB,
red: Double(pubkey.id[0]) / 255,
green: Double(pubkey.id[1]) / 255,
blue: Double(pubkey.id[2]) / 255,
opacity: 1
)

}
Loading