Migration of LXDE Desktop of Meilix to LXQt

Meilix is originally based on LXDE and our task is to migrate Meilix desktop to LXQt. Originally LXQt is a fusion of LXDE and the Razor-Qt desktop.

What is LXDE and LXQt ?
Both are desktop environment. They are light weight with a beautiful GUI and a user-friendly touch.

Older code to install a LXDE desktop in a Debian-based environment is (source):

apt-get -q -y --purge install --no-install-recommends \
lxsession lightdm lightdm-gtk-greeter lxterminal gvfs-backends seahorse \
network-manager-gnome xorg dbus-x11 openbox ubiquity-frontend-gtk vlc \
xfce4-mixer gstreamer0.10-alsa pulseaudio pavucontrol lxpanel \
mozilla-plugin-vlc lubuntu-core jockey-gtk

In the chroot environment, the maintainer has pick up the it’s dependencies and recommend packages and listed all packages which required to install a LXDE desktop. He keeps the size as small as possible. The maintainer had hand-picked only the required packages which can run the desktop.

What we did:
We tried with several approaches. Few worked and few did not. We removed all the packages related to desktop like gnome-games, gdm and gnome-language-en since we don’t want any sort of problem conflicts in the new desktop. We had also remove all the lines mentioned above which install LXDE.

Then I simply typed the line:

apt-get -q -y install lxqt

This way we only reach to the CLI version of the OS, and we actually don’t know whether we actually have the desktop install or not.
We tried to install lighdm their by

sudo apt-get install xinit

but that was also giving errors.

We changes the line to:

apt-get -q -y install xorg sddm lxqt

And suddenly the same approach which brought to a black screen and that was a desktop and the window manager was openbox.

Then we tried to add sddm.conf to get the desktop and realized that sddm starts plasma instead of LXQt. Then we added a config file for sddm to start up LXQt by default. This file is present in the location `/etc/sddm.conf` in the meilix-default-settings metapackage with the code as:

[Autologin]
User=hotelos
Session=lxqt.desktop

But due to a bug reported here, it still starts plasma instead of LXQt. So now I have to patch a script in the location /usr/share/initramfs-tools/scripts/casper-bottom/15autologin

In last paragraph of the script it will first detect the presence of sddm, if that exists, it will assume that plasma will be default desktop and try to detect Lubuntu.desktop and QLubuntu.desktop .
So, change plasma.desktop to lxqt.desktop.

In the 15autologin changes line 84:

if [ -f /root/usr/bin/sddm ]; then
	sddm_session=lxqt.desktop
	if [ -f /root/usr/share/xsessions/Lubuntu.desktop ]; then
    	    sddm_session=Lubuntu.desktop
	fi
	if [ -f /root/usr/share/xsessions/QLubuntu.desktop ]; then
    	    sddm_session=QLubuntu.desktop
	fi
fi

Here, sddm is made to configure it must use lxqt desktop.

And this worked. Finally we get the LXQt desktop for Meilix.

Since this script along with other scripts will be packed into a small filesystem called initramfs, therefore we have to write `update-initramfs -u ` after the installation of the meilix-default-settings package.

I had made a table of the things that worked and that didn’t.

Things which do not work Things which work
1. Firstly, we removed all these lines and replace it with `apt-get -q -y install lxqt`, but this will take us to a CLI and ask to install xinit which takes us to another error. We want to straight forward go to GUI.
2. Removing of all the others desktop dependencies like gnome (here), LXDE. The way build.sh was written is that it includes suggests and recommends packages to take less space.
3. Giving less RAM to the live boot ISO which gives the error in the installation of xinit in CLI mode.
4. Removing single line and adding single line and keep on testing rather than testing on a big code. This helped us to find that where actually the issue lies.
5. Removing the rc.xml file which is responsible for openbox configuration that we don’t need in the LXQt. Screenshots are attached below.
6. Finally I booted into CLI and there I type startx which ask me to do apt-get install xinit then after that I did startx from where it takes me to the black screen.
7. I included lightdm but it’s not working and when i tried to start it using the terminal of openbox “systemctl start lightdm” , it gives the error as “failed to start lightdm.service: Unit lightdm.service not found”.
8. When I tried to install lightdm “sudo apt-get install lightdm-gtx-greeter” and we get the error pasted below.
9. Then I though to go for sddm which is a “Simple Desktop Display Manager” a replacement for lightdm.
10. Finally we make it something like this “sudo apt-get install -q -y xorg sddm lxqt”
11. sddm starts plasma (KDE) instead of LXQt for getting the desire result we need is to add /etc/sddm.conf and set its login default to lxqt.desktop
12. After screenshot of number 10, I clicked ok and logout myself to CLI and login and type startx to get the screenshot.
13. All of the things that I was doing is in the xenial version of meilix and when I tried the same in zesty. I get the following result.
14. We think there must be something why Casper uses plasma by default instead of lxqt. We can also try preventing plasma.desktop being installed in the first place.
15. Casper will set up the login when live cd is started, and the file will be changed too.
So writing to the config file (sddm.conf) actually does nothing.
16. apt install lubuntu installs lubuntu.desktop while Casper (It is actually responsible for setting up live environment, which also needs to takes care of auto login) checks for Lubuntu.desktop (noting the capital letter)
It is in the script `/usr/share/initramfs-tools/scripts/casper-bottom/15autologin`
Probably we have to patch that.
So Casper will write configs to login manager like sddm.At the time of booting the ISO into the live desktop.
The problem is, the script Casper is using, detects a different file name for LXQt.
17. We get the “15autologin file” inside chroot directory if you build the iso by yourself.
18. Then we added the line to update – initramfs (initramfs is a small filesystem that linux will mount first, which will only have basic functions, then other partitions).

