public class Util {

    public static final int PERMISSION_REQUEST_STORAGE = 43;
    public static final int PERMISSION_REQUEST_FINGERPRINT=35;
    public static final String FINGERPRINT_KEY="cluttr_fingerprint";
    public static final String PASSWORD_KEY="cluttr_password";
    public static final String LOG_TAG = "Cluttr";
    public static final String ALBUM_PASS_KEY="album_integer";
    public static final String MEDIA_PASS_KEY="media_integer";
    public static final String URI_PASS_KEY="uri_key";
    public static final String CACHE_NAME="/cluttr.cache";
    public static final int VIDEO_ACTIVITY_VIEW=5984;
    public static final String BILLING_KEY = "";
    public static final List<String> DEFAULT_EXCLUDED = Collections.singletonList("/storage/emulated/0/Android");

    public static class Fragment {
        public static final String FRAGMENT_ALBUM_TAG="fragment_album";
        public static final String FRAGMENT_ALBUM_DETAIL_TAG="fragment_album_detail";
        public static final String FRAGMENT_MEDIA_DETAIL_TAG="fragment_media_detail";
        public static final String FRAGMENT_PERM_REQUEST_TAG="fragment_grant";
    }

    public static class IO {
        public static String humanReadableByteCount(long bytes, boolean si) {
            int unit = si ? 1000 : 1024;
            if (bytes < unit) return bytes + " B";
            int exp = (int) (Math.log(bytes) / Math.log(unit));
            String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");
            return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
        }
    }

    public static class ViewUtils{
        public static void colorizeToolbar(Toolbar toolbarView, int toolbarIconsColor) {
            final PorterDuffColorFilter colorFilter = new PorterDuffColorFilter(toolbarIconsColor, PorterDuff.Mode.MULTIPLY);
            for(int i = 0; i < toolbarView.getChildCount(); i++) {
                final View v = toolbarView.getChildAt(i);
                if(v instanceof ImageButton) {
                    ((ImageButton)v).getDrawable().setColorFilter(colorFilter);
                }
                if(v instanceof ActionMenuView) {
                    for(int j = 0; j < ((ActionMenuView)v).getChildCount(); j++) {
                        final View innerView = ((ActionMenuView)v).getChildAt(j);
                        if(innerView instanceof ActionMenuItemView) {
                            int drawablesCount = ((ActionMenuItemView)innerView).getCompoundDrawables().length;
                            for(int k = 0; k < drawablesCount; k++) {
                                if(((ActionMenuItemView)innerView).getCompoundDrawables()[k] != null) {
                                    final int finalK = k;
                                    innerView.post(new Runnable() {
                                        @Override
                                        public void run() {
                                            ((ActionMenuItemView) innerView).getCompoundDrawables()[finalK].setColorFilter(colorFilter);
                                        }
                                    });
                                }
                            }
                        }
                    }
                }
            }
        }

        public static void showChangelogDialog(Context context) {
            ActionMessageDialog d = new ActionMessageDialog(context);
            d.setTitleText(context.getResources().getString(R.string.changelog));
            d.setMsgText(context.getResources().getString(R.string.changelog_text));
            d.setNegativeButtonMsg(context.getResources().getString(android.R.string.ok));
            d.show();
        }
    }

    public static class Animations {
        public static void createReveal(View view) {
            view.setVisibility(View.VISIBLE);
            if (Build.VERSION.SDK_INT>20) {
                int cx = view.getMeasuredWidth() / 2;
                int cy = view.getMeasuredHeight() / 2;
                int finalRadius = Math.max(view.getWidth(), view.getHeight()) / 2;
                Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, 0, finalRadius);
                anim.start();
            } else {
                Animation anim = new AlphaAnimation(0,1);
                anim.setDuration(300);
                view.startAnimation(anim);
            }

        }

