Implementing Notifications in Open Event Server

In FOSSASIA’s Open Event Server project, along with emails, almost all actions have necessary user notifications as well. So, when a new session is created or a session is accepted by the event organisers, along with the email, a user notification is also sent. Though showing the user notification is mainly implemented in the frontend site but the content to be shown and on which action to show is strictly decided by the server project.

A notification essentially helps an user to get the necessary information while staying in the platform itself and not needing to go to check his/her email for every action he performs. So unlike email which acts as a backup for the informations, notification is more of an instant thing.

The API

The Notifications API is mostly like all other JSON API endpoints in the open event project. However in Notifications API we do not allow any to send a POST request. The admin of the server is able to send a GET a request to view all the notifications that are there in the system while a user can only view his/her notification. As of PATCH we allow only the user to edit his/her notification to mark it as read or not read. Following is the schema for the API:

class NotificationSchema(Schema):
    """
    API Schema for Notification Model
    """

    class Meta:
        """
        Meta class for Notification API schema
        """
        type_ = 'notification'
        self_view = 'v1.notification_detail'
        self_view_kwargs = {'id': '<id>'}
        self_view_many = 'v1.microlocation_list_post'
        inflect = dasherize

    id = fields.Str(dump_only=True)
    title = fields.Str(allow_none=True, dump_only=True)
    message = fields.Str(allow_none=True, dump_only=True)
    received_at = fields.DateTime(dump_only=True)
    accept = fields.Str(allow_none=True, dump_only=True)
    is_read = fields.Boolean()
    user = Relationship(attribute='user',
                        self_view='v1.notification_user',
                        self_view_kwargs={'id': '<id>'},
                        related_view='v1.user_detail',
                        related_view_kwargs={'notification_id': '<id>'},
                        schema='UserSchema',
                        type_='user'
                        )


The main things that are shown in the notification from the frontend are the
title and message. The title is the text that is shown without expanding the entire notification that gives an overview about the message in case you don’t want to read the entire message. The message however provides the entire detail that is associated with the action performed. The user relationship stores which user the particular notification is related with. It is a one-to-one relationship where one notification can only belong to one user. However one user can have multiple notifications. Another important attribute is the is_read attribute. This is the only attribute that is allowed to be changed. By default, when we make an entry in the database, is_read is set to FALSE. Once an user has read the notification, a request is sent from the frontend to change is_read to TRUE.

The different actions for which we send notification are stored in the models/notification.py file as global variables.

USER_CHANGE_EMAIL = "User email"'
NEW_SESSION = 'New Session Proposal'
PASSWORD_CHANGE = 'Change Password'
EVENT_ROLE = 'Event Role Invitation'
TICKET_PURCHASED = 'Ticket(s) Purchased'
TICKET_PURCHASED_ATTENDEE = 'Ticket(s) purchased to Attendee    '
EVENT_EXPORTED = 'Event Exported'
EVENT_EXPORT_FAIL = 'Event Export Failed'
EVENT_IMPORTED = 'Event Imported'

HTML Templates

The notification title and message that is stored in the database and later served via the Notification API is created using some string formatting HTML templates. We firstly import all the global variables that represent the various actions from the notification model. Then we declare a global dict type variable named NOTIFS which stores all title and messages to be stored in the notification table.

NEW_SESSION: {
        'title': u'New session proposal for {event_name}',
        'message': u"""The event <strong>{event_name}</strong> has received
             a new session proposal.<br><br>
            <a href='{link}' class='btn btn-info btn-sm'>View Session</a>""",
        'recipient': 'Organizer',
    },


This is an example of the contents stored inside the dict. For every action, there is a dict with attributes
title, message and recipient. Title contains the brief overview of the entire notification whereas message contains a more vivid description with proper links. Recipient contains the one who receives the notification. So for example in the above code snippet, it is a notification for a new session created. The notification goes to the organizer. The title and message contains named placeholders which are later replaced by particular values using python’s .format() function.

Notification Helper

Notification helper module contains two main parts :-

  1. A parent function which saves the notification to the table related to the user to whom the notification belongs.
  2. Individual notification helper functions that are used by the APIs to save notification after various actions are performed.

Parent Function

def send_notification(user, action, title, message):
    if not current_app.config['TESTING']:
        notification = Notification(user_id=user.id,
                                    title=title,
                                    message=message,
                                    action=action
                                    )
        save_to_db(notification, msg="Notification saved")
        record_activity('notification_event', user=user, action=action, title=title)