Number 3

xinit error after increasing of the RAM

Number 5

Number 6

This is Openbox.

Number 8

Number 10

Number 12

Number 13.

Finally the desktop LXQt

References:
LXQt Desktop
LXDE Desktop
Switching Desktop Environment

Continue ReadingMigration of LXDE Desktop of Meilix to LXQt

Implementation of Image Viewer in Susper

We have implemented image viewer in Susper similar to Google.

Before when a user clicks on a thumbnail the images are opened in a separate page, but we want to replace this with an image viewer similar to Google.

Implementation Logic:

1. Thumbnails for images in susper are arranged as shown in the above picture.

2. When a user clicks on an image a hidden empty div(image viewer) of the last image in a row is opened.

3. The clicked image is then rendered in the image viewer (hidden div of the last element in a row).

4. Again clicking on the same image closes the opened image viewer.

5. If a second image is clicked then, if an image is in the same row, it is rendered inside the same image viewer. else if the image is in another row, this closes the previous image viewer and renders the image in a new image viewer (hidden div of the last element of the row)

6. Since image viewer is strictly the hidden empty div of the last element in a row when it is expanded it occupies the position of the next row, moving them further down similar to what we want.

Implementation Code

results.component.html

<div *ngFor="let item of items;let i = index">
 <div class="item">
   <img src="{{item.link}}" height="200px" (click)="expandImage(i)" [ngClass]="'image'+i">
 </div>
 <div class=" item image-viewer" *ngIf="expand && expandedrow === i">
   <span class="helper"></span> <img [src]="items[expandedkey].link" height="200px" style="vertical-align: middle;">
 </div>

</div>

Each thumbnail image will have a <div class=” item image-viewer” which is in hidden state initially.

Whenever a user clicks on a thumbnail that triggers expandImage(i)

results.component.ts

