Handling Click Events using Custom Binding Adapters

The Open Event Organiser Android App is the Event management app for organizers using the Open Event Platform. It is currently released in the Alpha phase on the Google Play Store here and is being actively developed by the community.

The Data Binding Library is one of the most popular libraries among the android developers. We use it extensively in the application in order to greatly simplify the UI binding logic. While trying to show the details of a speaker in the application, we wanted to list his/her social media links using Image buttons.

Upon clicking one of these buttons, the user was supposed to be directed to the link after opening the default web browser. This blog post discusses how we used custom Binding Adapters to handle click events on an Image Button by defining a custom attribute.

Defining the Binding Adapter

We defined a simple Binding Adapter for an Image button meant to handle social media links. We used “imageOnClick” as the custom attribute name for specifying the URL that will be opened once the button is clicked.

@BindingAdapter("imageOnClick")
public static void bindOnImageButtonClickListener(ImageButton imageButton, String url) {
  imageButton.setOnClickListener(view -> {
    if (url != null) {
      Context context = imageButton.getContext();
      Intent intent = new Intent(Intent.ACTION_VIEW);
      intent.setData(Uri.parse(url));
      if (intent.resolveActivity(context.getPackageManager()) != null) {
        context.startActivity(intent);
      } else {
        Toast.makeText(context, "No Web browser found", Toast.LENGTH_SHORT).show();
      }
    }
  });
}

 

The method can be named anything you want and can be placed anywhere in the project but we would recommend creating a separate class for all the Binding adapters.
The important things to take away from the above method are:

  • The method needs to be public otherwise the Data binding framework won’t be able to find it.
  • We need to pass in the view as the first parameter and the attribute value as the second parameter.

Then we simply set the familiar click listener to handle the click interaction. We use the Context from the view passed in the method as the first parameter. Then we create an Intent and set the passed in URL as the data. We make sure that the user has a browser installed on his/her android phone before we try to open the browser. We show a suitable error message if they don’t.

Using it in Layout

Using the custom attribute in the layout was extremely simple. We specified the url using the attribute “imageOnClick” and the rest was handled by the Binding Adapter and the Data binding framework.  

<ImageButton
     android:id="@+id/action_speakers_linkedin"
     android:layout_width="@dimen/spacing_larger"
     android:layout_height="match_parent"
     android:contentDescription="@string/linkedin_icon"
     app:imageOnClick="@{ speaker.linkedin }"
     android:background="#ededed"
     android:visibility="@{ (speaker.linkedin != null) ? View.VISIBLE : View.GONE }"
     app:srcCompat="@drawable/ic_linkedin_colored"/>

References

Continue Reading

Option to secure particular albums in Phimpme Android Application

In the Phimpme Android application, users can perform various operations on the albums available such as creating a zip file of the album, rename an album and many more. However, one another useful functionality that has been added to the Phimpme Android application is the option to secure particular albums. So in this post, I will be discussing the implementation of this security feature.

Step 1

Firstly, a view item for providing the option to enable security for particular albums is required to be added to the security settings layout. The two-state toggle switch widget provided by the Android framework along with a textview has been added as the required view item. A screenshot depicting the layout change is provided below.

The code snippet representing the operation is provided below.

<TextView
  android:id=“@+id/security_body_apply_folders_title”
  android:layout_width=“wrap_content”
  android:layout_height=“wrap_content”
  android:text=“@string/local_folder”
  android:textColor=“@color/md_dark_background”
  android:textSize=“@dimen/medium_text” />

<android.support.v7.widget.SwitchCompat
  android:id=“@+id/security_body_apply_folder_switch”
  android:layout_width=“wrap_content”
  android:layout_height=“wrap_content”
  android:layout_alignParentEnd=“true”
  android:layout_centerVertical=“true”
  android:layout_gravity=“center_vertical”
  android:button=“@null”
  android:hapticFeedbackEnabled=“true” />

