Connecting SUSI iOS App to SUSI Smart Speaker

SUSI Smart Speaker is an Open Source speaker with many exciting features. The user needs an Android or iOS device to set up the speaker. You can refer this post for initial connection to SUSI Smart Speaker. In this post, we will see how a user can connect SUSI Smart Speaker to iOS devices (iPhone/iPad). Implementation - The first step is to detect whether an iOS device connects to SUSI.AI hotspot or not. For this, we match the currently connected wifi SSID with SUSI.AI hotspot SSID. If it matches, we show the connected device in Device Activity to proceed further with setups. Choosing Room - Room name is basically the location of your SUSI Smart Speaker in the home. You may have multiple SUSI Smart Speaker in different rooms, so the purpose of adding the room is to differentiate between them. When the user clicks on Wi-Fi displayed cell, it starts the initial setups. We are using didSelectRowAt method of UITableViewDelegate to get which cell is selected. On clicking the displayed Wi-Fi cell, a popup is open with a Room Location Text field. override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if indexPath.row == 0, let speakerSSID = fetchSSIDInfo(), speakerSSID == ControllerConstants.DeviceActivity.susiSSID { // Open a popup to select Rooms presentRoomsPopup() } } When the user clicks the Next button, we send the speaker room location to the local server of the speaker by the following API endpoint with room name as a parameter: http://10.0.0.1:5000/speaker_config/ Refer this post for getting more detail about how choosing room work and how it is implemented in SUSI iOS. Sharing Wi-Fi Credentials - On successfully choosing the room, we present a popup that asks the user to enter the Wi-Fi credentials of previously connected Wi-Fi so that we can connect our Smart Speaker to the wifi which can provide internet connection to play music and set commands over the speaker. We present a popup with a text field for entering wifi password. When the user clicks the Next button, we share the wifi credentials to wifi by the following API endpoint: http://10.0.0.1:5000/wifi_credentials/ With the following params- Wifissid - Connected Wi-Fi SSID Wifipassd - Connected Wi-Fi password In this API endpoint, we are sharing wifi SSID and wifi password with Smart Speaker. If the credentials successfully accepted by speaker than we present a popup for user SUSI account password, otherwise we again present Enter Wifi Credentials popup. Client.sharedInstance.sendWifiCredentials(params) { (success, message) in DispatchQueue.main.async { self.alertController.dismiss(animated: true, completion: nil) if success { self.presentUserPasswordPopup() } else { self.view.makeToast("", point: self.view.center, title: message, image: nil, completion: { didTap in UIApplication.shared.endIgnoringInteractionEvents() self.presentWifiCredentialsPopup() }) } } }   Sharing SUSI Account Credentials - In the method above we have seen that when SUSI Smart Speaker accept the wifi credentials, we proceed further with SUSI account credentials. We open a popup to Enter user’s SUSI account password: When the user clicks the Next button, we use following API endpoint to share user’s SUSI account credentials to SUSI Smart Speaker: http://10.0.0.1:5000/auth/…

Continue ReadingConnecting SUSI iOS App to SUSI Smart Speaker

Adding Support for Playing Youtube Videos in SUSI iOS App

SUSI supports very exciting features in chat screen, from simple answer type to complex map, RSS, table etc type responses. Even user can ask SUSI for the image of anything and SUSI response with the image in the chat screen. What if we can play the youtube video from SUSI, we ask SUSI for playing videos and it can play youtube videos, isn’t it be exciting? Yes, SUSI can play youtube videos too. All the SUSI clients (iOS, Android, and Web) support playing youtube videos in chat. Google provides a Youtube iFrame Player API that can be used to play videos inside the app only instead of passing an intent and playing the videos in the youtube app. iFrame API provide support for playing youtube videos in iOS applications. In this post, we will see how playing youtube video features implemented in SUSI iOS. Getting response from server side - When we ask SUSI for playing any video, in response, we get youtube Video ID in video_play action type. SUSI iOS make use of Video ID to play youtube video. In response below, you can see that we are getting answer action type and in the expression of answer action type, we get the title of the video. actions: [ { type: "answer", expression: "Playing Kygo - Firestone (Official Video) ft. Conrad Sewell" }, { identifier: "9Sc-ir2UwGU", identifier_type: "youtube", type: "video_play" } ] Integrating youtube player in the app - We have a VideoPlayerView that handle all the iFrame API methods and player events with help of YTPlayer HTML file. When SUSI respond with video_play action, the first step is to register the YouTubePlayerCell and present the cell in collectionView of chat screen. Registering the Cell - register(_:forCellWithReuseIdentifier:) method registers a class for use in creating new collection view cells. collectionView?.register(YouTubePlayerCell.self, forCellWithReuseIdentifier: ControllerConstants.youtubePlayerCell)   Presenting the YouTubePlayerCell - Here we are presenting the cell in chat screen using cellForItemAt method of UICollectionView. if message.actionType == ActionType.video_play.rawValue { if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ControllerConstants.youtubePlayerCell, for: indexPath) as? YouTubePlayerCell { cell.message = message cell.delegate = self return cell } }   Setting size for cell - Using sizeForItemAt method of UICollectionView to set the size. if message.actionType == ActionType.video_play.rawValue { return CGSize(width: view.frame.width, height: 158) } In YouTubePlayerCell, we are displaying the thumbnail of youtube video using UIImageView. Following method is using to get the thumbnail of particular video by using Video ID - Getting thumbnail image from URL Setting image to imageView func downloadThumbnail() { if let videoID = message?.videoData?.identifier { let thumbnailURLString = "https://img.youtube.com/vi/\(videoID)/default.jpg" let thumbnailURL = URL(string: thumbnailURLString) thumbnailView.kf.setImage(with: thumbnailURL, placeholder: ControllerConstants.Images.placeholder, options: nil, progressBlock: nil, completionHandler: nil) } } We are adding a play button in the center of thumbnail view so that when the user clicks play button, we can present player. On clicking the Play button, we are presenting the PlayerViewController, which hold all the player setups, by overFullScreen type of modalPresentationStyle. @objc func playVideo() { if let videoID = message?.videoData?.identifier { let playerVC = PlayerViewController(videoID: videoID) playerVC.modalPresentationStyle = .overFullScreen delegate?.loadNewScreen(controller: playerVC) } }…

