Uploaded Images History in Phimpme Android

In Phimpme Android one core feature is of sharing images to many different platforms. After sharing we usually wants to look in the our past records, where we uploaded what pictures? Which image we uploaded? What time it was? So I added a feature to view the upload history of images. User can go to the Upload history tab, present in the navigation drawer of the app. From there he can browse the repository.

How I added history feature in Phimpme

  • Store the data when User initiate an upload

To get which data uploading is in progress. I am storing its name, date, time and image path. When user approve to upload image from Sharing Activity.

Created a database model

public class UploadHistoryRealmModel extends RealmObject{

   String name;
   String pathname;
   String datetime;

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public String getPathname() {
       return pathname;
   }

   public void setPathname(String pathname) {
       this.pathname = pathname;
   }

   public String getDatetime() {
       return datetime;
   }

   public void setDatetime(String datetime) {
       this.datetime = datetime;
   }
} 

This is the realm model for storing the name, date, time and image path.

Saving in database

UploadHistoryRealmModel uploadHistory;
uploadHistory = realm.createObject(UploadHistoryRealmModel.class);
uploadHistory.setName(sharableAccountsList.get(position).toString());
uploadHistory.setPathname(saveFilePath);
uploadHistory.setDatetime(new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new Date()));
realm.commitTransaction();

Creating realm object and setting the details in begin and commit Transaction block

  • Added upload history entry in Navigation Drawer

    <LinearLayout
       xmlns:android="http://schemas.android.com/apk/res/android"
       android:id="@+id/ll_drawer_uploadhistory"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:background="@drawable/ripple"
       android:clickable="true"
       android:orientation="horizontal">
    
       <com.mikepenz.iconics.view.IconicsImageView
           android:id="@+id/Drawer_Upload_Icon"
           android:layout_width="@dimen/icon_width_height"
           android:layout_height="@dimen/icon_width_height"
           app:iiv_icon="gmd-file-upload"/>
    
       <TextView
           android:id="@+id/Drawer_Upload_Item"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@string/upload_history"
           android:textColor="@color/md_dark_background"
           android:textSize="16sp"/>
    </LinearLayout>

It consist of an ImageView and TextView in a horizontal oriented Linear Layout

  • Showing history in Upload History Activity

Added recyclerview in layout.

<android.support.v7.widget.RecyclerView
   android:id="@+id/upload_history_recycler_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:layout_below="@id/toolbar">
</android.support.v7.widget.RecyclerView>

Query the database and updated the adapter of Upload History

uploadResults = realm.where(UploadHistoryRealmModel.class);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
uploadHistoryRecyclerView.setLayoutManager(layoutManager);
uploadHistoryRecyclerView.setAdapter(uploadHistoryAdapter);

Added the adapter for recycler view and created an Item using Constraint layout.

Resources

Continue ReadingUploaded Images History in Phimpme Android

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

Implementation of Child Routes in SUSI Skill CMS

In a previous blog post I discussed about how we implemented routing in SUSI Web Chat Application. In this post I’m planning to discuss about how we developed child routes in SUSI Skill CMS .

When we start developing our application, it was working correctly but  all skills loaded in the same URL. ( skill.susi.ai/SkillPage ). When user clicks the edit button every skill loaded in the same URL ( skill.susi.ai/EditSkill ). We got a requirement to load each of our skills in separate routes. This is how we implemented the child routes of the application.

We wanted to show each individual skill under this type of URL,

skill.susi.ai/ [SKILL GROUP] / [SKILL NAME] / [LANGUAGE]

When user clicks on the edit button, we needed to show that particular skill under this URL.

skill.susi.ai/ [SKILL GROUP] / [SKILL NAME] / edit / [LANGUAGE]

First we set our routings in index.js file.

<Switch>
    <Route exact path="/:category/:skill/edit/:lang" component={Home} />
    <Route exact path="/:category/:skill/:lang" component={SkillListing}/>
    <Route exact path="/" component={BrowseSkill} />
    <Route exact path="*" component={NotFound} />
