Getting skills by an author in SUSI.AI Skill CMS

The skill description page of any skill in SUSI.AI skill cms displays all the details regarding the skill. It displays image, description, examples and name of author. The skill made by author can impress the users and they might want to know more skills made by that particular author.

We decided to display all the skills by an author. We needed an endpoint from server to get skills by author. This cannot be done on client side as that would result in multiple ajax calls to server for each skill of user. The endpoint used is :

"http://api.susi.ai/cms/getSkillsByAuthor.json?author=" + author

Here the author is the name of the author who published the particular skill. We make an ajax call to the server with the endpoint mentioned above and this is done when the user clicks the author. The ajax call response is as follows(example) :

{
 0:       "/home/susi/susi_skill_data/models/general/Entertainment/en/creator_info.txt",
 1: "/home/susi/susi_skill_data/models/general/Entertainment/en/flip_coin.txt",
 2: "/home/susi/susi_skill_data/models/general/Assistants/en/websearch.txt",
session: {
identity: {
type: "host",
name: "139.5.254.154",
anonymous: true
  }
 }
}

The response contains the list of skills made by author. We parse this response to get the required information. We decided to display a table containing name, category and language of the skill. We used map function on object keys to parse information from every key present in JSON response. Every value corresponding to a key represents a response of following type:

"/home/susi/susi_skill_data/models/general/Category/language/name.txt"

Explanation:

  • Category: There are currently six categories Assistants, Entertainment, Knowledge, Problem Solving, Shopping and Small Talks. Each skill falls under a different category.
  • language: This represents the ISO language code of the language in which skill is written.
  • name: This is the name of the skill.

We want these attributes from the string so we have used the split function:

let parse = data[skill].split('/');

data is JSON response and skill is the key corresponding to which we are parsing information. We store the array returned by split function in variable parse. Now we return the following table in map function:

return (
            <TableRow>
               <TableRowColumn>
                   <div>
                      <Img
                         style={imageStyle}
                         src={[
                              image1,
                              image2
                         ]}
                         unloader={<CircleImage name={name} size="40"/>}
                       />
                       {name}
                    </div>
                </TableRowColumn>
                <TableRowColumn>{parse[6]}</TableRowColumn>
                <TableRowColumn>{isoConv(parse[7])}</TableRowColumn>
             </TableRow>
          )

Here :

    • name: The name of skill converted into Title case by the following code :
let name = parse[8].split('.')[0];
name = name.charAt(0).toUpperCase() + name.slice(1);
  • parse[6]: This represents the category of the skill.
  • isoConv(parse[7]): parse[7] is the ISO code of the language of skill and isoConv is an npm package used to get full form of the language from ISO code.
  • CircleImage: This is a fallback option in case image at the URL is not found. This takes first two words from the name and makes a circular component.

After successful execution of the code, we have the following looking table:

Resources:

Continue Reading Getting skills by an author in SUSI.AI Skill CMS

Implementation of React Routers in SUSI Web Chat

When we were developing the SUSI Web Chat application we wanted to implement set of static pages with the chat application. In the start we just wanted to navigate  through different static pages and move back to the web chat application. But it takes time to load a new page when user clicks on a link. Our goal was therefore to minimize the loading time by using lazy loading. For that we used react-route .It is standard library for react js.

From the docs:

“React Router keeps your UI synced with the URL. It has a simple API with powerful features like lazy code loading, dynamic route matching, and location transition handling built right in. Make the URL your first thought, not an after-thought.” (https://www.npmjs.com/package/react-router-plus)

We need react-route to be installed in our application first. We can install it using NPM by running this command on project folder.

npm install --save react-router-dom

Next we have to set up our routes. We have two types of headers in our application. One is chat application header, second one is static page header. In static page header we have a navigation to switch between static pages.
First we need to choose the router type because there are two types of routers in react. “” and “” we can use “” in our example because our server can handle dynamic requests. If we are requesting data from static page we should use “” .
We used that in “” and made another new component called “” and used it on “index.js” like this.

import { BrowserRouter as Router } from 'react-router-dom';
Import App from .App;
 ReactDOM.render(
  	<IntlProvider locale={defaultPrefLanguage}>
		<Router> <App /> </Router>
	</IntlProvider>,
  	document.getElementById('root')  );

In “App.js” we can set up routes like this.

        <Switch>
            <Route exact path='/' component={ChatApp}/>
            <Route exact path="/overview" component={Overview} />
            <Route exact path='/blog' component={Blog} />
            <Route exact path="/logout" component={Logout} />
            <Route exact path="/settings" component={Settings} />
            <Route exact path="*" component={NotFound} />
        </Switch>

We use elements to render component when they match with the corresponding path. We use “path” to add router location that we need to match width the component. We use “exact” property to render the component if location exactly matches with the “path”. If we do not use “exact” property it renders when we have child routes after the path like “/blog/1 “ .
We used “” element to group routes.
We can’t use anchor () tags to navigate pages without reloading. We have to use tags instead of that. We have to replace all the places we have used

<a href= ‘URL’>lable name </a>

with this,

<Link to=’URL’>Lable name</Link>   

After doing above changes application will perform faster and it will load all page contents soon after you click the navigation links.

If you would like to join with FOSSASIA and contribute to SUSI Web Chat Application please fork this repository on Github.

Resources

Continue Reading Implementation of React Routers in SUSI Web Chat

Introduction To Kotlin in SUSI Android App

Lately, we wrote some of the code of SUSI Android App in Kotlin. Kotlin is a very similar language to Java but with much more advantages than Java. It is easy to adapt and learn. There is no doubt that Kotlin is better than Java but with the announcement of Kotlin Support in Google IO’17 for Android development, Kotlin seems a decent way to write code for an Android App.

Advantages of Kotlin over Java

    1. Reduce Boilerplate Code: It helps making development of app faster as it reduces more than 20 percent of boilerplate code. Writing long statements again and again is a headache for developers. Kotlin comes to rescue in that situation.
    2. Removes Null Pointer Exception: Once a large company faced millions of dollars of loss due to null pointer exception. It causes crashes of apps more often than anything else. Thus Kotlin helps in Null checks and makes app free from Null pointer Exceptions.
    3. Interoperable with Java: Kotlin code and Java code are interoperable. Which means you can write half your code in kotlin and half in Java and it will work like a charm. You can call java methods from Kotlin code and vice versa. So, you can simply move your existing Java based app to Kotlin slowly making your app always running.
    4. Lambda and Inline functions: Yes, Kotlin also has functionalities from functional programming languages. Mainly and most widely used feature of those languages is Lambda functions.
    5. Direct Reference of Views by Id: You do not need to write findViewById(R.id.view_name) or use any other library like Butterknife for view binding. You can simply use the view by its id.
    6. No semicolon:  Last but not the least, you do not need to add a semicolon after each statement. In fact, you do not need to add semicolon at all.

Setting up Android Studio to work with Kotlin

If you have latest Android Studio Canary Version, there is already a build support for Kotlin in it. You need not do anything in that case. But if you don’t have the Canary version, you can add Kotlin Plugin in your Android Studio. Follow the below steps to do that.

  1. Install the Kotlin Plugin:

Android Studio → Preferences… →Plugins → Browse Repository → type “Kotlin” in search box → install

  1. Restart your Android Studio and Rebuild the project. Everything else is already set up in SUSI Android App but if you want to do it for your other apps, follow this link.

Implementation in SUSI Android App

So, I am not going to give unnecessary code but will point out specific things where Kotlin helped a lot to reduce unnecessary code and made the code compact.

1. Listeners:

Earlier with Java

Button signup = (Button) findViewById(R.id.sign_up);

signup.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               startActivity(new Intent(LoginActivity.this, SignUpActivity.class));
            }
        });

Now, with Kotlin

fun signUp() {
   sign_up.setOnClickListener { startActivity(Intent([email protected]LoginActivity, SignUpActivity::class.java)) }
}

2. Models

With Java

public class MapData {

    private double latitude;
    private double longitude;
    private double zoom;

    public MapData(double latitude, double longitude, double zoom) {
        this.latitude = latitude;
        this.longitude = longitude;
        this.zoom = zoom;
    }

    public double getLatitude() {
        return latitude;
    }

    public void setLatitude(double latitude) {
        this.latitude = latitude;
    }

    public double getLongitude() {
        return longitude;
    }

    public void setLongitude(double longitude) {
        this.longitude = longitude;
    }