Continue ReadingAdding Support for Playing Youtube Videos in SUSI iOS App

Implementing Custom Date and Time Picker with 2-way Data Binding Support

The Data binding library is one of the most popular libraries among the android developers. We have been using it in the Open Event Organiser Android app for building interactive UI’s for some time now. The Open Event Organiser Android App is the Event management app for organizers using the Open Event Platform. This blog explains how we implemented our own custom Date and Time picker with 2-way data binding support using the Data binding framework. Why custom picker ? One specific requirement in the app is to have a button, clicking on that button should open a DatePicker which would allow the user to select the date. A similar behaviour was required to allow the user to select the time as well. In order to handle this requirement we were using Binding Adapters on Button. For eg. the following Binding Adapter allowed us to define a property date on a button and set an Observable String as it’s value. We implemented a similar Binding Adapter for selecting time as well. @BindingAdapter("date") public static void bindDate(Button button, ObservableField<String> date) { String format = DateUtils.FORMAT_DATE_COMPLETE; bindTemporal(button, date, format, zonedDateTime -> new DatePickerDialog(button.getContext(), (picker, year, month, dayOfMonth) -> setPickedDate( LocalDateTime.of(LocalDate.of(year, month + 1, dayOfMonth), zonedDateTime.toLocalTime()), button, format, date), zonedDateTime.getYear(), zonedDateTime.getMonthValue() - 1, zonedDateTime.getDayOfMonth())); } It calls the bindTemporal method which takes in a function along with the button, date and the format and does two things. First, it sets the value of the date as the text of the button. Secondly, it attaches a click listener to the button and applies the function passed in as the argument when clicked. Below is the bindTemporal method for reference: private static void bindTemporal(Button button, ObservableField<String> date, String format, Function<ZonedDateTime, AlertDialog> dialogProvider) { if (date == null) return; String isoDate = date.get(); button.setText(DateUtils.formatDateWithDefault(format, isoDate)); button.setOnClickListener(view -> { ZonedDateTime zonedDateTime = ZonedDateTime.now(); try { zonedDateTime = DateUtils.getDate(isoDate); } catch (DateTimeParseException pe) { Timber.e(pe); } dialogProvider.apply(zonedDateTime).show(); }); } It was working pretty well until recently when we started getting deprecation warnings about using Observable fields as a parameter of Binding Adapter. Below is the full warning: Warning:Use of ObservableField and primitive cousins directly as method parameters is deprecated and support will be removed soon. Use the contents as parameters instead in method org.fossasia.openevent.app.common.app.binding.DateBindings.bindDate The only possible way that we could think of was to pass in regular String in place of Observable String. Now if we pass in a regular String object then the application won’t be reactive. Hence we decided to implement our own custom view to resolve this problem. Custom Date and Time Picker We decided to create an Abstract DateTimePicker class which will hold all the common code of our custom Date and Time pickers. It is highly recommended that you go through this awesome blog post first before reading any further. We won’t be going through the details already explained in the post. Following are the important features of this Abstract class: It extends the AppCompatButton class. It stores an ObservableString named value…

