Adding an option to hide map in browse events.

Open Event provides filtering while browsing events. These filters are present in a sidebar which also consists of a map. In this blog, I will describe how I implemented the feature to toggle the visibility of the map present in sidebar for mobile devices.

About the issue

This issue was part of improvements decided for the browse events section. Earlier the map in sidebar was shown irrespective of the user’s device. It is essentially not required to always show the map in mobile device and is a better choice to provide user with an option to view or hide the map.

Sidebar as viewed from an android device before the fix was merged.

The Solution

A button is introduced in the mobile view which controls if the map should be visible or hidden. In the sidebar component (app/components/explore/side-bar.js), a variable “isMapVisible” which decides if the map should be visible (if it is true) or not (if it is false) at a particular instant. A new action “toggleMap” is written which changes the value of “isMapVisible” whenever the button is clicked.

isMapVisible = true;
@action
  toggleMap() {
    this.toggleProperty(‘isMapVisible’);
  }

In the handlebar file (app/templates/components/explore/side-bar.hbs), the map and associated text is changed as per the truthy or falsy value of the “isMapVisible” in the component.

<div class=”map item {{if (not isMapVisible) ‘mobile hidden’}}”>

{{#if device.isMobile}}
  <div class=”ui bottom attached button” role=”button” {{action ‘toggleMap’}}> {{if (not isMapVisible) ‘Show’ ‘Hide’}} Map </div>
{{/if}}

After making the changes, the sidebar looks as follows on the mobile devices

The above images are from an Android device.

Resources:

Issue: https://github.com/fossasia/open-event-frontend/issues/3122
Pull Request: https://github.com/fossasia/open-event-frontend/pull/3444

Ember Docs:https://guides.emberjs.com/v2.14.0/tutorial/simple-component/Toggle Component Tutorial: https://www.learnhowtoprogram.com/ember-js/ember-js/components-hide-show-image

Continue Reading

Addition of new filters for event search

Open Event has an event search provided but it lacked two of the userful filters which will make searching for an event for a user easier than the current ecosystem. In this blog post, I describe how I implemented filtering of events on the basis of CFP status and Ticket Type.

About the issue

Addition of these two filters were subparts of improving the browse events page better. The browse events page currently functions in an optimal way but these improvements make it even better.

How the filters are added

For adding these two filters, it was required that the request sent to server to filter the event must contain ticket type and cfp so that the results from the server can be received. To achieve this, the frontend code needed the following changes:

  1. Passing of request variable into sidebar component present in app/templates/explore.hbs and addition of a clear filter for the same in the same file.e startDate=start_date endDate=end_date location=location ticket_type=ticket_type}
{{explore/side-bar model=model category=category sub_category=sub_category event_type=event_type startDate=start_date endDate=end_date location=location ticket_type=ticket_type}}
{{#if filters.ticket_type}}
  <div class=”ui mini label”>
    {{ticket_type}}
    <a role=”button” {{action ‘clearFilter’ ‘ticket_type’}}>
      <i class=”icon close”></i>
    </a>
  </div>
{{/if}}
  1. Adding UI for both the filters in app/templates/components/explore/side-bar.hbs. Accordion UI is used to achieve this.
  <div class=”item”>
    {{#ui-accordion}}
      <span class=”title”>
        <i class=”dropdown icon”></i>
        {{t ‘Ticket Type’ }}
      </span>
      <div class=”content menu”>
        <a href=”#”
          class=”link item {{if (eq ticket_type ‘free’) ‘active’}}”
          {{action ‘selectTicketType’ ‘free’}}>
          {{t ‘Free’}}
        </a>
        <a href=”#”
          class=”link item {{if (eq ticket_type ‘paid’) ‘active’}}”
          {{action ‘selectTicketType’ ‘paid’}}>
          {{t ‘Paid’}}
        </a>
      </div>
    {{/ui-accordion}}
  </div>
  1. Editing the routes (app/routes/explore.js) to make sure if the request has new filter parameter then it should be sent to server.
if (params.ticket_type) {
      filterOptions.push({
        name : ‘tickets’,
        op   : ‘any’,
        val  : {
          name : ‘type’,
          op   : ‘eq’,
          val  : params.ticket_type
        }
      });
    }
  1. Addition of new request parameter in the controller (app/controllers/explore.js)
queryParams  : [‘category’, ‘sub_category’, ‘event_type’, ‘start_date’, ‘end_date’, ‘location’, ‘ticket_type’],

ticket_type  : null,

if (filterType === ‘ticket_type’) {  this.set(‘ticket_type’, null);
}
  1. Editing the sidebar component to set the value of request parameter when the user interacts with the filter.
hideClearFilters: computed(‘category’, ‘sub_category’, ‘event_type’, ‘startDate’, ‘endDate’, ‘location’, ‘ticket_type’, function() {
    return !(this.category || this.sub_category || this.event_type || this.startDate || this.endDate || this.location || this.ticket_type !== null);
}),

selectTicketType(ticketType) {
  this.set(‘ticket_type’, ticketType === this.ticket_type ? null : ticketType);
},

this.set(‘ticket_type’, null);

Sidebar after the addition of filters.

Resources:

Issue: https://github.com/fossasia/open-event-frontend/issues/3098
Pull Requests:

Ticket Type: https://github.com/fossasia/open-event-frontend/pull/3158
CFS: https://github.com/fossasia/open-event-frontend/pull/3144

Specifying Query Params: https://guides.emberjs.com/release/routing/query-params/

UI Resources for the feature: https://semantic-org.github.io/Semantic-UI-Ember/#/modules/accordion

Continue Reading

Fixing Notification Services Across EventYay

In this Blog-Post, I will show you, the fixing of multiple notification messages getting sent on multiple clicks or display of multiple messages for a single response.

What Caused the Problem?

In our Open Event Frontend, We are using Ember Notify Services to inject notification across the web app to show the notifications. Due to improper error handling on the client side sometimes we get multiple notification messages for a single click or for a single action. As shown in this picture.

How Did we Tackle It ?

Since the following issue was long pertaining, I solved the issue by adding a unique ID to each notification injected through the webapp. Since a unique ID was associated with each notification message, A single notification was getting displayed on each and every action irrespective of number of clicks or number of actions called. If an actions caused two notification to be simultaneously triggered, One of them on the basis of ID is suppressed so that a unique and understandable notification get shown.

Code Snippet:

Before the changes:

.then(() => {
          if (state === 'draft') {
            this.notify.success(this.l10n.t('Your event has been published successfully.'));
          } else {
            this.notify.success(this.l10n.t('Your event has been unpublished.'));
          }

After the changes:

this.notify.success(this.isNewInvite ? this.l10n.t('Role Invite sent successfully') : this.l10n.t('Role Invite updated successfully'), {
            id: 'man_role'
          });
        })
        .catch(() => {
          this.notify.error(this.l10n.t('Oops something went wrong. Please try again'), {
            id: 'man_role_error'
          });
        })

Pull Request : Open-Event-Frontend-3438

Issue : Open-Event-Frontend-3437

Tags :

OpenEvent, EventYay, Fossasia, Intern-2k19

Continue Reading

Addition Of Landing Pages For Mobile Apps

In this Blog-Post, I will showcase the landing pages on the Android frontend of Eventyay. As the Eventyay Android App has been launched on Play Store as well as F-Droid, We needed to make sure users know that they can access all the details regarding their event on their phones too. Now EventYay features two landing pages for each of its mobile apps :

EventYay Attendee App 

EventYay Orga App

Addition Of Landing Pages was a 3-Step Procedure, which is stated below :

  1. ) Defining The Needed Routes and Making Space in Footer.
  2. ) Writing the Code and Designing the Layout for the web-page.
  3. ) Writing Integration Tests for both of the pages.

I will explain each one of the processes involved in making Landing Pages for Mobile Apps in brief in this Blog-Post.

1. Defining The Needed Routes and Making Space in Footer.

To add any web-page in the Open-Event Frontend , we need to define its route in the Application Router File. The code snippet which adds them is:-

// app/router.js

router.map(function() {
  this.route('login');
  this.route('register');
  this.route('reset-password');
  this.route('attendee-app');
  this.route('organizer-app');

After successfully defining the routes, We need to create the HandleBars for the corresponding routes. To access those routes we need to create a section on EventYay Footer to show to the users.

The following code will result in showing the links to the landing page in footer-section

2. Writing the Code and Designing the Layout for the web-page.

After successfully defining the routes and links, its time we move on to writing the HandleBars for the above pages. The first design for the page would be the pointing menu offered by Semantic-UI v.2.0, which would describe the current tab.

After the designing of Navigation Menu in the AttendeeApp.hbs, Its time we design the main outlet and the body of the page. The Design of the layout page is based on the Landing Pages Created in Other Platforms Like Event.IO, EventBrite etc. A Mobile View on the right side with the Key Features of The App on the left side

3. Writing Integration Tests for both of the Pages.

Since writing test is the most important part of the Development Lifecycle. I preferred to write Acceptance Test instead of Integration Test or Unit Test, As both of the pages created were very simple in functionality. In Acceptance Test, We would be checking the URL For Both Of The Pages in every case After Login or Without Login.

Test For Attendee App :

test('visiting /landing-attendee-app without login', async function(assert) {
    await visit('/attendee-app');
    assert.equal(currentURL(), '/attendee-app');
  });

  test('visiting /landing-attendee-app with login', async function(assert) {
    await login(assert);
    await visit('/attendee-app');
    assert.equal(currentURL(), '/attendee-app');
  });

Resources

Issue: OpenEventFrontend-2929

Pull Request : OpenEventFrontend-2979

Additional Resources: Routing Guide., Templates in EmberJS.

Tags: OpenEvent, EventYay, Fossasia, Android Apps, Intern-2k19

Continue Reading

Allow Same Discount/Access Code for Multiple Events in the Open Event Server

In this Blog-Post, I will show how to allow the system to create the same Discount/Access Code for multiple events in the Open Event Server.

What was the issue:

The main problem was that the server used to identify the discount code and access code based on the discount code/access code itself, which did not allow multiple events to have the same discount/access codes.

Can you think of a better solution to this?
Yes, we should have been searching for it based on the discount/access code as well as the event they are associated with.

Changing the endpoint:

Now to do so, we want to pass the id of the event as well as discount/access code itself with the endpoint so that we can search the database based on the event_id and the code itself.  

Changes in Discount/Access Code Endpoint:

'/event/<int:discount_event_id>/discount-code/<code>'
'/event/<int:access_event_id>/access-code/<code>'

Change logic for database search:

Now when searching for discount/access code in the database, we need to pass the event_id along with the discount/access code, so that we can get the column of discount/access code associated with that event, even if we have multiple discount/access code with the same name for a different event. 

Changes in Database search logic:

access = db.session.query(AccessCode).filter_by(code=kwargs.get('code')
event_id = kwargs.get('access_event_id')).first()

discount = db.session.query(DiscountCode).filter_by(code=kwargs.get('code'),
event_id = kwargs.get('discount_event_id')).first()

Change endpoint in API docs and update Dredd hooks:

Now that we have changed the endpoint to get a discount/access code, we need to change API docs as well as Dredd hooks to accommodate the change in API docs.

Changes in API docs:

## Get Discount Code Detail using the code [/v1/event/{event_id}/discount-code/{code}]
## Get Access Code Detail using the code [/v1/event/{event_id}/access-code/{code}]

Changes in Dredd Hooks:

In discount code hook:

discount_code.event_id = 1

In access code hook:

event = EventFactoryBasic()
db.session.add(event)
db.session.commit()

Resources:

Link to Issue: fossasia/open-event-server#6027
Link to PR: fossasia/open-event-server#6208

Continue Reading

Creating an awesome ‘About Us’ page for the Open Event Organizer Android App

Open Event Organizer App (Eventyay Organizer App) is an Android app based on the Eventyay platform. It contains various features using which organizers can manage their events.

This article will talk about a library which can help you create great about pages for Android apps without the need of making custom layouts.

It is the Android About Page library.

Let’s go through the process of its implementation in the Eventyay Organizer App.

First add the dependency in the app level build.gradle file:

implementation 'com.github.medyo:android-about-page:1.2.5'

Creating elements to be added:

Element legalElement = new Element();
legalElement.setTitle("Legal");

Element developersElement = new Element();      
developersElement.setTitle(getString(R.string.developers));

Element shareElement = new Element();
shareElement.setTitle(getString(R.string.share));

Element thirdPartyLicenses = new Element();       
thirdPartyLicenses.setTitle(getString(R.string.third_party_licenses));

Setting image, description and adding items in the About Page:

AboutPage aboutPage = new AboutPage(getContext())
            .isRTL(false)
            .setImage(R.mipmap.ic_launcher)            
            .setDescription(getString(R.string.about_us_description))
            .addItem(new Element("Version " + BuildConfig.VERSION_NAME, R.drawable.ic_info))
            .addGroup("Connect with us")
            .addGitHub("fossasia/open-event-organizer-android")
            .addPlayStore(getContext().getPackageName())
            .addWebsite(getString(R.string.FRONTEND_HOST))
            .addFacebook(getString(R.string.FACEBOOK_ID))
            .addTwitter(getString(R.string.TWITTER_ID))
            .addYoutube(getString(R.string.YOUTUBE_ID))
            .addItem(developersElement)
            .addItem(legalElement)
            .addItem(shareElement);

if (BuildConfig.FLAVOR.equals("playStore")) {    
    aboutPage.addItem(thirdPartyLicenses);
}

View aboutPageView = aboutPage.create();

Now add the aboutPageView in the fragment.

To make the values configurable from build.gradle, add this is the defaultConfig:

resValue "string", "FACEBOOK_ID", "eventyay"
resValue "string", "TWITTER_ID", "eventyay"
resValue "string", "YOUTUBE_ID", "UCQprMsG-raCIMlBudm20iLQ"

That’s it! The About Page is now ready.

Resources:

Library used: Android About Page

Pull Request: #1904

Open Event Organizer App: Project repo, Play Store, F-Droid

Continue Reading

Mapbox implementation in Open Event Organizer Android App

Open Event Organizer Android App is used by event organizers to manage events on the Eventyay platform. While creating or updating an event, location is one of the important factors which needs to be added so that the attendees can be informed of the venue.

Here, we’ll go through the process of implementing Mapbox Places Autocomplete for event location in the F-Droid build variant.

The first step is to create an environment variable for the Mapbox Access Token. 

def MAPBOX_ACCESS_TOKEN = System.getenv('MAPBOX_ACCESS_TOKEN') ?: "YOUR_ACCESS_TOKEN"

Add the Mapbox dependency:

fdroidImplementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-places-v8:0.9.0'

Fetching the access token in EventDetailsStepOne as well as UpdateEventFragment:

ApplicationInfo applicationInfo = null;
        try {
            applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
        } catch (PackageManager.NameNotFoundException e) {
            Timber.e(e);
        }
        Bundle bundle = applicationInfo.metaData;

        String mapboxAccessToken = bundle.getString(getString(R.string.mapbox_access_token));

The app should not crash if the access token is not available. To ensure this, we need to put a check. Since, the default value of the access token is set to “YOUR_ACCESS_TOKEN”, the following code will check whether a token is available or not:

if (mapboxAccessToken.equals("YOUR_ACCESS_TOKEN")) {
    ViewUtils.showSnackbar(binding.getRoot(),                             R.string.access_token_required);
    return;
}

Initializing the PlacesAutocompleteFragment:

PlaceAutocompleteFragment autocompleteFragment = PlaceAutocompleteFragment.newInstance(
                mapboxAccessToken, PlaceOptions.builder().backgroundColor(Color.WHITE).build());

getFragmentManager().beginTransaction()
    .replace(R.id.fragment, autocompleteFragment)
    .addToBackStack(null)
    .commit();

Now, a listener needs to be set up to get the selected place and set the various fields like latitude, longitude, location name and searchable location name.

autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() {
                @Override
                public void onPlaceSelected(CarmenFeature carmenFeature) {
                    Event event = binding.getEvent();
                    event.setLatitude(carmenFeature.center().latitude());
                    event.setLongitude(carmenFeature.center().longitude());
                    event.setLocationName(carmenFeature.placeName());
                    event.setSearchableLocationName(carmenFeature.text());
                    binding.form.layoutLocationName.setVisibility(View.VISIBLE);
                    binding.form.locationName.setText(event.getLocationName());
                    getFragmentManager().popBackStack();
                }

                @Override
                public void onCancel() {
                    getFragmentManager().popBackStack();
                }
            });

This brings the process of implementing Mapbox SDK to completion.

GIF showing the working of Mapbox Places Autocomplete

Resources:

Documentation: Mapbox Places Plugin

Open Event Organizer App: Project repo, Play Store, F-Droid

Continue Reading

Implementation of Android App Links in Open Event Organizer App

Android App Links are HTTP URLs that bring users directly to specific content in an Android app. They allow the website URLs to immediately open the corresponding content in the related Android app.

Whenever such a URL is clicked, a dialog is opened allowing the user to select a particular app which can handle the given URL.

In this blog post, we will be discussing the implementation of Android App Links for password reset in Open Event Organizer App, the Android app developed for event organizers using the Eventyay platform.

What is the purpose of using App Links?

App Links are used to open the corresponding app when a link is clicked.

  • If the app is installed, then it will open on clicking the link.
  • If app is not installed, then the link will open in the browser.

The first steps involve:

  1. Creating intent filters in the manifest.
  2. Adding code to the app’s activities to handle incoming links.
  3. Associating the app and the website with Digital Asset Links.

Adding Android App Links

First step is to add an intent-filter for the AuthActivity.

<intent-filter>
    <action android:name="android.intent.action.VIEW" />

    <category android:name="android.intent.category.DEFAULT" />
     <category android:name="android.intent.category.BROWSABLE" />

     <data
         android:scheme="https"
         android:host="@string/FRONTEND_HOST"
         android:pathPrefix="/reset-password" />
</intent-filter>

Here, FRONTEND_HOST is the URL for the web frontend of the Eventyay platform.

This needs to be handled in AuthActivity:

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    handleIntent(intent);
}
private void handleIntent(Intent intent) {
    String appLinkAction = intent.getAction();
    Uri appLinkData = intent.getData();

    if (Intent.ACTION_VIEW.equals(appLinkAction) && appLinkData != null) {
        LinkHandler.Destination destination = LinkHandler.getDestinationAndToken(appLinkData.toString()).getDestination();
        String token = LinkHandler.getDestinationAndToken(appLinkData.toString()).getToken();

        if (destination.equals(LinkHandler.Destination.RESET_PASSWORD)) {
            getSupportFragmentManager().beginTransaction()
            .replace(R.id.fragment_container,                     
                         ResetPasswordFragment.newInstance(token))
            .commit();
        }
    }
}

 Call the handleIntent() method in onCreate():

handleIntent(getIntent());

Get the token in onCreate() method of ResetPasswordFragment:

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (getArguments() != null)
        token = getArguments().getString(TOKEN_KEY);
}

Set the token in ViewModel:

if (token != null)
    resetPasswordViewModel.setToken(token);

The setToken() method in ViewModel:

if (token != null)
    resetPasswordViewModel.setToken(token);

LinkHandler class for handling the links:

package com.eventyay.organizer.utils;

public class LinkHandler {

    public Destination destination;
    public String token;

    public LinkHandler(Destination destination, String token) {
        this.destination = destination;
        this.token = token;
    }

    public static LinkHandler getDestinationAndToken(String url) {
        if (url.contains("reset-password")) {
            String token = url.substring(url.indexOf('=') + 1);
            return new LinkHandler(Destination.RESET_PASSWORD, token);
        } else if (url.contains("verify")) {
            String token = url.substring(url.indexOf('=') + 1);
            return new LinkHandler(Destination.VERIFY_EMAIL, token);
        } else
            return null;
    }

    public Destination getDestination() {
        return destination;
    }

    public String getToken() {
        return token;
    }

    public enum Destination {
        VERIFY_EMAIL,
        RESET_PASSWORD
    }
}

enum is used to handle links for both, password reset as well as email verification.

Finally, the unit tests for LinkHandler:

package com.eventyay.organizer.utils;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import static org.junit.Assert.assertEquals;

@RunWith(JUnit4.class)
public class LinkHandlerTest {

    private String resetPassUrl = "https://eventyay.com/reset-password?token=12345678";
    private String verifyEmailUrl = "https://eventyay.com/verify?token=12345678";

    @Test
    public void shouldHaveCorrectDestination() {
        assertEquals(LinkHandler.Destination.RESET_PASSWORD,
            LinkHandler.getDestinationAndToken(resetPassUrl).getDestination());
        assertEquals(LinkHandler.Destination.VERIFY_EMAIL,
            LinkHandler.getDestinationAndToken(verifyEmailUrl).getDestination());
    }

    @Test
    public void shouldGetPasswordResetToken() {
        assertEquals(LinkHandler.Destination.RESET_PASSWORD,
            LinkHandler.getDestinationAndToken(resetPassUrl).getDestination());
        assertEquals("12345678",
            LinkHandler.getDestinationAndToken(resetPassUrl).getToken());
    }

    @Test
    public void shouldGetEmailVerificationToken() {
        assertEquals(LinkHandler.Destination.VERIFY_EMAIL,
            LinkHandler.getDestinationAndToken(verifyEmailUrl).getDestination());
        assertEquals("12345678",
            LinkHandler.getDestinationAndToken(verifyEmailUrl).getToken());
    }
}

Resources:

Documentation: Link

Further reading: Android App Linking

Pull Request: feat: Add app link for password reset

Open Event Organizer App: Project repo, Play Store, F-Droid

Continue Reading

Implementation of scanning in F-Droid build variant of Open Event Organizer Android App

Open Event Organizer App (Eventyay Organizer App) is the Android app used by event organizers to create and manage events on the Eventyay platform.

Various features include:

  1. Event creation.
  2. Ticket management.
  3. Attendee list with ticket details.
  4. Scanning of participants etc.

The Play Store build variant of the app uses Google Vision API for scanning attendees. This cannot be used in the F-Droid build variant since F-Droid requires all the libraries used in the project to be open source. Thus, we’ll be using this library: https://github.com/blikoon/QRCodeScanner 

We’ll start by creating separate ScanQRActivity, ScanQRView and activity_scan_qr.xml files for the F-Droid variant. We’ll be using a common ViewModel for the F-Droid and Play Store build variants.

Let’s start with requesting the user for camera permission so that the mobile camera can be used for scanning QR codes.

public void onCameraLoaded() {
    if (hasCameraPermission()) {
        startScan();
    } else {
        requestCameraPermission();
    }
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode != PERM_REQ_CODE)
            return;

    // If request is cancelled, the result arrays are empty.
    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        cameraPermissionGranted(true);
    } else {
        cameraPermissionGranted(false);
    }
}



