Implementing the List View of the Skill Cards

In this blog post, we are going to understand the implementation of the UI for the SUSI.AI skill card that is displayed on various routes of the SUSI Skill CMS Web-App. Now, there are two types of views of the views for the skill cards - List view and Grid view. We will learn to implement the List View in this blog. Final UI of the Skill Card Going through the implementation The UI has multiple components - The image thumbnail. The title and author section, Below that we have examples, ratings and the description section. Fetching the data The Skill Metadata for each skill is passed as props from the parent of the component, where this UI is implemented. This data object contains the various data points that are needed to display the UI. The key values used are - skill_name - Used in the Title of the Skill Card image - Used to display the thumbnail image of the skill model - used to create the link to the Skill Details page group - used to create the link to the Skill Details page language - used to create the link to the Skill Details page skill_tag - used to create the link to the Skill Details page examples - used to display the examples card. author - used to display the Author name skill_rating - Used to display the stars and the total number of ratings of the skill The following image shows the various areas, where the data is being used. Parsing the data and creating JSX Below is the code used to parse the data and achieving the UI, followed by the explanation. ….. loadSkillCards = () => { let cards = []; Object.keys(this.state.skills).forEach(el => { let skill = this.state.skills[el]; let skill_name = 'Name not available', examples = [], image = '', description = 'No description available', author_name = 'Author', average_rating = 0, total_rating = 0; if (skill.skill_name) skill_name = skill.skill_name.charAt(0).toUpperCase() + skill_name.slice(1); …. // Similarly parse, image, descriptions, author …. if (skill.examples) examples = skill.examples.slice(0, 2); // Select max 2 examples if (skill.skill_rating) { average_rating = parseFloat(skill.skill_rating.stars.avg_star); total_rating = parseInt(skill.skill_rating.stars.total_star, 10); } cards.push( <div style={styles.skillCard} key={el}> <div style={styles.imageContainer}> // Display the image, else default avatar compoennt CircleImage </div> <div style={styles.content}> <div style={styles.header}> // Add Link to the skill title <div style={styles.title}><span>{skill_name}</span></div> <div style={styles.authorName}><span>{author_name}</span></div> </div> <div style={styles.details}> <div style={styles.exampleSection}> {examples.map((eg, index) => { return ( <div key={index} style={styles.example}>&quot;{eg}&quot;</div>); })} </div> <div style={styles.textData}> <div style={styles.row}> <div style={styles.rating}> // Show the 5-star rating section </div> </div> <div style={styles.row}> // Insert the skill description </div> //Close the div tags ); }); this.setState({cards}); }; render() { . . return (<div style={styles.gridList}>{skillDisplay}</div>); } . .   An array of skills is passed as props and set in the state of the component in the constructor lifecycle method. The loadSkillCards() function is called in the didComponentMount lifecycle method, which is responsible for creating the JSX for all the Skill Cards. In this function, the map property of array is used, to…

Continue ReadingImplementing the List View of the Skill Cards

Adding chrome custom tabs support for native browsing in Eventyay Organizer App

In Eventyay Organizer App when a user taps a URL, we face a choice: either launch a browser, or build our own in-app browser using WebViews. Both options present challenges — launching the browser is a heavy context switch that isn't customizable, while WebViews don't share state with the browser and add maintenance overhead. Chrome Custom Tabs give apps more control over their web experience, and make transitions between native and web content more seamless without having to resort to a WebView. The first step for a Custom Tabs integration is adding the Custom Tabs Support Library to your project. Open your build.gradle file and add the support library to the dependency section. One must remember that Chrome Custom Tabs is not an Open Source library, so we are here including the  playStoreImplementation  as opposed to implementation, so that our FDroid build doesn’t fail. Adding the dependency in build.gradle(app-level) in the project: dependencies {    //Other dependencies    // Chrome Custom Tabs    playStoreImplementation 'com.android.support:customtabs:${versions.chromeCustomTabs} }   And add this to  versions.gradle  file: versions.chromeCustomTabs='27.1.0' The UI Customizations are done by using the  CustomTabsIntent  and the CustomTabsIntent.Builder  classes; the performance improvements are achieved by using the  CustomTabsClient  to connect to the Custom Tabs service, warm-up Chrome and let it know which urls will be opened. Since we need this to feature to be available for each place in the app that redirects to an external link, we must create it in the utility class of the project. Let’s name this class as BrowserUtils and do it as follows: public final class BrowserUtils {     private BrowserUtils() {    }     public static void launchUrl(Context context, String url) {        CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();        CustomTabsIntent customTabsIntent = builder.build();        customTabsIntent.launchUrl(context, Uri.parse(url));    } } Now all we need to do is replace the old method to create an intent: findPreference(getString(R.string.privacy_policy_key)).setOnPreferenceClickListener(preference -> {            Intent intent = new Intent(Intent.ACTION_VIEW);            intent.setData(Uri.parse(PRIVACY_POLICY_URL));            startActivity(intent);            return true;        }); with this call to the utility method: findPreference(getString(R.string.privacy_policy_key)).setOnPreferenceClickListener(preference -> {            BrowserUtils.launchUrl(getContext(), PRIVACY_POLICY_URL);            return true;        }); We can also add animation or customize the color of the toolbar, add action buttons or add animations like this: builder.setToolbarColor(colorInt); builder.setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left); builder.setExitAnimations(this, R.anim.slide_in_left, R.anim.slide_out_right); Here’s the result: Resources Chrome Custom Tabs Developer Guide https://developer.chrome.com/multidevice/android/customtabs Codepath Guide: Chrome Custom Tabs https://github.com/codepath/android_guides/wiki/Chrome-Custom-Tabs Google Chrome Custom Tabs Client: Sample implementation https://github.com/GoogleChrome/custom-tabs-client

Continue ReadingAdding chrome custom tabs support for native browsing in Eventyay Organizer App

Individual skill usage subsections in SUSI Skill CMS

In SUSI.AI Skills CMS several interactive skill related statistics are displayed on the skill page for each skill which includes user ratings, ratings over time, user feedback and skill usage data displayed interactively. The skill usage section is further subdivided to get more insight into how the skill has been used and from where. Therefore we have three subsections which display Time wise skill usage, device wise usage, and country wise usage. All this data can help evaluate which devices are mostly using the skill or data like in which country the skill is more popular than others. So in this post, we mainly discuss the UI of how these sections are implemented. Implementation Adding a Card component to the skill page component at the bottom of the skill page component. <SkillUsageCard skill_usage={this.state.skill_usage} device_usage_data={this.state.device_usage_data} countryWiseSkillUsage={this.state.countryWiseSkillUsage} />   In the render function of the newly made component, we import the Paper component from material-ui and render it at the top to contain the subsections to give it a card-like UI. <div> <Paper className="margin-b-md margin-t-md"> ... </Paper> </div>   Create div for the time wise skill usage. Calculate total skill usage for displaying the total skill usage count and also it helps to decide whether we need to render the section or not. So if the total skill usage by time count is greater than zero then render the line chart for visual analysis and display the total skill usage count too. let totalSkillUsage = 0; if (this.props.skill_usage) { // eslint-disable-next-line totalSkillUsage = this.props.skill_usage.reduce((totalCount, day) => { if (day) { return totalCount + day.count; } return totalCount; }, 0); } <div className="time-chart"> <div> <ResponsiveContainer width={this.state.width} height={300}> <LineChart ... > <XAxis dataKey="date" padding={{ right: 20 }} /> <YAxis allowDecimals={false} /> <Tooltip wrapperStyle={{ height: '60px' }} /> <Legend /> <Line ... /> </LineChart> </ResponsiveContainer> </div> </div> <div className="total-hits"> <div className="large-text">{totalSkillUsage}</div> Hits this week </div>   Create div for the Device wise usage. Conditionally render it in case the device wise data is available in the props. <div className="device-usage"> <div className="sub-title">Device wise Usage</div> {this.props.device_usage_data && this.props.device_usage_data.length ? ( <div className="pie-chart"> <ResponsiveContainer width={600} height={350}> <PieChart> <Pie ... > {this.props.device_usage_data.map((entry, index) => ( <Cell key={index} fill={entry.color} /> ))} </Pie> <Legend wrapperStyle={{ position: 'relative' }} /> </PieChart> </ResponsiveContainer> </div> </div>   Create a div for the country wise usage. We get the country wise usage data from the props and then we plug in the data in the geo chart component and also display the data as a table on the side. In case no data comes in or is unavailable we do not render the component at all. <div> {countryWiseSkillUsage && countryWiseSkillUsage.length ? ( <div className="country-usage-container"> <div className="country-usage-graph"> <GeoChart data={countryWiseSkillUsage} /> </div> <div className="country-usage-list"> <Table> ... </div> </div> ) : ( <div className="unavailable-message"> Country wise usage distribution is not available. </div> )} </div>   This is how the three subsection in the skill usage component are implemented Resources React Chartkick, Package which provides GeoChart component: https://github.com/ankane/react-chartkick  Sabastian Eschweiler, Getting started with material-UI for React, https://medium.com/codingthesmartway-com-blog/getting-started-with-material-ui-for-react-material-design-for-react-364b2688b555

Continue ReadingIndividual skill usage subsections in SUSI Skill CMS

Adding a feature to delete skills from skill page for admins

SUSI Skill CMS has evolved drastically over the past few months with not only the introduction of skill metrics, skill analytics and powerful sorting features and interactive skill view types we needed the SUSI admins to be able to delete skills directly from the skills page and hence the skill can be deleted without visiting the admin service and then locating the skill and deleting it. This feature can be useful when a skill live on the system needs to be removed instantaneously for any reason like the API used by the skill going down or if it is a redundant skill or anything else. This feature was much needed for the admins and thus it was implemented. About the API An API is developed at the server so from the client we call this API to fetch data from the server and plug this data into the chart we wish to render. Endpoint : /cms/deleteSkill.json?access_token={access_token}&model={model}&group={group}&language={language}&skill={skill}   Parameters : Model Group Skill Language Feedback Access token (taken from the session of the logged in user) Sample API call : /cms/deleteSkill.json?access_token=ANecfA1GjP4Bkgv4PwjL0OAW4kODzW&model=general&group=Knowledge&language=en&skill=whois Displaying a button with delete icon on skill page The option to delete skill should be available at the skill page for each skill so we add a button with a delete icon for this in the group of edit skills and skill version buttons, clicking over this button will open up a confirmation dialog with two actions notable the delete/confirm button which deletes the skills and the cancel button which can be useful in case the user changes their mind. On clicking the delete button the request to delete the skill is sent to the server and thus the skill is deleted. Import some required components import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import Cookies from 'universal-cookie'; import $ from 'jquery';   Adding some variables to the component state which will help us decide when the delete skill dialog is to be shown. this.state = { ... showDeleteDialog: false, ... }   Display the delete skill button only when the user is logged in user has admin rights. { cookies.get(showAdmin) ? ( ... ): '' }   Adding some JSX to the component’s render function which includes a div in the skill page section and the Dialog component for the delete skill and some actions which in our case is the the confirmation to delete skill and to cancel the skill deletion in case the user changes their mind. Also a tooltip is shown which appears on hovering over the delete skill button. <div className="skillDeleteBtn"> <FloatingActionButton onClick={this.handleDeleteToggle} data-tip="Delete Skill" backgroundColor={colors.header} > <DeleteBtn /> </FloatingActionButton> <ReactTooltip effect="solid" place="bottom" /> <Dialog title="Delete Skill" actions={deleteDialogActions} modal={false} open={this.state.showDeleteDialog} onRequestClose={this.handleDeleteToggle} > <div> Are you sure about deleting{' '} <span style={{ fontWeight: 'bold' }}> {this.state.skill_name} </span>? </div> </Dialog> </div>   Clicking the delete skill button will change the state variable which decides whether the dialog is to be shown or not. handleDeleteToggle = () => { this.setState({ showDeleteDialog: !this.state.showDeleteDialog, }); };   Adding submit and…

Continue ReadingAdding a feature to delete skills from skill page for admins

Adding a feature to report skills in the CMS

A lot of interesting features were introduced in the SUSI.AI Skills CMS over the past few months but it lacked the functionality for users to be able to report skills which they find inappropriate or for any other reason. So an API was developed at the server to flag the skills as inappropriate and thus using this API endpoint an option was added at the skill page for each skill to mark the skill as inappropriate or report it. This data could be useful by admins to re-review the skills and see if something is wrong with it and it things seem out of place the skill can be removed or can be disabled. About the API An API is developed at the server so from the client we call this API to fetch data from the server and plug this data into the chart we wish to render. Endpoint : /cms/reportSkill.json?model={model}&group={group}&skill={skill}&feedback={feedback message}&access_token={access_token}   Parameters : Model Group Skill Language Feedback Access token (taken from the session of the logged in user) Sample API call : /cms/reportSkill.json?model=general&group=Knowledge&skill=Anime Suggestions&feedback=Not good&access_token=6O7cqoMbzlClxPwg1is31Tz5pjVwo3 Displaying option to report on skill page The option to report skill should be available at the skill page for each skill so we add a field in the skill details section to the skill page component which will only be visible to the logged in users and on clicking over this field we display a dialog with a text field, the user can enter the message or the reason for reporting the skill and then clicking on the submit button when the user is done writing or click on the cancel button in case the user changes their mind to report the skill. Once the message is submitted we run a function by passing in the feedback message which in turn hits the corresponding endpoint and posts the data on the server. Import some required components import Dialog from 'material-ui/Dialog'; import FlatButton from 'material-ui/FlatButton'; import TextField from 'material-ui/TextField';   Adding some variables to the component state which will help us decide when the report dialog is to be shown and the feedback message as the user types and some other relevant data. this.state = { ... skillTag: '', showReportDialog: false, feedbackMessage: '' ... }   Display the report feature only when the user is logged in. { cookies.get('loggedIn') ? ( ... ): '' }   Adding some jsx to the component’s render function which includes a div in the skill details section and the Dialog component for the report message and confirmation and the dialog contains a text field to take report message and some actions which in our case is the send report action and the cancel report action. <tr> <td>Report: </td> <td> <div style={{ color: '#108ee9', cursor: 'pointer' }} onClick={this.handleReportToggle} > Flag as inappropriate </div> </td> <Dialog title="Flag as inappropriate" actions={reportDialogActions} modal={false} open={this.state.showReportDialog} onRequestClose={this.handleReportToggle} > <TextField hintText="Leave a feedback message" floatingLabelText="Feedback message" multiLine floatingLabelFocusStyle={{ color: 'rgb(66, 133, 244)', }} underlineFocusStyle={{ borderColor: 'rgb(66, 133, 244)', }} fullWidth onChange={(event, val) =>…

Continue ReadingAdding a feature to report skills in the CMS

Add Info on Skill Usage Distribution for all Skills by an Author in SUSI.AI

SUSI Skill CMS has a dashboard option available at the /dashboard route which displays several data for the logged in user as the skills created by the user and the ratings the user has provided to all the skills, since we have a skill usage section available on all skill pages which depicts the skill usage count for the past week in a line chart. Skill creators didn’t have a functionality to see the skill usage distribution on their skills which can provide some useful insight like how some of the skills they created are performing in comparison to the others so I developed a ‘My Analytics’ section in the dashboard page and displayed the skill usage distribution in the form of pie chart among the skills created by the logged in users. About the API An API is developed at the server so from the client we call this API to fetch data from the server and plug this data into the chart we wish to render. Endpoint : /cms/getSkillsByAuthor.json?author_email={email}   Parameters : Email ID which is taken from the cookies since it is stored there once the user logs in. Sample API call : /cms/getSkillsByAuthor.json?author_email=anshu.av97@gmail.com Fetching the data for the component We first create a separate My Analytics component and require it in the dashboard and make an AJAX call to the appropriate endpoint inside a loadSkillsUsage function which is called inside the componentDidMount hook after which the server returns raw data in the form of JSON. We then pass the response into a saveUsageData function to parse the data for our use and save it to the application state. loadSKillsUsage = () => { let url = urls.API_URL + `/cms/getSkillsByAuthor.json?author_email=${cookies.get('emailId')}`; let self = this; $.ajax({ url: url, dataType: 'jsonp', jsonp: 'callback', crossDomain: true, success: function(data) { self.saveUsageData(data.author_skills || []); ... }, error: function(err) { ... }, }); };   Set the application state with the received data which the pie chart component will use as it’s data source. saveUsageData = data => { const skillUsage = data.map(skill => { let dataObject = {}; dataObject.skill_name = skill.skill_name; dataObject.usage_count = skill.usage_count || 0; return dataObject; }); this.setState({ skillUsage }); }; Implementing the UI We create a separate ‘My Analytics’ component which is imported into the dashboard component to make the code cleaner and manageable. So inside the My analytics component, we fetch the data from the server as depicted above and after that, we render the pie chart component after importing from the recharts library. Importing the pie chart components from the recharts library. import { Legend, PieChart, Pie, Sector, Cell, ResponsiveContainer } from 'recharts';   Rendering the pie chart component while supplying appropriate props most important of which is the data prop which will be used in the chart and that data is available in the application state as saved earlier. We also have other styling props and a function which is triggered when hovering over cells of the pie chart to represent the data of the hovered cell.…

Continue ReadingAdd Info on Skill Usage Distribution for all Skills by an Author in SUSI.AI

Implementing feature to filter skills by average customer review

SUSI Skill CMS showcases all the skills on the index page but lacks the functionality to refine skills according to average customer review which is a much-needed feature since some users may only want to try skills which have at least a minimum rating so they can know instantly which skills are performing well in comparison to others. Thus, we implement several star inputs on the sidebar to select skills which have ratings greater than or equal to the selected rating input. Implementing the UI Add a menu to the sidebar at the bottom of all categories and display ‘Refine by’ submenu text to denote the section. <Menu desktop={true} disableAutoFocus={true}> <Subheader style={{ fontWeight: 'bold' }}>Refine by</Subheader> <h4 style={{ marginLeft: '12px', marginBottom: '4px' }}> Avg. Customer Review </h4> ...   Display rating options to the user by displaying a list of Ratings component imported from react-ratings-declarative, these are to be displayed for all ratings say four stars and above, three stars and above and so on, i.e. <div style={styles.singleRating} onClick={() => this.handleRatingRefine(4)} > <Ratings rating={4} widgetRatedColors="#ffbb28" widgetDimensions="20px" widgetSpacings="0px" > <Ratings.Widget /> <Ratings.Widget /> <Ratings.Widget /> <Ratings.Widget /> <Ratings.Widget /> </Ratings> <div style={styles.ratingLabel} className={this.state.rating_refine === 4 ? 'bold' : ''} > & Up </div> </div>   We add some styling and attach an onClick listener on each rating component which will handle the refining of skills according to the rating clicked, the idea behind this is to save the rating for the clicked option to the component state and re-render the skill cards handleRatingRefine = rating => { this.setState( { rating_refine: rating, }, this.loadCards(), ); };   When the component state is successfully set loadCards function as a callback is called which re-renders the cards by applying filter over the skills which match the average rating criteria which we just set. if (self.state.rating_refine) { data.filteredData = data.filteredData.filter( skill => skill.skill_rating.stars.avg_star >= self.state.rating_refine, ); } Displaying a button to clear any refinements made Once the skills are refined a button is needed to clear any refinements made. Initially when no refinements are made the rating_refine in the state is set to null which indicates that no refinements are made so whenever the value of that state is no null we render a button to clear the refinements or set the rating_refine state to null. {this.state.rating_refine ? ( <div className="clear-button" style={styles.clearButton} onClick={() => this.handleRatingRefine(null)} > Clear </div> ) : ( '' )} Resources MDN Docs, ES6 array filter, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter  Hassan, Displayed a simple rating view, https://medium.com/@hassanahmedkhan/simple-ratings-view-in-react-native-59a0ceb2d13f  João, Learn about javascript map, filter, reduce, https://medium.com/@joomiguelcunha/learn-map-filter-and-reduce-in-javascript-ea59009593c4 

Continue ReadingImplementing feature to filter skills by average customer review

Implementing My Rating Section on the SUSI.AI Skills Dashboard

SUSI Skill CMS provides the functionality to rate the skills, therefore users rate skills they use but there isn’t any place where they can see all the skills they rated, thus a ‘My Ratings’ section was implemented on the dashboard page to view these statistics. So to see what ratings they have given to skills they can just login to the cms and navigate to /dashboard and a my ratings components is visible there which lists all the ratings the user has provided in a nice tabular format. About the API An API endpoint is implemented on the server which fetches the skill data for skills the user has rated which includes the skill name, stars given and the timestamp. /cms/getProfileDetails.json?access_token=   So we pass the access token of the authenticated user and a JSON response is received which contains all the details as depicted below, this data is then parsed on the frontend and filled in a tabular form on the MyRatings section. { "rated_skills": [ {"amazon_shopping": { "stars": "1", "timestamp": "2018-06-10 13:05:32.295" }}, {"aboutsusi": { "stars": "2", "timestamp": "2018-06-10 13:26:26.222" }}, {"anagrams": { "stars": "3", "timestamp": "2018-06-10 13:25:31.195" }} ], "session": {"identity": { "type": "email", "name": "anshu.av97@gmail.com", "anonymous": false }}, "accepted": true, "message": "User ratings fetched." } Displaying the results on the web Make a MyRatings component and render it on the dashboard component Make an AJAX call to the API and save the returned data to the component state. First create a loadSkills function in componentDidMount which will be called just as the component is mounted to the DOM which will then fetch data from the server, extract the meaningful parts such as skill_name, skill_star and timestamp and push them to an array which in this case is ratingsData. While the data is being fetched we show a circular loader for better UX and once we receive the data we save it in the component state and turn loading to false which will replace the loading animation with the actual data. loadSkills = () => { let url; url = urls.API_URL + '/cms/getProfileDetails.json?access_token=' + cookies.get('loggedIn'); let self = this; let ratingsData = []; $.ajax({ url: url, jsonpCallback: 'pxcd', dataType: 'jsonp', jsonp: 'callback', crossDomain: true, success: function(data) { if (data.rated_skills) { for (let i of data.rated_skills) { let skill_name = Object.keys(i)[0]; ratingsData.push({ skill_name: skill_name, skill_star: i[skill_name].stars, rating_timestamp: i[skill_name].timestamp, }); } self.setState({ ratingsData, }); } self.setState({ loading: false, }); }, error: function(err) { self.setState({ loading: false, openSnackbar: true, msgSnackbar: "Error. Couldn't rating data.", }); }, }); };   Display a loading animation when the data is being fetched, we maintain a state in the component called loading which is initially true since we don’t have the data just as the component is rendered so after we receive the data we turn the loading state to false which will hide the circular loader and display the component with the data received. {this.state.loading ? ( <div className="center"> <CircularProgress size={62} color="#4285f5" /> <h4>Loading</h4> </div> ) : ( ... ) }   Add a…

Continue ReadingImplementing My Rating Section on the SUSI.AI Skills Dashboard

Upgrading Open Event to Use Sendgrid API v3

Sendgrid recently upgraded their web API to send emails, and support for previous versions was deprecated. As a result, Open Event Server’s mail sending tasks were rendered unsuccessful, because the requests they were sending to Sendgrid were not being processed. On top of that, it was also found out later that the existing Sendgrid API key on the development server was expired. This had to be fixed at the earliest because emails are a core part of Open Event functionality. The existing way for emails to be sent via Sendgrid used to hit the endpoint “https://api.sendgrid.com/api/mail.send.json” to send emails. Also, the payload structure was as follows: payload = { 'to': to, 'from': email_from, 'subject': subject, 'html': html } Also, a header  "Authorization": "Bearer " accompanied the above payload. However, Sendgrid changed the payload structure to be of the following format: { "personalizations": [ {"to": [ {"email": "example@example.com"} ] } ], "from": { "email": "example@example.com" }, "subject": "Hello, World!", "content": [ { "type": "text/plain", "value": "Heya!" } ] } Furthermore, the endpoint was changed to be “https://api.sendgrid.com/v3/mail/send”. To incorporate all these changes with the minimum number of modified lines in the codebase, it was required for that the structure change itself happens at a fairly low level. This was because there are lots of features in the server that perform a wide variety of email actions. Thus, it was clear that changing all of them will not be the most efficient thing to do. So the perfect place to implement the API changes was the function send_email() in mail.py, because all other higher-level email functions are built on top of this function. But this was not the only change, because this function itself used another function, called send_email_task() in tasks.py, specifically for sending email via Sendgrid. So, in conclusion, the header modifications were made in send_email() and payload structure as well as endpoint modifications were made within send_email_task(). This brought the server codebase back on track to send emails successfully. Finally, the key for development server was also renewed and added to its settings in the Heroku Postgres database. Screenshots: Resources Implement Email in Open Event Server SendGrid API v3 docs

Continue ReadingUpgrading Open Event to Use Sendgrid API v3

Option to Rename an Image in Phimpme Android Application

In the Phimpme Android application, users can perform various operations on images such as editing an image, sharing an image, moving the image to another folder, printing a pdf version of the image and many more. However, another important functionality that has been implemented is the option to rename an image. So in this blog post, I will be discussing how we achieved the functionality to rename an image. Step 1 First, we need to add an option in the overflow menu to rename the image being viewed. The option to rename an image has been added by implementing the following lines of code in the  menu_view_pager.xml file. <item   android:id="@+id/rename_photo"   app:showAsAction="never"   android:title="@string/Rename"/> Step 2 Now after the user chooses the option to rename any image from the overflow menu, an alert dialog with an edittext would be displayed to the user with the old name and provide the user to add a new name for the photo. The dialog box with edittext has been implemented with the following lines of XML code. <android.support.v7.widget.CardView   android:layout_width="match_parent"   android:layout_height="wrap_content"   app:cardCornerRadius="2dp"   android:id="@+id/description_dialog_card">   <ScrollView       android:layout_width="match_parent"       android:layout_height="match_parent">       <LinearLayout           android:layout_width="match_parent"           android:layout_height="wrap_content"           android:orientation="vertical">           <TextView               android:id="@+id/description_dialog_title"               android:layout_width="match_parent"               android:textColor="@color/md_dark_primary_text"               android:layout_height="wrap_content"               android:background="@color/md_dark_appbar"               android:padding="24dp"               android:text="@string/type_description"               android:textSize="18sp"               android:textStyle="bold" />           <LinearLayout               android:id="@+id/rl_description_dialog"               android:layout_width="match_parent"               android:layout_height="wrap_content"               android:orientation="horizontal"               android:padding="15dp">               <EditText                   android:id="@+id/description_edittxt"                   android:layout_width="fill_parent"                   android:layout_height="wrap_content"                   android:padding="15dp"                   android:hint="@string/description_hint"                   android:textColorHint="@color/grey"                   android:layout_margin="20dp"                   android:gravity="top|left"                   android:inputType="textCapSentences|textMultiLine"                   android:maxLength="2000"                   android:maxLines="4"                   android:selectAllOnFocus="true"/>               <ImageButton                   android:layout_width="@dimen/mic_image"                   android:layout_height="@dimen/mic_image"                   android:layout_alignRight="@+id/description_edittxt"                   app2:srcCompat="@drawable/ic_mic_black"                   android:layout_gravity="center"                   android:background="@color/transparent"                   android:paddingEnd="10dp"                   android:paddingTop="12dp"                   android:id="@+id/voice_input"/>           </LinearLayout>       </LinearLayout>   </ScrollView> </android.support.v7.widget.CardView> The screenshot displaying the dialog with edittext is provided below. Step 3 Now after retrieving the new name entered by the user for the photo we would extract the extension of the old name which can be .jpg, .png etc. Thereafter we’d need to create a new File object passing in the new path of the image(the path folder remains the same only the image name gets changed to a new one) as the constructor parameter. Now using the renameTo method of the File class the old image file can be renamed. However, with this rename operation the image reference would not be automatically updated in the MediaStore database. So we’d need to delete the old file path from the Android system MediaStore database which keeps a URI reference to all the media files present on the device. At last, we’d need to invoke the MediaScanner class to scan all the media files so that the new file path of the renamed image is scanned and is picked up by the MediaStore database. This can be done with the help of an action intent to initiate the media scan action. The code changes implemented to perform the above-mentioned operation is given below. public void onClick(View dialog) {       if (editTextNewName.length() != 0) {           int index = file.getPath().lastIndexOf("/");           String path = file.getPath().substring(0, index);           File newname = new File(path + "/" + editTextNewName.getText().toString() + "." +                   imageextension);           if(file.renameTo(newname)){               ContentResolver resolver = getApplicationContext().getContentResolver();               resolver.delete(                       MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaStore.Images.Media.DATA +                               "=?", new String[] { file.getAbsolutePath() });               Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);               intent.setData(Uri.fromFile(newname));               getApplicationContext().sendBroadcast(intent);           }           if(!allPhotoMode){               int a = getAlbum().getCurrentMediaIndex();               getAlbum().getMedia(a).setPath(newname.getPath());           }else {               listAll.get(current_image_pos).setPath(newname.getPath());           }           renameDialog.dismiss();           SnackBarHandler.showWithBottomMargin(parentView, getString(R.string.rename_succes), navigationView                   .getHeight());…

Continue ReadingOption to Rename an Image in Phimpme Android Application