Continue ReadingImplementing Custom Date and Time Picker with 2-way Data Binding Support

Implementing Tax Endpoint in Open Event Server

The Open Event Server enables organizers to manage events from concerts to conferences and meetups. It offers features for events with several tracks and venues. The Event organizers may want to charge taxes on the event tickets. The Open Event Server has a Tax endpoint in order to support it. This blog goes over it’s implementation details in the project. Model First up, we will discuss what fields have been stored in the database for Tax endpoint. The most important fields are as follows: The tax rate charged in percentage The id for the Tax The registered company The country The address of the event organiser The additional message to be included as the invoice footer We also store a field to specify whether the tax should be included in the ticket price or not. Each Event can have only one associated Tax information. You can checkout the full model for reference here. Schema We have defined two schemas for the Tax endpoint. This is because there are a few fields which contain sensitive information and should only be shown to the event organizer or the admin itself while the others can be shown to the public. Fields like name and rate aren’t sensitive and can be disclosed to the public. They have been defined in the TaxSchemaPublic class. Sensitive information like the tax id, address, registered company have been included in the TaxSchema class which inherits from the TaxSchemaPublic class. You can checkout the full schema for reference here. Resources The endpoint supports all the CRUD operations i.e. Create, Read, Update and Delete. Create and Update The Tax entry for an Event can be created using a POST request to the /taxes endpoint. We analyze if the posted data contains a related event id or identifier which is necessary as every tax entry is supposed to be related with an event. Moreover we also check whether a tax entry already exists for the event or not since an event should have only one tax entry. An error is raised if that is not the case otherwise the tax entry is created and saved in the database. An existing entry can be updated using the same endpoint by making a PATCH request.   Read A Tax entry can be fetched using a GET request to the  /taxes/{tax_id}  endpoint with the id for the tax entry. The entry for an Event can also be fetched from /events/{event_id}/tax  endpoint. Delete An existing Tax entry can be deleted by making a DELETE request to the /taxes/{tax_id} endpoint with the id of the entry. We make sure the tax entry exists. An error is raised if that is not the case else we delete it from the database. References Marshmallow: http://marshmallow.readthedocs.io/en/latest/index.html Flask: http://flask.pocoo.org

Continue ReadingImplementing Tax Endpoint in Open Event Server

Stripe Authorization in Open Event Server