@Override
public boolean hasCameraPermission() {
    return ContextCompat.checkSelfPermission(this, permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
}

@Override
public void requestCameraPermission() {
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, PERM_REQ_CODE);
}


@Override
public void showPermissionError(String error) {
    Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
}

public void cameraPermissionGranted(boolean granted) {
    if (granted) {
        startScan();
    } else {
        showProgress(false);
        showPermissionError("User denied permission");
    }
}

After the camera permission is granted, or if the camera permission is already granted, then the startScan() method would be called.

@Override
public void startScan() {
    Intent i = new Intent(ScanQRActivity.this, QrCodeActivity.class);
    startActivityForResult(i, REQUEST_CODE_QR_SCAN);
}

QrCodeActivity belongs to the library that we are using.

Now, the processing of barcode would be started after it is scanned. The processBarcode() method in ScanQRViewModel would be called.

public void onActivityResult(int requestCode, int resultCode, Intent intent) {

    if (requestCode == REQUEST_CODE_QR_SCAN) {
        if (intent == null)
            return;

        scanQRViewModel.processBarcode(intent.getStringExtra
            ("com.blikoon.qrcodescanner.got_qr_scan_relult"));

    } else {
        super.onActivityResult(requestCode, resultCode, intent);
    }
}

Let’s move on to the processBarcode() method, which is the same as the Play Store variant.

