1// CreateGroupView.swift2// SophaxChat3//4// Sheet for creating a new encrypted group conversation.5// The creator names the group and selects members from known peers.67import SwiftUI8import SophaxChatCore910struct CreateGroupView: View {11@EnvironmentObject var appState: AppState12@Environment(\.dismiss) private var dismiss1314@State private var groupName: String =""15@State private var selectedPeerIDs: Set<String>=[]16/// Defaults to true — MLS is preferred when all members support it.17/// Auto-updated as the member selection changes.18@State private var useMLS: Bool = true19@FocusState private var nameFocused: Bool2021 private var allSelectedPeersHaveKeyPackage: Bool {22 selectedPeerIDs.allSatisfy { appState.peerHasMLSKeyPackage($0)}23}2425 private var canCreate: Bool {26 let trimmed = groupName.trimmingCharacters(in:.whitespacesAndNewlines)27 guard !trimmed.isEmpty, trimmed.count <= 64,!selectedPeerIDs.isEmpty else { return false }28 if useMLS &&!allSelectedPeersHaveKeyPackage { return false }29 return true30}3132 var body: some View {33 NavigationStack {34 Form {35 Section {36 TextField("Group name", text: $groupName)37.focused($nameFocused)38.autocorrectionDisabled()39} header:{40 Text("Group Name")41}4243 Section {44 Toggle(isOn: $useMLS){45 Label("MLS (Post-Compromise Security)", systemImage:"lock.shield")46}47.disabled(!selectedPeerIDs.isEmpty &&!allSelectedPeersHaveKeyPackage)48} header:{49 Text("Encryption Protocol")50} footer:{51 if useMLS {52 if allSelectedPeersHaveKeyPackage || selectedPeerIDs.isEmpty {53 Text("MLS (RFC 9420) is the default — provides post-compromise security per epoch. All members must have exchanged keys with you at least once.")54.foregroundStyle(.secondary)55} else {56 Text("One or more selected members have not yet exchanged MLS keys. The group will use Sender Keys v2 instead. Ask them to open SophaxChat while nearby, then upgrade later.")57.foregroundStyle(.orange)58}59} else {60 Text("Sender Keys v2 — Signal-style per-sender chains. Enable MLS for RFC 9420 post-compromise security.")61.foregroundStyle(.secondary)62}63}6465 Section {66 if appState.peers.isEmpty {67 Text("No nearby peers found. Make sure other devices have SophaxChat open.")68.foregroundStyle(.secondary)69.font(.subheadline)70} else {71 ForEach(appState.peers.filter {!appState.isBlocked($0.id)}, id: \.id){ peer in72 Button {73 if selectedPeerIDs.contains(peer.id){74 selectedPeerIDs.remove(peer.id)75} else {76 selectedPeerIDs.insert(peer.id)77}78} label:{79 HStack(spacing: 12){80 PeerAvatar(peer: peer, size: 36)81 VStack(alignment:.leading, spacing: 1){82 Text(appState.displayName(for: peer))83.font(.subheadline.weight(.medium))84.foregroundStyle(.primary)85 if appState.onlinePeers.contains(peer.id){86 Text("Online")87.font(.caption2)88.foregroundStyle(.green)89}90}91 Spacer()92 if selectedPeerIDs.contains(peer.id){93 Image(systemName:"checkmark.circle.fill")94.foregroundStyle(Color.accentColor)95} else {96 Image(systemName:"circle")97.foregroundStyle(.tertiary)98}99}100}101.buttonStyle(.plain)102}103}104} header:{105 Text("Add Members")106} footer:{107 if !selectedPeerIDs.isEmpty {108 Text("\(selectedPeerIDs.count) member\(selectedPeerIDs.count == 1 ? "" : "s") selected (plus you)")109}110}111}112.navigationTitle("New Group")113.navigationBarTitleDisplayMode(.inline)114// Auto-select MLS when all chosen members support it; fall back to SKv2 when not.115// The user can still override the toggle manually.116.onChange(of: selectedPeerIDs){117 if !selectedPeerIDs.isEmpty {118 useMLS = allSelectedPeersHaveKeyPackage119}120}121.toolbar {122 ToolbarItem(placement:.cancellationAction){123 Button("Cancel"){ dismiss()}124}125 ToolbarItem(placement:.confirmationAction){126 Button("Create"){127 let name = groupName.trimmingCharacters(in:.whitespacesAndNewlines)128 if useMLS {129 appState.createMLSGroup(name: name, memberPeerIDs: Array(selectedPeerIDs))130} else {131 appState.createGroup(name: name, memberPeerIDs: Array(selectedPeerIDs))132}133 dismiss()134}135.disabled(!canCreate)136.fontWeight(.semibold)137}138}139.onAppear { nameFocused = true }140}141}142}143144#Preview {145 CreateGroupView().environmentObject(AppState())146}