Stripe is a popular software platform for online payments. Since Open Event  allows the event organizers to sell tickets, an option to accept payments through Stripe is extremely beneficial to the organizer. Stripe allows accepting payments on other’s behalf using Connect. Connect is the Stripe’s full stack solution for platforms that need to process payments and process to multiple parties. This blog post goes over how Event organizers are able to link their Stripe accounts in order to accept payments later. Registering the platform The Admin of the Open Event Server will create an account on Stripe and register the platform. Upon creating the  account he/she will get a secret_key and publishable_key.  Moreover on registering the platform a client_id will be provided. These keys are saved in the application settings and only the Admin is authorized to view or change them. Connecting the Organiser’s account The Open Event Frontend has a wizard for creating an Event. It provides the organiser an option to connect his/her Stripe account in order to accept payments. Upon clicking the following button, the organiser is directed to Stripe where he/she can fill the required details.   The button directs the organizer to the following URL: https://connect.stripe.com/oauth/authorize?response_type=code&client_id=client_id&scope=read_write&redirect_uri=redirect_uri The above URL has the following parameters: client_id – The client ID acquired when registering your platform.required. response_type – Response type. The value is always code. required. redirect_uri – The URL to redirect the customer to after authorization. scope - We need it to be read_write in order to be able to charge on behalf of the customer later. After successfully entering the required details, the organizer is redirected to the redirect_url as specified in the above URL with a query parameter named as authorization_code. The Frontend sends this code to the Server using the Stripe Authorization endpoint which will be discussed in detail below. Fetching Tokens from Stripe The Server accepts the authorization_code by exposing the Stripe Authorization endpoint. It then uses it to fetch organizer’s details and token from Stripe and stores it for future use. The schema for Stripe Authorization is extremely simple. We require the client to send an authorization_code which will be used to fetch the details. Stripe_publishable_key of the event organizer is exposed via the endpoint and will be used by the Frontend later. class StripeAuthorizationSchema(Schema): """ Stripe Authorization Schema """ class Meta: """ Meta class for StripeAuthorization Api Schema """ type_ = 'stripe-authorization' self_view = 'v1.stripe_authorization_detail' self_view_kwargs = {'id': '<id>'} inflect = dasherize id = fields.Str(dump_only=True) stripe_publishable_key = fields.Str(dump_only=True) stripe_auth_code = fields.Str(load_only=True, required=True) event = Relationship(attribute='event', self_view='v1.stripe_authorization_event', self_view_kwargs={'id': '<id>'}, related_view='v1.event_detail', related_view_kwargs={'stripe_authorization_id': '<id>'}, schema="EventSchema", type_='event') We use the Requests library in order to fetch the results. First we fetch the client_id that we had stored in the application settings using a helper method called get_credentials. We then use it along with the authorization_code in order to make a POST request to Stripe Connect API. The full method is given below for reference. @staticmethod def get_event_organizer_credentials_from_stripe(stripe_auth_code): """ Uses the stripe_auth_code to get the other…

Continue ReadingStripe Authorization in Open Event Server

Adding Defaults Prior to Schema Validation Elegantly

The Open Event Server offers features for events with several tracks and venues. When we were designing the models for the API, we wanted to add default values for some fields in case they aren’t provided by the client. This blog discusses how we have implemented in the project using python decorators that complies to the DRY principle and is easy to test and maintain. Problem Let’s first discuss the problem at hand in detail. We use Marshmallow extensively in our project. Marshmallow is an ORM/ODM/framework-agnostic library for converting complex data types, such as objects, to and from native python data types. We use it for Validating the input data, Deserializing the input data to app-level objects and Serializing app-level objects to primitive Python types. We can define Schema’s very easily using Marshmallow. It also provides an easy way to declare default values to the fields. Below is a sample schema declaration: class SampleSchema(Schema): """ Sample Schema declaration """ class Meta: """ Meta class for the Sample Schema """ type_ = 'sample-schema' id = fields.Str(dump_only=True) field_without_default = fields.Str(required=True) field_with_default = fields.Boolean(required=True, default=False) We have defined an id field for storing the unique ID of the Model. We have also defined two other fields. One of them named as “field_with_default” is a Boolean field and has a default value specified as False. When a client makes a POST/PATCH request to the server, we first validate the input data sent to us by the clients against the defined schema. Marshmallow also supports schema validation but it doesn’t support using default values during deserialization of the input data. It meant that whenever the input data had a missing field, the server would throw a validation error even for a field for which the default values are defined. It was clearly wrong since if the default values are defined, we would want that value to be used for the field. This defeats the entire purpose of declaring default values at the first place. So, we would ideally like the following behaviour from the Server: If the values are defined in the input data, use it during validation. If the value for a required field is not defined but default value has been defined in the Schema, then use that value. If no value has been defined for a required field and it doesn’t have any default value specified, then throw an error. Solution Marshmallow provides decorators like @pre_load and @post_load for adding pre-processing and post-processing methods. We can use them to add a method in each of the Schema classes which takes in the input data and the schema and adds default values to fields before we validate the input. The first approach which we took was to add the following method to each of the schema classes defined in the project. @pre_load def patch_defaults(schema, in_data): data = in_data.get('data') if data is None or data.get('attributes') is None: return in_data attributes = data.get('attributes') for name, field in schema.fields.items(): dasherized_name = dasherize(name) attribute = attributes.get(dasherized_name) if attribute is…

