Change Password Feature for Open Event Android Organizer App

In Open Event Organizer Android App, the users were able to successfully login and sign up but in case they wanted to change their login password they could not. So, we added a feature to allow users to change their existing password. This blog explains the technical details to implement this feature following MVVM architecture and using highly efficient libraries like Retrofit, RxJava, Raziz Labs DbFlow, Data Binding.

Specifications

We will implement a page where users can enter their old password and new password along with a confirm password field. Their will be a login button to send the password change request to server. Server then return a response and we will provide feedback regarding the request. We are following MVP architecture so there will be a Model class, Fragment class, Presenter class and Network Layer to make network requests.

Let’s start with creating ChangePassword model class. There are three fields to store old password, new password and new confirmed password. Several Lombok annotations like @Data, @AllArgsConstructor, @NoArgsConstructor are used to avoid boilerplate code for getters, setters and constructors. @JsonNaming annotation is used to translate the Java Object names to KebabCase when they are serialized.

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
public class ChangePassword {

public String oldPassword;
public String newPassword;

@JsonIgnore
public String confirmNewPassword;
}

The layout file is binded to model using Data Binding. There will be three TextInputEditText fields for user input. An option to toggle password visibility and a login button.

The Fragment class binds layout file to the Fragment and handle UI stuff. Presenter is called to make Login request when login button is pressed.

public class ChangePasswordFragment extends BaseFragment<ChangePasswordPresenter> implements ChangePasswordView {

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.change_password_fragment, container, false);
validator = new Validator(binding);

AppCompatActivity activity = ((AppCompatActivity) getActivity());
activity.setSupportActionBar(binding.toolbar);

ActionBar actionBar = activity.getSupportActionBar();
if (actionBar != null) {
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}

return binding.getRoot();
}

@Override
public void onStart() {
super.onStart();
getPresenter().attach(this);
binding.setOrganizerPassword(getPresenter().getChangePasswordObject());
getPresenter().start();

binding.btnChangePassword.setOnClickListener(view -> {
if (!validator.validate())
return;

String url = binding.url.baseUrl.getText().toString().trim();
getPresenter().setBaseUrl(url, binding.url.overrideUrl.isChecked());
getPresenter().changePasswordRequest(binding.oldPassword.getText().toString(),
binding.newPassword.getText().toString(),
binding.confirmNewPassword.getText().toString());

});
}

When the Login button is pressed, changePasswordRequest() method is called which makes an asynchronous call to ChangePasswordModel in order to perform the task of sending and receiving data from network in a different thread than the UI thread. Along with making requests, this method also verifies the password typed in confirm password field and send the the error as well as success message to the fragment.

public class ChangePasswordPresenter extends AbstractBasePresenter<ChangePasswordView> {

public void changePasswordRequest(String oldPassword, String newPassword, String confirmPassword) {
if (!newPassword.equals(confirmPassword)) {
getView().showError(“Passwords Do Not Match”);
return;
}

organizerPasswordObject.setOldPassword(oldPassword);
organizerPasswordObject.setNewPassword(newPassword);
organizerPasswordObject.setConfirmNewPassword(confirmPassword);

changePasswordModel.changePassword(organizerPasswordObject)
.compose(disposeCompletable(getDisposable()))
.compose(progressiveErroneousCompletable(getView()))
.subscribe(() -> getView().onSuccess(“Password Changed Successfully”), Logger::logError);
}
}

We are using Retrofit to make POST Request to server using the REST API. @Body annotation denotes the object request body which here contains a Map<String, ChangePassword> object. The Response from server is captured in Observable<ChangePasswordResponse> which is an RxJava Observable.

@POST(“auth/change-password”)
Observable<ChangePasswordResponse> changePassword(@Body Map<String, ChangePassword> changePassword);

This is the declaration for the method in Network Layer where the actual network request is made. It takes as input the changePassword object from Presenter which is already binded with data. Then it uses RxJava to asynchronously call the Api class and pass in the Map<String, ChangePassword> object. The result is then processed and Completable object is returned to the presenter. The Presenter processes the Completable object and shows user feedback in the form of a message in SnackBar.

References

  1. Official documentation for RxJava by ReactiveX https://github.com/ReactiveX/RxJava
  2. Official documentation for Retrofit by Square Inc https://github.com/square/retrofit
  3. Codebase for Open Event Organizer App on Github https://github.com/fossasia/open-event-orga-app
  4. Open Event Server deployment at heroku https://open-event-api-dev.herokuapp.com/
Continue Reading

Create Session in Open Event Android Organizer Application

Open Event Android Organizer Application offered variety of features to Organizers from all over the world to help them host their Events globally but it didn’t had the functionality to create Sessions in the app itself and associate it to Tracks. This feature addition was crucial since it enables Organizer to create Sessions which every common person enquires about before attending and event. In this Blog Post we will see how we added this functionality in the app.

Open Event Android Organizer Application is a client for Open Event Server which provides the REST API.

Problem

There can be various Sessions associated with Tracks for an Event. Open Event API had the endpoint to implement Creating Session but the Orga app didn’t, so we worked on creating a Session in the app.

The UI for creating a Session is shown above. User can fill in the necessary details and click on the green Floating Action Button to create a Session.

How to implement functionality?

