Skip to content

Commit

Permalink
Spec complete for sending and receiving messages
Browse files Browse the repository at this point in the history
  • Loading branch information
umair-ably committed Oct 3, 2024
1 parent 03a436f commit 924e103
Show file tree
Hide file tree
Showing 30 changed files with 978 additions and 83 deletions.
6 changes: 3 additions & 3 deletions AblyChat.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"originHash" : "fcc346d6fe86e610ac200cdbbf91c56204df67286546d5079bd9c610ee65953b",
"originHash" : "d5cd1b39ed966b59fccd3f0d3d46bcf897088e975a6b8a3622235a7adfacaba6",
"pins" : [
{
"identity" : "ably-cocoa",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ably/ably-cocoa",
"state" : {
"revision" : "7f639c609e50053abd4590f34333f9472645558a",
"version" : "1.2.33"
"branch" : "main",
"revision" : "63e6f001d06cb7defb6be92f87a831f920eaf8c1"
}
},
{
Expand Down
2 changes: 2 additions & 0 deletions Example/AblyChatExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@
Base,
);
mainGroup = 21F09A932C60CAF00025AF73;
packageReferences = (
);
productRefGroup = 21F09A9D2C60CAF00025AF73 /* Products */;
projectDirPath = "";
projectRoot = "";
Expand Down
2 changes: 1 addition & 1 deletion Example/AblyChatExample/AblyChatExampleApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SwiftUI
struct AblyChatExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
MessageDemoView()
}
}
}
24 changes: 0 additions & 24 deletions Example/AblyChatExample/ContentView.swift

This file was deleted.

150 changes: 150 additions & 0 deletions Example/AblyChatExample/MessageDemoView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import Ably
import AblyChat
import SwiftUI

// TODO: This entire file can be removed and replaced with the actual example app we're going with. Leaving it here as a reference to something that is currently working.

let clientId = "" // Set any string as a ClientID here e.g. "John"
let apiKey = "" // Set your Ably API Key here

struct MessageCell: View {
var contentMessage: String
var isCurrentUser: Bool

var body: some View {
Text(contentMessage)
.padding(12)
.foregroundColor(isCurrentUser ? Color.white : Color.black)
.background(isCurrentUser ? Color.blue : Color.gray)
.cornerRadius(12)
}
}

struct MessageView: View {
var currentMessage: Message

var body: some View {
HStack(alignment: .bottom) {
if let messageClientId = currentMessage.clientID {
if messageClientId == clientId {
Spacer()
} else {}
MessageCell(
contentMessage: currentMessage.text,
isCurrentUser: messageClientId == clientId
)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 16)
.padding(.vertical, 4)
}
}

struct MessageDemoView: View {
@State private var messages: [Message] = [] // Store the chat messages
@State private var newMessage: String = "" // Store the message user is typing
@State private var room: Room? // Keep track of the chat room

var clientOptions: ARTClientOptions {
let options = ARTClientOptions()
options.clientId = clientId
options.key = apiKey
return options
}

var body: some View {
VStack {
ScrollViewReader { proxy in
ScrollView {
LazyVStack(spacing: 0) {
ForEach(messages, id: \.self) { message in
MessageView(currentMessage: message)
.id(message)
}
}
.onChange(of: messages.count) {
withAnimation {
proxy.scrollTo(messages.last, anchor: .bottom)
}
}
.onAppear {
withAnimation {
proxy.scrollTo(messages.last, anchor: .bottom)
}
}
}

// send new message
HStack {
TextField("Send a message", text: $newMessage)
#if !os(tvOS)
.textFieldStyle(.roundedBorder)
#endif
Button(action: sendMessage) {
Image(systemName: "paperplane")
}
}
.padding()
}
.task {
await startChat()
}
}
}

func startChat() async {
let realtime = ARTRealtime(options: clientOptions)

let chatClient = DefaultChatClient(
realtime: realtime,
clientOptions: nil
)

do {
// Get the chat room
room = try await chatClient.rooms.get(roomID: "umairsDemoRoom1", options: .init())

// attach to room
try await room?.attach()

// subscribe to messages
let subscription = try await room?.messages.subscribe(bufferingPolicy: .unbounded)

// use subscription to get previous messages
let prevMessages = try await subscription?.getPreviousMessages(params: .init(orderBy: .oldestFirst))

// init local messages array with previous messages
messages = .init(prevMessages?.items ?? [])

// append new messages to local messages array as they are emitted
if let subscription {
for await message in subscription {
messages.append(message)
}
}
} catch {
print("Error starting chat: \(error)")
}
}

func sendMessage() {
guard !newMessage.isEmpty else {
return
}
Task {
do {
_ = try await room?.messages.send(params: .init(text: newMessage))

// Clear the text field after sending
newMessage = ""
} catch {
print("Error sending message: \(error)")
}
}
}
}

#Preview {
MessageDemoView()
}
18 changes: 17 additions & 1 deletion Example/AblyChatExample/Mocks/MockRealtime.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import Ably
@preconcurrency import Ably
import AblyChat

