Image Cropper On Ember JS Open Event Frontend

In Open Event Front-end, we have a profile page for every user who is signed in where they can edit their personal details and profile picture. To provide better control over profile editing to the user, we need an image cropper which allows the user to crop the image before uploading it as their profile picture. For this purpose, we are using a plugin called Croppie. Let us see how we configure Croppie in the Ember JS front-end to serve our purpose.

All the configuration related to Croppie lies in a model called cropp-model.js.

 onVisible() {
    this.$('.content').css('height', '300px');
    this.$('img').croppie({
      customClass : 'croppie',
      viewport    : {
        width  : 400,
        height : 200,
        type   : 'square'
      },
      boundary: {
        width  : 600,
        height : 300
      }
    });
  },

  onHide() {
    this.$('img').croppie('destroy');
    const $img = this.$('img');
    if ($img.parent().is('div.croppie')) {
      $img.unwrap();
    }
  },

  actions: {
    resetImage() {
      this.onHide();
      this.onVisible();
    },
    cropImage() {
      this.$('img').croppie('result', 'base64', 'original', 'jpeg').then(result => {
        if (this.get('onImageCrop')) {
          this.onImageCrop(result);
        }
      });
    }

There are two functions: onVisible() and onHide(), which are called every time when we hit reset button in our image cropper model.

  • When a user pushes reset button, the onHide() function fires first which basically destroys a croppie instance and removes it from the DOM.
  • onVisible(), which fires next, sets the height of the content div. This content div contains our viewport and zoom control. We also add a customClass of croppie to the container in case we are required to add some custom styling. Next, we set the dimensions and the type of viewport which should be equal to the dimensions of the cropped image. We define type of cropper as ‘square’ (available choices are ‘square’ and ‘circle’). We set the dimensions of our boundary. The interesting thing to notice here is that we are setting only the height of the boundary because if we pass only the height of the boundary, the width will be will be calculated using the viewport aspect ratio. So it will fit in all the screen sizes without overflowing.

The above two functions are invoked when we hit the reset button. When the user is satisfied with the image and hits ‘looks good’ button, cropImage() function is called where we are get the resulting image by passing some custom options provided by croppie like base64 bit encoding and size of cropped image which we are set to ‘original’ here and the extension of image which is we set here as ‘.jpeg’. This function returns the image of desired format which we use to set profile image.

Resources

Adding Snackbar to undo recent change in theme of SUSI.AI Web Chat

SUSI.AI Web Chat has two primary themes: Light and Dark. The user can switch between these two from settings, but what if the user does not prefer the theme change. He/She has to repeat the entire process of Going to Settings -> Selecting another theme -> saving it. To enable the user to instantly change the theme, we decided to add snackbar on theme change.

What is Snackbar ?

Snackbar contains info regarding the operation just performed and it displays them in a visual format. Snackbar can also have a button like “Undo” to revert the recent operation just made.

It appears at the bottom of screen by default. A simple snackbar looks like this:

Now we needed this to pop-up, whenever the theme is changed. When a user changes theme we run a function named “implementSettings” which checks what the theme is changed to.

The method is:

implementSettings = (values) => {
    this.setState({showSettings: false});
    if(values.theme!==this.state.currTheme){
      this.setState({SnackbarOpen: true});
    }
    this.changeTheme(values.theme);
    this.changeEnterAsSend(values.enterAsSend);
    setTimeout(() => {
       this.setState({
           SnackbarOpen: false
       });
   }, 2500);
  }

The argument values is an object that contains all the change that user has made in settings. Here values.theme contains the value of theme user selected from settings. We first check if the theme is same as the current one if so, we do nothing. If the theme is different from current, then we update the theme by calling this.changeTheme(values.theme) and also initiate snackbar by setting SnackbarOpen to open.

The snackbar component looks like:

<Snackbar
     open={this.state.SnackbarOpen}
     message={'Theme Changed'}
     action="undo"
     autoHideDuration={4000}
     onActionTouchTap={this.handleActionTouchTap}
     onRequestClose={this.handleRequestClose}
/>

This appears on the screen as follows :

Now if a user wants to change the theme instantly he/she can press the undo button. For this undo button, we have a method handleActionTouchTap. In this method, we change the theme back to previous one. The method is implemented in the following manner:

handleActionTouchTap = () => {
    this.setState({
      SnackbarOpen: false,
    });
    switch(this.state.currTheme){
      case 'light': {
          this.changeTheme('dark');
          break;
      }
      case 'dark': {
          this.changeTheme('light');
          break;
      }
      default: {
          // do nothing
      }
    }
  };

The above method is pretty self-explanatory which checks the current theme(“light” or “dark”) and then revert it. We also change the state of SnackbarOpen to “false” so that on clicking “UNDO” button, the theme changes back to previous one and the snackbar closes.Now user is having an option of instantly changing back to previous theme.

Resources:

Testing Link:

http://chat.susi.ai/

Addition Of Track Tags In Open Event Android App

In the Open Event Android app we only had a list of sessions in the schedule page without  knowing which track each session belonged to. There were several iterations of UI design for the same. Taking cues from the Google I/O 17 App we thought that the addition of track tags would be informative to the user.  In this blog post I will be talking about how this feature was implemented.

TrackTag Layout in Session

<Button
                android:layout_width="wrap_content"
                android:layout_height="@dimen/track_tag_height"
                android:id="@+id/slot_track"
                android:textAllCaps="false"
                android:gravity="center"
                android:layout_marginTop="@dimen/padding_small"
                android:paddingLeft="@dimen/padding_small"
                android:paddingRight="@dimen/padding_small"
                android:textStyle="bold"
                android:layout_below="@+id/slot_location"
                android:ellipsize="marquee"
                android:maxLines="1"
                android:background="@drawable/button_ripple"
                android:textColor="@color/white"
                android:textSize="@dimen/text_size_small"
                tools:text="Track" />

The important part here is the track tag was modelled as a <Button/> to give users a touch feedback via a ripple effect.

Tag Implementation in DayScheduleAdapter

All the dynamic colors for the track tags was handled separately in the DayScheduleAdapter.

final Track sessionTrack = currentSession.getTrack();
       int storedColor = Color.parseColor(sessionTrack.getColor());
       holder.slotTrack.getBackground().setColorFilter(storedColor, PorterDuff.Mode.SRC_ATOP);

       holder.slotTrack.setText(sessionTrack.getName());
       holder.slotTrack.setOnClickListener(v -> {
            Intent intent = new Intent(context, TrackSessionsActivity.class);
            intent.putExtra(ConstantStrings.TRACK, sessionTrack.getName());

            intent.putExtra(ConstantStrings.TRACK_ID, sessionTrack.getId());
            context.startActivity(intent);
      });

As the colors associated with a track were all stored inside the track model we needed to obtain the track from the currentSession. We could get the storedColor by calling getColor() on the track object associated with the session.

In order to set this color as the background of the button we needed to set a color filter with a PorterDuff Mode called as SRC_ATOP.

The name of the parent class is an homage to the work of Thomas Porter and Tom Duff, presented in their seminal 1984 paper titled “Compositing Digital Images”. In this paper, the authors describe 12 compositing operators that govern how to compute the color resulting of the composition of a source (the graphics object to render) with a destination (the content of the render target).

Ref : https://developer.android.com/reference/android/graphics/PorterDuff.Mode.html

To exactly understand what does the mode SRC_ATOP do we can refer to the image below for understanding. There are several other modes with similar functionality.

               

      SRC IMAGE             DEST IMAGE           SRC ATOP DEST

Now we also set an onClickListener for the track tag so that it directs us to the tracks Page.

So now we have successfully added track tags to the app.

Resources

GlobalSearchAdapter Setup in Open Event Android App

In this blog post I describe how the GlobalSearchAdapter in Open Event Android was made which enabled users to search quickly within the app. This post also outlines how to create Recycler Views with heterogenous layouts and explains how to write ViewHolders.

Adapter Logic

A custom adapter was built for the population of views in the Recycler View in the SearchActivity.

private List<Object> filteredResultList = new ArrayList<>();
//ViewType Constants
private final int TRACK = 0;
private final int SPEAKER = 2;
private final int LOCATION = 3;
private final int DIVIDER = 4;

The DIVIDER constant was assigned to the Result Type Header View.

In a gist all the item types such as Speaker, Track, Location, Divider etc have been designated some constants.

Getting the ItemViewType

@Override
public int getItemViewType(int position) {

   if(filteredResultList.get(position) instanceof Track){
       return TRACK;
   }
   else if(filteredResultList.get(position) instanceof String){
       return DIVIDER;
   }
   ...Similarly for other ItemTypes such as Session or Location
   else{
       return 1;
   }
}

As the filteredResultList is of type Object we can insert objects of any type into the list as Object is a superclass of all classes. We would want a view which represents a TRACK if we have an object of type Track in the filteredResultList. And similarly for the other result types we could insert objects of type LOCATION, SPEAKER types in this list. getItemViewType() basically determines the type of the item that is visible to us. If the list consists of an item of type SPEAKER, in the RecyclerView.

Speaker Item Type
Track Item Type
Divider Item Type
Location Item Type

Code for onCreateViewHolder in GlobalSearchAdapter for the Recycler View

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

   RecyclerView.ViewHolder resultHolder = null;
   LayoutInflater inflater = LayoutInflater.from(parent.getContext());

   switch(viewType) {
       case TRACK:
           View track = inflater.inflate(R.layout.item_track, parent,   false);
           resultHolder = new TrackViewHolder(track,context);
           break;
       case SPEAKER:
           View speaker = inflater.inflate(R.layout.search_item_speaker, parent, false);
           resultHolder = new SpeakerViewHolder(speaker,context);
           break;
       //Similarly for other types
       default:
           break;
   }
   return resultHolder;
}

