Skip to content

Commit 958b9a3

Browse files
authored
feat: UI customization (#2)
* feat: add customization * chore: update READMe.md
1 parent f6ffbf9 commit 958b9a3

File tree

6 files changed

+537
-233
lines changed

6 files changed

+537
-233
lines changed

README.md

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -69,64 +69,95 @@ To enable photo capture or image upload features in the chat interface, **you mu
6969

7070
## ⚙️ Configuration Parameters
7171

72-
| Parameter | Type | Required | Description |
73-
|--|-----------|----------|---------------------------------------------|
74-
| `accountId` | `Int` || Unique ID for the Chatwoot account |
75-
| `apiHost` | `String` || Chatwoot API host URL |
76-
| `accessToken` | `String` || Access token for authentication |
77-
| `pubsubToken` | `String` || Token for real-time updates |
78-
| `websocketUrl` | `String` || WebSocket URL for real-time communication |
72+
| Parameter | Type | Required | Default | Description |
73+
|----------------------|-----------|----------|---------|---------------------------------------------|
74+
| `accountId` | `Int` || - | Unique ID for the Chatwoot account |
75+
| `apiHost` | `String` || - | Chatwoot API host URL |
76+
| `accessToken` | `String` || - | Access token for authentication |
77+
| `pubsubToken` | `String` || - | Token for real-time updates |
78+
| `websocketUrl` | `String` || - | WebSocket URL for real-time communication |
79+
| `inboxName` | `String` || - | Display name for the inbox/chat channel |
80+
| `backArrowIcon` | `UIImage` || - | Back arrow icon for the header bar |
81+
| `connectedIcon` | `UIImage` || - | Icon displayed when the app is online |
82+
| `disconnectedIcon` | `UIImage` || - | Icon displayed when the app is offline |
83+
| `disableEditor` | `Bool` || `false` | Disables the message editor in chat UI |
84+
| `editorDisableUpload`| `Bool` || `false` | Disables file upload in the message editor |
7985

8086
---
8187

8288
## 🛠️ Example Usage
8389

84-
### UIKit
90+
### Step 1: Set up the SDK
8591

8692
```swift
87-
import ChatwootSDK
93+
// Create mandatory header icons as UIImage objects from SVG files using SVGKit
94+
let backArrowIcon = createUIImageFromSVG(named: "back_arrow.svg")
95+
let connectedIcon = createUIImageFromSVG(named: "connected_icon.svg")
96+
let disconnectedIcon = createUIImageFromSVG(named: "disconnected_icon.svg")
8897

89-
// 1. Setup in AppDelegate/SceneDelegate
9098
ChatwootSDK.setup(ChatwootConfiguration(
9199
accountId: 1,
92100
apiHost: "https://your-chatwoot.com",
93101
accessToken: "YOUR_ACCESS_TOKEN",
94102
pubsubToken: "YOUR_PUBSUB_TOKEN",
95-
websocketUrl: "wss://your-chatwoot.com"
103+
websocketUrl: "wss://your-chatwoot.com",
104+
inboxName: "Support", // Display name for the inbox
105+
backArrowIcon: backArrowIcon, // Required: UIImage object
106+
connectedIcon: connectedIcon, // Required: UIImage object
107+
disconnectedIcon: disconnectedIcon, // Required: UIImage object
108+
disableEditor: false, // Optional: disable message editor
109+
editorDisableUpload: false // Optional: disable file uploads
96110
))
111+
```
112+
113+
### Step 2: Show the Chat Interface (Recommended: Always Present from UIKit)
97114

98-
// 2. Present chat modally
115+
**Present Modally from UIKit (Recommended for Theming):**
116+
117+
```swift
118+
// UIKit Example
99119
ChatwootSDK.presentChat(from: self, conversationId: 123)
100120
```
101121

102-
### SwiftUI
122+
**SwiftUI Example (Call UIKit Presentation):**
103123

104124
```swift
105125
import SwiftUI
106126
import ChatwootSDK
107127

108-
struct ChatwootWrapper: UIViewControllerRepresentable {
109-
let conversationId: Int
110-
111-
func makeUIViewController(context: Context) -> UIViewController {
112-
return ChatwootSDK.loadChatUI(conversationId: conversationId)
113-
}
114-
115-
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
116-
}
117-
118128
struct ContentView: View {
119-
@State private var showChat = false
129+
@State private var conversationId: String = "14635"
120130

121131
var body: some View {
122132
Button("Open Chat") {
123-
showChat = true
133+
presentChatFromRoot()
124134
}
125-
.fullScreenCover(isPresented: $showChat) {
126-
ChatwootWrapper(conversationId: 123)
135+
}
136+
137+
private func presentChatFromRoot() {
138+
guard let conversationIdInt = Int(conversationId) else { return }
139+
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
140+
let rootVC = windowScene.windows.first?.rootViewController {
141+
ChatwootSDK.presentChat(from: rootVC, conversationId: conversationIdInt)
127142
}
128143
}
129144
}
130145
```
131146

132-
The conversationId is required to load the chat UI. Make sure you have a valid conversation ID before calling loadChatUI.
147+
- This approach ensures correct status bar theming and avoids SwiftUI modal limitations.
148+
- Do **not** use `.fullScreenCover` or `UIViewControllerRepresentable` for presenting the chat if you want full theming support.
149+
150+
---
151+
152+
The conversationId is required to load the chat UI. Make sure you have a valid conversation ID before calling loadChatUI.
153+
154+
## 🎨 Theme Customization
155+
156+
```swift
157+
// Set colors using UIColor or hex string
158+
ChatwootSDK.setThemeColor(.systemBlue)
159+
ChatwootSDK.setThemeColor("#1f93ff")
160+
ChatwootSDK.setTextColor(.white)
161+
```
162+
163+
---

Sources/ChatwootSDK/ChatwootSDK.swift

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,62 @@ public struct ChatwootConfiguration {
99
public let accessToken: String
1010
public let pubsubToken: String
1111
public let websocketUrl: String
12+
public let inboxName: String
13+
public let disableEditor: Bool
14+
public let editorDisableUpload: Bool
15+
#if canImport(UIKit)
16+
public let backArrowIcon: UIImage
17+
public let connectedIcon: UIImage
18+
public let disconnectedIcon: UIImage
19+
#endif
1220

21+
#if canImport(UIKit)
1322
public init(
1423
accountId: Int,
1524
apiHost: String,
1625
accessToken: String,
1726
pubsubToken: String,
18-
websocketUrl: String
27+
websocketUrl: String,
28+
inboxName: String,
29+
disableEditor: Bool = false,
30+
editorDisableUpload: Bool = false,
31+
backArrowIcon: UIImage,
32+
connectedIcon: UIImage,
33+
disconnectedIcon: UIImage
1934
) {
2035
self.accountId = accountId
2136
self.apiHost = apiHost
2237
self.accessToken = accessToken
2338
self.pubsubToken = pubsubToken
2439
self.websocketUrl = websocketUrl
40+
self.inboxName = inboxName
41+
self.disableEditor = disableEditor
42+
self.editorDisableUpload = editorDisableUpload
43+
self.backArrowIcon = backArrowIcon
44+
self.connectedIcon = connectedIcon
45+
self.disconnectedIcon = disconnectedIcon
2546
}
47+
#else
48+
public init(
49+
accountId: Int,
50+
apiHost: String,
51+
accessToken: String,
52+
pubsubToken: String,
53+
websocketUrl: String,
54+
inboxName: String,
55+
disableEditor: Bool = false,
56+
editorDisableUpload: Bool = false
57+
) {
58+
self.accountId = accountId
59+
self.apiHost = apiHost
60+
self.accessToken = accessToken
61+
self.pubsubToken = pubsubToken
62+
self.websocketUrl = websocketUrl
63+
self.inboxName = inboxName
64+
self.disableEditor = disableEditor
65+
self.editorDisableUpload = editorDisableUpload
66+
}
67+
#endif
2668
}
2769

2870
public enum ChatwootSDK {
@@ -36,6 +78,78 @@ public enum ChatwootSDK {
3678
}
3779

3880
#if canImport(UIKit)
81+
private static var currentThemeColor: UIColor = .white // Default as per theme.md
82+
private static var currentTextColor: UIColor? = nil // nil means auto-detect based on theme color
83+
/// Sets the theme color for the Chatwoot UI
84+
/// - Parameter color: The UIColor to use for theming.
85+
public static func setThemeColor(_ color: UIColor) {
86+
currentThemeColor = color
87+
}
88+
89+
/// Sets the theme color for the Chatwoot UI using a hex string
90+
/// - Parameter hex: Hex color string (supports formats: "#RRGGBB", "#RGB", "RRGGBB", "RGB")
91+
/// - Returns: True if the color was set successfully, false if the hex string is invalid
92+
@discardableResult
93+
public static func setThemeColor(hex: String) -> Bool {
94+
guard let color = UIColor(hex: hex) else {
95+
print("[Chatwoot] Warning: Invalid hex color string '\(hex)'. Theme color not changed.")
96+
return false
97+
}
98+
currentThemeColor = color
99+
return true
100+
}
101+
102+
/// Sets the theme color for the Chatwoot UI using a hex string (convenient overload)
103+
/// - Parameter hexString: Hex color string (supports formats: "#RRGGBB", "#RGB", "RRGGBB", "RGB")
104+
/// - Returns: True if the color was set successfully, false if the hex string is invalid
105+
@discardableResult
106+
public static func setThemeColor(_ hexString: String) -> Bool {
107+
return setThemeColor(hex: hexString)
108+
}
109+
110+
/// Sets the text color for the Chatwoot UI
111+
/// - Parameter color: The UIColor to use for text elements (close button, labels, etc.).
112+
public static func setTextColor(_ color: UIColor) {
113+
currentTextColor = color
114+
}
115+
116+
/// Sets the text color for the Chatwoot UI using a hex string
117+
/// - Parameter hex: Hex color string (supports formats: "#RRGGBB", "#RGB", "RRGGBB", "RGB")
118+
/// - Returns: True if the color was set successfully, false if the hex string is invalid
119+
@discardableResult
120+
public static func setTextColor(hex: String) -> Bool {
121+
guard let color = UIColor(hex: hex) else {
122+
print("[Chatwoot] Warning: Invalid hex color string '\(hex)'. Text color not changed.")
123+
return false
124+
}
125+
currentTextColor = color
126+
return true
127+
}
128+
129+
/// Sets the text color for the Chatwoot UI using a hex string (convenient overload)
130+
/// - Parameter hexString: Hex color string (supports formats: "#RRGGBB", "#RGB", "RRGGBB", "RGB")
131+
/// - Returns: True if the color was set successfully, false if the hex string is invalid
132+
@discardableResult
133+
public static func setTextColor(_ hexString: String) -> Bool {
134+
return setTextColor(hex: hexString)
135+
}
136+
137+
/// Gets the current theme color
138+
/// - Returns: The currently set UIColor for the theme.
139+
public static func getCurrentThemeColor() -> UIColor {
140+
return currentThemeColor
141+
}
142+
143+
/// Gets the current text color (auto-detects based on theme if not explicitly set)
144+
/// - Returns: The UIColor to use for text elements.
145+
public static func getCurrentTextColor() -> UIColor {
146+
if let textColor = currentTextColor {
147+
return textColor
148+
}
149+
// Auto-detect based on theme color luminance
150+
return currentThemeColor.isLight ? .black : .white
151+
}
152+
39153
/// Creates and returns a UIViewController for the Chatwoot chat interface
40154
/// - Parameter conversationId: Conversation ID to load a specific conversation
41155
/// - Returns: A UIViewController that can be presented modally or pushed onto a navigation stack

0 commit comments

Comments
 (0)