Audio Structure of SUSI Smart Speaker

Previously whenever a sound had to be played via the smart speaker, the subprocess python module was used to call the CVLC process and play audio via it. This puts forward a number of challenges while implementing various music features such as queuing songs, shuffling songs or handle the volume of the music. Thus, the audio structure was remade in the SUSI Smart Speaker project. The audio playing structure resides mainly in the susi_installer and the susi_linux repository. The above flow chart describes how audio is handled now in the project. Location of related files:Busy State: susi_linux/main/states/busy_state.py Player: susi_linux/main/player Sound Server: susi_installer/raspi/soundserver VLC Player: susi_installer/pythonmods/vlcplayer HW mixer: susi_installer/pythonmods/hwmixer In the new structure instead of using CVLC and a youtube URL server, we use Python VLC with a sound server. The new structure as given in the flowchart is illustrated step by step below-  Busy State The busy state class sends and accepts responses by the server through the susi_python wrapper. Here is the code which is responsible for handling play audio from a USB thumb drive or youtube. if 'identifier' in reply.keys():    url = reply['identifier']    if url[:3] == 'ytd':        player.playytb(url[4:])    else:        player.play(url)    self.transition(self.allowedStateTransitions.get('idle')) For example “SUSI, play audio” will have a response like :  {'identifier': 'file:///media/usb0/example.mp3', 'answer': 'Playing audio from your usb device'} For the above response the play method of the player class will be called. Player The Player class checks if the soundserver is running and sends the request to it. If the soundserver is not running then the VLC Player class is used directly. Play method:       def play(self, mrl, mode = None):        self._executeArg('play', 'mrl', mrl, mode)     def _executeArg(self, method, key, arg, mode = None):        if (mode == 'server') or ((mode is None) and (self.mode == 'server')):            send_request(method + '?' + key + '=' + arg)        else:            getattr(vlcplayer, method)(arg) Sound Server The soundserver provides various methods of the VLC Player as endpoints. These endpoints are then used by the busy state directly or via the remote access webpage or an external application. An external application such as an android/ios app can use these endpoints to control the music playback on the device. @app.route('/play', methods=['POST', 'PUT'])def play_route():    if 'ytb' in request.args:        vlcplayer.playytb(request.args.get('ytb'))        return do_return('Ok', 200)    elif 'mrl' in request.args:        vlcplayer.play(request.args.get('mrl'))        return do_return('Ok', 200)    else:        return do_return('Unknown play mode', 400) VLC Player The VLC Player class is actually responsible for playing and handling the music. This uses python VLC module for handling audio playback and various other functionalities. We use the Media List Player class found in the VLC player module to play music, using Media list player over media player gives us the advantage of queuing the files and essentially making a playlist. For more info on MediaListPlayer…

Continue ReadingAudio Structure of SUSI Smart Speaker

Dialog Component in SUSI.AI

