iOS: send/receive messages in chats, connect via QR code (#238)
* send messages from chats * update API to use chat IDs * send messages to groups * generate invitation QR code * connect via QR code
This commit is contained in:
parent
15a91278d6
commit
3b19aaf1d1
@ -1,35 +0,0 @@
|
||||
# Setup for iOS
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Prepare folders:
|
||||
|
||||
```sh
|
||||
mkdir -p ./apps/ios/Libraries/mac ./apps/ios/Libraries/ios ./apps/ios/Libraries/sim
|
||||
```
|
||||
|
||||
## Update binaries
|
||||
|
||||
1. Extract binaries to `./apps/ios/Libraries/mac`.
|
||||
|
||||
2. Prepare binaries:
|
||||
|
||||
```sh
|
||||
chmod +w ./apps/ios/Libraries/mac/*
|
||||
cp ./apps/ios/Libraries/mac/* ./apps/ios/Libraries/ios
|
||||
cp ./apps/ios/Libraries/mac/* ./apps/ios/Libraries/sim
|
||||
for f in ./apps/ios/Libraries/ios/*; do mac2ios $f; done | wc -l
|
||||
for f in ./apps/ios/Libraries/sim/*; do mac2ios -s $f; done | wc -l
|
||||
```
|
||||
|
||||
3. Put binaries into `./apps/ios/Libraries`.
|
||||
|
||||
```sh
|
||||
cp ./apps/ios/Libraries/sim/* ./apps/ios/Libraries
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```sh
|
||||
cp ./apps/ios/Libraries/ios/* ./apps/ios/Libraries
|
||||
```
|
@ -57,15 +57,15 @@ struct ChatPreview: Identifiable, Decodable {
|
||||
}
|
||||
|
||||
enum ChatType: String {
|
||||
case direct
|
||||
case group
|
||||
case direct = "@"
|
||||
case group = "#"
|
||||
}
|
||||
|
||||
enum ChatInfo: Identifiable, Codable {
|
||||
case direct(contact: Contact)
|
||||
case group(groupInfo: GroupInfo)
|
||||
|
||||
var displayName: String {
|
||||
var localDisplayName: String {
|
||||
get {
|
||||
switch self {
|
||||
case let .direct(contact): return "@\(contact.localDisplayName)"
|
||||
@ -216,6 +216,15 @@ enum MsgContent {
|
||||
}
|
||||
}
|
||||
|
||||
var cmdString: String {
|
||||
get {
|
||||
switch self {
|
||||
case let .text(text): return "text \(text)"
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case type
|
||||
case text
|
||||
@ -240,16 +249,3 @@ extension MsgContent: Decodable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//func parseMsgContent(_ mc: SomeMsgContent) -> MsgContent {
|
||||
// if let type = mc["type"] as? String {
|
||||
// let text_ = mc["text"] as? String
|
||||
// switch type {
|
||||
// case "text":
|
||||
// if let text = text_ { return .text(text) }
|
||||
// case let t:
|
||||
// return .unknown(type: t, text: text_ ?? "unknown item", json: prettyJSON(mc) ?? "error")
|
||||
// }
|
||||
// }
|
||||
// return .invalid(json: prettyJSON(mc) ?? "error")
|
||||
//}
|
||||
|
@ -16,20 +16,27 @@ private let jsonEncoder = getJSONEncoder()
|
||||
|
||||
enum ChatCommand {
|
||||
case apiGetChats
|
||||
case apiGetChatItems(type: ChatType, id: Int64)
|
||||
case apiGetChat(type: ChatType, id: Int64)
|
||||
case apiSendMessage(type: ChatType, id: Int64, msg: MsgContent)
|
||||
case addContact
|
||||
case connect(connReq: String)
|
||||
case string(String)
|
||||
case help
|
||||
|
||||
var cmdString: String {
|
||||
get {
|
||||
switch self {
|
||||
case .apiGetChats:
|
||||
return "/api/v1/chats"
|
||||
case let .apiGetChatItems(type, id):
|
||||
return "/api/v1/chat/\(type)/\(id)"
|
||||
return "/get chats"
|
||||
case let .apiGetChat(type, id):
|
||||
return "/get chat \(type.rawValue)\(id)"
|
||||
case let .apiSendMessage(type, id, mc):
|
||||
return "/send msg \(type.rawValue)\(id) \(mc.cmdString)"
|
||||
case .addContact:
|
||||
return "/c"
|
||||
case let .connect(connReq):
|
||||
return "/c \(connReq)"
|
||||
case let .string(str):
|
||||
return str
|
||||
case .help: return "/help"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,7 +49,10 @@ struct APIResponse: Decodable {
|
||||
enum ChatResponse: Decodable, Error {
|
||||
case response(type: String, json: String)
|
||||
case apiChats(chats: [ChatPreview])
|
||||
case apiDirectChat(chat: Chat) // direct/<id> or group/<id>, same as ChatPreview.id
|
||||
case apiChat(chat: Chat)
|
||||
case invitation(connReqInvitation: String)
|
||||
case sentConfirmation
|
||||
case sentInvitation
|
||||
// case newSentInvitation
|
||||
case contactConnected(contact: Contact)
|
||||
case newChatItem(chatItem: AChatItem)
|
||||
@ -52,7 +62,10 @@ enum ChatResponse: Decodable, Error {
|
||||
switch self {
|
||||
case let .response(type, _): return "* \(type)"
|
||||
case .apiChats: return "apiChats"
|
||||
case .apiDirectChat: return "apiDirectChat"
|
||||
case .apiChat: return "apiChat"
|
||||
case .invitation: return "invitation"
|
||||
case .sentConfirmation: return "sentConfirmation"
|
||||
case .sentInvitation: return "sentInvitation"
|
||||
case .contactConnected: return "contactConnected"
|
||||
case .newChatItem: return "newChatItem"
|
||||
}
|
||||
@ -64,7 +77,10 @@ enum ChatResponse: Decodable, Error {
|
||||
switch self {
|
||||
case let .response(_, json): return json
|
||||
case let .apiChats(chats): return String(describing: chats)
|
||||
case let .apiDirectChat(chat): return String(describing: chat)
|
||||
case let .apiChat(chat): return String(describing: chat)
|
||||
case let .invitation(connReqInvitation): return connReqInvitation
|
||||
case .sentConfirmation: return "sentConfirmation: no details"
|
||||
case .sentInvitation: return "sentInvitation: no details"
|
||||
case let .contactConnected(contact): return String(describing: contact)
|
||||
case let .newChatItem(chatItem): return String(describing: chatItem)
|
||||
}
|
||||
@ -127,6 +143,7 @@ func chatCreateUser(_ p: Profile) -> User? {
|
||||
|
||||
func chatSendCmd(_ cmd: ChatCommand) throws -> ChatResponse {
|
||||
var c = cmd.cmdString.cString(using: .utf8)!
|
||||
print("command", cmd.cmdString)
|
||||
// TODO some mechanism to update model without passing it - maybe Publisher / Subscriber?
|
||||
// DispatchQueue.main.async {
|
||||
// termId += 1
|
||||
@ -141,16 +158,33 @@ func chatRecvMsg() throws -> ChatResponse {
|
||||
|
||||
func apiGetChats() throws -> [ChatPreview] {
|
||||
let r = try chatSendCmd(.apiGetChats)
|
||||
switch r {
|
||||
case let .apiChats(chats): return chats
|
||||
default: throw r
|
||||
}
|
||||
if case let .apiChats(chats) = r { return chats }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiGetChatItems(type: ChatType, id: Int64) throws -> Chat {
|
||||
let r = try chatSendCmd(.apiGetChatItems(type: type, id: id))
|
||||
func apiGetChat(type: ChatType, id: Int64) throws -> Chat {
|
||||
let r = try chatSendCmd(.apiGetChat(type: type, id: id))
|
||||
if case let .apiChat(chat) = r { return chat }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSendMessage(type: ChatType, id: Int64, msg: MsgContent) throws -> ChatItem {
|
||||
let r = try chatSendCmd(.apiSendMessage(type: type, id: id, msg: msg))
|
||||
if case let .newChatItem(aChatItem) = r { return aChatItem.chatItem }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiAddContact() throws -> String {
|
||||
let r = try chatSendCmd(.addContact)
|
||||
if case let .invitation(connReqInvitation) = r { return connReqInvitation }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiConnect(connReq: String) throws {
|
||||
let r = try chatSendCmd(.connect(connReq: connReq))
|
||||
switch r {
|
||||
case let .apiDirectChat(chat): return chat
|
||||
case .sentConfirmation: return
|
||||
case .sentInvitation: return
|
||||
default: throw r
|
||||
}
|
||||
}
|
||||
|
@ -79,3 +79,4 @@ struct Test: Decodable {
|
||||
|
||||
//jsonDecoder.decode(Test.self, from: "{\"name\":\"hello\",\"id\":1}".data(using: .utf8)!)
|
||||
|
||||
"\(ChatType.direct)"
|
||||
|
@ -3,7 +3,7 @@
|
||||
version = "3.0">
|
||||
<TimelineItems>
|
||||
<LoggerValueHistoryTimelineItem
|
||||
documentLocation = "file:///Users/evgeny/opensource/simplex-chat/simplex-chat/apps/ios/Shared/MyPlayground.playground#CharacterRangeLen=88&CharacterRangeLoc=3634&EndingColumnNumber=0&EndingLineNumber=80&StartingColumnNumber=3&StartingLineNumber=79&Timestamp=665153525.657431"
|
||||
documentLocation = "file:///Users/evgeny/opensource/simplex-chat/simplex-chat/apps/ios/Shared/MyPlayground.playground#CharacterRangeLen=88&CharacterRangeLoc=3634&EndingColumnNumber=0&EndingLineNumber=80&StartingColumnNumber=3&StartingLineNumber=79&Timestamp=665235849.610096"
|
||||
selectedRepresentationIndex = "0"
|
||||
shouldTrackSuperviewWidth = "NO">
|
||||
</LoggerValueHistoryTimelineItem>
|
||||
|
@ -46,8 +46,9 @@ struct ChatListView: View {
|
||||
ChatView(chatInfo: chatPreview.chatInfo)
|
||||
.onAppear {
|
||||
do {
|
||||
let chat = try apiGetChatItems(type: .direct, id: chatPreview.chatInfo.apiId)
|
||||
chatModel.chats[chat.chatInfo.id] = chat
|
||||
let ci = chatPreview.chatInfo
|
||||
let chat = try apiGetChat(type: ci.chatType, id: ci.apiId)
|
||||
chatModel.chats[ci.id] = chat
|
||||
} catch {
|
||||
print("apiGetChatItems", error)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ struct ChatPreviewView: View {
|
||||
var chatPreview: ChatPreview
|
||||
|
||||
var body: some View {
|
||||
Text(chatPreview.chatInfo.displayName)
|
||||
Text(chatPreview.chatInfo.localDisplayName)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,14 @@ struct ChatView: View {
|
||||
}
|
||||
|
||||
func sendMessage(_ msg: String) {
|
||||
|
||||
do {
|
||||
let chatItem = try apiSendMessage(type: chatInfo.chatType, id: chatInfo.apiId, msg: .text(msg))
|
||||
let chat = chatModel.chats[chatInfo.id] ?? Chat(chatInfo: chatInfo, chatItems: [])
|
||||
chatModel.chats[chatInfo.id] = chat
|
||||
chat.chatItems.append(chatItem)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
43
apps/ios/Shared/Views/Helpers/AddContactView.swift
Normal file
43
apps/ios/Shared/Views/Helpers/AddContactView.swift
Normal file
@ -0,0 +1,43 @@
|
||||
//
|
||||
// AddContactView.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 29/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreImage.CIFilterBuiltins
|
||||
|
||||
struct AddContactView: View {
|
||||
var connReqInvitation: String
|
||||
@State private var shareInvitation = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Add contact")
|
||||
.font(.title)
|
||||
.padding(.bottom)
|
||||
Text("Show QR code to your contact\nto scan from the app")
|
||||
.font(.title2)
|
||||
.multilineTextAlignment(.center)
|
||||
QRCode(uri: connReqInvitation)
|
||||
.padding()
|
||||
Text("If you can't show QR code, you can share the invitation link via any channel")
|
||||
.font(.subheadline)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
Button { shareInvitation = true } label: {
|
||||
Label("Share", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
.padding()
|
||||
.shareSheet(isPresented: $shareInvitation, items: [connReqInvitation])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddContactView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AddContactView(connReqInvitation: "https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D")
|
||||
}
|
||||
}
|
@ -10,8 +10,13 @@ import SwiftUI
|
||||
|
||||
struct ChatHeaderView: View {
|
||||
@State private var showAddChat = false
|
||||
@State private var inviteContact = false
|
||||
@State private var scanQRCode = false
|
||||
@State private var addContact = false
|
||||
@State private var addContactAlert = false
|
||||
@State private var addContactError: Error?
|
||||
@State private var connReqInvitation: String = ""
|
||||
@State private var connectContact = false
|
||||
@State private var connectAlert = false
|
||||
@State private var connectError: Error?
|
||||
@State private var createGroup = false
|
||||
|
||||
var body: some View {
|
||||
@ -24,15 +29,54 @@ struct ChatHeaderView: View {
|
||||
Image(systemName: "square.and.pencil")
|
||||
}
|
||||
.confirmationDialog("Start new chat", isPresented: $showAddChat, titleVisibility: .visible) {
|
||||
Button("Invite contact") { inviteContact = true }
|
||||
Button("Scan QR code") { scanQRCode = true }
|
||||
Button("Add contact") { addContactAction() }
|
||||
Button("Scan QR code") { connectContact = true }
|
||||
Button("Create group") { createGroup = true }
|
||||
}
|
||||
.sheet(isPresented: $inviteContact, content: { InviteContactView() })
|
||||
.sheet(isPresented: $scanQRCode, content: { ScanQRCodeView() })
|
||||
.sheet(isPresented: $addContact, content: {
|
||||
AddContactView(connReqInvitation: connReqInvitation)
|
||||
})
|
||||
.alert(isPresented: $addContactAlert) {
|
||||
connectionError(addContactError)
|
||||
}
|
||||
.sheet(isPresented: $connectContact, content: {
|
||||
connectContactSheet()
|
||||
})
|
||||
.alert(isPresented: $connectAlert) {
|
||||
connectionError(connectError)
|
||||
}
|
||||
.sheet(isPresented: $createGroup, content: { CreateGroupView() })
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.top)
|
||||
}
|
||||
|
||||
func addContactAction() {
|
||||
do {
|
||||
connReqInvitation = try apiAddContact()
|
||||
addContact = true
|
||||
} catch {
|
||||
addContactAlert = true
|
||||
addContactError = error
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
func connectContactSheet() -> some View {
|
||||
ConnectContactView(completed: { err in
|
||||
connectContact = false
|
||||
if err != nil {
|
||||
connectAlert = true
|
||||
connectError = err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func connectionError(_ error: Error?) -> Alert {
|
||||
Alert(
|
||||
title: Text("Connection error"),
|
||||
message: Text(error?.localizedDescription ?? "")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
54
apps/ios/Shared/Views/Helpers/ConnectContactView.swift
Normal file
54
apps/ios/Shared/Views/Helpers/ConnectContactView.swift
Normal file
@ -0,0 +1,54 @@
|
||||
//
|
||||
// ConnectContactView.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 29/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CodeScanner
|
||||
|
||||
struct ConnectContactView: View {
|
||||
var completed: ((Error?) -> Void)
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Scan QR code")
|
||||
.font(.title)
|
||||
.padding(.bottom)
|
||||
Text("Your chat profile will be sent to your contact.")
|
||||
.font(.title2)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding()
|
||||
ZStack {
|
||||
CodeScannerView(codeTypes: [.qr], completion: processQRCode)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
.border(.gray)
|
||||
}
|
||||
.padding(13.0)
|
||||
}
|
||||
}
|
||||
|
||||
func processQRCode(_ resp: Result<ScanResult, ScanError>) {
|
||||
switch resp {
|
||||
case let .success(r):
|
||||
do {
|
||||
try apiConnect(connReq: r.string)
|
||||
completed(nil)
|
||||
} catch {
|
||||
print(error)
|
||||
completed(error)
|
||||
}
|
||||
case let .failure(e):
|
||||
print(e)
|
||||
completed(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ConnectContactView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
return ConnectContactView(completed: {_ in })
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
//
|
||||
// InviteContactView.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 29/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct InviteContactView: View {
|
||||
var body: some View {
|
||||
Text("InviteContactView")
|
||||
}
|
||||
}
|
||||
|
||||
struct InviteContactView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InviteContactView()
|
||||
}
|
||||
}
|
50
apps/ios/Shared/Views/Helpers/QRCode.swift
Normal file
50
apps/ios/Shared/Views/Helpers/QRCode.swift
Normal file
@ -0,0 +1,50 @@
|
||||
//
|
||||
// QRCode.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 30/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreImage.CIFilterBuiltins
|
||||
|
||||
struct QRCode: View {
|
||||
let uri: String
|
||||
@State private var image: UIImage?
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if let image = image {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.interpolation(.none)
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
generateImage()
|
||||
}
|
||||
}
|
||||
|
||||
private func generateImage() {
|
||||
guard image == nil else { return }
|
||||
|
||||
let context = CIContext()
|
||||
let filter = CIFilter.qrCodeGenerator()
|
||||
filter.message = Data(uri.utf8)
|
||||
|
||||
guard
|
||||
let outputImage = filter.outputImage,
|
||||
let cgImage = context.createCGImage(outputImage, from: outputImage.extent)
|
||||
else { return }
|
||||
|
||||
self.image = UIImage(cgImage: cgImage)
|
||||
}
|
||||
}
|
||||
|
||||
struct QRCode_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
QRCode(uri: "https://simplex.chat/invitation#/?v=1&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FFe5ICmvrm4wkrr6X1LTMii-lhBqLeB76%23MCowBQYDK2VuAyEAdhZZsHpuaAk3Hh1q0uNb_6hGTpuwBIrsp2z9U2T0oC0%3D&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAcz6jJk71InuxA0bOX7OUhddfB8Ov7xwQIlIDeXBRZaOntUU4brU5Y3rBzroZBdQJi0FKdtt_D7I%3D%2CMEIwBQYDK2VvAzkA-hDvk1duBi1hlOr08VWSI-Ou4JNNSQjseY69QyKm7Kgg1zZjbpGfyBqSZ2eqys6xtoV4ZtoQUXQ%3D")
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
//
|
||||
// ScanQRCodeView.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 29/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ScanQRCodeView: View {
|
||||
var body: some View {
|
||||
Text("ScanQRCodeView")
|
||||
}
|
||||
}
|
||||
|
||||
struct ScanQRCodeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ScanQRCodeView()
|
||||
}
|
||||
}
|
40
apps/ios/Shared/Views/Helpers/ShareSheet.swift
Normal file
40
apps/ios/Shared/Views/Helpers/ShareSheet.swift
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// ShareSheet.swift
|
||||
// SimpleX
|
||||
//
|
||||
// Created by Evgeny Poberezkin on 30/01/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension UIApplication {
|
||||
static let keyWindow = keyWindowScene?.windows.filter(\.isKeyWindow).first
|
||||
static let keyWindowScene = shared.connectedScenes.first { $0.activationState == .foregroundActive } as? UIWindowScene
|
||||
}
|
||||
|
||||
extension View {
|
||||
func shareSheet(isPresented: Binding<Bool>, items: [Any]) -> some View {
|
||||
guard isPresented.wrappedValue else { return self }
|
||||
let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
|
||||
let presentedViewController = UIApplication.keyWindow?.rootViewController?.presentedViewController ?? UIApplication.keyWindow?.rootViewController
|
||||
activityViewController.completionWithItemsHandler = { _, _, _, _ in isPresented.wrappedValue = false }
|
||||
presentedViewController?.present(activityViewController, animated: true)
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
struct ShareSheetTest: View {
|
||||
@State private var isPresentingShareSheet = false
|
||||
|
||||
var body: some View {
|
||||
Button("Show Share Sheet") { isPresentingShareSheet = true }
|
||||
.shareSheet(isPresented: $isPresentingShareSheet, items: ["Share me!"])
|
||||
}
|
||||
}
|
||||
|
||||
struct ShareSheetTest_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ShareSheetTest()
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@
|
||||
5C764E85279C748C000C6508 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C764E7C279C71DB000C6508 /* libz.tbd */; };
|
||||
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E88279CBCB3000C6508 /* ChatModel.swift */; };
|
||||
5C764E8A279CBCB3000C6508 /* ChatModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C764E88279CBCB3000C6508 /* ChatModel.swift */; };
|
||||
5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */ = {isa = PBXBuildFile; productRef = 5C8F01CC27A6F0D8007D2C8D /* CodeScanner */; };
|
||||
5C9FD96B27A56D4D0075386C /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96A27A56D4D0075386C /* JSON.swift */; };
|
||||
5C9FD96C27A56D4D0075386C /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96A27A56D4D0075386C /* JSON.swift */; };
|
||||
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */; };
|
||||
@ -53,12 +54,16 @@
|
||||
5CA05A4D27974EB60002BEB4 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */; };
|
||||
5CA05A4F279752D00002BEB4 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA05A4E279752D00002BEB4 /* MessageView.swift */; };
|
||||
5CA05A50279752D00002BEB4 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA05A4E279752D00002BEB4 /* MessageView.swift */; };
|
||||
5CC1C99227A6C7F5000D9FF6 /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */; };
|
||||
5CC1C99327A6C7F5000D9FF6 /* QRCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */; };
|
||||
5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */; };
|
||||
5CC1C99627A6CF7F000D9FF6 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */; };
|
||||
5CCD403127A5F1C600368C90 /* ChatHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */; };
|
||||
5CCD403227A5F1C600368C90 /* ChatHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */; };
|
||||
5CCD403427A5F6DF00368C90 /* InviteContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* InviteContactView.swift */; };
|
||||
5CCD403527A5F6DF00368C90 /* InviteContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* InviteContactView.swift */; };
|
||||
5CCD403727A5F9A200368C90 /* ScanQRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanQRCodeView.swift */; };
|
||||
5CCD403827A5F9A200368C90 /* ScanQRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanQRCodeView.swift */; };
|
||||
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
|
||||
5CCD403527A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
|
||||
5CCD403727A5F9A200368C90 /* ConnectContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ConnectContactView.swift */; };
|
||||
5CCD403827A5F9A200368C90 /* ConnectContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ConnectContactView.swift */; };
|
||||
5CCD403A27A5F9BE00368C90 /* CreateGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */; };
|
||||
5CCD403B27A5F9BE00368C90 /* CreateGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
@ -113,9 +118,11 @@
|
||||
5CA059E9279559F40002BEB4 /* Tests_macOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOSLaunchTests.swift; sourceTree = "<group>"; };
|
||||
5CA05A4B27974EB60002BEB4 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
|
||||
5CA05A4E279752D00002BEB4 /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = "<group>"; };
|
||||
5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCode.swift; sourceTree = "<group>"; };
|
||||
5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = "<group>"; };
|
||||
5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHeaderView.swift; sourceTree = "<group>"; };
|
||||
5CCD403327A5F6DF00368C90 /* InviteContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteContactView.swift; sourceTree = "<group>"; };
|
||||
5CCD403627A5F9A200368C90 /* ScanQRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanQRCodeView.swift; sourceTree = "<group>"; };
|
||||
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
|
||||
5CCD403627A5F9A200368C90 /* ConnectContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectContactView.swift; sourceTree = "<group>"; };
|
||||
5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateGroupView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@ -124,6 +131,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C8F01CD27A6F0D8007D2C8D /* CodeScanner in Frameworks */,
|
||||
5C764E83279C748B000C6508 /* libz.tbd in Frameworks */,
|
||||
5C764E82279C748B000C6508 /* libiconv.tbd in Frameworks */,
|
||||
5C1AEB86279F4A6400247F08 /* libffi.a in Frameworks */,
|
||||
@ -184,9 +192,11 @@
|
||||
5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */,
|
||||
5CA05A4E279752D00002BEB4 /* MessageView.swift */,
|
||||
5CCD403027A5F1C600368C90 /* ChatHeaderView.swift */,
|
||||
5CCD403327A5F6DF00368C90 /* AddContactView.swift */,
|
||||
5CCD403627A5F9A200368C90 /* ConnectContactView.swift */,
|
||||
5CCD403927A5F9BE00368C90 /* CreateGroupView.swift */,
|
||||
5CCD403627A5F9A200368C90 /* ScanQRCodeView.swift */,
|
||||
5CCD403327A5F6DF00368C90 /* InviteContactView.swift */,
|
||||
5CC1C99127A6C7F5000D9FF6 /* QRCode.swift */,
|
||||
5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
@ -303,6 +313,9 @@
|
||||
dependencies = (
|
||||
);
|
||||
name = "SimpleX (iOS)";
|
||||
packageProductDependencies = (
|
||||
5C8F01CC27A6F0D8007D2C8D /* CodeScanner */,
|
||||
);
|
||||
productName = "SimpleX (iOS)";
|
||||
productReference = 5CA059CA279559F40002BEB4 /* SimpleX.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
@ -398,6 +411,9 @@
|
||||
Base,
|
||||
);
|
||||
mainGroup = 5CA059BD279559F40002BEB4;
|
||||
packageReferences = (
|
||||
5C8F01CB27A6F0D8007D2C8D /* XCRemoteSwiftPackageReference "CodeScanner" */,
|
||||
);
|
||||
productRefGroup = 5CA059CB279559F40002BEB4 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
@ -453,17 +469,19 @@
|
||||
5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */,
|
||||
5C9FD96B27A56D4D0075386C /* JSON.swift in Sources */,
|
||||
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */,
|
||||
5CC1C99227A6C7F5000D9FF6 /* QRCode.swift in Sources */,
|
||||
5CCD403127A5F1C600368C90 /* ChatHeaderView.swift in Sources */,
|
||||
5CA05A4F279752D00002BEB4 /* MessageView.swift in Sources */,
|
||||
5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */,
|
||||
5CCD403427A5F6DF00368C90 /* InviteContactView.swift in Sources */,
|
||||
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */,
|
||||
5CA05A4C27974EB60002BEB4 /* WelcomeView.swift in Sources */,
|
||||
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */,
|
||||
5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */,
|
||||
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */,
|
||||
5CCD403727A5F9A200368C90 /* ScanQRCodeView.swift in Sources */,
|
||||
5CCD403727A5F9A200368C90 /* ConnectContactView.swift in Sources */,
|
||||
5CCD403A27A5F9BE00368C90 /* CreateGroupView.swift in Sources */,
|
||||
5C764E89279CBCB3000C6508 /* ChatModel.swift in Sources */,
|
||||
5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */,
|
||||
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -477,17 +495,19 @@
|
||||
5C2E261327A30FEA00F70299 /* TerminalView.swift in Sources */,
|
||||
5C9FD96C27A56D4D0075386C /* JSON.swift in Sources */,
|
||||
5C9FD96F27A5D6ED0075386C /* SendMessageView.swift in Sources */,
|
||||
5CC1C99327A6C7F5000D9FF6 /* QRCode.swift in Sources */,
|
||||
5CCD403227A5F1C600368C90 /* ChatHeaderView.swift in Sources */,
|
||||
5CA05A50279752D00002BEB4 /* MessageView.swift in Sources */,
|
||||
5CA059EE279559F40002BEB4 /* ContentView.swift in Sources */,
|
||||
5CCD403527A5F6DF00368C90 /* InviteContactView.swift in Sources */,
|
||||
5CCD403527A5F6DF00368C90 /* AddContactView.swift in Sources */,
|
||||
5CA05A4D27974EB60002BEB4 /* WelcomeView.swift in Sources */,
|
||||
5C2E261027A30FDC00F70299 /* ChatView.swift in Sources */,
|
||||
5C2E260C27A30CFA00F70299 /* ChatListView.swift in Sources */,
|
||||
5CA059EC279559F40002BEB4 /* SimpleXApp.swift in Sources */,
|
||||
5CCD403827A5F9A200368C90 /* ScanQRCodeView.swift in Sources */,
|
||||
5CCD403827A5F9A200368C90 /* ConnectContactView.swift in Sources */,
|
||||
5CCD403B27A5F9BE00368C90 /* CreateGroupView.swift in Sources */,
|
||||
5C764E8A279CBCB3000C6508 /* ChatModel.swift in Sources */,
|
||||
5CC1C99627A6CF7F000D9FF6 /* ShareSheet.swift in Sources */,
|
||||
5C2E260827A2941F00F70299 /* SimpleXAPI.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -649,6 +669,7 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "$(PRODUCT_NAME) needs camera access to scan QR codes to connect to other app users";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
@ -689,6 +710,7 @@
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "$(PRODUCT_NAME) needs camera access to scan QR codes to connect to other app users";
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
@ -921,6 +943,25 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
5C8F01CB27A6F0D8007D2C8D /* XCRemoteSwiftPackageReference "CodeScanner" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/twostraws/CodeScanner";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 2.0.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
5C8F01CC27A6F0D8007D2C8D /* CodeScanner */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 5C8F01CB27A6F0D8007D2C8D /* XCRemoteSwiftPackageReference "CodeScanner" */;
|
||||
productName = CodeScanner;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 5CA059BE279559F40002BEB4 /* Project object */;
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "CodeScanner",
|
||||
"repositoryURL": "https://github.com/twostraws/CodeScanner",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "c27a66149b7483fe42e2ec6aad61d5c3fffe522d",
|
||||
"version": "2.1.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
Loading…
Reference in New Issue
Block a user