Skip to content

Commit

Permalink
Merge pull request #424 from Kommunicate-io/CM-2038
Browse files Browse the repository at this point in the history
[CM-2038] Change of CSAT 3 level rating to 5 star rating | iOS SDK
  • Loading branch information
kandpal025 authored May 8, 2024
2 parents d8d832a + 8e6a645 commit bb0d3ff
Show file tree
Hide file tree
Showing 13 changed files with 594 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,55 @@ extension KMConversationService {
}
}
}

func submitFiveStarFeedback(
groupId: Int,
feedback: KMFeedback,
userId: String,
userName: String,
assigneeId: String,
applicationId: String,
completion: @escaping (Result<ConversationFeedback, FeedbackError>) -> Void
) {
guard let url = URLBuilder.feedbackURLForSubmission().url else {
completion(.failure(.api(.urlBuilding)))
return
}
let userInfo: [String: Any] = [
FeedbackParamKey.userName: userName,
FeedbackParamKey.userId: userId,
]
var params: [String: Any] = [
FeedbackParamKey.groupId: groupId,
FeedbackParamKey.rating: feedback.rating,
FeedbackParamKey.applicationId: applicationId,
FeedbackParamKey.assigneeId: assigneeId,
FeedbackParamKey.userInfo: userInfo,
]
if let comment = feedback.comment, !comment.isEmpty {
params[FeedbackParamKey.comment] = [comment]
}
DataLoader.postRequest(url: url, params: params) { result in
switch result {
case let .success(data):
guard let feedbackResponse =
try? ConversationFeedbackSubmissionResponse(data: data)
else {
completion(.failure(.api(.jsonConversion)))
return
}
do {
let feedback = try feedbackResponse.conversationFeedback()
completion(.success(feedback))
} catch let error as FeedbackError {
completion(.failure(error))
} catch {
completion(.failure(.notFound))
}
case let .failure(error):
completion(.failure(.api(.network(error))))
}
}
}

}
1 change: 1 addition & 0 deletions Sources/Kommunicate/Classes/KMAppSettingsResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct ChatWidgetResponse: Decodable {
let zendeskChatSdkKey: String?
let defaultUploadOverride : DefaultUploadOverride?
let pseudonymsEnabled : Bool?
let csatRatingBase : Int?
let disableChatWidget : Bool?
}

Expand Down
3 changes: 3 additions & 0 deletions Sources/Kommunicate/Classes/KMAppSettingsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class KMAppSettingService {
}

KMAppUserDefaultHandler.shared.botMessageDelayInterval = chatWidget.botMessageDelayInterval ?? 0
KMAppUserDefaultHandler.shared.csatRatingBase = chatWidget.csatRatingBase ?? 3

guard let primaryColor = chatWidget.primaryColor else {
setupDefaultSettings()
Expand All @@ -71,6 +72,8 @@ class KMAppSettingService {
appSettings.hidePostCTAEnabled = chatWidget.hidePostCTA ?? false
appSettings.defaultUploadOverrideUrl = chatWidget.defaultUploadOverride?.url ?? ""
appSettings.defaultUploadOverrideHeaders = chatWidget.defaultUploadOverride?.headers ?? [:]
appSettings.csatRatingBase = chatWidget.csatRatingBase ?? 3
appSettingsUserDefaults.setCSATRatingBase(value: chatWidget.csatRatingBase ?? 3)
appSettingsUserDefaults.updateOrSetAppSettings(appSettings: appSettings)
}

Expand Down
9 changes: 9 additions & 0 deletions Sources/Kommunicate/Classes/KMAppUserDefaultHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ class KMAppUserDefaultHandler: NSObject {
return userDefaultSuite.integer(forKey: Key.BotMessageDelayInterval)
}
}
var csatRatingBase: Int {
set {
userDefaultSuite.set(newValue, forKey: Key.CSATRatingBase)
}
get {
return userDefaultSuite.integer(forKey: Key.CSATRatingBase)
}
}