Dialog Component in SUSI.AI is rendered in App.js to remove code redundancy. Redux is integrated in the Dialog component which allows us to open/close the dialog from any component by altering the modal states. This implementation allows us to get rid of the need of having dialog component in different components. Redux Code There are two actions and reducers which control the dialog component. Default state of isModalOpen is false and modalType is an empty string. To open a dialog modal the action openModal is dispatched, which sets isModalOpen to true and the modalType. To close a dialog modal the action closeModal is dispatched, which sets isModalOpen to default state i.e. false. import { handleActions } from 'redux-actions'; import actionTypes from '../actionTypes'; const defaultState = { modalProps: { isModalOpen: false, modalType: '', }, }; export default handleActions( { [actionTypes.UI_OPEN_MODAL](state, { payload }) { return { ...state, modalProps: { isModalOpen: true, ...payload, }, }; }, [actionTypes.UI_CLOSE_MODAL](state) { return { ...state, modalProps: defaultState.modalProps, }; }, } defaultState, ); Shared Dialog Component Dialog Modal can be opened from any component by dispatching an action.  To open a Dialog Modal: this.props.actions.openModal({modalType: [modal name]}); To close a Dialog Modal: this.props.actions.closeModal(); Shared Dialog Component has a DialogData object which contains objects with two main properties : Dialog component and Dialog size. Other props can also be passed along with these two properties such as fullScreen. Dialog Content of different Dialogs are present in their respective folders. Each Dialog Content has a Title, Content and Actions.Different Dialog types present are: Confirm Delete with Input: This dialog modal is used when a user deletes account, device and skill. Confirm Dialog: This dialog modal is used where confirmation is required from the user/admin such as on changing skill status, on password reset,etc.Share Dialog: This dialog modal opens up when the share icon is clicked in the chat.Standard Action Dialog: This dialog modal opens up on restore skill, delete feedback, system settings and bot.Tour Dialog: This dialog modal opens up SUSI.AI tour. To add a new Dialog to DialogSection, the steps are: Import the Dialog Content ComponentAdd the Dialog Component to DialogData object in the following manner: const DialogData = { [dialog componet name]: { Component : [imported dialog component name], size : [size of the Dialog Component]}, } Code (Reduced) const DialogData = {   login: { Component: Login, size: 'sm' }, } const DialogSection = props => {  const {    actions,    modalProps: { isModalOpen, modalType, ...otherProps },    visited,  } = props;  const getDialog = () => {    if (isModalOpen) {      return DialogData[modalType];    }    return DialogData.noComponent;  };  const { size, Component, fullScreen = false } = getDialog(); return ( <Dialog     maxWidth={size}     fullWidth={true}     open={isModalOpen || !visited}     onClose={isModalOpen ? actions.closeModal : actions.setVisited}      fullScreen={fullScreen}    >     <DialogContainer>       {Component ? <Component {...otherProps} /> : null}     </DialogContainer>  </Dialog> ) }; In conclusion, having a shared dialog component reduces redundant code and allows to have a similar Dialog UI across the…

Continue ReadingDialog Component in SUSI.AI

My Devices in SUSI.AI

In this blog I’ll be explaining how to view, edit and delete connected devices from SUSI.AI webclient. To connect a device open up the SUSI.AI android app, and fill the details accordingly. Device can also be connected by logging in to your raspberry pi. Once the devices is connected you can edit, delete and access specific features for the device from the web client. My Devices All the connected devices can be viewed in My Devices tab in the Dashboard. In this tab all the devices connected to your account are listed in a table along with their locations on the map. Each device table row has three action buttons - view, edit and delete. Clicking on the view button takes to device specific page. Clicking on the edit button makes the fields name and room editable in table row. Clicking on the delete button opens a confirm with input dialog. Device can be deleted by entering the device name and clicking on delete. To fetch all the device getUserDevices action is dispatched on component mounting which sets the reducer state devices in settings reducer. initialiseDevices function is called after all the devices are fetched from the server. This function creates an array of objects of devices with name, room, macId, latitude, longitude and location. componentDidMount() { const { accessToken, actions } = this.props; if (accessToken) { actions .getUserDevices() .then(({ payload }) => { this.initialiseDevices(); this.setState({ loading: false, emptyText: 'You do not have any devices connected yet!', }); }) .catch(error => { this.setState({ loading: false, emptyText: 'Some error occurred while fetching the devices!', }); console.log(error); }); } document.title = 'My Devices - SUSI.AI - Open Source Artificial Intelligence for Personal Assistants, Robots, Help Desks and Chatbots'; } initialiseDevices = () => { const { devices } = this.props; if (devices) { let devicesData = []; let deviceIds = Object.keys(devices); let invalidLocationDevices = 0; deviceIds.forEach(eachDevice => { const { name, room, geolocation: { latitude, longitude }, } = devices[eachDevice]; let deviceObj = { macId: eachDevice, deviceName: name, room, latitude, longitude, location: `${latitude}, ${longitude}`, }; if ( deviceObj.latitude === 'Latitude not available.' || deviceObj.longitude === 'Longitude not available.' ) { deviceObj.location = 'Not found'; invalidLocationDevices++; } else { deviceObj.latitude = parseFloat(latitude); deviceObj.longitude = parseFloat(longitude); } devicesData.push(deviceObj); }); this.setState({ devicesData, invalidLocationDevices, }); } }; Device Page Clicking on the view icon button in my devices redirects to mydevices/:macId. This page consists of device information in tabular format, local configuration settings and location of the device on the map. User can edit and delete the device from actions present in table. Local configuration settings can be accessed only if the user is logged in the local server. Edit Device To edit a device click on the edit icon button in the actions column of the table. The name and room field become editable.On changing the values handleChange function is called which updates the devicesData state. Clicking on the tick icon saves the new details by calling the onDeviceSave function. This function class the addUserDevice…