Depending upon the the viewType returned the desired layout is inflated and the desired ViewHolder is returned.

Code for onBindViewHolder in GlobalSearchAdapter for the Recycler View

@Override
 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
 
    switch (holder.getItemViewType()){
        case TRACK:
            TrackViewHolder trackSearchHolder = (TrackViewHolder)holder;
            final Track currentTrack = (Track)getItem(position);
            trackSearchHolder.setTrack(currentTrack);
            trackSearchHolder.bindHolder();
            break;
         //Similarly for all the other View Types
        default:
            break;
    }
 }

These functions are being used to bind the data to the layouts that have been inflated already in the earlier snippet of code of onCreateViewHolder.

The bindHolder functions of each ViewHolder type are being used to do the view binding i.e converting the information in the Object Track into what we see in the TrackViewHolder as seen in TrackViewFormat.

All ViewHolders have been defined as separate classes in order to enable re usability of these classes.

ViewHolder Implementation

There are 4 main ViewHolders that were made to enable such a search. I’ll be talking about the TrackViewHolder in detail.

public class TrackViewHolder extends RecyclerView.ViewHolder {
    
    @BindView(R.id.imageView)
    ImageView trackImageIcon;
    @BindView(R.id.track_title)
    TextView trackTitle;
    @BindView(R.id.track_description)
    TextView trackDescription;
 
