Add screen to select individual relays when posting/broadcasting
Changelog-Added: Add screen to select individual relays when posting/broadcasting Closes: #525
This commit is contained in:
committed by
William Casarin
parent
552402f2b5
commit
04759107a2
@@ -198,6 +198,8 @@
|
|||||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */; };
|
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */; };
|
||||||
7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */; };
|
7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */; };
|
||||||
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
|
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
|
||||||
|
B02AAD55298CD07300807B3C /* BroadcastToRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02AAD54298CD07300807B3C /* BroadcastToRelaysView.swift */; };
|
||||||
|
B0B92015298F21F0008E39BA /* SelectableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0B92014298F21F0008E39BA /* SelectableRowView.swift */; };
|
||||||
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
|
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
|
||||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
||||||
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
|
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
|
||||||
@@ -483,6 +485,8 @@
|
|||||||
7C60CAEE298471A1009C80D6 /* CoreSVG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSVG.swift; sourceTree = "<group>"; };
|
7C60CAEE298471A1009C80D6 /* CoreSVG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSVG.swift; sourceTree = "<group>"; };
|
||||||
7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableScrollView.swift; sourceTree = "<group>"; };
|
7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableScrollView.swift; sourceTree = "<group>"; };
|
||||||
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
|
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
|
||||||
|
B02AAD54298CD07300807B3C /* BroadcastToRelaysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BroadcastToRelaysView.swift; sourceTree = "<group>"; };
|
||||||
|
B0B92014298F21F0008E39BA /* SelectableRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableRowView.swift; sourceTree = "<group>"; };
|
||||||
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
|
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
|
||||||
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
||||||
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
|
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -681,6 +685,7 @@
|
|||||||
4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */,
|
4C3AC7A628369BA200E1F516 /* SearchHomeView.swift */,
|
||||||
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */,
|
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */,
|
||||||
4C363AA128296A7E006E126D /* SearchView.swift */,
|
4C363AA128296A7E006E126D /* SearchView.swift */,
|
||||||
|
B0B92014298F21F0008E39BA /* SelectableRowView.swift */,
|
||||||
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */,
|
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */,
|
||||||
4C3AC7A02835A81400E1F516 /* SetupView.swift */,
|
4C3AC7A02835A81400E1F516 /* SetupView.swift */,
|
||||||
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */,
|
E9E4ED0A295867B900DD7078 /* ThreadV2View.swift */,
|
||||||
@@ -758,6 +763,7 @@
|
|||||||
4C06670028FC7C5900038D2A /* RelayView.swift */,
|
4C06670028FC7C5900038D2A /* RelayView.swift */,
|
||||||
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */,
|
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */,
|
||||||
F7908E91298B0F0700AB113A /* RelayDetailView.swift */,
|
F7908E91298B0F0700AB113A /* RelayDetailView.swift */,
|
||||||
|
B02AAD54298CD07300807B3C /* BroadcastToRelaysView.swift */,
|
||||||
);
|
);
|
||||||
path = Relays;
|
path = Relays;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1119,6 +1125,7 @@
|
|||||||
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
|
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
|
||||||
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
|
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
|
||||||
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
|
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
|
||||||
|
B02AAD55298CD07300807B3C /* BroadcastToRelaysView.swift in Sources */,
|
||||||
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
|
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
|
||||||
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
||||||
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
||||||
@@ -1135,6 +1142,7 @@
|
|||||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
|
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */,
|
||||||
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */,
|
F7F0BA272978E54D009531F3 /* ParicipantsView.swift in Sources */,
|
||||||
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
|
4C0A3F91280F6528000448DE /* ChatView.swift in Sources */,
|
||||||
|
B0B92015298F21F0008E39BA /* SelectableRowView.swift in Sources */,
|
||||||
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
|
4CF0ABE32981BC7D00D66079 /* UserView.swift in Sources */,
|
||||||
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
|
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
|
||||||
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
||||||
|
|||||||
@@ -28,12 +28,14 @@ enum Sheets: Identifiable {
|
|||||||
case post
|
case post
|
||||||
case report(ReportTarget)
|
case report(ReportTarget)
|
||||||
case reply(NostrEvent)
|
case reply(NostrEvent)
|
||||||
|
case broadcast(NostrEvent)
|
||||||
|
|
||||||
var id: String {
|
var id: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .report: return "report"
|
case .report: return "report"
|
||||||
case .post: return "post"
|
case .post: return "post"
|
||||||
case .reply(let ev): return "reply-" + ev.id
|
case .reply(let ev): return "reply-" + ev.id
|
||||||
|
case .broadcast(let ev): return "broadcast-" + ev.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,6 +300,8 @@ struct ContentView: View {
|
|||||||
PostView(replying_to: nil, references: [], damus_state: damus_state!)
|
PostView(replying_to: nil, references: [], damus_state: damus_state!)
|
||||||
case .reply(let event):
|
case .reply(let event):
|
||||||
ReplyView(replying_to: event, damus: damus_state!)
|
ReplyView(replying_to: event, damus: damus_state!)
|
||||||
|
case .broadcast(let event):
|
||||||
|
BroadcastToRelaysView(state: .init(state: damus_state!), broadCastEvent: event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onOpenURL { url in
|
.onOpenURL { url in
|
||||||
@@ -355,7 +359,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.onReceive(handle_notify(.broadcast_event)) { obj in
|
.onReceive(handle_notify(.broadcast_event)) { obj in
|
||||||
let ev = obj.object as! NostrEvent
|
let ev = obj.object as! NostrEvent
|
||||||
self.damus_state?.pool.send(.event(ev))
|
self.active_sheet = .broadcast(ev)
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.unfollow)) { notif in
|
.onReceive(handle_notify(.unfollow)) { notif in
|
||||||
guard let privkey = self.privkey else {
|
guard let privkey = self.privkey else {
|
||||||
@@ -415,10 +419,10 @@ struct ContentView: View {
|
|||||||
|
|
||||||
let post_res = obj.object as! NostrPostResult
|
let post_res = obj.object as! NostrPostResult
|
||||||
switch post_res {
|
switch post_res {
|
||||||
case .post(let post):
|
case .post(let post, onlyToRelayIds: let relayIds):
|
||||||
print("post \(post.content)")
|
print("post \(post.content)")
|
||||||
let new_ev = post_to_event(post: post, privkey: privkey, pubkey: pubkey)
|
let new_ev = post_to_event(post: post, privkey: privkey, pubkey: pubkey)
|
||||||
self.damus_state?.pool.send(.event(new_ev))
|
self.damus_state?.pool.send(.event(new_ev), to: relayIds)
|
||||||
case .cancel:
|
case .cancel:
|
||||||
active_sheet = nil
|
active_sheet = nil
|
||||||
print("post cancelled")
|
print("post cancelled")
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct RelayInfo: Codable {
|
public struct RelayInfo: Codable, Hashable {
|
||||||
let read: Bool
|
let read: Bool
|
||||||
let write: Bool
|
let write: Bool
|
||||||
|
|
||||||
static let rw = RelayInfo(read: true, write: true)
|
static let rw = RelayInfo(read: true, write: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct RelayDescriptor: Codable {
|
public struct RelayDescriptor: Codable, Hashable {
|
||||||
public let url: URL
|
public let url: URL
|
||||||
public let info: RelayInfo
|
public let info: RelayInfo
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,8 +133,8 @@ struct DMChatView: View {
|
|||||||
|
|
||||||
message = ""
|
message = ""
|
||||||
|
|
||||||
damus_state.pool.send(.event(dm))
|
|
||||||
end_editing()
|
end_editing()
|
||||||
|
damus_state.pool.send(.event(dm))
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|||||||
@@ -8,13 +8,15 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum NostrPostResult {
|
enum NostrPostResult {
|
||||||
case post(NostrPost)
|
case post(NostrPost, onlyToRelayIds: [String]?)
|
||||||
case cancel
|
case cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
|
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
|
||||||
|
|
||||||
struct PostView: View {
|
struct PostView: View {
|
||||||
|
@State var isPresentingRelaysScreen: Bool = false
|
||||||
|
@StateObject var relaysScreenState: BroadcastToRelaysView.ViewState
|
||||||
@State var post: String = ""
|
@State var post: String = ""
|
||||||
@FocusState var focus: Bool
|
@FocusState var focus: Bool
|
||||||
@State var showPrivateKeyWarning: Bool = false
|
@State var showPrivateKeyWarning: Bool = false
|
||||||
@@ -25,6 +27,14 @@ struct PostView: View {
|
|||||||
|
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
|
init(replying_to: NostrEvent?, references: [ReferencedId], damus_state: DamusState) {
|
||||||
|
_relaysScreenState = StateObject(wrappedValue: BroadcastToRelaysView.ViewState(state: damus_state))
|
||||||
|
|
||||||
|
self.replying_to = replying_to
|
||||||
|
self.references = references
|
||||||
|
self.damus_state = damus_state
|
||||||
|
}
|
||||||
|
|
||||||
enum FocusField: Hashable {
|
enum FocusField: Hashable {
|
||||||
case post
|
case post
|
||||||
}
|
}
|
||||||
@@ -46,7 +56,9 @@ struct PostView: View {
|
|||||||
let content = self.post.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
let content = self.post.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||||
let new_post = NostrPost(content: content, references: references, kind: kind)
|
let new_post = NostrPost(content: content, references: references, kind: kind)
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
|
NotificationCenter.default.post(name: .post,
|
||||||
|
object: NostrPostResult.post(new_post,
|
||||||
|
onlyToRelayIds: relaysScreenState.limitingRelayIds))
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,16 +67,55 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
NavigationView {
|
||||||
HStack {
|
VStack {
|
||||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of posting a note.")) {
|
ZStack(alignment: .topLeading) {
|
||||||
self.cancel()
|
TextEditor(text: $post)
|
||||||
|
.focused($focus)
|
||||||
|
.textInputAutocapitalization(.sentences)
|
||||||
|
|
||||||
|
if post.isEmpty {
|
||||||
|
Text(POST_PLACEHOLDER)
|
||||||
|
.padding(.top, 8)
|
||||||
|
.padding(.leading, 4)
|
||||||
|
.foregroundColor(Color(uiColor: .placeholderText))
|
||||||
|
.allowsHitTesting(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.foregroundColor(.primary)
|
|
||||||
|
// This if-block observes @ for tagging
|
||||||
Spacer()
|
if let searching = get_searching_string(post) {
|
||||||
|
VStack {
|
||||||
if !is_post_empty {
|
Spacer()
|
||||||
|
UserSearch(damus_state: damus_state, search: searching, post: $post)
|
||||||
|
}.zIndex(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear() {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
|
self.focus = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
|
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of posting a note.")) {
|
||||||
|
self.cancel()
|
||||||
|
}
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
}
|
||||||
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||||
|
HStack {
|
||||||
|
Button(action: { isPresentingRelaysScreen.toggle() }) {
|
||||||
|
Image(systemName: "network")
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.overlay {
|
||||||
|
if relaysScreenState.hasExcludedRelays {
|
||||||
|
Circle()
|
||||||
|
.stroke(style: StrokeStyle(lineWidth: 2, lineCap: .round, dash: [8, 5]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Button(NSLocalizedString("Post", comment: "Button to post a note.")) {
|
Button(NSLocalizedString("Post", comment: "Button to post a note.")) {
|
||||||
showPrivateKeyWarning = contentContainsPrivateKey(self.post)
|
showPrivateKeyWarning = contentContainsPrivateKey(self.post)
|
||||||
|
|
||||||
@@ -72,31 +123,13 @@ struct PostView: View {
|
|||||||
self.send_post()
|
self.send_post()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.disabled(is_post_empty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding([.top, .bottom], 4)
|
.sheet(isPresented: $isPresentingRelaysScreen, content: {
|
||||||
|
BroadcastToRelaysView(state: relaysScreenState, broadCastEvent: nil)
|
||||||
ZStack(alignment: .topLeading) {
|
})
|
||||||
TextEditor(text: $post)
|
.padding()
|
||||||
.focused($focus)
|
|
||||||
.textInputAutocapitalization(.sentences)
|
|
||||||
|
|
||||||
if post.isEmpty {
|
|
||||||
Text(POST_PLACEHOLDER)
|
|
||||||
.padding(.top, 8)
|
|
||||||
.padding(.leading, 4)
|
|
||||||
.foregroundColor(Color(uiColor: .placeholderText))
|
|
||||||
.allowsHitTesting(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This if-block observes @ for tagging
|
|
||||||
if let searching = get_searching_string(post) {
|
|
||||||
VStack {
|
|
||||||
Spacer()
|
|
||||||
UserSearch(damus_state: damus_state, search: searching, post: $post)
|
|
||||||
}.zIndex(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
|
|||||||
123
damus/Views/Relays/BroadcastToRelaysView.swift
Normal file
123
damus/Views/Relays/BroadcastToRelaysView.swift
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
//
|
||||||
|
// BroadcastToRelaysView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by devandsev on 2/4/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
extension BroadcastToRelaysView {
|
||||||
|
|
||||||
|
class ViewState: ObservableObject {
|
||||||
|
let damusState: DamusState
|
||||||
|
@Published var relayRows: [RowState] = []
|
||||||
|
|
||||||
|
var hasExcludedRelays: Bool { relayRows.contains { !$0.isSelected } }
|
||||||
|
var selectedRelays: [RelayDescriptor] { relayRows.filter { $0.isSelected }.map { $0.relay } }
|
||||||
|
var limitingRelayIds: [String]? {
|
||||||
|
guard hasExcludedRelays else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return selectedRelays.map { get_relay_id($0.url) }
|
||||||
|
}
|
||||||
|
|
||||||
|
init(state: DamusState) {
|
||||||
|
damusState = state
|
||||||
|
relayRows = state.pool.descriptors.map { RowState(relay: $0, isSelected: true) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RowState: Equatable {
|
||||||
|
let relay: RelayDescriptor
|
||||||
|
var isSelected: Bool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list of relays you can select to send your event to
|
||||||
|
///
|
||||||
|
/// Can be presented in 2 modes:
|
||||||
|
/// - If `broadCastEvent` is nil, user selects relays, goes to the previous screen and the post/reply will be sent to selected relays only. Presented in this mode from `PostView` and `ReplyView` by tapping relays button.
|
||||||
|
/// - If `broadCastEvent` is not nil, there will be a "Broadcast" button in the navBar to broadcast this event to selected relays. Presented in this mode by long-pressing a post and choosing "Broadcast".
|
||||||
|
struct BroadcastToRelaysView: View {
|
||||||
|
@ObservedObject var state: ViewState
|
||||||
|
|
||||||
|
let broadCastEvent: NostrEvent?
|
||||||
|
|
||||||
|
@Environment(\.presentationMode) var presentationMode
|
||||||
|
|
||||||
|
func selectAll() {
|
||||||
|
$state.relayRows.forEach { $0.wrappedValue.isSelected = true }
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectOne() {
|
||||||
|
guard let firstSelectedRow = $state.relayRows.first(where: { $0.wrappedValue.isSelected }) else {
|
||||||
|
$state.relayRows.forEach { $0.wrappedValue.isSelected = false }
|
||||||
|
$state.relayRows.first?.wrappedValue.isSelected = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$state.relayRows.forEach { $0.wrappedValue.isSelected = false }
|
||||||
|
firstSelectedRow.wrappedValue.isSelected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func dismiss() {
|
||||||
|
self.presentationMode.wrappedValue.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
Form {
|
||||||
|
Section {
|
||||||
|
List(Array($state.relayRows), id: \.wrappedValue.relay.url) { $relayRow in
|
||||||
|
SelectableRowView(isSelected: $relayRow.isSelected,
|
||||||
|
shouldChangeSelection: {
|
||||||
|
return !relayRow.isSelected || $state.relayRows.filter { $0.wrappedValue.isSelected }.count > 1
|
||||||
|
|
||||||
|
} ) {
|
||||||
|
RelayView(state: state.damusState, relay: relayRow.relay.url.absoluteString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header: {
|
||||||
|
HStack {
|
||||||
|
Text("Relays to send to", comment: "Section header text for relay server list. On this screen user can select to which specific relays the post should be sent.")
|
||||||
|
Spacer()
|
||||||
|
Button(NSLocalizedString("All", comment: "Button to select all relays in the list to which the post will be sent")) {
|
||||||
|
self.selectAll()
|
||||||
|
}
|
||||||
|
.disabled(!state.hasExcludedRelays)
|
||||||
|
Button(NSLocalizedString("One", comment: "Button to select only one relay from the list to which the post will be sent")) {
|
||||||
|
self.selectOne()
|
||||||
|
}
|
||||||
|
.disabled(state.selectedRelays.count == 1)
|
||||||
|
}
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
|
if broadCastEvent != nil {
|
||||||
|
Button(action: {
|
||||||
|
dismiss()
|
||||||
|
}) {
|
||||||
|
Text("Cancel", comment: "Navigation bar button to cancel broadcasting the user's note to all of the user's connected relay servers.")
|
||||||
|
}.foregroundColor(.primary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
|
if broadCastEvent != nil {
|
||||||
|
Button(action: {
|
||||||
|
if let broadCastEvent = broadCastEvent {
|
||||||
|
state.damusState.pool.send(.event(broadCastEvent), to: state.limitingRelayIds)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Broadcast", comment: "Navigation bar button to confirm broadcasting the user's note to all of the user's connected relay servers.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
damus/Views/SelectableRowView.swift
Normal file
36
damus/Views/SelectableRowView.swift
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// File.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by devandsev on 2/4/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SelectableRowView <Content: View>: View {
|
||||||
|
|
||||||
|
@Binding var isSelected: Bool
|
||||||
|
var shouldChangeSelection: () -> Bool
|
||||||
|
var content: () -> Content
|
||||||
|
|
||||||
|
@available(iOS, deprecated: 16, message: "In iOS 15 List rows selection works only in editing mode; with iOS 16 selection doesn't require Edit button at all. Consider using standard selection mechanism when deployment target is iOS 16")
|
||||||
|
init(isSelected: Binding<Bool>, shouldChangeSelection: @escaping () -> Bool = { true }, @ViewBuilder content: @escaping () -> Content) {
|
||||||
|
_isSelected = isSelected
|
||||||
|
self.shouldChangeSelection = shouldChangeSelection
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
content()
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
|
||||||
|
}
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
if shouldChangeSelection() {
|
||||||
|
isSelected.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user