Implementing Color Picker in the Open Event Orga App

In the Open Event Orga App, we have implemented a color picker to change the color of the Session’s item heading. Earlier the user had to enter the hex code in the given field to provide the color which is a bit cumbersome but now it is much easier with the help of a color picker. The PR related to this issue can be found here: #1073

The following approach was followed to implement the color picker.

Firstly we have used the library https://github.com/Pes8/android-material-color-picker-dialog. So it is included in the build.gradle file. The following line is added this file.

dependencies {
       implementation ‘com.pes.materialcolorpicker:library:1.2.0’
   }

Then navigate to the CreateTracksFragment.java class where we will need to implement the color picker. But firstly when the user enters the fragment , he should be able to see a random color in the edit text field and a small demo to the right of it where the selected color will be shown. These two things are done in the following steps.

1. To auto fill the color field with a random color the following code is written:

public String getRandomColor() {
  Random random = new Random();
  colorRed = random.nextInt(255);
  colorGreen = random.nextInt(255);
  colorBlue = random.nextInt(255);
  colorRGB = Color.rgb(colorRed, colorGreen, colorBlue);
  return String.format(“#%06X”,(0xFFFFFF & colorRGB));
}

public int getRed() {
  return colorRed;
}

public int getGreen() {
  return colorGreen;
}

public int getBlue() {
  return colorBlue;
}

public int getColorRGB() {
  return colorRGB;
}

 

With the help of the above code a random hex color is generated with the help of colorRed, colorGreen and colorBlue. These random fields are assigned to the local variables as they need to be used later on in the CreateTracksFragment.

This method is then called from the onStart( ) of the CreateTracksFragment.

2. Now a small demo color view is created to the right of the edit text field where the user can see the color of the current hex code. To implement this the following XML code is written. A small image view is created with the id color_picker.

<ImageView
  android:id=“@+id/color_picker”
  android:layout_width=“24dp”
  android:layout_height=“24dp”
  android:layout_margin=“10dp”
  android:layout_weight=“0.2” />

The above image view is filled with the current selected color with the help of the following code:

This ensures that whenever the user opens the CreateTracksFragment he should be able to view the color.

binding.form.colorPicker.setBackgroundColor(getPresenter().getColorRGB());

Now after implementing the above 2 features, the color picker dialog needs to be shown when the user clicks on the demo color view and the dialog box should show the value of Red, Green, Blue generated by the random method in the presenter. So these values are obtained from the presenter and added as constructor parameters as shown below. The color picker library has 2 main methods that need to be implemented which are :

→ setCallback( ) which is a callback method triggered when the user has selected the color and clicks on submit button. The action that needs to be performed after this is written in this particular callback method. In our case, we want to fill the edit text with the selected color and so we set the edit text with the callback value. Also we need fill the image view with the selected color and hence its color is also set in the callback function.

→ The show( ) method is required to make the dialog box of the color picker visible.

 

private void setColorPicker() {
  if (colorPickerDialog == null)
      colorPickerDialog = new ColorPicker(getActivity(), getPresenter().getRed(), getPresenter().getGreen(), getPresenter().getBlue());

  binding.form.colorPicker.setBackgroundColor(getPresenter().getColorRGB());

  binding.form.colorPicker.setOnClickListener(view -> {
      colorPickerDialog.show();
  });

  colorPickerDialog.setCallback(color -> {
      binding.form.trackColor.setText(String.format(“#%06X”, (0xFFFFFF & color)));
      binding.form.colorPicker.setBackgroundColor(color);
      colorPickerDialog.dismiss();
  });
}

 

→ When the colors have been selected we need to dismiss the dialog box. This is done by calling the .dismiss( )of the color picker.

Now after the CreateTracksFragment has been implemented certain changes need to be done to the UpdateTracksPresenter as well. Whenever the user selects to update the track, he should see the colors associated with that track as well which were set during its creation. We add the following to the UpdateTracksFragment.

 

public int getRed() {
  String colorRed = track.getColor();
  return Integer.valueOf(colorRed.substring(1, 3), 16);
}

public int getGreen() {
  String colorGreen = track.getColor();
  return Integer.valueOf(colorGreen.substring(3, 5), 16);
}

public int getBlue() {
  String colorBlue = track.getColor();
  return Integer.valueOf(colorBlue.substring(5, 7), 16);
}

public int getColorRGB() {
  return Color.rgb(getRed(), getGreen(), getBlue());
}

These methods are then eventually called from the UpdateTracksFragment and provided as parameters to the colorpicker similar to what had been implemented in the CreateTracksFragment.

private void setColorPicker() {
  if (colorPickerDialog == null)
      colorPickerDialog = new ColorPicker(getActivity(), getPresenter().getRed(), getPresenter().getGreen(), getPresenter().getBlue());

  binding.form.colorPicker.setBackgroundColor(getPresenter().getColorRGB());

  binding.form.colorPicker.setOnClickListener(view -> {
      colorPickerDialog.show();
  });

  colorPickerDialog.setCallback(color -> {
      binding.form.trackColor.setText(String.format(“#%06X”, (0xFFFFFF & color)));
      binding.form.colorPicker.setBackgroundColor(color);
      colorPickerDialog.dismiss();
  });

The final result can be seen in the form of GIF

 

Resources

1. Color Picker Library

https://github.com/Pes8/android-material-color-picker-dialog

2. Generating random hex values

https://stackoverflow.com/questions/11094823/java-how-to-generate-a-random-hexadecimal-value-within-specified-range-of-value

Setup interactive charts for data representation

At the end of this blog, you would be able to setup interactive charts using HighCharts and D3.js. As the charts/data-visualisation models will form the backbone of the upcoming SUSI.AI Analytics dashboard, as well as the data representational model for various useful data. For the purpose of integration with the SUSI Skills CMS project, we will be using the react-highcharts and react-tagcloud library.

There are various kinds of charts and plots that HighCharts offers. They are –

  • Line charts
  • Area charts
  • Column and Bar Charts
  • Pie Charts
  • Scatter and Bubble Charts
  • Combinations
  • Dynamic Charts
  • Gauges
  • Heat and Tree maps
  • 3D charts

Check out this link for the full list.

We would be aiming to build up the above charts for analysis of Term Frequency Trends and Trending Clouds.

For the Term Frequency Trends, we will need to setup a Basic Line Graph and for the later,  we need a World Cloud.

Setting up a Basic Line Graph

The aim is to setup a basic line graph and to accomplish that we use a react library called react-highcharts, which makes our work very easier.

Firstly, we create an object config that contains the labels and the required data, with the key values as mentioned in the API reference. The object looks like this –

const config = {
  xAxis: {
    categories: ['01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20']
  },
  series: [{
    data: [750, 745, 756, 740, 760, 752, 765]
  }]
};

Secondly, we create a React Component and pass the config object as a property to the ReactHighcharts component.

Finally, we render the component in a div of the index.html file, and the following output is achieved.

The code for the component that renders the Chart is as follows:

import React from 'react';
import ReactHighcharts from 'react-highcharts';
import ReactDOM from 'react-dom';

const config = {
  xAxis: {
    categories: ['01/13', '01/14', '01/15', '01/16', '01/17', '01/18', '01/19', '01/20']
  },
  series: [{
    data: [750, 745, 756, 740, 760, 752, 765]
  }]
};

ReactDOM.render(<ReactHighcharts  config={config} />, document.getElementById('app'));

Setting up a WordCloud

Here, we wish to setup a WordCloud that would show the different words that got searched or the top trending words. We would be using the react-tagcloud library for this.

Firstly, we create an object data that contains the text along with the count/frequency of search. The object looks like this –

const data = [
  { value: "JavaScript", count: 38 },
  { value: "React", count: 30 },
  { value: "Nodejs", count: 28 },
  { value: "Express.js", count: 25 },
  { value: "HTML5", count: 33 },
  { value: "MongoDB", count: 18 },
  { value: "CSS3", count: 20 }
];

Secondly, we create a React Component and pass the data object as a property to the TagCloud component.

Finally, we render the component in a div of the index.html file, and the following output is achieved.

The code for the component that renders the Chart is as follows:

import React from 'react';
import React, { Component } from 'react';
import { TagCloud } from "react-tagcloud";
 
const data = [
  { value: "JavaScript", count: 38 },
  { value: "React", count: 30 },
  { value: "Nodejs", count: 28 },
  { value: "Express.js", count: 25 },
  { value: "HTML5", count: 33 },
  { value: "MongoDB", count: 18 },
  { value: "CSS3", count: 20 }
];
 
const SimpleCloud = () => (
  <TagCloud 
       minSize={12}
       maxSize={35}
       tags={data}
       onClick={tag => alert(`'${tag.value}' was selected!`)} />
);

ReactDOM.render(<SimpleCloud />, document.getElementById('app'));

 

These were some examples of setting up some of the data-visualization models, that would form the basic building block of the SUSI Analytics project. I hope this blogs would be a good starting point for those wanting to start with setting up charts, graphs, etc.

Resources

Implementing Filters in Phimp.me Android App

Image filters in phimp.me android app are implemented using the OpenCV library that enables the android developers to write code in C++/C which is compatible with java. I have implemented around eight filters in the filters.cpp file, mainly dealing with the colour variations that took place between the images.

Suppose that the color model used in consideration is the RGB (Red,Green,Blue) model and we define that every pixel is defined using these values. If we simply want to boost or focus on the red part of the image it can be done by enhancing the particular color in the source image i.e. modifying each pixel of the app by enhancing the blue color of each pixel. The code for the same can be written as :

void applyBlueBoostEffect(cv::Mat &src, cv::Mat &dst, int val) {
register int x, y;
float opacity = val * 0.01f;
cvtColor(src, src, CV_BGRA2BGR);
dst = Mat::zeros(src.size(), src.type());
uchar r, g, b,val1;

for (y = 0; y < src.rows; y++) {
for (x = 0; x < src.cols; x++) {
r = src.at<Vec3b>(y, x)[0];
g = src.at<Vec3b>(y, x)[1];
b = src.at<Vec3b>(y, x)[2];
val1 = saturate_cast<uchar>(b*(1+opacity));
if(val1 > 255) {
val1 = 255;
}
dst.at<Vec3b>(y, x)[0] =
saturate_cast<uchar>(r);
dst.at<Vec3b>(y, x)[1] =
saturate_cast<uchar>(g);
dst.at<Vec3b>(y, x)[2] =
saturate_cast<uchar>(val1);
}
}
}

In the above function what we are doing is taking the source image and converting it to a matrix of known number of rows and columns. Now, we loop over this matrix created and at each

position in the matrix calculate the red, green and blue values present there. There is a parameterized value “val” that determines the amount to which the color chosen is enhanced.

A simple variable val1 contains the enhanced value of the color chosen, here which is color blue. We modify and boost the color by the equation  :

B = B*(1 + (0.01f* val) )

Finally at this particular position in the matrix all the three values of the color are updated i.e. the original red and green color but the modified blue color.

The output of this filter converts images as below shown :

Before                                                                                   After

  

In the above method defined we instead of enhancing one color we can even modify two colors or all the three at the same time to get different effects as per the required needs. An example where I implemented a filter to enhance two colors at the same time is given below, the purpose of this filter is to add a violet color tone in the image.

The code for the filter is :

void applyRedBlueEffect(cv::Mat &src, cv::Mat &dst, int val) {
register int x, y;
float opacity = val * 0.01f;
cvtColor(src, src, CV_BGRA2BGR);
dst = Mat::zeros(src.size(), src.type());
uchar r, g, b;
uchar val1,val3;

for (y = 0; y < src.rows; y++) {
for (x = 0; x < src.cols; x++) {
r = src.at<Vec3b>(y, x)[0];
g = src.at<Vec3b>(y, x)[1];
b = src.at<Vec3b>(y, x)[2];
val1 = saturate_cast<uchar>(r*(1+opacity));
if(val1 > 255) {
val1 = 255;
}

val3 = saturate_cast<uchar>(b*(1+opacity));
if(val3 > 255) {
val3 = 255;
}
dst.at<Vec3b>(y, x)[0] =
saturate_cast<uchar>(val1);
dst.at<Vec3b>(y, x)[1] =
saturate_cast<uchar>(g);
dst.at<Vec3b>(y, x)[2] =
saturate_cast<uchar>(val3);
}
}
}

 

Here we can easily see that there are two values that are updated at the same time i.e. the red and the blue part. The resultant image in that case will have two color values updated and enhanced.

Similarly, instead of two we can also update all the three colors to get uniform enhancement across each color and the resultant image to have all the three colors with a boost effect overall.

The output that we get after the filter that boosts the red and blue colors of an image is  :

 BEFORE                                                                 

AFTER

Resources

Making a SUSI Skill to get details about bank from IFSC

We are going to make a SUSI skill that fetches information about a bank when the IFSC (Indian Financial System Code) is known. Here is a detailed explanation of how we going about doing this.

Getting started with the skill creation

API endpoint that returns the bank details

Before going to the skill development, we need to find an API that would return the bank details from the IFSC, On browsing through various open source projects. I found an apt endpoint by Razorpay. Razorpay is a payment gateway for India which allows businesses to accept, process and disburse payments with ease. The Github link  to the repository is https://github.com/razorpay/ifsc.

API endpoint –  https://ifsc.razorpay.com/<:ifsc>
Request type –  GET
Response type –  JSON

Now, head over to the SUSI Etherpad, which is the current SUSI Skill Development Environment and create a new Pad. 

Here, we need to define the skill in the Etherpad. We will now write rules/intents for the skill. An intent represents an action that fulfills a user’s spoken request.

Intents consist of 2 parts –

  • User query – It contains different patterns of query that user can ask.
  • Answer – It contains the possible answer to the user query.

The main intent that our skill focuses on is, returning the bank name and address from the IFSC code. Here is how it looks –

Name of bank with IFSC code * | Bank's name with IFSC code *
!example:Name of bank with IFSC code SBIN0007245
!expect: The name of bank is State Bank of India
!console:The name of bank with IFSC code $1$ is $object$
{
"url":"https://ifsc.razorpay.com/$1$",
"path":"$.BANK"
}
eol

Part-wise explanation of the intent

  • The first line contains the query pattern that the user can use while querying. You can see that a wildcard character (*) is used in the pattern. It contains the IFSC of the bank that we wish to know, and will later on use to fetch the details via the API.
  • The second line contains an example query, followed by third line that contains the expected answer.
  • Last part of the rule contains the answer that is fetched from an external API –  https://ifsc.razorpay.com/<:ifsc>  ,via the console service  provided by SUSI Skills. Here, <:ifsc> refers to the IFSC that the user wants to know about. We get it from the user query itself, and can access it by the variable name $1$ as it matches with the 1st wildcard present in the query. If there would be 2 wildcards, we could have accessed them by $1$ and $2$ respectively.
  • The console service provides us with an option to enter the url of the API that we want to hit and path of the key we want to use.

The sample response of the endpoint looks like this :

{
  "BANK": "Karnataka Bank",
  "IFSC": "KARB0000001",
  "BRANCH": "RTGS-HO",
  "ADDRESS": "REGD. & HEAD OFFICE, P.B.NO.599, MAHAVEER CIRCLE, KANKANADY, MANGALORE - 575002",
  "CONTACT": "2228222",
  "CITY": "DAKSHINA KANNADA",
  "RTGS": true,
  "DISTRICT": "MANGALORE",
  "STATE": "KARNATAKA"
}

 

  • Since, we want to extract the name of the bank, the BANK key contains our desired value and we will use $.BANK in the path of the console service. And it can be accessed by $object$ in the answer. We frame the answer using $object$ and $1$ variables, and it like the one mentioned in the expected answer. eol marks the end of the console service.
  • Similarly, the intent that gives us the address of the bank looks like this –
Address of bank with IFSC code * | Bank's address with IFSC code *
!example:Address of bank with IFSC code SBIN0007245
!expect: The address of bank is TILAK ROAD HAKIMPARA, P.O.SILIGURI DARJEELING, WEST BENGAL ,PIN - 734401
!console:The address of bank with IFSC code $1$ is $object$
{
  "url":"https://ifsc.razorpay.com/$1$",
  "path":"$.BANK"
}
eol

Testing the skill

  • Open any SUSI Client and then write dream <your dream name> so that dreaming is enabled for SUSI. We will write down dream ifsc. Once dreaming is enabled, you can now test any skills which you’ve made in your Etherpad.
  • We can test the skills by asking queries and matching it with the expected answer. Once the testing is done, write stop dreaming to disable dreaming for SUSI.

  • After the testing was successful completely, we will go ahead and add it to the susi_skill_data.
  • The general skill format is –
::name <Skill_name>
::author <author_name>
::author_url <author_url>
::description <description> 
::dynamic_content <Yes/No>
::developer_privacy_policy <link>
::image <image_url>
::term_of_use <link>

#Intent
User query1|query2|query3....
Answer answer1|answer2|answer3...

We will add the basic skill details and author details to the etherpad file and make it in the format as mentioned above. The final text file looks like this –

::name IFSC to Bank Details
::author Akshat Garg
::author_url https://github.com/akshatnitd
::description It is a bank lookup skill that takes in IFSC code from the user and provides you all the necessary details for the Bank. It is valid for banks in India only
::dynamic_content Yes
::developer_privacy_policy 
::image images/download.jpeg
::terms_of_use 

Name of bank with IFSC code * | Bank's name with IFSC code *
!example:bank with IFSC code *
!expect: The name of bank is SBI
!console:The name of bank with IFSC code $1$ is $object$
{
"url":"https://ifsc.razorpay.com/$1$",
"path":"$.BANK"
}
eol

Address of bank with IFSC code * | Bank's address with IFSC code *
!example:Address of bank with IFSC code *
!expect: The address of bank is 
!console:The address of bank with IFSC code $1$ is $object$
{
"url":"https://ifsc.razorpay.com/$1$",
"path":"$.ADDRESS"
}
eol

Submitting the skill

The final part is adding the skill to the list of skills for SUSI. We can do it by 2 ways:

1st method (using the web interface)

  • Open https://susi.skills.com and login into SUSI account (or sign up, if not done).
  • Click on the create skill button.
  • Select the appropriate fields like Category, Language, Skill name, Logo.
  • Paste the text file that we had created.
  • Add comments regarding the skill and click on Save to save the skill.

2nd method (sending a PR)

  • Send a Pull Request to susi_skill_data repository providing the dream name. The PR should have the text file containing the skill.

So, this was a short blog on how we can develop a SUSI skill of our choice.

Resources

Option to secure particular albums in Phimpme Android Application

In the Phimpme Android application, users can perform various operations on the albums available such as creating a zip file of the album, rename an album and many more. However, one another useful functionality that has been added to the Phimpme Android application is the option to secure particular albums. So in this post, I will be discussing the implementation of this security feature.

Step 1

Firstly, a view item for providing the option to enable security for particular albums is required to be added to the security settings layout. The two-state toggle switch widget provided by the Android framework along with a textview has been added as the required view item. A screenshot depicting the layout change is provided below.

The code snippet representing the operation is provided below.

<TextView
  android:id=“@+id/security_body_apply_folders_title”
  android:layout_width=“wrap_content”
  android:layout_height=“wrap_content”
  android:text=“@string/local_folder”
  android:textColor=“@color/md_dark_background”
  android:textSize=“@dimen/medium_text” />

<android.support.v7.widget.SwitchCompat
  android:id=“@+id/security_body_apply_folder_switch”
  android:layout_width=“wrap_content”
  android:layout_height=“wrap_content”
  android:layout_alignParentEnd=“true”
  android:layout_centerVertical=“true”
  android:layout_gravity=“center_vertical”
  android:button=“@null”
  android:hapticFeedbackEnabled=“true” />

Step 2

Now we need to keep track of the albums selected by the user to secure. This can be done by storing the selected album/albums paths in an ArrayList<String> which can be referred later when required in the process.

The required code snippet to perform the above mentioned operation is provided below.

holder.foldercheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if(b){
securedfol.add(a.getPath());
a.setsecured(true);
}else{
securedfol.remove(a.getPath());
a.setsecured(false);
}
}
});

Step 3

Now we need to store the selected albums preference in the SharedPreference so that the user’s security preference persists even when the user exits the application and the user doesn’t have to redo the securing operation the next time user launches the application. The ArrayList<String> object containing the path of the user choice albums are converted to JSON representation by the use of the Gson Java library and the string key denoting the JSON representation of the list is saved in the SharedPreference thereafter.

if(securedfol.size()>0){
  SharedPreferences.Editor editor = SP.getEditor();
  Gson gson = new Gson();
  String securedfolders = gson.toJson(securedfol);
  editor.putString(getString(R.string.preference_use_password_secured_local_folders), securedfolders);
  editor.commit();}

Now at the time of performing other operations on the secured folders, the list containing the secured folder paths is retrieved from SharedPreference and the choosen folder’s path is searched in the obtained list, then the user is asked to authenticate accordingly.

This is how we have implemented the functionality to secure particular albums in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1.Android Developer Guide –
https://developer.android.com/training/data-storage/shared-preferences

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Gson Java library tutorial –
http://www.vogella.com/tutorials/JavaLibrary-Gson/article.html

Implementing Search Functionality in Phimpme Android Application.

In the Phimpme Android application, users are provided with options to perform various operations on the albums available such as move, creating a zip file of the album, rename an album and many more. However, one another useful functionality that has been added to the Phimpme Android application is the option to search albums. So in this post, I will be discussing the implementation of search functionality.

Step 1

Android framework provides developers with a search widget called SearchView that provides a user interface for the user to search a query and submit the request. So first setting up the widget in the action bar of the activity is required. The searchview widget can be added to the action bar as a menu item by adding the following lines in the XML menu resource file menu_albums.xml.

<item
  android:id=“@+id/search_action”
  android:title=“@string/search_menu”
  android:visible=“true”
  android:icon=“@drawable/ic_search_black_24dp”
  android:tint=“@color/white”
  app:actionViewClass=“android.support.v7.widget.SearchView”
  app:showAsAction=“always” />

Step 2

Now SearchView.OnQueryTextListener interface is used for initiating the search operation and listening to the callbacks for changes to the query text. For the purpose of listening to the querytext, two methods are used here both of which are listed below.

onQueryTextChanged(String Text) – This method is called every time the query text is changed by the user and returns a boolean value, false if SearchView should perform the default action of showing any suggestions and true if the action was handled by the listener.

onQueryTextSubmit(String query) – This method is called when the user submits a query which could be done with a key press on the keyboard or by pressing the submit button. It also returns a boolean value which is true if the query has been handled by the listener, otherwise false.

The code snippet for the implementation of the above mentioned interface is provided below.

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
  @Override
  public boolean onQueryTextSubmit(String query) {
      return false;
  }

  @Override
  public boolean onQueryTextChange(String newText) {
      return searchTitle(newText);
  }
});

Step 3

In the final step, with the use of the onQueryTextChange method of the SearchView.onQueryTextListener interface the search operation and displaying the search results in the UI can be achieved. The onQueryTextChange method is called every time the search-query text changes. From the onQueryTextChange method, another method named searchTitle is invoked. Inside the searchTitle method the album names matching the search-query are searched from an Arraylist<Albums> containing all the albums displayed in the application. The albums obtained as a result of the search operation are then stored in another Arraylist<Album> which is thereafter passed as a parameter to the swapDataSet method of the AlbumsAdapter class to display the searched albums in the album view. The code snippet used for the above operations is provided below.

public boolean searchTitle(String newText) {
  if (!fromOnClick) {
      String queryText = newText;
      queryText = queryText.toLowerCase();
      final ArrayList<Album> newList = new ArrayList<>();
      for (Album album : albList) {
          String name = album.getName().toLowerCase();
          if (name.contains(queryText)) {
              newList.add(album);
          }
      }
      if(newList.isEmpty()){
          checkNoSearchResults(newText);
      }
      else{
          if(textView.getVisibility() == View.VISIBLE){
              textView.setVisibility(View.INVISIBLE);
          }
      }
      albumsAdapter.swapDataSet(newList);
  } else {
      fromOnClick = false;
  }
  return true;
}

This is how we have implemented the search functionality in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository.

The screenshot for displaying the search result in album view is provided below.

Resources

1.Android Developer Guide – https://developer.android.com/reference/android/widget/SearchView

2.Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/

3.Creating a search interface in Android – https://medium.com/@yugandhardcs21/creating-a-search-interface-in-android-dc1fd6a53b4

Open Event Server – Pages API

This article illustrates how the Pages API has been designed and implemented on the server side, i.e., FOSSASIA‘s Open Event Server. Pages endpoint is used to create static pages such as “About Page” or any other page that doesn’t need to be updated frequently and only a specific content is to be shown.

Parameters

  1. name – This stores the name of the page.
      1. Type – String
      2. Required – Yes
  2. title – This stores the title of the page.
      1. Type – String
      2. Required – No
  3. url – This stores the url of the page.
      1. Type – String
      2. Required – Yes
  4. description – This stores the description of the page.
      1. Type – String
      2. Required – Yes
  5. language – This stores the language of the page.
      1. Type – String
      2. Required – No
  6. index – This stores the position of the page.
      1. Type – Integer
      2. Required – No
      3. Default – 0
  7. place – Location where the page will be placed.
      1. Type – String
      2. Required – No
      3. Accepted Values – ‘footer’ and ‘event’

These are the allowed parameters for the endpoint.

Model

Lets see how we model this API. The ORM looks like this :

__tablename__ = 'pages'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
title = db.Column(db.String)
url = db.Column(db.String, nullable=False)
description = db.Column(db.String)
place = db.Column(db.String)
language = db.Column(db.String)
index = db.Column(db.Integer, default=0)

As you can see, we created a table called “pages”. This table has 8 columns, 7 of which are the parameters that I have mentioned above. The column “id” is an Integer column and is the primary key column. This will help to differentiate between the various entries in the table.

The visualisation for this table looks as follows :

API

We support the following operations:

  1. GET all the pages in the database
  2. POST create a new page
  3. GET details of a single page as per id
  4. PATCH a single page by id
  5. DELETE a single page by id

To implement this we first add the routes in our python file as follows :

api.route(PageList, 'page_list', '/pages')
api.route(PageDetail, 'page_detail', '/pages/<int:id>')

Then we define these classes to handle the requests. The first route looks as follows:

class PageList(ResourceList):
   """
   List and create page
   """
   decorators = (api.has_permission('is_admin', methods="POST"),)
   schema = PageSchema
   data_layer = {'session': db.session,
                 'model': Page}

As can be seen above, this request requires the user to be an admin. It uses the Page model described above and handles a POST request.

The second route is:

class PageDetail(ResourceDetail):
   """
   Page detail by id
   """
   schema = PageSchema
   decorators = (api.has_permission('is_admin', methods="PATCH,DELETE"),)
   data_layer = {'session': db.session,
                 'model': Page}

This route also requires the user to be an admin. It uses the Page model and handles PATCH, DELETE requests.

To summarise our APIs are:

GET

/v1/pages{?sort,filter}

POST

/v1/pages{?sort,filter}

GET

/v1/pages/{page_id}

PATCH

/v1/pages/{page_id}

DELETE

/v1/pages/{page_id}

References

Open Event Frontend – Updating Ember Models Table from V1 to V2

FOSSASIA‘s Open Event Frontend uses the Ember Models Table for rendering all its tables. This provides features like easy sorting, pagination etc. Another major feature is that it can be modified to meet our styling needs. As we use Semantic UI for styling, we added the required CSS classes to our table.

In version 1 this was done by overriding the classes, as shown below :

const defaultMessages = {
  searchLabel            : 'Search:',
  searchPlaceholder      : 'Search',


  ..... more to follow 
};

const defaultIcons = {
  sortAsc         : 'caret down icon',
  sortDesc        : 'caret up icon',
  columnVisible   : 'checkmark box icon',
  
  ..... more to follow  
};

const defaultCssClasses = {
  outerTableWrapper              : 'ui ui-table',
  innerTableWrapper              : 'ui segment column sixteen wide inner-table-wrapper',
  table                          : 'ui tablet stackable very basic table',
  globalFilterWrapper            : 'ui row',

 ... more to follow
};

const assign = Object.assign || assign;

export default TableComponent.extend({
  layout,

  _setupMessages: observer('customMessages', function() {
    const customIcons = getWithDefault(this, 'customMessages', {});
    let newMessages = {};
    assign(newMessages, defaultMessages, customIcons);
    set(this, 'messages', O.create(newMessages));
  }),

  _setupIcons() {
    const customIcons = getWithDefault(this, 'customIcons', {});
    let newIcons = {};
    assign(newIcons, defaultIcons, customIcons);
    set(this, 'icons', O.create(newIcons));
  },

  _setupClasses() {
    const customClasses = getWithDefault(this, 'customClasses', {});
    let newClasses = {};
    assign(newClasses, defaultCssClasses, customClasses);
    set(this, 'classes', O.create(newClasses));
  },

  simplePaginationTemplate: 'components/ui-table/simple-pagination',

  ........
});

And was used in the template as follows:

<div class="{{classes.outerTableWrapper}}">
  <div class="{{classes.globalFilterDropdownWrapper}}">

But in version 2, some major changes were introduced as follows:

  1. All partials inside a models-table were replaced with components
  2. models-table can now be used with block content
  3. New themes mechanism introduced for styling

Here, I will talk about how the theming mechanism has been changed. As I mentioned above, in version 1 we used custom classes and icons. In version 2 the idea itself has changed. A new type called Theme was added. It provides four themes out of the box – SemanticUI, Bootstrap4, Bootstrap3, Default.

We can create our custom theme based on any of the predefined themes. To suit our requirements we decided to modify the SemanticUI theme. We created a separate file to keep our custom theme so that code remains clean and short.

import Default from 'ember-models-table/themes/semanticui';

export default Default.extend({
 components: {
   'pagination-simple'    : 'components/ui-table/simple-pagination',
   'numericPagination'    : 'components/ui-table/numeric-pagination',
   .....  
 },

 classes: {
   outerTableWrapper              : 'ui ui-table',
   innerTableWrapper              : 'ui segment column sixteen wide inner-table-wrapper',
   .....
 },

 icons: {
   sortAsc         : 'caret down icon',
   sortDesc        : 'caret up icon',
   ......
 },

 messages: {
   searchLabel            : 'Search:',
   .....
 }
});

So a theme mostly consists of four main parts:

  • Components
  • Classes
  • Icons
  • Messages

The last three are same as customClasses and customIcons and customMessages in version 1. Components is the map for components used internally in the models-table. In case you need to use a custom component, that can be done as follows:

Make a new JavaScript file and provide its path in your theme file.

import DefaultDropdown from '../../columns-dropdown';
import layout from 'your layout file path';
export default DefaultDropdown.extend({
  layout
});

Now just create the theme file object and pass it to themeInstance in the ui-table file (can also be passed in the template and the controller, but this has to be done for each table individually).

import TableComponent from 'ember-models-table/components/models-table';
import layout from 'open-event-frontend/templates/components/ui-table';
import Semantic from 'open-event-frontend/themes/semantic';

export default TableComponent.extend({
 layout,

 themeInstance: Semantic.create()
});

Hence, version 2 introduces many new styling options and requires some refactoring for those who were using version 1. It is totally worth it though considering how easy and well managed it is now.

References

Implementing Sponsors API for Events and Using Image Upload Widget in Open Event Frontend

 

This blog article will talk about how sponsors API has been implemented in events edit dashboard of Open Event Frontend. This discussion involves the /events/{event_identifier}/edit/sponsors route. Primary API endpoint of Open Event Server for fetching sponsors of an event are

GET         /v1/events/{event_identifier}/sponsors{?sort,filter}

GET        /v1/sponsors/{sponsor_id}

Next, we define the corresponding models according to the type of response returned by the server. This model extends the Base model.

import attr from 'ember-data/attr';
import ModelBase from 'open-event-frontend/models/base';
import { belongsTo } from 'ember-data/relationships';
import { computedSegmentedLink } from 'open-event-frontend/utils/computed-helpers';

export default ModelBase.extend({
 name        : attr('string'),
 level       : attr('number'),
 type        : attr('string'),
 url         : attr('string'),
 description : attr('string'),
 logoUrl     : attr('string'),

 event: belongsTo('event'),

 segmentedLink: computedSegmentedLink.bind(this)('url')
});

 

Here all the field attributes clearly signify what they mean. Event field has a relationship to events model, hence it is bound to event model through

belongsTo( ).

Next we fetch the data from the API and feed it into sponsor edit form available at event/{event_identifier}/edit/sponsor .

import Route from '@ember/routing/route';
import EventWizardMixin from 'open-event-frontend/mixins/event-wizard';

export default Route.extend(EventWizardMixin, {
 titleToken() {
   return this.get('l10n').t('Sponsors');
 },
 async model() {
   let data = this.modelFor('events.view.edit');
   data.sponsors = await data.event.get('sponsors');
   return data;
 }
});

 

We have defined model() asynchronously and return the fetched data to template. This data passes into sponsor form of event wizard located here.

We see that this form contains many widgets for handling form validation and its structure. Here we will explore the image upload widget of open event frontend that helps us adding image upload option to many forms across open event frontend.

Image upload component is like any other component of ember with many functions. This widget mainly includes processFiles() and uploadFiles(). Let us look at their working one by one. Full code of image-upload.js can be seen here.

processFiles(files) {
   if (files && files[0]) {
     isFileValid(files[0], this.maxSizeInKb, ['image/jpeg', 'image/png']).then(() => {
       const reader = new FileReader();
       reader.onload = e => {
         const untouchedImageData = e.target.result;
         if (this.get('needsCropper')) {
           this.set('imgData', untouchedImageData);
           this.set('cropperModalIsShown', true);
         } else {
           this.uploadImage(untouchedImageData);
         }
       };
       reader.readAsDataURL(files[0]);

     }).catch(error => {
       this.notify.error(error);
     });
   } else {
     this.notify.error(this.get('l10n').t('No FileReader support. Please use a more latest browser'));
   }

 },

 

This function accepts file input and processes them. It first passes it to a isFileValid() function with all arguments, which returns true if the files are in correct format. It then checks the dimensions and figures out if the image needs a cropper for cropping image. If yes, it opens a cropper modal and lets user crop the image to perfect size.

It then calls uploadImage() function to upload the cropped image. UploadImage() function looks something like this:

uploadImage(imageData) {
   this.set('selectedImage', imageData);
   this.set('needsConfirmation', false);
   this.set('uploadingImage', true);
   this.get('loader')
     .post('/upload/image', {
       data: imageData
     })
     .then(image => {
       this.set('uploadingImage', false);
       this.set('imageUrl', image.url);
     })
     .catch(() => {
       this.set('uploadingImage', false);
       this.set('errorMessage', this.i18n.t('An unexpected error occurred.'));
     });
 },

 

This function on receiving image data send a post request to the server for uploading the image to requested directory. If the server does not behave properly then it exits with an error.

Widgets such as image-upload help us to maintain our code in a better way by allowing re-usability of code. Ember provides good support for such controller, adapters, widgets to be used in the app.

Resources:

Open Event Server – Change a Column from NULL to NOT NULL

FOSSASIA‘s Open Event Server uses alembic migration files to handle all database operations and updating. Whenever the database is changed a corresponding migration python script is made so that the database will migrate accordingly for other developers as well. But often we forget that the automatically generated script usually just add/deletes columns or alters the column properties. It does not handle the migration of existing data in that column. This can lead to huge data loss or error in migration as well.

For example :

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.alter_column('ticket_holders', 'lastname',
                    existing_type=sa.VARCHAR(),
                    nullable=False)
    # ### end Alembic commands ###

Here, the goal was to change the column “ticket_holders” from nullable to not nullable. The script that alembic autogenerated just uses op.alter_column().

It does not count for the already existing data. So, if the column has any entries which are null, this migration will lead to an error saying that the column contains null entries and hence cannot be “NOT NULL”.

How to Handle This?

Before altering the column definition we can follow the following steps :

  1. Look for all the null entries in the column
  2. Give some arbitrary default value to those
  3. Now we can safely alter the column definition

Let’s see how we can achieve this. For connecting with the database we will use SQLAlchemy. First, we get a reference to the table and the corresponding column that we wish to alter.

ticket_holders_table = sa.sql.table('ticket_holders',
                                        sa.Column('lastname', sa.VARCHAR()))

 

Since we need the “last_name” column from the table “ticket_holders”, we specify it in the method argument.

Now, we will give an arbitrary default value to all the originally null entries in the column. In this case, I chose to use a space character.

op.execute(ticket_holders_table.update()
               .where(ticket_holders_table.c.lastname.is_(None))
               .values({'lastname': op.inline_literal(' ')}))

op.execute() can execute direct SQL commands as well but we chose to go with SQLAlchemy which builds an optimal SQL command from our modular input. One such example of a complex SQL command being directly executed is :

op.execute('INSERT INTO event_types(name, slug) SELECT DISTINCT event_type_id, lower(replace(regexp_replace(event_type_id, \'& |,\', \'\', \'g\'), \' \', \'-\')) FROM events where not exists (SELECT 1 FROM event_types where event_types.name=events.event_type_id) and event_type_id is not null;'))

Now that we have handled all the null data, it is safe to alter the column definition. So we proceed to execute the final statement –

op.alter_column('ticket_holders', 'lastname',
                    existing_type=sa.VARCHAR(),
                    nullable=False)

Now the entire migration script will run without any error. The final outcome would be –

  1. All the null “last_name” entries would be replaced by a space character
  2. The “last_name” column would now be a NOT NULL column.

References