</Switch>

We have to add the “exact” attribute, if we don’t add that it will not redirect users to “404” page when user trying to access wrong routes.

Next step is sending data from one component to another component.
In SUSI Skill CMS, user can choose any skill from the home page. Then after it goes to the skill page and shows details about the selected skill. We have to modify the button as,

<Link to={{ pathname: '/'+self.state.groupValue+'/'+el+'/'+self.state.languageValue }} >
<Card>
</Card>
</Link>

Now the user clicks on the card. It changes the URL and loads the corresponding component according to the routes that we defined in “index.js” file previously.
Second thing that we need to do is to catch URL routs and render relevant data according to the URL routes.
Let’s say I clicked on “distance” skill. Then user will go to this URL “http://skills.susi.ai/Knowledge/distance/en ”
Now It loads the “SkillListing” component according to the route we defined in “ index.js ” here ””.
To derive data from URL we simply used these codes in “SkillListing.js”.

let baseUrl = 'http://api.susi.ai/cms/getSkillMetadata.json';           
let modelValue = "general";
           this.name = this.props.location.pathname.split('/')[2];
           this.groupValue = this.props.location.pathname.split('/')[1];
           this.languageValue = this.props.location.pathname.split('/')[3];
           url = baseUrl + '?model=' + modelValue + '&group=' + this.groupValue + '&language=' + this.languageValue + '&skill=' + this.name;

We collected data from the URL and made another URL, we used this URL to get details of the skill from the server. We used this urls as below.

           $.ajax({
               url: url,
               jsonpCallback: 'pc',
               dataType: 'jsonp',
               jsonp: 'callback',
               crossDomain: true,
               success: function (data) {
                   self.updateData(data.skill_metadata)
               }
           });

If the Ajax request is success, those data are passed to “updateData()” and it updates the component and shows to users like this.

We applied same mechanism to the edit button and edit page. This is how we modified skill.susi.ai ‘s Routings. If you like to contribute SUSI Skill CMS please fork our repository on github. here

Resources:

  • Previous Blogpost about routing: https://blog.fossasia.org/implementation-of-react-routers-in-susi-web-chat/
  • React Router v4 tutorial https://medium.com/@pshrmn/a-simple-react-router-v4-tutorial-7f23ff27adf
Continue ReadingImplementation of Child Routes in SUSI Skill CMS

Enabling Google App Signing for Android Project

Signing key management of Android Apps is a hectic procedure and can grow out of hand rather quickly for large organizations with several independent projects. We, at FOSSASIA also had to face similar difficulties in management of individual keys by project maintainers and wanted to gather all these Android Projects under singular key management platform:

To handle the complexities and security aspect of the process, this year Google announced App Signing optional program where Google takes your existing key’s encrypted file and stores it on their servers and asks you to create a new upload key which will be used to sign further updates of the app. It takes the certificates of your new upload key and maps it to the managed private key. Now, whenever there is a new upload of the app, it’s signing certificate is matched with the upload key certificate and after verification, the app is signed by the original private key on the server itself and delivered to the user. The advantage comes where you lose your key, its password or it is compromised. Before App Signing program, if your key got lost, you had to launch your app under a new package name, losing your existing user base. With Google managing your key, if you lose your upload key, then the account owner can request Google to reassign a new upload key as the private key is secure on their servers.

There is no difference in the delivered app from the previous one as it is still finally signed by the original private key as it was before, except that Google also optimizes the app by splitting it into multiple APKs according to hardware, demographic and other factors, resulting in a much smaller app! This blog will take you through the steps in how to enable the program for existing and new apps. A bit of a warning though, for security reasons, opting in the program is permanent and once you do it, it is not possible to back out, so think it through before committing.

For existing apps:

First you need to go to the particular app’s detail section and then into Release Management > App Releases. There you would see the Get Started button for App Signing.

The account owner must first agree to its terms and conditions and once it’s done, a page like this will be presented with information about app signing infrastructure at top.