We will follow MVP Architecture and use Retrofit 2.x, RxJava, Dagger, Jackson, Data Binding and other industry standard libraries to implement this functionality.

Firstly, let’s create Session model class specifying the attributes and relationships to set up in database using RazizLabs DbFlow library. The POJO will be serialized into JSON by Jackson library to be passed on as a part of RequestBody to server.

Now we will create SessionApi class that will contain the request details to be passed to Retrofit. @POST denotes a POST request and @Body denotes the requestBody of the request which is a Session object.

public interface SessionApi {
@POST(“sessions”)
Observable<Session> postSession(@Body Session session);
}

This is the CreateSessionFragment class that contains the code binding model to the view. The Fragment class implements the CreateSessionView class overriding the method declarations present there. The @Inject annotation of Dagger is used to load singleton presenter instance lazily to improve app’s performance.

Event-Id and Track-Id’s are retrieved from Bundle from Fragment Transaction. These are then passed on to presenter when Create Session button is pressed. There are other methods to show binding progressbar, snackbar and other UI components to show progress of the background request to server and database.

public class CreateSessionFragment extends BaseFragment<CreateSessionPresenter> implements CreateSessionView {

@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup

container, @Nullable Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater,

R.layout.session_create_layout, container, false);
validator = new Validator(binding.form);

binding.sessionCreate.setOnClickListener(view -> {
if (validator.validate()) {
getPresenter().createSession(trackId, eventId);
}
});

return binding.getRoot();
}

@Override
public void onStart() {
super.onStart();
getPresenter().attach(this);
binding.setSession(getPresenter().getSession());
}
}

In the Presenter createSession method is called when create button is pressed in UI. The method attaches track-id and event-id to Session object. This is necessary for Relationship constraints on Session Model. Then after binding all the data to Session object, we pass it on to SessionRepository. The success response is provided to user by passing success response in getView().onSuccess() method.

public class CreateSessionPresenter extends

AbstractBasePresenter<CreateSessionView> {

public Session getSession() {
return session;
}

public void createSession(long trackId, long eventId) {

Track track = new Track();
Event event = new Event();

track.setId(trackId);
event.setId(eventId);
session.setTrack(track);
session.setEvent(event);

sessionRepository
.createSession(session)
.compose(dispose(getDisposable()))
.compose(progressiveErroneous(getView()))
.subscribe(createdSession ->

getView().onSuccess(“Session Created”), Logger::logError);
}
}

The SessionRepository uses RxJava to make asynchronous Retrofit Call to Server. We throw a Network Error to user if the device does not have Internet Connectivity.

The session object accepted as a parameter in createSession method is passed on to sessionApi. It will return Observable<Session> Response which we will process in doOnNext() method. Then the Session object along with required foreign key relationships with Track and Event is saved in database for offline use.