Continue ReadingMy Devices in SUSI.AI

Configure location feature using MVVM in Open Event Attendee Application

The open event attendee is an android app which allows users to discover events happening around the world using the Open Event Platform. It consumes the APIs of the open event server to get a list of available events and can get detailed information about them. It deals with events based on location, but we have to take the location as an input from the user. While in many cases, we have to search for events on our current location only. To make this work, I have added a current location option, where the app will get our location and search for nearby events. Earlier we had to enter our current location as well to search nearby events. Model–view–viewmodel is a software architectural pattern. MVVM facilitates separation of development of the graphical user interface – be it via a markup language or GUI code – from the development of the business logic or back-end logic (the data model). Why Model-view-ViewModel?Setup Geo location View ModelConfigure location feature with MVVMConclusionResources Let’s analyze every step in detail. Advantages of using Model-view-ViewModel A clean separation of different kinds of code should make it easier to go into one or several of those more granular and focused parts and make changes without worrying.External and internal dependencies are in separate pieces of code from the parts with the core logic that you would like to test.Observation of mutable live data whenever it is changed. Setup the Geolocation view model Created new kotlin class name GeoLocationViewModel which contains a configure function for current location: package org.fossasia.openevent.general.search class GeoLocationViewModel : ViewModel() { fun configure(activity: Activity) { } } The GeoLocationViewModel class implement as VIewModel(). Now add the class in module inside Modules.kt : val viewModelModule = module { viewModel { GeoLocationViewModel() } } Configure location feature with GeoLocationViewModel: First, add play store location service implementation inside dependencies of build.gradle: // Location Play Service playStoreImplementation 'com.google.android.gms:play-services-location:16.0.0' Now we need location permissions to implement this feature. Adding user permissions in the manifest file: <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> Now ask user for the location permission: private fun checkLocationPermission() { val permission = context?.let { ContextCompat.checkSelfPermission(it, Manifest.permission.ACCESS_COARSE_LOCATION) } if (permission != PackageManager.PERMISSION_GRANTED) { requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION), LOCATION_PERMISSION_REQUEST) } } Check for device location is enabled, if not send an intent to turn location on. The method is written inside configure function: val service = activity.getSystemService(Context.LOCATION_SERVICE) var enabled = false if (service is LocationManager) enabled = service.isProviderEnabled(LocationManager.NETWORK_PROVIDER) if (!enabled) { val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) activity.startActivity(intent) return } Now create Mutable live data for current location inside the view model class : private val mutableLocation = MutableLiveData<String>() val location: LiveData<String> = mutableLocation Now implement location request and location callback inside configure method: val locationRequest: LocationRequest = LocationRequest.create() locationRequest.priority = LocationRequest.PRIORITY_LOW_POWER val locationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult?) { if (locationResult == null) { return } for (location in locationResult.locations) { if (location != null) { val latitude = location.latitude val longitude = location.longitude try { val geocoder = Geocoder(activity,…

Continue ReadingConfigure location feature using MVVM in Open Event Attendee Application

Setting up your own Smart Speaker Assistant as Fast as Possible