        public static void createHide(final View view) {
            if (Build.VERSION.SDK_INT>20) {
                int cx = view.getMeasuredWidth() / 2;
                int cy = view.getMeasuredHeight() / 2;
                int finalRadius = Math.max(view.getWidth(), view.getHeight()) / 2;
                Animator anim = ViewAnimationUtils.createCircularReveal(view, cx, cy, finalRadius, 0);
                anim.addListener(new Animator.AnimatorListener() {
                    @Override
                    public void onAnimationStart(Animator animator) {

                    }

                    @Override
                    public void onAnimationEnd(Animator animator) {
                        view.setVisibility(View.GONE);
                    }

                    @Override
                    public void onAnimationCancel(Animator animator) {

                    }

                    @Override
                    public void onAnimationRepeat(Animator animator) {

                    }
                });
                anim.start();
            } else {
                Animation anim = new AlphaAnimation(1,0);
                anim.setDuration(300);
                anim.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {

                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        view.setVisibility(View.GONE);
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {

                    }
                });
                view.startAnimation(anim);
            }

        }
    }

    public static class ContentResolverHelper {

        public static String getPath(final Context context, final Uri uri) {

            final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

            // DocumentProvider
            if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
                // ExternalStorageProvider
                if (isExternalStorageDocument(uri)) {
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];

                    if ("primary".equalsIgnoreCase(type)) {
                        return Environment.getExternalStorageDirectory() + "/" + split[1];
                    }

                    // TODO handle non-primary volumes
                }
                // DownloadsProvider
                else if (isDownloadsDocument(uri)) {

                    final String id = DocumentsContract.getDocumentId(uri);
                    final Uri contentUri = ContentUris.withAppendedId(
                            Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                    return getDataColumn(context, contentUri, null, null);
                }
                // MediaProvider
                else if (isMediaDocument(uri)) {
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];

                    Uri contentUri = null;
                    if ("image".equals(type)) {
                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                    } else if ("video".equals(type)) {
                        contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                    } else if ("audio".equals(type)) {
                        contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                    }

                    final String selection = "_id=?";
                    final String[] selectionArgs = new String[]{
                            split[1]
                    };

                    return getDataColumn(context, contentUri, selection, selectionArgs);
                }
            }
            // MediaStore (and general)
            else if ("content".equalsIgnoreCase(uri.getScheme())) {
                return getDataColumn(context, uri, null, null);
            }
            // File
            else if ("file".equalsIgnoreCase(uri.getScheme())) {
                return uri.getPath();
            }

            return null;
        }

        /**
         * Get the value of the data column for this Uri. This is useful for
         * MediaStore Uris, and other file-based ContentProviders.
         *
         * @param context       The context.
         * @param uri           The Uri to query.
         * @param selection     (Optional) Filter used in the query.
         * @param selectionArgs (Optional) Selection arguments used in the query.
         * @return The value of the _data column, which is typically a file path.
         */
        public static String getDataColumn(Context context, Uri uri, String selection,
                                           String[] selectionArgs) {

            Cursor cursor = null;
            final String column = "_data";
            final String[] projection = {
                    column
            };

            try {
                cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                        null);
                if (cursor != null && cursor.moveToFirst()) {
                    final int column_index = cursor.getColumnIndexOrThrow(column);
                    return cursor.getString(column_index);
                }
            } finally {
                if (cursor != null)
                    cursor.close();
            }
            return null;
        }


        /**
         * @param uri The Uri to check.
         * @return Whether the Uri authority is ExternalStorageProvider.
         */
        public static boolean isExternalStorageDocument(Uri uri) {
            return "com.android.externalstorage.documents".equals(uri.getAuthority());
        }

        /**
         * @param uri The Uri to check.
         * @return Whether the Uri authority is DownloadsProvider.
         */
        public static boolean isDownloadsDocument(Uri uri) {
            return "com.android.providers.downloads.documents".equals(uri.getAuthority());
        }

        /**
         * @param uri The Uri to check.
         * @return Whether the Uri authority is MediaProvider.
         */
        public static boolean isMediaDocument(Uri uri) {
            return "com.android.providers.media.documents".equals(uri.getAuthority());
        }
    }
} public class EditActivity extends ColorfulActivity {

    public static final String IMAGE_URI_KEY="IMAGE_URI";
    private String imageUri;

    @BindView(R.id.cropImageView) CropImageView cropImageView;
    @BindView(R.id.edit_toolbar) Toolbar toolbar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_edit);
        ButterKnife.bind(this);

        toolbar.setTitle(R.string.edit);
        toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_48px);
        toolbar.inflateMenu(R.menu.image_edit_toolbar);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.rotate_left: {
                        cropImageView.rotateImage(-90);
                        break;
                    }case R.id.rotate_right: {
                        cropImageView.rotateImage(90);
                        break;
                    }case R.id.edit_save: {
                        saveBitmap();
                        Toast.makeText(EditActivity.this, R.string.saved, Toast.LENGTH_LONG).show();
                        finish();
                        break;
                    }
                }
                return false;
            }
        });

        imageUri = getIntent().getExtras().getString(IMAGE_URI_KEY);

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        Bitmap bitmap = BitmapFactory.decodeFile(imageUri, options);

        cropImageView.setImageBitmap(bitmap);
    }

    private void saveBitmap() {
        Bitmap bmp = cropImageView.getCroppedImage();
        String filename=imageUri.substring(0, imageUri.lastIndexOf("."));
        filename+="_edit.png";
        System.out.println(imageUri);

        FileOutputStream out = null;
        try {
            out = new FileOutputStream(filename);
            bmp.compress(Bitmap.CompressFormat.PNG, 100, out); // bmp is your Bitmap instance
            // PNG is a lossless format, the compression factor (100) is ignored
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
} public class MainActivity extends ColorfulActivity implements NavigationView.OnNavigationItemSelectedListener, FingerprintDialog.Callback {

    @BindView(R.id.nav_main) protected NavigationView navMenu;
    @BindView(R.id.nav_footer) protected NavigationView navFooter;
    @BindView(R.id.drawer_layout) protected DrawerLayout mDrawer;

    private boolean PICK_INTENT=false;
    private AuthenticationPassedCallback callback;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        navFooter.setNavigationItemSelectedListener(this);

        if (savedInstanceState == null) {
            Media.init(this, false);
            lockDrawer(true);

            if (requestPerms()) {
                if (getIntent().getAction().equals(Intent.ACTION_GET_CONTENT) || getIntent().getAction().equals(Intent.ACTION_PICK)) {
                    PICK_INTENT = true;
                    clearAndLoadFragments(new AlbumFragment());
                } else {
                    Log.d(Util.LOG_TAG, "Starting on intent " + getIntent().getAction());
                    clearAndLoadFragments(new AlbumFragment());
                }
                if (Cluttr.getApplication().isNewRelease()) {
                    Util.ViewUtils.showChangelogDialog(this);
                }

                Log.d(Util.LOG_TAG, "First start, indexing media");
                MediaLoader.init()
                        .subscribe(new Action1<Boolean>() {
                            @Override
                            public void call(Boolean aBoolean) {
                                //Stub
                            }
                        });
            }
        }


    }

    public void toggleTransparentNav(boolean transparent) {
        if (transparent) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        } else {
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        }
    }

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.settings:
                Intent sIntent = new Intent(this,SettingsActivity.class);
                startActivity(sIntent);
                return false;
            case R.id.about:
                Intent aIntent = new Intent(this,AboutActivity.class);
                startActivity(aIntent);
                return false;
            case R.id.donate_footer:
                startActivity(new Intent(this,DonateActivity.class));
                return false;
        }
        return false;
    }

    public void openDrawer() {
        mDrawer.openDrawer(Gravity.LEFT);
    }

    public boolean getPickIntent() {
        return PICK_INTENT;
    }

    public void lockDrawer(boolean lock) {
        if (lock) {
            mDrawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
        } else {
            mDrawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
        }
    }

    private void clearAndLoadFragments(BaseFragment frag) {
        //TODO: POP ENTIRE BACKSTACK AND CLEAR ALL FRAGMENTS
        lockDrawer(false);

        Fragment fragment = getSupportFragmentManager().findFragmentByTag(Util.Fragment.FRAGMENT_PERM_REQUEST_TAG);
        if(fragment != null)
            getSupportFragmentManager().beginTransaction().remove(fragment).commit();
        fragment = getSupportFragmentManager().findFragmentByTag(Util.Fragment.FRAGMENT_ALBUM_TAG);
        if(fragment != null)
            getSupportFragmentManager().beginTransaction().remove(fragment).commit();

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.main_content, frag, Util.Fragment.FRAGMENT_ALBUM_TAG);
        transaction.commit();

    }

    public boolean requestPerms() {
        if ((ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
                || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            Log.d(Util.LOG_TAG, "Requesting I/O permissions");
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE}, Util.PERMISSION_REQUEST_STORAGE);
            return false;
        } else {
            Log.d(Util.LOG_TAG, "Permissions already granted, loading media");
            return true;
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode==Util.PERMISSION_REQUEST_STORAGE
                && grantResults.length>1
                && grantResults[0]==PackageManager.PERMISSION_GRANTED
                && grantResults[1]==PackageManager.PERMISSION_GRANTED) {
            Log.d(Util.LOG_TAG, "Permissions granted, recreating activity");
            Cluttr.getApplication().restart();
            finish();
        } else if (requestCode==Util.PERMISSION_REQUEST_FINGERPRINT) {
            Digitus.get().handleResult(requestCode, permissions, grantResults);
        } else {
            Log.d(Util.LOG_TAG, "Permissions denied, showing grant fragment");
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.replace(R.id.main_content,new GrantFragment(),Util.Fragment.FRAGMENT_ALBUM_TAG);
            transaction.commit();
        }
    }

    public void openFingerprintDialog(AuthenticationPassedCallback callback) {
        this.callback=callback;
        FingerprintDialog.show(this, Util.FINGERPRINT_KEY, Util.PERMISSION_REQUEST_FINGERPRINT);
    }

    @Override
    public void onFingerprintDialogAuthenticated() {
        if (callback!=null) {
            callback.onAuthenticationPassed();
        }
    }

    @Override
    public void onFingerprintDialogVerifyPassword(FingerprintDialog dialog, final String password) {
        dialog.notifyPasswordValidation(password.equals(PreferenceManager.getDefaultSharedPreferences(this).getString(Util.PASSWORD_KEY,null)));
    }

    @Override
    public void onFingerprintDialogStageUpdated(FingerprintDialog dialog, FingerprintDialog.Stage stage) {
        Log.d(Util.LOG_TAG, "Fingerprint stage: " + stage.name());
    }

    @Override
    public void onFingerprintDialogCancelled() {
        Toast.makeText(this, "Authentication cancelled", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode==Util.VIDEO_ACTIVITY_VIEW) {
            setResult(RESULT_CANCELED);
            finish();
        }
    }

    public interface AuthenticationPassedCallback {
        void onAuthenticationPassed();
    }
} public class ViewActivity extends ColorfulActivity implements View.OnClickListener, Action1<Void>, Toolbar.OnMenuItemClickListener, View.OnSystemUiVisibilityChangeListener, ViewPager.OnPageChangeListener, MediaViewAdapter.SizeCallback {
    private MediaViewAdapter adapter;
    private String media;
    private Album album;
    private boolean showToolbar=true;
    private String path=null;
    private Subscription timeoutSubscription=null;
    private BottomSheetBehavior bottomSheetBehavior;
    private boolean operationInProgress=false;
    private List<MediaItem> results;

    @BindView(R.id.view_toolbar) Toolbar viewToolbar;
    @BindView(R.id.view_content_frame) View contentFrame;
    @BindView(R.id.view_pager) ViewPager pager;
    @BindView(R.id.media_item_toolbar) Toolbar toolbar;
    @BindView(R.id.media_item_toolbar_group) View toolbarGroup;
    @BindView(R.id.media_bottom_sheet) View bottomSheet;
    @BindView(R.id.media_tags_filename) TextView filename;
    @BindView(R.id.media_tags_location) TextView location;
    @BindView(R.id.media_tags_type) TextView type;
    @BindView(R.id.media_tags_exposure) TextView exposure;
    @BindView(R.id.media_tags_filesize) TextView filesize;
    @BindView(R.id.media_tags_date) TextView date;
    @BindView(R.id.media_tags_focal) TextView focal;
    @BindView(R.id.media_tags_resolution) TextView resolution;
    @BindView(R.id.media_tags_aperture) TextView aperture;
    @BindView(R.id.media_tags_iso) TextView iso;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view);
        ButterKnife.bind(this);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);

        if (getIntent().getAction()!=null && getIntent().getAction().equals(Intent.ACTION_VIEW)) {
            String p = getIntent().getDataString();
            path = Util.ContentResolverHelper.getPath(this, Uri.parse(p));
            Log.d(Util.LOG_TAG, "Starting on VIEW intent for " + getIntent().getDataString());

            adapter = new MediaViewAdapter(this, path, this);
            pager.setAdapter(adapter);

            setupMetadata();
        } else {
            Realm realm = Realm.getDefaultInstance();
            album = realm.where(Album.class).equalTo("path",getIntent().getExtras().getString(Util.ALBUM_PASS_KEY)).findFirst();
            media = getIntent().getExtras().getString(Util.MEDIA_PASS_KEY);
            results = getResults();
            int i = results.indexOf(album.getMedia().where().equalTo("path",media).findFirst());
            adapter = new MediaViewAdapter(this, results, this);
            pager.setAdapter(adapter);
            pager.setCurrentItem(i);

            viewToolbar.inflateMenu(R.menu.media_edit_toolbar);
            if (!Colorful.getThemeDelegate().isDark()) {
                Util.ViewUtils.colorizeToolbar(viewToolbar, Color.BLACK);
            }

            pager.addOnPageChangeListener(this);
            setupMetadata();
        }

        getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);

        toolbar.setNavigationIcon(R.drawable.md_nav_back);
        toolbar.inflateMenu(R.menu.media_item_toolbar);
        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                return false;
            }
        });
        toolbar.setNavigationOnClickListener(this);

        viewToolbar.setTitle(R.string.info);
        if (Colorful.getThemeDelegate().isDark()) {
            viewToolbar.setNavigationIcon(R.drawable.ic_close_white_48px);
        } else {
            viewToolbar.setNavigationIcon(R.drawable.ic_close_black_48px);
        }
        viewToolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                close();
            }
        });
        viewToolbar.setOnMenuItemClickListener(this);

        startTimeOut();

        bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
        toolbar.setTitleTextColor(Color.WHITE);

        toolbar.setTitle(adapter.getPageTitle(pager.getCurrentItem()));

    }



    private List<MediaItem> getResults() {
        switch (Media.getMediaSortType()) {
            case ALPHABETICAL: {
                return album.getMedia().where().findAll().sort("name", Media.isMediaSortAscending() ? Sort.ASCENDING : Sort.DESCENDING);
            }
            case DATE: {
                return album.getMedia().where().findAll().sort("date", Media.isMediaSortAscending() ? Sort.ASCENDING : Sort.DESCENDING);
            }
        }
        return null;
    }

    @Override
    public void sizeChanged(int w, int h) {
        resolution.setText(getResources().getString(R.string.resolution) + ": " + w + "x" + h);
    }

    private void setupMetadata() {
        File media;

        if (path==null) {
            media = new File(results.get(pager.getCurrentItem()).getPath());
        } else {
            media = new File(path);
        }

        filename.setText(media.getName());
        String loc = media.getAbsolutePath();
        loc = loc.substring(0,loc.lastIndexOf("/"));
        location.setText(getResources().getString(R.string.location) + ": " + loc);
        type.setText(getResources().getString(R.string.type) + ": " + media.getName().substring(media.getName().lastIndexOf(".")+1));
        filesize.setText(getResources().getString(R.string.filesize) + ": " + Util.IO.humanReadableByteCount(media.length(),false));

        focal.setText(getResources().getString(R.string.focal) + ": " + getResources().getString(R.string.na));
        date.setText(getResources().getString(R.string.date) + ": " + getResources().getString(R.string.na));
        aperture.setText(getResources().getString(R.string.aperture) + ": " + getResources().getString(R.string.na));
        exposure.setText(getResources().getString(R.string.exposure) + ": " + getResources().getString(R.string.na));
        iso.setText(getResources().getString(R.string.iso) + ": " + getResources().getString(R.string.na));

        //Metadata loading
        try {
            ExifInterface metadata = new ExifInterface(media.getAbsolutePath());

            String tmp = metadata.getAttribute(ExifInterface.TAG_FOCAL_LENGTH);
            if (tmp != null) {
                focal.setText(getResources().getString(R.string.focal) + ": " + tmp);
            }

            tmp = metadata.getAttribute(ExifInterface.TAG_APERTURE_VALUE);
            if (tmp != null) {
                aperture.setText(getResources().getString(R.string.aperture) + ": " + tmp);
            }

            tmp = metadata.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL);
            if (tmp != null) {
                date.setText(getString(R.string.date_taken) + ": " + tmp);
            }

            tmp = metadata.getAttribute(ExifInterface.TAG_EXPOSURE_TIME);
            if (tmp != null) {
                exposure.setText(getResources().getString(R.string.exposure) + ": " + tmp);
            }

            tmp = metadata.getAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS);
            if (tmp != null) {
                iso.setText(getResources().getString(R.string.iso) + ": " + tmp);
            }
        }
        catch (Exception e){
            Log.e(Util.LOG_TAG, e.getMessage());
        }
    }


    @Override
    public boolean onMenuItemClick(MenuItem item) {
        if (!operationInProgress) {

            Realm realm = Realm.getDefaultInstance();
            realm.beginTransaction();
            album.getMedia().where().equalTo("path", results.get(pager.getCurrentItem()).getPath()).findFirst().setSelected(true);
            realm.commitTransaction();
            realm.close();

            switch (item.getItemId()) {
                case R.id.delete: {
                    operationInProgress = true;
                    final ProgressDialog pDialog = new ProgressDialog(this);
                    pDialog.show();
                    pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                    MediaLoader.deleteFiles(album.getPath())
                            .subscribe(new Action1<Boolean>() {
                                @Override
                                public void call(Boolean aBoolean) {
                                    operationInProgress = false;
                                    pDialog.dismiss();
                                    if (aBoolean) {
                                        Toast.makeText(ViewActivity.this, R.string.action_complete, Toast.LENGTH_LONG).show();
                                    } else {
                                        Toast.makeText(ViewActivity.this, R.string.error, Toast.LENGTH_LONG).show();
                                    }
                                    finish();
                                }
                            });
                    break;
                }
                case R.id.move: {
                    operationInProgress=true;
                    AlbumPickerDialog dialog = new AlbumPickerDialog(this);
                    dialog.setOnItemSelectedListener(new AlbumPickerDialog.OnItemSelectedListener() {
                        @Override
                        public void onItemSelected(final Album target) {
                            if (target == null) {
                                operationInProgress=false;
                            } else {
                                final ProgressDialog pDialog = new ProgressDialog(ViewActivity.this);
                                pDialog.show();
                                MediaLoader.moveFiles(album.getPath(), target.getPath())
                                        .subscribe(new Action1<Boolean>() {
                                            @Override
                                            public void call(Boolean aBoolean) {
                                                operationInProgress = false;
                                                pDialog.dismiss();
                                                if (aBoolean) {
                                                    Toast.makeText(ViewActivity.this, R.string.action_complete, Toast.LENGTH_LONG).show();
                                                } else {
                                                    Toast.makeText(ViewActivity.this, R.string.error, Toast.LENGTH_LONG).show();
                                                }
                                                finish();
                                            }
                                        });
                            }
                        }
                    });
                    dialog.show();
                    break;
                }
                case R.id.copy: {
                    operationInProgress=true;
                    AlbumPickerDialog dialog = new AlbumPickerDialog(this);
                    dialog.setOnItemSelectedListener(new AlbumPickerDialog.OnItemSelectedListener() {
                        @Override
                        public void onItemSelected(final Album target) {
                            if (target == null) {
                                operationInProgress=false;
                            } else {
                                final ProgressDialog pDialog = new ProgressDialog(ViewActivity.this);
                                pDialog.show();
                                MediaLoader.copyFiles(album.getPath(), target.getPath())
                                        .subscribe(new Action1<Boolean>() {
                                            @Override
                                            public void call(Boolean aBoolean) {
                                                operationInProgress = false;
                                                pDialog.dismiss();
                                                if (aBoolean) {
                                                    Toast.makeText(ViewActivity.this, R.string.action_complete, Toast.LENGTH_LONG).show();
                                                } else {
                                                    Toast.makeText(ViewActivity.this, R.string.error, Toast.LENGTH_LONG).show();
                                                }
                                                finish();
                                            }
                                        });
                            }
                        }
                    });
                    dialog.show();
                    break;
                }
                case R.id.share: {
                    Media.share(this, album.getPath());
                    break;
                }case R.id.edit: {
                    startActivity(new Intent(this, EditActivity.class).putExtra(EditActivity.IMAGE_URI_KEY,results.get(pager.getCurrentItem()).getPath()));
                    break;
                }
            }
        }
        return false;
    }

    public void close() {
        bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
    }

    @Override
    public void onSystemUiVisibilityChange(int i) {
        if ((i & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
            toggleToolbar(true);
        } else {
            // The system bars are NOT visible.
        }
    }

    @Override
    public void onClick(View view) {
        finish();
        if (Media.useAnimations()) {
            overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
        } else {
            overridePendingTransition(0,0);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (timeoutSubscription!=null) {
            timeoutSubscription.unsubscribe();
        }
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        if (Media.useAnimations()) {
            overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
        } else {
            overridePendingTransition(0,0);
        }
    }

    @Override
    public void call(Void aVoid) {
        Log.d(Util.LOG_TAG,"Auto hiding toolbar");
        toggleToolbar(false);
    }

    public void toggleToolbar(boolean show) {
        if (show==showToolbar || toolbarGroup==null) {
            return;
        }

        showToolbar=show;
        if (showToolbar) {
            startTimeOut();
            showSystemUI();
            toolbarGroup.animate().translationY(0).setInterpolator(new DecelerateInterpolator()).start();
        } else {
            if (timeoutSubscription!=null) {
                timeoutSubscription.unsubscribe();
            }
            toolbarGroup.animate().translationY(-toolbarGroup.getBottom()).setInterpolator(new AccelerateInterpolator()).start();
            hideSystemUI();
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }
    @Override
    public void onPageScrollStateChanged(int state) {
    }
    @Override
    public void onPageSelected(int position) {
        toolbar.setTitle(adapter.getPageTitle(position));
        setupMetadata();
    }

    private Observable<Void> timeOut = Observable
            .create(new Observable.OnSubscribe<Void>() {
                @Override
                public void call(Subscriber<? super Void> subscriber) {
                    subscriber.onNext(null);
                    subscriber.onCompleted();
                }
            })
            .delay(4, TimeUnit.SECONDS)
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeOn(Schedulers.computation());

    private void startTimeOut() {
        if (timeoutSubscription!=null) {
            timeoutSubscription.unsubscribe();
        }
        timeoutSubscription = timeOut.subscribe(this);
    }

    public void hideSystemUI() {
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_IMMERSIVE);
    }

    public void showSystemUI() {
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
    }
} public class MediaAdapter extends RealmRecyclerViewAdapter<MediaItem, MediaAdapter.ItemViewHolder> {
    private Context context;
    private final PublishSubject<MediaItem> onClickSubject = PublishSubject.create();
    private final PublishSubject<MediaItem> onLongClickSubject = PublishSubject.create();
    private String query = null;
    private Album album;

    public MediaAdapter(Context context, Album album) {
        super(context, getResults(album, null), true);
        this.context=context;
        this.album=album;
    }

    private static OrderedRealmCollection<MediaItem> getResults(Album album, String query) {
        switch (Media.getMediaSortType()) {
            case ALPHABETICAL: {
                return album.getMedia().where().contains("name", query).findAll().sort("name", Media.isMediaSortAscending() ? Sort.ASCENDING : Sort.DESCENDING);
            }
            case DATE: {
                return album.getMedia().where().contains("name", query).findAll().sort("date", Media.isMediaSortAscending() ? Sort.ASCENDING : Sort.DESCENDING);
            }
        }
        return null;
    }

    public void updateQuery() {
        updateData(getResults(album, query));
    }

    public void filter(String search) {
        if (search.isEmpty()) {
            cancelFilter();
            return;
        }
        Long time = System.currentTimeMillis();
        query=search;
        updateQuery();
        Log.d(Util.LOG_TAG, "Filtered query " + query + " in " + (System.currentTimeMillis() - time) + " milliseconds");
    }

    public void cancelFilter() {
        query=null;
        updateQuery();
    }

    @Override
    public void onBindViewHolder(ItemViewHolder ViewHolder, int i) {
        ViewHolder.bind(context, getData().get(i));
    }

    @Override
    public ItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        final ItemViewHolder holder;
        if (Media.isStaggered()) {
            holder = new StaggeredViewHolder(LayoutInflater.from(context).inflate(R.layout.adapter_media_item_staggered, viewGroup, false));
        } else {
            holder = new SquareViewHolder(LayoutInflater.from(context).inflate(R.layout.adapter_media_item, viewGroup, false));
        }
        holder.getClickSubject().setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onClickSubject.onNext(getData().get(holder.getAdapterPosition()));
            }
        });
        holder.getClickSubject().setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                onLongClickSubject.onNext(getData().get(holder.getAdapterPosition()));
                return true;
            }
        });
        return holder;
    }

    protected abstract static class ItemViewHolder extends RecyclerView.ViewHolder{

        public ItemViewHolder(View v) {
            super(v);
        }

        public abstract void bind(Context context, MediaItem media);

        public abstract View getClickSubject();
    }

    protected static class SquareViewHolder extends ItemViewHolder{

        @BindView(R.id.media_item) ImageView mediaItem;
        @BindView(R.id.media_item_check) ImageView check;

        public SquareViewHolder(View v) {
            super(v);
            ButterKnife.bind(this,v);
        }

        @Override
        public void bind(Context context, MediaItem media) {
            try {
                Glide.with(context).load(media.getPath()).asBitmap().into(mediaItem);
            } catch (Exception e) {
                Log.e(Util.LOG_TAG,"Failed to load image " + media.getName());
            }
            if (media.isSelected()) {
                check.setVisibility(View.VISIBLE);
            } else {
                check.setVisibility(View.GONE);
            }
        }

        @Override
        public View getClickSubject() {
            return mediaItem;
        }
    }

    protected static class StaggeredViewHolder extends ItemViewHolder{
        @BindView(R.id.media_item) AspectImageView mediaItem;
        @BindView(R.id.media_item_check) ImageView check;

        public StaggeredViewHolder(View v) {
            super(v);
            ButterKnife.bind(this,v);
        }

        @Override
        public void bind(final Context context, final MediaItem media) {
            if (media.getAspectRatio()==-1) {
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeFile(media.getPath(), options);
                final float width = options.outWidth;
                final float height = options.outHeight;
                Realm realm = Realm.getDefaultInstance();
                realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        media.setAspectRatio(height/width);
                    }
                });
                realm.close();
            }

            mediaItem.setAspect(media.getAspectRatio());
            Glide.with(context).load(media.getPath()).into(mediaItem);

            if (media.isSelected()) {
                check.setVisibility(View.VISIBLE);
            } else {
                check.setVisibility(View.GONE);
            }
        }

        @Override
        public View getClickSubject() {
            return mediaItem;
        }
    }

    public Observable<MediaItem> getOnLongClickObservable() {
        return onLongClickSubject;
    }

    public Observable<MediaItem> getOnClickObservable() {
        return onClickSubject;
    }
} public class Album extends RealmObject {
    @PrimaryKey private String path;
    private RealmList<MediaItem> photos = new RealmList<>();
    private int size;
    private String name;
    private String coverPath;
    private long lastModified;
    private boolean hidden=false;

    public Album() {
    }

    public Album(String path, int size, String name, String coverPath, long lastModified) {
        this.path = path;
        this.size=size;
        this.name=name;
        this.coverPath=coverPath;
        this.lastModified=lastModified;
        hidden = new File(path + "/", ".nomedia").exists();
    }

    public boolean isHidden() {
        return hidden;
    }

    public void setHidden(boolean hidden) {
        this.hidden = hidden;
    }

    public void clearMedia() {
        photos.clear();
    }

    public List<Integer> getFilteredIndex(String query) {
        List<Integer> filter = new ArrayList<>();
        for (int index=0; index<photos.size(); index++) {
            if (photos.get(index).getName().contains(query)) {
                filter.add(index);
            }
        }
        return filter;
    }

    public boolean needsReload() {
        Log.d(Util.LOG_TAG, "Checking if " + getName() + " has been changed");
        Long modified = new File(path).lastModified();
        if (photos.size() < 1 || modified>lastModified) {
            Log.d(Util.LOG_TAG, "Album " + getName() + " has been modified, reloading");
            return true;
        }
        return false;
    }

    public long getLastModified() {
        return lastModified;
    }

    public void setLastModified(long lastModified) {
        this.lastModified = lastModified;
    }

    public int countSelected() {
        int selected=0;
        for (MediaItem i : photos) {
            if (i.isSelected()) {
                selected++;
            }
        }
        return selected;
    }

    public List<MediaItem> getSelected() {
        ArrayList<MediaItem> selected = new ArrayList<>();
        for (MediaItem item : photos) {
            if (item.isSelected()) {
                selected.add(item);
            }
        }
        return selected;
    }

    public void clearMediaSelection() {
        for (MediaItem i : photos) {
            i.setSelected(false);
        }
        Log.d(Util.LOG_TAG, "Clearing media selection");
    }

    public RealmList<MediaItem> getMedia() {
        return photos;
    }

    public void addMediaItem(MediaItem item) {
        photos.add(item);
    }

    public String getPath() {
        return path;
    }

    public String getName() {
        return name;
    }

    public void remove(int index) {
        photos.remove(index);
    }

    public int getSize() {
        if (photos.size()>0) {
            return photos.size();
        }
        return size;
    }

    public void setCoverPath(String coverPath) {
        this.coverPath = coverPath;
    }

    public String getCoverPath() {
        return coverPath;
    }

    @Override
    public boolean equals(Object obj) {
        return path.equals(((Album) obj).path);
    }
} public class Media {
    public static final String ALBUM_SORT_KEY="album_sort";
    public static final String MEDIA_SORT_KEY="media_sort";
    public static final String ALBUM_SORT_KEY_ASC="album_sort_asc";
    public static final String MEDIA_SORT_KEY_ASC="media_sort_asc";
    private static boolean sortAscending=true;
    private static boolean mediaSortAscending=true;
    private static AlbumSortType sortType=AlbumSortType.ALPHABETICAL;
    private static MediaSortType mediaSortType=MediaSortType.DATE;
    private static boolean showHidden=false;
    private static boolean needsRefresh=false;
    private static boolean animations=true;
    private static boolean staggered=false;
    private static int sizePortrait=2;
    private static int sizeLandscape=4;
    private static int sizeAlbumPortrait=4;
    private static int sizeAlbumLandscape=8;
    public enum MediaTypes {
        IMAGE, VIDEO, GIF, UNKNOWN
    }

    public enum AlbumSortType {
        ALPHABETICAL, SIZE
    }

    public enum MediaSortType {
        ALPHABETICAL, DATE
    }

    public static List<String> albumsNeedReloaded() {
        List<String> reloads = new ArrayList<>();
        Realm realm = Realm.getDefaultInstance();
        for (Album a : realm.where(Album.class).findAll()) {
            if (a.needsReload()) {
                reloads.add(a.getPath());
            }
        }
        return reloads;
    }

    public static void invalidate() {
        Log.e(Util.LOG_TAG, "Invalidate called, all caches will be wiped");
        Realm realm = Realm.getDefaultInstance();
        realm.beginTransaction();
        realm.deleteAll();
        realm.commitTransaction();
        realm.close();
    }

    public static void init(Context context, boolean refresh) {
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        sizePortrait = prefs.getInt("album_grid_portrait",2);
        sizeLandscape = prefs.getInt("album_grid_landscape",4);
        sizeAlbumPortrait = prefs.getInt("media_grid_portrait",4);
        sizeAlbumLandscape = prefs.getInt("media_grid_landscape",8);
        showHidden = prefs.getBoolean("show_hidden", false);
        animations = prefs.getBoolean("animations", true);
        staggered = prefs.getBoolean("staggered",false);

        sortType = Media.AlbumSortType.values()[prefs.getInt(Media.ALBUM_SORT_KEY, Media.AlbumSortType.ALPHABETICAL.ordinal())];
        mediaSortType = Media.MediaSortType.values()[prefs.getInt(Media.MEDIA_SORT_KEY, MediaSortType.DATE.ordinal())];
        sortAscending = prefs.getBoolean(ALBUM_SORT_KEY_ASC,true);
        mediaSortAscending = prefs.getBoolean(MEDIA_SORT_KEY_ASC,false);

        if (refresh) {
            needsRefresh=true;
        }
    }

    public static void setStaggered(boolean staggered) {
        Media.staggered = staggered;
    }

    public static boolean isStaggered() {
        return staggered;
    }

    public static boolean needsRefresh() {
        return needsRefresh;
    }

    public static boolean isShowHidden() {
        return showHidden;
    }

    public static boolean useAnimations() {
        return animations;
    }

    public static void setUseAnimations(boolean animations) {
        Media.animations = animations;
    }

    public static void setShowHidden(boolean showHidden, Context context) {
        Media.showHidden = showHidden;
        PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean("show_hidden", showHidden).apply();
    }
    public static void setAlbumSortType(Context context, AlbumSortType type, boolean ascending) {
        sortType = type;
        sortAscending=ascending;
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        prefs.edit()
                .putInt(ALBUM_SORT_KEY,sortType.ordinal())
                .putBoolean(ALBUM_SORT_KEY_ASC,sortAscending)
                .apply();
    }

    public static void setMediaSortType(Context context, MediaSortType type, boolean ascending) {
        mediaSortType = type;
        mediaSortAscending=ascending;
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
        prefs.edit()
                .putInt(MEDIA_SORT_KEY,mediaSortType.ordinal())
                .putBoolean(MEDIA_SORT_KEY_ASC,mediaSortAscending)
                .apply();
    }

    /*public static Comparator<Album> getAlbumComparator() {
        switch (sortType) {
            case ALPHABETICAL: {
                return new AlbumAlphabetical(sortAscending);
            }
            case SIZE: {
                return new AlbumMediaCount(sortAscending);
            }
        }
        return null;
    }

    public static Comparator<MediaItem> getMediaComparator() {
        switch (mediaSortType) {
            case ALPHABETICAL: {
                return new MediaAlphabetical(mediaSortAscending);
            }
            case DATE: {
                return new MediaDate(mediaSortAscending);
            }
        }
        return null;
    }*/

    public static boolean isMediaSortAscending() {
        return mediaSortAscending;
    }

    public static MediaSortType getMediaSortType() {
        return mediaSortType;
    }

    public static boolean isSortAscending() {
        return sortAscending;
    }

    public static AlbumSortType getSortType() {
        return sortType;
    }

    public static int getSizeAlbumPortrait() {
        return sizeAlbumPortrait;
    }

    public static int getSizeAlbumLandscape() {
        return sizeAlbumLandscape;
    }

    public static int getSizePortrait() {
        return sizePortrait;
    }

    public static int getSizeLandscape() {
        return sizeLandscape;
    }

    public static List<Integer> getFilteredIndex(String query) {
        //TODO
        return null;
    }

    public static void share(Context context, String album) {

        ArrayList<Uri> files = new ArrayList<Uri>();

        Realm realm = Realm.getDefaultInstance();
        for(MediaItem item : realm.where(Album.class).equalTo("path", album).findFirst().getMedia().where().equalTo("isSelected",true).findAll()) {
            File file = new File(item.getPath());
            Uri uri = Uri.fromFile(file);
            files.add(uri);
        }

        final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
        intent.setType("image/*");
        intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, files);

        realm.beginTransaction();
        realm.where(Album.class).equalTo("path", album).findFirst().clearMediaSelection();
        realm.commitTransaction();
        realm.close();

        context.startActivity(Intent.createChooser(intent, "Share"));
    }

} public class MediaItem extends RealmObject {
    @PrimaryKey private String path;
    private long date;
    private String mime;
    private boolean isSelected;
    private float aspectRatio=-1;
    private String name;

    public MediaItem() {
    }

    public MediaItem(String path, long date) {
        this.path = path;
        this.date = date;
        setMIME();
        name = getName();
    }

    public MediaItem(File file) {
        path=file.getAbsolutePath();
        date=file.lastModified();
        setMIME();
        name = getName();
    }

    public float getAspectRatio() {
        return aspectRatio;
    }

    public void setAspectRatio(float aspectRatio) {
        this.aspectRatio = aspectRatio;
    }

    public boolean isSelected() {
        return isSelected;
    }

    public void setSelected(boolean selected) {
        isSelected = selected;
    }

    public String getPath() {
        return path;
    }

    public String getName() {
        return new File(path).getName();
    }

    public String getFileExtension() {
        return path.substring(path.lastIndexOf(".")+1);
    }

    public Media.MediaTypes getMediaType() {
        if (mime.endsWith("gif")) {
            return Media.MediaTypes.GIF;
        } else if (mime.startsWith("image")) {
            return Media.MediaTypes.IMAGE;
        } else if (mime.startsWith("video")) {
            return Media.MediaTypes.VIDEO;
        }
        return Media.MediaTypes.UNKNOWN;
    }

    public String getMime() {
        return mime;
    }

    private void setMIME() {
        mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(getFileExtension().toLowerCase());
        if (mime == null) mime="unknown";
    }

    public long getDate() {
        return date;
    }

    @Override
    public boolean equals(Object obj) {
        return path.equals(((MediaItem) obj).path);
    }
} public class ActionMessageDialog extends Dialog {
    @BindView(R.id.action_dialog_toolbar) Toolbar toolbar;
    @BindView(R.id.action_dialog_message) TextView msg;
    @BindView(R.id.action_dialog_ok) Button ok;
    @BindView(R.id.action_dialog_cancel) Button cancel;

    private String buttonText=null, negativeText, titleText;
    private Spanned msgText;

    private OnPositiveListener listener;

    public ActionMessageDialog(Context context) {
        super(context);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.dialog_action_message);
        ButterKnife.bind(this);

        WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
        Window window = getWindow();
        lp.copyFrom(window.getAttributes());
        lp.width = WindowManager.LayoutParams.MATCH_PARENT;
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
        window.setAttributes(lp);

        toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_48px);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dismiss();
            }
        });

        toolbar.setTitle(titleText);
        msg.setText(msgText);
        if (buttonText==null) {
            ok.setVisibility(View.GONE);
        } else {
            ok.setText(buttonText);
        }
        if (negativeText!=null) {
            cancel.setText(negativeText);
        }
    }

    public void setMsgText(String text) {
        msgText= SpannedString.valueOf(text);
    }

    public void setMsgText(Spanned text) {
        msgText = text;
    }

    public void setTitleText(String text) {
        titleText=text;
    }


    public void setPositiveButtonMsg(String msg) {
        buttonText=msg;
    }


    public void setNegativeButtonMsg(String msg) {
        negativeText=msg;
    }

    @OnClick(R.id.action_dialog_cancel)
    public void close() {
        dismiss();
    }

    @OnClick(R.id.action_dialog_ok)
    public void click() {
        if (listener!=null) {
            listener.onPositive();
            dismiss();
        }
    }

    public void setOnPositiveListener(OnPositiveListener listener) {
        this.listener = listener;
    }

    public interface OnPositiveListener {
        void onPositive();
    }
} public abstract class MediaLoader {
    private static List<String> excluded;

    public static Observable<Boolean> init() {
        return Observable
                .create(new Observable.OnSubscribe<Boolean>() {
                    @Override
                    public void call(Subscriber<? super Boolean> subscriber) {
                        long time = System.currentTimeMillis();

                        String prefString = PreferenceManager.getDefaultSharedPreferences(Cluttr.getApplication()).getString("excluded_folders","");
                        if (prefString.isEmpty()) {
                            excluded = new ArrayList<>();
                        } else {
                            excluded = new ArrayList<>(Arrays.asList(prefString.split(":")));
                        }


                        boolean result = true;
                        try {
                            for (File root : listStorages()) {
                                recursiveLoadAlbums(root);
                            }
                        } catch (Exception ex) {
                            Log.e(Util.LOG_TAG, "A fatal error has occured during media loading, cache will be wiped", ex);
                            //TODO: WIPE CACHE
                            result = false;
                        }
                        Log.d(Util.LOG_TAG, "Indexing all media took " + (System.currentTimeMillis() - time) + "ms");
                        subscriber.onNext(result);
                        subscriber.onCompleted();
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io());
    }

    private static List<File> listStorages() {
        List<File> roots = new ArrayList<>();
        roots.add(new File(FileUtil.getSdCardPath()));

        File sd = ExtSdHelper.getRootFile();
        if (sd==null) return roots;
        if (sd.exists()) {
            Cluttr.getApplication().getContentResolver().takePersistableUriPermission(ExtSdHelper.getSdUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            roots.add(sd);
        } else {
            Log.e(Util.LOG_TAG, "External SDCard nolonger found, clearing cache");
            //TODO: CLEAR CACHE
        }

        return roots;
    }

    private static void recursiveLoadAlbums(File dir) {
        final File[] contents = dir.listFiles(new MediaFilter());
        Realm realm = Realm.getDefaultInstance();
        final Album album = realm.where(Album.class).equalTo("path", dir.getPath()).findFirst();
        if (album!=null) {
            Log.d(Util.LOG_TAG, "Album " + album.getName() + " is already in cache");
            if (album.needsReload()) {
                final Album newAlbum = new Album(dir.getPath(), contents.length, dir.getName(), contents[0].getPath(), dir.lastModified());
                realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        for (File item : contents) {
                            newAlbum.addMediaItem(new MediaItem(item));
                        }
                        realm.copyToRealmOrUpdate(newAlbum);
                    }
                });
                updateSingleCover(newAlbum.getPath());
            } else {
                Log.d(Util.LOG_TAG, "Album is up to date, skipping index");
            }
        } else {
            if (contents.length>0) {
                Log.d(Util.LOG_TAG, "New album found: " + dir.getName() + ", adding to cache");
                final Album newAlbum = new Album(dir.getPath(), contents.length, dir.getName(), contents[0].getPath(), dir.lastModified());
                realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        for (File item : contents) {
                            newAlbum.addMediaItem(new MediaItem(item));
                        }
                        realm.copyToRealmOrUpdate(newAlbum);
                    }
                });
                updateSingleCover(newAlbum.getPath());
            }
        }
        realm.close();
        for (File child : dir.listFiles(new FolderFilter())) {
            if (Util.DEFAULT_EXCLUDED.contains(child.getPath()) || excluded.contains(child.getPath()) || child.isHidden()) {
                Log.d(Util.LOG_TAG, "Folder " + child.getPath() + " is excluded, skipping");
            } else {
                recursiveLoadAlbums(child);
            }
        }
    }

    public static Observable<Boolean> refreshAlbum(final String key) {
        return Observable
                .create(new Observable.OnSubscribe<Boolean>() {
                    @Override
                    public void call(Subscriber<? super Boolean> subscriber) {
                        refreshAlbumImp(key);
                        subscriber.onNext(true);
                        subscriber.onCompleted();
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io());
    }

    private static void refreshAlbumImp(String key) {
        long time = System.currentTimeMillis();
        Realm realm = Realm.getDefaultInstance();
        Album album = realm.where(Album.class).equalTo("path", key).findFirst();
        File dir = new File(album.getPath());
        final File[] contents = dir.listFiles(new MediaFilter());
        final Album newAlbum = new Album(dir.getPath(), contents.length, dir.getName(), contents[0].getPath(), dir.lastModified());
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                for (File item : contents) {
                    newAlbum.addMediaItem(new MediaItem(item));
                }
                realm.copyToRealmOrUpdate(newAlbum);
            }
        });
        updateSingleCover(newAlbum.getPath());
        realm.close();
        Log.d(Util.LOG_TAG, "Album " + newAlbum.getName() + " refreshed in " + (System.currentTimeMillis()-time) + "ms");
    }

    private static boolean deleteFileImpl(final Album album, final MediaItem item) {
        File media = new File(item.getPath());
        Log.d(Util.LOG_TAG,"Deleting " + media.getAbsolutePath());
        if (FileUtil.deleteFile(media)) {
            Realm realm = Realm.getDefaultInstance();
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    album.getMedia().where().equalTo("path", item.getPath()).findFirst().deleteFromRealm();
                }
            });
            realm.close();
            return true;
        }
        return false;
    }

    public static void updateCoverPhotos() {
        long time = System.currentTimeMillis();
        Realm realm = Realm.getDefaultInstance();
        realm.beginTransaction();
        for (Album a : realm.where(Album.class).findAll()) {
            switch (Media.getMediaSortType()) {
                case ALPHABETICAL: {
                    a.setCoverPath(a.getMedia().sort("name", Media.isMediaSortAscending() ? Sort.ASCENDING : Sort.DESCENDING).first().getPath());
                    break;
                }
                case DATE: {
                    a.setCoverPath(a.getMedia().sort("date", Media.isMediaSortAscending() ? Sort.ASCENDING : Sort.DESCENDING).first().getPath());
                    break;
                }
            }
        }
        realm.commitTransaction();
        realm.close();
        Log.d(Util.LOG_TAG, "Reloading all cover photos took " + (System.currentTimeMillis() - time));
    }

    public static void updateSingleCover(String key) {
        long time = System.currentTimeMillis();
        Realm realm = Realm.getDefaultInstance();
        realm.beginTransaction();
        Album a = realm.where(Album.class).equalTo("path",key).findFirst();
        switch (Media.getMediaSortType()) {
            case ALPHABETICAL: {
                a.setCoverPath(a.getMedia().sort("name", Media.isMediaSortAscending() ? Sort.ASCENDING : Sort.DESCENDING).first().getPath());
                break;
            }
            case DATE: {
                a.setCoverPath(a.getMedia().sort("date", Media.isMediaSortAscending() ? Sort.ASCENDING : Sort.DESCENDING).first().getPath());
                break;
            }
        }
        realm.commitTransaction();
        realm.close();
        Log.d(Util.LOG_TAG, "Reloading all cover photos took " + (System.currentTimeMillis() - time));
    }

    private static boolean moveFileImpl(Album sourceAlbum, MediaItem item, Album targetAlbum) {
        if (sourceAlbum.getPath().equals(targetAlbum.getPath())) return true;
        return copyFileImpl(sourceAlbum, item, targetAlbum) && deleteFileImpl(sourceAlbum, item);
    }

    private static boolean copyFileImpl(Album sourceAlbum, MediaItem item, final Album targetAlbum) {
        File dst;
        try {
            File src = new File(item.getPath());
            dst = new File(targetAlbum.getPath() + "/" + src.getName());
            if (dst.exists()) {
                Log.d(Util.LOG_TAG,dst.getName() + " already exists, finding a new name");
                String name = dst.getName().substring(0,dst.getName().indexOf("."));
                if (name.trim().endsWith(")")) {
                    name = name.substring(0,name.lastIndexOf("("));
                    name = name.trim();
                }
                String extension = dst.getName().substring(dst.getName().lastIndexOf("."));
                Log.d(Util.LOG_TAG, "Split name into " + name + " and " + extension);
                int nameInt=0;
                dst = new File(targetAlbum.getPath() + "/" + name + "(" + nameInt + ")" + extension);
                while (dst.exists()) {
                    nameInt++;
                    dst = new File(targetAlbum.getPath() + "/" + name + "(" + nameInt + ")" + extension);
                }
                Log.d(Util.LOG_TAG, "New file name is " + dst.getName());
            }
            Log.d(Util.LOG_TAG, "Copying " + src.getAbsolutePath() + " to " + dst.getAbsolutePath());

            boolean result = FileUtil.copyFile(src, dst);
            if (!result) {
                return false;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        final File finalDst = dst;
        Realm realm = Realm.getDefaultInstance();
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                targetAlbum.addMediaItem(new MediaItem(finalDst));
            }
        });
        realm.close();
        return true;
    }

    public static Observable<Boolean> deleteFiles(final String album) {
        return Observable
                .create(new Observable.OnSubscribe<Boolean>() {
                    @Override
                    public void call(Subscriber<? super Boolean> subscriber) {
                        boolean result = true;
                        Realm realm = Realm.getDefaultInstance();
                        final Album target = realm.where(Album.class).equalTo("path", album).findFirst();
                        for (MediaItem item : target.getSelected()) {
                            if (!deleteFileImpl(target, item)) {
                                result=false;
                            }
                        }
                        realm.executeTransaction(new Realm.Transaction() {
                            @Override
                            public void execute(Realm realm) {
                                target.clearMediaSelection();
                                target.setLastModified(new File(target.getPath()).lastModified());
                                if (target.getMedia().size()<1) {
                                    realm.where(Album.class).equalTo("path", target.getPath()).findFirst().deleteFromRealm();
                                }
                            }
                        });
                        realm.close();
                        updateCoverPhotos();
                        subscriber.onNext(result);
                        subscriber.onCompleted();
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io());
    }

    public static Observable<Boolean> moveFiles(final String sourceAlbum, final String targetAlbum) {
        return Observable
                .create(new Observable.OnSubscribe<Boolean>() {
                    @Override
                    public void call(Subscriber<? super Boolean> subscriber) {
                        boolean result = true;
                        Realm realm = Realm.getDefaultInstance();
                        final Album source = realm.where(Album.class).equalTo("path", sourceAlbum).findFirst();
                        final Album target = realm.where(Album.class).equalTo("path", targetAlbum).findFirst();
                        for (MediaItem item : source.getMedia().where().equalTo("isSelected",true).findAll()) {
                            if (!moveFileImpl(source,item,target)) {
                                result=false;
                            }
                        }
                        realm.executeTransaction(new Realm.Transaction() {
                            @Override
                            public void execute(Realm realm) {
                                source.clearMediaSelection();
                                target.clearMediaSelection();
                                source.setLastModified(new File(source.getPath()).lastModified());
                                target.setLastModified(new File(target.getPath()).lastModified());
                                if (source.getSize()<1) {
                                    realm.where(Album.class).equalTo("path", source.getPath()).findFirst().deleteFromRealm();
                                }
                            }
                        });
                        realm.close();
                        updateCoverPhotos();
                        subscriber.onNext(result);
                        subscriber.onCompleted();
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io());
    }

    public static Observable<Boolean> copyFiles(final String sourceAlbum, final String targetAlbum) {
        return Observable
                .create(new Observable.OnSubscribe<Boolean>() {
                    @Override
                    public void call(Subscriber<? super Boolean> subscriber) {
                        boolean result = true;
                        Realm realm = Realm.getDefaultInstance();
                        final Album source = realm.where(Album.class).equalTo("path", sourceAlbum).findFirst();
                        final Album target = realm.where(Album.class).equalTo("path", targetAlbum).findFirst();
                        for (MediaItem item : source.getMedia().where().equalTo("isSelected",true).findAll()) {
                            if (!copyFileImpl(source,item,target)) {
                                result=false;
                            }
                        }
                        realm.executeTransaction(new Realm.Transaction() {
                            @Override
                            public void execute(Realm realm) {
                                source.clearMediaSelection();
                                target.clearMediaSelection();
                                source.setLastModified(new File(source.getPath()).lastModified());
                                target.setLastModified(new File(target.getPath()).lastModified());
                                if (source.getSize()<1) {
                                    realm.where(Album.class).equalTo("path", source.getPath()).findFirst().deleteFromRealm();
                                }
                                if (target.getSize()<1) {
                                    realm.where(Album.class).equalTo("path", target.getPath()).findFirst().deleteFromRealm();
                                }
                            }
                        });
                        realm.close();
                        updateCoverPhotos();
                        subscriber.onNext(result);
                        subscriber.onCompleted();
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io());
    }
}
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.CardView;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.support.v7.widget.Toolbar;
import android.text.InputType;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.pluscubed.recyclerfastscroll.RecyclerFastScroller;
import org.polaric.cluttr.R;
import org.polaric.cluttr.Util;
import org.polaric.cluttr.activities.EditActivity;
import org.polaric.cluttr.activities.MainActivity;
import org.polaric.cluttr.activities.ViewActivity;
import org.polaric.cluttr.adapters.MediaAdapter;
import org.polaric.cluttr.data.Album;
import org.polaric.cluttr.data.Media;
import org.polaric.cluttr.data.MediaItem;
import org.polaric.cluttr.dialogs.ActionMessageDialog;
import org.polaric.cluttr.io.MediaLoader;
import org.polaric.colorful.Colorful;
import java.io.File;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import io.realm.Realm;
import rx.functions.Action1;
package org.polaric.cluttr.fragments;







public class MediaFragment extends BaseFragment implements View.OnClickListener, Action1<MediaItem>, Toolbar.OnMenuItemClickListener, SearchView.OnQueryTextListener, MenuItemCompat.OnActionExpandListener {
    private final String STATE_OUT_SELECT="select_bool";
    private final String STATE_OUT_OPERATION="operation_bool";

    @BindView(R.id.media_app_bar) AppBarLayout appbar;
    @BindView(R.id.media_coordinator) CoordinatorLayout coordinator;
    @BindView(R.id.fastscroller) RecyclerFastScroller scroller;
    @BindView(R.id.media_toolbar) Toolbar toolbar;
    @BindView(R.id.media_file_toolbar) Toolbar fileToolbar;
    @BindView(R.id.media_recycler) RecyclerView mediaRecycler;
    @BindView(R.id.media_card_container) CardView editContainer;

    private Unbinder unbinder;
    private Album album;
    private MediaAdapter adapter;
    private boolean isSelecting=false;
    private boolean operationInProgress=false;
    private SearchView searchView;
    private boolean isRefreshInProgress=false;

    private Realm realm;

    public MediaFragment() {

    }

    @Override
    public void onClick(View view) {
        getFragmentManager().popBackStack();
    }

    @Override
    public void onDetach() {
        super.onDetach();
        try {
            if (album.countSelected()>0) {
                realm.executeTransaction(new Realm.Transaction() {
                    @Override
                    public void execute(Realm realm) {
                        album.clearMediaSelection();
                    }
                });
            }
        } catch (Exception e) {
            Log.e(Util.LOG_TAG, "NPE, fix this");