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

Change Password for SUSI Accounts Using Access Token and Email-ID

In this blog, I discuss how the SUSI server synchronizes with SUSI Accounts and SUSI webchat for users to Change Password. When a user logs in, the clients store the email id of the user along with the access token in cookies. These are stored once the client gets a positive login response from the server. Both of these are required at the time of making the final call. Web clients store the email id and access token in the following way.

cookies.set('loggedIn', loggedIn, { path: '/', maxAge: time });
cookies.set('emailId', email, { path: '/', maxAge: time });

First, the client has to ask the user to enter their current password. A javascript test is used to validate that at least 6 characters must be entered by the user. A similar test is run on the new password. But while confirming the password, client checks whether the user has entered the same password as new password or not. These are just the basics. In next stage (which is achieved only when all the above conditions are met), client encodes the email id (which it gets from cookies), current password, new password and the access token (which it again extracts from cookies).

Now, Client just has to make an ajax request to the server. The response is processed and sent back to the client. Let us now look at PasswordChange Servlet.

The base user role is defined as USER. Initial steps of the servlet are to extract the values form the request it receives. The values extracted from the request are in turn used to make a client’s identity. Before that, server checks if current and new password have same values or not. If not, then server returns a JSON response to user stating, “Your current password and new password matches”. Otherwise, it will continue its control flow as it is. Look at the code snippet below:

if(password.equals(newpassword)){
            result.put("message", "Your current password and new password matches");
            result.put("accepted", false);
            return new ServiceResponse(result);
        }

The reader here may think that they have discovered a hack. But they have not. Why? Because this is just the first step. In later stages, the hash of passwords are used to match to see whether the passwords match or not. To obtain a proper client identity, first a Client credentials object is made with support from the email id which is received in ‘changepassword’ attribute. Using the ClientCredentials object made above, an object of Authentication class is made. This object uses a method defined in its class to return a valid client identity. Using the client identity, value of password hash is extracted from the database along with the salt used to hash the password. If any error is encountered while extracting the client’s password hash value and/or salt value, an error is thrown towards the client, with a message stating “invalid credentials”.

ClientCredential pwcredential = new ClientCredential(ClientCredential.Type.passwd_login, useremail);
            Authentication authentication = DAO.getAuthentication(pwcredential);
            ClientCredential emailcred = new ClientCredential(ClientCredential.Type.passwd_login,
                authentication.getIdentity().getName());
            ClientIdentity identity = authentication.getIdentity();
            String passwordHash;
            String salt;

            try {
                passwordHash = authentication.getString("passwordHash");
                salt = authentication.getString("salt");
            } catch (Throwable e) {
                Log.getLog().info("Invalid password try for user: " + identity.getName() + " from host: " + post.getClientHost() + " : password or salt missing in database");
                result.put("message", "invalid credentials");
                throw new APIException(422, "Invalid credentials");
            }

Using the same salt value that was used earlier, a hash for password entered by the user will be generated which now matches  the previous value. This is the point where the hack you were thinking you found, failed. Again the server throws an error message if user’s credential did not match. Passwords are hard to handle and easy to guess. So here we have used quite many tests before changing them. Users are not allowed to use their email id as a password as well.

If the server is clear on all the above facts and tests, It finally generates a new hashed value of the password received in the parameter ‘newpassword’ and replaces the old hash value with the new one. To notify the clients that password change exited with a success response, it sends a JSON object with message “Your password has been changed!” and accepted flag set to true.

if (DAO.hasAuthentication(emailcred)) {
                    Authentication emailauth = DAO.getAuthentication(emailcred);
                    String newsalt = createRandomString(20);
                    emailauth.remove("passwordHash");
                    emailauth.put("passwordHash", getHash(newpassword, salt));
                    Log.getLog().info("password change for user: " + identity.getName() + " via newpassword from host: " + post.getClientHost());
                    result.put("message", "Your password has been changed!");
                    result.put("accepted", true);
                }

 

Additional Resources:

Wikipedia article: What is DAO?

Continue ReadingChange Password for SUSI Accounts Using Access Token and Email-ID

Implementing Skill Listing in SUSI Android App using Nested RecyclerViews