    private Track currentTrack;
    private Context context;
    private TextDrawable.IBuilder drawableBuilder = TextDrawable.builder().round();
 
    public void setTrack(Track track) {
        this.currentTrack = track;
    }
 
    public TrackViewHolder(View itemView,Context context) {
        super(itemView);
        ButterKnife.bind(this, itemView);
        this.context = context;
    }
    public void bindHolder(){
 
        //Set all Views to their correct configurations
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(context,   TrackSessionsActivity.class);
                intent.putExtra(ConstantStrings.TRACK,   currentTrack.getName());
 
                // Send Track ID to Activity to leverage color cache
                intent.putExtra(ConstantStrings.TRACK_ID,   currentTrack.getId());
                context.startActivity(intent);
            }
        });
} }

Those @BindView annotations that we can see are the result of a library called as Butterknife which is used to reduce standard boilerplate findViewById lines.

@BindView(R.id.imageView) ImageView trackImageIcon;
IS THE SAME AS THIS  
ImageView trackImageIcon = (ImageView)findViewById(R.id.imageView);

The advantage of such a ViewHolder is that it knows what kind of data it stores as compared to traditional ViewHolders which do not know the kind of data it stores.

By making ViewHolders separate from the RecyclerViewAdapter we are essentially decoupling classes and are enabling reusability of code. Also we make the ViewHolder a bit more intelligent by storing the object it binds in the ViewHolder itself. In the above example we are storing an object of Track which is bind to the ViewHolder. We also see that we do the view binding inside the viewholder itself. All this helps us to reduce code inside the adapter class.

A recent addition to the app was custom colors for all TRACKS in the app that improved the visual feel of the app. So basically, for example if a SESSION has been associated with the track of Blockchain it would be given a color such as purple. onClickListeners are also being set with some extras which are self-descriptive in nature. Similarly the other ViewHolders have been implemented.

Resources

Using AutoCompleteTextView for interactive search in Open Event Android App

