Implementing Contextual Action Bar in the Open Event Organizers App

The following blog post is regarding the using of Contextual Action Bar in the Open Event Orga App. In the Open Event Orga App, whenever an item was long pressed, then we had implemented a non standard way of showing the edit and delete button at the top. This would eventually degrade the UX of the app. So as to handle this, CAB was implemented.

When the user long presses on any item, we sometimes see a list of items such as Share, Delete or Edit on the toolbar. This is a standard functionality known as the Contextual Action Bar which is present in many apps. (image for reference given below)

Apart from the standard Action Bar, Android also provides us with the implementation of the Contextual Action Menu. A context menu is a floating menu that appears when the user performs a long-click on an element. It provides actions that affect the selected content or context frame.( image given below for reference)

(Contextual Action Bar)

(Contextual Action Menu)

To implement the standard CAB in the Orga App, the following steps given below have been followed.

In the FaqlistFragment class, an interface ActionMode.Callback is implemented. In its callback methods, you can specify the actions for the contextual action bar, respond to click events on action items, and handle other lifecycle events for the action mode.

Each of the callback methods have been discussed below:

  • onCreateActionMode( ) ->  This method is called first when the ActionMode is started. Here with the help of MenuInflater, the menu which has been made in the menus.xml file is inflated. Also when the app goes into Actions Mode , the status bar is black in color. Hence to handle that the color of the status bar is being changed programmatically.

 

  • onPrepareActionMode( ) -> This should generally return true.

  • onActionItemClicked( ) -> The action mode consists of many items which have separate action. The separate actions for the items are defined here. For eg.In the code snippet given below, when the icon with the id “del is selected then the showDeleteDialog( ) must be called.

 

  • onDestroyActionMode( ) -> When the user exits the Actions Mode, the mode should be assigned a null value and the app should be set to default state.
public ActionMode.Callback actionCallback = new ActionMode.Callback() {
  @Override
  public boolean onCreateActionMode(ActionMode mode, Menu menu) {
      MenuInflater inflater = mode.getMenuInflater();
      inflater.inflate(R.menu.menu_faqs, menu);
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
          //hold current color of status bar
          statusBarColor = getActivity().getWindow().getStatusBarColor();
          //set the default color
          getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.color_top_surface));
      }
      return true;
  }

  @Override
  public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
       return true;
  }

  @Override
  public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
      switch (item.getItemId()) {
          case R.id.del:
              showDeleteDialog();
              break;
          default:
              return false;
      }
      return false;
  }

  @Override
  public void onDestroyActionMode(ActionMode mode) {
      actionMode = null;
      getPresenter().resetToDefaultState();
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
          //return to “old” color of status bar
          getActivity().getWindow().setStatusBarColor(statusBarColor);
      }
  }
};
  1. In the styles.xml, the XML Tag given below needs to be added so that the Action Mode replaces the current Action Bar otherwise, a new Action Bar is created above the Action Bar which is already present.

 <item name=“windowActionModeOverlay”>true</item>

  1. In the Orga App, as the MVP pattern is followed we need to add the following to methods to the interface so that the View and Presenter can communicate via these methods.
public interface FaqListView extends Progressive, Erroneous, Refreshable, Emptiable<Faq> {

  void exitContextualMenuMode();

  void enterContextualMenuMode();
}

 

  1. In the enterContextualMenuMode( ) implementation in the FaqListFragment the startActionMode()  is called to enable the contextual action mode.
@Override
public void enterContextualMenuMode() {
  actionMode = getActivity().startActionMode(actionCallback);
}

References:

  • Official Android documentation for Menu’s

https://developer.android.com/guide/topics/ui/menus#CAB

  • Sample CAB implementation

http://www.technotalkative.com/contextual-action-bar-cab-android/

  • Action mode over Toolbar

http://www.androhub.com/android-contextual-action-mode-over-toolbar/            

Continue ReadingImplementing Contextual Action Bar in the Open Event Organizers App

Use Of DBFlowChangeListener for Automatic Updation of FAQ List in the Open Event Orga App

In the Open Event Orga App, whenever a faq is created in via the CreatefaqFragment, the new faqs aren’t updated in the FAQ’s list. The user has to swipe down and refresh to see the newly created FAQ. This seems to be a major problem and hence to solve this issue a DBFlowChangeListener class has been created.

Following blog post shows the implementation of the ChangeListener class.