SUSI Skills are rules that are defined in SUSI Skill Data repo which are basically the responses SUSI gives to the user queries. When a user queries something from the SUSI Android app, a query to SUSI Server is made which further fetches response from SUSI Skill Data and gives the response to the app. Similarly, when we need to list all skills, an API call is made to server to list all skills. The server then checks the SUSI Skill Data repo for the skills and then return all the required information to the app. Then the app displays all the information about the skill to user. User then can view details of each skill and then interact on the chat interface to use that skill. This process is similar to what SUSI Skill CMS does. The CMS is a skill wiki like interface to view all skills and then edit them. Though the app can not be currently used to edit the skills but it can be used to view them and try them on the chat interface.

API Information

For listing SUSI Skill groups, we have to call on  /cms/getGroups.json

This will give you all groups in SUSI model in which skills are present. Current response:

{
  "session": {"identity": {
    "type": "host",
    "name": "14.139.194.24",
    "anonymous": true
  }},
  "accepted": true,
  "groups": [
    "Small Talk",
    "Entertainment",
    "Problem Solving",
    "Knowledge",
    "Assistants",
    "Shopping"
  ],
  "message": "Success: Fetched group list"
}

So, the groups object gives all the groups in which SUSI Skills are located.

Next comes, fetching of skills. For that the endpoint is /cms/getGroups.json?group=GROUP_NAME

Since we want all skills to be fetched, we call this api for every group. So, for example we will be calling http://api.susi.ai/cms/getSkillList.json?group=Entertainment for getting all skills in group “Entertainment”. Similarly for other groups as well.

Sample response of skill:

{
  "accepted": true,
  "model": "general",
  "group": "Shopping",
  "language": "en",
  "skills": {"amazon_shopping": {
    "image": "images/amazon_shopping.png",
    "author_url": "https://github.com/meriki",
    "examples": ["Buy a dress"],
    "developer_privacy_policy": null,
    "author": "Y S Ramya",
    "skill_name": "Shop At Amazon",
    "dynamic_content": true,
    "terms_of_use": null,
    "descriptions": "Searches items on Amazon.com for shopping",
    "skill_rating": null
  }},
  "message": "Success: Fetched skill list",
  "session": {"identity": {
    "type": "host",
    "name": "14.139.194.24",
    "anonymous": true
  }}
}

It gives all details about skills:

  1. image
  2. author_url
  3. examples
  4. developer_privacy_policy
  5. author
  6. skill_name
  7. dynamic_content
  8. terms_of_use
  9. descriptions
  10. skill_rating

Implementation in SUSI Android App

Skill Listing UI of Google Assistant

Skill Listing UI of SUSI SKill CMS

Skill Listing UI of SUSI Android App

The UI of skill listing in SUSI Android App is the mixture of UI of Skill listing in Google Assistant ap and SUSI Skill CMS. It displays skills in a beautiful manner with horizontal recyclerview nested in vertical recyclerview.

So, for implementing horizontal recyclerview inside vertical recyclerview, you need two viewholders and two adapters (one each for a recyclerview).

Let’s see the implementation.

1. First task is to fetch the information of groups in which skills are located. This line calls method in SkillListModel which then makes an API call to fetch groups.

skillListingModel.fetchGroups(this)

2. When the API call is succeeded, the below mentioned method is called which then calls a  skillListingModel.fetchSkills(groups[0], this) which fetches the skills located in group[0] group.

override fun onGroupFetchSuccess(response: Response<ListGroupsResponse>) {
   if (response.isSuccessful && response.body() != null) {
       groupsCount = response.body().groups.size
       groups = response.body().groups
       skillListingModel.fetchSkills(groups[0], this)
   } else {
       skillListingView?.visibilityProgressBar(false)
       skillListingView?.displayErrorDialog()
   }
}

3. When API call for fetching skills in group[0] succeeds, the count value is increased and then skills in group[1] are fetched and so on.