So, as per the instructions, download the PEPK jar file to encrypt your private key. For this process, you need to have your existing private key and its alias and password. It is fine if you don’t know the key password but store password is needed to generate the encrypted file. Then execute this command in the terminal as written in Step 2 of your Play console:

java -jar pepk.jar –keystore={{keystore_path}} –alias={{alias}} –output={{encrypted_file_output_path}} –encryptionkey=eb10fe8f7c7c9df715022017b00c6471f8ba8170b13049a11e6c09ffe3056a104a3bbe4ac5a955f4ba4fe93fc8cef27558a3eb9d2a529a2092761fb833b656cd48b9de6a

You will have to change the bold text inside curly braces to the correct keystore path, alias and the output file path you want respectively.

Note: The encryption key has been same for me for 3 different Play Store accounts, but might be different for you. So please confirm in Play console first

When you execute the command, it will ask you for the keystore password, and once you enter it, the encrypted file will be generated on the path you specified. You can upload it using the button on console.

After this, you’ll need to generate a new upload key. You can do this using several methods listed here, but for demonstration we’ll be using command line to do so:

keytool -genkey -v -keystore {{keystore_path}} -alias {{alias_name}} -keyalg RSA -keysize 2048 -validity 10000

The command will ask you a couple of questions related to the passwords and signing information and then the key will be generated. This will be your public key and be used for further signing of your apps. So keep it and the password secure and handy (even if it is expendable now).

After this step, you need to create a PEM upload certificate for this key, and in order to do so, execute this command:

keytool -export -rfc -keystore {{keystore_path}} -alias {{alias_name}} -file {{upload_certificate.pem}}

After this is executed, it’ll ask you the keystore password, and once you enter it, the PEM file will be generated and you will have to upload it to the Play console.

If everything goes right, your Play console will look something like this:

 

Click enrol and you’re done! Now you can go to App Signing section of the Release Management console and see your app signing and new upload key certificates

 

You can use the SHA1 hash to confirm the keys as to which one corresponds to private and upload if ever in confusion.

For new apps:

For new apps, the process is like a walk in park. You just need to enable the App Signing, and you’ll get an option to continue, opt-out or re-use existing key.

 

If you re-use existing key, the process is finished then and there and an existing key is deployed as the upload key for this app. But if you choose to Continue, then App Signing will be enabled and Google will use an arbitrary key as private key for the app and the first app you upload will get its key registered as the upload key

 

This is the screenshot of the App Signing console when there is no first app uploaded and you can see that it still has an app signing certificate of a key which you did not upload or have access to.

If you want to know more about app signing program, check out these links:

Continue ReadingEnabling Google App Signing for Android Project

Introducing Stream Servlet in loklak Server

A major part of my GSoC proposal was adding stream API to loklak server. In a previous blog post, I discussed the addition of Mosquitto as a message broker for MQTT streaming. After testing this service for a few days and some minor improvements, I was in a position to expose the stream to outside users using a simple API.

In this blog post, I will be discussing the addition of /api/stream.json endpoint to loklak server.

HTTP Server-Sent Events

Server-sent events (SSE) is a technology where a browser receives automatic updates from a server via HTTP connection. The Server-Sent Events EventSource API is standardized as part of HTML5 by the W3C.

Wikipedia

This API is supported by all major browsers except Microsoft Edge. For loklak, the plan was to use this event system to send messages, as they arrive, to the connected users. Apart from browser support, EventSource API can also be used with many other technologies too.

Jetty Eventsource Plugin

For Java, we can use Jetty’s EventSource plugin to send events to clients. It is similar to other Jetty servlets when it comes to processing the arguments, handling requests, etc. But it provides a simple interface to send events as they occur to connected users.

Adding Dependency

To use this plugin, we can add the following line to Gradle dependencies –

compile group: 'org.eclipse.jetty', name: 'jetty-eventsource-servlet', version: '1.0.0'

[SOURCE]

The Event Source