Continue ReadingAdding Defaults Prior to Schema Validation Elegantly

Adding Modules API on Open Event Server

The Open Event Server enables organizers to manage events from concerts to conferences and meet-ups. It offers features for events with several tracks and venues. Event managers can create invitation forms for speakers and build schedules in a drag and drop interface. The event information is stored in a database. The system provides API endpoints to fetch the data, and to modify and update it. The Open Event Server is based on JSON 1.0 Specification and hence build on top of Flask Rest Json API (for building Rest APIs) and Marshmallow (for Schema). In this blog, we will talk about how to add API for accessing the Modules on Open Event Server. The focus is on Schema creation and it’s API creation. Schema Creation For the ModuleSchema, we’ll make our Schema as follows Now, let’s try to understand this Schema. In this feature, we are providing Admin the rights to set whether Admin wants to include tickets, payment and donation in the open event application. First of all, we will provide three fields in this Schema, which are ticket_include, payment_include and donation_include. The very first attribute ticket_include should be Boolean as we want Admin to update it whether he wants to include ticketing system in the application from default one which is False. Next attribute payment_include should be Boolean as we want Admin to update it whether he wants to include payment system in the application from default one which is False. Next attribute donation_include should be Boolean as we want Admin to update it whether he wants to include donation system in the application from default one which is False. API Creation For the ModuleDetail, we’ll make our API as follows Now, let’s try to understand this API. In this API, we are providing Admin the rights to set whether Admin wants to include tickets, payment and donation in the open event application. First of all, there is the need to know that this API has two method GET and PATCH. Decorators shows us that only Admin has permissions to access PATCH method for this API i.e. only Admins can modify the modules . before_get method shows us that this API will give first record of Modules model irrespective of the id requested by user. Schema used here is default one of Modules Hence, GET Request is accessible to all the users. So, we saw how Module Schema and API is created to allow users to get it’s values and Admin users to modify it’s values. Resources Documentation | Marshmallow : https://marshmallow-jsonapi.readthedocs.io/en/latest/ Documentation | Flask Rest JSONAPI : http://flask-rest-jsonapi.readthedocs.io/en/latest/

Continue ReadingAdding Modules API on Open Event Server

Handling Click Events using Custom Binding Adapters

The Open Event Organiser Android App is the Event management app for organizers using the Open Event Platform. It is currently released in the Alpha phase on the Google Play Store here and is being actively developed by the community. The Data Binding Library is one of the most popular libraries among the android developers. We use it extensively in the application in order to greatly simplify the UI binding logic. While trying to show the details of a speaker in the application, we wanted to list his/her social media links using Image buttons. Upon clicking one of these buttons, the user was supposed to be directed to the link after opening the default web browser. This blog post discusses how we used custom Binding Adapters to handle click events on an Image Button by defining a custom attribute. Defining the Binding Adapter We defined a simple Binding Adapter for an Image button meant to handle social media links. We used “imageOnClick” as the custom attribute name for specifying the URL that will be opened once the button is clicked. @BindingAdapter("imageOnClick") public static void bindOnImageButtonClickListener(ImageButton imageButton, String url) { imageButton.setOnClickListener(view -> { if (url != null) { Context context = imageButton.getContext(); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); if (intent.resolveActivity(context.getPackageManager()) != null) { context.startActivity(intent); } else { Toast.makeText(context, "No Web browser found", Toast.LENGTH_SHORT).show(); } } }); }   The method can be named anything you want and can be placed anywhere in the project but we would recommend creating a separate class for all the Binding adapters. The important things to take away from the above method are: The method needs to be public otherwise the Data binding framework won’t be able to find it. We need to pass in the view as the first parameter and the attribute value as the second parameter. Then we simply set the familiar click listener to handle the click interaction. We use the Context from the view passed in the method as the first parameter. Then we create an Intent and set the passed in URL as the data. We make sure that the user has a browser installed on his/her android phone before we try to open the browser. We show a suitable error message if they don’t. Using it in Layout Using the custom attribute in the layout was extremely simple. We specified the url using the attribute “imageOnClick” and the rest was handled by the Binding Adapter and the Data binding framework.   <ImageButton android:id="@+id/action_speakers_linkedin" android:layout_width="@dimen/spacing_larger" android:layout_height="match_parent" android:contentDescription="@string/linkedin_icon" app:imageOnClick="@{ speaker.linkedin }" android:background="#ededed" android:visibility="@{ (speaker.linkedin != null) ? View.VISIBLE : View.GONE }" app:srcCompat="@drawable/ic_linkedin_colored"/> References Data Binding Library: Data Binding Library Binding Adapters guide: Binding Adapters

