org.fs.galleon.presenters.ToolsFragmentPresenter.java Source code

Java tutorial

Introduction

Here is the source code for org.fs.galleon.presenters.ToolsFragmentPresenter.java

Source

/*
 * Galleon Copyright (C) 2016 Fatih.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fs.galleon.presenters;

import android.Manifest;
import android.app.Activity;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.util.Log;
import android.view.MenuItem;
import com.googlecode.leptonica.android.AdaptiveMap;
import com.googlecode.leptonica.android.GrayQuant;
import com.googlecode.leptonica.android.ImageFormat;
import com.googlecode.leptonica.android.MorphApp;
import com.googlecode.leptonica.android.Pix;
import com.googlecode.leptonica.android.ReadFile;
import com.googlecode.leptonica.android.Rotate;
import com.googlecode.leptonica.android.Skew;
import com.jakewharton.rxbinding.view.RxView;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java8.util.stream.Collectors;
import java8.util.stream.IntStreams;
import java8.util.stream.StreamSupport;
import org.fs.common.AbstractPresenter;
import org.fs.common.BusManager;
import org.fs.common.ThreadManager;
import org.fs.exception.AndroidException;
import org.fs.galleon.GalleonApplication;
import org.fs.galleon.R;
import org.fs.galleon.commons.SimpleCallbackImp;
import org.fs.galleon.entities.ImageEntity;
import org.fs.galleon.events.SingleImageSelectionEvent;
import org.fs.galleon.events.TitleEvent;
import org.fs.galleon.managers.IFileManager;
import org.fs.galleon.utils.Images;
import org.fs.galleon.views.IToolsFragmentView;
import org.fs.util.APICompats;
import org.fs.util.Collections;
import org.fs.util.ObservableList;
import org.fs.util.StringUtility;
import org.pdf.haru.PdfCore;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

public class ToolsFragmentPresenter extends AbstractPresenter<IToolsFragmentView>
        implements IToolsFragmentPresenter, SimpleCallbackImp.SwipeCallback {

    private final static int PERMISSION_READ_STORAGE = 0x09;

    private final static int REQUEST_TAKE_PHOTO = 0x01;
    private final static int REQUEST_PICK_GALLERY = 0x02;

    private final static String FILE_PDF_EXTENSION = ".pdf";//need to create pdf extension here, might be required for new native api
    private final static String FILE_EXTENSION = ".jpg";
    private final static String FILE_PREFIX = "JPEG";
    private final static String FILE_STAMP = "yyyyMMdd_HHmmssSSS";

    private final static String IMAGE_TYPE = "image/*";

    private final static String GRANT_PERMISSION = "org.fs.galleon.fileprovider";

    private final static SimpleDateFormat timeStamp = new SimpleDateFormat(FILE_STAMP, Locale.getDefault());

    private final static String KEY_TEMP_TAKEN_PHOTO = "temp.taken.photo.file";
    private final static String KEY_PDF_FILES = "pdf.files";
    private final static String KEY_IMAGE_ENTITIES = "image.entities";

    private Subscription viewProcessSub;
    private Subscription eventListener;

    private File tempTakenPhoto;
    private ObservableList<File> pdfs;
    private List<ImageEntity> images;

    private final IFileManager fileManager;

    public ToolsFragmentPresenter(IToolsFragmentView view, IFileManager fileManager) {
        super(view);
        this.fileManager = fileManager;
    }

    @Override
    public void onStart() {
        if (view.isAvailable()) {
            String titleStr = view.getContext().getString(R.string.titleNavigationTools);
            BusManager.send(new TitleEvent(titleStr));
            if (!Collections.isNullOrEmpty(images)) {
                view.clearAllAdapters();//it will overlap same date over and over again if we do not clear previous
            }
            if (!Collections.isNullOrEmpty(images)) {
                view.bindRecyclerAdapter(images);
            }
            if (pdfs == null) {
                pdfs = new ObservableList<>();
            }
        }
        eventListener = BusManager.add((event) -> {
            if (event instanceof SingleImageSelectionEvent) {
                SingleImageSelectionEvent e = (SingleImageSelectionEvent) event;
                if (e.entity() != null) {
                    ImageEntity entity = e.entity();
                    File file = new File(entity.getImageUri().getPath());
                    String f = file.getName();
                    final String fx = f.substring(0, f.lastIndexOf('.'));
                    if (pdfs != null) {
                        StreamSupport.stream(pdfs).filter(x -> {
                            String filename = x.getName();
                            filename = filename.substring(0, filename.lastIndexOf('.'));
                            return filename.equalsIgnoreCase(fx);
                        }).forEach(x -> {
                            if (view.isAvailable()) {
                                view.setFileTitle(x.getName());
                                view.setPdfFile(Uri.fromFile(x));
                            }
                        });
                    }
                }
            }
        });
        dispatchAskPermissionsIfNeeded();
    }

    @Override
    public void onStop() {
        if (viewProcessSub != null) {
            viewProcessSub.unsubscribe();
            viewProcessSub = null;
        }
        if (eventListener != null) {
            BusManager.remove(eventListener);
            eventListener = null;
        }
    }

    @Override
    public void restoreState(Bundle restoreState) {
        if (restoreState != null) {
            if (restoreState.containsKey(KEY_TEMP_TAKEN_PHOTO)) {
                String absolutePath = restoreState.getString(KEY_TEMP_TAKEN_PHOTO);
                if (!StringUtility.isNullOrEmpty(absolutePath)) {
                    tempTakenPhoto = new File(absolutePath);
                }
            }
            if (restoreState.containsKey(KEY_IMAGE_ENTITIES)) {
                images = restoreState.getParcelableArrayList(KEY_IMAGE_ENTITIES);
            }
            if (restoreState.containsKey(KEY_PDF_FILES)) {
                List<String> absolutePaths = restoreState.getStringArrayList(KEY_PDF_FILES);
                if (!Collections.isNullOrEmpty(absolutePaths)) {
                    List<File> fileList = StreamSupport.stream(absolutePaths).map(File::new)
                            .collect(Collectors.toList());
                    pdfs.addAll(fileList);
                }
            }
        }
    }

    @Override
    public void storeState(Bundle storeState) {
        if (tempTakenPhoto != null) {
            storeState.putString(KEY_TEMP_TAKEN_PHOTO, tempTakenPhoto.getAbsolutePath());
        }
        if (!Collections.isNullOrEmpty(images)) {
            storeState.putParcelableArrayList(KEY_IMAGE_ENTITIES, new ArrayList<>(images));
        }
        if (!Collections.isNullOrEmpty(pdfs)) {
            List<String> absolutePaths = StreamSupport.stream(pdfs).map(File::getAbsolutePath)
                    .collect(Collectors.toList());
            storeState.putStringArrayList(KEY_PDF_FILES, new ArrayList<>(absolutePaths));
        }
    }

    @Override
    public void observeRecyclerView(RecyclerView viewRecycler) {
        ItemTouchHelper touchHelper = new ItemTouchHelper(SimpleCallbackImp.swipeEndHelper(this));
        touchHelper.attachToRecyclerView(viewRecycler);
    }

    @Override
    public void observeProcessView(FloatingActionButton viewProcess) {
        viewProcessSub = RxView.clicks(viewProcess).subscribe(click -> {
            if (view.isAvailable()) {
                if (Collections.isNullOrEmpty(images))
                    return;
                view.showProgress();
                dispatchImageToPdfProcess().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                        .subscribe(data -> {
                            if (view.isAvailable()) {
                                pdfs.addAll(data);
                            }
                        }, this::log, view::hideProgress);
            }
        });
    }

    @Override
    public void activityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_PICK_GALLERY) {
            if (resultCode == Activity.RESULT_OK) {
                ClipData clipData = null;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    clipData = data.getClipData();
                }
                if (clipData != null) {
                    //multi choice
                    List<ImageEntity> items = IntStreams.range(0, clipData.getItemCount())
                            .mapToObj(clipData::getItemAt)
                            .map(item -> Images.getPath(view.getContext(), item.getUri()))
                            .map(xPath -> new ImageEntity.Builder().imageUri(Uri.fromFile(new File(xPath))).build())
                            .collect(Collectors.toList());

                    createIfImageListNotExists();
                    if (!Collections.isNullOrEmpty(items)) {
                        images.addAll(items);
                    }

                } else {
                    //single choice
                    Uri uri = data.getData();
                    if (uri != null) {
                        String path = Images.getPath(view.getContext(), uri);
                        ImageEntity entity = new ImageEntity.Builder().imageUri(Uri.fromFile(new File(path)))
                                .build();
                        createIfImageListNotExists();
                        images.add(entity);
                    }
                }
            }
        } else if (requestCode == REQUEST_TAKE_PHOTO) {
            if (resultCode == Activity.RESULT_OK) {
                if (tempTakenPhoto != null && tempTakenPhoto.exists()) {
                    ImageEntity entity = new ImageEntity.Builder().imageUri(Uri.fromFile(tempTakenPhoto)).build();
                    //I might need to use orientation data and etc.
                    createIfImageListNotExists();//create if its null
                    if (view.isAvailable()) {
                        images.add(entity);
                    }
                    tempTakenPhoto = null;//clear it now
                }
            }
        }
    }

    @Override
    public boolean optionsMenuSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.actionTakePhoto: {
            dispatchTakePictureIntent();
            return true;
        }
        case R.id.actionPickGallery: {
            dispatchSelectGalleryIntent();
            return true;
        }
        case R.id.actionClearAll: {
            dispatchClearAll();
            return true;
        }
        default:
            return false;
        }
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        if (view.isAvailable()) {
            int adapterPosition = viewHolder.getAdapterPosition();
            ImageEntity entity = view.removeAt(viewHolder.getAdapterPosition());
            String titleStr = view.getContext().getString(R.string.titleDeleteEntity);
            String okStr = view.getContext().getString(android.R.string.ok);
            titleStr = String.format(Locale.getDefault(), titleStr, okStr, toEntityName(entity));
            view.showError(titleStr, okStr, (v) -> view.addAt(adapterPosition, entity));
        }
    }

    @Override
    public void requestPermissions(int requestCode, String[] permissions, int[] results) {
        //I do not require need for this
    }

    @Override
    protected String getClassTag() {
        return ToolsFragmentPresenter.class.getSimpleName();
    }

    @Override
    protected boolean isLogEnabled() {
        return GalleonApplication.isDebug();
    }

    private Observable<File> createIfNotExists() {
        return Observable.just(toImageFileName()).flatMap(fileName -> {
            if (view.isAvailable()) {
                try {
                    final Context context = view.getContext();
                    File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
                    File temp = new File(storageDir, fileName + FILE_EXTENSION);
                    boolean success = temp.createNewFile();
                    if (success) {
                        log(Log.INFO, String.format(Locale.ENGLISH, "%s is temp created", temp.getName()));
                    }
                    return Observable.just(temp);
                } catch (IOException ioError) {
                    throw new AndroidException(ioError);
                }
            }
            return Observable.empty();
        });
    }

    private void dispatchTakePictureIntent() {
        createIfNotExists().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
                .subscribe(file -> {
                    if (file.exists()) {
                        log(Log.INFO, String.format(Locale.ENGLISH, "%s is temp file.", file.getAbsolutePath()));
                    }
                    if (view.isAvailable()) {
                        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                        if (intent.resolveActivity(view.getContext().getPackageManager()) != null) {
                            this.tempTakenPhoto = file;//set it in property
                            Uri uri = FileProvider.getUriForFile(view.getContext(), GRANT_PERMISSION, file);
                            intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
                            //fileProvider requires gran permission to others access that uri
                            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
                                final Context context = view.getContext();
                                List<ResolveInfo> infos = context.getPackageManager().queryIntentActivities(intent,
                                        PackageManager.MATCH_DEFAULT_ONLY);
                                if (infos != null) {
                                    StreamSupport.stream(infos).filter(x -> x.activityInfo != null)
                                            .map(x -> x.activityInfo.packageName).forEach(pack -> {
                                                context.grantUriPermission(pack, uri,
                                                        Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                                                                | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                                            });
                                }
                            }
                            view.startActivityForResult(intent, REQUEST_TAKE_PHOTO);
                        } else {
                            view.showError("You need to install app that can capture photo.");
                        }
                    }
                }, this::log);
    }

    private void dispatchClearAll() {
        if (view.isAvailable()) {
            //if we have items that needs to be cleared but before that icon is black
            if (!Collections.isNullOrEmpty(pdfs) || !Collections.isNullOrEmpty(images)) {
                //clean adapters
                view.clearAllAdapters();
                //delete those from file system
                StreamSupport.stream(pdfs).filter(File::exists).forEach(File::delete);
                pdfs.clear();
                images.clear();
                tempTakenPhoto = null;
            }
        }
    }

    private Observable<List<File>> dispatchImageToPdfProcess() {
        return Observable.just(images).flatMap(Observable::from).map(uri -> new File(uri.getImageUri().getPath()))
                .map(file -> {
                    File toolsDirectory = fileManager.toolsDirectory();
                    if (toolsDirectory == null) {
                        ThreadManager.runOnUiThread(() -> {
                            if (view.isAvailable()) {
                                view.showError(
                                        "You should picked a tools directory for working with img to pdf conversions.");
                            }
                        });
                        return null;
                    }
                    String newFileName = file.getName();
                    newFileName = newFileName.substring(0, newFileName.lastIndexOf('.'));
                    File jpegFile = new File(fileManager.toolsDirectory(), newFileName + FILE_EXTENSION);
                    File pdfFile = new File(fileManager.toolsDirectory(), newFileName + FILE_PDF_EXTENSION);

                    Pix pixs = ReadFile.readFile(file);
                    pixs = AdaptiveMap.backgroundNormSimple(pixs);
                    pixs = pixs.convertRGBToGray();
                    float angle = Skew.findSkew(pixs);
                    pixs = Rotate.rotate(pixs, angle, true, true);
                    pixs = MorphApp.pixTophat(pixs);
                    pixs = MorphApp.pixInvert(pixs);
                    pixs = GrayQuant.pixGammaRTC(pixs);
                    pixs = GrayQuant.pixThresholdToBinary(pixs);
                    pixs.write(jpegFile, ImageFormat.JfifJpg);

                    PdfCore.createFromJpeg(jpegFile, pixs.getWidth(), pixs.getHeight(), pdfFile);
                    if (pdfFile.exists()) {
                        log(Log.WARN, String.format(Locale.ENGLISH, "%s named file has size of %d",
                                pdfFile.getName(), pdfFile.length()));
                    }
                    pixs.recycle();
                    return pdfFile;
                }).filter(f -> !StringUtility.isNullOrEmpty(f)).toList();
    }

    private void dispatchSelectGalleryIntent() {
        if (view.isAvailable()) {
            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
            intent.setType(IMAGE_TYPE);
            if (APICompats.isApiAvailable(Build.VERSION_CODES.JELLY_BEAN_MR2)) {
                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
            }
            view.startActivityForResult(
                    Intent.createChooser(intent, view.getContext().getString(R.string.app_name)),
                    REQUEST_PICK_GALLERY);
        }
    }

    private void dispatchAskPermissionsIfNeeded() {
        if (view.isAvailable()) {
            if (ContextCompat.checkSelfPermission(view.getContext(),
                    Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                if (view.shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
                    AlertDialog dialog = new AlertDialog.Builder(view.getContext()).setCancelable(true)
                            .setTitle(R.string.titleReadImagesPermission)
                            .setMessage(R.string.titleReadImagesPermissionMessage)
                            .setNegativeButton(android.R.string.cancel,
                                    (d, w) -> log(String.format(Locale.ENGLISH, "%d is (negative) selected", w)))
                            .setPositiveButton(android.R.string.ok, (d, w) -> {
                                if (view.isAvailable()) {
                                    view.requestPermissionsCompat(
                                            new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
                                            PERMISSION_READ_STORAGE);
                                }
                            }).create();
                    dialog.show();
                } else {
                    view.requestPermissionsCompat(new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
                            PERMISSION_READ_STORAGE);
                }
            }
        }
    }

    private String toImageFileName() {
        return String.format(Locale.ENGLISH, "%s_%s", FILE_PREFIX, timeStamp.format(new Date()));
    }

    private String toEntityName(ImageEntity entity) {
        String path = entity.getImageUri().getPath();
        return path.substring(path.lastIndexOf("/"));
    }

    private void createIfImageListNotExists() {
        if (images == null) {
            images = new ArrayList<>();
        }
    }
}