Skip to content

Commit 18e297b

Browse files
author
Abdulhakim Ajetunmobi
authored
Add iOS Chat App (#24)
* Add iOS Chat App
1 parent 79c118d commit 18e297b

File tree

11 files changed

+771
-0
lines changed

11 files changed

+771
-0
lines changed

messaging-swift/Chat.xcodeproj/project.pbxproj

Lines changed: 437 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"colors" : [
3+
{
4+
"idiom" : "universal"
5+
}
6+
],
7+
"info" : {
8+
"author" : "xcode",
9+
"version" : 1
10+
}
11+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"images" : [
3+
{
4+
"idiom" : "universal",
5+
"platform" : "ios",
6+
"size" : "1024x1024"
7+
},
8+
{
9+
"idiom" : "mac",
10+
"scale" : "1x",
11+
"size" : "16x16"
12+
},
13+
{
14+
"idiom" : "mac",
15+
"scale" : "2x",
16+
"size" : "16x16"
17+
},
18+
{
19+
"idiom" : "mac",
20+
"scale" : "1x",
21+
"size" : "32x32"
22+
},
23+
{
24+
"idiom" : "mac",
25+
"scale" : "2x",
26+
"size" : "32x32"
27+
},
28+
{
29+
"idiom" : "mac",
30+
"scale" : "1x",
31+
"size" : "128x128"
32+
},
33+
{
34+
"idiom" : "mac",
35+
"scale" : "2x",
36+
"size" : "128x128"
37+
},
38+
{
39+
"idiom" : "mac",
40+
"scale" : "1x",
41+
"size" : "256x256"
42+
},
43+
{
44+
"idiom" : "mac",
45+
"scale" : "2x",
46+
"size" : "256x256"
47+
},
48+
{
49+
"idiom" : "mac",
50+
"scale" : "1x",
51+
"size" : "512x512"
52+
},
53+
{
54+
"idiom" : "mac",
55+
"scale" : "2x",
56+
"size" : "512x512"
57+
}
58+
],
59+
"info" : {
60+
"author" : "xcode",
61+
"version" : 1
62+
}
63+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>com.apple.security.app-sandbox</key>
6+
<true/>
7+
<key>com.apple.security.files.user-selected.read-only</key>
8+
<true/>
9+
</dict>
10+
</plist>

messaging-swift/Chat/ChatApp.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//
2+
// ChatApp.swift
3+
// Chat
4+
//
5+
// Created by Abdulhakim Ajetunmobi on 31/08/2023.
6+
//
7+
8+
import SwiftUI
9+
10+
@main
11+
struct ChatApp: App {
12+
var body: some Scene {
13+
WindowGroup {
14+
ContentView()
15+
}
16+
}
17+
}

messaging-swift/Chat/ChatView.swift

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//
2+
// ChatView.swift
3+
// Chat
4+
//
5+
// Created by Abdulhakim Ajetunmobi on 31/08/2023.
6+
//
7+
8+
import SwiftUI
9+
import VonageClientSDKChat
10+
11+
struct ChatView: View {
12+
@StateObject var chatViewModel: ChatViewModel
13+
@State private var message: String = ""
14+
15+
var body: some View {
16+
VStack {
17+
if chatViewModel.events.isEmpty {
18+
ProgressView()
19+
} else {
20+
VStack {
21+
List {
22+
ForEach(chatViewModel.events, id: \.id) { event in
23+
switch event.kind {
24+
case .memberJoined, .memberLeft:
25+
let displayText = chatViewModel.generateDisplayText(event)
26+
Text(displayText.body)
27+
.frame(maxWidth: .infinity, alignment: .center)
28+
case.messageText:
29+
let displayText = chatViewModel.generateDisplayText(event)
30+
Text(displayText.body)
31+
.frame(maxWidth: .infinity, alignment: displayText.isUser ? .trailing : .leading)
32+
default:
33+
EmptyView()
34+
}
35+
}.listRowSeparator(.hidden)
36+
}.listStyle(.plain)
37+
38+
Spacer()
39+
40+
HStack {
41+
TextField("Message", text: $message)
42+
Button("Send") {
43+
Task {
44+
await chatViewModel.sendMessage(message)
45+
self.message = ""
46+
}
47+
}.buttonStyle(.bordered)
48+
}.padding(8)
49+
}
50+
}
51+
}.onAppear {
52+
Task {
53+
await chatViewModel.getMemberIDIfNeeded()
54+
await chatViewModel.getConversationEvents()
55+
}
56+
}
57+
}
58+
}
59+
60+
@MainActor
61+
final class ChatViewModel: NSObject, ObservableObject {
62+
private let conversationID = "CON-ID"
63+
64+
private var client: VGChatClient
65+
private var memberID: String?
66+
67+
@Published var events: [VGPersistentConversationEvent] = []
68+
69+
init(client: VGChatClient) {
70+
self.client = client
71+
super.init()
72+
client.delegate = self
73+
}
74+
75+
func getMemberIDIfNeeded() async {
76+
guard memberID == nil else { return }
77+
await getMemberID()
78+
}
79+
80+
private func getMemberID() async {
81+
let member = try? await client.getConversationMember(conversationID, memberId: "me")
82+
memberID = member?.id
83+
84+
if memberID == nil {
85+
memberID = try? await client.joinConversation(conversationID)
86+
}
87+
}
88+
89+
func getConversationEvents() async {
90+
let params = VGGetConversationEventsParameters(order: .asc, pageSize: 100)
91+
let eventsPage = try? await client.getConversationEvents(conversationID, parameters: params)
92+
self.events = eventsPage?.events ?? []
93+
}
94+
95+
func sendMessage(_ message: String) async {
96+
_ = try? await client.sendMessageTextEvent(conversationID, text: message)
97+
}
98+
99+
func generateDisplayText(_ event: VGPersistentConversationEvent) -> (body: String, isUser: Bool) {
100+
var from = "System"
101+
102+
switch event.kind {
103+
case .memberJoined:
104+
let memberJoinedEvent = event as! VGMemberJoinedEvent
105+
from = memberJoinedEvent.body.user.name
106+
return ("\(from) joined", false)
107+
case .memberLeft:
108+
let memberLeftEvent = event as! VGMemberLeftEvent
109+
from = memberLeftEvent.body.user.name
110+
return ("\(from) left", false)
111+
case .messageText:
112+
let messageTextEvent = event as! VGMessageTextEvent
113+
var isUser = false
114+
115+
if let userInfo = messageTextEvent.from as? VGEmbeddedInfo {
116+
isUser = userInfo.memberId == memberID
117+
from = isUser ? "" : "\(userInfo.user.name): "
118+
}
119+
120+
return ("\(from) \(messageTextEvent.body.text)", isUser)
121+
default:
122+
return ("", false)
123+
}
124+
}
125+
}
126+
127+
extension ChatViewModel: VGChatClientDelegate {
128+
nonisolated func chatClient(_ client: VGChatClient, didReceiveConversationEvent event: VGConversationEvent) {
129+
Task { @MainActor in
130+
self.events.append(event as! VGPersistentConversationEvent)
131+
}
132+
}
133+
134+
nonisolated func client(_ client: VGBaseClient, didReceiveSessionErrorWith reason: VGSessionErrorReason) {}
135+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// ContentView.swift
3+
// Chat
4+
//
5+
// Created by Abdulhakim Ajetunmobi on 31/08/2023.
6+
//
7+
8+
import SwiftUI
9+
import VonageClientSDKChat
10+
11+
struct ContentView: View {
12+
@StateObject private var loginModel = LoginViewModel()
13+
14+
var body: some View {
15+
NavigationStack {
16+
VStack {
17+
Button("Login as Alice") {
18+
Task {
19+
await loginModel.login("Alice")
20+
}
21+
}.buttonStyle(.bordered)
22+
Button("Login as Bob") {
23+
Task {
24+
await loginModel.login("Bob")
25+
}
26+
}.buttonStyle(.bordered)
27+
}
28+
.padding()
29+
.navigationDestination(isPresented: $loginModel.isLoggedIn) {
30+
let chatViewModel = ChatViewModel(client: loginModel.client)
31+
ChatView(chatViewModel: chatViewModel)
32+
}
33+
}.alert(isPresented: $loginModel.isError) {
34+
Alert(title: Text(loginModel.error))
35+
}
36+
}
37+
}
38+
39+
@MainActor
40+
final class LoginViewModel: ObservableObject {
41+
@Published var error = ""
42+
@Published var isError = false
43+
@Published var isLoggedIn = false
44+
45+
private let aliceJwt = "ALICE_JWT"
46+
private let bobJwt = "BOB_JWT"
47+
48+
let client = VGChatClient()
49+
50+
func login(_ username: String) async {
51+
do {
52+
let jwt = username == "Alice" ? aliceJwt : bobJwt
53+
try await client.createSession(jwt)
54+
isLoggedIn = true
55+
} catch {
56+
self.error = error.localizedDescription
57+
self.isError = true
58+
}
59+
}
60+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"info" : {
3+
"author" : "xcode",
4+
"version" : 1
5+
}
6+
}

messaging-swift/Podfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Uncomment the next line to define a global platform for your project
2+
platform :ios, '16.0'
3+
4+
target 'Chat' do
5+
# Comment the next line if you don't want to use dynamic frameworks
6+
use_frameworks!
7+
8+
# Pods for Chat
9+
pod 'VonageClientSDKChat', '1.3.0'
10+
end

0 commit comments

Comments
 (0)