override fun onSkillFetchSuccess(response: Response<ListSkillsResponse>, group: String) {
   if (response.isSuccessful && response.body() != null) {
       skills.add(Pair(group, response.body().skillMap))
       count++
       if(count == groupsCount) {
           skillListingView?.visibilityProgressBar(false)
           skillListingView?.updateAdapter(skills)
       } else {
           skillListingModel.fetchSkills(groups[count], this)
       }
   } else {
       skillListingView?.visibilityProgressBar(false)
       skillListingView?.displayErrorDialog()
   }
}

4. When skills in all groups are fetched, the data in adapter is updated using skillGroupAdapter.notifyDataSetChanged()

override fun updateAdapter(skills: ArrayList<Pair<String, Map<String, SkillData>>>) {
   this.skills.clear()
   this.skills.addAll(skills)
   skillGroupAdapter.notifyDataSetChanged()
}

5. The data is set to the layout in two adapters made earlier. The following is the code to set the group name and adapter to horizontal recyclerview. This is the GroupAdapter to set data to row item in vertical recyclerview.

override fun onBindViewHolder(holder: GroupViewHolder?, position: Int) {
   if(skills[position].first != null)
       holder?.groupName?.text = skills[position].first
   holder?.skillList?.setHasFixedSize(true)
   val mLayoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
   holder?.skillList?.layoutManager = mLayoutManager
   holder?.skillList?.adapter = SkillListAdapter(context, skills[position])
}

6. Similarly, the data of each individual element in the horizontal recyclerview is set in the skillAdapter. The data set are title, examples, description and image. We have used Picasso library to load images from the URL.

override fun onBindViewHolder(holder: SkillViewHolder?, position: Int) {
   val skillData = skillDetails.second.values.toTypedArray()[position]

   if(skillData.skillName == null || skillData.skillName.isEmpty()){
       holder?.skillPreviewTitle?.text = context.getString(R.string.no_skill_name)
   } else {
       holder?.skillPreviewTitle?.text = skillData.skillName
   }

   if( skillData.descriptions == null || skillData.descriptions.isEmpty()){
       holder?.skillPreviewDescription?.text = context.getString(R.string.no_skill_description)
   } else {
       holder?.skillPreviewDescription?.text = skillData.descriptions
   }

   if(skillData.examples == null || skillData.examples.isEmpty())
       holder?.skillPreviewExample?.text = StringBuilder("\"").append("\"")
   else
       holder?.skillPreviewExample?.text = StringBuilder("\"").append(skillData.examples[0]).append("\"")

   if(skillData.image == null || skillData.image.isEmpty()){
       holder?.previewImageView?.setImageResource(R.drawable.ic_susi)
   } else {
       Picasso.with(context.applicationContext).load(StringBuilder(imageLink)
               .append(skillDetails.first.replace(" ","%20")).append("/en/").append(skillData.image).toString())
               .fit().centerCrop()
               .into(holder?.previewImageView)
   }
}

Summary

So, this blog talked about how the Skill Listing feature in SUSI Android App is implemented. This included how a network call is made, logic for making different network calls, making a nested horizontal recyclerview inside vertical recyclerview, 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 nested recyclerviews similar to Google Play Store.

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. To see how to implement Horizontal recyclerView inside Vertical recyclerView http://android-pratap.blogspot.in/2015/12/horizontal-recyclerview-in-vertical.html
  5. To see how to implement custom RecyclerView Adapter https://www.survivingwithandroid.com/2016/09/android-recyclerview-tutorial.html
Continue ReadingImplementing Skill Listing in SUSI Android App using Nested RecyclerViews

Displaying Proper Notification While Image is Being Uploaded in Phimpme

In this blog, I will explain how to display App Icon and appropriate text messages in the notification bar while the image is being uploaded on the various social media platform in Phimpme Android application.

Displaying Application icon in the Notification Bar

Whenever Phimpme application uses the notification the application icon should be present during the progress bar is showing and after the image has been uploaded to the social network website.

In the notification bar there are two types of the icon that can be set:

  • Small icon
  • Large Icon

In the small icon, we are putting the upload sign to tell the users that the image is being uploaded.

In the large icon, I am putting the logo of the Phimpme application. This way when the user exits the application while there is an upload process going on, he or she will know that the upload process is from the Phimpme application.

To set the app icon in the Notification bar:

.setLargeIcon(BitmapFactory.decodeResource(ActivitySwitchHelper.getContext().getResources(),
       R.mipmap.ic_launcher))

To set the small icon in the Notification bar:

.setSmallIcon(R.drawable.ic_cloud_upload_black_24dp)

Displaying appropriate account name while uploading the Image

While uploading an Image the notification bar should show the appropriate Account Name in which the account is being uploaded. For example, if the image is being uploaded on Google Plus then the Notification should display “Uploading the image on Google Plus”.

For this, we need to modify the NotificationHandler.make() function and make it accept String resource as a parameter. We can then modify setContentTitle() function to display the appropriate message.

public static void make(@StringRes int title){

//Display Notification code over here

}

setContentTitle() function in Phimpme to display the appropriate function:

mBuilder.setContentTitle(ActivitySwitchHelper.getContext().getString(R.string.upload_progress) + " " + ActivitySwitchHelper.getContext().getResources().getString(title))

Notification make() function after the changes:

public static void make(@StringRes int title){
   mNotifyManager = (NotificationManager) ActivitySwitchHelper.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
   mBuilder = new NotificationCompat.Builder(ActivitySwitchHelper.getContext());
   mBuilder.setContentTitle(ActivitySwitchHelper.getContext().getString(R.string.upload_progress) + " " + ActivitySwitchHelper.getContext().getResources().getString(title))
           .setLargeIcon(BitmapFactory.decodeResource(ActivitySwitchHelper.getContext().getResources(),
                   R.mipmap.ic_launcher))
           .setContentText(ActivitySwitchHelper.getContext().getString(R.string.progress))
           .setSmallIcon(R.drawable.ic_cloud_upload_black_24dp)
           .setOngoing(true);
   mBuilder.setProgress(0, 0, true);
   // Issues the notification
   mNotifyManager.notify(id, mBuilder.build());
}

Conclusion

Notification makes any application more interactive and show live updates even when the application is in use. By following this method users can be aware of the upload functionality and in which account the image is beign uploaded.

Github

Resources

 

Continue ReadingDisplaying Proper Notification While Image is Being Uploaded in Phimpme

Implementing Roles API on Open Event Frontend to Create Roles Using an External Modal

This blog article will illustrate how the roles are created via the external model  on the admin permissions page in Open Event Frontend, using the roles API. Our discussion primarily will involve the admin/permissions/index route to illustrate the process.The primary end point of Open Event API with which we are concerned with for fetching the permissions  for a user is

POST /v1/roles

First we need to create a model for the user-permissions, which will have the fields corresponding to the api, so we proceed with the ember CLI command:

ember g model role

Next we define the model according to the requirements. The model needs to extend the base model class, and has only two fields one for the title and one for the actual name of the role.

import attr from 'ember-data/attr';
import ModelBase from 'open-event-frontend/models/base';

export default ModelBase.extend({
 name           : attr('string'),
 titleName      : attr('string')
 });

Next we need to modify the existing modal to incorporate the API and creation of roles in it. It is very important to note here that using createRecord as the model will result in a major flaw. If createRecord is used and the user tries to create multiple roles, other than the first POST request all the subsequent requests will be PATCH requests and will keep on modifying the same role. To avoid this, a new record needs to be created every time the user clicks on Add Role.  We slightly modify the modal component call to pass in the name and titleName to it.

{{modals/add-system-role-modal  isOpen=isAddSystemRoleModalOpen
                                isLoading=isLoading
                                name=name
                                titleName=titleName
                                addSystemRole=(action 'addSystemRole')}}

Upon entering the details of the roles and successful validation of the form, if the user clicks the Add Role button of the modal, the action addSystemRole will be triggered. We will write the entire logic for the same in the respective controller of the route.