Step 2

Now we need to keep track of the albums selected by the user to secure. This can be done by storing the selected album/albums paths in an ArrayList<String> which can be referred later when required in the process.

The required code snippet to perform the above mentioned operation is provided below.

holder.foldercheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if(b){
securedfol.add(a.getPath());
a.setsecured(true);
}else{
securedfol.remove(a.getPath());
a.setsecured(false);
}
}
});

Step 3

Now we need to store the selected albums preference in the SharedPreference so that the user’s security preference persists even when the user exits the application and the user doesn’t have to redo the securing operation the next time user launches the application. The ArrayList<String> object containing the path of the user choice albums are converted to JSON representation by the use of the Gson Java library and the string key denoting the JSON representation of the list is saved in the SharedPreference thereafter.

if(securedfol.size()>0){
  SharedPreferences.Editor editor = SP.getEditor();
  Gson gson = new Gson();
  String securedfolders = gson.toJson(securedfol);
  editor.putString(getString(R.string.preference_use_password_secured_local_folders), securedfolders);
  editor.commit();}

Now at the time of performing other operations on the secured folders, the list containing the secured folder paths is retrieved from SharedPreference and the choosen folder’s path is searched in the obtained list, then the user is asked to authenticate accordingly.

This is how we have implemented the functionality to secure particular albums 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 Guide –
https://developer.android.com/training/data-storage/shared-preferences

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

3.Gson Java library tutorial –
http://www.vogella.com/tutorials/JavaLibrary-Gson/article.html

Continue Reading

Implementing Search Functionality in Phimpme Android Application.

In the Phimpme Android application, users are provided with options to perform various operations on the albums available such as move, creating a zip file of the album, rename an album and many more. However, one another useful functionality that has been added to the Phimpme Android application is the option to search albums. So in this post, I will be discussing the implementation of search functionality.

Step 1

Android framework provides developers with a search widget called SearchView that provides a user interface for the user to search a query and submit the request. So first setting up the widget in the action bar of the activity is required. The searchview widget can be added to the action bar as a menu item by adding the following lines in the XML menu resource file menu_albums.xml.

<item
  android:id=“@+id/search_action”
  android:title=“@string/search_menu”
  android:visible=“true”
  android:icon=“@drawable/ic_search_black_24dp”
  android:tint=“@color/white”
  app:actionViewClass=“android.support.v7.widget.SearchView”
  app:showAsAction=“always” />

Step 2

Now SearchView.OnQueryTextListener interface is used for initiating the search operation and listening to the callbacks for changes to the query text. For the purpose of listening to the querytext, two methods are used here both of which are listed below.

onQueryTextChanged(String Text) – This method is called every time the query text is changed by the user and returns a boolean value, false if SearchView should perform the default action of showing any suggestions and true if the action was handled by the listener.

onQueryTextSubmit(String query) – This method is called when the user submits a query which could be done with a key press on the keyboard or by pressing the submit button. It also returns a boolean value which is true if the query has been handled by the listener, otherwise false.

The code snippet for the implementation of the above mentioned interface is provided below.

searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
  @Override
  public boolean onQueryTextSubmit(String query) {
      return false;
  }

  @Override
  public boolean onQueryTextChange(String newText) {
      return searchTitle(newText);
  }
});

Step 3

In the final step, with the use of the onQueryTextChange method of the SearchView.onQueryTextListener interface the search operation and displaying the search results in the UI can be achieved. The onQueryTextChange method is called every time the search-query text changes. From the onQueryTextChange method, another method named searchTitle is invoked. Inside the searchTitle method the album names matching the search-query are searched from an Arraylist<Albums> containing all the albums displayed in the application. The albums obtained as a result of the search operation are then stored in another Arraylist<Album> which is thereafter passed as a parameter to the swapDataSet method of the AlbumsAdapter class to display the searched albums in the album view. The code snippet used for the above operations is provided below.

