Displaying Skills Feedback on SUSI iOS

SUSI allows the user to rate the SUSI Skills with the five-star rating system. SUSI offer a good feedback system where the user can post feedback to any skill by using iOS, Android, and Web clients. In Skill Detail, there is a skill feedback text field where the user can write feedback about SUSI Skill. We display the users posted feedbacks on Skill Detail screen. In this post, we will see how the displaying skills feedback feature implemented on SUSI iOS.

Implementation –

We are displaying three feedback on Skill Detail screen, to see all feedback, there is a “See All Review” option, by clicking user is directed to a new screen where he/she can see all feedback related to particular skill.

We use the endpoint below for getting skill feedback from server side –

https://api.susi.ai/cms/getSkillFeedback.json

With the following params:

  • Model
  • Group
  • Language
  • Skill Name

The API endpoint above return the all the feedback array related to particular susi skill. We store feedbacks in an array of Feedback object, which holds three value:

    • Feedback String – Feedback string posted by the user
    • Email – Email address of feedback poster user
    • Time Stamp – Time of posting feedback
class Feedback: NSObject {
var feedbackString: String = ""
var email: String = ""
var timeStamp: String = ""
...
}

To display feedbacks, we are using UITableView with two prototype cells, one for feedbacks and one for “See All Review” option.

There can be different cases eg. when the total number of feedback for skill is less than three or three. When the feedback count is three or less than three, there is no need to show “See All Review” option. Also, tableView height is different for different feedback count. For varying tableView height, we have created an outlet for tableView height constraints and vary accordingly.

@IBOutlet weak var feedbackTableHeighConstraint: NSLayoutConstraint!

Now, let’s see how the number of cells, height for cells and different cells presented according to feedback count with UITableViewDelegate and UITableViewDataSource methods.

Handling number of tableView rows –

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let feedbacks = feedbacks, feedbacks.count > 0 else {
feedbackTableHeighConstraint.constant = 0.0
return 0
}
if feedbacks.count < 4 {
feedbackTableHeighConstraint.constant = CGFloat(72 * feedbacks.count)
return feedbacks.count
} else {
feedbackTableHeighConstraint.constant = 260.0
return 4
}
}

Where feedbacks is the array of Feedback object which holds the feedbacks we are getting from the server side for a skill.

var feedbacks: [Feedback]?

In the above method, we see that how we are handling the number of cells case. Now let’s see how to handle which cells to be present on basis of the number of cells case –

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let feedbacks = feedbacks, feedbacks.count > 0 else {
feedbackTableHeighConstraint.constant = 0.0
return UITableViewCell()
}
if feedbacks.count < 4 {
if let cell = tableView.dequeueReusableCell(withIdentifier: "feedbackDisplayCell", for: indexPath) as? FeedbackDisplayCell {
cell.feedback = feedbacks[indexPath.row - Int(indexPath.row/2)]
return cell
}
} else if feedbacks.count > 3 {
if indexPath.row == 3 {
let cell = tableView.dequeueReusableCell(withIdentifier: "allFeedbackCell", for: indexPath)
return cell
} else {
if let cell = tableView.dequeueReusableCell(withIdentifier: "feedbackDisplayCell", for: indexPath) as? FeedbackDisplayCell {
cell.feedback = feedbacks[indexPath.row]
return cell
}
}
}
return UITableViewCell()
}

If the number of feedbacks is greater than three than we provide “See All Review” option to the user to see all the feedback related to skill. We are displaying all feedbacks using UITableViewController. When the user clicks the “See All Review” option, we pass the feedbacks (Array of all the feedback) to new UITableViewController. By passing feedbacks, we are reducing one network call.

let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let allFeedbackVC = storyboard.instantiateViewController(withIdentifier: "AllFeedbackController") as? AllFeedbackViewController {
allFeedbackVC.feedbacks = self.feedbacks
let nvc = AppNavigationController(rootViewController: allFeedbackVC)
self.present(nvc, animated: true, completion: nil)
}

On all skill feedback screen, we are displaying the full review. For the different size of text, we are setting the different size of cell size by using the method below:

if let feedbacks = feedbacks {
let estimatedLabelHeight = UILabel().heightForLabel(text: feedbacks[indexPath.row].feedbackString, font: UIFont.systemFont(ofSize: 14.0), width: 250.0)
return 64 + estimatedLabelHeight
} else {
return 80
}

 

Final Output –

Resources –

  1. SUSI API Link: https://api.susi.ai/
  2. SUSI iOS Link: https://github.com/fossasia/susi_iOS
  3. Apple’s Documentation on UITableViewDelegate: https://developer.apple.com/documentation/uikit/uitableviewdelegate?changes=_6
  4. Apple’s Documentation on UITableViewDataSource: https://developer.apple.com/documentation/uikit/uitableviewdatasource

Allowing user to submit ratings for skills in SUSI iOS

Rating is a great way to get feedback from the user. Generally, the 5-Star rating system used to get feedback. The Five-Star Quality Rating System was developed as an easy-to-understand rating system for users.

In SUSI iOS we are using the Star Rating field to get feedback of SUSI skills. We enable the user to rate the skills from one to five star. In the rating submission, we get the number of stars user picked.

The stars help show if you would recommend the skill to others. The values start at 1 (being the lowest) and go to 5 (being the highest), as seen below –

Server-Side Implementation –

fiveStarRatings API is using to submit users rating. Whenever the user taps the star fiveStarRatings being called:

func submitRating(_ params: [String: AnyObject], _ completion: @escaping(_ updatedRatings: Ratings?, _ success: Bool, _ error: String?) -> Void) {
let url = getApiUrl(UserDefaults.standard.object(forKey: ControllerConstants.UserDefaultsKeys.ipAddress) as! String, Methods.fiveStarRateSkill)
_ = makeRequest(url, .get, [:], parameters: params, completion: { (results, message) in
// handle request
....
})
}

The following params we send in the request:

  • Model
  • Group
  • Language
  • Skill
  • Stars
  • Access Token

After successfully rating submission we get updated ratings for each star of the particular skill. The following response we get from the server after successfully submitted rating:

{
"ratings": {
"one_star": 0,
"four_star": 1,
"five_star": 0,
"total_star": 1,
"three_star": 0,
"avg_star": 4,
"two_star": 0
},
"session": {"identity": {
"type": "email",
"name": "[email protected]",
"anonymous": false
}},
"accepted": true,
"message": "Skill ratings updated"
}

We use ratings object from fiveStarRatings API to update the ratings displayed on charts and labels and also, we use ratings object to update Skill model which we passed from SkillListingController to SkillDetailController so the user can see updated rating chart when clicking back to skill.

func updateSkill(with ratings: Ratings) {..}

If the user already submitted ratings for a skill, we are using getRatingByUser API with same params as in fiveStarRatings except ratings to get already submitted user rating and we display that ratings as initial ratings in UI.

Implementation of Submit Rating UI –

RatingView is behind the submit rating UI. FloatRatingViewDelegate protocol is implemented to get ratings is updating or get updated.

@objc public protocol FloatRatingViewDelegate {
/// Returns the rating value when touch events end
@objc optional func floatRatingView(_ ratingView: RatingView, didUpdate rating: Double)

/// Returns the rating value as the user pans
@objc optional func floatRatingView(_ ratingView: RatingView, isUpdating rating: Double)
}

Rating updated on rating display chart:

Now see, how we handle the case when Skill is not rated before and the user first time rate the skill.

There is a label that shows the “Skill not rated yet” when a skill is not rated. When the user rates the skill, the label is hidden and chart bar and the label is shown.

if self.ratingsBackViewHeightConstraint.constant == 0 {
self.ratingsBackViewHeightConstraint.constant = 128.0
self.contentType.topAnchor.constraint(equalTo: self.ratingBackView.bottomAnchor, constant: 16).isActive = true
self.ratingsBackStackView.isHidden = false
self.topAvgRatingStackView.isHidden = false
self.notRatedLabel.isHidden = true
}

 

Resources –

  1. Swift Protocol: https://docs.swift.org/swift-book/LanguageGuide/Protocols.html
  2. SUSI Skills: https://skills.susi.ai/
  3. SUSI Server Link: https://github.com/fossasia/susi_server
  4. SUSI iOS Link: https://github.com/fossasia/susi_iOS