public void processBarcode(String barcode) {

    Observable.fromIterable(attendees)
        .filter(attendee -> attendee.getOrder() != null)
        .filter(attendee -> (attendee.getOrder().getIdentifier() + "-" + attendee.getId()).equals(barcode))
        .compose(schedule())
        .toList()
        .subscribe(attendees -> {
            if (attendees.size() == 0) {
                message.setValue(R.string.invalid_ticket);
                tint.setValue(false);
            } else {
                checkAttendee(attendees.get(0));
            }
        });
}

The checkAttendee() method:

private void checkAttendee(Attendee attendee) {
    onScannedAttendeeLiveData.setValue(attendee);

    if (toValidate) {
        message.setValue(R.string.ticket_is_valid);
        tint.setValue(true);
        return;
    }

    boolean needsToggle = !(toCheckIn && attendee.isCheckedIn ||
        toCheckOut && !attendee.isCheckedIn);

    attendee.setChecking(true);
    showBarcodePanelLiveData.setValue(true);

    if (toCheckIn) {
        message.setValue(
            attendee.isCheckedIn ? R.string.already_checked_in : R.string.now_checked_in);
        tint.setValue(true);
        attendee.isCheckedIn = true;
    } else if (toCheckOut) {
        message.setValue(
            attendee.isCheckedIn ? R.string.now_checked_out : R.string.already_checked_out);
        tint.setValue(true);
        attendee.isCheckedIn = false;
    }

    if (needsToggle)
        compositeDisposable.add(
            attendeeRepository.scheduleToggle(attendee)
                .subscribe(() -> {
                    // Nothing to do
                }, Logger::logError));
}