    public double getZoom() {
        return zoom;
    }

    public void setZoom(double zoom) {
        this.zoom = zoom;
    }
}

With Kotlin

class MapData (var latitude: Double, var longitude: Double, var zoom: Double) 

3. Constructor

With Java

public class LoginPresenter {
    private LoginActivity loginActivity;
    public LoginPresenter(loginActivity: LoginActivity){
        this.loginActivity = loginActivity;
    }
}

With Kotlin

class LoginPresenter(loginActivity: LoginActivity) {
}

Summary

So, this blog was to give you an idea about Kotlin programming language, it’s advantages over java and information how you can set it up on your Android Studio so as to help you a little in understanding the codebase of SUSI Android App a little more.

Resources

  1. Official Kotlin Guide for Syntax Reference and further learning  https://kotlinlang.org/docs/reference/
  2. Blog by Elye on Setting up Kotlin on Android Studio https://android.jlelse.eu/setup-kotlin-for-android-studio-1bffdf1362e8
  3. Youtube Video tutorial by Derek Banas on Kotlin https://www.youtube.com/watch?v=H_oGi8uuDpA
Continue Reading Introduction To Kotlin in SUSI Android App

Parsing SUSI.AI Blog Feed

Our SUSI.AI web chat is improving every day.Recently we decided to add a blog page to our SUSI.AI web chat to show all the latest blogs related to SUSI.AI. These blogs are written by our developer team. We have the following feed from which we need to parse the information of blogs and display:

http://blog.fossasia.org/tag/susi-ai/feed/

This feed is in RSS (Really Simple Syndication) format. We decided to use Google feed API service to parse this RSS content, but that service is now deprecated. Then we decided to convert this RSS into JSON format and then we will parse the information.
We have used the rss2json API for converting our RSS content into JSON. We make an ajax call to this API for fetching the JSON content:

$.ajax({
        url: 'https://api.rss2json.com/v1/api.json',
        method: 'GET',
        dataType: 'json',
        data: {
            'rss_url': 'http://blog.fossasia.org/tag/susi-ai/feed/',
            'api_key': api_key: '0000000000000000000000000000000000000000', // put your api key here,
            'count': 50
        }
        }).done(function (response) {
            if(response.status !== 'ok'){ throw response.message; }
            this.setState({ posts: response.items, postRendered: true});
        }.bind(this));

Explanation:

  • URL: This is base URL of API to which we are making calls in order to fetch JSON data.
  • rss_url: This is the URL of RSS feed which needs to be converted into JSON format.
  • api_key: This is the key, which can be generated after making an account on the website
  • count: Count of feed items to return, the default is 20.

The converted JSON response looks like:

This can be checked here.

Now we have used cards of material-ui to show the content of this JSON response. On the success of our ajax call, we update the array(named as posts) present in the initial state of our component with response.items( an array containing information of blogs).

We map through each element in an array named posts and return the corresponding card, containing relevant information in it. We parsed the following information from JSON:

  • Name of the author: posts.author
  • Title of the blog: posts.title
  • Link to blog on WordPress: posts.link

Publish date of the blog:
The publish date is in this format: “2017-08-05 09:05:27” (example), we need to format this date. We used following code to do that:

let date = posts.pubDate.split(' ');
let d = new Date(date[0]);
dateFormat(d, 'dddd, mmmm dS, yyyy') // dateFormat is an npm package

This converts “2017-08-05 09:05:27” to “Saturday, August 5th, 2017”.

Description and Featured Images of the blog:
We needed to show a short description of the blog and a featured image. There is an element in our posts array with name description that contains HTML, starting from featured image and followed by a short description. We needed to convert this HTML into simple text for using this. I used htmlToText npm package for this purpose:
let description = htmlToText.fromString(posts.description).split(‘…’);
description variable now contains simple text. A simple example :