public boolean searchTitle(String newText) {
  if (!fromOnClick) {
      String queryText = newText;
      queryText = queryText.toLowerCase();
      final ArrayList<Album> newList = new ArrayList<>();
      for (Album album : albList) {
          String name = album.getName().toLowerCase();
          if (name.contains(queryText)) {
              newList.add(album);
          }
      }
      if(newList.isEmpty()){
          checkNoSearchResults(newText);
      }
      else{
          if(textView.getVisibility() == View.VISIBLE){
              textView.setVisibility(View.INVISIBLE);
          }
      }
      albumsAdapter.swapDataSet(newList);
  } else {
      fromOnClick = false;
  }
  return true;
}

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

The screenshot for displaying the search result in album view is provided below.

Resources

1.Android Developer Guide – https://developer.android.com/reference/android/widget/SearchView

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

3.Creating a search interface in Android – https://medium.com/@yugandhardcs21/creating-a-search-interface-in-android-dc1fd6a53b4

Continue Reading

Adding new test cases for increasing test coverage of Loklak Server

It is a good practice to have test cases covering major portion of actual code base. The idea was same to add new test cases in Loklak Server to increase its test coverage. The results were quite amazing with a significant increase of about 3% in total test coverage of the overall project. And about 80-100% increase in the test coverage of individual files for which tests have been written.

Integration Process

For integration, a total of 6 new test cases have been written:

  1. ASCIITest
  2. GeoLocationTest
  3. CommonPatternTest
  4. InstagramProfileScraperTest
  5. EventBriteCrawlerServiceTest
  6. LocationWiseTimeServiceTest

For increasing code coverage, Java docs have been written which increase the lines of code being covered.

Implementation

Basic implementation of adding a new test case is same. Here’s is an example of EventBriteCrawlerServiceTest implementation. This can be used as a reference for adding a new test case in the project.

Prerequisite: If the test file being written tests any additional external service (e.g. EventBriteCrawlerServiceTest tests any event being created on EventBrite platform) then, a corresponding new test service or test event should be written beforehand on the given platform. For EventBriteCrawlerServiceTest, a test-event has been created.

The given below steps will be followed for creating a new test case (EventBriteCrawlerServiceTest), assuming the prerequisite step has been done:

  • A new java file is generated in test/org/loklak/api/search/ as EventBriteCrawlerServiceTest.java.
  • Define package for the test file and import EventBriteCrawlerService.java file which has to be tested along with necessary packages and methods.

package org.loklak.api.search;

import org.loklak.api.search.EventBriteCrawlerService;
import org.loklak.susi.SusiThought;
import org.junit.Test;
import org.json.JSONArray;
import org.json.JSONObject;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNotNull;

 

  • Define the class and methods that need to be tested. It is a good idea to break the testing into several test methods rather than testing more features in a single method.

public class EventBriteCrawlerServiceTest {
  @Test
  public void apiPathTest() { }

  @Test
  public void eventBriteEventTest() { }

  @Test
  public void eventBriteOrganizerTest() { }

  @Test
  public void eventExtraDetailsTest() { }
}

 

  • Create an object of EventBriteCrawlerService in each test method.

EventBriteCrawlerService eventBriteCrawlerService = new EventBriteCrawlerService();

 

  • Call specific method of EventBriteCrawlerService that needs to be tested in each of the defined methods of test file and assert the actualresult to the expectedresult.

@Test
public void apiPathTest() {
    EventBriteCrawlerService eventBriteCrawlerService = 
        new EventBriteCrawlerService();

     assertEquals("/api/eventbritecrawler.json",
        eventBriteCrawlerService.getAPIPath());
}

 

  • For methods fetching an actual result (Integration tests) of event page, define and initialise an expected set of results and assert the expected result with the actual result by parsing the json result.