@Override
public Observable<Session> createSession(Session session) {
if (!repository.isConnected()) {
return Observable.error(new Throwable(Constants.NO_NETWORK));
}return sessionApi
.postSession(session)
.doOnNext(created -> {
created.setTrack(session.getTrack());
created.setEvent(session.getEvent());
repository
.save(Session.class, created)
.subscribe();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}

The above code snippets are from Open Event Orga Application. For exploring the entire codebase please refer here. For details about the REST API used by the app please visit here.

References

  1. Official RxJava Project on Github by ReactiveX https://github.com/ReactiveX/RxJava.
  2. Official Retrofit Project on Github by Square Inc https://github.com/square/retrofit.
  3. Official Open Event Organizer App on Github by FOSSASIA https://github.com/fossasia/open-event-orga-app.
  4. Documentation for REST API of Open Event Server on Heroku by FOSSASIA https://open-event-api-dev.herokuapp.com/.
Continue Reading

Delete Image Permanently from Trashbin in Phimpme App

In the Phimpme Android application, users can perform various operations on the images including renaming an image, sharing images, deleting images from the storage etc. However, with the implementation of the Trash Bin feature in the app, the user is now provided with the option to restore back the deleted images. Whenever the delete operation is performed, the selected images are moved to the Trash Bin and the user has the option to either delete the photos permanently or restoring back the deleted photos by navigating to the Trash bin section. So in this post, I’d be discussing the implementation of permanently deleting image/images from the Trashbin.

Step 1

Firstly, we need to add permanent delete option in the popup menu provided in the itemview in the TrashView section. Every item in the Trashbin section displays a popup menu with two options-restore and delete permanently. The permanent delete option has been implemented in the itemview by adding the following lines of code.

<?xml version=“1.0” encoding=“utf-8”?>
<menu xmlns:android=“http://schemas.android.com/apk/res/android” xmlns:app=“http://schemas.android.com/apk/res-auto”>

<item
    android:id=“@+id/delete_permanently”
    android:title=“@string/delete_permanently”
    app:showAsAction=“never” />
</menu>

Step 2

Now when the user opts to permanently delete any photo from the bin, a function deletePermanent would be invoked passing-in the trashbin object corresponding to the selected item as the parameter. Inside the deletePermanent method, a check is performed to determine whether the corresponding image file exists or not using the .exists method of the File class and if the result is true the file is deleted permanently using the .delete method of the File class. The method deletePermanent returns a boolean value depending on whether the image file is deleted permanently from the storage or not. The code snippets used to implement the deletePermanent method is provided below.

private boolean deletePermanent(TrashBinRealmModel trashBinRealmModel){
  boolean succ = false;
  String path = trashBinRealmModel.getTrashbinpath();
  File file = new File(Environment.getExternalStorageDirectory() + “/” + “.nomedia”);
  //File file = new File(Environment.getExternalStorageDirectory() + “/” + “TrashBin”);
  if(file.exists()){
      File file1 = new File(path);
      if(file1.exists()){
          succ = file1.delete();
      }
  }
  return succ;
}

Step 3

The function deletePermanent used implemented in the previous step returns a boolean value which will be used in this step. So if the deletePermanent method returns true indicating that the image has been deleted from the trashbin, first a method deleteFromRealm is invoked passing-in the path of the image in the Trashbin to delete the corresponding image’s record from the realm database. Thereafter, that particular trashbin item is removed from the ArrayList<TrashbinRealmModel> populating the TrashBin adapter to display the items in the TrashBin Activity and the adapter is notified of this change by the use of NotifyItemRemoved and NotifyItemRangeChanged methods of the RecyclerView adapter class passing-in the position of the item as parameter to the former and position along with the size of the updated list as parameters to the latter function. After the adapter is updated about the change in the dataset, the adapter re-populates the recyclerview thus displaying the remaining items in the trashbin section. The code snippets implementing the above-mentioned operations are provided below.

if(deletePermanent(trashBinRealmModel)){
  deleteFromRealm(trashItemsList.get(position).getTrashbinpath());
  trashItemsList.remove(position);
  notifyItemRemoved(position);
  notifyItemRangeChanged(position, trashItemsList.size());
}
private void deleteFromRealm(final String path){
  Realm realm = Realm.getDefaultInstance();
  realm.executeTransaction(new Realm.Transaction() {
      @Override public void execute(Realm realm) {
          RealmResults<TrashBinRealmModel> trashBinRealmModels = realm.where(TrashBinRealmModel.class).equalTo
                  (“trashbinpath”, path).findAll();
          trashBinRealmModels.deleteAllFromRealm();
      }
  });
}

This is how we have implemented the functionality to permanently delete an image from the Trashbin section in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

  1. Android Developer documentation –https://developer.android.com/reference/java/io/File
  2. Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/
  3. Realm database operations Android – https://www.androidhive.info/2016/05/android-working-with-realm-database-replacing-sqlite-core-data/
Continue Reading

Compressing Images in Resolution 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 compress an image. Two modes of compress operation have been implemented namely compress by size and compress by dimensions. In the previous blog post I discussed the implementation of compress by size operation. So in this blog post, I will be discussing how we achieved the functionality to compress any image by resolution.

Step 1

First, we need to add an option in the bottombar menu(in the SingleMediaActivity) to compress the image being viewed. The option to compress an image has been added by implementing the following lines of code in the  menu_bottom_view_pager.xml file.

<item
      android:id=“@+id/action_compress”
      android:orderInCategory=“2”
      app:showAsAction=“always”
      android:icon=“@drawable/compressicn”
      android:title=“Compress”/>

Step 2

Now on selecting the compress option, the user would be prompted to choose the compress mode i.e compress by size or compress by dimension. Once the user opts for compressing the image by resolution, a dialog containing the various resolutions options to choose from is to be displayed. Code snippets used to implement the dialog along with seekbar is displayed below.

<android.support.v7.widget.CardView
  xmlns:android=“http://schemas.android.com/apk/res/android”
  xmlns:app=“http://schemas.android.com/apk/vn.mbm.phimp.me”
  android:layout_width=“match_parent”
  android:layout_height=“wrap_content”
  app:cardCornerRadius=“2dp”
  android:id=“@+id/dialog_chose_provider_title”>
  <LinearLayout
      android:layout_width=“match_parent”
      android:layout_height=“wrap_content”
      android:orientation=“vertical”>

      <TextView
          android:id=“@+id/compress_title”
          android:layout_width=“match_parent”
          android:layout_height=“wrap_content”
          android:padding=“24dp”
          android:text=“@string/compress_by_dimension”
          android:textColor=“@color/md_dark_primary_text”
          android:textSize=“18sp”
          android:textStyle=“bold” />

      <ListView
          android:id=“@+id/listview”
          android:layout_width=“match_parent”
          android:layout_height=“321dp”
          android:layout_alignParentStart=“true”
          android:layout_centerVertical=“true”/>

  </LinearLayout>
</android.support.v7.widget.CardView>

A screenshot displaying the resolutions option dialog is provided below.

Step 3

Now in this final step as mentioned for the compress by size post, we would use the image compression library Compressor to obtain the compress by resolution functionality. After the user has specified the new resolution required, the Compressor class of the library is instantiated by passing-in the context as the parameter and some of its functions are invoked simaltaneously. The functions invoked are setMaxWidth(), setMaxHeight(),  setCompressFormat(), setDestinationDirectoryPath(), compressToFile().

setMaxWidth() – to set the width of the output image.

setMaxHeight() – to set the height of the output image.

setCompressFormat(Bitmap.CompressFormat) – to determine the format of the output compressed image.

setDestinationDirectoryPath(File) – to specify the path to which the compressed image is to be saved.

compressToFile(File) – to perform the compress operation, passing-in the file object of the corresponding image to be compressed.

The compressToFile() function performs the final compress operation and saves the compressed image to the specified path.

Code snippets to implement the above mentioned operations are given below

private void compressDim() {
  ListCompressAdapter lviewAdapter;
  ArrayList<String> compress_option= new ArrayList<String>();
  MediaDetailsMap<String,String> mediaDetailsMap = SingleMediaActivity.mediacompress.getMainDetails(this);
  //gives in the form like 1632×1224 (2.0 MP) , getting width and height of it
  String dim[]=mediaDetailsMap.get(“Resolution”).split(“x”);
  int  width= Integer.parseInt(dim[0].replaceAll(” “,“”));
  String ht[]=dim[1].split(” “);
  int height= Integer.parseInt(ht[0]);
  LayoutInflater inflater = getLayoutInflater();
  final View dialogLayout = inflater.inflate(R.layout.dialog_compresspixel, null);
  TextView title = (TextView) dialogLayout.findViewById(R.id.compress_title);
  title.setBackgroundColor(getPrimaryColor());
  //create options of compress in dimensions in multiple of 2
  int awidth=width;
  int aheight=height;
  ListView listView = (ListView)dialogLayout.findViewById(R.id.listview);
  while ((width%2==0)&&(height%2==0)) {
      compress_option.add(width + ” X “ + height);
      width=width/2;
      height=height/2;
  }

  lviewAdapter = new ListCompressAdapter(this, compress_option);
  listView.setAdapter(lviewAdapter);
  final int finalWidth = awidth;
  final int finalHeight = aheight;
  listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
          if (position==0){
              cwidth[0] = finalWidth ;
              cheight[0] = finalHeight;}
          else{
              cwidth[0] = finalWidth /(position*2);
              cheight[0] = finalHeight /(position*2);}
          view.setBackgroundColor(R.color.md_light_blue_A400);
          new SaveCompressedImage().execute(“Resolution”);
          finish();
      }
  });
