Live Music & Generic Statuses
Changelog-Added: Added live music statuses Changelog-Added: Added generic user statuses
This commit is contained in:
48
damus/Components/Status/Music/MusicController.swift
Normal file
48
damus/Components/Status/Music/MusicController.swift
Normal 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))
|
||||
}
|
||||
}
|
||||
141
damus/Components/Status/UserStatus.swift
Normal file
141
damus/Components/Status/UserStatus.swift
Normal 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
|
||||
}
|
||||
|
||||
116
damus/Components/Status/UserStatusSheet.swift
Normal file
116
damus/Components/Status/UserStatusSheet.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
37
damus/Components/Status/UserStatusView.swift
Normal file
37
damus/Components/Status/UserStatusView.swift
Normal 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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user