expandImage(key) {
 if (key === this.expandedkey    this.expand === false) {
   this.expand = !this.expand;
 }
 this.expandedkey = key;
 let i = key;
 let previouselementleft = 0;
 while ( $('.image' + i) && $('.image' + i).offset().left > previouselementleft) {
   this.expandedrow = i;
   previouselementleft = $('.image' + i).offset().left;
   i = i + 1;

The expandImage() function takes the unique key and finds which image is the last element is the last image in the whole row, and on finding the last image, expands the image viewer of the last element and renders the selected image in the image viewer.

The source code for the whole implementation of image viewer could be seen at pull: https://github.com/fossasia/susper.com/pull/687/files

Resources:

  1. Selecting elements in Jquery: https://learn.jquery.com/using-jquery-core/selecting-elements/

 

Continue ReadingImplementation of Image Viewer in Susper

Implementing Change Password Feature in SUSI Android App using Custom Dialogs

Recently a new servlet was implemented on the SUSI Server about changing the password of the logged in user. This feature comes in handy to avoid unauthorized usage of the SUSI Account. Almost all the online platforms have this feature to change the password to avoid notorious user to unethical use someone else’s account. In SUSI Android app this new API was used with a nice UI to change the password of the user. The process is very simple and easy to grasp. This blog will try to cover the API information and implementation of the Change Password feature in the android client.

API Information

For changing the password of SUSI Account of the user, we have to call on  /aaa/changepassword.json

We have to provide three parameters along with this api call:

  1. changepassword:  Email of user (type string) using which user is logged in.
  2. password:  Old password (type string with min length of 6) of the user.
  3. newpassword: New password (type string with min length of 6) of the user.
  4. access_token: An encrypted access_token indicating user is logged in.

Sample Response (Success)

{
  "session": {"identity": {
    "type": "email",
    "name": "YOUR_EMAIL_ADDRESS",
    "anonymous": false
  }},
  "accepted": true,
  "message": "Your password has been changed!"
}

Error Response (Failure). This happens when user is not logged in:

HTTP ERROR 401
Problem accessing /aaa/changepassword.json. Reason:
   Base user role not sufficient. Your base user role is 'ANONYMOUS', your user role is 'anonymous'

Implementation in SUSI Android App

The change password option is located in Settings Activity and displayed only when user is logged in. So, if a logged in user wants to change the password of his/her SUSI AI account, he/she can simply go to the Settings and click on the option. Clicking on the options open up a dialog box with 3 input layouts for:

  1. Current Password
  2. New Password
  3. Confirm New Password

So, user can simply add these three inputs and click “Ok”. This will change the password of their account. Let’s see some code explanation.

  1. When user clicks on the “reset password” option from the settings, the showResetPasswordAlert() method is called which displays the dialog. And when user clicks on the “OK” button the resetPassword method() in the presenter is called passing input from the three input layout as parameters.

settingsPresenter.resetPassword(password.editText?.text.toString(), newPassword.editText?.text.toString(), conPassword.editText?.text.toString())

fun showResetPasswordAlert() {
   val builder = AlertDialog.Builder(activity)
   val resetPasswordView = activity.layoutInflater.inflate(R.layout.alert_reset_password, null)
   password = resetPasswordView.findViewById(R.id.password) as TextInputLayout
   newPassword = resetPasswordView.findViewById(R.id.newpassword) as TextInputLayout
   conPassword = resetPasswordView.findViewById(R.id.confirmpassword) as TextInputLayout
   builder.setView(resetPasswordView)
   builder.setTitle(Constant.CHANGE_PASSWORD)
           .setCancelable(false)
           .setNegativeButton(Constant.CANCEL, null)
           .setPositiveButton(getString(R.string.ok), null)
   resetPasswordAlert = builder.create()
   resetPasswordAlert.show()
   setupPasswordWatcher()
   resetPasswordAlert.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
       settingsPresenter.resetPassword(password.editText?.text.toString(), newPassword.editText?.text.toString(), conPassword.editText?.text.toString())
   }
}
  1. In the resetPassword method, all details about the passwords are checked like:
  1. If passwords are not empty.
  2. If passwords’ lengths are greater than 6.
  3. If new password and confirmation new password matches

   

When all the conditions are satisfied and all the inputs are valid, resetPassword() in model is called which makes network call to change password of the user.

settingModel.resetPassword(password,newPassword,this)

override fun resetPassword(password: String, newPassword: String, conPassword: String) {
   if (password.isEmpty()) {
       settingView?.invalidCredentials(true, Constant.PASSWORD)
       return
   }
   if (newPassword.isEmpty()) {
       settingView?.invalidCredentials(true, Constant.NEW_PASSWORD)
       return
   }
   if (conPassword.isEmpty()) {
       settingView?.invalidCredentials(true, Constant.CONFIRM_PASSWORD)
       return
   }

   if (!CredentialHelper.isPasswordValid(newPassword)) {
       settingView?.passwordInvalid(Constant.NEW_PASSWORD)
       return
   }

   if (newPassword != conPassword) {
       settingView?.invalidCredentials(false, Constant.NEW_PASSWORD)
       return
   }
   settingModel.resetPassword(password,newPassword,this)
}

Summary

So, this blog talked about how the Change Password feature is implemented in SUSI Android App. This included how a network call is made, logic for making network, information about API, making dialogs with custom UI, etc. So, If you are looking forward to contribute to SUSI Android App, this can help you a little. But if not so, this may also help you in understanding and how you can implement a dialog box with custom UI.

References

  1. To know about servlets https://en.wikipedia.org/wiki/Java_servlet
  2. To see how to implement one https://www.javatpoint.com/servlet-tutorial
  3. To see how to make network calls in android using Retrofit https://guides.codepath.com/android/Consuming-APIs-with-Retrofit
  4. Official docs for displaying dialog https://developer.android.com/guide/topics/ui/dialogs.html
  5. Implementing dialog boxes with custom UI https://stackoverflow.com/questions/13341560/how-to-create-a-custom-dialog-box-in-android
  6. Pull Request for API reference: https://github.com/fossasia/susi_server/pull/352
Continue ReadingImplementing Change Password Feature in SUSI Android App using Custom Dialogs

Implementing Custom Placeholders in Open Event Front-end

We have custom placeholders that are custom images used for different event topics in Open Event Front-end. The custom placeholder are set in the system-images route under admin. The system-images is a dynamic route which changes according the selected topic which brings a challenge to handle dynamic requests to the Orga-server. Lets see how we did it?
Retrieving data from server:

Each custom-placeholder is related to an event-sub-topic, which is related to an event-topic. We fetch a list of all the event-topics available on the system-images route. We also include event-sub-topics in the request & sort the topics by name.

model() {
  return this.store.query('event-topic', {
    include : 'event-sub-topics',
    sort    : 'name'
  });
},
afterModel(model, transition) {
  this._super(...arguments);
  if (transition.targetName === 'admin.content.system-images.index') {
    this.replaceWith('admin.content.system-images.list', model.toArray()[0].id);
  }
}

We override the afterModel() method of the route and redirect to the system-images/list route. We pass the ID of the topic to the dynamic list route, where we use this ID to get all the subtopics of the selected topic.

Handling dynamic route & model:

On the system-images/list route we set the params passed by the system-images route. In the model function we return all the subtopics of the selected topic. On clicking any topic we pass the ID of the topic to the list route where we retrieve all the sub-topics.

model(params) {
  this.set('params', params);
  return this.store.findRecord('event-topic', params.topic_id, { include: 'event-sub-topics' });
}

We dynamically render all the subtopics and the related custom-placeholders by looping through the array of the subtopics. We render an image along with a button to change the placeholder for each of the sub-topic. We use a custom modal for changing the placeholder image and for entering details related to the placeholder.

    <h4>{{subTopic.name}}</h4>
    <img src="{{subTopic.placeholder.originalImageUrl}}" class="ui big image" alt={{subTopic.name}}>
    <div class="ui hidden divider"></div>
    <button class="ui button primary" {{action 'openModal' subTopic.placeholder}} id="changebutton">{{t 'Change'}}</button>

Saving a custom-placeholder 

We use a modal which gets triggered by clicking the change button of the subtopic. The modal allows us to change the image related to the subtopic and other related details like the copyright information and origin information.

The `updatePlaceholder` method gets triggered when the update button is clicked, which sends a PATCH request to the server and updates all the changes made to the custom-placeholder. If the request is successful we close the model and show a success message to the user using the notify service.

updatePlaceholder() {
  this.get('placeholder').then(placeholder => {
    placeholder.save()
      .then(() => {
        this.set('isOpen', false);
        this.notify.success(this.l10n.t('Placeholder has been saved successfully.'));
      })
      .catch(()=> {
        this.notify.error(this.l10n.t('An unexpected error has occured. Placeholder not saved.'));
      });
  });
}

Thank you for reading the blog, you can check the source code for the example here.

Resources

Continue ReadingImplementing Custom Placeholders in Open Event Front-end

Reactive Side Effects of Actions in Loklak Search

In a Redux based application, every component of the application is state driven. Redux based applications manage state in a predictable way, using a centralized Store, and Reducers to manipulate various aspects of the state. Each reducer controls a specific part of the state and this allows us to write the code which is testable, and state is shared between the components in a stable way, ie. there are no undesired mutations to the state from any components. This undesired mutation of the shared state is prevented by using a set of predefined functions called reducers which are central to the system and updates the state in a predictable way.

These reducers to update the state require some sort triggers to run. This blog post concentrates on these triggers, and how in turn these triggers get chained to form a Reactive Chaining of events which occur in a predictable way, and how this technique is used in latest application structure of Loklak Search. In any state based asynchronous application, like, Loklak Search the main issue with state management is to handle the asynchronous action streams in a predictable manner and to chain asynchronous events one after the other.  The technique of reactive action chaining solves the problem of dealing with asynchronous data streams in a predictable and manageable manner.

Overview

Actions are the triggers for the reducers, each redux action consists of a type and an optional payload. Type of the action is like its ID which should be purposely unique in the application. Each reducer function takes the current state which it controls and action which is dispatched. The reducer decides whether it needs to react to that action or not. If the user reacts to the action, it modifies the state according to the action payload and returns the modified state, else, it returns the original state. So at the core, the actions are like the triggers in the application, which make one or more reducers to work. This is the basic architecture of any redux application. The actions are the triggers and reducers are the state maintainers and modifiers. The only way to modify the state is via a reducer, and a reducer only runs when a corresponding action is dispatched.

Now, who dispatches these actions? This question is very important. The Actions can be technically dispatched from anywhere in the application, from components, from services, from directives, from pipes etc. But we almost in every situation will always want the action to be dispatched by the component. Component who wishes to modify the state dispatch the corresponding actions.

Reactive Effects

If the components are the one who dispatch the action, which triggers a reducer function which modifies the state, then what are these effects, cause the cycle of events seem pretty much complete. The Effects are the Side Effects, of a particular action. The term “side effect” means these are the piece of code which runs whenever an action is dispatched. Don’t confuse them with the reducer functions, effects are not same as the reducer functions as they are not associated with any state i.e. they don’t modify any state. They are the independent sources of other actions. What this means is whenever an Action is dispatched, and we want to dispatch some other action, maybe immediately or asynchronously, we use these side effects. So in a nutshell, effects are the piece of code which reacts to a particular action, and eventually dispatches some other actions.

The most common use case of effects is to call a corresponding service and fetch the data from the server, and then when the data is loaded, dispatch a SearchCompleteAction. These are the simplest of use cases of effects and are most commonly use in Loklak Search. This piece of code below shows how it is done.

@Effect()
search$: Observable<Action>
= this.actions$
.ofType(apiAction.ActionTypes.SEARCH)
.map((action: apiAction.SearchAction) => action.payload)
.switchMap(query => {
return this.apiSearchService.fetchQuery(query)
.map(response => new apiAction.SearchCompleteSuccessAction(response))

This is a very simple type of an effect, it filters out all the actions and react to only the type of action which we are interested in, here SEARCH, and then after calling the respective Service, it either dispatches SearchComplete or a SearchFail action depending on the status of the response from the API. The effect runs on SEARCH action and eventually dispatches, the success or the fail actions.

This scheme illustrates the effect as another point apart from components, to dispatch some action. The difference being, components dispatch action on the basis of the User inputs and events, whereas Effects dispatch actions on the basis of other actions.

Reactive Chaining of Actions

We can thus take the advantage of this approach in a form of reactive chaining of actions. This reactive chaining of actions means that some component dispatches some action, which as a side effects, dispatches some other action, and it dispatches another set of actions and so on. This means a single action dispatched from a component, brings about the series of actions which follow one another. This approach makes it possible to write reducers at the granular level rather than complete state level. As a series of actions can be set up which, start from a fine grain, and reaches out to a coarse grain. The loklak search application uses this technique to update the state of query. The reducers in the loklak search rather than updating the whole query structure update only the required part of the state. This helps in code maintainability as the one type of query attribute has no effect on the other type

@Effect()
inputChange$: Observable<Action>
= this.actions$
.ofType(queryAction.ActionTypes.VALUE_CHANGE)
.map(_ => new queryAction.QueryChangeAction(''));

@Effect()
filterChange$: Observable<Action>
= this.actions$
.ofType(queryAction.ActionTypes.FILTER_CHANGE)
.map(_ => new queryAction.QueryChangeAction(''));

Here the QUERY_CHANGE action further can do other processing of the query and then dispatch the SearchAction, which eventually calls the service and then return the response, then the success or fail actions can be dispatched eventually.

Conclusion

The reactive side effects is one of the most beautiful thing we can do with Redux and Reactive Programming. They provide an easy clean way to chain events in an application, which helps in a cleaner non-overlapping state management along with clean and simple reducers. This idea of the reactive chaining can be extended to any level of sophistication, and that too in a simple and easy to understand manner.

Resources and links

Continue ReadingReactive Side Effects of Actions in Loklak Search

Open Event Server: Forming jsonapi Compatible Error Responses In flask-rest-jsonapi Decorators

From the jsonapi documentation:

Error objects provide additional information about problems encountered while performing an operation. Error objects MUST be returned as an array keyed by errors in the top level of a JSON API document.

To return jsonapi compatible error objects in flask-rest-jsonapi, one must raise an exception and an appropriate error message will be shown depending on the type of exception specified.

Following is an example of how to raise a jsonapi compatible error

try:
               self.session.query(Person).filter_by(id=view_kwargs['id']).one()
           except NoResultFound:
               raise ObjectNotFound({'parameter': 'id'}, "Person: {} not found".format(view_kwargs['id']))

 

But the above method of raising an exception fails when working with decorators in flask-rest-jsonapi. Taking inspiration from the JsonApiException class of flask-rest-jsonapi itself, we’ll be building a custom class which can formulate jsonapi compatible error message and we can just simply return them by using make_response from flask.

In our basic class definition, we’ll define a default title and status code in case none of them is provided by the user. The default status code will be 500. Following is the code for the same:

class ErrorResponse:
   """
   Parent ErrorResponse class for handling json-api compliant errors.
   Inspired by the JsonApiException class of `flask-rest-jsonapi` itself
   """
   title = 'Unknown error'
   status = 500
   headers = {'Content-Type': 'application/vnd.api+json'}


We will be accepting the following four parameters for the initialization of an object of the said class:

  • source: the source of the error in the request document
  • detail: Any details about the error
  • title: Title for the error
  • Status: HTTP status for the error

Following is the initialisation function for the same:

def __init__(self, source, detail, title=None, status=None):
       """Initialize a jsonapi ErrorResponse Object

       :param dict source: the source of the error
       :param str detail: the detail of the error
       """
       self.source = source
       self.detail = detail
       if title is not None:
           self.title = title
       if status is not None:
           self.status = status

 

We’ll be using the jsonapi_errors module to format all of these parameters into jsonapi error objects:

  def respond(self):
       """
       :return: a jsonapi compliant response object
       """
       dict_ = self.to_dict()
       return make_response(json.dumps(jsonapi_errors([dict_])), self.status, self.headers)

 

Related links:

Continue ReadingOpen Event Server: Forming jsonapi Compatible Error Responses In flask-rest-jsonapi Decorators

Setting the Foreground Color Based on the Background Color in Phimpme Android

In the situations where the background color gets changed, the foreground color should also change to some other color. Otherwise the foreground item may not be properly visible on the background.

In Phimpme Android, we came across this situation when dealing with the bottom navigation bar. We have a preference option in our application which enables the user to change the primary color of the application. This change affects the background color of the bottom navigation bar also. We initially had white as selected foreground item’s color and grey as the unselected items’ color. It was fine for darker background colors. But problem arose when we changed the primary color of the application to the lighter shade of any color. The foreground item which was in white color was not clearly visible over the background. So we had to dynamically decide the foreground color when the background color gets changed.

Deciding the foreground color of the bottom navigation bar in Phimpme Android was tough. The foreground item should not only be visible on the background color but it should also be visibly attractive. We needed a minimal look. So we decided to use only two colors – black and white for the selected item’s color. The unselected items’ color can be fixed. A shade of gray with less alpha can be used for it.

The black and white colors are decided based on the lightness of the background color. We used the rgb value of the background color to find the lightness. We fixed a threshold for this lightness by visual judgement and selected the foreground color from the black and white colors based on this threshold lightness. If the lightness of the background color of the bottom navigation color of Phimpme Android application is less than the defined threshold value, we used white as the foreground color and vice-versa.

The lightness can be found out by taking the average of the red, green and blue values of the background color. We considered 100 as the threshold lightness for deciding the foreground color.

void setIconColor(int color){
   if(Color.red(color) + Color.green(color)+ Color.blue(color) < 300)
       colors[0] = Color.WHITE;
   else
       colors[0] = Color.BLACK;
}

For setting the different colors for different states of the foreground items on the bottom navigation bar, we used the setItemIconTintList method which takes ColorStatesList as an argument.

colors[1]  = ContextCompat.getColor(this, R.color.bottom_navigation_tabs);
ColorStateList bottomNavList = new ColorStateList(states, colors);
navigationView.setItemIconTintList(bottomNavList);

This above ColorStateList takes a two dimensional array of states for which we wanted to change the color of item and an array of corresponding colors for those states as arguments. In Phimpme Android we used the following way.

private int[][] states = new int[][] {
       new int[] {android.R.attr.state_checked}, // checked
       new int[] {-android.R.attr.state_checked}, // unchecked
};

private int[] colors = new int[] {
       Color.WHITE, // checked
       0 // unchecked set default in onCreate
};

Resources:

Continue ReadingSetting the Foreground Color Based on the Background Color in Phimpme Android

Full-Screen Speech Input Implementation in SUSI Android App

SUSI Android has some very good features and one of them is, it can take input in speech format from user i.e if the user says anything then it can detect it and convert it to text. This feature is implemented in SUSI Android app using Android’s built-in speech-to-text functionality. You can implement Android’s built-in speech-to-text functionality using either only RecognizerIntent class or SpeechRecognizerIntent class, RecognitionListner interface and RecognizerIntent class. Using former method has some disadvantages:

  • During speech input, it shows a dialog box (as shown here) and it breaks the connection between user and app.
  • We can’t show partial result i.e text to the user but using the later method we can show it.

We used  SpeechRecognizerIntent class, RecognitionListner interface and RecognizerIntent class to implement Android’s built-in speech-to-text functionality in SUSI Android and you know the reason for that. In this blog post, I will show you how I implemented this feature in SUSI Android with new UI.

Layout design

You can give speech input to SUSI Android either by clicking mic button

or using ‘Hi SUSI’ hotword. When you click on mic button or use ‘Hi SUSI’ hotword, you can see a screen where you will give speech input.

Two important part of this layout are:

<TextView

  android:id=“@+id/txtchat”

  android:layout_width=“wrap_content”

  android:layout_height=“wrap_content”

  

 />

  • TextView: It used to show the partial result of speech input i.e it will show converted text (partial) of your speech.
<org.fossasia.susi.ai.speechinputanimation.SpeechProgressView

  android:id=“@+id/speechprogress”

  android:layout_width=“match_parent”

  android:layout_height=“50dp”

  android:layout_margin=“8dp”

  android:layout_gravity=“center”/>

  • SpeechProgressView: It is a custom view which use to show the animation when the user gives speech input. When the user starts speaking, the animation starts. This custom view contains five bars and these five bars animate according to user input.

Full-screen speech input implementation

When the user clicks on mic button or uses ‘Hi SUSI’ hotword, a screen comes where the user can give speech input. As already mentioned I used SpeechRecognizerIntent class, RecognitionListner interface and RecognizerIntent class to implement speech-to-text functionality in SUSI Android. RecognizerIntent class starts an intent and asks for speech input

val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)

intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,

    RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)

intent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, “com.domain.app”)

intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true)

and send it through the speech recognizer. It does it through ACTION_RECOGNIZE_SPEECH. SpeechRecognizer class provides access to the speech recognition service. This service allows access to the speech recognizer and recognition related event occurs RecognitionListner receive notification from SpeechRecognizer class.

recognizer = SpeechRecognizer

      .createSpeechRecognizer(activity.applicationContext)

val listener = object : RecognitionListener {

 //implement all override methods

}

When the user starts speaking, the height of bars changes according to change in sound level. When sound level changes, onRmsChanged method get called where we are calling onRmsChanged method of SpeechProgressView class which is responsible for animating bars according to change in sound level.

override fun onRmsChanged(rmsdB: Float) {

  if (speechprogress != null)

      speechprogress.onRmsChanged(rmsdB)

}

When user finished speaking onEndOfSpeech method get called where we call onEndOfSpeech method of SpeechProgressView class which is responsible for rotating animation. Rotation is used to show that SUSI Android has finished listening and now it is processing your input.

override fun onEndOfSpeech() {

  if (speechprogress != null)

      speechprogress.onEndOfSpeech()

}

In case of any error, onError method get called and in case of successful speech input, onResults method get called. In both cases, we reset bars to their initial position and show chat activity user. The user can again give speech input either by clicking on mic or using ‘Hi SUSI’ hotword.