@Test
public void eventBriteOrganizerTest() {
    EventBriteCrawlerService eventBriteCrawlerService =
        new EventBriteCrawlerService();
    String eventTestUrl = "https://www.eventbrite.com/e/
        testevent-tickets-46017636991";
    SusiThought resultPage = eventBriteCrawlerService
        .crawlEventBrite(eventTestUrl);
    JSONArray jsonArray = resultPage.getData();
    JSONObject organizer_details =
        jsonArray.getJSONObject(1);
    String organizer_contact_info = organizer_details
        .getString("organizer_contact_info");
    String organizer_link = organizer_details
        .getString("organizer_link");
    String organizer_profile_link = organizer_details
        .getString("organizer_profile_link");
    String organizer_name = organizer_details
        .getString("organizer_name");

    assertEquals("https://www.eventbrite.com
        /e/testevent-tickets-46017636991
        #lightbox_contact",
        organizer_contact_info);
    assertEquals("https://www.eventbrite.com
        /e/testevent-tickets-46017636991
        #listing-organizer", organizer_link);
    assertEquals("", organizer_profile_link);
    assertEquals("aurabh Srivastava", organizer_name);
}

 

  • If the test file is testing the harvester, then import and add the test class in TestRunner.java file. e.g.

import org.loklak.harvester.TwitterScraperTest;

@RunWith(Suite.class)
@Suite.SuiteClasses({
  TwitterScraperTest.class
})

Testing

The last step will be to check if the test case passes with integration of other tests. The gradle will test the test case along with other tests in the project.

./gradlew build

Resources

Continue Reading

Option to exclude albums in Phimpme Android Application

In the Phimpme Android Application, users can perform various operations on the albums available such as move, creating a .zip file of the album, rename an album, delete the album and much more. However one another important functionality that has been implemented in the application is the option to exclude album/albums. In this post we will be discussing how we achieved the functionality to exclude albums in Phimpme Android Application.

Step 1

First we need to keep track of the albums selected by the user to exclude. This can be done by storing the selected albums in an Arraylist<Album> which can be referred later when required for the process of exclusion. The storing of the albums can be done with the help of following lines of code.

private int toggleSelectAlbum(int index) {
if (dispAlbums.get(index) != null) {
  dispAlbums.get(index).setSelected(!dispAlbums.get(index).isSelected());
  if (dispAlbums.get(index).isSelected()) selectedAlbums.add(dispAlbums.get(index));
  else selectedAlbums.remove(dispAlbums.get(index));
}
return index;
}

Step 2

After the selected albums are stored in an Arraylist<Album>, a function call of excludeSelectedAlbums() of HandlingAlbums class is done passing in Context as the parameter. In the method excludeSelectedAlbums() the selected albums are retrieved from the Arraylist one by one and another method excludeAlbum() is called with the album, context being passed as the parameters. The code snippet performing the above operation is provided below.

public void excludeSelectedAlbums(Context context) {
for (Album selectedAlbum : selectedAlbums)
  excludeAlbum(context, selectedAlbum);
clearSelectedAlbums();
}

Step 3

Thereafter an instance of class CustomsAlbumHelper is created and excludeAlbum() method is called with path of the album passed as parameter, and the selected album is removed from the list containing the currently displayed albums. Code snippets for the above operations are provided below.

private void excludeAlbum(Context context, Album a) {
CustomAlbumsHelper h = CustomAlbumsHelper.getInstance(context);
h.excludeAlbum(a.getPath());
dispAlbums.remove(a);
}

Step 4

Now in the excludeAlbum() method of CustomsAlbumHelper class a writable instance of albums_settings sqlite database is obtained. A check is performed if the current album is present in the albums table or not, if not then a row representing the current album is created. Thereafter the excluded value for the current album in the table is updated to integer 1 denoting that the album is excluded. The code snippet required for the above mentioned operations are provided below.

public void excludeAlbum(String path) {
  SQLiteDatabase db = this.getWritableDatabase();
  checkAndCreateAlbum(db, path);
  ContentValues values = new ContentValues();
  values.put(ALBUM_EXCLUDED, 1);
  db.update(TABLE_ALBUMS, values, ALBUM_PATH+“=?”, new String[]{    path});
  db.close();
}

This is how we have achieved the functionality of excluding albums in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android GitHub repository listed in the resources section below.

The screenshot for the display of the excluded albums is provided below.

Resources

  1. Android Developer Guide –https://developer.android.com/training/data-storage/sqlite.html
  1. Github-Phimpme Android Repository –https://github.com/fossasia/phimpme-android/
  1. Sqlite Operations Tutorial – https://www.androidhive.info/2011/11/android-sqlite-database-tutorial/
Continue Reading

Option to hide albums in Phimpme Android Application

In Phimpme Android Application, users can perform various operations on the albums available such as move, creating a .zip file for the album, delete the album, exclude an album, rename an album, pin an album to the top and more. However one another important functionality that has been added in the application is the option to hide  album/albums. So in this post I will be discussing how we achieved the functionality to hide albums in Phimpme Android Application.

Step 1

First we need to get the albums which are selected to be hidden. This can be done by storing the selected items in an Arraylist<Album> which will keep track of the users choice to hide albums. This can be achieved with the following lines of code.

private int toggleSelectAlbum(int index) {
if (dispAlbums.get(index) != null) {
  dispAlbums.get(index).setSelected(!dispAlbums.get(index).isSelected());
  if (dispAlbums.get(index).isSelected()) selectedAlbums.add(dispAlbums.get(index));
  else selectedAlbums.remove(dispAlbums.get(index));
}
return index;
}

Step 2

After storing of the albums to be hidden a function hideSelectedAlbums() is called with Context being passed as the parameter. Now inside this function we retrieve the albums looping through the Arraylist that stores the albums to be hidden and call another function hideAlbum() passing in the album and context as the parameters. The code snippet representing the above operation is given below.

public void hideSelectedAlbums(Context context) {
for (Album selectedAlbum : selectedAlbums)
  hideAlbum(selectedAlbum, context);
clearSelectedAlbums();
}

Step 3.

Now a .nomedia file is added to the directories/albums which are to be hidden and the android Media Scanner System service is triggered thereafter. As a result of the presence of the .nomedia file inside the directory the Media Scanner service won’t scan the particular directory/album thereby the album will not be picked up at the time of displaying the albums. The code snippet to perform the mentioned operation is provided below.

File dirName = new File(path);
File file = new File(dirName, “.nomedia”);
if (!file.exists()) {
try {
  FileOutputStream out = new FileOutputStream(file);
  out.flush();
  out.close();
  scanFile(context, new String[]{ file.getAbsolutePath() });
} catch (Exception e) {
  e.printStackTrace();
}
}

The screenshot for the display of hidden folders is provided below.

This is how we have achieved the functionality of hiding albums in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android GitHub repository listed in the resources section below.

Resources

  1. Android Developer Guide – https://developer.android.com/reference/android/media/MediaScannerConnection.html
  2. Github-Phimpme Android Repository –https://github.com/fossasia/phimpme-android/
  3. Hiding directories using .nomedia file – http://www.easycodeway.com/2016/08/hide-files-in-android-using-nomedia-file.html
Continue Reading

Displaying Image location Address In Phimpme Android Application

In Phimpme Android application one of the features available is to view the details of any image. The details consists of attributes including Date and time at which the image was captured, size of the image, title, path, EXIF data, description added to the image, location etc. However in the location attribute the location coordinates of the image as well as the location address can be displayed depending on the user’s preference. The process of obtaining the coordinates from address is called as Geocoding and obtaining string address from coordinates is called reverse Geocoding. So in this post I will be explaining how to implement set of strings denoting the address from the available coordinates.

Step 1

First we need to create an instance of the class Geocoder passing context and function Locale.getDefault() as the parameters.  The function of the attribute Locale.getdefault is provided below.

Locale.getDefault() – It returns the current value of the default locale for the current instance of the Java Virtual Machine. The Java Virtual Machine sets the default locale during startup based on the host environment.The code snippet to perform the above mentioned operation is given below.

Geocoder geocoder = new Geocoder(context, Locale.getDefault());

Step 2

Now a function call of getFromLocation() of the Geocoder class is done where we need to pass the Lattitude and Longitude values of the Location object as parameters. The lattitude and longitudes values can be obtained by the use of the Location object functions getLatitude() and getLongitude() respectively. The function getFromLocation() will return a list of Address objects which will contain the extracted addresses from the passed latitude and longitude values. We also need to pass a third parameter an integer value which will determine the maximum no of addresses to be returned. Here we have requested for a maximum of one address. The following code snippet is used to perform the desired function call.

try {
 List<Address> addressList = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);
} catch (IOException e) {
  e.printStackTrace();
}