new Compressor(getApplicationContext())
      .setMaxWidth(cwidth[0])
      .setMaxHeight(cheight[0])
      .setCompressFormat(Bitmap.CompressFormat.JPEG)
      .setDestinationDirectoryPath( FileUtilsCompress.createFolders().getPath())
      .compressToFile(new File(saveFilePath));

This is how we have implemented the functionality to compress an image by size in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

  1. Android Developer documentation –https://developer.android.com/reference/java/io/File
  2. Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/
  3. Compressor Library – https://github.com/zetbaitsu/Compressor

 

Continue Reading

Compress Images Size 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 compress an image. Two modes of compress operation have been implemented namely compress by size and compress by dimensions. So in this blog post, I will be discussing how we achieved the functionality to compress any image by size.

Step 1

First, we need to add an option in the bottom bar menu(in the SingleMediaActivity) to compress the image being viewed. The option to compress an image has been added by implementing the following lines of code in the  menu_bottom_view_pager.xml file.

<item
      android:id=“@+id/action_compress”
      android:orderInCategory=“2”
      app:showAsAction=“always”
      android:icon=“@drawable/compressicn”
      android:title=“Compress”/>

Step 2

Now on selecting the compress option, the user would be prompted to choose the compress mode i.e compress by size or compress by dimension. Once the user has opted for the size mode a dialog with a seekbar to set the compress percentage is to be displayed. Code snippets used to implement the dialog along with seekbar is displayed below.

<RelativeLayout
  android:layout_width=“wrap_content”
  android:layout_height=“wrap_content”
  android:layout_marginTop=“8dp”>

  <SeekBar
      android:id=“@+id/seekBar”
      android:layout_width=“match_parent”
      android:layout_height=“wrap_content”
      android:layout_weight=“0.65”
      android:max=“90”
      android:progressBackgroundTint=“@color/colorPrimary”
      android:thumb=“@drawable/ic_location_on_black_24dp” />

  <TextView
      android:id=“@+id/textview2”
      android:layout_width=“wrap_content”
      android:layout_height=“wrap_content”
      android:layout_alignParentEnd=“true”
      android:layout_below=“@+id/seekBar”
      android:layout_marginBottom=“5dp”
      android:layout_marginEnd=“16dp”
      android:text=“95%”
      android:textAppearance=“@style/TextAppearance.AppCompat.Body1”
      android:textSize=“16sp” />
</RelativeLayout>

A screenshot displaying the dialog to set the compress percentage is provided below

Step 3

Here to obtain the compress functionality we have used an open-sourced custom image compression library Compressor. After the user has set the compress percentage, the Compressor class of the library is instantiated by passing-in the context as the parameter and some of its functions are invoked simultaneously. The functions invoked are setQuality(), setCompressFormat(), setDestinationDirectoryPath(), compressToFile().

setQuality(int) – to set the percentage compressed.

setCompressFormat(Bitmap.CompressFormat) – to determine the format of the output compressed image.

setDestinationDirectoryPath(File) – to specify the path to which the compressed image is to be saved.

compressToFile(File) – to perform the compress operation, passing-in the file object of the corresponding image to be compressed.

The compressToFile() function performs the final compress operation and saves the compressed image to the specified path.

Code snippets to implement the above-mentioned operations are given below