override fun onResults(results: Bundle) {

  if (speechprogress != null)

      speechprogress.onResultOrOnError()

  activity.supportFragmentManager.popBackStackImmediate()

}

Reference

Continue ReadingFull-Screen Speech Input Implementation in SUSI Android App

Making GUI for SUSI Linux with PyGTK

SUSI Linux app provides access to SUSI on Linux distributions on desktop as well as hardware devices like Raspberry Pi. It started off as a headless client but we decided to add a minimalist GUI to SUSI Linux for performing login and configuring settings. Since, SUSI Linux is a Python App, it was desirable to use a GUI Framework compatible with Python. Many popular GUI frameworks now provide bindings for Python. Some popular available choices are:

wxPython: wxPython is a Python GUI framework based on wxWidgets, a cross-platform GUI library written in C++. In addition to the standard dialogs, it includes a 2D path drawing API, dockable windows, support for many file formats and both text-editing and word-processing widgets. wxPython though mainly support Python 2 as programming language.

PyQT: Qt is a multi-licensed cross-platform framework written in C++. Qt needs a commercial licence for use but if application is completely Open Source, community license can be used. Qt is an excellent choice for GUIs and many applications are based on it.

PyGTK / PyGObject: PyGObject is a Python module that lets you write GUI applications in GTK+. It provides bindings to GObject, a cross platform C library. GTK+ applications are natively supported in most distros and you do not need to install any other development tools for developing with PyGTK.