Providing a search option is essential in the Open Event Android app to make it easy for the user to see the desired results only. But sometimes it becomes difficult to implement this with a good performance if the data set is large, so providing simply a list to scroll through may not be enough and efficient. AutoCompleteTextView provides a way to search data by offering the suggestions after a user types in some initial letters of the search query.

How does it work? Actually we feed the data to an adapter which is attached to the view. So, when a user starts typing the query the suggestions starts appearing with similar names in the form of the list.

For example see above. Typing “Hall” gives the user suggestion to pick up the entry which have word “Hall” in it. Making it easier for user to search.

Let’s see how to implement it. For the first step declare the view in XML layout like this. Where our view goes by the id “map_toolbar” and white text colour for the text that will be appearing in it. Input type signifies that the autocomplete and auto correct is enabled.

<AutoCompleteTextView
       android:id="@+id/map_toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:ems="12"
       android:hint="@string/search"
       android:shadowColor="@color/white"
       android:imeOptions="actionSearch"
       android:inputType="textAutoComplete|textAutoCorrect"
       android:textColorHint="@color/white"
       android:textColor="@color/white"
/>

Now initialise the adapter in the fragment/activity with the list “searchItems” containing the information about the location. This function is in a fragment so modifying things accordingly. “textView” is the AutoCompleteTextView that we initialised. To explain this function further when a user clicks on any item from the suggestions the soft keyboard hides. You can do define desired operation here. 

Setting up AutoCompleteTextView with the locations

ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_dropdown_item_1line, searchItems);
textView.setAdapter(adapter);