addSystemRole() {
     this.set('isLoading', true);
     this.get('store').createRecord('role', {
       name      : this.get('name'),
       titleName : this.get('titleName')
     }).save()
       .then(() => {
         this.set('isLoading', false);
         this.notify.success(this.l10n.t('User permissions have 
         been saved successfully.'));
         this.set('isAddSystemRoleModalOpen', false);
         this.setProperties({
           name          : null,
           roleTitleName : null
         });
       })
       .catch(()=> {
         this.set('isLoading', false);
         this.notify.error(this.l10n.t('An unexpected error has occurred.
         User permissions not saved.'));
       });
   },

At first the isLoading property is made true.This adds the semantic UI class loading to the the form,  and so the form goes in the loading state, Next, a record is created of the type role  and it’s properties are made equal to the corresponding values entered by the user.

Then save() is called, which subsequently makes a POST request to the server. If the request is successful the modal is closed by setting the isAddSystemRoleModalOpen property to false. Also, the fields of the modal are cleared for a  better user experience in case multiple roles need to be added one after the other.

In cases when  there is an error during the processing of the request the catch() block executes. And the modal is not closed. Neither are the fields cleared.

Resources

Continue ReadingImplementing Roles API on Open Event Frontend to Create Roles Using an External Modal

Displaying essential features when the Phimpme Application starts

In this blog, I will explain how I implemented showcase View to display all the essential features of the Phimpme Android application when the application starts first. In this, the users will know which activity is used for what purpose.  

Importing material design Showcase View

I used material design showcase in Phimpme  Android application to take the benefit of the latest Android design and to add more text on the screen which is easily visible by the users. We need to add the following to our gradle.

compile 'com.github.deano2390:MaterialShowcaseView:1.1.0'

There is a very good repository in Github for material design Showcase view which we have used here.

Implementing Material design showcaseView on the desired activity

In Phimpme Android application we have three main activity on the home screen. Namely:

  • Camera
  • Gallery
  • Accounts Activity

Camera Activity and Gallery Activity is used to take pictures and select the picture respectively. Accounts Activity contains more than 10 accounts of various social media platforms and storage platform. When the application starts my aim is to display the function of all three activities in a showcase View. it is implemented in the following Steps:

Step 1

Import all the module from the Material showcase view we have used in the gradle.

import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence;
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView;
import uk.co.deanwild.materialshowcaseview.ShowcaseConfig;

Step 2

Get the reference all the buttons you want to show the features. These buttons will be highlighted when the showcase View is displayed.

nav_home = (BottomNavigationItemView) findViewById(R.id.navigation_home);
nav_cam = (BottomNavigationItemView) findViewById(R.id.navigation_camera);
nav_acc = (BottomNavigationItemView) findViewById(R.id.navigation_accounts);

Step 3

In Phimpme Android application I have used to display the features of three navigation buttons. So, to display the features of more than one button or View we have to use MaterialShowcaseSequence class to display the features of the buttons in a sequence, that is one after the other.

In the onCreate activity, I am calling presentShowcaseSequence function. This function has a delay time. This delay time is required to wait for four seconds until the splash screen activity is over and the activity is started.

private void presentShowcaseSequence() {
   ShowcaseConfig config = new ShowcaseConfig();
   config.setDelay(4000); // half second between each showcase view
   MaterialShowcaseSequence sequence = new MaterialShowcaseSequence(this, SHOWCASE_ID);
   sequence.setOnItemShownListener(new MaterialShowcaseSequence.OnSequenceItemShownListener() {
       @Override
       public void onShow(MaterialShowcaseView itemView, int position) {
       }
   });
   sequence.setConfig(config);

Step 4

Set the initial target Button. This target button will be pointed first when the app is launched for the first time.

sequence.addSequenceItem(nav_home, getResources().getString(R.string.home_button), getResources().getString(R.string.ok_button));

 

Step 5

Add subsequent target buttons to display the features of that buttons. To add more subsequent target buttons we will use function addSequenceitem. To set the target we have specify the button which we want to focus in setTarget(Button). We need to display the text which will show the important features in regarding that activity in setContentText(text to be displayed). For a dismiss button we need to specify the a string in setDismissText(Dismiss button string)

sequence.addSequenceItem(
       new MaterialShowcaseView.Builder(this)
               .setTarget(nav_cam)
               .setDismissText(getResources().getString(R.string.ok_button))
               .setContentText(getResources().getString(R.string.camera_button))
               .build()
);
sequence.addSequenceItem(
       new MaterialShowcaseView.Builder(this)
               .setTarget(nav_acc)
               .setDismissText(getResources().getString(R.string.ok_button))
               .setContentText(getResources().getString(R.string.accounts_button))
               .build()
);
sequence.start();

Sequence.start is used to display the showcase.

Conclusion

Using this method users can easily have the knowledge of the functionality of the application and can navigate through the activities without wasting a lot of time in figuring out the functionality of the application.

Github

Resources

 

Continue ReadingDisplaying essential features when the Phimpme Application starts

Implementing Feedback Feature in SUSI Android App

Recently, on SUSI Server, a new servlet was added which is used to rate SUSI Skills either positive or negative. The server stores the rating of a particular skill in a JSON file. These ratings help in improving answers provided by SUSI. So, the server part is done and it was required to implement this in the SUSI Android App. In this blog, I will cover the topic of implementation of the Rating or Feedback feature in SUSI Android App. This will including all the cases when feedback should be sent, when it should not be sent, when to send positive feedback, when to send negative feedback, etc.

API Information

For rating a SUSI Skill, we have to call on  /cms/rateSkill.json providing 5 parameters which are:

  1. model: The model of SUSI Skill. (String)
  2. group: The Group under the model in which that particular skill resides. (String)
  3. language: The language of skill. (String)
  4. skill: This is skill name. (String)
  5. rating: This can be two strings, either “positive” or “negative”. String)

Basically, in the SUSI Skill Data repo (in which all the skills are stored), models, groups language etc are part of folder structure.

So, if a skill is located here

https://github.com/fossasia/susi_skill_data/blob/master/models/general/Knowledge/en/news.txt

This would mean

model = general

group = Knowledge

language = en

skill = news

rating = positive/negative

Implementation in SUSI Android App

    

So, when the like button on a particular skill is clicked, a positive call is made and when the dislike button is clicked, a negative call is made.

Let’s see example when the thumbs up button or like button is clicked.

There can be three cases possible:

  1. None of Like button or dislike button is clicked already: In this case, initially, both like and dislike button will be transparent/hollow. So, when like button is clicked, the like button will be colored blue and a call will be made with positive feedback.
  2. Like button is already clicked: In this case, like button is already clicked. So, it will already be blue. So, when user clicks again on positive button, it should get back to normal/hollow indicating rating which was sent is cancelled and a a call will be made with negative feedback thus cancelling or neutralizing the earlier, positive feedback.
  3. Dislike button is already clicked: In this case, the dislike button is already blue, indicating a negative call is already made. So, now when the like button is clicked, we need to cancel the earlier negative feedback call and sending another negative feedback call. Thus, sending two negative feedback calls. And after that coloring dislike button as blue.

Look at the code below. It is self explanatory. There are three if-else conditions covering all the above mentioned three cases.

thumbsUp.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       thumbsUp.setImageResource(R.drawable.thumbs_up_solid);
       if(!model.isPositiveRated() && !model.isNegativeRated()) {
           rateSusiSkill(Constant.POSITIVE, model.getSkillLocation(), context);
           setRating(true, true);
       } else if(!model.isPositiveRated() && model.isNegativeRated()) {
           setRating(false, false);
           thumbsDown.setImageResource(R.drawable.thumbs_down_outline);
           rateSusiSkill(Constant.POSITIVE, model.getSkillLocation(), context);
           sleep(500);
           rateSusiSkill(Constant.POSITIVE, model.getSkillLocation(), context);
           setRating(true, true);
       } else if (model.isPositiveRated() && !model.isNegativeRated()) {
           rateSusiSkill(Constant.NEGATIVE, model.getSkillLocation(), context);
           setRating(false, true);
           thumbsUp.setImageResource(R.drawable.thumbs_up_outline);
       }
   }
});

Similarly for when dislike button is clicked, the above three mentioned cases still hold resulting in this code snippet.

thumbsDown.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       thumbsDown.setImageResource(R.drawable.thumbs_down_solid);
       if(!model.isPositiveRated() && !model.isNegativeRated()) {
           rateSusiSkill(Constant.NEGATIVE, model.getSkillLocation(), context);
           setRating(true, false);
       } else if(model.isPositiveRated() && !model.isNegativeRated()) {
           setRating(false, true);
           thumbsUp.setImageResource(R.drawable.thumbs_up_outline);
           rateSusiSkill(Constant.NEGATIVE, model.getSkillLocation(), context);
           sleep(500);
           rateSusiSkill(Constant.NEGATIVE, model.getSkillLocation(), context);
           setRating(true, false);
       } else if (!model.isPositiveRated() && model.isNegativeRated()) {
           rateSusiSkill(Constant.POSITIVE, model.getSkillLocation(), context);
           setRating(false, false);
           thumbsDown.setImageResource(R.drawable.thumbs_down_outline);
       }
   }
});