Comparing all these frameworks, PyGTK was found to meet our needs very well. To make UIs in PyGTK, you have a WYSIWYG (What you see is what you get) editor called Glade. Though you can design whole UI programmatically, it is always convenient to use an editor like Glade to simplify the creation and styling of widgets.

To create a UI, you need to install Glade in your specific distribution. After that open glade, and add a Top Level container Window or AppWindow to your app.

Once that is done, you may pick from the available Layout Managers. We are using BoxLayout Manager in SUSI Linux GUIs. Once that is done, add your widgets to the Application Window using Drag and Drop.

Properties of widgets are available on the right panel. Edit your widget properties to give them meaningful IDs so we can address them later in our code. GTK also provides Signals for signaling about a events associated with the widgets. Open the Signals tab in the Widget properties pane. Then, you need to write name of the signal handler for the events associated with Widgets. A signal handler is a function that is fired upon the occurrence of the associated event. For example, we have signals like text_changed in Text Entry boxes, and clicked for Button.

After completing the design of GUI, we can address the .glade file of the UI we just created in the Python code. We can do this using the following snippet.

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk

builder = Gtk.Builder()
builder.add_from_file("glade_files/signin.glade")

You can reference each widget from the Glade file using its ID like below.

email_field = builder.get_object("email_field")