textView.setOnItemClickListener((parent, view, position, id) -> {

Things you want to do on clicking the item

View mapView = getActivity().getCurrentFocus();

if (mapView != null) {
  InputMethodManager imm =     (InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
  imm.hideSoftInputFromWindow(mapView.getWindowToken(), 0);
}
});

See the complete code here to find the implementation of AutoCompleteTextView in the map fragment of Open Event Android app.

Managing Edge Glow Color in Nested ScrollView in Open Event Android App

After the introduction of material design by Google many new UI elements have been introduced. Material design is based on the interaction and movement of colours and objects in real world rather than synthetic unnatural phenomenon. Sometimes it gets messy when we try to keep up with our material aesthetic. The most popular example of it is edge glow colour. The edge glow colour of ListView, RecyclerView, ScrollView and NestedScrollView is managed by the accent colour declared in the styles. We cannot change the accent colour of a particular activity as it is a constant, so it is same across entire app but in popular apps like Contacts by Google it appears different for every individual contacts. Fixing an issue in Open Event Android app, I came across the same problem. In this tutorial I solve this problem particularly for NestedScrollView. See the bottom of screenshots for comparison.

   
  • You need to pre check if the Android version is above or equal to LOLLIPOP before doing this as the setColor() function for edge glow is introduced in LOLLIPOP
  • The fields are declared as “mEdgeGlowTop” and “mEdgeGlowBottom” that we have to modify.
  • We get the glow property of NestedScrollView through EdgeEffectCompat class instead of EdgeEffect class directly, unlike for ListView due its new introduction.

Let’s have a look at the function which accepts arguments color as an integer and nested scroll view of which the color has to be set.

First you have to get the fields “mEdgeGlowTop” and “mEdgeGlowBottom” that signifies the bubbles that are generated when you scroll up and down from Nested Scroll View Class. Similarly “mEdgeGlowLeft”  and “mEdgeGlowRight” for the horizontal scrolling.

public static void changeGlowColor(int color, NestedScrollView scrollView) {
   try {

       Field edgeGlowTop = NestedScrollView.class.getDeclaredField("mEdgeGlowTop");

       edgeGlowTop.setAccessible(true);

       Field edgeGlowBottom = NestedScrollView.class.getDeclaredField("mEdgeGlowBottom");

       edgeGlowBottom.setAccessible(true);

Get the reference to edge effect which is the different part unlike recycler view or list view of setting the edge glow color in nested scrollview.

EdgeEffectCompat edgeEffect = (EdgeEffectCompat) edgeGlowTop.get(scrollView);

       if (edgeEffect == null) {
           edgeEffect = new EdgeEffectCompat(scrollView.getContext());
           edgeGlowTop.set(scrollView, edgeEffect);
       }

       Views.setEdgeGlowColor(edgeEffect, color);

       edgeEffect = (EdgeEffectCompat) edgeGlowBottom.get(scrollView);
       if (edgeEffect == null) {
           edgeEffect = new EdgeEffectCompat(scrollView.getContext());
           edgeGlowBottom.set(scrollView, edgeEffect);
       }

       Views.setEdgeGlowColor(edgeEffect, color);

   } catch (Exception ex) {

       ex.printStackTrace();
   }

}

Finally set the edge glow color. This only works for the versions that are above or equal to LOLLIPOP as edge effect was introduced in the android beginning from those versions.

@TargetApi(Build.VERSION_CODES.LOLLIPOP)

public static void setEdgeGlowColor(@NonNull EdgeEffectCompat edgeEffect, @ColorInt int color) throws Exception {
        Field field = EdgeEffectCompat.class.getDeclaredField("mEdgeEffect");

        field.setAccessible(true);
        EdgeEffect effect = (EdgeEffect) field.get(edgeEffect);
        
        if (effect != null)
            effect.setColor(color);
    }

 

(Don’t forget to catch any exception. You can monitor them by using Log.e(“Error”, ”Message”, e ); for debugging and testing).

Resources

  • https://developer.android.com/reference/android/support/v4/widget/NestedScrollView.html

Adding Global Search and Extending Bookmark Views in Open Event Android

When we design an application it is essential that the design and feature set enables the user to find all relevant information she or he is looking for. In the first versions of the Open Event Android App it was difficult to find the Sessions and Speakers related to a certain Track. It was only possible to search for them individually. The user also could not view bookmarks on the Main Page but had to go to a separate tab to view them. These were some capabilities I wanted to add to the app.

In this post I will outline the concepts and advantages of a Global Search and a Home Screen in the app. I took inspiration from the Google I/O 2017 App  that had these features already. And, I am demonstrating how I added a Home Screen which also enabled users to view their bookmarks on the Home Screen itself.

Global Search v/s Local Search

Local Search
Global Search

 

 

 

 

 

 

 

 

 

If we observe clearly in the above images we can see there exists a stark difference in the capabilities of each search.
See how in the Local Search we are just able to search within the Tracks section and not anything else.
This is fixed in the Global Search page which exists along with the new home screen.
As all the results that a user might need are obtained from a single search, it improves the overall user-experience of the app. Also a noticeable feature that was missing in the current iteration of the application was that a user had to go to a separate tab to view his/her bookmarks. It would be better for the app to have a home page detailing all the Event’s/Conference’s details as well as display user bookmarks on the homepage.

New Home

Home screen
Home screen with Bookmarks

 

 

 

 

 

 

 

 

 

Home screen with Bookmarks               
Home screen Demo

 

 

 

 

 

 

 

 

 

The above posted images/gifs indicate the functioning and the UI/UX of the new Homescreen within the app.
Currently I am working to further improve the way the Bookmarks are displayed.
The new home screen provides the user with the event details i.e FOSSASIA 2017 in this case. This would be different for each conference/event and the data is fetched from the open-event-orga server(the first part of the project) if it doesn’t already exist in the JSON files provided in the assets folder of the application. All the event information is being populated by the JSON files provided in the assets folder in the app directory structure.

  • config.json
  • sponsors.json
  • microlocations.json
  • event.json(this stores the information that we see on the home screen)
  • sessions.json
  • speakers.json
  • track.json

All the file names are descriptive enough to denote what do all of them store.I hope that I have put forward why the addition of a New Home with Bookmarks along with the Global Search feature was a neat addition to the app.

Link to PR for this feature : https://github.com/fossasia/open-event-android/pull/1565

Resources

 

 

Creating nested routes in Open Event Front-end and Navigating them with Tabs via semantic UI – Ember Integration

Semantic UI is a modern development framework which helps build responsive and aesthetically beautiful layouts. While it is a really powerful framework in itself, it additionally offers seamless integrations with some of the other open source frameworks including ember js.

Open Event Front-end is a project of FOSSASIA organisation, which was created with the aim of decoupling the front end and the back end for the open event orga server. It is primarily based on ember JS and uses semantic UI for it’s UI.

Here we will be making a nested route /events/ with /events/live/, events/draft, events/past , events/import as it’s subroutes.

To get started with it, we simply use the ember CLI to generate the routes

$ ember generate route events

Then we go on to generate the successive sub routes as follows

$ ember generate route events/live
$ ember generate route events/past
$ ember generate route events/draft
$ ember generate route events/import

The router.js file should be looking like this now.

this.route('events', function() {
    this.route('live');
    this.route('draft');
    this.route('past');
    this.route('import');
  });

This means that our routes and sub routes are in place. Since we used the ember CLI to generate these routes, the template files for them would have generated automatically. Now these routes exist and we need to write the data in the templates of these routes which will get displayed to the end user.

Since the routes are nested, the content of the parent route can be made available to all the children routes via the outlet in ember js.

Next, we go to the template file of events/ route which is at templates/events.hbs And write the following code to create a menu and use ember integration of semantic UI link-to to link the tabs of the menu with the corresponding correct route. It will take care of selecting the appropriate data for the corresponding route and display it in the correct tab via the outlet

<.div class="row">
  <.div class="sixteen wide column">
    <.div class="ui fluid pointing secondary menu">
      {{#link-to 'events.live' class='item'}}
        {{t 'Live'}}
      {{/link-to}}
      {{#link-to 'events.draft' class='item'}}
        {{t 'Draft'}}
      {{/link-to}}
      {{#link-to 'events.past' class='item'}}
        {{t 'Past'}}
      {{/link-to}}
      {{#link-to 'events.import' class='item'}}
        {{t 'Import'}}
      {{/link-to}}
    <./div>
  <./div>
<./div>
<.div class="ui segment">
  {{outlet}}
<./div>

So finally, we start filling in the data for each of these routes. Let’s fill some dummy data at templates/events/live.hbs

<.div class="row">
  <.div class="sixteen wide column">
    <.table class="ui tablet stackable very basic table">
      <.thead>
        <.tr>
          <.th>{{t 'Name'}}<./th>
          <.th>{{t 'Date'}}<./th>
          <.th>{{t 'Roles'}}<./th>
          <.th>{{t 'Sessions'}}<./th>
          <.th>{{t 'Speakers'}}<./th>
          <.th>{{t 'Tickets'}}<./th>
          <.th>{{t 'Public URL'}}<./th>
          <.th><./th>
        <./tr>
      <./thead>
      <.tbody>
        <.tr>
          <.td>
            <.div class="ui header weight-400">
              <.img src="http://placehold.it/200x200" alt="Event logo" class="ui image">
              Sample Event
            <./div>
          <./td>
          <.td>
            March 18, 2016 - 09:30 AM
            <.br>(to)<.br>
            March 20, 2016 - 05:30 PM
          <./td>
          <.td>
            <.div class="ui ordered list">
              <.div class="item">sample@gmail.com ({{t 'Organizer'}})<./div>
              <.div class="item">sample2@gmail.com ({{t 'Manager'}})<./div>
            <./div>
          <./td>
          <.td>
            <.div class="ui list">
              <.div class="item">{{t 'Drafts'}}: 0<./div>
              <.div class="item">{{t 'Submitted'}}: 0<./div>
              <.div class="item">{{t 'Accepted'}}: 0<./div>
              <.div class="item">{{t 'Confirmed'}}: 0<./div>
              <.div class="item">{{t 'Pending'}}: 0<./div>
              <.div class="item">{{t 'Rejected'}}: 0<./div>
            <./div>
          <./td>
          <.td>
            2
          <./td>
          <.td>
            <.div class="ui bulleted list">
              <.div class="item">{{t 'Premium'}} (12/100)<./div>
              <.div class="item">{{t 'VIP'}} (10/15)<./div>
              <.div class="item">{{t 'Normal'}} (100/200)<./div>
              <.div class="item">{{t 'Free'}} (100/500)<./div>
            <./div>
          <./td>
          <.td>
            <.div class="ui link list">
              <.a class="item" target="_blank" rel="noopener" href="http://nextgen.eventyay.com/e/ecc2001a">
                http://nextgen.eventyay.com/e/ecc2001a
              <./a>
            <./div>
          <./td>
          <.td class="center aligned">
            <.div class="ui vertical compact basic buttons">
              {{#ui-popup content=(t 'Edit event details') class='ui icon button'}}
                <.i class="edit icon"><./i>
              {{/ui-popup}}
              {{#ui-popup content=(t 'View event details') class='ui icon button'}}
                <.i class="unhide icon"><./i>
              {{/ui-popup}}
              {{#ui-popup content=(t 'Delete event') class='ui icon button'}}
                <.i class="trash outline icon"><./i>
              {{/ui-popup}}
            <./div>
          <./td>
        <./tr>
      <./tbody>
    <./table>
  <./div>
<./div>

 Similarly we can fill the required data for each of the routes.And this is it, our nested route is ready. Here is a screenshot what you might expect.

Screenshot highlighting the tabs

Resources

Bottoms sheets in android

Hey Guys I recently used Bottom sheets, so that I should write a blog about it because I don’t see a lot of developers using this in their app UI’s.

It’s a very interesting UI element. A Bottom Sheet is a sheet of material that slides up from the bottom edge of the screen. Displayed only as a result of a user-initiated action, and can be swiped up to reveal additional content. It can be a temporary modal surface or a persistent structural element of an app.

This component was introduced in the Android Design Support library 23.2. Many apps like Google Maps use the bottom sheet, in which a sliding window pops up from the bottom of the screen. Also the Google play music app uses. When we drag up the sheet we see the song details as well as the current playlist.

Usage of expanded and collapsed Bottom sheets in Android

There are 3 states of Bottom sheets :-

  • Expanded — When the sheet is completely visible.
  • Collapsed — When the sheet is partially visible.
  • Hidden — When the sheet is completely hidden.

Step 1 is we need to import the latest design support library. Put this line in your app level build.gradle file.

compile ‘com.android.support:design:23.2.0’

Then one should create a new Blank Activity (not Empty Activity) in Android Studio. It sets up the CoordinatorLayout by default.

So now there ate two layouts created by default namely activity_main.xml and content_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="yet.best.bottomsheets.MainActivity"
tools:showIn="@layout/activity_main">

<Button
android:id="@+id/open"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_margin="5dp"
android:text="Open Bottom Sheet" />

<Button
android:id="@+id/collapse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/open"
android:layout_centerHorizontal="true"
android:layout_margin="5dp"
android:text="Collapse Bottom Sheet" />

<Button
android:id="@+id/hide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/collapse"
android:layout_centerHorizontal="true"
android:layout_margin="5dp"
android:text="Hide Bottom Sheet" />

</RelativeLayout>

Notice that 3 Buttons have been created in this layout to perform different actions with the bottom sheets.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="yet.best.bottomsheets.MainActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_main" />

<include
android:id="@+id/bottom_sheet"
layout="@layout/bottomsheet_main" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
android:src="@android:drawable/ic_dialog_email"
app:layout_anchor="@id/bottom_sheet"
app:layout_anchorGravity="top|right|end" />

</android.support.design.widget.CoordinatorLayout>

Those who aren’t familiar with the coordinator layout — basically there is a base level layout activity_main which contains the FloatingButton and within this layout including content_main.xml which will contain the rest of the layout. Notice that one also has to include bottomsheet_main.xml. This layout contains our bottom sheet layout which will be created next.

Create a new layout file called bottomsheet_main.xml

bottomsheet_main.xml

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="#d3d3d3"
app:behavior_hideable="true"
app:behavior_peekHeight="70dp"
app:layout_behavior="@string/bottom_sheet_behavior">

<TextView
android:id="@+id/heading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="7dp"
android:text="Collapsed"
android:textSize="18sp" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Main Content"
android:textSize="20sp" />

</RelativeLayout>

This layout is how our bottom sheet will actually look. You can design it as you want.

Now for the actual java code. This is the easiest part actually. Just set listeners to the 3 buttons created and perform the corresponding action with the bottom sheet.

public class MainActivity extends AppCompatActivity {

BottomSheetBehavior bottomSheetBehavior;
Button open, collapse, hide;
TextView heading;
@Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
  setSupportActionBar(toolbar);

View bottomSheet = findViewById(R.id.bottom_sheet);
  bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);

setup();
 }

private void setup() {
  open = (Button) findViewById(R.id.open);
  collapse = (Button) findViewById(R.id.collapse);
  hide = (Button) findViewById(R.id.hide);
  heading = (TextView) findViewById(R.id.heading);

//Handling movement of bottom sheets from buttons
  open.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
    heading.setText("Welcome");
    heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorPrimary));
   }
  });

collapse.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
    heading.setText("Collapsed");
    heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorAccent));
   }
  });

hide.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
    bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
   }
  });

//Handling movement of bottom sheets from sliding
  bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
   @Override
   public void onStateChanged(View bottomSheet, int newState) {
    if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
     heading.setText("Collapsed");
     heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorAccent));
    } else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
     heading.setText("Welcome");
     heading.setTextColor(ContextCompat.getColor(MainActivity.this, R.color.colorPrimary));
    }
   }