Step 3

After obtaining the list of Address objects returned from the function call of getFromLocation() we will extract the first address from the list since we want a maximum of 1 address. The Address object will contain information like the address name, country, state, postal code etc. Now the set of strings describing the location can be retrieved with the help of the function getMaxAddressLineIndex() of Address class. The code snippets to perform the above mentioned operations is provided below.

ArrayList<String> addresslines = new ArrayList<String>();
Address address = addressList.get(0);
for(int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
  addresslines.add(address.getAddressLine(i));
}
details.put(context.getString(R.string.location), TextUtils.join(System.getProperty(“line.separator”),
      addresslines));

The screenshot displaying the location address is provided below.

This is how we have achieved the functionality of displaying location address in a set of strings from available coordinates in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android GitHub repository listed in the resources section below.

Resources

1.Android Developer Guide – https://developer.android.com/training/location/display-address.html

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

3.Address Class Guide- https://developer.android.com/reference/android/location/Address.html

 

Continue Reading

Option to Print Photos in the Phimpme Android Application

In the Phimpme Android application, users can perform various operations on the photos available such as copy, move, add the image to favourites collection, share the images with others, use it as covers, wallpapers and much more. However one another important functionality that has been added in the Phimpe Android application is printing of images. In this post we will be discussing about the implementation of the above mentioned functionality.

