fedilab-Android-App/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java

586 lines
22 KiB
Java

package app.fedilab.android.imageeditor;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static app.fedilab.android.imageeditor.FileSaveHelper.isSdkHigherThan28;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AnticipateOvershootInterpolator;
import android.widget.Button;
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.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.exifinterface.media.ExifInterface;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.transition.ChangeBounds;
import androidx.transition.TransitionManager;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.theartofdev.edmodo.cropper.CropImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import app.fedilab.android.R;
import app.fedilab.android.imageeditor.base.BaseActivity;
import app.fedilab.android.imageeditor.filters.FilterListener;
import app.fedilab.android.imageeditor.filters.FilterViewAdapter;
import app.fedilab.android.imageeditor.tools.EditingToolsAdapter;
import app.fedilab.android.imageeditor.tools.ToolType;
import es.dmoral.toasty.Toasty;
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;
import ja.burhanrashid52.photoeditor.shape.ShapeBuilder;
import ja.burhanrashid52.photoeditor.shape.ShapeType;
public class EditImageActivity extends BaseActivity implements OnPhotoEditorListener,
View.OnClickListener,
PropertiesBSFragment.Properties,
ShapeBSFragment.Properties,
EmojiBSFragment.EmojiListener,
StickerBSFragment.StickerListener, EditingToolsAdapter.OnItemSelected, FilterListener {
public static final String FILE_PROVIDER_AUTHORITY = "com.burhanrashid52.photoeditor.fileprovider";
public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit";
public static final String PINCH_TEXT_SCALABLE_INTENT_KEY = "PINCH_TEXT_SCALABLE";
private static final int CAMERA_REQUEST = 52;
private static final int PICK_REQUEST = 53;
private final EditingToolsAdapter mEditingToolsAdapter = new EditingToolsAdapter(this);
private final FilterViewAdapter mFilterViewAdapter = new FilterViewAdapter(this);
private final ConstraintSet mConstraintSet = new ConstraintSet();
PhotoEditor mPhotoEditor;
@Nullable
@VisibleForTesting
Uri mSaveImageUri;
private PhotoEditorView mPhotoEditorView;
private PropertiesBSFragment mPropertiesBSFragment;
private ShapeBSFragment mShapeBSFragment;
private ShapeBuilder mShapeBuilder;
private EmojiBSFragment mEmojiBSFragment;
private StickerBSFragment mStickerBSFragment;
private TextView mTxtCurrentTool;
private Typeface mWonderFont;
private RecyclerView mRvTools, mRvFilters;
private ConstraintLayout mRootView;
private boolean mIsFilterVisible;
private Uri uri;
private boolean exit;
private FileSaveHelper mSaveFileHelper;
private static int exifToDegrees(int exifOrientation) {
if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) {
return 90;
} else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) {
return 180;
} else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) {
return 270;
}
return 0;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
makeFullScreen();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_image);
Bundle b = getIntent().getExtras();
if (getSupportActionBar() != null)
getSupportActionBar().hide();
String path = null;
if (b != null)
path = b.getString("imageUri", null);
if (path == null) {
finish();
}
uri = Uri.parse(path);
exit = false;
initViews();
handleIntentImage(mPhotoEditorView.getSource());
mWonderFont = Typeface.createFromAsset(getAssets(), "beyond_wonderland.ttf");
mPropertiesBSFragment = new PropertiesBSFragment();
mEmojiBSFragment = new EmojiBSFragment();
mStickerBSFragment = new StickerBSFragment();
mShapeBSFragment = new ShapeBSFragment();
mStickerBSFragment.setStickerListener(this);
mEmojiBSFragment.setEmojiListener(this);
mPropertiesBSFragment.setPropertiesChangeListener(this);
mShapeBSFragment.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);
// NOTE(lucianocheng): Used to set integration testing parameters to PhotoEditor
boolean pinchTextScalable = getIntent().getBooleanExtra(PINCH_TEXT_SCALABLE_INTENT_KEY, true);
//Typeface mTextRobotoTf = ResourcesCompat.getFont(this, R.font.roboto_medium);
//Typeface mEmojiTypeFace = Typeface.createFromAsset(getAssets(), "emojione-android.ttf");
Typeface mEmojiTypeFace = Typeface.createFromAsset(getAssets(), "emojione-android.ttf");
mPhotoEditor = new PhotoEditor.Builder(this, mPhotoEditorView)
.setPinchTextScalable(pinchTextScalable) // set flag to make text scalable when pinch
//.setDefaultTextTypeface(mTextRobotoTf)
.setPinchTextScalable(true)
.setDefaultEmojiTypeface(mEmojiTypeFace)
.build(); // build photo editor sdk
mPhotoEditor.setOnPhotoEditorListener(this);
//Set Image Dynamically
try {
mPhotoEditorView.getSource().setImageURI(uri);
} catch (Exception e) {
Toasty.error(EditImageActivity.this, getString(R.string.toast_error)).show();
}
if (uri != null) {
try (InputStream inputStream = getContentResolver().openInputStream(uri)) {
assert inputStream != null;
ExifInterface exif = new ExifInterface(inputStream);
int rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
int rotationInDegrees = exifToDegrees(rotation);
mPhotoEditorView.getSource().setRotation(rotationInDegrees);
} catch (Exception e) {
e.printStackTrace();
}
}
Button send = findViewById(R.id.send);
send.setOnClickListener(v -> {
exit = true;
saveImage();
});
}
private void handleIntentImage(ImageView source) {
Intent intent = getIntent();
if (intent != null) {
// NOTE(lucianocheng): Using "yoda conditions" here to guard against
// a null Action in the Intent.
if (Intent.ACTION_EDIT.equals(intent.getAction()) ||
ACTION_NEXTGEN_EDIT.equals(intent.getAction())) {
try {
Uri uri = intent.getData();
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
source.setImageBitmap(bitmap);
} catch (IOException e) {
e.printStackTrace();
}
} else {
String intentType = intent.getType();
if (intentType != null && intentType.startsWith("image/")) {
Uri imageUri = intent.getData();
if (imageUri != null) {
source.setImageURI(imageUri);
}
}
}
}
}
private void initViews() {
ImageView imgUndo;
ImageView imgRedo;
ImageView imgCamera;
ImageView imgGallery;
ImageView imgSave;
ImageView imgClose;
ImageView imgCrop;
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);
imgCamera = findViewById(R.id.imgCamera);
imgCamera.setOnClickListener(this);
imgGallery = findViewById(R.id.imgGallery);
imgGallery.setOnClickListener(this);
imgSave = findViewById(R.id.imgSave);
imgSave.setOnClickListener(this);
imgClose = findViewById(R.id.imgClose);
imgClose.setOnClickListener(this);
imgCrop = findViewById(R.id.imgCrop);
imgCrop.setOnClickListener(this);
}
@Override
public void onEditTextChangeListener(final View rootView, String text, int colorCode) {
TextEditorDialogFragment textEditorDialogFragment =
TextEditorDialogFragment.show(this, text, colorCode);
textEditorDialogFragment.setOnTextEditorListener((inputText, newColorCode) -> {
final TextStyleBuilder styleBuilder = new TextStyleBuilder();
styleBuilder.withTextColor(newColorCode);
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 onTouchSourceImage(MotionEvent event) {
}
@SuppressLint("NonConstantResourceId")
@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;
case R.id.imgCrop:
shareImage();
break;
case R.id.imgCamera:
Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(cameraIntent, CAMERA_REQUEST);
break;
case R.id.imgGallery:
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_REQUEST);
break;
}
}
private void shareImage() {
if (mSaveImageUri == null) {
//showSnackbar(getString(R.string.msg_save_image_to_share));
return;
}
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_STREAM, buildFileProviderUri(mSaveImageUri));
startActivity(Intent.createChooser(intent, getString(R.string.msg_share_image)));
}
private Uri buildFileProviderUri(@NonNull Uri uri) {
return FileProvider.getUriForFile(this,
FILE_PROVIDER_AUTHORITY,
new File(uri.getPath()));
}
private void saveImage() {
final String fileName = System.currentTimeMillis() + ".png";
final boolean hasStoragePermission =
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PERMISSION_GRANTED;
if (hasStoragePermission || isSdkHigherThan28()) {
showLoading("Saving...");
mSaveFileHelper.createFile(fileName, (fileCreated, filePath, error, uri) -> {
if (fileCreated) {
SaveSettings saveSettings = new SaveSettings.Builder()
.setClearViewsEnabled(true)
.setTransparencyEnabled(true)
.build();
mPhotoEditor.saveAsFile(filePath, saveSettings, new PhotoEditor.OnSaveListener() {
@Override
public void onSuccess(@NonNull String imagePath) {
mSaveFileHelper.notifyThatFileIsNowPubliclyAvailable(getContentResolver());
hideLoading();
showSnackbar("Image Saved Successfully");
mSaveImageUri = uri;
mPhotoEditorView.getSource().setImageURI(mSaveImageUri);
}
@Override
public void onFailure(@NonNull Exception exception) {
hideLoading();
showSnackbar("Failed to save Image");
}
});
} else {
hideLoading();
showSnackbar(error);
}
});
} else {
requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
ExifInterface exif;
int rotation;
int rotationInDegrees = 0;
if (data != null && data.getData() != null) {
try (InputStream inputStream = getContentResolver().openInputStream(data.getData())) {
assert inputStream != null;
exif = new ExifInterface(inputStream);
rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
rotationInDegrees = exifToDegrees(rotation);
} catch (Exception e) {
e.printStackTrace();
}
}
switch (requestCode) {
case CAMERA_REQUEST:
if (data != null && data.getExtras() != null) {
mPhotoEditor.clearAllViews();
Bitmap photo = (Bitmap) data.getExtras().get("data");
mPhotoEditorView.getSource().setImageBitmap(photo);
mPhotoEditorView.getSource().setRotation(rotationInDegrees);
}
break;
case PICK_REQUEST:
if (data != null && data.getData() != null) {
try {
mPhotoEditor.clearAllViews();
Uri uri = data.getData();
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri);
mPhotoEditorView.getSource().setImageBitmap(bitmap);
mPhotoEditorView.getSource().setRotation(rotationInDegrees);
} catch (IOException e) {
e.printStackTrace();
}
}
break;
case CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE:
CropImage.ActivityResult result = CropImage.getActivityResult(data);
if (result != null) {
Uri resultUri = result.getUri();
if (resultUri != null) {
mPhotoEditorView.getSource().setImageURI(resultUri);
mPhotoEditorView.getSource().setRotation(rotationInDegrees);
if (uri != null && uri.getPath() != null) {
File fdelete = new File(uri.getPath());
if (fdelete.exists()) {
//noinspection ResultOfMethodCallIgnored
fdelete.delete();
}
}
uri = resultUri;
}
}
break;
}
}
}
@Override
public void onColorChanged(int colorCode) {
mPhotoEditor.setShape(mShapeBuilder.withShapeColor(colorCode));
mTxtCurrentTool.setText(R.string.label_brush);
}
@Override
public void onOpacityChanged(int opacity) {
mPhotoEditor.setShape(mShapeBuilder.withShapeOpacity(opacity));
mTxtCurrentTool.setText(R.string.label_brush);
}
@Override
public void onShapeSizeChanged(int shapeSize) {
mPhotoEditor.setShape(mShapeBuilder.withShapeSize(shapeSize));
mTxtCurrentTool.setText(R.string.label_brush);
}
@Override
public void onShapePicked(ShapeType shapeType) {
mPhotoEditor.setShape(mShapeBuilder.withShapeType(shapeType));
}
@Override
public void onEmojiClick(String emojiUnicode) {
mPhotoEditor.addEmoji(emojiUnicode);
mTxtCurrentTool.setText(R.string.label_emoji);
}
@Override
public void onStickerClick(Bitmap bitmap) {
mPhotoEditor.addImage(bitmap);
mTxtCurrentTool.setText(R.string.label_sticker);
}
private void showSaveDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(getString(R.string.msg_save_image));
builder.setPositiveButton("Save", (dialog, which) -> saveImage());
builder.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss());
builder.setNeutralButton("Discard", (dialog, which) -> finish());
builder.create().show();
}
@Override
public void onFilterSelected(PhotoFilter photoFilter) {
mPhotoEditor.setFilterEffect(photoFilter);
}
@Override
public void onToolSelected(ToolType toolType) {
switch (toolType) {
case SHAPE:
mPhotoEditor.setBrushDrawingMode(true);
mShapeBuilder = new ShapeBuilder();
mPhotoEditor.setShape(mShapeBuilder);
mTxtCurrentTool.setText(R.string.label_shape);
showBottomSheetDialogFragment(mShapeBSFragment);
break;
case TEXT:
TextEditorDialogFragment textEditorDialogFragment = TextEditorDialogFragment.show(this);
textEditorDialogFragment.setOnTextEditorListener((inputText, 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:
showBottomSheetDialogFragment(mEmojiBSFragment);
break;
case STICKER:
showBottomSheetDialogFragment(mStickerBSFragment);
break;
case BRUSH:
mPhotoEditor.setBrushDrawingMode(true);
mTxtCurrentTool.setText(R.string.label_brush);
mPropertiesBSFragment.show(getSupportFragmentManager(), mPropertiesBSFragment.getTag());
break;
}
}
private void showBottomSheetDialogFragment(BottomSheetDialogFragment fragment) {
if (fragment == null || fragment.isAdded()) {
return;
}
fragment.show(getSupportFragmentManager(), fragment.getTag());
}
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.app_name);
} else if (!mPhotoEditor.isCacheEmpty()) {
showSaveDialog();
} else {
super.onBackPressed();
}
}
}