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: Event creation.Ticket management.Attendee list with ticket details.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 ReadingImplementation of scanning in F-Droid build variant of Open Event Organizer Android App

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…

Continue ReadingImplementing Check-in time chart in Orga App