Step 1

First we need to create an instance of the class PrintHelper passing context as the constructor parameter which can be done with the following line of code.

PrintHelper photoPrinter = new PrintHelper(this);

Step 2

Now a  function call of setScalemode() is done where we require passing a parameter out of the two options SCALE_MODE_FIT and SCALE_MODE_FILL. The difference between the two options is explained below.

SCALE_MODE_FIT – This option sizes the image so that the whole image is displayed within the printable area of the page.

SCALE_MODE_FILLThis option scales the image so that it fills the entire printable area of the page. Choosing this setting means that some portion of the top and bottom, or left and right edges of the image is left out. This option is the default value if no scale mode is set.

Though neither of the scaling options alter the existing aspect ratio of the image, we are going with the latter of the two as the requirement here is to display the whole image in the printable area. The following code snippet is used to perform the desired function call.

photoPrinter.setScaleMode(PrintHelper.SCALE_MODE_FIT);

Step 3

After obtaining an instance of the class PrintHelper and calling the function setScalemode with the proper scale parameter, the path of the image to be printed is extracted and is passed in as a parameter to the decodefile function of the class BitmapFactory which has another parameter.

A Bitmap object is the return result of the operation performed by the function decodefile. The Bitmap object is thereafter passed in as a parameter to the printBitmap() function of the PrintHelper class along with a string attribute which will denote the file name of the printed photo. The code snippet to the above mentioned operations are given below.

Bitmap bitmap = BitmapFactory.decodeFile(getAlbum().getCurrentMedia().getPath(), new BitmapFactory.Options());
photoPrinter.printBitmap(getString(R.string.print), bitmap);