If you're worried about privacy concerns when using smart assistants or just want to build your own one with complete freedom then this guide will help you. SUSI.AI provides Artificial Intelligence for Smart Speakers, Personal Assistants, Robots, Help Desks and Chatbots. SUSI.AI is a completely free and open source software. In this guide, we will be building our own smart speaker assistant which will talk to the user just like Alexa or Google home. The keyword will be "SUSI". Things you will need Raspberry PiReSpeaker 2-mics Hat / USB mic / USB sound cardSD cardspeaker3.5mm Aux cable/ JST PH2.0 connector Step 1 Download and flash the image Download the latest Susibian image from https://github.com/fossasia/susi_installer/releases. The downloaded image will look something like susibian-xxxxxxxxxxxx.img.xzInsert the SD card in your PCIf you're on Linux, open up a terminal window and go to your downloads folder(or the place where the image is downloaded) and type the following commands Extract the image  unxz susibian-<timestamp>.img.xz  Example: unxz susibian-201905170311.img.xz Write the image to SD-card  Find the Disk Name of your SD card device by typing lsblk into a terminal window. Here, for me, SD card disk name name is sdc. sudo dd if=<path_to_downloaded_image_file> of=/dev/<disk_name> bs=4M status=progress Replace <path_to_downloaded_image_file> to path to susibian image. Replace <disk_name> with the disk name found in the before mentioned step. NOTE: In the example command above, sdc is the device name for my scenario only, please check your device name before executing the command as it can result in loss of data! Or Use balena etcher : Select the imageSelect SD card deviceFlash! Step 2 Setting up the hardware Insert the microSD card into your raspberry piAttach ReSpeaker Hat to your Raspberry PiConnect your speaker through the 3.5mm jack on ReSpeaker Hat or connect your JST PH2.0 speakers using the onboard JST connectorPlug in the power supply to your raspberry pi.  If you’re using a USB sound card make sure to follow - Audio Debugging Step 3 Setting up the speaker Download SUSI-AI android app. https://github.com/fossasia/susi_android/releases/download/1.1/susiai_20180826.apkCreate an account or log in.In the app, go to settings -> Devices -> click here to move to device setup screenWait for your device to show up and then click on setup.  Chose the wifi network you want your speaker to connect to and enter the password.Add credentials. OR Connect your computer or mobile phone to the SUSI.AI hotspot using the password "password".Open http://10.0.0.1:5000 which will show you the set-up page as visible below:  Put in your Wifi credentials. For an open network set an empty password. The device should connect automatically to any open network, leave SSID and password empty.Click on "Reboot Smart Speaker" Testing Wait for reboot of the speaker, SUSI will say "SUSI has started" as soon it is ready.After the setup, three LEDs on top of ReSpeaker Hat should light up.Say "SUSI" and wait for a bell sound.Speak your query!!Enjoy your new personal assistant Tags SUSI Smart Speaker, SUSI.AI, FOSSASIA, GSoC19 Resources https://github.com/fossasia/susi_installer/blob/development/docs/development_device_handout.mdhttps://github.com/fossasia/susi_installer/blob/development/docs/configure_guide.md

Continue ReadingSetting up your own Smart Speaker Assistant as Fast as Possible

Adding time counter on ordering tickets in Eventyay Attendee

