Browse Source

Merge branch 'damus-io:master' into sidebar

translations_damus-localizations-en-us-xcloc-localized-contents-en-us-xliff--master_es_419
Ben Weeks 2 years ago
committed by GitHub
parent
commit
d9b60c0052
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      README.md
  2. 4
      damus.xcodeproj/project.pbxproj
  3. 24
      damus/ContentView.swift
  4. 7
      damus/Models/SearchHomeModel.swift
  5. 25
      damus/Nostr/NostrLink.swift
  6. 59
      damus/Nostr/RelayPool.swift
  7. 17
      damus/Views/EventActionBar.swift
  8. 4
      damus/Views/ProfilePicView.swift
  9. 37
      damus/Views/ProfileView.swift
  10. 4
      damus/damus.entitlements

2
README.md

@ -56,7 +56,7 @@ damus implements the following [Nostr Implementation Possibilities][nips]
- Formatting Notes (may not format as intended in other web clients) - Formatting Notes (may not format as intended in other web clients)
- Italics: 1 asterisk `*italic*` - Italics: 1 asterisk `*italic*`
- Bold: 2 asterisk `**bold**` - Bold: 2 asterisk `**bold**`
- Strikethrough: 2 tildes `~~strikethrough~~` - Strikethrough: 1 tildes `~strikethrough~`
- Code: 1 back-tick ``code`` - Code: 1 back-tick ``code``
#### 💬 Encrypted DMs (chat app, bottom navigation) #### 💬 Encrypted DMs (chat app, bottom navigation)

4
damus.xcodeproj/project.pbxproj

@ -1098,7 +1098,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4; CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@ -1138,7 +1138,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4; CURRENT_PROJECT_VERSION = 5;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;

24
damus/ContentView.swift