Continue ReadingHandling Click Events using Custom Binding Adapters

Creating Onboarding Screens for SUSI iOS

Onboarding screens are designed to introduce users to how the application works and what main functions it has, to help them understand how to use it. It can also be helpful for developers who intend to extend the current project. When you enter in the SUSI iOS app for the first time, you see the onboarding screen displaying information about SUSI iOS features. SUSI iOS is using Material design so the UI of Onboarding screens are following the Material design. There are four onboarding screens: Login (Showing the login features of SUSI iOS) - Login to the app using SUSI.AI account or else signup to create a new account or just skip login. Chat Interface (Showing the chat screen of SUSI iOS) - Interact with SUSI.AI asking queries. Use microphone button for voice interaction. SUSI Skill (Showing SUSI Skills features) - Browse and try your favorite SUSI.AI Skill. Chat Settings (SUSI iOS Chat Settings) - Personalize your chat settings for the better experience. Onboarding Screens User Interface   There are three important components of every onboarding screen: Title - Title of the screen (Login, Chat Interface etc). Image - Showing the visual presentation of SUSI iOS features. Description - Small descriptions of features. Onboarding screen user control: Pagination - Give the ability to the user to go next and previous onboarding screen. Swiping - Left and Right swipe are implemented to enable the user to go to next and previous onboarding screen. Skip Button - Enable users to skip the onboarding instructions and go directly to the login screen. Implementation of Onboarding Screens: Initializing PaperOnboarding: override func viewDidLoad() { super.viewDidLoad() UIApplication.shared.statusBarStyle = .lightContent view.accessibilityIdentifier = "onboardingView" setupPaperOnboardingView() skipButton.isHidden = false bottomLoginSkipButton.isHidden = true view.bringSubview(toFront: skipButton) view.bringSubview(toFront: bottomLoginSkipButton) } private func setupPaperOnboardingView() { let onboarding = PaperOnboarding() onboarding.delegate = self onboarding.dataSource = self onboarding.translatesAutoresizingMaskIntoConstraints = false view.addSubview(onboarding) // Add constraints for attribute: NSLayoutAttribute in [.left, .right, .top, .bottom] { let constraint = NSLayoutConstraint(item: onboarding, attribute: attribute, relatedBy: .equal, toItem: view, attribute: attribute, multiplier: 1, constant: 0) view.addConstraint(constraint) } }   Adding content using dataSource methods: let items = [ OnboardingItemInfo(informationImage: Asset.login.image, title: ControllerConstants.Onboarding.login, description: ControllerConstants.Onboarding.loginDescription, pageIcon: Asset.pageIcon.image, color: UIColor.skillOnboardingColor(), titleColor: UIColor.white, descriptionColor: UIColor.white, titleFont: titleFont, descriptionFont: descriptionFont),OnboardingItemInfo(informationImage: Asset.chat.image, title: ControllerConstants.Onboarding.chatInterface, description: ControllerConstants.Onboarding.chatInterfaceDescription, pageIcon: Asset.pageIcon.image, color: UIColor.chatOnboardingColor(), titleColor: UIColor.white, descriptionColor: UIColor.white, titleFont: titleFont, descriptionFont: descriptionFont),OnboardingItemInfo(informationImage: Asset.skill.image, title: ControllerConstants.Onboarding.skillListing, description: ControllerConstants.Onboarding.skillListingDescription, pageIcon: Asset.pageIcon.image, color: UIColor.loginOnboardingColor(), titleColor: UIColor.white, descriptionColor: UIColor.white, titleFont: titleFont, descriptionFont: descriptionFont),OnboardingItemInfo(informationImage: Asset.skillSettings.image, title: ControllerConstants.Onboarding.chatSettings, description: ControllerConstants.Onboarding.chatSettingsDescription, pageIcon: Asset.pageIcon.image, color: UIColor.iOSBlue(), titleColor: UIColor.white, descriptionColor: UIColor.white, titleFont: titleFont, descriptionFont: descriptionFont)] extension OnboardingViewController: PaperOnboardingDelegate, PaperOnboardingDataSource { func onboardingItemsCount() -> Int { return items.count } func onboardingItem(at index: Int) -> OnboardingItemInfo { return items[index] } }   Hiding/Showing Skip Buttons: func onboardingWillTransitonToIndex(_ index: Int) { skipButton.isHidden = index == 3 ? true : false bottomLoginSkipButton.isHidden = index == 3 ? false : true } Resources: Usage of PaperOnboarding SUSI iOS Link: https://github.com/fossasia/susi_iOS

Continue ReadingCreating Onboarding Screens for SUSI iOS

Adding multiple email support for users on Open Event Server

The Open Event Server enables organizers to manage events from concerts to conferences and meet-ups. It offers features for events with several tracks and venues. Event managers can create invitation forms for speakers and build schedules in a drag and drop interface. The event information is stored in a database. The system provides API endpoints to fetch the data, and to modify and update it. The Open Event Server is based on JSON 1.0 Specification and hence build on top of Flask Rest Json API (for building Rest APIs) and Marshmallow (for Schema). In this blog, we will talk about how to add support of multiple emails for a user in Open Event Server. The focus is on model and schema creation for this support. Model Creation For the UserEmail, we’ll make our model as follows from app.models import db class UserEmail(db.Model): """user email model class""" __tablename__ = 'user_emails' id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(120), unique=True, nullable=False) verified = db.Column(db.Boolean, default=False) user_id = db.Column(db.Integer, db.ForeignKey('users.id', ondelete='CASCADE')) user = db.relationship("User", backref="emails", foreign_keys=[user_id]) def __init__(self, email=None, user_id=None): self.email = email self.user_id = user_id def __str__(self): return 'User:' + unicode(self.user_id).encode('utf-8') + ' email: ' + unicode(self.email).encode('utf-8') def __unicode__(self): return unicode(self.id) Now, let’s try to understand the attributes of this model. id is most important Column required in every model to set it as primary key and to uniquely identify an UserEmail object. email is that attribute which is required hence should be unique and non-nullable. Verified attribute is used to check whether a email is verified or not (thus should be boolean) User_id is the attribute which specifies id of the user whose email is contained in the UserEmail object. Finally, a relationship with the user of id user_id and these emails (associated with the User.id == user_id) will be stored in the attribute emails in User Model. Schema Creation For the model UserEmail, we’ll make our schema UserEmailSchema as follows from marshmallow_jsonapi import fields from marshmallow_jsonapi.flask import Schema, Relationshipfrom app.api.helpers.utilities import dasherizeclass UserEmailSchema(Schema): """   API Schema for user email Model   """class Meta: """  Meta class for user email API schema  """ type_ = 'user-emails' self_view = 'v1.user_emails_detail' self_view_kwargs = {'id': '<id>'} inflect = dasherize id = fields.Str(dump_only=True) email = fields.Email(allow_none=False) user_id = fields.Integer(allow_none=False) user = Relationship(attribute='user', self_view='v1.user_email', self_view_kwargs={'id': '<id>'}, related_view='v1.user_detail', related_view_kwargs={'user_id': '<id>'}, schema='UserSchema', type_='user' ) Marshmallow-jsonapi provides a simple way to produce JSON API-compliant data in any Python web framework. Now, let’s try to understand the schema UserEmailSchema id : Same as in model id is used as uniquely identify an UserEmail object. email : Same as in model email is required thus allow_none is set to False. User_id : user_id is the id of user whose email is contained in a UserEmailSchema object. User : It tells whole attributes of the user to which this email belongs to. So, we saw how to add multiple email support for users on Open Event Server. We just required to create a model and its schema to add this feature. Similarly, to add…

Continue ReadingAdding multiple email support for users on Open Event Server