In Eventyay Attendee, ordering tickets for events has always been a core functionality that we focus on. When ordering tickets, adding a time counter to make a reservation and release tickets after timeout is a common way to help organizers control their tickets’ distribution and help users save up their tickets. Let’s take a look at how to implement this feature Implementing the time counter Some notes on implementing time counterConclusionResources INTEGRATING TIME COUNTER TO YOUR SYSTEM Step 1: Create the UI for your time counter. In here, we made a simple View container with TextView inside to update the time. Step 2: Set up the time counter with Android CountdownTimer with the total countdown time and the ticking time. In Eventyay, the default countdown time is 10 minutes (600,000 ms) with the ticking time is (1,000 ms), which means the UI is updated every one second. private fun setupCountDownTimer(orderExpiryTime: Int) { rootView.timeoutCounterLayout.isVisible = true rootView.timeoutInfoTextView.text = getString(R.string.ticket_timeout_info_message, orderExpiryTime.toString()) val timeLeft: Long = if (attendeeViewModel.timeout == -1L) orderExpiryTime * 60 * 1000L else attendeeViewModel.timeout timer = object : CountDownTimer(timeLeft, 1000) { override fun onFinish() { findNavController(rootView).navigate(AttendeeFragmentDirections .actionAttendeeToTicketPop(safeArgs.eventId, safeArgs.currency, true)) } override fun onTick(millisUntilFinished: Long) { attendeeViewModel.timeout = millisUntilFinished val minutes = millisUntilFinished / 1000 / 60 val seconds = millisUntilFinished / 1000 % 60 rootView.timeoutTextView.text = "$minutes:$seconds" } } timer.start() } Step 3: Set up creating a pending order when the timer starts counting so that users can hold a reservation for their tickets. A simple POST request about empty order to the API is made fun initializeOrder(eventId: Long) { val emptyOrder = Order(id = getId(), status = ORDER_STATUS_INITIALIZING, event = EventId(eventId)) compositeDisposable += orderService.placeOrder(emptyOrder) .withDefaultSchedulers() .subscribe({ mutablePendingOrder.value = it orderIdentifier = it.identifier.toString() }, { Timber.e(it, "Fail on creating pending order") }) } Step 4: Set up canceling order when the time counter finishes. As time goes down, the user should be redirected to the previous fragment and a pop-up dialog should show with a message about reservation time has finished. There is no need to send an HTTP request to cancel the pending order as it is automatically handled by the server. Step 5: Cancel the time counter in case the user leaves the app unexpectedly or move to another fragment. If this step is not made, the CountdownTimer still keeps counting in the background and possibly call onFinished() at some point that could evoke functions and crash the app override fun onDestroy() { super.onDestroy() if (this::timer.isInitialized) timer.cancel() } RESULTS CONCLUSION For a project with a ticketing system, adding a time counter for ordering is a really helpful feature to have. With the help of Android CountdownTimer, it is really to implement this function to enhance your user experience. RESOURCES Eventyay Attendee Android Codebase: https://github.com/fossasia/open-event-android Eventyay Attendee Android PR: #1843 - Add time counter on ordering ticket Documentation: https://developer.android.com/reference/android/os/CountDownTimer

Continue ReadingAdding time counter on ordering tickets in Eventyay Attendee

Integrating Redux in SkillCreator and BotBuilder