private void compressSize() {

  LayoutInflater inflater = getLayoutInflater();
  View dialogLayout = inflater.inflate(R.layout.dialog_compresssize, null);
  TextView title = (TextView) dialogLayout.findViewById(R.id.compress_title);
  title.setBackgroundColor(getPrimaryColor());
  SeekBar percentsize = (SeekBar) dialogLayout.findViewById(R.id.seekBar);
  percentsize.getThumb().setColorFilter(getAccentColor(), PorterDuff.Mode.SRC_IN);
  percentsize.setProgress(0);
  final TextView percent=(TextView)dialogLayout.findViewById(R.id.textview2);
  percentsize.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
      @Override
      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
          //options of compress by size from 5% to 9;
          int progress1=95-progress;
          progress1=progress1-progress1%5;
          percent.setText(progress1+“%”);
          percentagecompress=progress1;}

      @Override
      public void onStartTrackingTouch(SeekBar seekBar) {
          //do nothing
      }

      @Override
      public void onStopTrackingTouch(SeekBar seekBar) {
          //do nothing
      }
  });}
new Compressor(getApplicationContext())
.setQuality(percentagecompress)
.setCompressFormat(Bitmap.CompressFormat.JPEG)
.setDestinationDirectoryPath(FileUtilsCompress.createFolders().getPath())
.compressToFile(new File(saveFilePath));

This is how we have implemented the functionality to compress an image by size in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository.

Resources

  1. Android Developer documentation –https://developer.android.com/reference/java/io/File
  2. Compressor Library – https://github.com/zetbaitsu/Compressor
  3. Seekbar Tutorial – https://abhiandroid.com/ui/seekbar
Continue Reading

Implementing Undo Operation in Phimpme Android application

In the Phimpme Android application, users can perform various operations on the images available including renaming an image, sharing images, deleting images from the storage etc. However, there might occur a scenario where the user accidentally or in a rush performs some action and would want to undo it the very next second, so in such cases the undo action feature comes in pretty handy. So in this blog post, I will be discussing the undo operation option implemented for the delete image feature in Phimpme Android app.

Step 1

Firstly, we need to add an “Undo” action button in the Snackbar that displays the success operation message. With this addition, the user would have time to undo an operation until the snackbar message is being displayed on the screen. The undo action button can be added to the Snackbar by the use of the setAction method of the Snackbar class. The code snippets used to implement the button is provided below

Snackbar snackbar = SnackBarHandler.showWithBottomMargin2(mDrawerLayout, String.valueOf(no) + ” “ + getString(R.string
              .trashbin_move_onefile),
      navigationView.getHeight
              (), Snackbar.LENGTH_SHORT);
snackbar.setAction(“UNDO”, new View.OnClickListener() {
  @Override
  public void onClick(View view) {
      getAlbum().moveAllMedia(getApplicationContext(), getAlbum().getPath(), media1);
      refreshListener.onRefresh();
  }
});
snackbar.show();

Step 2

When the user performs the delete operation, first the photos are moved to the trashbin and the user have the option to delete them from there. Now, if the user opts for undo operation, on clicking the undo button the function moveallMedia will be invoked passing-in context, the path of the current album and an ArrayList<Media> containing the deleted media on which the undo operation is to be performed upon, as parameters. Here the ArrayList<Media> passed as a parameter contains the trashbin paths of the images deleted by the previous action. A method storeDeletedFilesTemporarily has been used that returns the list containing the deleted images trashbin paths. The implementation of the method storeDeletedFilesTemporarily has been provided below.

private ArrayList<Media> storeDeletedFilesTemporarily(){
ArrayList<Media> deletedImages = new ArrayList<>();
if(!all_photos && !fav_photos && editMode){
for(Media m: getAlbum().getSelectedMedia()){
String name = m.getPath().substring(m.getPath().lastIndexOf(“/”) + 1);
deletedImages.add(new Media(Environment.getExternalStorageDirectory() + “/” + “.nomedia” + “/” + name));
}
} else if(all_photos && !fav_photos && editMode){
for(Media m: selectedMedias){
String name = m.getPath().substring(m.getPath().lastIndexOf(“/”) + 1);
deletedImages.add(new Media(Environment.getExternalStorageDirectory() + “/” + “.nomedia” + “/” + name));
}
}
return deletedImages;
}

Step 3

Now in the final step, I’d discuss the implementation of the moveAllMedia method that actually completes the undo operation. As I mentioned earlier that on performing the delete operation the images are moved to the bin folder(which is hidden from the other applications), so the moveAllMedia method here will again move the images from the trashbin folder to the current folder. With this operation deleted images would be stored back to their original folder and their trashbin record would be simultaneously deleted from the Realm database. The code implementation of the moveAllMedia method is provided below.

public int moveAllMedia(Context context, String targetDir, ArrayList<Media> albummedia){
 int n = 0;
 try
 {
    int index=-1;
    for(int i =0;i<albummedia.size(); i++)
    {
       String s = albummedia.get(i).getPath();
       int indexOfLastSlash = s.lastIndexOf(“/”);
       String fileName = s.substring(indexOfLastSlash + 1);

       if(!albummedia.get(i).getPath().equals(targetDir+“/”+fileName)){
          index=-1;
       }else{
          index=i;
          break;

       }
    }
    if(index!=-1)
    {
       n = –1;
    }else{
       for (int i = 0; i < albummedia.size(); i++) {

          if (moveMedia(context, albummedia.get(i).getPath(), targetDir)) {
             String from = albummedia.get(i).getPath();
             scanFile(context, new String[]{ from, StringUtils.getPhotoPathMoved(albummedia.get(i).getPath(),
                   targetDir) },
                   new MediaScannerConnection.OnScanCompletedListener() {
                      @Override
                      public void onScanCompleted(String s, Uri uri) {
                         Log.d(“scanFile”, “onScanCompleted: “ + s);
                      }
                   });
             media.remove(albummedia.get(i));
             n++;
          }
       }
       setCount(media.size());
    }
 } catch (Exception e) { e.printStackTrace(); }
 return n;

}