@Override
   public void onSlide(View bottomSheet, float slideOffset) {}
  });
 }
}

We just use the bottomSheetBehavior.setState() method to set the relevant state on each button click.

The bottom sheets can also be dragged by touch gestures. One can simply touch the sheet and drag them up or down. For these touch gestures to be handled one has to implement the onStateChanged() listener. This listener is fired everytime the state of the sheet changes by gestures. Whenever this triggers it checks the state of the bottom sheet and again do the desired action which user would have done by the button clicks.

As you can see, this is a pretty neat UI solution and can be implemented so easily. Go try it out for yourself. Adios!

Working with ConstraintLayout in Android

Few months ago, during the Google I/O conference, Google introduced a new set of tools for Android developers. Among them is a new Layout editor and a new layout called the ConstraintLayout.

I’ll be highlighting the key points in this brand new layout.

ConstraintLayout is available in a new Support Library that’s compatible with Android 2.3 (Gingerbread) and higher, but the new layout editor is available only in Android Studio 2.2 Preview.

Layout Editor & Constraints Overview.

The new layout editor in Android Studio 2.2 Preview is specially built for the ConstraintLayout. You can specify the constraints manually, or automatically reference within the layout editor.

Overview of Constraints?

A constraint is the description of how a view should be positioned relative to other items, in a layout. A constraint is typically defined for one or more sides by connecting the view to:

  • An anchor point, or another view,
  • An edge of the layout,
  • Or An invisible guide line.