send_notification
() is the parent function which takes as parameters user, action, title and message and stores them in the notification table in the database. The user is the one to whom the notification belongs to, action represents the particular action in an API which triggered the notification. Title and message are the contents that are shown in the frontend in the form of a notification. The frontend can implement it as a dropdown notification like facebook or a desktop notification like gitter or whatsapp. After the notification is saved we also update the activity table with the action that a notification has been saved for a user with the following action and title. Later, the Notification API mentioned in the very beginning of the blog uses this data that is being stored now and serves it as a JSON response.

Individual Functions

Apart from this, we have individual functions that uses the parent function to store notifications particular to a particular actions. For example, we have a send_notif_new_session_organizer() function which is used to save notification for all the organizers of an event that a new session has been added to their particular event. This function is called when a POST request is made in the Sessions API and the data is saved successfully. The function is executed for all the organizers of the event for which the session has been created.

def send_notif_new_session_organizer(user, event_name, link):
    message_settings = MessageSettings.query.filter_by(action=NEW_SESSION).first()
    if not message_settings or message_settings.notification_status == 1:
        notif = NOTIFS[NEW_SESSION]
        action = NEW_SESSION
        title = notif['title'].format(event_name=event_name)
        message = notif['message'].format(event_name=event_name, link=link)

        send_notification(user, action, title, message)


In the above function, we take in 3 parameters, user, event_name and link. The value of the user parameter is used to link the notification to that particular user. Event_name and link are used in the title and message of the notification that is saved in the database. Firstly in the function we check if there is certain message setting which tells that the user doesn’t want to receive notifications related to new sessions being created. If not, we proceed. We get the title and message strings from the NOTIFS dict from the system_notification.py file.

After that, using string formatting we get the actual message. For example,

u'New session proposal for {event_name}'.format(‘FOSSASIA’)

would give us a resulting string of the form:

u'New session proposal for FOSSASIA'

After this, we use this variables and send them as parameters to the send_notification() parent function to save the notification properly.

 

Reference:

Continue ReadingImplementing Notifications in Open Event Server

Implement Email in Open Event Server

In FOSSASIA’s Open Event Server project, we send out emails when various different actions are performed using the API. For example, when a new user is created, he/she receives an email welcoming him to the server as well as an email verification email. Users get role invites from event organisers in the form of emails, when someone buys a ticket he/she gets a PDF link to the ticket as email. So as you can understand all the important informations that are necessary to be notified to the user are sent as an email to the user and sometimes to the organizer as well.

In FOSSASIA, we use sendgrid’s API or an SMTP server depending on the admin settings for sending emails. You can read more about how we use sendgrid’s API to send emails in FOSSASIA here. Now let’s dive into the modules that we have for sending the emails. The three main parts in the entire email sending are:

  1. Model – Storing the Various Actions
  2. Templates – Storing the HTML templates for the emails
  3. Email Functions – Individual functions for various different actions

Let’s go through each of these modules one by one.

Model

USER_REGISTER = 'User Registration'
USER_CONFIRM = 'User Confirmation'
USER_CHANGE_EMAIL = "User email"
INVITE_PAPERS = 'Invitation For Papers'
NEXT_EVENT = 'Next Event'
NEW_SESSION = 'New Session Proposal'
PASSWORD_RESET = 'Reset Password'
PASSWORD_CHANGE = 'Change Password'
EVENT_ROLE = 'Event Role Invitation'
SESSION_ACCEPT_REJECT = 'Session Accept or Reject'
SESSION_SCHEDULE = 'Session Schedule Change'
EVENT_PUBLISH = 'Event Published'
AFTER_EVENT = 'After Event'
USER_REGISTER_WITH_PASSWORD = 'User Registration during Payment'
TICKET_PURCHASED = 'Ticket(s) Purchased'


In the Model file, named as
mail.py, we firstly declare the various different actions for which we send the emails out. These actions are globally used as the keys in the other modules of the email sending service. Here, we define global variables with the name of the action as strings in them. These are all constant variables, which means that there value remains throughout and never changes. For example, USER_REGISTER has the value ‘User Registration’, which essentially means that anything related to the USER_REGISTER key is executed when the User Registration action occurs. Or in other words, whenever an user registers into the system by signing up or creating a new user through the API, he/she receives the corresponding emails.
Apart from this, we have the model class which defines a table in the database. We use this model class to store the actions performed while sending emails in the database. So we store the action, the time at which the email was sent, the recipient and the sender. That way we have a record about all the emails that were sent out via our server.