// swiftlint:disable:next line_length
/// A mock implementation of `RealtimeClientProtocol`. It only exists so that we can construct an instance of `DefaultChatClient` without needing to create a proper `ARTRealtime` instance (which we can’t yet do because we don’t have a method for inserting an API key into the example app). TODO remove this once we start building the example app
final class MockRealtime: NSObject, RealtimeClientProtocol, Sendable {
func request(_: String, path _: String, params _: [String: String]?, body _: Any?, headers _: [String: String]?, callback _: @escaping ARTHTTPPaginatedCallback) throws {
fatalError("not implemented")
}

var device: ARTLocalDevice {
fatalError("Not implemented")
}
Expand All @@ -14,6 +19,10 @@ final class MockRealtime: NSObject, RealtimeClientProtocol, Sendable {
let channels = Channels()

final class Channels: RealtimeChannelsProtocol {
func get(_: String, options _: ARTRealtimeChannelOptions) -> MockRealtime.Channel {
fatalError("Not implemented")
}

func get(_: String) -> Channel {
fatalError("Not implemented")
}
Expand All @@ -32,6 +41,13 @@ final class MockRealtime: NSObject, RealtimeClientProtocol, Sendable {
}

final class Channel: RealtimeChannelProtocol {
// Let 'defaultChannelOptions' is not concurrency-safe because non-'Sendable' type 'ARTRealtimeChannelOptions' may have shared mutable state - marked Ably import with @preconcurrency for now.
let properties: ARTChannelProperties

init(properties: ARTChannelProperties) {
self.properties = properties
}

var state: ARTRealtimeChannelState {
fatalError("Not implemented")
}
Expand Down
6 changes: 3 additions & 3 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"originHash" : "fcc346d6fe86e610ac200cdbbf91c56204df67286546d5079bd9c610ee65953b",
"originHash" : "d5cd1b39ed966b59fccd3f0d3d46bcf897088e975a6b8a3622235a7adfacaba6",
"pins" : [
{
"identity" : "ably-cocoa",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ably/ably-cocoa",
"state" : {
"revision" : "7f639c609e50053abd4590f34333f9472645558a",
"version" : "1.2.33"
"branch" : "main",
"revision" : "63e6f001d06cb7defb6be92f87a831f920eaf8c1"
}
},
{
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageDescription
let package = Package(
name: "AblyChat",
platforms: [
.macOS(.v11),
.macOS(.v12),
.iOS(.v14),
.tvOS(.v14),
],
Expand All @@ -20,7 +20,7 @@ let package = Package(
dependencies: [
.package(
url: "https://github.com/ably/ably-cocoa",
from: "1.2.0"
branch: "main"
),
.package(
url: "https://github.com/apple/swift-argument-parser",
Expand Down
68 changes: 68 additions & 0 deletions [email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// swift-tools-version: 6.0

import PackageDescription

let package = Package(
name: "AblyChat",
platforms: [
.macOS(.v12),
.iOS(.v14),
.tvOS(.v14),
],
products: [
.library(
name: "AblyChat",
targets: [
"AblyChat",
]
),
],
dependencies: [
.package(
url: "https://github.com/ably/ably-cocoa",
branch: "main"
),
.package(
url: "https://github.com/apple/swift-argument-parser",
from: "1.5.0"
),
.package(
url: "https://github.com/apple/swift-async-algorithms",
from: "1.0.1"
),
],
targets: [
.target(
name: "AblyChat",
dependencies: [
.product(
name: "Ably",
package: "ably-cocoa"
),
]
),
.testTarget(
name: "AblyChatTests",
dependencies: [
"AblyChat",
.product(
name: "AsyncAlgorithms",
package: "swift-async-algorithms"
),
]
),
.executableTarget(
name: "BuildTool",
dependencies: [
.product(
name: "ArgumentParser",
package: "swift-argument-parser"
),
.product(
name: "AsyncAlgorithms",
package: "swift-async-algorithms"
),
]
),
]
)
Loading

0 comments on commit 924e103

Please sign in to comment.