[https://blog.fossasia.org/wp-content/uploads/2017/08/image2-768×442.png] Our SUSI.AI Web Chat has many static pages like Overview, Devices, Team and Support.We have separate CSS files for each component. Recently, we faced a problem regarding design pattern where CSS files of one component were affecting another component. This blog is all about solving this issue and we take an example of distortion

Now, this text contains both link for featured image and our short description text. For getting the link for featured image, I have used regex(Regular Expression) in the following code and saved the link in the variable image and for short description I have used the split function :

let text = description[0].split(']');
let image = susi // temporary image for initialisation
let regExp = /\[(.*?)\]/;
let imageUrl = regExp.exec(description[0]);
if(imageUrl) {
   image = imageUrl[1]
}

After successfully parsing this information from JSON, we can have cards with details and card looks like this:

As we need all this to be rendered when the component mounts, so we put our ajax call inside componentDidMount() function of our react component.

Resources:

Continue Reading Parsing SUSI.AI Blog Feed

How to Implement Feedback System in SUSI iOS

The SUSI iOS app provides responses for various queries but the response is always not accurate. To improve the response, we make use of the feedback system, which is the first step towards implementing Machine Learning on the SUSI Server. The way this works is that for every query, we present the user with an option to upvote or downvote the response and based on that a positive or negative feedback is saved on the server. In this blog, I will explain how this feedback system was implemented in the SUSI iOS app.

Steps to implement:

We start by adding the UI which is two buttons, one with a thumbs up and the other with a thumbs down image.

textBubbleView.addSubview(thumbUpIcon)
textBubbleView.addSubview(thumbDownIcon)
textBubbleView.addConstraintsWithFormat(format: "H:[v0]-4-[v1(14)]-2-[v2(14)]-8-|", views: timeLabel, thumbUpIcon, thumbDownIcon)
textBubbleView.addConstraintsWithFormat(format: "V:[v0(14)]-2-|", views: thumbUpIcon)
textBubbleView.addConstraintsWithFormat(format: "V:[v0(14)]-2-|", views: thumbDownIcon)
thumbUpIcon.isUserInteractionEnabled = true
thumbDownIcon.isUserInteractionEnabled = true

Here, we add the subviews and assign constraints so that these buttons align to the bottom right next to each other. Also, we enable the user interaction for these buttons.

We know that the user can rate the response by pressing either of the buttons added above. To do that we make an API call to the endpoint below:

BASE_URL+'/cms/rateSkill.json?'+'model='+model+'&group='+group+'&skill='+skill+’&language’+language+’&rating=’+rating

Here, the BASE_URL is the url of the server, the other three params model, group, language and skill are retrieved by parsing the skill location parameter we get with the response. The rating is positive or negative based on which button was pressed by the user. The skill param in the response looks like this:

skills:
[
"/susi_skill_data/models/general/entertainment/en/quotes.txt"
]

Let’s write the method that makes the API call and responds to the UI that it was successful.

if let accepted = response[ControllerConstants.accepted] as? Bool {
  if accepted {
    completion(true, nil)
    return
  }
  completion(false, ResponseMessages.ServerError)
  return
}

Here after receiving a response from the server, we check if the `accepted` variable is true or not. Based on that, we pass `true` or `false` to the completion handler. Below the response we actually receive by making the request.

{
session: {
identity: {
type: "host",
name: "23.105.140.146",
anonymous: true
}
},
accepted: true,
message: "Skill ratings updated"
}

Finally, let’s update the UI after the request has been successful.

if sender == thumbUpIcon {
thumbDownIcon.tintColor = UIColor(white: 0.1, alpha: 0.7)
thumbUpIcon.isUserInteractionEnabled = false
thumbDownIcon.isUserInteractionEnabled = true
feedback = "positive"
} else {
thumbUpIcon.tintColor = UIColor(white: 0.1, alpha: 0.7)
thumbDownIcon.isUserInteractionEnabled = false
thumbUpIcon.isUserInteractionEnabled = true
feedback = "negative"
}
sender.tintColor = UIColor.hexStringToUIColor(hex: "#2196F3")

Here, we check the sender (the thumbs up or down button) and based on that pass the rating (positive or negative) and update the color of the button.

Below is the app in action with the feedback system.

Resources:

Continue Reading How to Implement Feedback System in SUSI iOS

Updating Settings Activity With MVP Architecture in SUSI Android

SUSI Android app includes settings that allow users to modify app features and behaviours. For example, SUSI Android allows users to specify whether they want voice output i.e text to speech irrespective of input type.

Currently different settings available in SUSI Android are:

Settings                                          Use
Enter As Send It allows users to specify whether they want to use action button of soft keyboard as carriage return or send message
Mic Input It allows users to specify whether they want to use speech for input type or not. User can also use keyboard to provide input.
Speech Always It allows users to specify whether they want speech output i.e text to speech irrespective of input type
Speech Output It allows users to specify whether they want speech output in case of speech input or not.
Select Server It allows user to specify whether they want to use default susi_server or their own server.
Language It allows users to select text to speech engine language.

Android’s standard architecture isn’t always sufficient, especially in the case of complex applications that need to be tested regularly. So we need an architecture that can improve the app’s testability and MVP(Model View Presenter) architecture is one of the best options for that.

Working with MVP architecture to show settings

MVP architecture creates three layers: Model, View and Presenter. Use of each layer is described below

  • Model: The Model holds the business logic of the application. It controls how data can be created, stored, and modified.
  • View: The View is a UI that displays data and routes user actions to the Presenter.
  • Presenter: Presenter is a mediator between View and Model. It retrieves data from the Model and shows it in the View. It also processes user actions forwarded to it by the View.

Steps followed to implement MVP architecture are:

1. I first created three interfaces: ISettingsView, ISettingModel and  ISettingsPresenter. These classes contain all the important functions we needed in model, presenter and view. Presenter’s interaction with model is handled by  ISettingModel and presenter’s interaction with view is handled by ISettingsPresenter and ISettingView.

  1. I created model, view and presenter class i.e SettingModel, SettingsPresenter and SettingsActivity and implemented ISettingModel, ISettingsPresenter and ISettingsView in SettingModel, SettingPresenter and SettingsActivity respectively so that presenter can communicate with both model and view.
class SettingsPresenter(fragmentActivity: FragmentActivity): ISettingsPresenter, ISettingModel.onSettingFinishListener
class SettingsActivity : AppCompatActivity(), ISettingsView
class SettingModel: ISettingModel
  1. After that, I created ChatSettingsFragment class. It is a PreferenceFragment class which resides in SettingsActivity and contains all settings.
class ChatSettingsFragment : PreferenceFragmentCompat() 

How Presenter communicates with Model and View

As already mentioned presenter’s interaction with view is handled by ISettingsView and ISettingsPresenter. So first I created object of ISettingsPresenter in ChatSettingsFragment, which is part of view, as shown below

lateinit var settingsPresenter: ISettingsPresenter
settingsPresenter = SettingsPresenter(activity)

And used it to call method of presenter, SettingsPresenter

settingsPresenter.sendSetting(Constant.ENTER_SEND, newValue.toString())

After that, I created an object of ISettingsView in SettingsPresenter, so that it can communicate with view, SettingsActivity.

Presenter’s interaction with model is handled by ISettingModel. So first I created an object of SettingModel class which implemented ISettingModel and then used it to call methods of SettingModel.

var settingModel: SettingModel = SettingModel()
settingModel.sendSetting(key, value, this)

Here the third parameter in the sendSetting method is ISettingModel.onSettingFinishListener which is part of ISettingModel. SettingModel used it to communicate with SettingsPresenter as shown below

override fun sendSetting(key: String, value: String, listener: ISettingModel.onSettingFinishListener) {

settingResponseCall.enqueue(object : Callback<ChangeSettingResponse> {

 override fun onResponse(call: Call<ChangeSettingResponse>?, response:

Response<ChangeSettingResponse>) {

          listener.onSuccess(response)

      }

  })

}

onSuccess method present in SettingsPresenter.

Reference

Continue Reading Updating Settings Activity With MVP Architecture in SUSI Android

How SUSI AI Web Chat Custom Theme Settings are Stored in Server

We had a feature in SUSI Web Chat to make custom themes but those themes were not storing on the server. We needed to store those theme data on server. In this post I discuss how we implemented that feature. This is the PR that I sent to solve this issue.

Previously we had two theme options. According to the user’s choice it changes theme colors. Since we needed to store custom themes and use them without any conflicts with existing “light” and “dark” themes we made another theme option called “custom”. After user clicks on the custom theme it automatically changes to “custom” mode.

This is how we did it in “onClick” of the custom theme .

    this.setState({'theme':'custom'})
     let currSettings = UserPreferencesStore.getPreferences();
     let settingsChanged = {};
     if(currSettings.Theme !=='custom'){
       settingsChanged.Theme = 'custom';
       Actions.settingsChanged(settingsChanged);
     }

Then after we collected all the chosen color values to a variable. While we store our color values on a variable we avoid the “#” letter which is at very first of the color value. Because we can’t send that value to the server with “#” character.

this.customTheme.body=state.body.substring(1);

After selecting color values user have to press the save button to push those selected values to server. We execute below method on click of the save button.

 saveThemeSettings = () => {
    let customData='';
    Object.keys(this.customTheme).forEach((key) => {
      customData=customData+this.customTheme[key]+','
    });
    this.setState({'theme':'custom'})
    let currSettings = UserPreferencesStore.getPreferences();
    let settingsChanged = {};
    if(currSettings.Theme !=='custom'){
      settingsChanged.Theme = 'custom';
      Actions.settingsChanged(settingsChanged);
    }
    Actions.customThemeChanged(customData);
    this.handleClose();
  }

Using this method we derived those data that we added into the variable and made a single string array. Then after we executed the action that we needed to execute to store data on the server.
It is “Actions.customThemeChanged(customData);”.
This action is defined in “Settings.actions.js” file.

export function customThemeChanged(customTheme) {
  ChatAppDispatcher.dispatch({
    type: ActionTypes.CHANGE_CUSTOM_THEME,
    customTheme
  });
  Actions.pushCustomThemeToServer(customTheme);
}

We used this Action name constant “CHANGE_CUSTOM_THEME” in “ChatConstant.js” file

We defined this “pushCustomThemeToServer”  function on “API.actions.js” file. here

export function pushCustomThemeToServer(customTheme){
  
  if(cookies.get('loggedIn')===null||
    cookies.get('loggedIn')===undefined) {
    return;
  }
       url = BASE_URL+'/aaa/changeUserSettings.json?'
          +'key=custom_theme_value&value='+customTheme
          +'&access_token='+cookies.get('loggedIn');
        makeServerCall(url);
}

Here we check whether user is logged in or not. If user is logged in we get the access token from cookies and attach it to the request URL and execute the “makeServerCall” function that we defined previously.

Now our data are saved on server. Use this url to check what settings you have in your user account.
api.susi.ai/aaa/listUserSettings.json?access_token=YOUR_ACCESS_TOKEN
Now we can use stored values. First we need to update state. For that we got theme values from server like this

  var themeValue=[];
   if(UserPreferencesStore.getThemeValues()){
     themeValue=UserPreferencesStore.getThemeValues().split(',');
   }

 

Here we got data from server and put it to the array.

Then after we set it to state. While adding custom theme settings to state we set the “#” character before each colour value.  Here is the code

    header: themeValue.length>4?'#'+themeValue[0]:'#4285f4',
    pane: themeValue.length>4?'#'+themeValue[1]:'#f5f4f6',
    body: themeValue.length>4?'#'+themeValue[2]:'#fff',
    composer: themeValue.length>4?'#'+themeValue[3]:'#f5f4f6',
    textarea:  themeValue.length>4?'#'+themeValue[4]:'#fff',

 

Now we have to use these data with our JSX elements. This is how we did this.

We checked the current theme mode. If it is “custom” we used the values we got from server. Otherwise we used corresponding colors for other “light” and “dark” theme. Here is the full code.

 

var bodyColor;
    var TopBarColor;
    var composerColor;
    var messagePane;
    var textArea;
switch(this.state.currTheme){
  case 'custom':{
    bodyColor = this.state.body;
    TopBarColor = this.state.header;
    composerColor = this.state.composer;
    messagePane = this.state.pane;
    textArea = this.state.textarea;
    break;
  }

You can use these variables wherever you need to show colors. As an example this is how we passed header color to top bar.

 <TopBar  header={TopBarColor} >

This is how we stored and fetched custom theme data from store.

Resources:

  • How to store and receive data from SUSI server using HTTP requests. https://github.com/fossasia/chat.susi.ai/blob/master/docs/Accounting.md
  • How Flux Architecture works: https://facebook.github.io/flux/
Continue Reading How SUSI AI Web Chat Custom Theme Settings are Stored in Server

Encoding and Decoding Images as Data in UserDefaults in SUSI iOS

In this blog post, I will be explaining how to encode and decode images and save them in UserDefaults so that the image persists even if it is removed from the Photos app. It happens a number of times that images are removed from the gallery by the users which results in the app loosing the image. So, to avoid this, we save the image by encoding it in a data object and save it inside UserDefaults. In SUSI iOS app we simply select an image from the image picker, encode it and save it in UserDefaults. To set the image, we simply fetch the image data from the UserDefaults and decode it to an image.

There are two ways we can do the encoding and decoding process:

  • Using Data object
  • Using Base64 string

For the scope of this tutorial, we will use the Data object.

Implementation Steps

  1. To use the image picker, we need to add permissions to `Info.plist` file.
<key>NSLocationWhenInUseUsageDescription</key>
<string>Susi is requesting to get your current location</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Susi needs to request your gallery access to select wallpaper</string>
  1. Select image from gallery

First, we present an alert which gives an option to select the image from the gallery.

// Show wallpaper options to set wallpaper or clear wallpaper
func showWallpaperOptions() {
  let imageDialog = UIAlertController(title: ControllerConstants.wallpaperOptionsTitle, message: nil, preferredStyle: UIAlertControllerStyle.alert)
  imageDialog.addAction(UIAlertAction(title: ControllerConstants.wallpaperOptionsPickAction, style: .default, handler: { (_: UIAlertAction!) in
  imageDialog.dismiss(animated: true, completion: nil)
  self.showImagePicker()
  }))
  imageDialog.addAction(UIAlertAction(title: ControllerConstants.wallpaperOptionsNoWallpaperAction, style: .default, handler: { (_: UIAlertAction!) in
    imageDialog.dismiss(animated: true, completion: nil)
    self.removeWallpaperFromUserDefaults()
  }))
  imageDialog.addAction(UIAlertAction(title: ControllerConstants.dialogCancelAction, style: .cancel, handler: { (_: UIAlertAction!) in
    imageDialog.dismiss(animated: true, completion: nil)
  }))
  self.present(imageDialog, animated: true, completion: nil)
}

Here, we create and UIAlertController with three options to select, one which presents the image picker controller, the second one removes the background wallpaper and the third dismisses the alert.

  1. Set the image as background view
// Callback when image is selected from gallery
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
  dismiss(animated: true, completion: nil)
  let chosenImage = info[UIImagePickerControllerOriginalImage] as? UIImage
  if let image = chosenImage {
    setBackgroundImage(image: image)
  }
}

We use the `didFinishPickingMediaWithInfo` delegate method to set the image as background. First we get the image using the the `info` dictionary using the `UIImagePickerControllerOriginalImage` key.

  1. Save the image in UserDefaults (encoding)
// Save image selected by user to user defaults
func saveWallpaperInUserDefaults(image: UIImage!) {
  let imageData = UIImageJPEGRepresentation(image!, 1.0)
  let defaults = UserDefaults.standard
  defaults.set(imageData, forKey: userDefaultsWallpaperKey)
}

We first convert the image to a data object using the `UIImageJPEGRepresentation` method followed by saving the data object in UserDefaults with the key `wallpaper`.

  1. Decode the data object back to UIImage 

Now whenever we need to decode the image, we simply get the data object from the UserDefaults and use it to display the image.

// Check if user defaults have an image data saved else return nil/Any
func getWallpaperFromUserDefaults() -> Any? {
  let defaults = UserDefaults.standard
  return defaults.object(forKey: userDefaultsWallpaperKey)
}

Below is the output when an image is selected and displayed as a background.

Resources:

Continue Reading Encoding and Decoding Images as Data in UserDefaults in SUSI iOS

Implementing Proper CSS for Static Pages in SUSI.AI Web Chat

Our SUSI.AI Web Chat has many static pages like Overview, Devices, Team and Support. We have separate CSS files for each component. Recently, we faced a problem regarding design pattern where CSS files of one component were affecting another component. This blog is all about solving this issue and we take an example of distortion in our team’s page.

The current folder structure looks like this :

We can see that there are separate CSS files for all components. When the build of our react web app is complete, all the CSS files are loaded at once. So if CSS files contain classes with similar names, then this can disturb the original intended design of a particular component.

Our Team Page after merging of recent pull requests looked like this :

The Card component holding the images had extended vertically. The card component has following code:

<Card className='team-card' key={i}>
  <CardMedia className="container" >
    <img src={serv.avatar} alt={serv.name} 
      className="image" />
      <div className="overlay" >
        <div className="text">
         <FourButtons member={serv} />
        </div>
      </div>
  </CardMedia>
  <CardTitle title={serv.name} subtitle={serv.designation} />
</Card>

The CardMedia component is having className = “container”. This was defined in Team.css file. The CSS for this component is as follows :

.container {
  position: relative;
}
.container:hover .overlay {
  bottom: 0;
  height: 100%;
  opacity:0.7;
}

After inspecting through Chrome’s developer’s tool, it was found that these CSS properties were overwritten by another component having the same className as container. To resolve this issue there are multiple approaches:

  • Find the component with the same className and change the className of that component.
  • Change the className of current component.
  • Change the name of both components to resolve conflicts in future.

All the approaches will do the job for us. Here the easiest task was to change the className of the current component. This will save us time and we would not be adding extra lines of code. This is an efficient solution. So we decided to change the className to “container_div”. Then the CSS files will look like this:

.container_div {
  position: relative;
}
.container_div:hover .overlay {
  bottom: 0;
  height: 100%;
  opacity:0.7;
}

We also have to update the className in our CardMedia to “container_div”. After doing these changes. The cards were back to intended design:

To avoid such conflicts in future, it is recommended to name your CSS classes uniquely and after you’re done with making any component, recheck through developer’s tool that your component’s className does not have any conflicts with other components.

Resources:

CSS best practises: https://code.tutsplus.com/tutorials/30-css-best-practices-for-beginners–net-6741

Code for Team’s Page: https://github.com/fossasia/chat.susi.ai/tree/master/src/components/Team

Team Page: http://chat.susi.ai/team

Continue Reading Implementing Proper CSS for Static Pages in SUSI.AI Web Chat

Implementation of SUSI Web Chat Auto Sizing Message Composer

While we are using SUSI Web Chat Application we may have to send lengthy messages. Existing application’s Message composer supports for lengthy messages but it manages a constant value for every user input. While we were developing the application we got a requirement to build a growing message composer.

Final output of this implementation produces a message composer that grows when user completes a new line until user completes 5 lines and after 5 lines it maintains a fixed size and enables scrolling.

So we tried several packages to get this done. And finally we did this  using react-textarea-autosize  it gives all these features and it gives user to customize the elements furthermore.

First we have to install the npm package:

npm install --save react-textarea-autosize

After the installation we have to import the package on top of the “MessageComposer.react.js”

import TextareaAutosize from 'react-textarea-autosize';

Next we need to use this package like this,

         <TextareaAutosize
           className='scroll'
           id='scroll'
           minRows={1}
           maxRows={5}
           placeholder="Type a message..."
           value={this.state.text}
           onChange={this._onChange.bind(this)}
           onKeyDown={this._onKeyDown.bind(this)}
           ref={(textarea) => { this.nameInput = textarea; }}
           style={{ background: this.props.textarea}}
         />

 

This package provides “minRows” and “maxRows”  attributes and we can define minimum height of the text area and maximum height it can grow. If you need to know more about auto growing text areas and to get examples refer this.

Next we wanted to hide the scrollbar which is displaying when the textarea height is exceeding.

How we hide the scrollbars  on chrome browsers.

.scroll::-webkit-scrollbar {
 	 display: none;
}

This is how we hide the scrollbar on firefox browser.

.scroll {
 	overflow: -moz-scrollbars-none;
}

Now we have to style up the textarea because it comes with default styles. We wrapped up the textarea with the div and applied our styles to that. In my case we wrapped up my textarea with  <div className=“textBack”>

This is how we styled the textarea using the wrapper div.

.textBack{
 background: #fff;
 width: 83%;
 border-radius: 40px;
 padding: 5px 20px;
 display: block;
 position: relative;
 top: 12%;
 box-sizing: content-box;
 margin: 0px 0 10px 0;
}

Our textarea is like this.

It expands when user exceeds the width of textarea.

This is how we implemented the SUSI Web Chat’s growing message composer. If you would like to contribute please fork our repository on github  

Resources:

Continue Reading Implementation of SUSI Web Chat Auto Sizing Message Composer