class Mail(db.Model):
    __tablename__ = 'mails'
    id = db.Column(db.Integer, primary_key=True)
    recipient = db.Column(db.String)
    time = db.Column(db.DateTime(timezone=True))
    action = db.Column(db.String)
    subject = db.Column(db.String)
    message = db.Column(db.String)

    def __init__(self, recipient=None, time=None, action=None, subject=None,
                 message=None):
        self.recipient = recipient
        self.time = time
        if self.time is None:
            self.time = datetime.now(pytz.utc)
        self.action = action
        self.subject = subject
        self.message = message

    def __repr__(self):
        return '<Mail %r to %r>' % (self.id, self.recipient)

    def __str__(self):
        return unicode(self).encode('utf-8')

    def __unicode__(self):
        return 'Mail %r by %r' % (self.id, self.recipient,)


The table name in which all the information is stored is named as mails. It stores the recipient, the time at which the email is sent (timezone aware), the action which initiated the email sending, the subject of the email and the entire html body of the email. In case a datetime value is sent, we use that, else we use the current time in the time field.

HTML Templates

We store the html templates in the form of key value pairs in a file called system_mails.py inside the helpers module of the API. Inside the system_mails, we have a global dict variable named MAILS as shown below.

MAILS = {
    EVENT_PUBLISH: {
        'recipient': 'Organizer, Speaker',
        'subject': u'{event_name} is Live',
        'message': (
            u"Hi {email}<br/>" +
            u"Event, {event_name}, is up and running and ready for action. Go ahead and check it out." +
            u"<br/> Visit this link to view it: {link}"
        )
    },
    INVITE_PAPERS: {
        'recipient': 'Speaker',
        'subject': u'Invitation to Submit Papers for {event_name}',
        'message': (
            u"Hi {email}<br/>" +
            u"You are invited to submit papers for event: {event_name}" +
            u"<br/> Visit this link to fill up details: {link}"
        )
    },
    SESSION_ACCEPT_REJECT: {
        'recipient': 'Speaker',
        'subject': u'Session {session_name} has been {acceptance}',
        'message': (
            u"Hi {email},<br/>" +
            u"The session <strong>{session_name}</strong> has been <strong>{acceptance}</strong> by the organizer. " +
            u"<br/> Visit this link to view the session: {link}"
        )
    },
    SESSION_SCHEDULE: {
        'recipient': 'Organizer, Speaker',
        'subject': u'Schedule for Session {session_name} has been changed',
        'message': (
            u"Hi {email},<br/>" +
            u"The schedule for session <strong>{session_name}</strong> has been changed. " +
            u"<br/> Visit this link to view the session: {link}"
        )
    },


Inside the MAILS dict, we have key-value pairs, where in keys we use the global variables from the Model to define the action related to the email template. In the value, we again have 3 different key-value pairs – recipient, subject and message. The recipient defines the group who should receive this email, the subject goes into the subject part of the email while message forms the body for the email. For subject and message we use unicode strings with named placeholders that are used later for formatting using python’s
.format() function.

Email Functions

This is the most important part of the entire email sending system since this is the place where the entire email sending functionality is implemented using the above two modules. We have all these functions inside a single file namely mail.py inside the helpers module of the API. Firstly, we import two things in this file – The global dict variable MAILS defined in the template file above, and the various global action variables defined in the model. There is one main module which is used by every other individual modules for sending the emails defined as send_email(to, action, subject, html). This function takes as parameters the email to which the email is to be sent, the subject string, the html body string along with the action to store it in the database.

Firstly we ensure that the email address for the recipient is present and isn’t an empty string. After we have ensured this, we retrieve the email service as set in the admin settings. It can either be “smtp” or “sendgrid”. The email address for the sender has different formatting depending on the email service we are using. While sendgrid uses just the email say for example “medomag20@gmail.com”, smtp uses a format  a little different like this: Medozonuo Suohu<medomag20@gmail.com>. So we set that as well in the email_from variable.

def send_email(to, action, subject, html):
    """
    Sends email and records it in DB
    """
    if not string_empty(to):
        email_service = get_settings()['email_service']
        email_from_name = get_settings()['email_from_name']
        if email_service == 'smtp':
            email_from = email_from_name + '<' + get_settings()['email_from'] + '>'
        else:
            email_from = get_settings()['email_from']
        payload = {
            'to': to,
            'from': email_from,
            'subject': subject,
            'html': html
        }

        if not current_app.config['TESTING']:
            if email_service == 'smtp':
                smtp_encryption = get_settings()['smtp_encryption']
                if smtp_encryption == 'tls':
                    smtp_encryption = 'required'
                elif smtp_encryption == 'ssl':
                    smtp_encryption = 'ssl'
                elif smtp_encryption == 'tls_optional':
                    smtp_encryption = 'optional'
                else:
                    smtp_encryption = 'none'

                config = {
                    'host': get_settings()['smtp_host'],
                    'username': get_settings()['smtp_username'],
                    'password': get_settings()['smtp_password'],
                    'encryption': smtp_encryption,
                    'port': get_settings()['smtp_port'],
                }

                from tasks import send_mail_via_smtp_task
                send_mail_via_smtp_task.delay(config, payload)


After this we create the payload containing the email address for the recipient, the email address of the sender, the subject of the email and the html body of the email.
For unittesting and any other testing we avoid email sending since that is really not required in the flow. So we check that the current app is not configured to run in a testing environment. After that we have two different implementation depending on the email service used.

SMTP

There are 3 kind of possible encryptions for the email that can be used with smtp server – tls, ssl and optional. We determine this based on the admin settings again. Also, from the admin settings we collect the host, username, password and port for the smtp server.

After this we start a celery task for sending the email. Since email sending to a number of clients can be time consuming so we do it using the celery queueing service without disturbing the main workflow of the entire system.

@celery.task(name='send.email.post.smtp')
def send_mail_via_smtp_task(config, payload):
    mailer_config = {
        'transport': {
            'use': 'smtp',
            'host': config['host'],
            'username': config['username'],
            'password': config['password'],
            'tls': config['encryption'],
            'port': config['port']
        }
    }

    mailer = Mailer(mailer_config)
    mailer.start()
    message = Message(author=payload['from'], to=payload['to'])
    message.subject = payload['subject']
    message.plain = strip_tags(payload['html'])
    message.rich = payload['html']
    mailer.send(message)
    mailer.stop()

Inside the celery task, we use the Mailer and Message classes from the marrow module of python. We configure the Mailer according to the various settings received from the admin and then use the payload to send the email.

Sendgrid

For sending email using the sendgrid API, we need to set the Bearer key which is used for authenticating the email service. This key is also defined in the admin settings. After we have set the Bearer key as the authorization header, we again initiate the celery task corresponding to the sendgrid email sending service.

@celery.task(name='send.email.post')
def send_email_task(payload, headers):
    requests.post(
        "https://api.sendgrid.com/api/mail.send.json",
        data=payload,
        headers=headers
    )


For sending the email service, all we need to do is make a POST request to the api endpoint “
https://api.sendgrid.com/api/mail.send.json” with the headers which contains the Bearer Key and the data which contains the payload containing all the information related to the recipient, sender, subject of email and the body of the email.

Apart from these, this module implements all the individual functions that are called based on the various functions that occur. For example, let’s look into the email sending function in case a new session is created.

def send_email_new_session(email, event_name, link):
    """email for new session"""
    send_email(
        to=email,
        action=NEW_SESSION,
        subject=MAILS[NEW_SESSION]['subject'].format(
            event_name=event_name
        ),
        html=MAILS[NEW_SESSION]['message'].format(
            email=email,
            event_name=event_name,
            link=link
        )
    )


This function is called inside the Sessions API, for every speaker of the session as well as for every organizer of the event to which the session is submitted. Inside this function, we use the
send_email().  But firstly we need to create the subject of the email and the message body of the email using the templates and by replacing placeholders by actual value using python formatting. MAILS[NEW_SESSION] returns a unicode string: u’New session proposal for {event_name}’ . So what we do is use the .format() function to replace {event_name} by the actual event_name received as parameter. So it is equivalent to doing something like:

u'New session proposal for {event_name}'.format(‘FOSSASIA’)

which would give us a resulting string of the form:

u'New session proposal for FOSSASIA'

Similarly, we create the html message body using the templates and the parameters received. After this is done, we make a function call to send_email()  which then sends the final email.

References:

Continue ReadingImplement Email in Open Event Server

Implementation of Set Different Language for Query in SUSI Android

SUSI.AI has many skills. Some of which are displaying web search of a certain query, provide a list of relevant information of a topic, displaying a map of the certain position and simple text message of any query. Previously SUSI.AI reply all query in English language but now one important feature is added in SUSI.AI and that feature is, reply query of the user in the language that user wants. But to get the reply in different language user has to send language code of that language along with query to SUSI Server. In this blog post, I will show you how it is implemented in SUSI Android app.

Different language supported in SUSI Android

Currently, different languages added in option in SUSI Android and their language code are:

Language Language Code
English en
German de
Spanish es
French fr
Italian it
Default Default language of the device.

Layout design

I added an option for choosing the different language in settings. When the user clicks on Language option a dialog box pops up. I used listpreference to show the list of different languages.

<ListPreference

  android:title=“@string/Language”

  android:key=“Lang_Select”

  android:entries=“@array/languagentries”

  android:entryValues=“@array/languagentry”>

</ListPreference>

“title” is used to show the title of setting, “entries” is used to show the list of entry to the user and “entryValue” is the value corresponding to each entry. I used listpreference because it has own UI so we don‘t have to develop our own UI for it and also it stores the string into the SharedPreferences so we don’t need to manage the values in SharedPreference. SharedPreference needed to set value in Language in settings so that once user close app and again open it setting will show same value otherwise it will show default value. We used an array of string to show the list of languages.

<string-array name=“languagentries”>

  <item>Default</item>

  <item>English</item>

  <item>German</item>

  <item>Spanish</item>

  <item>French</item>

  <item>Italian</item>

</string-array>

SetLanguage implementation

To set language user must choose Language option in setting.

On clicking Language option a dialog box pop up with the list of different languages. When the user chooses a language then we save corresponding language code in preference with key “prefLanguage” so that we can use it later to send it to the server along with the query. It also uses to send language to the server to store user language on the server, so that user can use the same language in the different client.

querylanguage.setOnPreferenceChangeListener { _, newValue ->

  PrefManager.putString(Constant.LANGUAGE, newValue.toString())

  if(!settingsPresenter.getAnonymity()) {

      settingsPresenter.sendSetting(Constant.LANGUAGE, newValue.toString(), 1)

  }

}

newValue.toString() is the value i.e language code of corresponding language.

Now when we query anything from SUSI.AI we send language code along with query to the server. Default language is default language of the device. Before sending language to the server we check language is default language or any specific language.

val language = if (PrefManager.getString(Constant.LANGUAGE, Constant.DEFAULT).equals(Constant.DEFAULT))

Locale.getDefault().language

else PrefManager.getString(Constant.LANGUAGE, Constant.DEFAULT)

And after that, we send the corresponding language along with query to the server.

clientBuilder.susiApi.getSusiResponse(timezoneOffset, longitude, latitude, source, language, query)

Reference

Continue ReadingImplementation of Set Different Language for Query in SUSI Android

Using Vector Images in SUSI Android

Android designed to run across many devices with different screen sizes and display resolutions. One of the things that confuse many new Android developers is how to support multiple screen sizes. For making  SUSI Android app more user-friendly and interactive we used a lot of images in the form of drawable resources. Most of these drawables are in the form of PNG (Portable Network Graphic) and to support multiple screen size we have to include separate images of varied dimensions because we can’t scale PNG images without losing quality.

Other disadvantages of using PNG images are

  • PNG images take large disk space.
  • PNG images have fixed colour and dimensions which cannot be changed.

To overcome these shortcomings of PNG image we decided to use vector images in SUSI Android.

Advantages of using vector image are

  • It is smaller in size as compared to PNG i.e it takes less disk space than PNG.
  • It can be scaled without losing quality. So we need to include only a single image for all screen size.

Disadvantages of using vector image are

  • Reconstruction of vector data may take considerably longer than that contained in a bitmap file of equivalent complexity because each image element must be drawn individually and in sequence.
  • Vector image can’t be used to display complex photographs.
  • If the object consists of a large number of small elements then file size grow very fast.

Vector produce smaller size file than bitmap only for simple stuff. When it comes to creating a photographic quality vector where colour information is paramount and may vary on a pixel-by-pixel basis, the file can be much larger than its bitmap version, that’s why vector image is only suitable for small images like logo designs, icons etc.

In this blog post, I will show you how we used vector images in SUSI Android.

How to create vector image using Asset Studio tool

Asset Studio is inbuilt Android Studio tool which we use to create a vector image from default material icon or local SVG images. To use vector asset in Android Studio

  • Go to File> New > Vector Asset
  • Vector Asset Studio Tool Window is prompted

  • Create vector image from default material icon or local SVG image.

Asset Studio will add icon in drawable. It can be used anywhere in the project.

Vector images in SUSI Android

In SUSI Android all drawable images like search, mic, arrow, check etc are vector images,  even the logo of the app is a vector image.

This vector image is the result of code you can find here.

Now I will describe some special attributes used here

  • fillColor: It defines the colour of the image. So the colour of your image depends on the colour you provide in fillColor. We used white color(#ffffff) as fillColor that’s why the image is white in colour.
  • strokeColour: It defines the colour of boundary of the image.
  • pathData: Shape of your image depends on pathData. Different commands used in pathData are
  1. M: It is used to define the starting point. For example, M10,10 means starting     Coordinate of the image will be (10,10). M used for absolute position and m   for relative position.
  2. L: L means to draw a line from the current position to given position. For example, M10,10 L20,20 means a straight line will be drawn from coordinate (10,10) to coordinate (20,20. L Use for absolute position and l for relative position.
  3. C: C means to draw an arc. For example, Cx1,x2 x3,x4 x5,x6 means draw a curve  (cubic Bezier curve) from coordinate  (x1,x2) using (x3,x4) as control point at the beginning and (x5,x6) as control point at the end.
  4. Z: Z means to close the path using the straight line between the current position and starting position.

    Example

Let’s consider a curve

M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z

It starts from point (9,16.17). From (9,16.17) to (4.83,12) a straight line will be drawn. Now again a straight will be drawn from point (4.83,12) to the relative point (-1.42,1.41). After that from point (-1.42, 1.41) it will draw a polyline like an upward arrow(^) with the end point at (21,7). Again it will draw a straight line between point (21,7) and relative point (-1.41, -1.41). At the last z will close curve using the straight line between current point and starting point).

Resources

Tags

 

Continue ReadingUsing Vector Images in SUSI Android

Shift from Java to Kotlin in SUSI Android

Previously SUSI Android was written in JAVA. But recently Google announced that it will officially support Kotlin on Android as a first class language so we decided to shift from Java to Kotlin. Kotlin runs on Java Virtual Machine and hence we can use Kotlin for Android app development. Kotlin is new programming language developed by JetBrains, which also developed IntelliJ-the IDE that Android Studio is based on. So Android Studio has excellent support for Kotlin.

Advantages of Kotlin over Java

  • Kotlin is a null safe language. It changes all the instances used in the code to non nullable type thus it ensures that the developers don’t get any nullPointerException.
  • Good Android Studio support. Kotlin tool is included with Android Studio 3.0 by default. But for Android Studio 2.x we need to install the required plugin for Kotlin.
  • Kotlin also provides support for Lambda function and other high order functions.
  • One of Kotlin’s greatest strengths as a potential alternative to Java is interoperability between Java and Kotlin. You can even have Java and Kotlin code existing side by side in the same project.
  • Kotlin provides the way to declare Extension function similar to that of C# and Gosu. We can use this function in the same way as we use the member functions of our class.

After seeing the above points it is now clear that Kotlin is much more effective than Java. In this blog post, I will show you how ForgotPasswordActivity is shifted from JAVA to Kotlin in SUSI Android.

How to install Kotlin plugin in Android Studio 2.x

We are using Android Studio 2.3.0 in our android project. To use Kotlin in Android Studio 2.x we need to install Kotlin plugin.

Steps to include Kotlin plugin in Android Studio 2.x are:

  • Go to File
  • After that select Settings
  • Then select Plugin Option from the left sidebar.
  • After that click on Browse repositories.
  • Search for Kotlin and install it.

Once it is installed successfully, restart Android Studio. Now configure Kotlin in Android Studio.

Shift  SUSI Android from JAVA to Kotlin

Kotlin is compatible with JAVA. Thus it allows us to change the code bit by bit instead of all at once. In SUSI Android app we implemented MVP architecture with Kotlin. We converted the code by one activity each time from JAVA to Kotlin. I converted the ForgotPassword of SUSI Android from JAVA to Kotlin with MVP architecture. I will discuss only shifting of SUSI Android from JAVA to Kotlin and not about MVP architecture implementation.

First thing is how to extend parent class. In JAVA we need to use extend keyword

public class ForgotPasswordActivity extends AppCompatActivity

but in Kotlin parent class is extended like

class ForgotPasswordActivity : AppCompatActivity ()

Second is no need to bind view in Kotlin. In JAVA we bind view using Butter Knife.

@BindView(R.id.forgot_email)

protected TextInputLayout email;

and

email.setError(getString(R.string.email_invalid_title));

but in Kotlin we can directly use view using id.

forgot_email.error = getString(R.string.email_invalid_title)

Another important thing is that instead of using setError and getError we can use only error for both purposes.

forgot_email.error

It can be used to get error in TextInputLayout ‘forgot_email’ and

forgot_email.error = getString(R.string.email_invalid_title)

can be use to set error in TextInputLayout ‘forgot_email’.

Third one is in Kotlin we don’t need to define variable type always

val notSuccessAlertboxHelper = AlertboxHelper(this@ForgotPasswordActivity, title, message, null, null, button, null, color)

but in JAVA we need to define it always.

AlertboxHelper notSuccessAlertboxHelper = new AlertboxHelper(ForgotPasswordActivity.this, title, message, null, null, positiveButtonText, null, colour);

And one of the most important feature of Kotlin null safety. So you don’t need to worry about NullPointerException. In java we need to check for null otherwise our app crash

if (url.getEditText() == null)

      throw new IllegalArgumentException(“No Edittext hosted!”);

But in Kotlin there is no need to check for null. You need to use ’?’ and Kotlin will handle it itself.  

input_url.editText?.text.toString()

You can find the previous ForgotPasswordActivity here. You can compare it with new ForgotPaswordActivity.

Reference

Continue ReadingShift from Java to Kotlin in SUSI Android

Burst Camera Mode in Phimpme Android

Camera is an integral part of core feature in Phimpme Android. Various features were added in the camera part such as resolution, timer, shutter sound, white balance etc. Click burst shot from camera is also an important feature to be added. Burst shot is clicking multiple pictures in one go.

Adding a Burst mode in Phimpme Camera

  • Adding burst mode enable entry in options

The popup view in Camera is added programmatically in app. Setting up the values from sharedpreferences. It takes the value and set burst mode off, 1x, 2x etc. according to value.

final String[] burst_mode_values = getResources().getStringArray(R.array.preference_burst_mode_values);
  String[] burst_mode_entries = getResources().getStringArray(R.array.preference_burst_mode_entries);
String burst_mode_value = sharedPreferences.getString(PreferenceKeys.getBurstModePreferenceKey(), "1");

Two methods created for setting up the previous and next values. To set up the previous value we need to check the current value to be not equal to -1 and greater that zero. Upgrade or downgrade the value of burst mode, according to the click.

public int onClickPrev() {
         if( burst_mode_index != -1 && burst_mode_index > 0 ) {
            burst_mode_index--;
            update(); ...
}

public int onClickNext() {
            if( burst_mode_index != -1 && burst_mode_index < burst_mode_values.length-1 ) {
              burst_mode_index++;
            update();...
}
  • Saving the value in sharedpreferences

So on clicking the previous and next, the value of burst mode value will be updated. As shown in the above code snippet, after every increment and decrement the values set on view and called update method to update the value in the sharedpreference as shown below.

private void update() {
        String new_burst_mode_value = burst_mode_values[burst_mode_index];
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(main_activity);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(PreferenceKeys.getBurstModePreferenceKey(), new_burst_mode_value);
editor.apply();}

  • Taking multiple Images

Now in the implementation part, we need to continuously click the image according to the burst value set by the user. So to enable this, first check the value not to be negative and should be greater than zero. Whole iteration work on separate variable named remaining burst photos. The value of the variable decrease after every image click i.e. takePhoto method calls.

if( remaining_burst_photos == -1 || remaining_burst_photos > 0 ) {
  if( remaining_burst_photos > 0 )
     remaining_burst_photos--;
  long timer_delay = applicationInterface.getRepeatIntervalPref();
  if( timer_delay == 0 ) {
     phase = PHASE_TAKING_PHOTO;
     takePhoto(true);
  }
  else {
     takePictureOnTimer(timer_delay, true);
  }
}

Resources:

 

Continue ReadingBurst Camera Mode in Phimpme Android

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

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

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