Steps of Implementation

In the FaqListPresenter constructor pass an argument of the type DatabaseChangeListener<Faq> and assign it to local variable faqChangeListener.

@Inject
public FaqListPresenter(FaqRepository faqRepository, DatabaseChangeListener<Faq> faqChangeListener) {
  this.faqRepository = faqRepository;
  this.faqChangeListener = faqChangeListener;
}

A method listenChanges( ) is made in the Presenter that will initiate the listening of database. Following code shows the listenChanges( ) . startListening( ) is a method in the DBFlowDatabaseChangeListener which is accessed with the help of DatabaseChangeListener interface.

private void listenChanges() {
  
  faqChangeListener.startListening();
  
  faqChangeListener.getNotifier()
      .compose(dispose(getDisposable()))
      .map(DbFlowDatabaseChangeListener.ModelChange::getAction)
      .filter(action -> action.equals(BaseModel.Action.INSERT) || action.equals(BaseModel.Action.DELETE))
      .subscribeOn(Schedulers.io())
      .subscribe(faqModelChange -> loadFaqs(false), Logger::logError);
}

In the startListening( )  in DatabaseChangeListener, DirectModelNotifier is used to get notified of database changes. It is ultimately registered for any kind of model change as shown in the following code snippet. ReplaySubject is one of the 4 kinds of subjects ( Publish Subject, Replay Subject, Behavior Subject, Async Subject) that RxJava provides. It emits all the items of the source Observable, regardless of when the subscriber subscribes.

public void startListening() {
  if (disposable == null || disposable.isDisposed())
      replaySubject = ReplaySubject.create();

  modelModelChangedListener = new DirectModelNotifier.ModelChangedListener<T>() {

      @Override
      public void onTableChanged(@Nullable Class<?> aClass, @NonNull BaseModel.Action action) {
          replaySubject.onNext(new ModelChange<>(null, action));
      }

      @Override
      public void onModelChanged(@NonNull T model, @NonNull BaseModel.Action action) {
          replaySubject.onNext(new ModelChange<>(model, action));
      }
  };

  DirectModelNotifier.get().registerForModelChanges(classType, modelModelChangedListener);
}

When the faqChangeListener is subscribed it calls the loadFaqs( ) , it ultimately loads the updated values from the database. Hence all the updated values are now displayed in the FaqListFragment.

public void loadFaqs(boolean forceReload) {
  getFaqSource(forceReload)
      .compose(dispose(getDisposable()))
      .compose(progressiveErroneousRefresh(getView(), forceReload))
      .toList()
      .compose(emptiable(getView(), faqs))
      .subscribe(Logger::logSuccess, Logger::logError);
}

Hence the use of DBFlowListener makes it possible to communicate between 2 fragments. Its usability has also been exploited in Tracks, Tickets, Sponsors fragments as well.

References:

  1. RaizLab’s DBFLow:      https://github.com/Raizlabs/DBFlow
  2. Docs related to DBFlow:             https://agrosner.gitbooks.io/dbflow/content/Observability.html
  3. Subjects used in RxJavahttps://blog.mindorks.com/understanding-rxjava-subject-publish-replay-behavior-and-async-subject-224d663d452f
Continue ReadingUse Of DBFlowChangeListener for Automatic Updation of FAQ List in the Open Event Orga App

Adding option to Unfavourite image in Phimp.Me

This blog is in accordance with the P.R #1900. Here I have implemented the option to unfavourite image in the Phimp.Me android application.

Implementation

In the Phimp.Me app there are the following modes present:

1. all_photos:

All the photos are displayed in this mode irrespective of where they are saved.

2. fav_photos:

The photos which are added to favourites are displayed in the fav_photos.

3. Albums_mode:

All the albums which are present in the app are displayed in the albums_mode.

The main idea here is to find whether the selected image is already FAVOURITE or not. If it is already FAVORITED then it can be removed from that mode by removing its path form the Realm Database. If it isn’t already FAVORITED then the image is ignored and the next image is taken into consideration.

The process of removing the images from favourites can be an expensive one as the user can select myriad images which would ultimately block the Main UI. So it is better handled asynchronously and is implemented using the AsyncTask.

Whenever the user adds an image to the FAVOURITES, it gets added to the Realm Database where the model class being used is the FavouriteImagesModel.The selected media in the all_photos and fav_photos mode can be accessed via by selectedMedia.size()  and the number of selected media in the albums_mode can be accessed by getAlbum().getSelectedCount().

