diff --git a/Peered/ContentView.swift b/Peered/ContentView.swift index a4c00ec..78484ea 100644 --- a/Peered/ContentView.swift +++ b/Peered/ContentView.swift @@ -7,18 +7,28 @@ import SwiftUI +extension EnvironmentValues { + @Entry var ownPeer: OwnPeer = .fallback +} + struct ContentView: View { @AppStorage("peered_username") private var username: String = "" @State private var notes = [Note]() @State private var notesClient: NoteEditingSessionClient? + @State private var ownPeer: OwnPeer? var body: some View { - NavigationView { + NavigationStack { List(notes) { note in NavigationLink(note.name) { - NoteEditorScreen(note: note, username: username) + let peer = ownPeer ?? .init(peer: .init(displayName: username)) + if ownPeer == nil { + ownPeer = peer + } + return NoteEditorScreen(note: note, peer: peer) } } + .environment(\.ownPeer, ownPeer ?? .fallback) .navigationTitle("Peered") .toolbar { Button("Create note") { @@ -30,7 +40,7 @@ struct ContentView: View { .onAppear { notes = NotesStorage().loadNotes() if notesClient == nil { - notesClient = .init(id: username) + notesClient = .init(peer: .init(displayName: username)) } notesClient?.startBrowsingForNotes() } diff --git a/Peered/NoteEditor/ManageMembersView.swift b/Peered/NoteEditor/ManageMembersView.swift new file mode 100644 index 0000000..55f29a5 --- /dev/null +++ b/Peered/NoteEditor/ManageMembersView.swift @@ -0,0 +1,26 @@ +// +// ManageMembersView.swift +// Peered +// +// Created by Oskar Chybowski on 06/10/2025. +// +import SwiftUI + +struct ManageMembersView: View { + @Bindable var noteAdvertiser: NoteEditingSessionServer + + var body: some View { + List(noteAdvertiser.visiblePeers) { peer in + HStack { + Text(peer.id) + + Spacer() + + PeerStateButton(peerState: peer.state) { + noteAdvertiser.invite(peer: peer) + } + } + } + .navigationTitle("Visible users") + } +} diff --git a/Peered/NoteEditor/NoteEditingSessionServer.swift b/Peered/NoteEditor/NoteEditingSessionServer.swift index 9631462..8becc69 100644 --- a/Peered/NoteEditor/NoteEditingSessionServer.swift +++ b/Peered/NoteEditor/NoteEditingSessionServer.swift @@ -1,15 +1,39 @@ import MultipeerConnectivity import Foundation +struct OwnPeer { + let peer: MCPeerID + + static var fallback: Self { Self(peer: .init(displayName: "fallback_user")) } +} + +struct Peer: Identifiable { + enum ConnectionState { + case available + case joined + case rejected + case invitationPending + } + + var id: String { + mcPeer.displayName + } + let mcPeer: MCPeerID + var state: ConnectionState +} + @Observable final class NoteEditingSessionServer: NSObject { private let session: MCSession private let browser: MCNearbyServiceBrowser + private let ownPeer: OwnPeer - init(username: String) { - let peer = MCPeerID(displayName: username) - browser = .init(peer: peer, serviceType: "peered") - session = .init(peer: peer) + var visiblePeers: [Peer] = [] + + init(peer: OwnPeer) { + ownPeer = peer + browser = .init(peer: peer.peer, serviceType: "peered") + session = .init(peer: peer.peer) super.init() browser.delegate = self } @@ -21,6 +45,16 @@ final class NoteEditingSessionServer: NSObject { func stopServer() { browser.stopBrowsingForPeers() } + + func invite(peer: Peer) { + guard peer.state == .available else { return } + browser.invitePeer( + peer.mcPeer, + to: session, + withContext: nil, // FIXME: put note here? + timeout: 5 + ) + } } extension NoteEditingSessionServer: MCNearbyServiceBrowserDelegate { @@ -29,9 +63,13 @@ extension NoteEditingSessionServer: MCNearbyServiceBrowserDelegate { foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]? ) { - browser.invitePeer(peerID, to: session, withContext: nil, timeout: 30) - print("seeing peer \(peerID.displayName)") + guard !visiblePeers.contains(where: { $0.mcPeer == peerID }) && peerID.displayName != ownPeer.peer.displayName else { return } + let newPeer = Peer(mcPeer: peerID, state: .available) + visiblePeers.append(newPeer) } - func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) {} + func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { + guard let peerIdx = visiblePeers.firstIndex(where: { $0.mcPeer == peerID }) else { return } + visiblePeers.remove(at: peerIdx) + } } diff --git a/Peered/NoteEditor/NoteEditorScreen.swift b/Peered/NoteEditor/NoteEditorScreen.swift index 9d48867..60f0219 100644 --- a/Peered/NoteEditor/NoteEditorScreen.swift +++ b/Peered/NoteEditor/NoteEditorScreen.swift @@ -6,6 +6,7 @@ // import SwiftUI +import MultipeerConnectivity struct NoteEditorScreen: View { let note: Note @@ -13,14 +14,27 @@ struct NoteEditorScreen: View { @State private var noteContent: String = "" @State private var timer = Timer.publish(every: 5, on: .current, in: .common) .autoconnect() + @State private var showManageMembers = false - init(note: Note, username: String) { + init(note: Note, peer: OwnPeer) { self.note = note - self._noteAdvertiser = .init(wrappedValue: .init(username: username)) + self._noteAdvertiser = .init(initialValue: .init(peer: peer)) } var body: some View { TextEditor(text: $noteContent) + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button("Manage members") { + showManageMembers = true + } + } + } + .sheet(isPresented: $showManageMembers) { + NavigationStack { + ManageMembersView(noteAdvertiser: noteAdvertiser) + } + } .onAppear { noteContent = try! String(contentsOf: note.path, encoding: .utf8) noteAdvertiser.startServer() diff --git a/Peered/NoteEditor/PeerStateButton.swift b/Peered/NoteEditor/PeerStateButton.swift new file mode 100644 index 0000000..fb3482d --- /dev/null +++ b/Peered/NoteEditor/PeerStateButton.swift @@ -0,0 +1,26 @@ +// +// PeerStateButton.swift +// Peered +// +// Created by Oskar Chybowski on 06/10/2025. +// + +import SwiftUI + +struct PeerStateButton: View { + let peerState: Peer.ConnectionState + let onTap: () -> Void + + var body: some View { + switch peerState { + case .available: + Button("Invite", action: onTap) + case .joined: + Text("Joined") + case .rejected: + Text("Rejected") + case .invitationPending: + Text("Invitation pending") + } + } +} diff --git a/Peered/NotesList/NoteEditingSessionClient.swift b/Peered/NotesList/NoteEditingSessionClient.swift index 13b54da..c00e3a3 100644 --- a/Peered/NotesList/NoteEditingSessionClient.swift +++ b/Peered/NotesList/NoteEditingSessionClient.swift @@ -13,16 +13,17 @@ import Foundation final class NoteEditingSessionClient: NSObject { private let session: MCSession private let advertiser: MCNearbyServiceAdvertiser + private let ownPeer: MCPeerID - init(id: String) { - let id = MCPeerID(displayName: id) + init(peer: MCPeerID) { + ownPeer = peer session = MCSession( - peer: id, + peer: peer, securityIdentity: nil, encryptionPreference: .required ) advertiser = MCNearbyServiceAdvertiser( - peer: id, + peer: peer, discoveryInfo: [:], serviceType: "peered" )