This is how we have implemented the functionality to perform Undo operation in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository.

Resources

  1. Android Developer documentation –https://developer.android.com/reference/java/io/File
  2. MediaScanner Tutorial – https://stackoverflow.com/questions/9414955/trigger-mediascanner-on-specific-path-folder-how-to/25086535

 

Continue Reading

Option to Restore Deleted Images in Phimpme Android Application

In the Phimpme Android application, users can perform various operations on the images including renaming an image, sharing images, deleting images from the storage etc. However with the implementation of Trash Bin feature in the app, the user is now provided with the option to restore back the deleted images. Whenever the delete operation is performed, the selected images are moved to the Trash Bin and the user has the option to either delete the photos permanently or restoring back the deleted photos by navigating to the Trash bin section. So, in this post I’d be discussing the implementation of restore image/images functionality.

Step 1

Firstly, we need to add restore option in the popup menu provided in the itemview in the TrashView section. Every item in the Trashbin section displays a popup menu with two options-restore and delete permanently. The popup menu along with the two options have been implemented by adding the following lines of code.

<?xml version=“1.0” encoding=“utf-8”?>
<menu xmlns:android=“http://schemas.android.com/apk/res/android” xmlns:app=“http://schemas.android.com/apk/res-auto”>

<item
    android:id=“@+id/restore_option”
    android:title=“@string/restore_photo”
    app:showAsAction=“never” />

<item
    android:id=“@+id/delete_permanently”
    android:title=“@string/delete_permanently”
    app:showAsAction=“never” />
</menu>

Step 2

Now after the user opts to restore a particular image, the corresponding realm object for the image would be retrieved from the Realm database. This realm object would contain attributes namely trashbinpath, oldpath, datetime, timeperiod. Here the oldpath attribute contains the path where it  was stored in the device storage before the delete operation was performed on the image. Thereafter to restore the image to its old path, a move operation would be performed which would move the image from the TrashBin section to the album it was stored in before deletion. With this move operation, the image is restored to its old path and is visible in the respective album resulting in the removal/deletion of the image from the TrashBin section. The method implemented to perform the above mentioned operations is provided below.

private boolean restoreImage(TrashBinRealmModel trashBinRealmModel){
  String oldpath = trashBinRealmModel.getOldpath();
  boolean success = false;
  try {
      String from = trashBinRealmModel.getTrashbinpath();
      if (success = moveMedia(context, from, targetDir)) {
          scanFile(context, new String[]{ from, StringUtils.getPhotoPathMoved(pathofmed, targetDir) });
      }
  } catch (Exception e) { e.printStackTrace(); }
  return success;
}

Step 3

In the previous step, the move operation for the restore feature was performed by the restoreImage method which inturn uses method moveMedia() to get the job done. So in this step, I’d be discussing about the implementation of the moveMedia() method. Inside the moveMedia() method, moveFile method of ContentHelper class is invoked passing-in context, sourcefile, and the destination folder as the parameters. Thereafter a normal rename operation is performed by the use of the renameTo method to move the file to destination folder. Now if the rename operation is performed successfully, the file is moved to its old path whereas if the rename operation fails to get the job done, a copy operation is initiated to copy the file to the destination folder and the image  the is deleted from the trashbin section if the copy operation is successful. The code snippets used to implement the moveMedia and moveFile are provided below.

private boolean moveMedia(Context context, String source, String targetDir) {
 File from = new File(source);
 File to = new File(targetDir);
 return ContentHelper.moveFile(context, from, to);
}
public static boolean moveFile(Context context, @NonNull final File source, @NonNull final File targetDir) {
 // First try the normal rename.
 File target = new File(targetDir, source.getName());

 boolean success = source.renameTo(target);

 if (!success) {
    success = copyFile(context, source, targetDir);
    if (success) {
       success = deleteFile(context, source);
    }
 }
 //if (success) scanFile(context, new String[]{ source.getPath(), target.getPath() });
 return success;

}

This is how we have implemented the functionality to restore a deleted image in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

  1. Android Developer documentation –https://developer.android.com/reference/java/io/File
  2. Github-Phimpme Android Repository – https://github.com/fossasia/phimpme-android/
  3. Renaming a file in java – http://stacktips.com/tutorials/java/how-to-delete-and-rename-a-file-in-java
Continue Reading

Implementing Progress Bar in PSLab App

This blog is a step by step guide on how to make a functional progress bar in an Android app by taking an example of the progress bar implemented in PSLab Android application. The example is based on my work done in PSLab Android repository under PR #1077 and so it will only demonstrate making a simple progress bar (both linear and circular) and not the one showing progress in percentage too.

How progress bar was implemented in the PSLab app?