Since each view within the layout is defined by associations to other views within the layout, it’s easier to achieve flat hierarchy for complex layouts.

In principle, the ConstraintLayout works very similar to the RelativeLayout, but uses various handles (or say anchors) for the constraints.

  • Resize handle. The resize handle is the alt text seen in the corners of the figure above, and it’s used to resize the view.
  • Side handle. The side handle is the alt text in the figure above, and it’s used to specify the location of a widget. E.g using the left side handle to always be aligned to the right of another view, or the left of the ConstraintLayout itself.
  • Baseline handle. The baseline handle is the alt text in the figure above. It is used to align the text of a view by the baseline of the text on another view.

Getting started with ConstraintLayout

Setup

Ensure that you’re running the AS 2.2 preview, and Android Support Repository version 32 or higher, it’s required before you can use the ConstraintLayout. Let’s get started.

  • First, you need to add the constraint layout library to your app’s dependencies within your build.gradle file:
dependencies {
    compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha1'
}
  • Sync your project.

Add Constraints

There are typically two ways to create ConstraintLayout in AS. You can create a new XML layout and select the root element to be a ConstraintLayout or convert an existing layout into a ConstraintLayout as shown in the image below:

Once you have the ConstraintLayout setup, what is next is to add the constraints to the views within that layout.