This would toggle the check-in state of the attendee.

Resources:

Library used: QRCodeScanner

Pull Request: feat: Implement scanning in F-Droid build variant

Open Event Organizer App: Project repo, Play Store, F-Droid

Continue Reading

Implementation of Role Invites in Open Event Organizer Android App

Open Event Organizer Android App consists of various features which can be used by event organizers to manage their events. Also, they can invite other people for various roles. After acceptance of the role invite, the particular user would have access to features like the event settings and functionalities like scanning of tickets and editing of event details, depending on the access level of the role.

There can be various roles which can be assigned to a user: Organizer, Co-Organizer, Track Organizer, Moderator, Attendee, Registrar.

Here we will go through the process of implementing the feature to invite a person for a particular role for an event using that person’s email address.

The ‘Add Role’ screen has an email field to enter the invitee’s email address and select the desired role for the person. Upon clicking the ‘Send Invite’ button, the person would be sent a mail containing a link to accept the role invite.

The Role class is used for the different types of available roles.

@Data
@Builder
@Type("role")
@AllArgsConstructor
@NoArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
public class Role {

    @Id(LongIdHandler.class)
    public Long id;

    public String name;
    public String titleName;
}

The RoleInvite class:

@Data
@Builder
@Type("role-invite")
@AllArgsConstructor
@NoArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
public class RoleInvite {