Now, to handle all the declared signals in the Glade file, we need to make a Handler class. In this class, you need to define call the valid callbacks for your signals. On the occurrence of the signal, respective callback is fired.

class Handler:

   def onDeleteWindow(self, *args):
       Gtk.main_quit(*args)

   def signInButtonClicked(self, *args):
       # implementation

   def input_changed(self, *args):
       # implementation

We may associate a handler function to more than one Signal. For that, we just need to specify the respective function in both the Signals.

Now, we need to connect this Handler to builder signals. This can be done using the following line.

builder.connect_signals(Handler())

Now, we can show our window using the following lines.

window.show_all()
Gtk.main()

The above lines displays the window and start the Gtk main loop. The script waits on the Gtk main loop. The app may be quitted using the Gtk.main_quit() call. Running this script shows the Login Screen of our app like below.

Resources:

Continue ReadingMaking GUI for SUSI Linux with PyGTK

Modifying SUSI Skills using SUSI Skill CMS

SUSI Skill CMS is a complete solution right from creating a skill to modifying the skill. The skills in SUSI are well synced with the remote repository and can be completely modified using the Edit Skill feature of SUSI Skill CMS. Here’s how to Modify a Skill.

  1. Sign Up/Login to the website using your credentials in skills.susi.ai
  2. Choose the SKill which you want to edit and click on the pencil icon.
  3. The following screen allows editing the skill. One can change the Group, Language, Skill Name, Image and the content as well.
  4. After making the changes the commit message can be added to Save the changes.