An EventSource is the object which is required for EventSourceServlet to send events. All the logics for emitting events needs to be defined in the related class. To link a servlet with an EventSource, we need to override the newEventSource method –

public class StreamServlet extends EventSourceServlet {
    @Override
    protected EventSource newEventSource(HttpServletRequest request) {
        String channel = request.getParameter("channel");
        if (channel == null) {
            return null;
        }
        if (channel.isEmpty()) {
            return null;
        }
        return new MqttEventSource(channel);
    }
}

[SOURCE]

If no channel is provided, the EventSource object will be null and the request will be rejected. Here, the MqttEventSource would be used to handle the stream of Tweets as they arrive from the Mosquitto message broker.

Cross Site Requests

Since the requests to this endpoint can’t be of JSONP type, it is necessary to allow cross site requests on this endpoint. This can be done by overriding the doGet method of the servlet –

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     response.setHeader("Access-Control-Allow-Origin", "*");
    super.doGet(request, response);
}

[SOURCE]

Adding MQTT Subscriber

When a request for events arrives, the constructor to MqttEventSource is called. At this stage, we need to connect to the stream from Mosquitto for the channel. To achieve this, we can set the class as MqttCallback using appropriate client configurations –

public class MqttEventSource implements MqttCallback {
    ...
    MqttEventSource(String channel) {
        this.channel = channel;
    }
    ...
    this.mqttClient = new MqttClient(address, "loklak_server_subscriber");
    this.mqttClient.connect();
    this.mqttClient.setCallback(this);
    this.mqttClient.subscribe(this.channel);
    ...
}

[SOURCE]

By setting the callback to this, we can override the messageArrived method to handle the arrival of a new message on the channel. Just to mention, the client library used here is Eclipse Paho.

Connecting MQTT Stream to SSE Stream

Now that we have subscribed to the channel we wish to send events from, we can use the Emitter to send events from our EventSource by implementing it –

public class MqttEventSource implements EventSource, MqttCallback {
    private Emitter emitter;


    @Override
    public void onOpen(Emitter emitter) throws IOException {
        this.emitter = emitter;
        ...
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) throws Exception {
        this.emitter.data(message.toString());
    }
}

[SOURCE]

Closing Stream on Disconnecting from User

When a client disconnects from the stream, it doesn’t makes sense to stay connected to the server. We can use the onClose method to disconnect the subscriber from the MQTT broker –

@Override
public void onClose() {
    try {
        this.mqttClient.close();
        this.mqttClient.disconnect();
    } catch (MqttException e) {
        // Log some warning 
    }
}

[SOURCE]

Conclusion

In this blog post, I discussed connecting the MQTT stream to SSE stream using Jetty’s EventSource plugin. Once in place, this event system would save us from making too many requests to collect and visualize data. The possibilities of applications of such feature are huge.

This feature can be seen in action at the World Mood Tracker app.

The changes were introduced in pull request loklak/loklak_server#1474 by @singhpratyush (me).

Resources

Continue ReadingIntroducing Stream Servlet in loklak Server

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 Skill Listing in SUSI iOS

Skills are basically a set of rules which respond to the user’s queries through any client app. All the skills are defined in the SUSI Skill Data repo where the user’s queries are matched with the already present skills and the server responds accordingly. Apps like Alexa, Google Assistant have an interface to view skills with a set of sample queries that can be used therefore, we are adding the same skill display UI in the SUSI iOS app.

Implementation

All the skills are arranged into categories or groups, so we first need to fetch all those groups followed by fetching skills from each group. For listing all the groups, we use the endpoint below:

http://api.susi.ai/cms/getGroups.json

Which returns all the groups in the groups object like below:

{
  "session": {"identity": {
    "type": "host",
    "name": "67.214.191.117",
    "anonymous": true
  }},
  "accepted": true,
  "groups": [
    "Social",
    "News",
    "Food and Drink",
    "Travel and Transportation",
    "Connected Car",
    "Movies and TV",
    "Problem Solving",
    "Knowledge",
    "Business and Finance",
    "Productivity",
    "Games, Trivia and Accessories",
    "Lifestyle",
    "Health and Fitness",
    "Music and Audio",
    "Shopping",
    "Communication",
    "Novelty and Humour",
    "Utilities",
    "Sports", 
    "Weather"
  ],
  "message": "Success: Fetched group list"
}