After the printbitmap() is called no further action is required from the side of the application. The Android system print interface appears where the users can select the printing options. The user can proceed to print the image or cancel the operation. If the user decides to proceed with the operation a print job is created and printing operation notification appears in the system navigation bar. The system print interface appearing is displayed below.

This is how we have achieved the functionality of printing images in the Phimpme Android application. To get the full source code, please refer to the Phimpme Android GitHub repository listed in the resources section below.

Resources

1.Android Developer Guide – https://developer.android.com/training/printing/index.html

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

3.PrintHelper Class Guide – https://developer.android.com/reference/android/support/v4/print/PrintHelper.html

 

Continue Reading

Compressing Albums in the Phimpme Android Application

The Phimpme Android application comes in with all the functionalities ranging from viewing images to taking photos, editing pictures  and sharing them with the world from within a single application without having to switch to or install other social media apps on your mobile phone. Apart from these basic functionalities, the Phimpme Android app also comes with additional features to enhance user experience like the ability to compress the whole album with a large number of photos so that it becomes easier to share them. In this post, I will be explaining how we achieved this functionality to compress the Albums.

Step 1

The first thing we need to do before compressing an album is to get all the paths of the images in that album and store it in an ArrayList<String> so that it can be used later for the compression process. This can be done using the code snippet provided below, it stores all the paths of the file in a particular folder whose name ends with .jpg

path = new ArrayList<>();
File folder = new File(getAlbums().getSelectedAlbum(0).getPath() + "/");
File[] fpath = folder.listFiles();
for(int i = 0; i < fpath.length; i++){
   if(fpath[i].getPath().endsWith(".jpg") ){
       path.add(fpath[i].getPath());
   }
}

Step 2

Since the compression is a heavy task, we can make use of an AsyncTask to run the task on the background thread so that the user experience is not at all hampered. In the onPreExecute method of the AsyncTask, we need to display the Notification that the compression of the particular album has started, for this we have made use of the Notification handler class that we have created in the Phimpme Android application to ease the process of displaying the notification and to avoid repetition of codes. The onPreExecute method of the AsyncTask is given below.

@Override
protected void onPreExecute() {
   super.onPreExecute();
   NotificationHandler.make(R.string.folder, R.string.zip_fol, R.drawable.ic_archive_black_24dp );
}

Step 3

On the doInBackground method of the AsyncTask, we run the process to compress the files one by one. For this we will make use of the ZipEntry class which is used to represent a zip file entry in Android/Java. First we will create a File with the .zip extension.  After this, we will make use of an object of the class ZipOutputStream as depicted in the code snippet provided below.

BufferedInputStream origin = null; 
FileOutputStream dest = new FileOutputStream(_zipFile); 
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest)); 
byte data[] = new byte[BUFFER];

After initializing the ZipOutPutStream object, we will put the zip entries in it by using the putNextEntry function of the class. To create a Zip entry of a file, we need to make use of for loop to generate the object of type ZipEntry and after that by using the putNextEntry function of the class, we will put the entries one by one as depicted in the code snippet given below.

for (int i = 0; i < path.size(); i++) {
FileInputStream fi = new FileInputStream(path.get(i));
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(path.get(i).substring(path.get(i).lastIndexOf("/") + 1));
out.putNextEntry(entry);

While preparing the Zip file, we will update the progress of the compression operation by making use of the Notification handler class.

This is how we have implemented the feature to compress the Albums in the Phimpme Android Application. To get the full source code for the same, please check the Phimpme Android GitHub repository listed on the resources below.

Resources

  1. StackOverflow – Compressing Files in Android – https://stackoverflow.com/questions/25562262/how-to-compress-files-into-zip-folder-in-android
  2. Blog – Compressing Files in Android programmatically – http://stacktips.com/tutorials/android/how-to-programmatically-zip-and-unzip-file-in-android
  3. GitHub – Phimpme Android Repository – https://github.com/fossasia/phimpme-android/
Continue Reading

Enhancing Rotation in Phimp.me using Horizontal Wheel View

Installation

To implement rotation of an image in Phimp.me,  we have implemented Horizontal Wheel View feature. It is a custom view for user input that models horizontal wheel controller. How did we include this feature using jitpack.io?

Step 1: 

The jitpack.io repository has to be added to the root build.gradle:

allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
}


