1
0
mirror of git://jb55.com/damus synced 2024-09-30 00:40:45 +00:00

Live Music & Generic Statuses

Changelog-Added: Added live music statuses
Changelog-Added: Added generic user statuses
This commit is contained in:
William Casarin 2023-08-21 22:12:01 -07:00
parent 59cf8056bd
commit 0338297bfe
18 changed files with 537 additions and 55 deletions

View File

@ -163,11 +163,14 @@
4C5D5C992A6AF8F80024563C /* NdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90548A2A6AEDEE00811EEC /* NdbNote.swift */; };
4C5D5C9A2A6AF8F80024563C /* NdbTagIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9054882A6AED4700811EEC /* NdbTagIterator.swift */; };
4C5D5C9D2A6B2CB40024563C /* AsciiCharacter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5D5C9C2A6B2CB40024563C /* AsciiCharacter.swift */; };
4C5E54032A9522F600FF6E60 /* UserStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5E54022A9522F600FF6E60 /* UserStatus.swift */; };
4C5E54062A9671F800FF6E60 /* UserStatusSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5E54052A9671F800FF6E60 /* UserStatusSheet.swift */; };
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9113283D694D0052CD1C /* FollowTarget.swift */; };
4C5F9116283D855D0052CD1C /* EventsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9115283D855D0052CD1C /* EventsModel.swift */; };
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9117283D88E40052CD1C /* FollowingModel.swift */; };
4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C63334F283D40E500B1C9C3 /* HomeModel.swift */; };
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C633351283D419F00B1C9C3 /* SignalModel.swift */; };
4C64305C2A945AFF00B0C0E9 /* MusicController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64305B2A945AFF00B0C0E9 /* MusicController.swift */; };
4C64987C286D03E000EAE2B3 /* DirectMessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */; };
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */; };
4C649881286E0EE300EAE2B3 /* secp256k1 in Frameworks */ = {isa = PBXBuildFile; productRef = 4C649880286E0EE300EAE2B3 /* secp256k1 */; };
@ -339,6 +342,7 @@
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */; };
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; };
4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABF52985CD5500D66079 /* UserSearch.swift */; };
4CF38C882A9442DC00BE01B6 /* UserStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF38C872A9442DC00BE01B6 /* UserStatusView.swift */; };
4CFD502F2A2DA45800A229DB /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFD502E2A2DA45800A229DB /* MediaView.swift */; };
4CFF8F6329CC9AD7008DB934 /* ImageContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */; };
4CFF8F6729CC9E3A008DB934 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6629CC9E3A008DB934 /* ImageView.swift */; };
@ -697,11 +701,14 @@
4C5C7E67284ED36500A22DF5 /* SearchHomeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHomeModel.swift; sourceTree = "<group>"; };
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = "<group>"; };
4C5D5C9C2A6B2CB40024563C /* AsciiCharacter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsciiCharacter.swift; sourceTree = "<group>"; };
4C5E54022A9522F600FF6E60 /* UserStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserStatus.swift; sourceTree = "<group>"; };
4C5E54052A9671F800FF6E60 /* UserStatusSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserStatusSheet.swift; sourceTree = "<group>"; };
4C5F9113283D694D0052CD1C /* FollowTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowTarget.swift; sourceTree = "<group>"; };
4C5F9115283D855D0052CD1C /* EventsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsModel.swift; sourceTree = "<group>"; };
4C5F9117283D88E40052CD1C /* FollowingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowingModel.swift; sourceTree = "<group>"; };
4C63334F283D40E500B1C9C3 /* HomeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeModel.swift; sourceTree = "<group>"; };
4C633351283D419F00B1C9C3 /* SignalModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalModel.swift; sourceTree = "<group>"; };
4C64305B2A945AFF00B0C0E9 /* MusicController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicController.swift; sourceTree = "<group>"; };
4C64987B286D03E000EAE2B3 /* DirectMessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesView.swift; sourceTree = "<group>"; };
4C64987D286D082C00EAE2B3 /* DirectMessagesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessagesModel.swift; sourceTree = "<group>"; };
4C684A542A7E91FE005E6031 /* LongPostTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongPostTests.swift; sourceTree = "<group>"; };
@ -894,6 +901,7 @@
4CF0ABED29844B5500D66079 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; };
4CF0ABEF29857E9200D66079 /* Bech32Object.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Object.swift; sourceTree = "<group>"; };
4CF0ABF52985CD5500D66079 /* UserSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSearch.swift; sourceTree = "<group>"; };
4CF38C872A9442DC00BE01B6 /* UserStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserStatusView.swift; sourceTree = "<group>"; };
4CFD502E2A2DA45800A229DB /* MediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = "<group>"; };
4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContextMenuModifier.swift; sourceTree = "<group>"; };
4CFF8F6629CC9E3A008DB934 /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; };
@ -1231,6 +1239,25 @@
path = Notifications;
sourceTree = "<group>";
};
4C5E54042A95232A00FF6E60 /* Status */ = {
isa = PBXGroup;
children = (
4C64305A2A945AF200B0C0E9 /* Music */,
4CF38C872A9442DC00BE01B6 /* UserStatusView.swift */,
4C5E54022A9522F600FF6E60 /* UserStatus.swift */,
4C5E54052A9671F800FF6E60 /* UserStatusSheet.swift */,
);
path = Status;
sourceTree = "<group>";
};
4C64305A2A945AF200B0C0E9 /* Music */ = {
isa = PBXGroup;
children = (
4C64305B2A945AFF00B0C0E9 /* MusicController.swift */,
);
path = Music;
sourceTree = "<group>";
};
4C687C2A2A6058450092C550 /* Search */ = {
isa = PBXGroup;
children = (
@ -1635,6 +1662,7 @@
4CE4F9DF285287A000C00DD9 /* Components */ = {
isa = PBXGroup;
children = (
4C5E54042A95232A00FF6E60 /* Status */,
4C687C2A2A6058450092C550 /* Search */,
4C7D09702A0AEF4C00943473 /* Gradients */,
31D2E846295218AF006D67F8 /* Shimmer.swift */,
@ -2293,6 +2321,7 @@
3A23838E2A297DD200E5AA2E /* ZapButtonModel.swift in Sources */,
F71694F82A6983AF001F4053 /* GrayGradient.swift in Sources */,
4C1D4FB12A7958E60024F453 /* VersionInfo.swift in Sources */,
4C64305C2A945AFF00B0C0E9 /* MusicController.swift in Sources */,
5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */,
F79C7FAD29D5E9620000F946 /* EditPictureControl.swift in Sources */,
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
@ -2307,6 +2336,7 @@
4CE1399229F0666100AC6A0B /* ShareActionButton.swift in Sources */,
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */,
4C363A9C282838B9006E126D /* EventRef.swift in Sources */,
4C5E54032A9522F600FF6E60 /* UserStatus.swift in Sources */,
4C7D095F2A098C5D00943473 /* ConnectWalletView.swift in Sources */,
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */,
5C6E1DAF2A194075008FC15A /* PinkGradient.swift in Sources */,
@ -2368,6 +2398,7 @@
4C3EA66028FF5E7700C48A62 /* node_id.c in Sources */,
4C687C212A5F7ED00092C550 /* DamusBackground.swift in Sources */,
4CA352A02A76AE80003BB08B /* Notify.swift in Sources */,
4CF38C882A9442DC00BE01B6 /* UserStatusView.swift in Sources */,
4CE6DEE727F7A08100C66700 /* damusApp.swift in Sources */,
4C1253582A76C9060004F4B8 /* PresentSheetNotify.swift in Sources */,
4C363A962827096D006E126D /* PostBlock.swift in Sources */,
@ -2376,6 +2407,7 @@
4CEE2AED2805B22500AB5EEF /* NostrRequest.swift in Sources */,
4C06670E28FDEAA000038D2A /* utf8.c in Sources */,
4C3EA66D28FF782800C48A62 /* amount.c in Sources */,
4C5E54062A9671F800FF6E60 /* UserStatusSheet.swift in Sources */,
F71694F42A6732B7001F4053 /* GradientFollowButton.swift in Sources */,
4C3AC7A728369BA200E1F516 /* SearchHomeView.swift in Sources */,
4CB883B0297705DD00DC99E7 /* ZapButton.swift in Sources */,

View File

@ -0,0 +1,48 @@
//
// MusicController.swift
// damus
//
// Created by William Casarin on 2023-08-21.
//
import SwiftUI
import MediaPlayer
enum MusicState {
case playback_state(MPMusicPlaybackState)
case song(MPMediaItem?)
}
class MusicController {
let player: MPMusicPlayerController
let onChange: (MusicState) -> ()
init(onChange: @escaping (MusicState) -> ()) {
player = .systemMusicPlayer
player.beginGeneratingPlaybackNotifications()
self.onChange = onChange
print("Playback State: \(player.playbackState)")
print("Now Playing Item: \(player.nowPlayingItem?.title ?? "None")")
NotificationCenter.default.addObserver(self, selector: #selector(self.songChanged(notification:)), name: .MPMusicPlayerControllerNowPlayingItemDidChange, object: player)
NotificationCenter.default.addObserver(self, selector: #selector(self.playbackStatusChanged(notification:)), name: .MPMusicPlayerControllerPlaybackStateDidChange, object: player)
}
deinit {
print("deinit musiccontroller")
}
@objc
func songChanged(notification: Notification) {
onChange(.song(player.nowPlayingItem))
}
@objc
func playbackStatusChanged(notification: Notification) {
onChange(.playback_state(player.playbackState))
}
}

View File

@ -0,0 +1,141 @@
//
// UserStatus.swift
// damus
//
// Created by William Casarin on 2023-08-22.
//
import Foundation
import MediaPlayer
struct Song {
let started_playing: Date
let content: String
}
struct UserStatus {
let type: UserStatusType
let expires_at: Date?
let content: String
func to_note(keypair: FullKeypair) -> NostrEvent? {
return make_user_status_note(status: self, keypair: keypair)
}
init(type: UserStatusType, expires_at: Date?, content: String) {
self.type = type
self.expires_at = expires_at
self.content = content
}
init?(ev: NostrEvent) {
guard let tag = ev.referenced_params.just_one() else {
return nil
}
let str = tag.param.string()
if str == "general" {
self.type = .general
} else if str == "music" {
self.type = .music
} else {
return nil
}
if let tag = ev.tags.first(where: { t in t.count >= 2 && t[0].matches_str("expiration") }),
tag.count == 2,
let expires = UInt32(tag[1].string())
{
self.expires_at = Date(timeIntervalSince1970: TimeInterval(expires))
} else {
self.expires_at = nil
}
self.content = ev.content
}
}
enum UserStatusType: String {
case music
case general
}
class UserStatusModel: ObservableObject {
@Published var general: UserStatus?
@Published var music: UserStatus?
func update_status(_ s: UserStatus) {
switch s.type {
case .music:
self.music = s
case .general:
self.general = s
}
}
var _playing_enabled: Bool
var playing_enabled: Bool {
set {
var new_val = newValue
if newValue {
MPMediaLibrary.requestAuthorization { astatus in
switch astatus {
case .notDetermined: new_val = false
case .denied: new_val = false
case .restricted: new_val = false
case .authorized: new_val = true
@unknown default:
new_val = false
}
}
}
if new_val != playing_enabled {
_playing_enabled = new_val
self.objectWillChange.send()
}
}
get {
return _playing_enabled
}
}
init(playing: UserStatus? = nil, status: UserStatus? = nil) {
self.general = status
self.music = playing
self._playing_enabled = false
self.playing_enabled = false
}
static var current_track: String? {
let player = MPMusicPlayerController.systemMusicPlayer
guard let nowPlayingItem = player.nowPlayingItem else { return nil }
return nowPlayingItem.title
}
}
func make_user_status_note(status: UserStatus, keypair: FullKeypair, expiry: Date? = nil) -> NostrEvent?
{
var tags: [[String]] = [ ["d", status.type.rawValue] ]
if let expiry {
tags.append(["expiration", String(UInt32(expiry.timeIntervalSince1970))])
} else if let expiry = status.expires_at {
tags.append(["expiration", String(UInt32(expiry.timeIntervalSince1970))])
}
let kind = NostrKind.status.rawValue
guard let ev = NostrEvent(content: status.content, keypair: keypair.to_keypair(), kind: kind, tags: tags) else {
return nil
}
return ev
}

View File

@ -0,0 +1,116 @@
//
// UserStatusSheet.swift
// damus
//
// Created by William Casarin on 2023-08-23.
//
import SwiftUI
enum StatusDuration: String, CaseIterable {
case never = "Never"
case thirty_mins = "30 Minutes"
case hour = "1 Hour"
case four_hours = "4 Hours"
case day = "1 Day"
case week = "1 Week"
var expiration: Date? {
switch self {
case .never:
return nil
case .thirty_mins:
return Date.now.addingTimeInterval(60 * 30)
case .hour:
return Date.now.addingTimeInterval(60 * 60)
case .four_hours:
return Date.now.addingTimeInterval(60 * 60 * 4)
case .day:
return Date.now.addingTimeInterval(60 * 60 * 24)
case .week:
return Date.now.addingTimeInterval(60 * 60 * 24 * 7)
}
}
}
struct UserStatusSheet: View {
let postbox: PostBox
let keypair: Keypair
@State var duration: StatusDuration = .never
@ObservedObject var status: UserStatusModel
@Environment(\.dismiss) var dismiss
var status_binding: Binding<String> {
Binding(get: {
status.general?.content ?? ""
}, set: { v in
status.general = UserStatus(type: .general, expires_at: duration.expiration, content: v)
})
}
var body: some View {
VStack(alignment: .leading, spacing: 20) {
Text("Set Status")
.font(.largeTitle)
TextField(text: status_binding, label: {
Text("📋 Working")
})
HStack {
Text("Clear status")
Spacer()
Picker("Duration", selection: $duration) {
ForEach(StatusDuration.allCases, id: \.self) { d in
Text("\(d.rawValue)")
.tag(d)
}
}
}
Toggle(isOn: $status.playing_enabled, label: {
Text("Broadcast music playing on Apple Music")
})
HStack(alignment: .center) {
Button(action: {
dismiss()
}, label: {
Text("Cancel")
})
Spacer()
Button(action: {
guard let status = self.status.general,
let kp = keypair.to_full(),
let ev = make_user_status_note(status: status, keypair: kp, expiry: duration.expiration)
else {
return
}
postbox.send(ev)
dismiss()
}, label: {
Text("Save")
})
.buttonStyle(GradientButtonStyle())
}
.padding([.top], 30)
Spacer()
}
.padding(30)
}
}
struct UserStatusSheet_Previews: PreviewProvider {
static var previews: some View {
UserStatusSheet(postbox: PostBox(pool: RelayPool()), keypair: Keypair(pubkey: .empty, privkey: nil), status: .init())
}
}

View File

@ -0,0 +1,37 @@
//
// UserStatus.swift
// damus
//
// Created by William Casarin on 2023-08-21.
//
import SwiftUI
import MediaPlayer
struct UserStatusView: View {
@ObservedObject var status: UserStatusModel
var body: some View {
VStack(alignment: .leading, spacing: 0) {
if let general = status.general {
Text(verbatim: "\(general.content)")
.foregroundColor(.gray)
.font(.callout.italic())
}
if let playing = status.music {
Text(verbatim: "🎵\(playing.content)")
.foregroundColor(.gray)
.font(.callout.italic())
}
}
}
}
struct UserStatusView_Previews: PreviewProvider {
static var previews: some View {
UserStatusView(status: .init())
}
}

View File

@ -7,6 +7,7 @@
import SwiftUI
import AVKit
import MediaPlayer
struct TimestampedProfile {
let profile: Profile
@ -30,6 +31,7 @@ enum Sheets: Identifiable {
case zap(ZapSheet)
case select_wallet(SelectWallet)
case filter
case user_status
case suggestedUsers
static func zap(target: ZapTarget, lnurl: String) -> Sheets {
@ -43,6 +45,7 @@ enum Sheets: Identifiable {
var id: String {
switch self {
case .report: return "report"
case .user_status: return "user_status"
case .post(let action): return "post-" + (action.ev?.id.hex() ?? "")
case .event(let ev): return "event-" + ev.id.hex()
case .zap(let sheet): return "zap-" + hex_encode(sheet.target.id)
@ -315,6 +318,8 @@ struct ContentView: View {
MaybeReportView(target: target)
case .post(let action):
PostView(action: action, damus_state: damus_state!)
case .user_status:
UserStatusSheet(postbox: damus_state!.postbox, keypair: damus_state!.keypair, status: damus_state!.profiles.profile_data(damus_state!.pubkey).status)
case .event:
EventDetailView()
case .zap(let zapsheet):
@ -647,14 +652,32 @@ struct ContentView: View {
muted_threads: MutedThreadsManager(keypair: keypair),
wallet: WalletModel(settings: settings),
nav: self.navigationCoordinator,
user_search_cache: user_search_cache
user_search_cache: user_search_cache,
music: MusicController(onChange: music_changed)
)
home.damus_state = self.damus_state!
pool.connect()
}
func music_changed(_ state: MusicState) {
guard let damus_state else { return }
switch state {
case .playback_state:
break
case .song(let song):
guard let song, let kp = damus_state.keypair.to_full() else { return }
let pdata = damus_state.profiles.profile_data(damus_state.pubkey)
let music = UserStatus(type: .music, expires_at: Date.now.addingTimeInterval(song.playbackDuration), content: "\(song.title ?? "Unknown") - \(song.artist ?? "Unknown")")
pdata.status.music = music
guard let ev = music.to_note(keypair: kp) else { return }
damus_state.postbox.send(ev)
}
}
}
struct ContentView_Previews: PreviewProvider {
@ -744,7 +767,6 @@ func update_filters_with_since(last_of_kind: [UInt32: NostrEvent], filters: [Nos
func setup_notifications() {
UIApplication.shared.registerForRemoteNotifications()
let center = UNUserNotificationCenter.current()

View File

@ -68,6 +68,8 @@
</dict>
<key>NSCameraUsageDescription</key>
<string>Damus needs access to your camera if you want to upload photos from it</string>
<key>NSAppleMusicUsageDescription</key>
<string>Damus needs access to your media library for playback statuses</string>
<key>NSMicrophoneUsageDescription</key>
<string>Damus needs access to your microphone if you want to upload recorded videos from it</string>
</dict>

View File

@ -32,7 +32,8 @@ struct DamusState {
let wallet: WalletModel
let nav: NavigationCoordinator
let user_search_cache: UserSearchCache
let music: MusicController?
@discardableResult
func add_zap(zap: Zapping) -> Bool {
// store generic zap mapping
@ -87,6 +88,8 @@ struct DamusState {
muted_threads: MutedThreadsManager(keypair: kp),
wallet: WalletModel(settings: UserSettingsStore()),
nav: NavigationCoordinator(),
user_search_cache: user_search_cache)
user_search_cache: user_search_cache,
music: nil
)
}
}

View File

@ -190,9 +190,19 @@ class HomeModel {
handle_nwc_response(ev, relay: relay_id)
case .http_auth:
break
case .status:
handle_status_event(ev)
}
}
func handle_status_event(_ ev: NostrEvent) {
guard let st = UserStatus(ev: ev) else {
return
}
damus_state.profiles.profile_data(ev.pubkey).status.update_status(st)
}
func handle_nwc_response(_ ev: NostrEvent, relay: String) {
Task { @MainActor in
// TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time
@ -502,7 +512,7 @@ class HomeModel {
func subscribe_to_home_filters(friends fs: [Pubkey]? = nil, relay_id: String? = nil) {
// TODO: separate likes?
var home_filter_kinds: [NostrKind] = [
.text, .longform, .boost
.text, .longform, .boost, .status
]
if !damus_state.settings.onlyzaps_mode {
home_filter_kinds.append(.like)
@ -1401,7 +1411,7 @@ func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @esc
}
DispatchQueue.main.async {
damus_state.profiles.zappers[ptag] = zapper
damus_state.profiles.profile_data(ptag).zapper = zapper
guard let zap = process_zap_event_with_zapper(damus_state: damus_state, ev: ev, zapper: zapper) else {
completion(.failed)
return

View File

@ -24,4 +24,5 @@ enum NostrKind: UInt32, Codable {
case nwc_request = 23194
case nwc_response = 23195
case http_auth = 27235
case status = 30315
}

View File

@ -7,6 +7,36 @@
import Foundation
class ValidationModel: ObservableObject {
@Published var validated: NIP05?
init() {
self.validated = nil
}
}
class ProfileDataModel: ObservableObject {
@Published var profile: TimestampedProfile?
init() {
self.profile = nil
}
}
class ProfileData {
var status: UserStatusModel
var profile_model: ProfileDataModel
var validation_model: ValidationModel
var zapper: Pubkey?
init() {
status = .init()
profile_model = .init()
validation_model = .init()
zapper = nil
}
}
class Profiles {
static let db_freshness_threshold: TimeInterval = 24 * 60 * 60
@ -21,10 +51,9 @@ class Profiles {
qos: .userInteractive,
attributes: .concurrent)
private var profiles: [Pubkey: TimestampedProfile] = [:]
private var validated: [Pubkey: NIP05] = [:]
private var profiles: [Pubkey: ProfileData] = [:]
var nip05_pubkey: [String: Pubkey] = [:]
var zappers: [Pubkey: Pubkey] = [:]
private let database = ProfileDatabase()
@ -36,36 +65,40 @@ class Profiles {
func is_validated(_ pk: Pubkey) -> NIP05? {
validated_queue.sync {
validated[pk]
self.profile_data(pk).validation_model.validated
}
}
func invalidate_nip05(_ pk: Pubkey) {
validated_queue.async(flags: .barrier) {
self.validated.removeValue(forKey: pk)
self.profile_data(pk).validation_model.validated = nil
}
}
func set_validated(_ pk: Pubkey, nip05: NIP05?) {
validated_queue.async(flags: .barrier) {
self.validated[pk] = nip05
self.profile_data(pk).validation_model.validated = nip05
}
}
func enumerated() -> EnumeratedSequence<[Pubkey: TimestampedProfile]> {
return profiles_queue.sync {
return profiles.enumerated()
func profile_data(_ pubkey: Pubkey) -> ProfileData {
guard let data = profiles[pubkey] else {
let data = ProfileData()
profiles[pubkey] = data
return data
}
return data
}
func lookup_zapper(pubkey: Pubkey) -> Pubkey? {
zappers[pubkey]
profile_data(pubkey).zapper
}
func add(id: Pubkey, profile: TimestampedProfile) {
profiles_queue.async(flags: .barrier) {
let old_timestamped_profile = self.profiles[id]
self.profiles[id] = profile
let old_timestamped_profile = self.profile_data(id).profile_model.profile
self.profile_data(id).profile_model.profile = profile
self.user_search_cache.updateProfile(id: id, profiles: self, oldProfile: old_timestamped_profile?.profile, newProfile: profile.profile)
}
@ -81,21 +114,21 @@ class Profiles {
func lookup(id: Pubkey) -> Profile? {
var profile: Profile?
profiles_queue.sync {
profile = profiles[id]?.profile
profile = self.profile_data(id).profile_model.profile?.profile
}
return profile ?? database.get(id: id)
}
func lookup_with_timestamp(id: Pubkey) -> TimestampedProfile? {
profiles_queue.sync {
return profiles[id]
return self.profile_data(id).profile_model.profile
}
}
func has_fresh_profile(id: Pubkey) -> Bool {
var profile: Profile?
profiles_queue.sync {
profile = profiles[id]?.profile
profile = self.profile_data(id).profile_model.profile?.profile
}
if profile != nil {
return true
@ -113,6 +146,6 @@ class Profiles {
func invalidate_zapper_cache(pubkey: Pubkey, profiles: Profiles, lnurl: LNUrls) {
profiles.zappers.removeValue(forKey: pubkey)
profiles.profile_data(pubkey).zapper = nil
lnurl.endpoints.removeValue(forKey: pubkey)
}

View File

@ -34,7 +34,6 @@ struct EventTop: View {
Spacer()
EventMenuContext(damus: state, event: event)
}
.lineLimit(1)
}
}

View File

@ -43,8 +43,11 @@ struct EventProfile: View {
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles, disable_animation: disable_animation)
}
}
EventProfileName(pubkey: pubkey, profile: profile, damus: damus_state, size: size)
VStack(alignment: .leading) {
EventProfileName(pubkey: pubkey, profile: profile, damus: damus_state, size: size)
UserStatusView(status: damus_state.profiles.profile_data(pubkey).status)
}
}
}
}

View File

@ -93,8 +93,9 @@ struct EventShell<Content: View>: View {
HStack(spacing: 10) {
Pfp(is_anon: is_anon)
VStack {
VStack(alignment: .leading, spacing: 2) {
EventTop(state: state, event: event, pubkey: pubkey, is_anon: is_anon)
UserStatusView(status: state.profiles.profile_data(pubkey).status)
ReplyPart(events: state.events, event: event, privkey: state.keypair.privkey, profiles: state.profiles)
}
}

View File

@ -49,7 +49,7 @@ struct SelectedEventView: View {
.padding(.horizontal)
.minimumScaleFactor(0.75)
.lineLimit(1)
if event_is_reply(event.event_refs(damus.keypair.privkey)) {
ReplyDescription(event: event, replying_to: replying_to, profiles: damus.profiles)
.padding(.horizontal)

View File

@ -55,7 +55,7 @@ struct EventProfileName: View {
return donation
}
var body: some View {
HStack(spacing: 2) {
switch current_display_name {

View File

@ -0,0 +1,20 @@
//
// ProfilePopup.swift
// damus
//
// Created by William Casarin on 2023-08-21.
//
import SwiftUI
struct ProfilePopup: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
struct ProfilePopup_Previews: PreviewProvider {
static var previews: some View {
ProfilePopup()
}
}

View File

@ -83,23 +83,37 @@ struct SideMenuView: View {
var TopProfile: some View {
let profile = damus_state.profiles.lookup(id: damus_state.pubkey)
return HStack {
ProfilePicView(pubkey: damus_state.pubkey, size: 60, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
return VStack(alignment: .leading, spacing: verticalSpacing) {
HStack {
ProfilePicView(pubkey: damus_state.pubkey, size: 60, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
VStack(alignment: .leading) {
if let display_name = profile?.display_name {
Text(display_name)
.foregroundColor(textColor())
.font(.title)
.lineLimit(1)
}
if let name = profile?.name {
Text("@" + name)
.foregroundColor(DamusColors.mediumGrey)
.font(.body)
.lineLimit(1)
VStack(alignment: .leading) {
if let display_name = profile?.display_name {
Text(display_name)
.foregroundColor(textColor())
.font(.title)
.lineLimit(1)
}
if let name = profile?.name {
Text("@" + name)
.foregroundColor(DamusColors.mediumGrey)
.font(.body)
.lineLimit(1)
}
}
}
navLabel(title: NSLocalizedString("Set Status", comment: "Sidebar menu label to set user status"), img: "add-reaction")
.font(.title2)
.foregroundColor(textColor())
.frame(maxWidth: .infinity, alignment: .leading)
.dynamicTypeSize(.xSmall)
.onTapGesture {
present_sheet(.user_status)
}
UserStatusView(status: damus_state.profiles.profile_data(damus_state.pubkey).status)
.dynamicTypeSize(.xSmall)
}
}
@ -190,17 +204,17 @@ struct SideMenuView: View {
}
}
@ViewBuilder
func navLabel(title: String, img: String) -> some View {
Image(img)
.tint(DamusColors.adaptableBlack)
Text(title)
.font(.title2)
.foregroundColor(textColor())
.frame(maxWidth: .infinity, alignment: .leading)
.dynamicTypeSize(.xSmall)
HStack {
Image(img)
.tint(DamusColors.adaptableBlack)
Text(title)
.font(.title2)
.foregroundColor(textColor())
.frame(maxWidth: .infinity, alignment: .leading)
.dynamicTypeSize(.xSmall)
}
}
struct SideMenuLabelStyle: LabelStyle {