To achieve the above steps we require the following API Endpoints of the SUSI Server.

  1. http://api.susi.ai/cms/getSkillMetadata.json – This gives us the meta data which populates the various Skill Content, Image, Author etc.
  2. http://api.susi.ai/cms/getAllLanguages.json – This gives us all the languages of a Skill Group.
  3. http://api.susi.ai/cms/getGroups.json – This gives us all the list of Skill Groups whether Knowledge, Entertainment, Smalltalk etc.

Now since we have all the APIs in place we make the following AJAX calls to update the Skill Process.

  1. Since we are detecting changes in all the fields (Group Value, Skill Name, Language Value, Image Value, Commit Message, Content changes and the format of the content), the AJAX call can only be sent when there is a change in the PR and there is no null or undefined value in them. For that, we make various form validations. They are as follows.
    1. We first detect whether the User is in a logged in state.
if (!cookies.get('loggedIn')) {
            notification.open({
                message: 'Not logged In',
                description: 'Please login and then try to create/edit a skill',
                icon: <Icon type="close-circle" style={{ color: '#f44336' }} />,
            });
        }
  1. We check whether the image uploaded matches the format of the Skill image to be stored which is ::image images/imageName.png
if (!new RegExp(/images\/\w+\.\w+/g).test(this.state.imageUrl)) {
            notification.open({
                message: 'Error Processing your Request',
                description: 'image must be in format of images/imageName.jpg',
                icon: <Icon type="close-circle" style={{ color: '#f44336' }} />,
            });
        }
  1. We check if the commit message is not null and notify the user if he forgot to add a message.
if (this.state.commitMessage === null) {
            notification.open({
                message: 'Please make some changes to save the Skill',
                icon: <Icon type="close-circle" style={{ color: '#f44336' }} />,
            });
        }
  1. We also check whether the old values of the skill are completely similar to the new ones, in this case, we do not send the request.
if (toldValues===newValues {
            notification.open({
                message: 'Please make some changes to save the Skill',
                icon: <Icon type="close-circle" style={{ color: '#f44336' }} />,
            });
        }

To check out the complete code, go to this link.

  1. Next, if the above validations are successful, we send a POST request to the server and show the notification to the user accordingly, whether the changes to the Skill Data have been updated or not. Here’s the AJAX call snippet.
// create a form object
let form = new FormData();       
/* Append the following fields from the Skill Component:- OldModel, OldLanguage, OldSkill, NewModel, NewGroup, NewLanguage, NewSkill, changelog, content, imageChanged, old_image_name, new_image_name, image_name_changed, access_token */  
if (image_name_changed) {
            file = this.state.file;
            // append file to image
        }

        let settings = {
            "async": true,
            "crossDomain": true,
            "url": "http://api.susi.ai/cms/modifySkill.json",
            "method": "POST",
            "processData": false,
            "contentType": false,
            "mimeType": "multipart/form-data",
            "data": form
        };
        $.ajax(settings)..done(function (response) {
         //show success
        }.
        .fail(function(response){
         // show failure
        }
  1. To verify all this we head to the commits section of the SUSI Skill Data repo and see the changes we made. The changes can be seen here https://github.com/fossasia/susi_skill_data/commits/master 

Resources

  1. AJAX POST Request – https://api.jquery.com/jquery.post/ 
  2. Material UI – http://material-ui.com 
  3. Notifications – http://www.material-ui.com/#/components/notification 
Continue ReadingModifying SUSI Skills using SUSI Skill CMS