rework invite

This commit is contained in:
spaced4ndy 2023-11-29 21:58:10 +04:00
parent ab45ec5e8e
commit 0b7b43cc4b
12 changed files with 273 additions and 366 deletions

View File

@ -580,15 +580,15 @@ func apiVerifyGroupMember(_ groupId: Int64, _ groupMemberId: Int64, connectionCo
return nil
}
func apiAddContact(incognito: Bool) async -> (String, PendingContactConnection)? {
func apiAddContact(incognito: Bool) async -> ((String, PendingContactConnection)?, Alert?) {
guard let userId = ChatModel.shared.currentUser?.userId else {
logger.error("apiAddContact: no current user")
return nil
return (nil, nil)
}
let r = await chatSendCmd(.apiAddContact(userId: userId, incognito: incognito), bgTask: false)
if case let .invitation(_, connReqInvitation, connection) = r { return (connReqInvitation, connection) }
AlertManager.shared.showAlert(connectionErrorAlert(r))
return nil
if case let .invitation(_, connReqInvitation, connection) = r { return ((connReqInvitation, connection), nil) }
let alert = connectionErrorAlert(r)
return (nil, alert)
}
func apiSetConnectionIncognito(connId: Int64, incognito: Bool) async throws -> PendingContactConnection? {

View File

@ -11,7 +11,7 @@ import SwiftUI
struct ChatHelp: View {
@EnvironmentObject var chatModel: ChatModel
@Binding var showSettings: Bool
@State private var showAddChat = false
@State private var showNewChatSheet = false
var body: some View {
ScrollView { chatHelp() }
@ -39,7 +39,7 @@ struct ChatHelp: View {
HStack(spacing: 8) {
Text("Tap button ")
NewChatButton(showAddChat: $showAddChat)
NewChatSheetButton(showNewChatSheet: $showNewChatSheet)
Text("above, then choose:")
}

View File

@ -13,7 +13,7 @@ struct ChatListView: View {
@EnvironmentObject var chatModel: ChatModel
@Binding var showSettings: Bool
@State private var searchText = ""
@State private var showAddChat = false
@State private var showNewChatSheet = false
@State private var userPickerVisible = false
@State private var showConnectDesktop = false
@State private var showCreateGroupSheet = false
@ -125,11 +125,10 @@ struct ChatListView: View {
}
ToolbarItem(placement: .navigationBarTrailing) {
switch chatModel.chatRunning {
// case .some(true): NewChatButton(showAddChat: $showAddChat)
case .some(true):
HStack {
createGroupButton()
NewChatButton2()
NewChatSheetButton(showNewChatSheet: $showNewChatSheet)
}
case .some(false): chatStoppedIcon()
case .none: EmptyView()
@ -207,7 +206,7 @@ struct ChatListView: View {
.padding(.trailing, 12)
connectButton("Tap to start a new chat") {
showAddChat = true
showNewChatSheet = true
}
Spacer()

View File

@ -9,8 +9,20 @@
import SwiftUI
struct AddContactLearnMore: View {
var showTitle: Bool
var body: some View {
List {
if showTitle {
Text("One-time invitation link")
.font(.largeTitle)
.bold()
.fixedSize(horizontal: false, vertical: true)
.padding(.vertical)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
VStack(alignment: .leading, spacing: 18) {
Text("To connect, your contact can scan QR code or use the link in the app.")
Text("If you can't meet in person, show QR code in a video call, or share the link.")
@ -23,6 +35,6 @@ struct AddContactLearnMore: View {
struct AddContactLearnMore_Previews: PreviewProvider {
static var previews: some View {
AddContactLearnMore()
AddContactLearnMore(showTitle: true)
}
}

View File

@ -109,7 +109,7 @@ func shareLinkButton(_ connReqInvitation: String) -> some View {
func oneTimeLinkLearnMoreButton() -> some View {
NavigationLink {
AddContactLearnMore()
AddContactLearnMore(showTitle: false)
.navigationTitle("One-time invitation link")
.navigationBarTitleDisplayMode(.large)
} label: {

View File

@ -71,7 +71,8 @@ struct CreateLinkView: View {
private func createInvitation() {
creatingConnReq = true
Task {
if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) {
let (r, _) = await apiAddContact(incognito: incognitoGroupDefault.get())
if let (connReq, pcc) = r {
await MainActor.run {
connReqInvitation = connReq
contactConnection = pcc

View File

@ -51,7 +51,8 @@ struct NewChatButton: View {
func addContactAction() {
Task {
if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) {
let (r, _) = await apiAddContact(incognito: incognitoGroupDefault.get())
if let (connReq, pcc) = r {
actionSheet = .createLink(link: connReq, connection: pcc)
}
}

View File

@ -1,53 +0,0 @@
//
// NewChatButton2.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 28.11.2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
enum NewChatSheet: Identifiable {
case newChat(link: String, connection: PendingContactConnection)
var id: String {
switch self {
case let .newChat(link, _): return "newChat \(link)"
}
}
}
struct NewChatButton2: View {
@State private var actionSheet: NewChatSheet?
var body: some View {
Button {
addContactAction()
} label: {
Image(systemName: "square.and.pencil")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
}
.sheet(item: $actionSheet) { sheet in
switch sheet {
case let .newChat(link, pcc):
NewChatView(selection: .invite, connReqInvitation: link, contactConnection: pcc)
}
}
}
func addContactAction() {
Task {
if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) {
actionSheet = .newChat(link: connReq, connection: pcc)
}
}
}
}
//#Preview {
// NewChatButton2()
//}

View File

@ -0,0 +1,32 @@
//
// NewChatSheetButton.swift
// SimpleX (iOS)
//
// Created by spaced4ndy on 28.11.2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct NewChatSheetButton: View {
@Binding var showNewChatSheet: Bool
var body: some View {
Button {
showNewChatSheet = true
} label: {
Image(systemName: "square.and.pencil")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
}
.sheet(isPresented: $showNewChatSheet) {
NewChatView(selection: .invite)
}
}
}
//#Preview {
// NewChatSheetButton()
//}

View File

@ -10,6 +10,16 @@ import SwiftUI
import SimpleXChat
import CodeScanner
enum SomeAlert: Identifiable {
case someAlert(alert: Alert, id: String)
var id: String {
switch self {
case let .someAlert(_, id): return id
}
}
}
enum NewChatOption: Identifiable {
case invite
case connect
@ -21,175 +31,119 @@ struct NewChatView: View {
@EnvironmentObject var m: ChatModel
@State var selection: NewChatOption
@State var showScanQRCodeSheet = false
@State var connReqInvitation: String = ""
@State var contactConnection: PendingContactConnection? = nil
@State private var connReqInvitation: String = ""
@State private var contactConnection: PendingContactConnection? = nil
@State private var creatingConnReq = false
@State private var someAlert: SomeAlert?
var body: some View {
NavigationView {
VStack(alignment: .leading) {
Text("Start a New Chat")
.font(.largeTitle)
.bold()
.fixedSize(horizontal: false, vertical: true)
.padding(.vertical)
List {
Group {
HStack {
Text("New chat")
.font(.largeTitle)
.bold()
.fixedSize(horizontal: false, vertical: true)
.padding(.vertical)
Spacer()
InfoSheetButton {
AddContactLearnMore(showTitle: true)
}
}
Picker("New chat", selection: $selection) {
Label("Invite", systemImage: "link")
Label("Share link", systemImage: "link")
.tag(NewChatOption.invite)
Label("Connect", systemImage: "qrcode")
Label("Connect via link", systemImage: "qrcode")
.tag(NewChatOption.connect)
}
.pickerStyle(.segmented)
}
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
switch selection {
case .invite: InviteView(
contactConnection: $contactConnection,
connReqInvitation: connReqInvitation
)
case .connect: ConnectView(showScanQRCodeSheet: showScanQRCodeSheet)
switch selection {
case .invite:
if connReqInvitation != "" {
InviteView(contactConnection: $contactConnection, connReqInvitation: connReqInvitation)
} else if creatingConnReq {
creatingLinkProgressView()
} else {
retryButton()
}
case .connect:
ConnectView(showScanQRCodeSheet: showScanQRCodeSheet)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.padding()
.background(Color(.systemGroupedBackground))
.onChange(of: selection) { sel in
if case .invite = sel,
connReqInvitation == "" && contactConnection == nil && !creatingConnReq {
createInvitation()
}
}
.onChange(of: selection) { sel in
createInvitation(sel)
}
.onAppear {
createInvitation(selection)
}
.onDisappear { m.connReqInv = nil }
.alert(item: $someAlert) { a in
switch a {
case let .someAlert(alert, _): alert
}
.onAppear { m.connReqInv = connReqInvitation }
.onDisappear { m.connReqInv = nil }
}
}
private func createInvitation() {
creatingConnReq = true
Task {
if let (connReq, pcc) = await apiAddContact(incognito: incognitoGroupDefault.get()) {
await MainActor.run {
connReqInvitation = connReq
contactConnection = pcc
m.connReqInv = connReq
}
} else {
await MainActor.run {
creatingConnReq = false
private func createInvitation(_ selection: NewChatOption) {
if case .invite = selection,
connReqInvitation == "" && contactConnection == nil && !creatingConnReq {
creatingConnReq = true
Task {
let (r, alert) = await apiAddContact(incognito: incognitoGroupDefault.get())
if let (connReq, pcc) = r {
await MainActor.run {
connReqInvitation = connReq
contactConnection = pcc
m.connReqInv = connReq
}
} else {
await MainActor.run {
creatingConnReq = false
if let alert = alert {
someAlert = .someAlert(alert: alert, id: "createInvitation error")
}
}
}
}
}
}
private func creatingLinkProgressView() -> some View {
ProgressView("Creating link…")
.progressViewStyle(.circular)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.padding(.top)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
}
private func retryButton() -> some View {
Button {
createInvitation(selection)
} label: {
Text("Retry")
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.padding(.top)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
}
}
struct InviteView: View {
@EnvironmentObject private var chatModel: ChatModel
private struct InviteView: View {
@EnvironmentObject var chatModel: ChatModel
@Binding var contactConnection: PendingContactConnection?
var connReqInvitation: String
@AppStorage(GROUP_DEFAULT_INCOGNITO, store: groupDefaults) private var incognitoDefault = false
var body: some View {
GeometryReader { geo in
ScrollView {
VStack(alignment: .leading, spacing: 8) {
if connReqInvitation != "" {
HStack {
Text("Share this unique invite link")
.textCase(.uppercase)
.font(.footnote)
.foregroundColor(.secondary)
Spacer()
Button {
copyLink()
} label: {
Text("Copy")
.font(.footnote)
}
}
.padding(.horizontal)
Text(simplexChatLink(connReqInvitation))
.lineLimit(2)
.font(.callout)
.padding(.horizontal)
.padding(.vertical, 8)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(uiColor: .systemBackground))
)
Text("Or show this code")
.textCase(.uppercase)
.font(.footnote)
.foregroundColor(.secondary)
.padding(.horizontal)
.padding(.top, 8)
VStack(alignment: .center) {
SimpleXLinkQRCode(uri: connReqInvitation)
.padding(.horizontal)
.padding(.vertical, 10)
.frame(width: geo.size.width * 0.8)
}
.frame(maxWidth: .infinity, alignment: .center)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(uiColor: .systemBackground))
)
VStack(alignment: .leading) {
IncognitoToggle(incognitoEnabled: $incognitoDefault)
Divider()
shareLinkButton2(connReqInvitation)
}
.padding(.horizontal)
.padding(.vertical, 8)
.frame(maxWidth: .infinity)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(uiColor: .systemBackground))
)
.padding(.top)
VStack(alignment: .center) {
oneTimeLinkLearnMoreButton2()
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.top)
} else {
VStack(alignment: .center, spacing: 4) {
ProgressView()
.progressViewStyle(.circular)
Text("Creating link…")
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.top)
// Text("Creating link")
// .textCase(.uppercase)
// .font(.footnote)
// .foregroundColor(.secondary)
// .padding(.horizontal)
//
// VStack(alignment: .center) {
// ProgressView()
// .progressViewStyle(.circular)
// .scaleEffect(2)
// .frame(maxWidth: .infinity)
// .padding(.horizontal)
// .padding(.vertical, 14)
// }
// .frame(maxWidth: .infinity, alignment: .center)
// .background(
// RoundedRectangle(cornerRadius: 12, style: .continuous)
// .fill(Color(uiColor: .systemBackground))
// )
}
}
}
.padding(.vertical)
.onAppear { chatModel.connReqInv = connReqInvitation }
viewBody()
.onChange(of: incognitoDefault) { incognito in
Task {
do {
@ -205,15 +159,54 @@ struct InviteView: View {
}
}
}
}
@ViewBuilder private func viewBody() -> some View {
Section("Share this 1-time invite link") {
shareLinkView()
}
qrCodeView()
Section {
IncognitoToggle(incognitoEnabled: $incognitoDefault)
} footer: {
sharedProfileInfo(incognitoDefault)
}
}
private func copyLink() {
UIPasteboard.general.string = simplexChatLink(connReqInvitation)
private func shareLinkView() -> some View {
HStack {
let link = simplexChatLink(connReqInvitation)
Text(link)
.lineLimit(1)
.font(.caption)
.truncationMode(.middle)
Button {
showShareSheet(items: [link])
} label: {
Image(systemName: "square.and.arrow.up")
}
}
}
private func qrCodeView() -> some View {
Section("Or show this code") {
SimpleXLinkQRCode(uri: connReqInvitation)
.padding()
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(uiColor: .systemBackground))
)
.padding(.horizontal)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
}
}
struct ConnectView: View {
private struct ConnectView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@State var showScanQRCodeSheet = false
@State private var connectionLink: String = ""
@ -222,130 +215,55 @@ struct ConnectView: View {
@State private var scannedLink: String = ""
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 8) {
(
Text("Paste the URL your received from your contact.")
+ Text("\n\n")
+ Text("You'll be connected to begin a private conversation with them.")
)
.font(.footnote)
.foregroundColor(.secondary)
.padding(.horizontal)
HStack {
Text("Unique invite link")
.textCase(.uppercase)
.font(.footnote)
.foregroundColor(.secondary)
Spacer()
if (connectionLink != "") {
Button {
clearLink()
} label: {
Text("Clear")
.font(.footnote)
}
}
viewBody()
.alert(item: $alert) { a in planAndConnectAlert(a, dismiss: true) }
.actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true) }
.sheet(isPresented: $showScanQRCodeSheet) {
if #available(iOS 16.0, *) {
ScanConnectionCodeView(scannedLink: $scannedLink)
.presentationDetents([.fraction(0.8)])
} else {
ScanConnectionCodeView(scannedLink: $scannedLink)
}
.padding(.horizontal)
.padding(.top)
pasteLinkView()
Text("Or scan QR code")
.textCase(.uppercase)
.font(.footnote)
.foregroundColor(.secondary)
.padding(.horizontal)
.padding(.top, 8)
VStack(alignment: .center) {
scanQRCodeButton()
}
.padding(.horizontal)
.padding(.vertical, 8)
.frame(maxWidth: .infinity, alignment: .center)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(uiColor: .systemBackground))
)
VStack(alignment: .center) {
oneTimeLinkLearnMoreButton2()
}
.frame(maxWidth: .infinity, alignment: .center)
.padding(.top)
}
}
.padding(.vertical)
.alert(item: $alert) { a in planAndConnectAlert(a, dismiss: true) }
.actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true) }
.sheet(isPresented: $showScanQRCodeSheet) {
if #available(iOS 16.0, *) {
ScanConnectionCodeView(scannedLink: $scannedLink)
.presentationDetents([.fraction(0.8)])
} else {
ScanConnectionCodeView(scannedLink: $scannedLink)
.onChange(of: scannedLink) { link in
connect(link)
}
}
.onChange(of: scannedLink) { link in
connect(link)
}
}
private func clearLink() {
connectionLink = ""
@ViewBuilder private func viewBody() -> some View {
Section {
// TODO clear link button
pasteLinkView()
} header: {
Text("Paste the link you received")
}
Section {
scanQRCodeButton()
} header: {
Text("Or scan QR code")
}
}
@ViewBuilder private func pasteLinkView() -> some View {
if connectionLink == "" {
VStack(alignment: .center) {
ZStack {
Text("\n")
.font(.callout)
.padding(.horizontal)
.padding(.vertical, 8)
.opacity(0)
Button {
if let link = UIPasteboard.general.string {
// TODO test pasted text is a link, alert if not
connectionLink = link.trimmingCharacters(in: .whitespaces)
connect(connectionLink)
}
} label: {
Text("Click Here to Paste Link")
.foregroundColor(.accentColor)
.padding(.horizontal)
.padding(.vertical, 8)
}
Button {
if let link = UIPasteboard.general.string {
// TODO test pasted text is a link, alert if not
connectionLink = link.trimmingCharacters(in: .whitespaces)
connect(connectionLink)
}
} label: {
settingsRow("doc.plaintext") {
Text("Tap to paste link")
}
}
.frame(maxWidth: .infinity, alignment: .center)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(uiColor: .systemBackground))
)
} else {
VStack() {
ZStack {
Text("\n")
.font(.callout)
.padding(.horizontal)
.padding(.vertical, 8)
.opacity(0)
Text(connectionLink)
.lineLimit(2)
.font(.callout)
.padding(.horizontal)
.padding(.vertical, 8)
}
}
.frame(maxWidth: .infinity)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(Color(uiColor: .systemBackground))
)
Text(connectionLink)
.lineLimit(1)
.font(.caption)
.truncationMode(.middle)
}
}
@ -370,7 +288,7 @@ struct ConnectView: View {
}
}
struct ScanConnectionCodeView: View {
private struct ScanConnectionCodeView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@Binding var scannedLink: String
@ -407,35 +325,33 @@ struct ScanConnectionCodeView: View {
}
}
struct InfoSheetButton<Content: View>: View {
@ViewBuilder let content: Content
@State private var showInfoSheet = false
var body: some View {
Button {
showInfoSheet = true
} label: {
Image(systemName: "info.circle")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
}
.sheet(isPresented: $showInfoSheet) {
content
}
}
}
// TODO move IncognitoToggle here
// TODO move shareLinkButton here
func shareLinkButton2(_ connReqInvitation: String) -> some View {
Button {
showShareSheet(items: [simplexChatLink(connReqInvitation)])
} label: {
settingsRow("square.and.arrow.up") {
Text("Share link")
}
}
}
// TODO move oneTimeLinkLearnMoreButton here
func oneTimeLinkLearnMoreButton2() -> some View {
NavigationLink {
AddContactLearnMore()
.navigationTitle("One-time invitation link")
.navigationBarTitleDisplayMode(.large)
} label: {
settingsRow("info.circle") {
Text("Need Guidance?")
.underline()
}
}
}
// TODO move shareLinkButton to connection details
// TODO move planAndConnect here
// TODO delete NewChatButton, CreateLinkView, ScanToConnectView, PasteToConnectView, AddContactView
//#Preview {
// NewChatView()
//}

View File

@ -10,24 +10,23 @@ import SwiftUI
struct IncognitoHelp: View {
var body: some View {
VStack(alignment: .leading) {
List {
Text("Incognito mode")
.font(.largeTitle)
.bold()
.fixedSize(horizontal: false, vertical: true)
.padding(.vertical)
ScrollView {
VStack(alignment: .leading) {
Group {
Text("Incognito mode protects your privacy by using a new random profile for each contact.")
Text("It allows having many anonymous connections without any shared data between them in a single chat profile.")
Text("When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.")
}
.padding(.bottom)
}
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
VStack(alignment: .leading, spacing: 18) {
Text("Incognito mode protects your privacy by using a new random profile for each contact.")
Text("It allows having many anonymous connections without any shared data between them in a single chat profile.")
Text("When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.")
Text("Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).")
}
.listRowBackground(Color.clear)
}
.frame(maxWidth: .infinity)
.padding()
}
}

View File

@ -179,7 +179,7 @@
64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; };
64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; };
64AEA4ED2B15D2A400334292 /* NewChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AEA4EC2B15D2A400334292 /* NewChatView.swift */; };
64AEA4EF2B15FEE100334292 /* NewChatButton2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AEA4EE2B15FEE100334292 /* NewChatButton2.swift */; };
64AEA4EF2B15FEE100334292 /* NewChatSheetButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AEA4EE2B15FEE100334292 /* NewChatSheetButton.swift */; };
64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */; };
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
@ -465,7 +465,7 @@
64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = "<group>"; };
64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = "<group>"; };
64AEA4EC2B15D2A400334292 /* NewChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = "<group>"; };
64AEA4EE2B15FEE100334292 /* NewChatButton2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatButton2.swift; sourceTree = "<group>"; };
64AEA4EE2B15FEE100334292 /* NewChatSheetButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewChatSheetButton.swift; sourceTree = "<group>"; };
64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemInfoView.swift; sourceTree = "<group>"; };
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
@ -741,7 +741,7 @@
5CB2085228DB7CAF00D024EC /* ConnectViaLinkView.swift */,
64D0C2C529FAC1EC00B38D5F /* AddContactLearnMore.swift */,
64AEA4EC2B15D2A400334292 /* NewChatView.swift */,
64AEA4EE2B15FEE100334292 /* NewChatButton2.swift */,
64AEA4EE2B15FEE100334292 /* NewChatSheetButton.swift */,
);
path = NewChat;
sourceTree = "<group>";
@ -1121,7 +1121,7 @@
5C13730B28156D2700F43030 /* ContactConnectionView.swift in Sources */,
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */,
6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */,
64AEA4EF2B15FEE100334292 /* NewChatButton2.swift in Sources */,
64AEA4EF2B15FEE100334292 /* NewChatSheetButton.swift in Sources */,
5C93293129239BED0090FFF9 /* ProtocolServerView.swift in Sources */,
5C9CC7AD28C55D7800BEF955 /* DatabaseEncryptionView.swift in Sources */,
5CBD285A295711D700EC2CF4 /* ImageUtils.swift in Sources */,