    @Id(LongIdHandler.class)
    public Long id;

    @Relationship("event")
    public Event event;

    @Relationship("role")
    public Role role;

    public String email;
    public String createdAt;
    public String status;
    public String roleName;
}

A POST request is required for sending the role invite using the email address of the recipient as well as the role name.

@POST("role-invites")
Observable<RoleInvite> postRoleInvite(@Body RoleInvite roleInvite);

On clicking the ‘Send Invite’ button, the email address would be validated and if it is valid, the invite would be sent.

binding.btnSubmit.setOnClickListener(v -> {
        if (!validateEmail(binding.email.getText().toString())){            
            showError(getString(R.string.email_validation_error));
            return;
        }
        roleId = binding.selectRole.getSelectedItemPosition() + 1;
        roleInviteViewModel.createRoleInvite(roleId);
});

createRoleInvite() method in RoleInviteViewModel:

public void createRoleInvite(long roleId) {

    long eventId = ContextManager.getSelectedEvent().getId();
    Event event = new Event();
    event.setId(eventId);
    roleInvite.setEvent(event);
    role.setId(roleId);
    roleInvite.setRole(role);

    compositeDisposable.add(
        roleRepository
            .sendRoleInvite(roleInvite)
            .doOnSubscribe(disposable -> progress.setValue(true))
            .doFinally(() -> progress.setValue(false))
            .subscribe(sentRoleInvite -> {
                success.setValue("Role Invite Sent");
            }, throwable -> error.setValue(ErrorUtils.getMessage(throwable).toString())));
}

It takes roleId as an argument which is used to set the desired role before sending the POST request.

We can notice the use of sendRoleInvite() method of RoleRepository. Let’s have a look at that:

@Override
public Observable<RoleInvite> sendRoleInvite(RoleInvite roleInvite) {
    if (!repository.isConnected()) {
        return Observable.error(new Throwable(Constants.NO_NETWORK));
    }

    return roleApi
        .postRoleInvite(roleInvite)
        .doOnNext(inviteSent -> Timber.d(String.valueOf(inviteSent)))
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());
}

Resources:

API Documentation: Roles, Role Invites

Pull Request: feat: Implement system of role invites

Open Event Organizer App: Project repo, Play Store, F-Droid

Continue Reading
Close Menu