Both horizontal and circular progress bar is available in the Material Design Library provided by Google in Android Studio. So, no extra dependencies are needed.

Just drag and drop the progress bar of whichever shape necessary i.e. circular or horizontal, directly on the screen available in the Design tab as shown in Figure 1.

Figure 1. Design tab in Android Studio

There are two ways to use the progress bar available in the Material Design Library.

  • To show a loading screen
  • To show the progress of a process

Loading Screen

Loading Screens are used when the time that will be taken by the process is not known. So, an indeterminate circular progress bar is used to show that some process is going on and so the user needs to wait.

Layout

A circular progress bar is used for this process and so drag and drop the circular progress bar as shown in figure 1. Now set the position, height, and width of the progress bar in the layout as necessary. To set the color of the progress bar, use attribute android:indeterminateTint

Backend

To implement this type of functionality, use the setVisibility function to show the progress bar while some process is taking place in the backend and immediately remove it as soon as the result is ready to be displayed. To make the progress bar visible use progressBar.setVisibility(View.VISIBLE) and to make it invisible use progressBar.setVisibility(View.GONE)

Showing Progress

This is a very common type of process and is used by most of the apps. A horizontal progress bar is used to show the actual progress of the process taking place in the backend on a scale of 0-100 (the scale may vary) where 0 means the process hasn’t started and 100 means the result is now ready to be displayed.

Layout

Horizontal Progress bars are used for this type of usage. So, drag and drop the horizontal progress bar as shown in figure 1. Now set the position, height, and width of the progress bar in the layout as necessary. Different styles of the progress bar can be found in the documentation [1].

Backend

Initially, set the progress of the bar to 0 as no process is taking place by using method setProgress(). Now as soon as the process starts, to increase the progress by a fixed value, use progressBar.incrementProgressBy() method and to set the progress directly, use progressBar.setProgress() method.

So in this way, a progress bar can be implemented in an Android application. Other features like adding custom designs and animations can be done by making the necessary shapes and animations respectively and using the functions available in the documentation [1].

Resources

  1. https://developer.android.com/reference/android/widget/ProgressBar – Documentation of Progress Bar

 

Continue Reading

Making custom dialog boxes in PSLab app

This blog covers the topic of how to create custom dialog boxes in an Android app by taking an example of how it was implemented in PSLab Android application. The custom dialog box that will be illustrated in this blog will have a layout similar to that from my PR feat: added a guide for Logic Analyzer Instrument using Alert Dialog Box in PSLab Android repository. But all the main functionalities that can be added to a custom dialog will be illustrated in this blog.

How custom dialog was implemented in the PSLab app?

  • The first step is to make a layout for the custom dialog box. The layout of custom dialog should be such that it shouldn’t occupy the whole screen as the idea here is to overlay the dialog over the activity and not replace the whole activity with the dialog layout. The layout can be implemented by using the following attributes :
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

   <RelativeLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content">

       <TextView
           android:id="@+id/custom_dialog_text"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"/>

       <ImageView
           android:id="@+id/custom_dialog_schematic"
           android:layout_width="wrap_content"
           android:layout_height="@dimen/schematic_height" />

       <TextView
           android:id="@+id/description_text"
           android:layout_width="match_parent"
           android:layout_height="wrap_content" />

       <CheckBox
           android:id="@+id/toggle_show_again"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content" />

       <RelativeLayout
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:layout_below="@id/toggle_show_again">

           <Button
               android:id="@+id/dismiss_button"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content" />

       </RelativeLayout>
   </RelativeLayout>
</ScrollView>

The result of the above layout after adding proper margins and padding is as shown in figure 1.

Figure 1. The output of the layout made for custom dialog

  • Now as the layout is ready, we can focus on adding Java code to inflate the layout over a particular activity. Make a function in the activity or the fragment in which the custom dialog is to be inflated. Add the following code (if the layout is same as that in PSLab app else modify the code as per the layout) in the function to display the custom dialog box.
public void howToConnectDialog(String title, String intro, int iconID, String desc) {
   try {
       final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
       LayoutInflater inflater = getLayoutInflater();
       View dialogView = inflater.inflate(R.layout.custom_dialog_box, null);

 // Find and set all the attributes used in the layout and then create the dialog as shown
       
       builder.setView(dialogView);
       builder.setTitle(title);
       final AlertDialog dialog = builder.create();
       ok_button.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
                dialog.dismiss();
           }
       });
       dialog.show();
   } catch (Exception e) {
       e.printStackTrace();
   }
}

Here, the LayoutInflater inflates the custom dialog layout in the Alertdialog builder. The Alertdialog builder is used to hold and set values for all the views present in the inflated layout. The builder then creates a final custom dialog when builder.create() method is called. Finally, the dialog is shown by calling method dialog.show() and is dismissed by calling method dialog.dismiss().

  • Now the dialog box is ready and it will be inflated when the above method is called. But the “Don’t show again” checkbox currently has no functionality and so the dialog box will pop up although “Don’t show again” checkbox is ticked mark by the user. For adding this functionality, we will need to use Shared Preferences to save the state of the dialog box. For using Shared Preferences, first, add the following lines of code in the above method
final SharedPreferences settings = getActivity().getSharedPreferences(PREFS_NAME, 0);
Boolean skipMessage = settings.getBoolean("skipMessage", false);