SkillCreator in SUSI.AI used for creating new skills and Botbuilder for creating new bots had parent-child communication as seen in React components. Many components were using a state, which was lifted up to its parent and passed to its children (Design, Configure, Deploy, SkillCreator) as props. This architecture has many problems, maintainability and readability being the prime one. For maintaining the state of SkillCreator and Botbuilder we used Redux, which lets us use global state and we don’t have to worry about passing props just for the purpose of sending data deep down. With Redux, the code size reduced and we successfully eliminated a lot of code redundancy.  Basic Data Flow in Redux A UI Event like onClick happensThe UI event dispatches an action, mapDispatchToAction gives access to actions defined in action files.Perform synchronous or asynchronous tasks like fetching data from external API.The payload is used by reducer function, which updates the global store based on payload and previous state.The global store changes and using the mapStateToProps present, the function is subscribed to changes over time and gets the updated values of the store. Code Integration  The state object consists of 3 nested objects. Each view, .i.e on botWizard, for Build tab we update the skill object. For the Design tab, design object and etc. The objective of storing in Redux store is, we require the data to persist when a user navigates/change to other tabs for example Design Tab, the data of skills tab is mapped from the store so if user switches back to skills tab, no data loss will be there. All such required fields are stored in the store. Default States:  const defaultState = { skill: { name: '', file: null, category: null, language: '', image: avatarsIcon, imageUrl: '<image_name>', code: '::name <Bot_name>\n::category <Category>\n::language <Language>\n::author <author_name>\n::author_url <author_url>\n::description <description> \n::dynamic_content <Yes/No>\n::developer_privacy_policy <link>\n::image images/<image_name>\n::terms_of_use <link>\n\n\nUser query1|query2|quer3....\n!example:<The question that should be shown in public skill displays>\n!expect:<The answer expected for the above example>\nAnswer for the user query', author: '', }, design: { botbuilderBackgroundBody: '#ffffff', botbuilderBodyBackgroundImg: '', botbuilderUserMessageBackground: '#0077e5', botbuilderUserMessageTextColor: '#ffffff', botbuilderBotMessageBackground: '#f8f8f8', botbuilderBotMessageTextColor: '#455a64', botbuilderIconColor: '#000000', botbuilderIconImg: botIcon, code: '::bodyBackground #ffffff\n::bodyBackgroundImage \n::userMessageBoxBackground #0077e5\n::userMessageTextColor #ffffff\n::botMessageBoxBackground #f8f8f8\n::botMessageTextColor #455a64\n::botIconColor #000000\n::botIconImage ', }, configCode: "::allow_bot_only_on_own_sites no\n!Write all the domains below separated by commas on which you want to enable your chatbot\n::allowed_sites \n!Choose if you want to enable the default susi skills or not\n::enable_default_skills yes\n!Choose if you want to enable chatbot in your devices or not\n::enable_bot_in_my_devices no\n!Choose if you want to enable chatbot in other user's devices or not\n::enable_bot_for_other_users no", view: 'code', loading: true, reducers/create.js The CREATE_SET_VIEW reducer function updates the view, e.g it can be ‘code’, ‘ui’, ‘tree’. The action setView is dispatched to update the view tab. The action setSkillData is dispatched when we want to update the skill object in our global store.Reducer function for it: [actionTypes.CREATE_SET_SKILL_DATA](state, { payload }) { return { ...state, skill: { ...state.skill, ...payload, }, }; }, reducers/create.js The setSkillData is called whenever the user updates the code of skills. Updating Design Code The setDesignData is dispatched whenever we…

Continue ReadingIntegrating Redux in SkillCreator and BotBuilder

Add Navigation Architecture Component in Eventyay Attendee

The eventyay attendee is an android app which allows users to discover events happening around the world using the Open Event Platform. It consumes the APIs of the open event server to get a list of available events and can get detailed information about them. The project earlier used Fragment transition to handle fragment with multiple activities. Multiple activities are used because it is very complex to handle all fragment with a single activity using fragment transition. If we try to make a single activity application with fragment transition, it contains a lot of bugs with the back stack. But we can use Navigation Architecture Component to efficiently make Eventyay attendee a single activity. It is the perfect technology to handle all fragments and authentication in a single activity with no bigs or back stack. Issues with fragment transition and multiple activitiesWhy navigation architecture component?Process for using Navigation Architecture Component in the applicationAnimation of fragments with Navigation Architecture ComponentConclusionResources  Let’s analyze every step in detail. Issues with fragment transition and multiple activities Need to handle back stack on each Fragment TransitionHard to debugThis can leave your app in an unknown state when receiving multiple click events or during configuration changes.Fragment instances can be createdMultiple activities make the app slower due to using multiple intents Why navigation architecture component? Handling Fragment transactions Handling Up and Back actions correctly by defaultProviding standardized resources for animations and transitionsTreating deep linking as a first-class operationIncluding Navigation UI patterns, such as navigation drawers and bottom navigation, with minimal additional workProviding type safety when passing information while navigatingVisualizing and editing navigation graphs with Android Studio's Navigation Editor Steps involved in using Navigation Architecture Component in the application Adding dependencies to build.gradle: //Navigation implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha09' implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha09' Create new resources file for navigation graph under res -> navigation: Add fragments in navigation_graph.xml with navigation editor. Let’s take one example: <?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/mobile_navigation" app:startDestination="@id/eventsFragment"> <fragment android:id="@+id/eventsFragment" android:name="org.fossasia.openevent.general.event.EventsFragment" android:label="EventsFragment" /> </navigation> Here app:startDestination attribute is for the very first fragment after which app exit on back pressed. Replace Frame layout with a fragment in the main activity layout file. And add the above navigation graph to it:  <fragment android:id="@+id/frameContainer" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="0dp" android:background="@android:color/white" android:layout_weight="1" app:defaultNavHost="true" app:navGraph="@navigation/navigation_graph" app:layout_behavior="@string/appbar_scrolling_view_behavior" /> Now, this fragment layout is directly connected with all fragments added in the navigation graph. We only need to navigate the fragment. Set bottom navigation drawer with the navigation architecture component -  First, we need to make the ids of menu component same as the ids of fragments in the navigation graph. Bottom navigation menu: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/eventsFragment" android:icon="@drawable/ic_baseline_event_24px" android:title="@string/events" /> <item android:id="@+id/searchFragment" android:icon="@drawable/ic_search_grey_24dp" android:title="@string/search" /> <item android:id="@+id/favoriteFragment" android:icon="@drawable/ic_baseline_favorite_border_24px" android:title="@string/likes" /> <item android:id="@+id/orderUnderUserFragment" android:icon="@drawable/ic_baseline_ticket_24dp" android:title="@string/tickets" /> <item android:id="@+id/profileFragment" android:icon="@drawable/ic_baseline_account_circle_24px" android:title="@string/profile" /> </menu> Setup the bottom navigation menu with navigation controller: // import require libraries import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.NavigationUI.setupWithNavController // Set nav controller with host fragment using Kotlin smart cast val hostFragment = supportFragmentManager.findFragmentById(R.id.frameContainer) if (hostFragment is NavHostFragment) navController = hostFragment.navController setupWithNavController(navigationMenu, navController) Navigate…

Continue ReadingAdd Navigation Architecture Component in Eventyay Attendee

Examples of how AsyncTask is used in PSLab Android App

In this blog, we will look at a very useful and important feature provided by Android - AsyncTask and more importantly how AsyncTasks have been put to use for various functionalities throughout the PSLab Android Project What are Threads? Threads are basically paths of sequential execution within a process. In a way, threads are lightweight processes. A process may contain more than one threads and all these threads are executed in parallel. Such a method is called “Multithreading”. Multithreading is very useful when some long tasks need to be executed in the background while other tasks continue to execute in the foreground. Android has the main UI thread which works continuously and interacts with a user to display text, images, listen for click and touch, receive keyboard inputs and many more. This thread needs to run without any interruption to have a seamless user experience. When AsyncTask comes into the picture? AsyncTask enables proper and easy use of the UI thread. This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.   In PSLab Android application, we communicate with PSLab hardware through I/O(USB) interface. We connect the PSLab board with the mobile and request and wait for data such as voltage values and signal samples and once the data is received we display it as per requirements. Now clearly we can’t run this whole process on the main thread because it might take a long time to finish and because of that other UI tasks would be delayed which eventually degrade the user experience. So, to overcome this situation, we use AsyncTasks to handle communication with PSLab hardware. Methods of AsyncTask   AsyncTask is an Abstract class and must be subclassed to use. Following are the methods of the AsyncTask: onPreExecute()Used to set up the class before the actual executiondoInBackground(Params...)This method must be overridden to use AsyncTask. This method contains the main part of the task to be executed. Like the network call etc.The result from this method  is passed as a parameter to onPostExecute() methodonProgressUpdate(Progress...)This method is used to display the progress of the AsyncTaskonPostExecute(Result)Called when the task is finished and receives the results from the doInBackground() method There are 3 generic types passed to the definition of the AsyncTask while inheriting. The three types in order are  Params: Used to pass some parameters to doInBackground(Params...) method of the Task Progress: Defines the units in which the progress needs to be displayed/Result : Defines the data type to be returned from onInBackground() and receive as a parameter in the onPostExecute(Result) method Example of the usage of the AsyncClass is as under :  private class SampleTask extends AsyncTask<Params, Progress, Result> { @Override protected Result doInBackground(Params... params) { // The main code goes here return result; } @Override protected void onProgressUpdate(Progress... progress) { // display the progress } @Override protected void onPostExecute(Result result) { // display the result } } We can create an instance of this class as under and execute…

Continue ReadingExamples of how AsyncTask is used in PSLab Android App

Tax Information on Public Ticket Page

This blog post will elaborate on how Tax Information is being displayed on the public page of an event. In current implementation, the user gets to know the total tax inclusive amount only after he/she decides to place an order but no such information was given to them on the public ticket page itself. Order summary example in eventyay Example : In initial implementation, the user gets to know that the order is of only $120 and no information is given about the additional 30% being charged and taking the total to $156. To tackle this issue, I added two hybrid components to the ticket object to handle the two tax cases :  Inclusion in the price : In European and Asian Countries , the tax amount is included in the ticket price itself. For this case, I created the following parameter to store the tax amount included in gross amount. // app/models/ticket.js includedTaxAmount: computed('event.tax.isTaxIncludedInPrice', 'event.tax.rate', function() {   const taxType = this.event.get('tax.isTaxIncludedInPrice');   if (taxType) {     const taxRate = this.event.get('tax.rate');     return ((taxRate * this.price) / (100 + taxRate)).toFixed(2);   }   return 0; }) Added on the ticket price : In basic US tax policy, the tax amount is added on top of the ticket price. For such cases I have added a new attribute to ticket model which calculates the total amount payable for that particular ticket with tax inclusion // app/models/ticket.js ticketPriceWithTax: computed('event.tax.isTaxIncludedInPrice', 'event.tax.rate', function() {   let taxType = this.event.get('tax.isTaxIncludedInPrice');   if (!taxType) {     return ((1 + this.event.get('tax.rate') / 100) * this.price).toFixed(2);   }   return this.price; }) Now, the public ticket page has to be edited accordingly. The design I decided to follow is inspired by eventbrite itself :  Eventbrite specimen of the proposed implementation For this implementation, I modified the ticket list template to accommodate the changes in the following way :  // app/components/public/ticket-list.hbs<td id="{{ticket.id}}_price"> {{currency-symbol eventCurrency}} {{format-number ticket.price}} {{#if (and taxInfo (not-eq ticket.type 'free'))}}   {{#if showTaxIncludedMessage}}     <small class="ui gray-text small">       {{t 'includes'}} {{currency-symbol eventCurrency}} {{format-number ticket.includedTaxAmount}}     </small>   {{else}}     <small class="ui gray-text small">       + {{currency-symbol eventCurrency}} {{format-number (sub ticket.ticketPriceWithTax ticket.price)}}     </small>   {{/if}}   <div>     <small class="ui gray-text tiny aligned right">({{taxInfo.name}})</small>   </div> {{/if}}</td> Tax amount is included in ticket price Hence making the new public ticket list display to look like this in case of tax amount inclusion and additional charge as follows Tax amount is charged over the base price Discount Code application cases: In the cases when a user applies the discount code, the ticket price need to be updated, hence, the tax applied has to be updated accordingly. I achieved this by updating the two computed properties of the ticket model on each togglePromotionalCode and applyPromotionalCode action. When a promotional code is applied, the appropriate attribute is updated according to the discount offered. // app/components/public/ticket-list.jstickets.forEach(ticket => { let ticketPrice = ticket.get('price'); let taxRate = ticket.get('event.tax.rate'); let…

Continue ReadingTax Information on Public Ticket Page