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

@@ -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())
}
}