Summary

So, this blog talked about how the Feedback feature in SUSI Android App is implemented. This included how a network call is made, logic for sending positive/negative feedback, logic to withdraw feedback 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 rating mechanism in social media websites like Facebook, Twitter, Quora, Reddit, etc work.

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. To see how to implement click listeners on button https://developer.android.com/reference/android/view/View.OnClickListener.html
Continue ReadingImplementing Feedback Feature in SUSI Android App

Implementing Users API to Display the Users at Admin Route in Open Event Frontend

This article will illustrate how the users are displayed and updated on the /admin/users route, their roles, user links etc. using the users API in Open Event Frontend. The primary end point of Open Event API with which we are concerned with for fetching the users is

GET /v1/users

First, we need to create a model for the user, which will have the fields corresponding to the API, so we proceed with the ember CLI command:

ember g model user

Next, we need to define the model according to the requirements. The model needs to extend the base model class. As a user can have multiple notifications, orders and  sessions etc. so we have to use ember data relationships “hasMany”. Hence, the model will have the following format.

import ModelBase from 'open-event-frontend/models/base';
import { hasMany } from 'ember-data/relationships';

export default ModelBase.extend({
  email        : attr('string'),
  password     : attr('string'),
  isVerified   : attr('boolean', { readOnly: true }),
  isSuperAdmin : attr('boolean', { readOnly: true }),
  isAdmin      : attr('boolean', { readOnly: true }),
  firstName : attr('string'),
  lastName  : attr('string')
});

