Bugfixes, renames, create custom TextEditor, logic simplifications
This commit is contained in:
+10
-15
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// AllNotesScreen.swift
|
||||
// Peered
|
||||
//
|
||||
// Created by Oskar Chybowski on 11/05/2025.
|
||||
@@ -7,18 +7,14 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension EnvironmentValues {
|
||||
@Entry var ownPeer: OwnPeer = .fallback
|
||||
}
|
||||
|
||||
struct ContentView: View {
|
||||
@AppStorage("peered_username") private var username: String = "fallback_user"
|
||||
struct AllNotesScreen: View {
|
||||
@AppStorage("peered_username") private var username: String?
|
||||
@State private var notes = [Note]()
|
||||
@State private var notesClient: NoteEditingSessionClient?
|
||||
@State private var ownPeer: OwnPeer?
|
||||
|
||||
var isUsernameValid: Bool {
|
||||
username != "fallback_user" && !username.isEmpty
|
||||
username.map(\.isEmpty) ?? false
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -44,7 +40,6 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.environment(\.ownPeer, ownPeer ?? .fallback)
|
||||
.navigationTitle("Peered")
|
||||
.toolbar {
|
||||
Button("Create note") {
|
||||
@@ -55,20 +50,20 @@ struct ContentView: View {
|
||||
}
|
||||
.onAppear {
|
||||
notes = NotesStorage().loadNotes()
|
||||
if isUsernameValid {
|
||||
setupSession()
|
||||
if let username, isUsernameValid {
|
||||
setupSession(username: username)
|
||||
}
|
||||
}
|
||||
.onChange(of: username) { _, newUsername in
|
||||
guard isUsernameValid else { return }
|
||||
setupSession()
|
||||
guard let newUsername, isUsernameValid else { return }
|
||||
setupSession(username: newUsername)
|
||||
}
|
||||
.sheet(isPresented: .constant(!isUsernameValid)) {
|
||||
SetUserNameBottomSheetView(username: $username)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupSession() {
|
||||
private func setupSession(username: String) {
|
||||
notesClient?.stopBrowsingForNotes()
|
||||
let peer = OwnPeer(peer: .init(displayName: username))
|
||||
ownPeer = peer
|
||||
@@ -78,5 +73,5 @@ struct ContentView: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContentView()
|
||||
AllNotesScreen()
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
//
|
||||
import SwiftUI
|
||||
|
||||
struct ManageMembersView: View {
|
||||
struct ManageMembersScreen: View {
|
||||
@Bindable var noteAdvertiser: NoteEditingSessionServer
|
||||
let noteTitle: String
|
||||
@Binding var noteContent: String
|
||||
@@ -16,9 +16,7 @@ struct Peer: Identifiable {
|
||||
case invitationPending
|
||||
}
|
||||
|
||||
var id: String {
|
||||
mcPeer.displayName
|
||||
}
|
||||
var id: String { mcPeer.displayName }
|
||||
let mcPeer: MCPeerID
|
||||
var state: ConnectionState
|
||||
}
|
||||
@@ -47,6 +45,7 @@ final class NoteEditingSessionServer: NSObject {
|
||||
|
||||
func stopServer() {
|
||||
browser.stopBrowsingForPeers()
|
||||
session.disconnect()
|
||||
}
|
||||
|
||||
func invite(peer: Peer, to note: NoteInvitation.NoteContent) {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import SwiftUI
|
||||
|
||||
struct NoteTextEditor: View {
|
||||
@Binding var text: String
|
||||
let remoteText: String?
|
||||
@State private var selection: TextSelection? = nil
|
||||
|
||||
var body: some View {
|
||||
TextEditor(text: $text, selection: $selection)
|
||||
.onChange(of: remoteText) { oldValue, newValue in
|
||||
guard let newValue, newValue != text else { return }
|
||||
|
||||
let previousRemote = oldValue ?? text
|
||||
|
||||
// Where the remote edit begins
|
||||
let changeStart = commonPrefixUTF16Length(previousRemote, newValue)
|
||||
|
||||
// How many UTF-16 units were inserted (positive) or removed (negative)
|
||||
let delta = newValue.utf16.count - previousRemote.utf16.count
|
||||
|
||||
// Apply remote text first
|
||||
text = newValue
|
||||
|
||||
// Get current cursor offset
|
||||
guard
|
||||
let selection,
|
||||
case .selection(let range) = selection.indices,
|
||||
let cursorPos = range.lowerBound.samePosition(in: text.utf16)
|
||||
else { return }
|
||||
|
||||
let cursorUTF16 = text.utf16.distance(
|
||||
from: text.utf16.startIndex,
|
||||
to: cursorPos
|
||||
)
|
||||
|
||||
// Only shift if the change happened strictly before the cursor
|
||||
guard changeStart < cursorUTF16 else { return }
|
||||
|
||||
let newCursorUTF16 = max(0, min(cursorUTF16 + delta, newValue.utf16.count))
|
||||
let newUTF16 = newValue.utf16
|
||||
let newUTF16Index = newUTF16.index(newUTF16.startIndex, offsetBy: newCursorUTF16)
|
||||
guard let newIndex = newUTF16Index.samePosition(in: newValue) else { return }
|
||||
|
||||
self.selection = TextSelection(range: newIndex..<newIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private func commonPrefixUTF16Length(_ a: String, _ b: String) -> Int {
|
||||
var count = 0
|
||||
let aUTF16 = a.utf16
|
||||
let bUTF16 = b.utf16
|
||||
let minLen = min(aUTF16.count, bUTF16.count)
|
||||
while count < minLen {
|
||||
let aIdx = aUTF16.index(aUTF16.startIndex, offsetBy: count)
|
||||
let bIdx = bUTF16.index(bUTF16.startIndex, offsetBy: count)
|
||||
guard aUTF16[aIdx] == bUTF16[bIdx] else { break }
|
||||
count += 1
|
||||
}
|
||||
return count
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
struct NoteMessage: Codable {
|
||||
let senderID: String
|
||||
let content: String
|
||||
}
|
||||
@@ -36,8 +36,10 @@ struct NotesStorage {
|
||||
|
||||
func createNote(name: String) {
|
||||
let currentNotes = loadNotes()
|
||||
var proposedName = name
|
||||
var index: Int? = nil
|
||||
var proposedName: String {
|
||||
index.map { name + " \($0)" } ?? name
|
||||
}
|
||||
|
||||
while currentNotes.contains(where: { $0.name == proposedName }) {
|
||||
if let _index = index {
|
||||
@@ -46,12 +48,6 @@ struct NotesStorage {
|
||||
index = 1
|
||||
}
|
||||
}
|
||||
|
||||
proposedName = if let index {
|
||||
"\(proposedName) \(index)"
|
||||
} else {
|
||||
proposedName
|
||||
}
|
||||
let pathToWrite = URL.documentsDirectory.appendingPathComponent(proposedName).appendingPathExtension(for: .text)
|
||||
try! String().write(to: pathToWrite, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import SwiftUI
|
||||
struct PeeredApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
AllNotesScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import SwiftUI
|
||||
|
||||
struct SetUserNameBottomSheetView: View {
|
||||
@State private var proposedUsername: String = ""
|
||||
@Binding var username: String
|
||||
@Binding var username: String?
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
|
||||
Reference in New Issue
Block a user