Implementing Five Star Rating UI in SUSI iOS

Five-star rating system introduced in SUSI to rate skills. SUSI enable the user to rate skills between 1 to 5 star. The five-star rating system is the best way to get feedback from the user. It also helps the developer for further development. Ratings help to better understand individual preferences and present a more personalized user experience. The user feedback helps products understand whether or not the content is valuable and improve offerings over time. This can benefit products with and without sophisticated personalization.

Let’s see how the five-star rating system is implemented in SUSI iOS.

Average ratings displayed near the Try It button – It shows the average rating of a particular skill.

Enable user to submit the rating of any skill between 1-star to 5-star.

The only logged-in user can submit the ratings for skills.

Rating chart that display number of rating for each star (1 to 5), the right labels of chart bars shows the number of users rated for a particular star with the percentage.

Average and total ratings for particular skills is also displayed near the bar chart.

Thumbs-up and thumbs-down ratings removed from the skill detail screen and replaced with 5-star ratings.

Implementation of Rating Chart

For the rating chart, we are using TEAChart class, which enable us to present rating data on bar charts.

Setting colors for bar chart:

We are using Google’s Material Design color for rating bars colors.

let barChartColors = [
UIColor.fiveStarRating(),
UIColor.fourStarRating(),
UIColor.threeStarRating(),
UIColor.twoStarRating(),
UIColor.oneStarRating()
]

Assigning colors to bars:

barChartView.barColors = barChartColors

Assign Data to the bars:

// Sample data
barChartView.data = [5, 1, 1, 1, 2]

Set background color and bar spacing:

barChartView.barSpacing = 3
barChartView.backgroundColor = UIColor.barBackgroundColor()

Final Output –

Resources –

  1. Material Design: https://material.io/design/
  2. SUSI iOS Link: https://github.com/fossasia/susi_iOS
Continue ReadingImplementing Five Star Rating UI in SUSI iOS

Implementing Check-in time chart in Orga App

Earlier in the Open event orga app there were no charts present to track the check-in time of the attendees. Hence it was quite cumbersome for the organiser to track the people and at what time they have checked-in. Using this feature of check-in time chart, the process has become quite easier.

Whenever an attendee checks-in, the data point is added to the chart and a chart is plotted. The Y-axis shows the number of attendees and the X-axis shows the time at which they have checked-in.

To implement this feature I have taken use of the MPAndroidCharts library which makes the job a lot easier. Following steps were followed to implement the charts:

  • Adding the following Library dependency in the build.gradle file
implementation “com.github.PhilJay:MPAndroidChart:v3.0.3”
  • Now the following code is added to the ticket_analytics.xml file. This is done so that the UI of the charts can be created. The following XML file consists of the LineChart XML tag which shows the check-in time chart on screen. Also the labelling of the axis needs to be done, so the X-axis is explicitly named as “TIME”.
<LinearLayout
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  android:orientation=“vertical”>

  <TextView
      android:layout_width=“wrap_content”
      android:layout_height=“wrap_content”
      android:layout_marginLeft=“@dimen/spacing_normal”
      android:layout_marginStart=“@dimen/spacing_normal”
      android:layout_marginTop=“@dimen/spacing_normal”
      android:text=“@string/check_in_summary”
      android:textAllCaps=“true”
      android:textSize=“@dimen/text_size_small” />

  <com.github.mikephil.charting.charts.LineChart
      android:id=“@+id/chartCheckIn”
      android:layout_width=“match_parent”
      android:layout_height=“200dp”
      android:layout_marginEnd=“@dimen/spacing_normal”
      android:layout_marginLeft=“@dimen/spacing_normal”
      android:layout_marginRight=“@dimen/spacing_normal”
      android:layout_marginStart=“@dimen/spacing_normal” />

  <TextView
      android:layout_width=“wrap_content”
      android:layout_height=“wrap_content”
      android:layout_gravity=“center”
      android:layout_marginBottom=“8dp”
      android:layout_marginTop=“8dp”
      android:text=“@string/check_in_time”
      android:textSize=“10sp” />

  <LinearLayout
      android:layout_width=“match_parent”
      android:layout_height=“wrap_content”
      android:orientation=“vertical”>

      <FrameLayout
          android:layout_width=“match_parent”
          android:layout_height=“1dp”
          android:background=“@color/color_shadow” />

      <FrameLayout
          android:layout_width=“match_parent”
          android:layout_height=“@dimen/spacing_small”
          android:background=“@color/color_bottom_surface” />
  </LinearLayout>
</LinearLayout>
  • Now the a method loadCheckIn( )  chart needs to added to the EventsDashboardPresenter. This is called from the EventsDashboardFragment. The loadDataCheckIn( ) is created in the ChartAnalyzer class. We pass getId( ) as the parameter.
private void loadCheckInTimesChart() {
  chartAnalyser.showChart(getView().getCheckinTimeChartView());
  chartAnalyser.loadDataCheckIn(getId())
      .compose(disposeCompletable(getDisposable()))
      .subscribe(() -> {
          getView().showChartCheckIn(true);
          chartAnalyser.showChart(getView().getCheckinTimeChartView());
      }, throwable -> getView().showChartCheckIn(false));
}
  • Now we add the method loadDataCheckIn( ) in the ChartAnalyzer class. This method returns a Completable and takes eventId as the single parameter.
public Completable loadDataCheckIn(long eventId) {
  clearData();
  isCheckinChart = true;
  return getAttendeeSource(eventId).doOnNext(attendee -> {
     String checkInTime = attendee.getCheckinTimes();
     int length = checkInTime.split(“,”).length;
     String latestCheckInTime = checkInTime.split(“,”)[length – 1];
     error = checkInTime == null ? true : false;
     addDataPointForCheckIn(checkInTimeMap, latestCheckInTime);
  })
    .toList()
    .doAfterSuccess(attendees -> this.attendees = attendees)
    .toCompletable()
    .doOnComplete(() -> {
        if (error)
            throw new IllegalAccessException(“No checkin’s found”);
        checkInDataSet = setDataForCheckIn(checkInTimeMap, “check-in time”);
        prepare();
    });
}

It calls the getAttendeeSource( ) which further gives a call to the method getAttendees( ) from the AttendeeRepository. All the inormation related to the attendees is returned from which the check-in times is extracted. The check-in times are returned in comma separated form and hence we need to extract the first element of the sequence.

private Observable<Attendee> getAttendeeSource(long eventId) {
  if (attendees == null || attendees.isEmpty())
      return attendeeRepository.getAttendees(eventId, false);
  else
      return Observable.fromIterable(attendees);
}
  • After the success of loading the attendees, the method addDataPointForCheckIn is called. We call it by inserting the parameters Map<Integer, Long> and the dateString which we had passed from the loadDataCheckIn( ). Following is the code for it. A map is created out of the data. The key in the map is the time and value is the number of people who have checked-in at that time.
private void addDataPointForCheckIn(Map<Integer, Long> map, String dateString) {
  int hour = DateUtils.getDate(dateString).getHour();
  Long numberOfCheckins = map.get(hour);

  if (numberOfCheckins == null)
      numberOfCheckins = 0L;

  map.put(hour, ++numberOfCheckins);
}
  • After the map is created it is passed on to the setDataForCheckIn( ) and the label is provided as “check-in times”. Following is the code for setDataForCheckIn( ). All the values of the map are parsed and a new entry object is made in which the value of the key and value pairs are passed. This object is then added to the ArrayList.
private LineDataSet setDataForCheckIn(Map<Integer, Long> map, String label) throws ParseException {
  List<Entry> entries = new ArrayList<>();
  for (Map.Entry<Integer, Long> entry : map.entrySet()) {
      entries.add(new Entry(entry.getKey(), entry.getValue()));
  }
  Collections.sort(entries, new EntryXComparator());

  // Add a starting point 2 hrs ago
  entries.add(0, new Entry(entries.get(0).getX() – 2, 0));
  return new LineDataSet(entries, label);
}
  • The object LineDataSet is returned with all the entries stored in the ArrayList. Now the prepare( ) is called. It is in this method that we add the code for the UI of the chart.
private void prepare() {
  if (isCheckinChart) {
      initializeLineSet(checkInDataSet, R.color.light_blue_500, R.color.light_blue_100);
      lineData.addDataSet(checkInDataSet);
  } else {
      initializeLineSet(freeSet, R.color.light_blue_500, R.color.light_blue_100);
      initializeLineSet(paidSet, R.color.purple_500, R.color.purple_100);
      initializeLineSet(donationSet, R.color.red_500, R.color.red_100);
      lineData.addDataSet(freeSet);
      lineData.addDataSet(paidSet);
      lineData.addDataSet(donationSet);
      lineData.setDrawValues(false);
  }
lineData.setDrawValues(false);
}

initializeLineSet( ) is the method where we add the color which will be used for plotting the data set.In our case the color is blue.

  • We also need to plot the time stamps in the X-axis. Unfortunately MPAndroidCharts doesn’t have a functionality for that. So to handle it an inner class MyMyAxisValueFormatter is created which extends IAxisValueFormatter. Following is the code for it.
public class MyAxisValueFormatter implements IAxisValueFormatter {

  @Override
  public String getFormattedValue(float value, AxisBase axis) {
      if (value < 0)
          return values[24 + (int) value];
      return values[(int) value];
  }
}

The values array list consists of the time stamps that will be present on the X-Axis.

String[] values = new String[] {“00:00”, “1:00”, “2:00”, “3:00”, “4:00”, “5:00”, “6:00”, “7:00”, “8:00”, “9:00”, “10:00”, “11:00”, “12:00”, “13:00”, “14:00”, “15:00”, “16:00”, “17:00”, “18:00”, “19:00”, “20:00”, “21:00”, “22:00”, “23:00”};
  • Finally the showChart( ) is called in which we specify details regarding the grid color, legend, visibility of X and Y axis etc. We also specify the animation that needs to be done whenever the chart is on screen.

 

public void showChart(LineChart lineChart) {
  lineChart.setData(lineData);
  lineChart.getXAxis().setEnabled(true);
  lineChart.getAxisRight().setEnabled(false);
  lineChart.getDescription().setEnabled(false);
  lineChart.getLegend().setEnabled(false);

  YAxis yAxis = lineChart.getAxisLeft();
  yAxis.setGridLineWidth(1);
  yAxis.setGridColor(Color.parseColor(“#992ecc71”));
  if (!isCheckinChart)
      if (maxTicketSale > TICKET_SALE_THRESHOLD)
          yAxis.setGranularity(maxTicketSale / TICKET_SALE_THRESHOLD);
  else {
      XAxis xAxis = lineChart.getXAxis();
      xAxis.setValueFormatter(new MyAxisValueFormatter());
      yAxis.setGranularity(1);
      lineChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM);
      lineChart.getXAxis().setGranularity(1f);
  }

  Description description = new Description();
  description.setText(“”);
  lineChart.setDescription(description);
  lineChart.animateY(1000);
}

 

Continue ReadingImplementing Check-in time chart in Orga App

Coloring Waveforms in PSLab Charts

Charts are used to display set of data in an analytical manner such that an observer can easily come to a conclusion by just looking at it without having to go through all the numerical data sets. Legends are used to differentiate a set of data set from another set. Generally, different colors and different names are used to form a legend in a chart.

MPAndroidChart is an amazing library with the capability of generating different types of graphs in an Android device. In PSLab several user interfaces are implemented using LineCharts to display different waveforms such as readings from channels attached to PSLab device, logic levels etc.

When several data sets are being plotted on the same graph area, legends are used. In PSLab Android application, Oscilloscope supports four different type of waveforms to be plotted on the same graph. Logic Analyzer implements one to four different types of logic level waveforms on the same plot. To identify which is which, legends with different colors can be used rather than just the names. For the legends to have different colors, it should be explicitly set which color should be held by which data set. Otherwise it will use the default color to all the legends making it hard to differentiate data lines when there are more than one data set is plotted.

Assume a data set is generated from a reading taken from a probe attached to PSLab device. The set will be added as an Entry to an array list as follows;

ArrayList<Entry> dataSet = new ArrayList<Entry>();

The next step will be to create a Line Data Set

LineDataSet lineData = new LineDataSet(dataSet, "DataSet 1");

This LineDataSet will contain sample values of the waveform captured by the microprocessor. A LineDataSet object support many methods to alter its look and feel. In order to set a color for the legend, setColor() method will be useful. This method accepts an integer as the color. This method can be accessed as follows;

lineData.setColor(Color.YELLOW);

MPAndroidChart provides different sets of colors under ColorTemplate. This class has several predefined colors with five colors in each color palette are added by the developers of the library and they can be accessed using the following line of code by simply calling the index value of the palette array list.

set1.setColor(ColorTemplate.JOYFUL_COLORS[0]);

Set of color palettes available in the ColorTemplate class are;

  1. LIBERTY_COLORS
  2. JOYFUL_COLORS
  3. PASTEL_COLORS
  4. COLORFUL_COLORS
  5. VORDIPLOM_COLORS
  6. MATERIAL_COLORS

The following demonstrates how the above activities produce a line chart with three different data sets with different colored legends.

This implementation can be used to enhance the readability of the waveforms letting user being able to differentiate between one waveform from another in PSLab Android application.

Resources:

PSLab official web site: https://pslab.fossasia.org/

Continue ReadingColoring Waveforms in PSLab Charts

Dynamic Ticket Analysis UI using Data Binding in Open Event Android Orga App

Any event manager application has the responsibility to show the analytics about the event to the organiser and in Open Event Android Orga App (Github Repo), we wanted to achieve a way to display the analytics of total and sold tickets with the data present to us.
To analyse, we have a list of tickets, which are divided into 3 categories:

  • Free
  • Paid
  • Donation

Our goal was to show information about total tickets and the amount of sold tickets per category. This blog will focus on the dynamic UI creation for the ticket analysis component of the Event Details Dashboard using Android Layout Data Binding. By using Data Binding, we not only reduced the amount of Java Boilerplate code we would have to write, but also accomplished UI reuse in just XML which wouldn’t have been possible without it. You’ll see in a moment what I mean.

Properties

So first, we’d need to define some properties which will be bound in the UI. These properties are declared in the Event model and their type is ObservableLong provided by the Android DataBinding package. The reason why we are using these instead of primitives is because these fields being Observable, will update the UI as soon as they are updated, without requiring the programmer to set the View Property at all.

There are six fields, 3 for total tickets of each type and 3 for sold tickets

public final ObservableLong freeTickets = new ObservableLong();
public final ObservableLong paidTickets = new ObservableLong();
public final ObservableLong donationTickets = new ObservableLong();

public final ObservableLong soldFreeTickets = new ObservableLong();
public final ObservableLong soldPaidTickets = new ObservableLong();
public final ObservableLong soldDonationTickets = new ObservableLong();

Some more advantages we get from using these are the batch view update and the use of computed properties in UI. Imagine having a TextView display the amount of free tickets and a progress bar showing the percentage of free tickets sold. Traditionally, you’d have to set the text and compute the percentage and set the progress bar as the data changes, whereas you can just use the fields in layout as is in both TextView and ProgressBar with the computations required and they’ll work in harmony.

We have leveraged this feature to show the analytics component of tickets with a

  • Ticket Type
  • Circular Progress Bar
  • Sold Tickets
  • Total Tickets

All using the XML layout and databinding

Ticket Component

For each ticket component, we have 4 variables, namely

  • Ticket Type Name
  • Total Amount
  • Completed Amount (Sold Tickets)
  • Color

First 3 are fairly self explanatory, the color attribute we used in our component needs a little bit of description. We decided to give each ticket category its own color for circular progress bar for aesthetics. So, we need each component to have its own color attribute too. But this is not a normal android color ID or a hex. We needed 2 variants of the same color to show in the circular progress to discern the total and completed part. As we are using Material Color Palette, which has a color divided by intensities, we used 500 variant for completed portion and 100 (lighter) variant for the background of circular progress.

Let’s look at the layout now:

<data>
    <variable name="color" type="String" />
    <variable name="ticketName" type="String" />
    <variable name="total" type="long" />
    <variable name="completed" type="long" />
</data>

<LinearLayout
     android:orientation="vertical">
    <TextView
        android:text="@{ticketName}" />
    <FrameLayout>
        <com.mikhaellopez.circularprogressbar.CircularProgressBar
            app:circular_progress_color="@{color}"
            app:progress_with_animation="@{total == 0 ? 0 : (int) ((completed*100)/total)}" />

        <LinearLayout
            android:orientation="horizontal">

            <TextView
                android:text="@{completed}" />
            <TextView
                android:text='@{"/" + total}' />

        </LinearLayout>
    </FrameLayout>

    <TextView
        android:text='@{(total == 0 ? 0 : (int) ((completed*100)/total)) + "%"}' />

</LinearLayout>

Note: The layout snippet is not complete. Only attribute names to be discussed in the blog are shown for brevity

As you can see, after the data variable declarations, we have a CardView first showing the ticket name on top, and then we have a FrameLayout wrapping the circular progress and a textview showing the Sold/Total tickets.

Circular Progress Bar

Let’s discuss the circular progress first, we have used this library to create a circular progress bar, the two other attributes circular_progress_color and progress_with_animation are specific to Open Event Orga Application and we have created custom adapters for them:

@BindingAdapter("progress_with_animation")
public static void bindCircularProgress(CircularProgressBar circularProgressBar, int progress) {
    circularProgressBar.setProgressWithAnimation(progress, 500);
}

@BindingAdapter("circular_progress_color")
public static void bindCircularProgressColor(CircularProgressBar circularProgressBar, String colorName) {
    Context context = circularProgressBar.getContext();
    Resources resources = circularProgressBar.getResources();

    int color = ContextCompat.getColor(context, resources.getIdentifier(colorName + "_500", "color", context.getPackageName()));
    int bgColor = ContextCompat.getColor(context, resources.getIdentifier(colorName + "_100", "color", context.getPackageName()));

    circularProgressBar.setColor(color);
    circularProgressBar.setBackgroundColor(bgColor);
}
  • progress_with_animation sets the provided integer value as the progress of the circular progress bar with an animation of 500 ms
  • circular_progress_color finds the 100 and 500 variant of the color name string provided and sets them as background and foreground color of the progress bar

These are the color definitions we have used in the app:

<color name="light_blue_100">#B3E5FC</color>
<color name="light_blue_500">#03A9F4</color>
<color name="purple_100">#E1BEE7</color>
<color name="purple_500">#9C27B0</color>
<color name="red_100">#ffcdd2</color>
<color name="red_500">#f44336</color>

As you can that if we pass purple as the color name, it’ll load purple_100 and purple_500 and set it as corresponding background and foreground color

Other Properties

Now, let’s talk about other properties like the progress value :

  • total == 0 ? 0 : (int) ((completed*100)/total)The conditional is used to prevent divide by zero error.
    The same expression is used to display the circular progress and percentage text in the TextView at the bottom of the layout
  • completed and “/” + total are used to in TextViews of different sizes to create a nice design with completed/total format

This completes our ticket component design and now we’ll see how to reuse this component to display different ticket types.

Composite Layout

To use the ticket component, we just include the layout and bind specific variables from Event model to create a dynamic layout like this:

<data>
    <variable
        name="event"
        type="org.fossasia.openevent.app.data.models.Event" />
</data>

<LinearLayout
    android:orientation="vertical">

    <TextView
        android:text="@string/tickets" />

    <LinearLayout
        android:orientation="horizontal">

        <include
            layout="@layout/ticket_analytics_item"
            bind:color='@{"light_blue"}'
            bind:completed="@{event.soldFreeTickets}"
            bind:ticketName="@{@string/ticket_free}"
            bind:total="@{event.freeTickets}" />

        <include
            layout="@layout/ticket_analytics_item"
            bind:color='@{"purple"}'
            bind:completed="@{event.soldPaidTickets}"
            bind:ticketName="@{@string/ticket_paid}"
            bind:total="@{event.paidTickets}" />

        <include
            layout="@layout/ticket_analytics_item"
            bind:color='@{"red"}'
            bind:completed="@{event.soldDonationTickets}"
            bind:ticketName="@{@string/ticket_donation}"
            bind:total="@{event.donationTickets}" />
    </LinearLayout>

</LinearLayout>

The layout consists of a horizontal with 3 equally divided ticket components,

  • Free Ticket Component -> Light Blue
  • Paid Ticket Component -> Purple
  • Donation Ticket Component -> Red

This is how it looks on a device

 

So this is how data binding made us accomplish easily which would have been a very convoluted solution using traditional ID based view binding. For more info about data binding, refer to these sites:

https://developer.android.com/topic/libraries/data-binding/index.html

http://www.vogella.com/tutorials/AndroidDatabinding/article.html

Continue ReadingDynamic Ticket Analysis UI using Data Binding in Open Event Android Orga App