After the groups have been fetched, we need to get the skills for each group. Here, we use the endpoint below:

http://api.susi.ai/cms/getSkillList.json?group=GROUP_NAME

Since, we have a number of groups, we need to make the above API call as many times as the group count is. A sample call would look like:

http://api.susi.ai/cms/getSkillList.json?group=News

which would fetch all the skills in the News group.

{
  "accepted": true,
  "model": "general",
  "group": "News",
  "language": "en",
  "skills": {"news": {
    "image": "images/news.png",
    "author_url": "https://github.com/AliAyub007",
    "examples": [
      "News",
      "latest news",
      "most viewed articles in science today",
      "most viewed articles in science in the last week",
      "most viewed articles in science in the last month",
      "most shared articles in science today",
      "can you tell me last week's most shared articles in science",
      "do you know most shared articles in arts in the last month",
      "most emailed articles today in arts",
      "most emailed articles in science in the last week",
      "can you tell me most emailed articles in science in the last month",
      "articles in science",
      "show me articles",
      "most emailed articles",
      "most shared articles",
      "tell me news in tech world"
    ],
    "developer_privacy_policy": null,
    "author": "Ali Ayub Khan",
    "skill_name": "NEWS",
    "dynamic_content": true,
    "terms_of_use": null,
    "descriptions": "A skill to give news.",
    "skill_rating": null
  }},
  "message": "Success: Fetched skill list",
  "session": {"identity": {
    "type": "host",
    "name": "23.94.137.239",
    "anonymous": true
  }}
}

Each such json object gives us the following values:

  • Model
  • Group
  • Language
  • Image Path
  • Author’s URL
  • Author’s Name
  • A list of sample queries
  • Skill Name
  • Licence and terms of use
  • Rating
  • Description

Implementation in SUSI iOS

The UI for listing skills is a little complex as it consists of a UITableView where each UITableViewCell consists of a UILabel(group name) and a UICollectionVIew (horizontal scroll).

Let’s see the step by step process to implement the skill listing.

  1. First, we need to fetch all the groups available using the endpoint above.
// get all groups
func getAllGroups() {
  Client.sharedInstance.getAllGroups { (groups, success, message) in
    DispatchQueue.main.async {
      if success {
        self.groups = groups
        self.tableView.reloadData()
      } else {
        print(message ?? "error")
      }
    }
  }
}
  1. After the response from the above call is obtained, the groups object containing all the groups is returned to the controller which the group name to set the UILabel.
var groupName: String? {
  didSet {
    backgroundColor = Color.grey.lighten3
    groupNameLabel.text = groupName
  }
}
  1. After the label is set, each cell makes another API call to populate the collection view, since these calls are concurrent, the collection view populates as soon as the response is fetched.
  2. The response from the above API call is used to create an array of Skill Model objects which is used to parse the response and to effectively use the data to display.
if let skills = response[Client.SkillListing.skills] as? [String : AnyObject],
  let model = response[Client.SkillListing.model] as? String,
  let group = response[Client.SkillListing.group] as? String,
  let language = response[Client.SkillListing.language] as? String,
  skills.count > 0 {
    let skillData = Skill.getAllSkill(skills, model, group, language)
    completion(skillData, true, nil)
    return
}
static func getAllSkill(_ skills: [String : AnyObject], _ model: String, _ group: String, _ language: String) -> [Skill] {
  var skillData = [Skill]()
  for skill in skills {
    let newSkill = Skill(dictionary: skill.value as! [String : AnyObject])
    newSkill.imagePath = getImagePath(model, group, language, newSkill.imagePath)
    skillData.append(newSkill)
  }
  return skillData
}
  1. At last, the skill object for the collection view is used to populate it as below:
var skill: Skill? {
  didSet {
    if let skill = skill {
      if let url = URL(string: skill.imagePath) {
        imageView.kf.setImage(with: url)
      }
      exampleQueryLabel.text = "\(skill.examples.first?.debugDescription ?? "")"
      skillNameLabel.text = skill.skillName
      skillDescription.text = skill.skillDescription
    }
  }
}

Here, we make use of the didSet method to populate the collection view. We have used Kingfisher to display the images.

That’s all for the scope of this tutorial. We learned how to fetch the skill groups followed by fetching the skills and displaying the skill data using the skill model object.

Below is the final UI we see after implementation.

Resources

Continue ReadingImplementing Skill Listing in SUSI iOS

Settings Controller UI using Static Table View

Dynamic Table Views are used at places where there may be any kind of reusability of cells. This means that there would exist cells that would have the same UI elements but would differ in the content being displayed. Initially the Settings Controller was built using UICollectionViewController which is completely dynamic but later I realized that the cells will remain static every time so there is no use of dynamic cells to display the UI hence, I switched to static table view cells. Using Static Table View is very easy. In this blog, I will explain how the implementation of the same was achieved in SUSI iOS app.

Let’s start by dragging and dropping a UITableViewController into the storyboard file.

The initial configuration of the UITableView has content as Dynamic Prototypes but we need Static cells so we choose them and make the sections count to 5 to suit our need. Also, to make the UI better, we choose the style as Grouped.

Now for each section, we have the control of statically adding UI elements so, we add all the settings with their corresponding section headers and obtain the following UI.

       

After creating this UI, we can refer any UI element independently be it in any of the cells. So here we create references to each of the UISlider and UISwitch so that we can trigger an action whenever the value of anyone of them changes to get their present state.

To create an action, simply create a function and add `@IBAction` in front so that they can be linked with the UI elements in the storyboard and then click and drag the circle next to the function to UI element it needs to be added. After successful linking, hovering over the same circle would reveal all the UI elements which trigger that function. Below is a method with the @IBAction identifier indicating it can be linked with the UI elements in the storyboard. This method is executed whenever any slider or switch value changes, which then updates the UserDefaults value as well sends an API request to update the setting for the user on the server.

@IBAction func settingChanged(sender: AnyObject?) {
        var params = [String: AnyObject]()
        var key: String = ""

        if let senderTag = sender?.tag {
            if senderTag == 0 {
                key = ControllerConstants.UserDefaultsKeys.enterToSend
            } else if senderTag == 1 {
                key = ControllerConstants.UserDefaultsKeys.micInput
            } else if senderTag == 2 {
                key = ControllerConstants.UserDefaultsKeys.hotwordEnabled
            } else if senderTag == 3 {
                key = ControllerConstants.UserDefaultsKeys.speechOutput
            } else if senderTag == 4 {
                key = ControllerConstants.UserDefaultsKeys.speechOutputAlwaysOn
            } else if senderTag == 5 {
                key = ControllerConstants.UserDefaultsKeys.speechRate
            } else if senderTag == 6 {
                key = ControllerConstants.UserDefaultsKeys.speechPitch
            }

            if let slider = sender as? UISlider {
                UserDefaults.standard.set(slider.value, forKey: key)
            } else {
                UserDefaults.standard.set(!UserDefaults.standard.bool(forKey: key), forKey: key)
            }

            params[ControllerConstants.key] = key as AnyObject
            params[ControllerConstants.value] = UserDefaults.standard.bool(forKey: key) as AnyObject

            if let delegate = UIApplication.shared.delegate as? AppDelegate, let user = delegate.currentUser {
                params[Client.UserKeys.AccessToken] = user.accessToken as AnyObject
                params[ControllerConstants.count] = 1 as AnyObject

                Client.sharedInstance.changeUserSettings(params) { (_, message) in
                    DispatchQueue.main.async {
                        self.view.makeToast(message)
                    }
                }
            }
        }
    }

References

Continue ReadingSettings Controller UI using Static Table View

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