So in the execute method of doInBackground() a condition check is initially made and then 2 separate loops are run depending upon the mode in which the selected images exist.

Initially it is checked whether the selected image is already a FAVOURITE one or not by the following code. If it belongs to the favourite mode then the size of the favouriteImageModels would become 1.

RealmResults<FavouriteImagesModel> favouriteImagesModels = realm.where
                                               (FavouriteImagesModel.class).equalTo(“path”, selectedMedias.get(i).getPath( )).findAll( );

If ( favouriteImagesModels.size( ) == 1) {
            favouriteImagePresent = true;
             imagesUnfavourited++;
   }

Now as the image belongs to the favourite mode we ultimately use the following code to remove the image from FAVOURITES.

 favouriteImagesModels.deleteAllFromRealm();

The full code which handle the option to unfavourite an image is shown below.

@Override
                   protected Boolean doInBackground(String arg0) {
                    

        if ( all_photos || fav_photos )   {


                           realm = Realm.getDefaultInstance();
                           realm.executeTransaction ( new Realm.Transaction( ) {
                               

                               @Override
                               public void execute (Realm realm)  {
                                   for (int i = 0 ;  i < selectedMedias.size( ) ;  i++) {
                                       RealmResults<FavouriteImagesModel> favouriteImagesModels = realm.where
                                               (FavouriteImagesModel.class).equalTo(“path”, selectedMedias.get(i).getPath( )).findAll( );
                                       If ( favouriteImagesModels.size( ) == 1) {
                                           favouriteImagePresent = true;
                                           imagesUnfavourited++;
                                       }


                                       favouriteImagesModels.deleteAllFromRealm();
                                   }
                               }
                           });
                       }

         else if ( !fav_photos && !albumsMode ) {
                           realm = Realm.getDefaultInstance();
                           realm.executeTransaction(new Realm.Transaction() {
                           

                              @Override
                               public void execute(Realm realm) {
                                   for (int i = 0;  i < getAlbum().getSelectedCount();  i++) {
                                       RealmResults<FavouriteImagesModel> favouriteImagesModels = realm.where
                                               (FavouriteImagesModel.class).equalTo(“path”, getAlbum( ).getSelectedMedia(i).getPath( ) ).findAll( );
                                       If ( favouriteImagesModels.size() == 1) {
                                           favouriteImagePresent = true;
                                           imagesUnfavourited++;
                                       }
                                       favouriteImagesModels.deleteAllFromRealm();
                                   }
                               }
                   }

After the doInBackground( ) method has been executed the onPostExecute( ) comes into play and some other UI related changes are done such as a SnackBar message is shown if the image is removed from favourites.

Resources

  • Realm for Android

https://realm.io/blog/realm-for-android/

  • Asynchronous Transactions in Realm

https://realm.io/docs/java/latest/#working-with-realmobjects

 

Tags: GSoC18, FOSSASIA, Phimp.Me, Unfavourite image, Realm  

Continue ReadingAdding option to Unfavourite image in Phimp.Me

Implementing Color Picker in the Open Event Orga App

In the Open Event Orga App, we have implemented a color picker to change the color of the Session’s item heading. Earlier the user had to enter the hex code in the given field to provide the color which is a bit cumbersome but now it is much easier with the help of a color picker. The PR related to this issue can be found here: #1073

The following approach was followed to implement the color picker.

Firstly we have used the library https://github.com/Pes8/android-material-color-picker-dialog. So it is included in the build.gradle file. The following line is added this file.

dependencies {
       implementation ‘com.pes.materialcolorpicker:library:1.2.0’
   }

Then navigate to the CreateTracksFragment.java class where we will need to implement the color picker. But firstly when the user enters the fragment , he should be able to see a random color in the edit text field and a small demo to the right of it where the selected color will be shown. These two things are done in the following steps.

1. To auto fill the color field with a random color the following code is written:

public String getRandomColor() {
  Random random = new Random();
  colorRed = random.nextInt(255);
  colorGreen = random.nextInt(255);
  colorBlue = random.nextInt(255);
  colorRGB = Color.rgb(colorRed, colorGreen, colorBlue);
  return String.format(“#%06X”,(0xFFFFFF & colorRGB));
}

public int getRed() {
  return colorRed;
}

public int getGreen() {
  return colorGreen;
}

public int getBlue() {
  return colorBlue;
}

public int getColorRGB() {
  return colorRGB;
}

 

With the help of the above code a random hex color is generated with the help of colorRed, colorGreen and colorBlue. These random fields are assigned to the local variables as they need to be used later on in the CreateTracksFragment.

This method is then called from the onStart( ) of the CreateTracksFragment.

2. Now a small demo color view is created to the right of the edit text field where the user can see the color of the current hex code. To implement this the following XML code is written. A small image view is created with the id color_picker.

<ImageView
  android:id=“@+id/color_picker”
  android:layout_width=“24dp”
  android:layout_height=“24dp”
  android:layout_margin=“10dp”
  android:layout_weight=“0.2” />

The above image view is filled with the current selected color with the help of the following code:

This ensures that whenever the user opens the CreateTracksFragment he should be able to view the color.

binding.form.colorPicker.setBackgroundColor(getPresenter().getColorRGB());

Now after implementing the above 2 features, the color picker dialog needs to be shown when the user clicks on the demo color view and the dialog box should show the value of Red, Green, Blue generated by the random method in the presenter. So these values are obtained from the presenter and added as constructor parameters as shown below. The color picker library has 2 main methods that need to be implemented which are :

→ setCallback( ) which is a callback method triggered when the user has selected the color and clicks on submit button. The action that needs to be performed after this is written in this particular callback method. In our case, we want to fill the edit text with the selected color and so we set the edit text with the callback value. Also we need fill the image view with the selected color and hence its color is also set in the callback function.

→ The show( ) method is required to make the dialog box of the color picker visible.

 

private void setColorPicker() {
  if (colorPickerDialog == null)
      colorPickerDialog = new ColorPicker(getActivity(), getPresenter().getRed(), getPresenter().getGreen(), getPresenter().getBlue());

  binding.form.colorPicker.setBackgroundColor(getPresenter().getColorRGB());

  binding.form.colorPicker.setOnClickListener(view -> {
      colorPickerDialog.show();
  });

  colorPickerDialog.setCallback(color -> {
      binding.form.trackColor.setText(String.format(“#%06X”, (0xFFFFFF & color)));
      binding.form.colorPicker.setBackgroundColor(color);
      colorPickerDialog.dismiss();
  });
}

 

→ When the colors have been selected we need to dismiss the dialog box. This is done by calling the .dismiss( )of the color picker.

Now after the CreateTracksFragment has been implemented certain changes need to be done to the UpdateTracksPresenter as well. Whenever the user selects to update the track, he should see the colors associated with that track as well which were set during its creation. We add the following to the UpdateTracksFragment.

 

public int getRed() {
  String colorRed = track.getColor();
  return Integer.valueOf(colorRed.substring(1, 3), 16);
}

public int getGreen() {
  String colorGreen = track.getColor();
  return Integer.valueOf(colorGreen.substring(3, 5), 16);
}

public int getBlue() {
  String colorBlue = track.getColor();
  return Integer.valueOf(colorBlue.substring(5, 7), 16);
}

public int getColorRGB() {
  return Color.rgb(getRed(), getGreen(), getBlue());
}

These methods are then eventually called from the UpdateTracksFragment and provided as parameters to the colorpicker similar to what had been implemented in the CreateTracksFragment.

private void setColorPicker() {
  if (colorPickerDialog == null)
      colorPickerDialog = new ColorPicker(getActivity(), getPresenter().getRed(), getPresenter().getGreen(), getPresenter().getBlue());

  binding.form.colorPicker.setBackgroundColor(getPresenter().getColorRGB());

  binding.form.colorPicker.setOnClickListener(view -> {
      colorPickerDialog.show();
  });

  colorPickerDialog.setCallback(color -> {
      binding.form.trackColor.setText(String.format(“#%06X”, (0xFFFFFF & color)));
      binding.form.colorPicker.setBackgroundColor(color);
      colorPickerDialog.dismiss();
  });

The final result can be seen in the form of GIF

 

Resources

1. Color Picker Library

https://github.com/Pes8/android-material-color-picker-dialog

2. Generating random hex values

https://stackoverflow.com/questions/11094823/java-how-to-generate-a-random-hexadecimal-value-within-specified-range-of-value

Continue ReadingImplementing Color Picker in the Open Event Orga App