As an example, drag an ImageView to the layout. The new layout builder will immediately ask to add a drawable or resource, select one from the options and press ok. Also drag a TextView unto the layout.

To create a constraint, drag the top side handle on the ImageView to the top of the ConstraintLayout. You can also drag from the top side handle of the TextView to the bottom handle of the ImageView

Using the Inspector Pane

Now that we’re able to add constraints, we will need to use the inspector. It’s on the right hand side of the layout builder and it lists various properties of the selected widget. Typically, it looks as shown below:

You can use the sliders to move the view by percentage along the x and y axes. You can also control the dimensions of the view from the inspector pane, by altering the values corresponding to the layout_width and layout_height fields.

Taking a closer look at the square widget on the inspector pane. It contains some more control over the dimensions of the views.

There are other modes of controlling the size of the view. Clicking on the inner lines in the image above help you cycle through the other modes.

  • Fixed mode: alt text This allows you specify the width and height of the view.
  • Any size: alt text This mode allows the image to fill up all the space required to fulfill that constraint. You can look at this like “match constraint”
  • Wrap content: alt text This just expands to fill the content of the view. E.g text or image

Using Auto-connect to add constraints.

Autoconnect as the name suggests, automatically creates connections between views/widgets. It tries to create connections to neighboring views/widgets.
To enable autoconnect, look out for the alt text icon on the top bar of the layout editor.

Thats’s almost it for the constraint layout.
If you want, you can head over to the official documentation on it at http://tools.android.com/tech-docs/layout-editor

 

Credits : https://segunfamisa.com/posts/constraint-layout-in-androidhttps://segunfamisa.com/posts/constraint-layout-in-android

Cheers.