@ -94,9 +94,9 @@ struct ContentView: View {
var PostingTimelineView: some View { var PostingTimelineView: some View {
VStack { VStack {
TabView(selection: $filter_state) { TabView(selection: $filter_state) {
ContentTimelineView contentTimelineView(filter: posts_filter_event)
.tag(FilterState.posts) .tag(FilterState.posts)
ContentTimelineView contentTimelineView(filter: posts_and_replies_filter_event)
.tag(FilterState.posts_and_replies) .tag(FilterState.posts_and_replies)
} }
.tabViewStyle(.page(indexDisplayMode: .never)) .tabViewStyle(.page(indexDisplayMode: .never))
@ -114,10 +114,10 @@ struct ContentView: View {
.ignoresSafeArea(.keyboard) .ignoresSafeArea(.keyboard)
} }
var ContentTimelineView: some View { func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
ZStack { ZStack {
if let damus = self.damus_state { if let damus = self.damus_state {
TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter_event) TimelineView(events: $home.events, loading: $home.loading, damus: damus, show_friend_icon: false, filter: filter)
} }
if privkey != nil { if privkey != nil {
PostButtonContainer { PostButtonContainer {
@ -127,6 +127,14 @@ struct ContentView: View {
} }
} }
func posts_and_replies_filter_event(_ ev: NostrEvent) -> Bool {
return true
}
func posts_filter_event(_ ev: NostrEvent) -> Bool {
return !ev.is_reply(nil)
}
var FiltersView: some View { var FiltersView: some View {
VStack{ VStack{
Picker("Filter State", selection: $filter_state) { Picker("Filter State", selection: $filter_state) {
@ -137,14 +145,6 @@ struct ContentView: View {
} }
} }
func filter_event(_ ev: NostrEvent) -> Bool {
if self.filter_state == .posts {
return !ev.is_reply(nil)
}
return true
}
func MainContent(damus: DamusState) -> some View { func MainContent(damus: DamusState) -> some View {
VStack { VStack {
NavigationLink(destination: MaybeProfileView, isActive: $profile_open) { NavigationLink(destination: MaybeProfileView, isActive: $profile_open) {

7
damus/Models/SearchHomeModel.swift

@ -112,8 +112,12 @@ func load_profiles(profiles_subid: String, relay_id: String, events: [NostrEvent
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, events: events) let authors = find_profiles_to_fetch(profiles: damus_state.profiles, events: events)
filter.authors = authors filter.authors = authors
if !authors.isEmpty { guard !authors.isEmpty else {
return
}
print("loading \(authors.count) profiles from \(relay_id)") print("loading \(authors.count) profiles from \(relay_id)")
damus_state.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { sub_id, conn_ev in damus_state.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { sub_id, conn_ev in
let (sid, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: conn_ev) { sub_id, ev in let (sid, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: conn_ev) { sub_id, ev in
guard sub_id == profiles_subid else { guard sub_id == profiles_subid else {
@ -133,6 +137,5 @@ func load_profiles(profiles_subid: String, relay_id: String, events: [NostrEvent
print("done loading \(authors.count) profiles from \(relay_id)") print("done loading \(authors.count) profiles from \(relay_id)")
damus_state.pool.unsubscribe(sub_id: profiles_subid, to: [relay_id]) damus_state.pool.unsubscribe(sub_id: profiles_subid, to: [relay_id])
} }
}
} }

25
damus/Nostr/NostrLink.swift

@ -80,7 +80,32 @@ func parse_nostr_ref_uri(_ p: Parser) -> ReferencedId? {
return ReferencedId(ref_id: pk, relay_id: nil, key: typ) return ReferencedId(ref_id: pk, relay_id: nil, key: typ)
} }
func decode_universal_link(_ s: String) -> NostrLink? {
var uri = s.replacingOccurrences(of: "https://damus.io/r/", with: "")
uri = uri.replacingOccurrences(of: "https://damus.io/", with: "")
uri = uri.replacingOccurrences(of: "/", with: "")
guard let decoded = try? bech32_decode(uri) else {
return nil
}
let h = hex_encode(decoded.data)
if decoded.hrp == "note" {
return .ref(ReferencedId(ref_id: h, relay_id: nil, key: "e"))
} else if decoded.hrp == "npub" {
return .ref(ReferencedId(ref_id: h, relay_id: nil, key: "p"))
}
// TODO: handle nprofile, etc
return nil
}
func decode_nostr_uri(_ s: String) -> NostrLink? { func decode_nostr_uri(_ s: String) -> NostrLink? {
if s.starts(with: "https://damus.io/") {
return decode_universal_link(s)
}
var uri = s.replacingOccurrences(of: "nostr://", with: "") var uri = s.replacingOccurrences(of: "nostr://", with: "")
uri = uri.replacingOccurrences(of: "nostr:", with: "") uri = uri.replacingOccurrences(of: "nostr:", with: "")

59
damus/Nostr/RelayPool.swift

@ -28,9 +28,20 @@ struct RelayHandler {
let callback: (String, NostrConnectionEvent) -> () let callback: (String, NostrConnectionEvent) -> ()
} }
struct QueuedRequest {
let req: NostrRequest
let relay: String
}
struct NostrRequestId: Equatable, Hashable {
let relay: String?
let sub_id: String
}
class RelayPool { class RelayPool {
var relays: [Relay] = [] var relays: [Relay] = []
var handlers: [RelayHandler] = [] var handlers: [RelayHandler] = []
var request_queue: [QueuedRequest] = []
var descriptors: [RelayDescriptor] { var descriptors: [RelayDescriptor] {
relays.map { $0.descriptor } relays.map { $0.descriptor }
@ -148,13 +159,38 @@ class RelayPool {
send(.subscribe(.init(filters: filters, sub_id: sub_id)), to: to) send(.subscribe(.init(filters: filters, sub_id: sub_id)), to: to)
} }
func count_queued(relay: String) -> Int {
var c = 0
for request in request_queue {
if request.relay == relay {
c += 1
}
}
return c
}
func queue_req(r: NostrRequest, relay: String) {
let count = count_queued(relay: relay)
guard count <= 10 else {
print("can't queue, too many queued events for \(relay)")
return
}
print("queueing request: \(r) for \(relay)")
request_queue.append(QueuedRequest(req: r, relay: relay))
}
func send(_ req: NostrRequest, to: [String]? = nil) { func send(_ req: NostrRequest, to: [String]? = nil) {
let relays = to.map{ get_relays($0) } ?? self.relays let relays = to.map{ get_relays($0) } ?? self.relays
for relay in relays { for relay in relays {
if relay.connection.isConnected { guard relay.connection.isConnected else {
relay.connection.send(req) queue_req(r: req, relay: relay.id)
continue
} }
relay.connection.send(req)
} }
} }
@ -193,9 +229,28 @@ class RelayPool {
} }
} }
func run_queue(_ relay_id: String) {
self.request_queue = request_queue.reduce(into: Array<QueuedRequest>()) { (q, req) in
guard req.relay == relay_id else {
q.append(req)
return
}
print("running queueing request: \(req.req) for \(relay_id)")
self.send(req.req, to: [relay_id])
}
}
func handle_event(relay_id: String, event: NostrConnectionEvent) { func handle_event(relay_id: String, event: NostrConnectionEvent) {
record_last_pong(relay_id: relay_id, event: event) record_last_pong(relay_id: relay_id, event: event)
// run req queue when we reconnect
if case .ws_event(let ws) = event {
if case .connected = ws {
run_queue(relay_id)
}
}
// handle reconnect logic, etc? // handle reconnect logic, etc?
for handler in handlers { for handler in handlers {
handler.callback(relay_id, event) handler.callback(relay_id, event)

17
damus/Views/EventActionBar.swift

@ -24,6 +24,7 @@ struct EventActionBar: View {
let generator = UIImpactFeedbackGenerator(style: .medium) let generator = UIImpactFeedbackGenerator(style: .medium)
@State var sheet: ActionBarSheet? = nil @State var sheet: ActionBarSheet? = nil
@State var confirm_boost: Bool = false @State var confirm_boost: Bool = false
@State var show_share_sheet: Bool = false
@StateObject var bar: ActionBarModel @StateObject var bar: ActionBarModel
var body: some View { var body: some View {
@ -40,6 +41,7 @@ struct EventActionBar: View {
EventActionButton(img: "bubble.left", col: nil) { EventActionButton(img: "bubble.left", col: nil) {
notify(.reply, event) notify(.reply, event)
} }
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
} }
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
@ -55,6 +57,7 @@ struct EventActionBar: View {
} }
} }
} }
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")") Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
@ -69,6 +72,12 @@ struct EventActionBar: View {
} }
} }
} }
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
EventActionButton(img: "square.and.arrow.up", col: Color.gray) {
show_share_sheet = true
}
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
/* /*
HStack(alignment: .bottom) { HStack(alignment: .bottom) {
@ -86,6 +95,13 @@ struct EventActionBar: View {
} }
*/ */
} }
.sheet(isPresented: $show_share_sheet) {
if let note_id = bech32_note_id(event.id) {
if let url = URL(string: "https://damus.io/" + note_id) {
ShareSheet(activityItems: [url])
}
}
}
.alert("Boost", isPresented: $confirm_boost) { .alert("Boost", isPresented: $confirm_boost) {
Button("Cancel") { Button("Cancel") {
confirm_boost = false confirm_boost = false
@ -142,7 +158,6 @@ func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) ->
.font(.footnote.weight(.medium)) .font(.footnote.weight(.medium))
.foregroundColor(col == nil ? Color.gray : col!) .foregroundColor(col == nil ? Color.gray : col!)
} }
.padding(.trailing, 40)
} }
struct LikeButton: View { struct LikeButton: View {

4
damus/Views/ProfilePicView.swift

@ -51,7 +51,9 @@ struct InnerProfilePicView: View {
} }
var body: some View { var body: some View {
Group { ZStack {
Color(uiColor: .systemBackground)
KFAnimatedImage(url) KFAnimatedImage(url)
.callbackQueue(.dispatch(.global(qos: .background))) .callbackQueue(.dispatch(.global(qos: .background)))
.processingQueue(.dispatch(.global(qos: .background))) .processingQueue(.dispatch(.global(qos: .background)))

37
damus/Views/ProfileView.swift

@ -113,6 +113,7 @@ struct ProfileView: View {
@State private var showingEditProfile = false @State private var showingEditProfile = false
@State var showing_select_wallet: Bool = false @State var showing_select_wallet: Bool = false
@State var is_zoomed: Bool = false @State var is_zoomed: Bool = false
@State var show_share_sheet: Bool = false
@StateObject var user_settings = UserSettingsStore() @StateObject var user_settings = UserSettingsStore()
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
@ -166,6 +167,18 @@ struct ProfileView: View {
static let markdown = Markdown() static let markdown = Markdown()
var ShareButton: some View {
Button(action: {
show_share_sheet = true
}) {
Image(systemName: "square.and.arrow.up.circle.fill")
.symbolRenderingMode(.palette)
.font(.system(size: 32))
.padding()
.foregroundStyle(.white, .black, .black.opacity(0.8))
}
}
var DMButton: some View { var DMButton: some View {
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey) let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey) let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey)
@ -186,6 +199,9 @@ struct ProfileView: View {
.aspectRatio(contentMode: .fill) .aspectRatio(contentMode: .fill)
.frame(width: geo.size.width, height: 150) .frame(width: geo.size.width, height: 150)
.clipped() .clipped()
ShareButton
.offset(x: geo.size.width - 80.0, y: 50.0 )
} }
VStack(alignment: .leading) { VStack(alignment: .leading) {
let data = damus_state.profiles.lookup(id: profile.pubkey) let data = damus_state.profiles.lookup(id: profile.pubkey)
@ -223,8 +239,10 @@ struct ProfileView: View {
EditButton(damus_state: damus_state) EditButton(damus_state: damus_state)
} }
} }
} }
.offset(y: -15.0) // Increase if set a frame .offset(y: -15.0) // Increase if set a frame
} }
ProfileNameView(pubkey: profile.pubkey, profile: data, damus: damus_state) ProfileNameView(pubkey: profile.pubkey, profile: data, damus: damus_state)
@ -324,6 +342,13 @@ struct ProfileView: View {
followers.unsubscribe() followers.unsubscribe()
// our profilemodel needs a bit more help // our profilemodel needs a bit more help
} }
.sheet(isPresented: $show_share_sheet) {
if let npub = bech32_pubkey(profile.pubkey) {
if let url = URL(string: "https://damus.io/" + npub) {
ShareSheet(activityItems: [url])
}
}
}
.ignoresSafeArea() .ignoresSafeArea()
} }
} }
@ -380,15 +405,11 @@ struct KeyView: View {
isCopied = false isCopied = false
} }
} label: { } label: {
Label { Label("Public Key", systemImage: "key.fill")
Text("Public key") .font(.custom("key", size: 12.0))
} icon: {
Image("ic-key")
.contentShape(Rectangle())
.frame(width: 16, height: 16)
}
.labelStyle(IconOnlyLabelStyle()) .labelStyle(IconOnlyLabelStyle())
.symbolRenderingMode(.hierarchical) .foregroundStyle(hex_to_rgb(pubkey))
.symbolRenderingMode(.palette)
} }
.padding(.leading,4) .padding(.leading,4)
Text(abbrev_pubkey(bech32, amount: 16)) Text(abbrev_pubkey(bech32, amount: 16))

4
damus/damus.entitlements

@ -4,6 +4,10 @@
<dict> <dict>
<key>aps-environment</key> <key>aps-environment</key>
<string>development</string> <string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:damus.io</string>
</array>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array> <array>
<string>$(AppIdentifierPrefix)com.jb55.damus2</string> <string>$(AppIdentifierPrefix)com.jb55.damus2</string>

Loading…
Cancel
Save