private let userDefaultSuite: UserDefaults

Expand All @@ -54,5 +62,6 @@ private extension KMAppUserDefaultHandler {
enum Key {
static let CSATEnabled = "CSAT_ENABLED"
static let BotMessageDelayInterval = "BOT_MESSAGE_DELAY_INTERVAL"
static let CSATRatingBase = "CSAT_RATTING_BASE"
}
}
102 changes: 80 additions & 22 deletions Sources/Kommunicate/Classes/KMConversationViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ open class KMConversationViewController: ALKConversationViewController, KMUpdate
private let faqIdentifier = 11_223_346
private let kmConversationViewConfiguration: KMConversationViewConfiguration
private weak var ratingVC: RatingViewController?
private weak var fiveStarRatingVC: KMStarRattingViewController?
private let registerUserClientService = ALRegisterUserClientService()
let kmBotService = KMBotService()
private var assigneeUserId: String?
Expand Down Expand Up @@ -718,34 +719,65 @@ extension KMConversationViewController {
}

private func showRatingView() {
guard let currentViewController = UIViewController.topViewController(), currentViewController is KMConversationViewController, self.ratingVC == nil else { return }
guard let currentViewController = UIViewController.topViewController(), currentViewController is KMConversationViewController else { return }

let ratingVC = RatingViewController()
ratingVC.closeButtontapped = { [weak self] in
self?.hideRatingView()
}
ratingVC.feedbackSubmitted = { [weak self] feedback in
print("feedback submitted with rating: \(feedback.rating)")
KMCustomEventHandler.shared.publish(triggeredEvent: KMCustomEvent.submitRatingClick, data: ["rating": feedback.rating.rawValue,"comment":feedback.comment ?? "","conversationId": self?.viewModel.channelKey])
self?.hideRatingView()
self?.submitFeedback(feedback: feedback)
}
if KMAppUserDefaultHandler.shared.csatRatingBase == 5 {
guard self.fiveStarRatingVC == nil else { return }
let ratingVC = KMStarRattingViewController()
ratingVC.closeButtontapped = { [weak self] in
self?.hideRatingView()
}
ratingVC.feedbackSubmitted = { [weak self] feedback in
print("feedback submitted with rating: \(feedback.rating)")
KMCustomEventHandler.shared.publish(triggeredEvent: KMCustomEvent.submitRatingClick, data: ["rating": feedback.rating,"comment":feedback.comment ?? "","conversationId": self?.viewModel.channelKey])
self?.hideRatingView()
self?.submitFiveStarFeedback(feedback: feedback)
}

present(ratingVC, animated: true, completion: { [weak self] in
self?.ratingVC = ratingVC
})
present(ratingVC, animated: true, completion: { [weak self] in
self?.fiveStarRatingVC = ratingVC
})
} else {
guard self.ratingVC == nil else { return }
let ratingVC = RatingViewController()
ratingVC.closeButtontapped = { [weak self] in
self?.hideRatingView()
}
ratingVC.feedbackSubmitted = { [weak self] feedback in
print("feedback submitted with rating: \(feedback.rating)")
KMCustomEventHandler.shared.publish(triggeredEvent: KMCustomEvent.submitRatingClick, data: ["rating": feedback.rating.rawValue,"comment":feedback.comment ?? "","conversationId": self?.viewModel.channelKey])
self?.hideRatingView()
self?.submitFeedback(feedback: feedback)
}

present(ratingVC, animated: true, completion: { [weak self] in
self?.ratingVC = ratingVC
})
}
}

