Notifications

Changelog-Added: Add new Notifications View
This commit is contained in:
William Casarin
2023-02-21 12:27:03 -08:00
parent e4dd585754
commit 64b1a57918
27 changed files with 918 additions and 61 deletions

View File

@@ -37,9 +37,7 @@ struct DMChatView: View {
var Header: some View {
let profile = damus_state.profiles.lookup(id: pubkey)
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
let fmodel = FollowersModel(damus_state: damus_state, target: pubkey)
let profile_page = ProfileView(damus_state: damus_state, profile: pmodel, followers: fmodel)
let profile_page = ProfileView(damus_state: damus_state, pubkey: pubkey)
return NavigationLink(destination: profile_page) {
HStack {
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles)

View File

@@ -61,10 +61,8 @@ struct EventView: View {
if event.known_kind == .boost {
if let inner_ev = event.inner_event {
VStack(alignment: .leading) {
let prof_model = ProfileModel(pubkey: event.pubkey, damus: damus)
let follow_model = FollowersModel(damus_state: damus, target: event.pubkey)
let prof = damus.profiles.lookup(id: event.pubkey)
let booster_profile = ProfileView(damus_state: damus, profile: prof_model, followers: follow_model)
let booster_profile = ProfileView(damus_state: damus, pubkey: event.pubkey)
NavigationLink(destination: booster_profile) {
Reposted(damus: damus, pubkey: event.pubkey, profile: prof)

View File

@@ -11,6 +11,14 @@ struct EventBody: View {
let damus_state: DamusState
let event: NostrEvent
let size: EventViewKind
let should_show_img: Bool
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind, should_show_img: Bool? = nil) {
self.damus_state = damus_state
self.event = event
self.size = size
self.should_show_img = should_show_img ?? should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
}
var content: String {
event.get_content(damus_state.keypair.privkey)
@@ -21,8 +29,6 @@ struct EventBody: View {
ReplyDescription(event: event, profiles: damus_state.profiles)
}
let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey, booster_pubkey: nil)
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, artifacts: .just_content(content))
.frame(maxWidth: .infinity, alignment: .leading)
}

View File

@@ -31,10 +31,7 @@ struct EventProfile: View {
var body: some View {
HStack(alignment: .center) {
VStack {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: FollowersModel(damus_state: damus_state, target: pubkey))
NavigationLink(destination: pv) {
NavigationLink(destination: ProfileView(damus_state: damus_state, pubkey: pubkey)) {
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles)
}
}

View File

@@ -19,10 +19,7 @@ struct TextEvent: View {
let profile = damus.profiles.lookup(id: pubkey)
VStack {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
NavigationLink(destination: pv) {
NavigationLink(destination: ProfileView(damus_state: damus, pubkey: pubkey)) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus.profiles)
}

View File

@@ -0,0 +1,189 @@
//
// RepostGroupView.swift
// damus
//
// Created by William Casarin on 2023-02-21.
//
import SwiftUI
enum EventGroupType {
case repost(EventGroup)
case reaction(EventGroup)
case zap(ZapGroup)
case profile_zap(ZapGroup)
var events: [NostrEvent] {
switch self {
case .repost(let grp):
return grp.events
case .reaction(let grp):
return grp.events
case .zap(let zapgrp):
return zapgrp.zap_requests()
case .profile_zap(let zapgrp):
return zapgrp.zap_requests()
}
}
}
enum ReactingTo {
case your_post
case tagged_in
case your_profile
}
func determine_reacting_to(our_pubkey: String, ev: NostrEvent?) -> ReactingTo {
guard let ev else {
return .your_profile
}
if ev.pubkey == our_pubkey {
return .your_post
}
return .tagged_in
}
func determine_reacting_to_text(_ r: ReactingTo) -> String {
switch r {
case .tagged_in:
return "a post you were tagged in"
case .your_post:
return "your post"
case .your_profile:
return "your profile"
}
}
func event_author_name(profiles: Profiles, _ ev: NostrEvent) -> String {
let alice_pk = ev.pubkey
let alice_prof = profiles.lookup(id: alice_pk)
return Profile.displayName(profile: alice_prof, pubkey: alice_pk)
}
func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?) -> String {
let verb = reacting_to_verb(group: group)
let reacting_to = determine_reacting_to(our_pubkey: our_pubkey, ev: ev)
let target = determine_reacting_to_text(reacting_to)
if group.events.count == 1 {
let ev = group.events.first!
let profile = profiles.lookup(id: ev.pubkey)
let display_name = Profile.displayName(profile: profile, pubkey: ev.pubkey)
return String(format: "%@ is %@ %@", display_name, verb, target)
}
if group.events.count == 2 {
let alice_name = event_author_name(profiles: profiles, group.events[0])
let bob_name = event_author_name(profiles: profiles, group.events[1])
return String(format: "%@ and %@ are %@ %@", alice_name, bob_name, verb, target)
}
if group.events.count > 2 {
let alice_name = event_author_name(profiles: profiles, group.events.first!)
let count = group.events.count - 1
return String(format: "%@ and %d other people are %@ %@", alice_name, count, verb, target)
}
return "??"
}
func reacting_to_verb(group: EventGroupType) -> String {
switch group {
case .reaction:
return "reacting"
case .repost:
return "reposting"
case .zap: fallthrough
case .profile_zap:
return "zapping"
}
}
struct EventGroupView: View {
let state: DamusState
let event: NostrEvent?
let group: EventGroupType
var GroupDescription: some View {
Text(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event))
}
func ZapIcon(_ zapgrp: ZapGroup) -> some View {
let fmt = format_msats_abbrev(zapgrp.msat_total)
return VStack(alignment: .center) {
Image(systemName: "bolt.fill")
.foregroundColor(.orange)
Text("\(fmt)")
.foregroundColor(Color.orange)
}
}
var GroupIcon: some View {
Group {
switch group {
case .repost:
Image(systemName: "arrow.2.squarepath")
.foregroundColor(Color("DamusGreen"))
case .reaction:
Image("shaka-full")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(.accentColor)
case .profile_zap(let zapgrp):
ZapIcon(zapgrp)
case .zap(let zapgrp):
ZapIcon(zapgrp)
}
}
}
var body: some View {
HStack(alignment: .top) {
GroupIcon
.frame(width: PFP_SIZE + 10)
VStack(alignment: .leading) {
ProfilePicturesView(state: state, events: group.events)
GroupDescription
if let event {
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: event.id)) {
Text(event.content)
.padding([.top], 1)
.foregroundColor(.gray)
}
.buttonStyle(.plain)
}
}
}
.padding([.top], 6)
}
}
let test_encoded_post = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
let test_repost = NostrEvent(id: "", content: test_encoded_post, pubkey: "", kind: 6, tags: [], createdAt: 1)
let test_reposts = [test_repost, test_repost]
let test_event_group = EventGroup(events: test_reposts)
struct EventGroupView_Previews: PreviewProvider {
static var previews: some View {
VStack {
EventGroupView(state: test_damus_state(), event: test_event, group: .repost(test_event_group))
.frame(height: 200)
.padding()
EventGroupView(state: test_damus_state(), event: test_event, group: .reaction(test_event_group))
.frame(height: 200)
.padding()
}
}
}

View File

@@ -0,0 +1,86 @@
//
// NotificationItemView.swift
// damus
//
// Created by William Casarin on 2023-02-21.
//
import SwiftUI
enum ShowItem {
case show(NostrEvent?)
case dontshow(NostrEvent?)
}
func notification_item_event(events: EventCache, notif: NotificationItem) -> ShowItem {
switch notif {
case .repost(let evid, _):
return .dontshow(events.lookup(evid))
case .reply(let ev):
return .show(ev)
case .reaction(let evid, _):
return .dontshow(events.lookup(evid))
case .event_zap(let evid, _):
return .dontshow(events.lookup(evid))
case .profile_zap:
return .show(nil)
}
}
struct NotificationItemView: View {
let state: DamusState
let item: NotificationItem
var show_item: ShowItem {
notification_item_event(events: state.events, notif: item)
}
func Item(_ ev: NostrEvent?) -> some View {
Group {
switch item {
case .repost(_, let evgrp):
EventGroupView(state: state, event: ev, group: .repost(evgrp))
case .event_zap(_, let zapgrp):
EventGroupView(state: state, event: ev, group: .zap(zapgrp))
case .profile_zap(let grp):
EventGroupView(state: state, event: nil, group: .profile_zap(grp))
case .reaction(_, let evgrp):
EventGroupView(state: state, event: ev, group: .reaction(evgrp))
case .reply(let ev):
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: ev.id)) {
EventView(damus: state, event: ev, has_action_bar: true)
}
.buttonStyle(.plain)
}
Divider()
.padding([.top,.bottom], 5)
}
}
var body: some View {
Group {
switch show_item {
case .show(let ev):
Item(ev)
case .dontshow(let ev):
if let ev {
Item(ev)
}
}
}
}
}
let test_notification_item: NotificationItem = .repost("evid", test_event_group)
struct NotificationItemView_Previews: PreviewProvider {
static var previews: some View {
NotificationItemView(state: test_damus_state(), item: test_notification_item)
}
}

View File

@@ -0,0 +1,43 @@
//
// NotificationsView.swift
// damus
//
// Created by William Casarin on 2023-02-21.
//
import SwiftUI
struct NotificationsView: View {
let state: DamusState
@ObservedObject var notifications: NotificationsModel
var body: some View {
ScrollViewReader { scroller in
ScrollView {
LazyVStack(alignment: .leading) {
Color.white.opacity(0)
.id("startblock")
.frame(height: 5)
ForEach(notifications.notifications, id: \.id) { item in
NotificationItemView(state: state, item: item)
}
}
.padding(.horizontal)
}
.onReceive(handle_notify(.scroll_to_top)) { notif in
let _ = notifications.flush()
self.notifications.should_queue = false
scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top)
}
}
.onAppear {
let _ = notifications.flush()
}
}
}
struct NotificationsView_Previews: PreviewProvider {
static var previews: some View {
NotificationsView(state: test_damus_state(), notifications: NotificationsModel())
}
}

View File

@@ -0,0 +1,37 @@
//
// ProfilePicturesView.swift
// damus
//
// Created by William Casarin on 2023-02-22.
//
import SwiftUI
struct ProfilePicturesView: View {
let state: DamusState
let events: [NostrEvent]
@State var nav_target: String? = nil
@State var navigating: Bool = false
var body: some View {
NavigationLink(destination: ProfileView(damus_state: state, pubkey: nav_target ?? ""), isActive: $navigating) {
EmptyView()
}
HStack {
ForEach(events.prefix(8)) { ev in
ProfilePicView(pubkey: ev.pubkey, size: 32.0, highlight: .none, profiles: state.profiles)
.onTapGesture {
nav_target = ev.pubkey
navigating = true
}
}
}
}
}
struct ProfilePicturesView_Previews: PreviewProvider {
static var previews: some View {
ProfilePicturesView(state: test_damus_state(), events: [test_event, test_event])
}
}

View File

@@ -110,8 +110,6 @@ struct ProfileView: View {
static let markdown = Markdown()
@State private var selected_tab: ProfileTab = .posts
@StateObject var profile: ProfileModel
@StateObject var followers: FollowersModel
@State private var showingEditProfile = false
@State var showing_select_wallet: Bool = false
@State var is_zoomed: Bool = false
@@ -120,6 +118,21 @@ struct ProfileView: View {
@State var filter_state : FilterState = .posts
@State var yOffset: CGFloat = 0
@StateObject var profile: ProfileModel
@StateObject var followers: FollowersModel
init(damus_state: DamusState, profile: ProfileModel, followers: FollowersModel) {
self.damus_state = damus_state
self._profile = StateObject(wrappedValue: profile)
self._followers = StateObject(wrappedValue: followers)
}
init(damus_state: DamusState, pubkey: String) {
self.damus_state = damus_state
self._profile = StateObject(wrappedValue: ProfileModel(pubkey: pubkey, damus: damus_state))
self._followers = StateObject(wrappedValue: FollowersModel(damus_state: damus_state, target: pubkey))
}
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
@Environment(\.openURL) var openURL
@@ -459,9 +472,7 @@ struct ProfileView: View {
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
let ds = test_damus_state()
let followers = FollowersModel(damus_state: ds, target: ds.pubkey)
let profile_model = ProfileModel(pubkey: ds.pubkey, damus: ds)
ProfileView(damus_state: ds, profile: profile_model, followers: followers)
ProfileView(damus_state: ds, pubkey: ds.pubkey)
}
}