The complete code for the model can be seen here

Now, we need to load the data from the API using the above model, so we will send a GET request to the API to fetch the users. This can be easily achieved using this.

return this.get('store').query('user', {'page[size]': 10 });

The above line is querying for the users from the store which is place where cache of all of the records that have been loaded by our application is there. If a route asks for a record, the store can return it immediately if it is there in the cache and we want to display only 10 users in a page so defined how many number of users has to be loaded at a time.

Now we need to filter the users based on whether they are active or they have deleted their accounts. For this purpose, we need to pass filter to the query which will tell what type of users to be loaded at once.

The next thing we need to do is to display the above data fetched from the API into an ember table. Ember table helps in allowing us to render very large data sets by only rendering the rows that are being displayed. For this, we defined a controller class which will help in letting the table know what all columns will be required to display and the attribute values they correspond in the API. We can also define the template for each column. The code for the controller class looks like this.

import Ember from 'ember';

const { Controller } = Ember;
export default Controller.extend({
  columns: [
    {
      propertyName     : 'first-name',
      title            : 'Name',
      disableSorting   : true,
      disableFiltering : true
    },
    {
      propertyName     : 'email',
      title            : 'Email',
      disableSorting   : true,
      disableFiltering : true
     },
     {
      propertyName     : 'last-accessed-at',
      title            : 'Last Accessed',
      template         : 'components/ui-table/cell/admin/users/cell-last-accessed-at',
      disableSorting   : true,
      disableFiltering : true
    }
   ]
});

In the above code, we can see a field called ‘disableSorting’ which is true if we don’t want to sort the table based on that column. Since we want the last-accessed-at column to be customized, so we have separately added a template for the column which will ensure how it will look in the column. The complete code for the other columns which are there in table apart from these can be found here.

Now to display the ember table we will write the following code.

{{events/events-table columns=columns data=model
    useNumericPagination=true
    showGlobalFilter=true
    showPageSize=true
}}

In the above piece of code, we are calling the same ember table as we used in case of events to reduce the code duplication. We are passing the columns and data in the table which remains unique to the table. Next, we are ensuring that our page shows the amount of data we’re fetching at one go, allows the filtering the table based on the columns.