The PREFS_NAME is the key to distinctly identify the state of the given dialog box from the stored state of many dialog boxes (if any). A suggested key name is “customDialogPreference”. The skipMessage is a boolean used here to check the state if the dialog box should be inflated or not.

Now format the onClickListener of the OK button with the following code :

Boolean checkBoxResult = false;
if (dontShowAgain.isChecked())
   checkBoxResult = true;
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("skipMessage", checkBoxResult);
editor.apply();
dialog.dismiss();

The following code checks the state of “Don’t show again” dialog and then inflates the custom dialog if applicable. If the checkbox is ticked mark then on pressing the OK button the dialog won’t be inflated again.

Resources

  1. https://developer.android.com/guide/topics/ui/dialogs – Android Documentation on Dialogs (Great Resource to make a custom dialog)
Continue Reading

Move Photos from one Path to Another in Phimpme Android Application

In the Phimpme Android application, users can perform various operations on images such as editing an image, sharing an image, printing a pdf version of the image and many more. However, another important functionality that has been implemented is the option to move an image from one path(folder) to another. So in this blog post, I will be discussing how we achieved the functionality to move any image.

Step 1

First, we need to add an option in the overflow menu to move any no of images from one folder to another. The option to move an image has been added by implementing the following lines of code in the  menu_view_pager.xml file.

<item
  android:id=“@+id/action_move”
  app:showAsAction=“never”
  android:title=“Move”/>

Step 2

Now after the user opts for the move operation, a bottomSheetDialog containing all the albums/folders in the storage to choose for moving the photos into will be displayed. Once the user selects the required location to move the photos to, a method moveSelectedMedia would be invoked passing-in the context and the destination path as the parameters. Thereafter inside the method moveSelectedMedia, first a check would be performed to determine whether the selected photos(to move) are already present in the folder(path folder and destination folder both are same), if true the operation would be terminated as the photos are already present in the destination folder, and if false the selected photos would be moved to the destination folder one by one by the use of another method moveMedia. The code snippets used to implement the moveSelectedMedia is provided below.

public int moveSelectedMedia(Context context, String targetDir) {
 int n = 0;
 try
 {
    int index=-1;
    for(int i =0;i<selectedMedias.size();i++)
    {
       String s = selectedMedias.get(i).getPath();
       int indexOfLastSlash = s.lastIndexOf(“/”);
       String fileName = s.substring(indexOfLastSlash + 1);

       if(!selectedMedias.get(i).getPath().equals(targetDir+“/”+fileName)){
          index=-1;
       }else{
          index=i;
          break;

       }
    }
    if(index!=-1)
    {
       n = –1;
    }else{
       for (int i = 0; i < selectedMedias.size(); i++) {

          if (moveMedia(context, selectedMedias.get(i).getPath(), targetDir)) {
             String from = selectedMedias.get(i).getPath();
             scanFile(context, new String[]{ from, StringUtils.getPhotoPathMoved(selectedMedias.get(i).getPath(), targetDir) },
                   new MediaScannerConnection.OnScanCompletedListener() {
                      @Override
                      public void onScanCompleted(String s, Uri uri) {
                         Log.d(“scanFile”, “onScanCompleted: “ + s);
                      }
                   });
             media.remove(selectedMedias.get(i));
             n++;
          }
       }
       setCount(media.size());
    }
 } catch (Exception e) { e.printStackTrace(); }
 return n;
}

Step 3

In this step, I’d discuss the use and implementation of the moveMedia method mentioned in the earlier step. So the moveMedia is used to move a single photo to a particular destination, taking-in the context, the oldpath of the photo and the destination folder as parameters. Inside this method another method moveFile of the ContentHelper class is invoked passing-in the context, File object of the photo and File object of the destination folder as parameters. Now inside the moveFile method the actual move operation takes place. First the photo is tried to move by doing a normal rename operation by using the renameTo function of the File class. The success of the rename operation updates the path of the photo to the destination path and deletes the original path of the photo whereas if the normal rename operation fails, then the photo is moved by first performing a copy operation to copy the photo to the destination folder and then deleting the original photo. The code snippets used to implement the moveMedia and moveFile functions are provided below.

private boolean moveMedia(Context context, String source, String targetDir) {
 File from = new File(source);
 File to = new File(targetDir);
 return ContentHelper.moveFile(context, from, to);
}
public static boolean moveFile(Context context, @NonNull final File source, @NonNull final File targetDir) {
 // First try the normal rename.
 File target = new File(targetDir, source.getName());

 boolean success = source.renameTo(target);

 if (!success) {
    success = copyFile(context, source, targetDir);
    if (success) {
       success = deleteFile(context, source);
    }
 }

 //if (success) scanFile(context, new String[]{ source.getPath(), target.getPath() });
 return success;
}

And at last, after the photos are moved successfully the media adapter is called with the updated photos list to display thus displaying the remaining photos in the folder.

This is how we have implemented the functionality to move images from one path to another in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android Github repository listed in the resource section below.

Resources

1. Android Developer documentation –              https://developer.android.com/reference/java/io/File

2. Github-Phimpme Android Repository – https://github.com/fossasia/phimpme- android/

3. Renaming a file in java – http://stacktips.com/tutorials/java/how-to-delete-and-rename-a-file-in-java

Continue Reading
Close Menu