private func hideRatingView() {
guard let ratingVC = ratingVC,
UIViewController.topViewController() is RatingViewController,
!ratingVC.isBeingDismissed
else {
return
if KMAppUserDefaultHandler.shared.csatRatingBase == 5 {
guard let ratingVC = fiveStarRatingVC,
UIViewController.topViewController() is KMStarRattingViewController,
!ratingVC.isBeingDismissed
else {
return
}
dismiss(animated: true, completion: { [weak self] in
self?.fiveStarRatingVC = nil
})
} else {
guard let ratingVC = ratingVC,
UIViewController.topViewController() is RatingViewController,
!ratingVC.isBeingDismissed
else {
return
}
dismiss(animated: true, completion: { [weak self] in
self?.ratingVC = nil
})
}
dismiss(animated: true, completion: { [weak self] in
self?.ratingVC = nil
})
}

private func submitFeedback(feedback: Feedback) {
Expand All @@ -770,6 +802,29 @@ extension KMConversationViewController {
}
}
}

private func submitFiveStarFeedback(feedback: KMFeedback) {
guard let channelId = viewModel.channelKey else { return }
conversationService.submitFiveStarFeedback(
groupId: channelId.intValue,
feedback: feedback,
userId: KMUserDefaultHandler.getUserId(),
userName: KMUserDefaultHandler.getDisplayName() ?? "",
assigneeId: assigneeUserId ?? "",
applicationId: KMUserDefaultHandler.getApplicationKey()
) { [weak self] result in
switch result {
case let .success(conversationFeedback):
print("feedback submit response success: \(conversationFeedback)")
guard conversationFeedback.feedback != nil else { return }
DispatchQueue.main.async {
self?.showFiveStar(feedback: feedback)
}
case let .failure(error):
print("feedback submit response failure: \(error)")
}
}
}

private func updateMessageListBottomPadding(isClosedViewHidden: Bool) {
var heightDiff: Double = 0
Expand Down Expand Up @@ -803,6 +858,9 @@ extension KMConversationViewController {
private func show(feedback: Feedback) {
updateMessageListBottomPadding(isClosedViewHidden: false)
}
private func showFiveStar(feedback: KMFeedback) {
updateMessageListBottomPadding(isClosedViewHidden: false)
}
}

extension KMConversationViewController {
Expand Down
80 changes: 80 additions & 0 deletions Sources/Kommunicate/Classes/Views/KMFiveStarView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// KMFiveStarView.swift
// Kommunicate
//
// Created by Abhijeet Ranjan on 03/05/24.
//

import Foundation
import KommunicateChatUI_iOS_SDK
import UIKit

class KMFiveStarView: UIView {

var ratingSelected: ((KMStarRatingType) -> Void)?

var starButtons: [UIButton] = []
private var rating: Int = 0 {
didSet {
updateStars()
}
}

var maxRating: Int = 5 {
didSet {
setupStars()
}
}

var ratingDidChange: ((Int) -> Void)?

var filledStarImage: UIImage?
var emptyStarImage: UIImage?

override init(frame: CGRect) {
super.init(frame: frame)
setupStars()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupStars()
}

func setupStars() {
starButtons.forEach { $0.removeFromSuperview() }
starButtons.removeAll()
for i in 0..<maxRating {
let starButton = UIButton()
starButton.tag = i
starButton.setImage(emptyStarImage, for: .normal)
starButton.addTarget(self, action: #selector(starTapped(_:)), for: .touchUpInside)
addSubview(starButton)
starButtons.append(starButton)
}
updateStars()
}

override func layoutSubviews() {
super.layoutSubviews()
let starSize = CGSize(width: self.bounds.height, height: self.bounds.height)
let spacing = (self.bounds.width - CGFloat(maxRating) * starSize.width) / CGFloat(maxRating + 1)

for (index, starButton) in starButtons.enumerated() {
let x = CGFloat(index) * (starSize.width + spacing) + spacing
starButton.frame = CGRect(origin: CGPoint(x: x, y: 0), size: starSize)
}
}

@objc private func starTapped(_ sender: UIButton) {
rating = sender.tag + 1
ratingDidChange?(rating)
}

private func updateStars() {
for (index, starButton) in starButtons.enumerated() {
let starImage = index < rating ? filledStarImage : emptyStarImage
starButton.setImage(starImage, for: .normal)
}
}
}
Loading

0 comments on commit bb0d3ff

Please sign in to comment.