package com.indieweb.indigenous.photoeditor; import android.Manifest; import android.annotation.SuppressLint; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.view.View; import android.view.animation.AnticipateOvershootInterpolator; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintSet; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.transition.ChangeBounds; import androidx.transition.TransitionManager; import com.indieweb.indigenous.R; import com.indieweb.indigenous.photoeditor.base.BaseActivity; import com.indieweb.indigenous.photoeditor.filters.FilterListener; import com.indieweb.indigenous.photoeditor.filters.FilterViewAdapter; import com.indieweb.indigenous.photoeditor.tools.EditingToolsAdapter; import com.indieweb.indigenous.photoeditor.tools.ToolType; import java.io.File; import java.io.IOException; import java.util.Objects; import ja.burhanrashid52.photoeditor.OnPhotoEditorListener; import ja.burhanrashid52.photoeditor.PhotoEditor; import ja.burhanrashid52.photoeditor.PhotoEditorView; import ja.burhanrashid52.photoeditor.PhotoFilter; import ja.burhanrashid52.photoeditor.SaveSettings; import ja.burhanrashid52.photoeditor.TextStyleBuilder; import ja.burhanrashid52.photoeditor.ViewType; public class EditImageActivity extends BaseActivity implements OnPhotoEditorListener, View.OnClickListener, PropertiesBSFragment.Properties, EmojiBSFragment.EmojiListener, EditingToolsAdapter.OnItemSelected, FilterListener { PhotoEditor mPhotoEditor; private int imagePosition = 0; private PhotoEditorView mPhotoEditorView; private PropertiesBSFragment mPropertiesBSFragment; private EmojiBSFragment mEmojiBSFragment; private TextView mTxtCurrentTool; private RecyclerView mRvTools, mRvFilters; private EditingToolsAdapter mEditingToolsAdapter = new EditingToolsAdapter(this); private FilterViewAdapter mFilterViewAdapter = new FilterViewAdapter(this); private ConstraintLayout mRootView; private ConstraintSet mConstraintSet = new ConstraintSet(); private boolean mIsFilterVisible; private boolean hasFilter = false; @Nullable @VisibleForTesting Uri mSaveImageUri; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); makeFullScreen(); setContentView(R.layout.photoeditor_activity_edit_image); initViews(); handleIntentImage(); mPropertiesBSFragment = new PropertiesBSFragment(); mEmojiBSFragment = new EmojiBSFragment(); mEmojiBSFragment.setEmojiListener(this); mPropertiesBSFragment.setPropertiesChangeListener(this); LinearLayoutManager llmTools = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); mRvTools.setLayoutManager(llmTools); mRvTools.setAdapter(mEditingToolsAdapter); LinearLayoutManager llmFilters = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); mRvFilters.setLayoutManager(llmFilters); mRvFilters.setAdapter(mFilterViewAdapter); mPhotoEditor = new PhotoEditor.Builder(this, mPhotoEditorView) .setPinchTextScalable(true) .build(); mPhotoEditor.setOnPhotoEditorListener(this); } /** * Handle incoming image. */ private void handleIntentImage() { try { Bundle extras = getIntent().getExtras(); if (extras != null) { String uri = extras.getString("imageUri"); Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), Uri.parse(uri)); mPhotoEditorView.getSource().setImageBitmap(bitmap); imagePosition = extras.getInt("imagePosition"); } } catch (Exception ignored) { } } /** * Init views and listeners. */ private void initViews() { ImageView imgUndo; ImageView imgRedo; ImageView imgSave; ImageView imgClose; mPhotoEditorView = findViewById(R.id.photoEditorView); mTxtCurrentTool = findViewById(R.id.txtCurrentTool); mRvTools = findViewById(R.id.rvConstraintTools); mRvFilters = findViewById(R.id.rvFilterView); mRootView = findViewById(R.id.rootView); imgUndo = findViewById(R.id.imgUndo); imgUndo.setOnClickListener(this); imgRedo = findViewById(R.id.imgRedo); imgRedo.setOnClickListener(this); imgSave = findViewById(R.id.imgSave); imgSave.setOnClickListener(this); imgClose = findViewById(R.id.imgClose); imgClose.setOnClickListener(this); } @Override public void onEditTextChangeListener(final View rootView, String text, int colorCode) { TextEditorDialogFragment textEditorDialogFragment = TextEditorDialogFragment.show(this, text, colorCode); textEditorDialogFragment.setOnTextEditorListener(new TextEditorDialogFragment.TextEditor() { @Override public void onDone(String inputText, int colorCode) { final TextStyleBuilder styleBuilder = new TextStyleBuilder(); styleBuilder.withTextColor(colorCode); mPhotoEditor.editText(rootView, inputText, styleBuilder); mTxtCurrentTool.setText(R.string.label_text); } }); } @Override public void onAddViewListener(ViewType viewType, int numberOfAddedViews) { } @Override public void onRemoveViewListener(ViewType viewType, int numberOfAddedViews) { } @Override public void onStartViewChangeListener(ViewType viewType) { } @Override public void onStopViewChangeListener(ViewType viewType) { } @Override public void onClick(View view) { switch (view.getId()) { case R.id.imgUndo: mPhotoEditor.undo(); break; case R.id.imgRedo: mPhotoEditor.redo(); break; case R.id.imgSave: saveImage(); break; case R.id.imgClose: onBackPressed(); break; } } @SuppressLint("MissingPermission") private void saveImage() { if (requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { showLoading(getString(R.string.saving)); boolean exists = true; String FOLDER_NAME = "Indigenous"; File directory = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), FOLDER_NAME); if (!directory.exists()) { exists = directory.mkdirs(); } File file = null; if (exists) { file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + File.separator + "" + FOLDER_NAME + File.separator + "" + System.currentTimeMillis() + ".png"); } try { file.createNewFile(); SaveSettings saveSettings = new SaveSettings.Builder() .setClearViewsEnabled(true) .setTransparencyEnabled(true) .build(); final File finalFile = file; mPhotoEditor.saveAsFile(file.getAbsolutePath(), saveSettings, new PhotoEditor.OnSaveListener() { @Override public void onSuccess(@NonNull String imagePath) { hideLoading(); hasFilter = false; showSnackbar(getString(R.string.save_success)); mSaveImageUri = Uri.fromFile(new File(imagePath)); mPhotoEditorView.getSource().setImageURI(mSaveImageUri); MediaScannerConnection.scanFile(EditImageActivity.this, new String[] { finalFile.getAbsolutePath() }, null, null); } @Override public void onFailure(@NonNull Exception exception) { hideLoading(); showSnackbar(getString(R.string.save_error)); } }); } catch (IOException e) { hideLoading(); showSnackbar(Objects.requireNonNull(e.getMessage())); } } } @Override public void onColorChanged(int colorCode) { mPhotoEditor.setBrushColor(colorCode); mTxtCurrentTool.setText(R.string.label_brush); } @Override public void onOpacityChanged(int opacity) { mPhotoEditor.setOpacity(opacity); mTxtCurrentTool.setText(R.string.label_brush); } @Override public void onBrushSizeChanged(int brushSize) { mPhotoEditor.setBrushSize(brushSize); mTxtCurrentTool.setText(R.string.label_brush); } @Override public void onEmojiClick(String emojiUnicode) { mPhotoEditor.addEmoji(emojiUnicode); mTxtCurrentTool.setText(R.string.label_emoji); } @Override public void isPermissionGranted(boolean isGranted, String permission) { if (isGranted) { saveImage(); } } /** * Show save dialog. */ private void showSaveDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(getString(R.string.msg_save_image)); builder.setPositiveButton(getString(R.string.save), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { saveImage(); } }); builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); builder.setNeutralButton(getString(R.string.discard), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { checkSavedImage(); finish(); } }); builder.create().show(); } @Override public void onFilterSelected(PhotoFilter photoFilter) { hasFilter = photoFilter != PhotoFilter.NONE; mPhotoEditor.setFilterEffect(photoFilter); } @Override public void onToolSelected(ToolType toolType) { switch (toolType) { case BRUSH: mPhotoEditor.setBrushDrawingMode(true); mTxtCurrentTool.setText(R.string.label_brush); mPropertiesBSFragment.show(getSupportFragmentManager(), mPropertiesBSFragment.getTag()); break; case TEXT: TextEditorDialogFragment textEditorDialogFragment = TextEditorDialogFragment.show(this); textEditorDialogFragment.setOnTextEditorListener(new TextEditorDialogFragment.TextEditor() { @Override public void onDone(String inputText, int colorCode) { final TextStyleBuilder styleBuilder = new TextStyleBuilder(); styleBuilder.withTextColor(colorCode); mPhotoEditor.addText(inputText, styleBuilder); mTxtCurrentTool.setText(R.string.label_text); } }); break; case ERASER: mPhotoEditor.brushEraser(); mTxtCurrentTool.setText(R.string.label_eraser_mode); break; case FILTER: mTxtCurrentTool.setText(R.string.label_filter); showFilter(true); break; case EMOJI: mEmojiBSFragment.show(getSupportFragmentManager(), mEmojiBSFragment.getTag()); break; } } void showFilter(boolean isVisible) { mIsFilterVisible = isVisible; mConstraintSet.clone(mRootView); if (isVisible) { mConstraintSet.clear(mRvFilters.getId(), ConstraintSet.START); mConstraintSet.connect(mRvFilters.getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START); mConstraintSet.connect(mRvFilters.getId(), ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END); } else { mConstraintSet.connect(mRvFilters.getId(), ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.END); mConstraintSet.clear(mRvFilters.getId(), ConstraintSet.END); } ChangeBounds changeBounds = new ChangeBounds(); changeBounds.setDuration(350); changeBounds.setInterpolator(new AnticipateOvershootInterpolator(1.0f)); TransitionManager.beginDelayedTransition(mRootView, changeBounds); mConstraintSet.applyTo(mRootView); } @Override public void onBackPressed() { if (mIsFilterVisible) { showFilter(false); mTxtCurrentTool.setText(R.string.editor); } else if (!mPhotoEditor.isCacheEmpty() || hasFilter) { showSaveDialog(); } else { checkSavedImage(); finish(); } } /** * Check if an image was saved to set the uri and position. */ private void checkSavedImage() { if (mSaveImageUri != null) { Intent returnIntent = new Intent(); setResult(RESULT_OK, returnIntent); returnIntent.putExtra("imageEditedUri", mSaveImageUri.toString()); returnIntent.putExtra("imageEditedPosition", imagePosition); } } }