The UI of the users page for the above code snippets look like this.

Fig 1: The UI of the users table under admin/users route

The entire code for implementing the users API can be seen here.

To conclude, this is how we efficiently fetched the users using the Open-Event-Orga users API, ensuring that there is no unnecessary API call to fetch the data and no code duplication using the same ember table again.

Resources:

Continue ReadingImplementing Users API to Display the Users at Admin Route in Open Event Frontend

Implementing Sessions API for the event in Open Event Frontend

This article will illustrate how the sessions are displayed and updated on the events/{event_id}/sessions route to display the sessions available for a particular event using the sessions API in Open Event Frontend. The primary end point of Open Event API with which we are concerned with for fetching the sessions is

GET /v1/sessions/{session_id}

First, we need to create a model for the sessions, which will have the fields corresponding to the API, so we proceed with the ember CLI command:

ember g model session

Next, we need to define the model according to the requirements. The model needs to extend the base model class. As a session can have multiple speakers and a session always belongs to an event, so we have to use ember data relationships “hasMany” and “belongsTo”. Hence, the model will have the following format.

import ModelBase from 'open-event-frontend/models/base';
import { belongsTo, hasMany } from 'ember-data/relationships';

export default ModelBase.extend({
  title         : attr('string'),
  subtitle      : attr('string'),

  speakers      : hasMany('speaker'),
  event         : belongsTo('event')
});

Complete code for the model can be seen here

Now, we need to load the data from the API using the above model, so we will send a GET request to the API to fetch the sessions corresponding to a particular event. This can be easily achieved using this.

return this.modelFor('events.view').query('sessions');

The above line is asking for getting the current model that is on the route events.view and query for the sessions property from that model.

Now we need to filter the sessions based on their sessions whether they have been accepted or confirmed or pending or rejected and display them on different pages. For this purpose, we need to pass filter and pages to the query which will tell what type and now of sessions to be loaded at once. Also, we need to display the speakers associated with session and event details. For this case, the above query will be formatted like this.

return this.modelFor('events.view').query('sessions', {
      include      : 'event,speakers',
    filter       : filterOptions,
      'page[size]' : 10
    });  

In the above query, the filterOptions are designed in such a way which check for what type of sessions user is querying for. The code can be found here.

The next thing we need to do is to display the above data fetched from the API into an ember table. For this, we need to have a controller class which will help in letting the table know what all columns will be required to display and the attribute values they correspond in the API. We can also define the template for each column. The code for the controller class looks like this.

export default Controller.extend({
  columns: [
    {
      propertyName   : 'state',
      title          : 'State',
      disableSorting : true,
      template       : 'components/ui-table/cell/events/view/sessions/cell-session-state'
    },
    {
      propertyName : 'title',
      title          : 'Title'
    },
    {
      propertyName    : 'speakers',
      template       : 'components/ui-table/cell/cell-speakers',
      title          : 'Speakers',
      disableSorting  : true
     }]
});

In the above code, we can see a field called ‘disableSorting’ which is true if we don’t want to sort the table based on that column. Since we want the state column to be customized, so we have separately added a template for the column which will ensure how it will look in the column. The complete code for the other columns which are there in table apart from the state, title and speakers can be found here.

Now to display the ember table we will write the following code.

{{events/events-table columns=columns data=model
    useNumericPagination=true
    showGlobalFilter=true
    showPageSize=true
}}
I

In the above piece of code, we are calling the same ember table as we used in case of events to reduce the code duplication. We are passing the columns and data in the table which remains unique to the table. Next, we are ensuring that our page shows the amount of data we’re fetching at one go, allows the filtering the table based on the columns.

The UI of the sessions page for the above code snippets look like this.

Fig 1: The UI of the session table under events/{event_id}/session route

The entire code for implementing the sessions API can be seen here.

To conclude, this is how we efficiently fetched the sessions details using the Open-Event-Orga sessions API, ensuring that there is no unnecessary API call to fetch the data and no code duplication using the same ember table again.

Resources:

Continue ReadingImplementing Sessions API for the event in Open Event Frontend