Then, add the dependency to your module build.gradle:

compile 'com.github.shchurov:horizontalwheelview:0.9.5'


Sync the Gradle files to complete the installation.

Step 2: Setting Up the Layout

Horizontal Wheel View has to be added to the XML layout file as shown below:

<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2">

<com.github.shchurov.horizontalwheelview.HorizontalWheelView
android:id="@+id/horizontalWheelView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toStartOf="@+id/rotate_apply"
android:padding="5dp"
app:activeColor="@color/accent_green"
app:normalColor="@color/black" />

</FrameLayout>


It has to be wrapped inside a Frame Layout to give weight to the view.
To display the angle by which the image has been rotated, a simple text view has to be added just above it.

<TextView
android:id="@+id/tvAngle"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:textColor="@color/black"
android:textSize="14sp" />

Step 3: Updating the UI

First, declare and initialise objects of HorizontalWheelView and TextView.

HorizontalWheelView horizontalWheelView = (HorizontalWheelView) findViewById(R.id.horizontalWheelView);
TextView tvAngle= (TextView) findViewById(R.id.tvAngle);

 

Second, set up listener on the HorizontalWheelView and update the UI accordingly.

horizontalWheelView.setListener(new HorizontalWheelView.Listener() {
@Override
public void onRotationChanged(double radians) {
updateText();
updateImage();
}
});


updateText()
updates the angle and updateImage() updates the image to be rotated. The following functions have been defined below:

private void updateText() {
String text = String.format(Locale.US, "%.0f°", horizontalWheelView.getDegreesAngle());
tvAngle.setText(text);
}

private void updateImage() {
int angle = (int) horizontalWheelView.getDegreesAngle();
//Code to rotate the image using the variable 'angle'
rotatePanel.rotateImage(angle);
}


rotateImage()
is a method of ‘rotatePanel’ which is an object of RotateImageView, a custom view to rotate the image.

Let us have a look at some part of the code inside RotateImageView.

private int rotateAngle;


‘rotateAngle’ is a global variable to hold the angle by which image has to be rotated.

public void rotateImage(int angle) {
rotateAngle = angle;
this.invalidate();
}


The method invalidate() is used to trigger UI refresh and every time UI is refreshed, the draw() method is called.
We have to override the draw() method and write the main code to rotate the image in it.

The draw() method is defined below:

@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (bitmap == null)
return;
maxRect.set(0, 0, getWidth(), getHeight());// The maximum bounding rectangle

calculateWrapBox();
scale = 1;
if (wrapRect.width() > getWidth()) {
scale = getWidth() / wrapRect.width();
}

canvas.save();
canvas.scale(scale, scale, canvas.getWidth() >> 1,
canvas.getHeight() >> 1);
canvas.drawRect(wrapRect, bottomPaint);
canvas.rotate(rotateAngle, canvas.getWidth() >> 1,
canvas.getHeight() >> 1);
canvas.drawBitmap(bitmap, srcRect, dstRect, null);
canvas.restore();
}

private void calculateWrapBox() {
wrapRect.set(dstRect);
matrix.reset(); // Reset matrix is ​​a unit matrix
int centerX = getWidth() >> 1;
int centerY = getHeight() >> 1;
matrix.postRotate(rotateAngle, centerX, centerY); // After the rotation angle
matrix.mapRect(wrapRect);
}

 

And here you go:

Resources

Refer to Github- Horizontal Wheel View for more functions and for a sample application.

Continue Reading
Close Menu
%d bloggers like this: