Prevent Android Activity from Operating while using Bottom Sheet in PSLab App

This blog demonstrates how to prevent the Android Activity in the background from operating while the Bottom Sheet is up in the foreground. The demonstration will be purely from the work I have done under PR #1355 in PSLab Android repository.

Why prevent the Activity from operating?

When using Bottom Sheet in Android, it is preferable to dim the screen behind the Bottom Sheet to provide a good user experience. But the dimming of the screen is itself an indication that the screen won’t work. Also, if the Bottom Sheet is open and while sliding it, if, by mistake, any button in the background of the bottom sheet gets pressed, then if the function related to that button starts executing then it can create a bad user experience.

For example, in PSLab Android app, in Accelerometer instrument, there are record/pause and delete buttons in the toolbar as shown in figure 1. Now, if the bottom sheet is opened and while closing it if the delete button is by mistake pressed by the user, then whole recorded data gets deleted. Thus, it’s a good practice to prevent the background Activity from operating while Bottom Sheet is opened.

Figure 1. Accelerometer Instrument in PSLab Android app

How to prevent the Activity from operating?

In this demonstration, I will use the method followed by PSLab Android app in creating a Bottom Sheet and making the background dim using a View widget. A step by step guide on how to make a Bottom Sheet as in PSLab Android app can be found in [1] and [2].

Strategy

The strategy used in solving this problem is setting an OnClickListener to the View that is used to dim the background and close the Bottom Sheet (if open) and hide the View as soon as the method is called. The View is again made visible when an upward slide gesture is made to open the Bottom Sheet.

Follow the below steps to get the desired results:

  • First, in OnCreate() method, set the OnTouchListener to the view.
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
                              if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED)
                    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
tvShadow.setVisibility(View.GONE);
      }
});
  • Now, override the OnSlide() method of the GestureDetector class and add the following code to it.
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
    Float value = (float) MathUtils.map((double) slideOffset, 0.0, 1.0, 0.0, 0.8);
    view.setVisibility(View.VISIBLE);
    view.setAlpha(value);
   }

So, now test the Bottom Sheet and you will find that the Bottom Sheet will get closed as soon as the click is made outside it if it is opened. The demonstration of the working of the above code is shown in figure 2.

Figure 2. Demonstration of preventing the background Activity from operating while Bottom Sheet is up

Resources

  1. http://thetechnocafe.com/make-bottom-sheet-android/: Blog on how to make a Bottom Sheet in Android
Continue ReadingPrevent Android Activity from Operating while using Bottom Sheet in PSLab App

How to use Mobile Sensors as Instruments in PSLab Android App

This blog demonstrates how to use built-in mobile sensors in an Android application. This blog will mainly feature my work done in PSLab Android repository of making a Compass and Accelerometer instrument using built-in mobile sensors.

How to access built-in mobile sensors?

Android provides an abstract class called SensorManager which is able to communicate with the hardware i.e. here the sensors in the mobile. But the SensorManager can’t provide continuous data fetched by the sensor. For this, Android provides an interface known as SensorEventListener which receives notifications from SensorManager whenever there is a new sensor data.

How to implement the functionality of sensors in Android app?

Following is a step by step process on how to add support for different sensors in an Android app

  • First, make a new class which extends SensorEventListener and override the default methods.
public class SensorActivity extends Activity implements SensorEventListener {

     public SensorActivity() {
        // Default Constructor      
     }

     @Override
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
     }

     @Override
     public void onSensorChanged(SensorEvent event) {
     }
 }

Here, the SensorActivity() is the default constructor of the class and the onAccuracyChanged() and onSensorChanged() methods will be explained soon.

  • Now declare the SensorManager and use the sensor needed in the app.
private final SensorManager mSensorManager;
private final Sensor mAccelerometer;

     public SensorActivity() {
         mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
         mAccelerometer =        mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
     }

Here, I have used Sensor.TYPE_ACCELEROMETER to use the built-in Accelerometer in the device. Some of the other options available are:

  1. TYPE_LIGHT – To measure ambient light
  2. TYPE_MAGNETOMETER – To measure magnetic field along different axis
  3. TYPE_GYROSCOPE – To measure movements (sudden changes) in any particular direction

The list of all available sensors in Android can be found in [1].

  • It is necessary to disable the sensors especially when the activity is paused. Failing to do so can drain the battery in just a few hours.

NOTE: The system will not disable sensors automatically when the screen turns off.

So, to save the battery and make the app efficient, we can use the registerListener method to notify the SensorManager to start fetching data from sensor and unregisterListener to notify it to stop.

@Override
protected void onResume() {
         super.onResume();
         mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
     }

@Override
     protected void onPause() {
         super.onPause();
         mSensorManager.unregisterListener(this);
     }


The onResume() method activates when the app is resumed from a paused state and the onPause() method is called when the app is paused i.e. some other app draws over the current app.

  • Now coming back to onAccuracyChanged() and onSensorChanged() methods, the onAccuracyChanged() method is used to set the accuracy of a sensor. For example, while using GeoLocation sensor, sometimes the position of the mobile isn’t very accurate and so we can define the accuracy level in this method so that the fetched data is used for calculations only if it is in the provided range. And the onSensorChanged() method is the main method where all the data is processed as soon as the new data is notified.

To get the latest value from the sensor, we can use

@Override
public void onSensorChanged(SensorEvent event) {
   data = Float.valueOf(event.values[0]);
   unRegisterListener();
}

Here, the event is an instance of the SensorEvent class which provides the updated data fetched from the sensor. Event.values is used to get the values for any of the three axis including the bias in their values. Following is the list of the index for which we can get a necessary value

values[0] = x_uncalib without bias compensation
values[1] = y_uncalib without bias compensation
values[2] = z_uncalib without bias compensation
values[3] = estimated x_bias
values[4] = estimated y_bias 
values[5] = estimated z_bias

So, in this way, we can add support for any built-in mobile sensor in our Android application.

Resources

Continue ReadingHow to use Mobile Sensors as Instruments in PSLab Android App

How to Add Icons or Menus into the PSLab Android App Toolbar

This blog demonstrates how to add different icons like help, play, pause, etc. and/or menu bar in the toolbar of an Android app along with setting their visibilities on the toolbar i.e. to display the icons only when space is available else to add them in the menu. The topic will be mainly explained by taking the example of menus and icons added to the PSLab app.

How to add a menu in a toolbar?

Following are the steps to add a menu or an icon in the toolbar widget of the Android app

  • First, add toolbar widget to the main layout file as follows
<android.support.v7.widget.Toolbar
   android:id="@+id/compass_toolbar"
   android:layout_width="match_parent"
   android:layout_height="?attr/actionBarSize"
   android:background="?attr/colorPrimary"
   app:popupTheme="@style/AppTheme.PopupOverlay"
   app:title="@string/compass" />

Here, popupTheme is the theme that activates when inflating the toolbar. Usually, it is kept similar to the default theme of the toolbar.

  • Now as the toolbar is ready, we can make the menu that needs to be inflated on the toolbar. For making a menu, make a folder named menu in the resources folder. Now, add a menu resource file in it by giving a proper name and then add the following code
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <item
       android:id="@+id/compass_help_icon"
       android:icon="@drawable/compass_help_icon"
       android:title="@string/show_axis_help"
       app:showAsAction="always" />
</menu>

A detailed explanation of the above code is as follows:

  1. The <menu>…</menu> covers all the items in the menu. There can be sub-menu and also sub-sub-menu too. To make a sub-menu, use <menu>…</menu> inside the main menu.
  2. The <item> tag inside the menu defines a specific item to be included in the menu. The icon attribute of an item is used to show the icon on the toolbar. The title attribute of an item is used to show the text inside the menu if space isn’t available to show the icon on the toolbar. The showAsAction attribute is used to define the method of an item i.e. how the item should be visible to the user. Following are some of the values that showAsAction attribute can take:
    • always – It is used to show the icon of the item on the toolbar everytime
    • never – It is used to show the item as a text in the menu everytime the activity is opened
    • ifRoom – It is used to show the icon on the toolbar if there is enough space else the item is included in the menu

NOTE: Always give IDs to menu items as they are used to distinctly identify the item in the java code.

Figure 1. Example of menu and icons in toolbar in PSLab app

As shown in figure 1, the first two icons have always value in their showAsAction attribute whereas other items have never values in their showAsAction attribute.

  • Now the layout and the menu are ready to be inflated from the Java code. First, the toolbar needs to be set up from the Java code. So find the toolbar with its id and then write the following line in the code.
setSupportActionBar(mToolbar);
  • Now the toolbar is ready and so the menu can be inflated on it. So, override the following method to inflate the menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
   MenuInflater inflater = getMenuInflater();
   inflater.inflate(R.menu.activity_compass_help_menu, menu);
   return true;
}

Here, the getMenuInflater() method is used to inflate the menu on the toolbar.

  • Now override the onCreateOptionsMenu() method to do the predefined task of selecting the icon or the item from the menu.
@Override
public boolean onOptionsItemSelected(MenuItem item) {
   switch (item.getItemId()) {
       case R.id.compass_help_icon:
           // Do something
           break;
       default:
           break;
   }
   return true;
}

So, in this way a menu can be made so that the number of items delivered to the user can be increased by using the minimum space possible.

Resources

  1. https://developer.android.com/guide/topics/ui/menus – Android Developers guide on how to make a menu in Android
Continue ReadingHow to Add Icons or Menus into the PSLab Android App Toolbar

Implementing Notification Action Buttons in Open Event Frontend

The Open-Event-Frontend allows the event organiser to create access codes for his or her event.  Access codes can be used to password protect hidden tickets reserved for sponsors, members of the press and media. Notifications are an important part of the project. We show each registered user notifications based on their activity. This blog post goes over the implementation of the notification action buttons in the notification panel.

Notification Action Model

The model for Notification action is very simple. It has the following variables:

  1. Subject: The subject of the notification. E.g. ‘event’, ‘order’ etc.
  2. actionType: The action that can be taken by the user for that notification. E.g: ‘view’, ‘submit’.
  3. subjectId: The id of the subject. In case of an event, it will store the event id. Similarly for other cases.
  4. Link: The link to be applied to the button.

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

export default ModelBase.extend({
  subject    : attr('string'),
  actionType : attr('string'),
  subjectId  : attr('number'),
  link       : attr('string'),

  notification: belongsTo('notification')
});

Action Button Title

We make use of ember computed property to determine the action button title. The title of the button depends on the subject and the actionType defined in the notification-action model. The actionType can be one of ‘download’, ‘submit’ and ‘view’. If the action type is ‘download’ and the subject is ‘invoice’, then the button title will be “Download Invoice”. Similarly, for other cases, we do the same.

buttonTitle: computed('subject', 'actionType', function() {
    let action;
    const actionType = this.get('actionType');
    switch (actionType) {
      case 'download':
        action = 'Download';
        break;

      case 'submit':
        action = 'Submit';
        break;

      default:
        action = 'View';
    }

    let buttonSubject;
    const subject = this.get('subject');
    switch (subject) {
      case 'event-export':
        buttonSubject = ' Event';
        break;

      case 'event':
        buttonSubject = ' Event';
        break;

      case 'invoice':
        buttonSubject = ' Invoice';
        break;

      case 'order':
        buttonSubject = ' Order';
        break;

      case 'tickets-pdf':
        buttonSubject = ' Tickets';
        break;

      case 'event-role':
        buttonSubject = ' Invitation Link';
        break;

      case 'session':
        buttonSubject = ' Session';
        break;

      case 'call-for-speakers':
        if (this.get('actionType') === 'submit') {
          buttonSubject = ' Proposal';
        } else {
          buttonSubject = ' Call for Speakers';
        }
        break;

      default:
        // Nothing here.
    }

    return action + buttonSubject;
  })

Action Button Route

The route that the button will lead to depends on the subject of the action. If the link is provided in the notification action, we simply set it on the button otherwise we use the subject to derive the route name. For e.g., if the subject is an event, then the route will be “events.view”.

/**
   * The route name to which the action button will direct the user to.
   */
  buttonRoute: computed('subject', function() {
    const subject = this.get('subject');
    let routeName;
    switch (subject) {
      case 'event-export':
        routeName = 'events.view';
        break;

      case 'event':
        routeName = 'events.view';
        break;

      case 'invoice':
        routeName = 'orders.view';
        break;

      case 'order':
        routeName = 'orders.view';
        break;

      default:
      // Nothing here.
    }
    return routeName;
  })

Template

We simply check if the link exists or not. If it does then we simply use it otherwise we use the computed button route name.

{{#if action.link}}
     {{#link-to action.link tagName='button' class='ui blue button'}}
         {{t action.buttonTitle}}
         {{/link-to}}
{{else}}
    {{#link-to action.buttonRoute action.subjectId tagName='button' class='ui blue button'}}
         {{t action.buttonTitle}}
         {{/link-to}}
{{/if}}

References

Continue ReadingImplementing Notification Action Buttons in Open Event Frontend

Integrating System Roles API in Open Event Frontend

The Eventyay system supports different system roles and allows to set panel permissions for every role. The system supports two inbuilt roles namely Admin and Super Admin. The users having access to permissions panel can create new custom system roles and define set of panel permissions for them. Also the users are provided with the option of editing and deleting any system role except the two inbuilt system roles. The feature is implemented using custom-system-roles and panel-permissions API on the server.

Adding route for system-roles

The route for custom-system-system roles is defined which contains a model returning user permissions, system roles and the panel permissions. The model is defined as async so that the execution is paused while fetching the data from the store by adding the await expression.

async model() {
 return {
   userPermissions  : await this.get('store').findAll('user-permission'),
   systemRoles      : await this.get('store').findAll('custom-system-role'),
   panelPermissions : await this.get('store').findAll('panel-permission')
 };
},

The route created above gets all the data for user permissions, system-roles and panel permissions which is later used by the template for rendering of data.

Adding model for system-roles and panel-permissions

The model for system-roles is created which contains the ‘name’ attribute of type string and a relationship with panel permissions. Every system role can have multiple panel permissions, therefore a hasMany relationship is defined in the model.

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

 panelPermissions: hasMany('panelPermission')
});

Similarly, the model for panel-permissions is added to the models directory. The defined model contains ‘panelName’ as an attribute of type string and a bool value canAccess, defining if the panel is accessible by any role or not.

export default ModelBase.extend({
 panelName : attr('string'),
 canAccess : attr('boolean')
});

Defining controller for system-roles

The controller for system-roles is defined in the controllers/admin/permissions directory. The action for adding, updating and deleting system roles are defined in the controller. While adding the system roles, all the panels are fetched and checked which panel permissions are selected by the admin. A special property namely ‘isChecked’ is added to every panel permission checkbox which toggles on change. If the property is set true the corresponding panel is added to the panel permissions relationship of corresponding role. If no panel is selected, an error message to select atleast one panel is displayed.

deleteSystemRole(role) {
 this.set('isLoading', true);
 role.destroyRecord()
  ...
  // Notify success or failure
},
addSystemRole() {
 this.set('isLoading', true);
 let panels = this.get('panelPermissions');

 panels.forEach(panel => {
   if (panel.isChecked) {
     this.get('role.panelPermissions').addObject(panel);
   } else {
     this.get('role.panelPermissions').removeObject(panel);
   }
 });
 if (!this.get('role.panelPermissions').length) {
  // Notification to select atleast one panel
 } else {
   this.get('role').save()
    // Notify success or failure
 }
},
updatePermissions() {
 this.set('isLoading', true);
 this.get('model.userPermissions').save()
  ...
  // Notify success or failure
}

The actions defined above in the controller can be used in template by passing the appropriate parameters if required. The addSystemRole action makes a POST request to server for creating a new system role, the updatePermissions action makes a PATCH request for updating the existing system role and the deleteSystemRole action makes a delete request to the server for deleting the role.

Adding data to template for system-roles

The data obtained from the model defined in route is rendered in the template for system-roles. A loop for showing all system roles is added to the template with the name attribute containing the name of system role and another loop is added to display the panel permissions for the corresponding role.

{{#each model.systemRoles as |role|}}
 <tr>
   <td>{{role.name}}</td>
   <td>
     <div class="ui bulleted list">
       {{#each role.panelPermissions as |permission|}}
         <div class="item">{{concat permission.panelName ' panel'}}</div>
       {{/each}}
     </div>
   </td>
   <td>
    // Buttons for editing and deleting roles
   </td>
 </tr>
{{/each}}

A modal is to the component for creating and editing system roles. The data from this template is passed to the modal where the existing permissions are already checked and can be modified by the admins.

Resources

Continue ReadingIntegrating System Roles API in Open Event Frontend

Integrating Event Roles API in Open Event Frontend

The Eventyay system supports different type of roles for an event like Attendee, organizer, co-organizer, track-organizer, moderator and the registrar. Every role has certain set of permissions such as Create, Read, Update, Delete. The Admin of the system is allowed to change the permissions for any role. The interface for updating the even role permissions was already available on the server but was not integrated on the frontend. The system is now integrated with the API and allows admin to change event role permission for any role.

Adding model for event role permissions

The model for event role permissions is added to the models directory. The model contains the attributes like canDelete, canUpdate, canCreate, canRead and the relationship with event role and the service.

export default ModelBase.extend({
 canDelete : attr('boolean'),
 canUpdate : attr('boolean'),
 canCreate : attr('boolean'),
 canRead   : attr('boolean'),

 role        : belongsTo('role'),
 service     : belongsTo('service'),
 serviceName : computed.alias('service.name')
});

The above defined model ensures that every permission belongs to a role and service. An alias is declared in the model using the computed property which is later used in the controller to sort the permissions according to service name in lexicographical order.

Adding route for event roles

The route for event role is created which contains model returning an object containing the list of roles, services and permissions. The model is defined as async so that the execution is paused while fetching the data from the store by adding the await expression.

export default Route.extend({
 titleToken() {
   return this.get('l10n').t('Event Roles');
 },
 async model() {
   return {
     roles       : ['Attendee', 'Co-organizer', 'Moderator', 'Organizer', 'Track Organizer', 'Registrar'],
     services    : await this.get('store').query('service', {}),
     permissions : await this.get('store').query('event-role-permission', { 'page[size]': 30 })
   };
 }
});

The route created above queries the data for roles, services and permissions which is later used by the template for rendering of the data obtained.

Adding controller for event roles

The controller for event roles is added to the controllers/admin/permissions directory. The computed property is used to sort the services obtained from model lexicographically and the permissions are sorted by the help of alias created in the model.

services: computed('model', function() {
 return this.get('model.services').sortBy('name');
}),
sortDefinition : ['serviceName'],
permissions    : computed.sort('model.permissions', 'sortDefinition'),
actions        : {
 updatePermissions() {
   this.set('isLoading', true);
   this.get('model.permissions').save()
     .then(() => {
       // Notify success and add Error handler
      }
   }
}

An action named updatePermissions is defined which is triggered when the admin updates and saves the permissions for any role where a PATCH request is made to the server in order to update the permissions.

Rendering data in the template

The data obtained from the model is manipulated in the controller and is rendered to the table in the event-roles template. Every role is fetched from the model and added to the template, all the permissions in sorted order are obtained from the controller and matched with the current role name. The relationship of permissions with role is used to check if its title is equal to the the current role. The permissions are updated accordingly, if the role title is equal to current role.

<tbody>
 {{#each model.roles as |role|}}
   <tr>
     <td>{{role}}</td>
     {{#each permissions as |permission|}}
       {{#if (eq permission.role.titleName role)}}
         <td>
           {{ui-checkbox label=(t 'Create') checked=permission.canCreate onChange=(action (mut permission.canCreate))}}
           <br>
           {{ui-checkbox label=(t 'Read') checked=permission.canRead onChange=(action (mut permission.canRead))}}
           <br>
           {{ui-checkbox label=(t 'Update') checked=permission.canUpdate onChange=(action (mut permission.canUpdate))}}
           <br>
           {{ui-checkbox label=(t 'Delete') checked=permission.canDelete onChange=(action (mut permission.canDelete))}}
         </td>
       {{/if}}
     {{/each}}
   </tr>
 {{/each}}
</tbody>

After rendering the data as shown above, the checkbox for permissions of different services for different roles are checked or unchecked depending upon the bool value of corresponding permission. The admin can update the permissions by checking or unchecking the checkbox and saving the changes made.

Resources

Continue ReadingIntegrating Event Roles API in Open Event Frontend

Adding Speakers Page in Open Event Frontend

Open Event Frontend earlier displayed all the speakers of an event on the main info page only, now a separate route for speakers is created and a separate page is added to display the speakers of an event. The design and layout of speakers page is kept similar to that on Open Event Web app. The info page only shows the featured speakers for an event and the complete list of speakers with additional information is present on speakers route.

Getting the event speakers data

The event data is obtained from the public model and a query is made for the speakers to get the required data. The speakers are fetched only for the sessions which are accepted, this is done by applying a filter while the query is made.

async model() {
 const eventDetails = this.modelFor('public');
 return {
   event    : eventDetails,
   speakers : await eventDetails.query('speakers', {
     filter: [
       {
         name : 'sessions',
         op   : 'any',
         val  : {
           name : 'state',
           op   : 'eq',
           val  : 'accepted'
         }
       }
     ]
   })
 };
}

Adding template for displaying speakers

A template is added to display three speakers in a row. The speakers data obtained from the model is looped through and details of every speaker is passed to the speaker-item component, which handles the design and layout for every item in the speakers list.

<div class="ui stackable grid container">
 {{#each model.speakers as |speaker|}}
   <div class="five wide column speaker-column">
     {{public/speaker-item speaker=speaker}}
   </div>
 {{/each}}
</div>

Adding component for speaker-item

A component for displaying the speaker-item is added to templates/component/public directory. The component contains of an accordion which displays the speaker details like biography, social links and the sessions that would be taken by him.

{{#ui-accordion}}
 <div class="title">
   <div class="ui">
     <img alt="speaker" class="ui medium rounded image" src="{{if speaker.photo.iconImageUrl speaker.image '/images/placeholders/avatar.png'}}">
    ...
    ... 
    ...
    // Speaker Details
   </div>
 </div>
{{/ui-accordion}}

The accordion with speaker image and other details appears for every speaker of an event.

Resources

Continue ReadingAdding Speakers Page in Open Event Frontend

Implementing the PDF download of Schedule in Open Event Web app

Open Event Web app now provides an option to its users to download the PDF of event schedule. Earlier it supported the download of list-view only, now it provides the support to download calendar-view as well. The problem incurred while downloading the calendar-view was that the view gets cropped due to limitations with the library used for PDF generation, thus only some parts of the calendar remained in the PDF. The problem is resolved by creating an image for every date in the schedule and adding the generated image to the PDF.

Selecting and adding the data for PDF generation

The data to be added to PDF depending on the filters and date-selectors applied is chosen from the DOM. Selection of data is done by looping through all the dates and adding only the ones which do not have ‘hide’ class added to them. The selected dates are first expanded such that their complete view is available while generating the image. The complete data is stored in a variable depending on if the complete schedule is requested for download or some filter is applied, which is later used for generating the image.

let fullScheduler = true;
let mapValue = '';

pdf = new jsPDF('l', 'pt', 'a1');
$('.calendar').each(function() {
 let hidePresent = $(this).attr('class').split(' ').indexOf('hide') <= 0;

 if (hidePresent) {
   $timeline = $(this);

  // Expanding the schedule for current date
  ...
  ...
 }
 fullScheduler = hidePresent && fullScheduler;
});

if(fullScheduler) {
 $timeline = $('.calendar').parent();
 mapValue = $timeline.children();
}

Adding the notification while generating the PDF

A loader with the notification is added to provide better user experience, as the PDF generation takes place at the time of request itself it may take some time depending on the size of the schedule. The notifications are added using ‘sweetalert’ library already added for Add to calendar notifications.

swal("Generating the PDF",{
 icon: "./images/loader.gif",
 buttons: false
});

Downloading the PDF

The selected dates are stored in an array named ‘schedArr’ whose data sequentially is passed for canvas generation. A new page is added to the PDF of size equal to canvas and the generated canvas is added to that page. With every new page added to the calendar a count is increased to keep a track if all the selected dates are added to PDF.

async.eachSeries(schedArr, function (child, callback) {
 html2canvas(child, {
   onrendered: function (canvas) {
     pdf.addPage(canvas.width, canvas.height);
     child.style.width = initialWidth[count] + 'px';
     pdf.addImage(canvas, 'png', 0, 0, canvas.width, canvas.height);
     currDate++;
     if(currDate === schedArr.length){
       pdf.deletePage(1);
       swal.close();
       pdf.save(scheduleDate + '.pdf');
     }
     count++;
     callback();
   }
 });
});

When the last page is added, the notification is closed and user is prompted to download pop-box.

Resources

Continue ReadingImplementing the PDF download of Schedule in Open Event Web app

Integrating Google Calendar API in Open Event Web app

Open Event Web app allows organizers to add a feature which enables the users to add any session to their google calendar. The organizer is required to generate an API key and client ID on Google developers console and add the generated credentials to web app generator. The credentials are added to the generated application and every session is added with an Add to calendar button, which on click makes the request to add the corresponding session to the calendar.

Creating Client ID and API key

Enable the Google calendar API from Google developers console. Go to `Create Credentials` tab and generate an API key and client ID for your app. While creating the client ID, an input field is present which requires Authorised javascript origins, mention the domains where the generated application would be deployed.

Adding Client ID and API key to the generator

The Client ID and API key obtained from the developer console is added to the web app generator. The event generated uses these credentials to make a request to the server for adding any session to the calendar.

The added credentials are used to initialise the client in the procedure `initClient()` –

function initClient() {
 let id = document.getElementById('gcalendar-id').value;
 let key = document.getElementById('gcalendar-key').value;
 let CLIENT_ID = id;
 let API_KEY = key;
 let DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"];
 let SCOPES = "https://www.googleapis.com/auth/calendar";

 gapi.client.init({
   apiKey: API_KEY,
   clientId: CLIENT_ID,
   discoveryDocs: DISCOVERY_DOCS,
   scope: SCOPES
 })
}

Adding session to the Google calendar

Every Google calendar enabled event is provided a button with every session, so that corresponding session can be added to the calendar. A procedure named `handleAuthClick` is called with the details of session being passed as parameter when the user clicks on the button. This function handles the authentication required for adding session to the calendar.

function handleAuthClick(title, location, calendarStart, calendarEnd, timezone, description) {
 let isSignedIn = gapi.auth2.getAuthInstance().isSignedIn.get();
 if (!isSignedIn) {
   gapi.auth2.getAuthInstance().signIn().then(function() {
     main.listUpcomingEvents(title, location, calendarStart, calendarEnd, timezone, description);
   });
 } else {
   main.listUpcomingEvents(title, location, calendarStart, calendarEnd, timezone, description);
 }
}

A function named `listUpcomingEvents` makes the request to insert the event object with details of the session to the calendar.

function listUpcomingEvents(title, location, calendarStart, calendarEnd, timezone, description) {
 let event = {
  ... // Event details  


  ... // Code for notifications
  ...
  ...
   'colorId': '5'
 };

 let request = gapi.client.calendar.events.insert({
   'calendarId': 'primary',
   'resource': event
 });
 request.execute(function(event) {
  // Success notification
 });
}

When the session with the corresponding data is added to calendar, an alert box notifying successful addition of session is shown up on the screen.

Resources

Continue ReadingIntegrating Google Calendar API in Open Event Web app

Delete Image Permanently from Trashbin in Phimpme App

In the Phimpme Android application, users can perform various operations on the images including renaming an image, sharing images, deleting images from the storage etc. However, with the implementation of the Trash Bin feature in the app, the user is now provided with the option to restore back the deleted images. Whenever the delete operation is performed, the selected images are moved to the Trash Bin and the user has the option to either delete the photos permanently or restoring back the deleted photos by navigating to the Trash bin section. So in this post, I’d be discussing the implementation of permanently deleting image/images from the Trashbin.

Step 1

Firstly, we need to add permanent delete option in the popup menu provided in the itemview in the TrashView section. Every item in the Trashbin section displays a popup menu with two options-restore and delete permanently. The permanent delete option has been implemented in the itemview by adding the following lines of code.

<?xml version=“1.0” encoding=“utf-8”?>
<menu xmlns:android=“http://schemas.android.com/apk/res/android” xmlns:app=“http://schemas.android.com/apk/res-auto”>

<item
    android:id=“@+id/delete_permanently”
    android:title=“@string/delete_permanently”
    app:showAsAction=“never” />
</menu>

Step 2

Now when the user opts to permanently delete any photo from the bin, a function deletePermanent would be invoked passing-in the trashbin object corresponding to the selected item as the parameter. Inside the deletePermanent method, a check is performed to determine whether the corresponding image file exists or not using the .exists method of the File class and if the result is true the file is deleted permanently using the .delete method of the File class. The method deletePermanent returns a boolean value depending on whether the image file is deleted permanently from the storage or not. The code snippets used to implement the deletePermanent method is provided below.

private boolean deletePermanent(TrashBinRealmModel trashBinRealmModel){
  boolean succ = false;
  String path = trashBinRealmModel.getTrashbinpath();
  File file = new File(Environment.getExternalStorageDirectory() + “/” + “.nomedia”);
  //File file = new File(Environment.getExternalStorageDirectory() + “/” + “TrashBin”);
  if(file.exists()){
      File file1 = new File(path);
      if(file1.exists()){
          succ = file1.delete();
      }
  }
  return succ;
}

Step 3

The function deletePermanent used implemented in the previous step returns a boolean value which will be used in this step. So if the deletePermanent method returns true indicating that the image has been deleted from the trashbin, first a method deleteFromRealm is invoked passing-in the path of the image in the Trashbin to delete the corresponding image’s record from the realm database. Thereafter, that particular trashbin item is removed from the ArrayList<TrashbinRealmModel> populating the TrashBin adapter to display the items in the TrashBin Activity and the adapter is notified of this change by the use of NotifyItemRemoved and NotifyItemRangeChanged methods of the RecyclerView adapter class passing-in the position of the item as parameter to the former and position along with the size of the updated list as parameters to the latter function. After the adapter is updated about the change in the dataset, the adapter re-populates the recyclerview thus displaying the remaining items in the trashbin section. The code snippets implementing the above-mentioned operations are provided below.

if(deletePermanent(trashBinRealmModel)){
  deleteFromRealm(trashItemsList.get(position).getTrashbinpath());
  trashItemsList.remove(position);
  notifyItemRemoved(position);
  notifyItemRangeChanged(position, trashItemsList.size());
}
private void deleteFromRealm(final String path){
  Realm realm = Realm.getDefaultInstance();
  realm.executeTransaction(new Realm.Transaction() {
      @Override public void execute(Realm realm) {
          RealmResults<TrashBinRealmModel> trashBinRealmModels = realm.where(TrashBinRealmModel.class).equalTo
                  (“trashbinpath”, path).findAll();
          trashBinRealmModels.deleteAllFromRealm();
      }
  });
}

This is how we have implemented the functionality to permanently delete an image from the Trashbin section 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 documentation –https://developer.android.com/reference/java/io/File
  2. Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/
  3. Realm database operations Android – https://www.androidhive.info/2016/05/android-working-with-realm-database-replacing-sqlite-core-data/
Continue ReadingDelete Image Permanently from Trashbin in Phimpme App