diff --git a/.gitignore b/.gitignore index 050d01186..471debea6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ .externalNativeBuild .cxx local.properties +/cropper/build/ diff --git a/app/build.gradle b/app/build.gradle index df0147226..3d47323d6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -71,7 +71,7 @@ allprojects { } dependencies { implementation project(':autoimageslider') - implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'androidx.appcompat:appcompat:1.4.2' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation "com.google.code.gson:gson:2.8.6" @@ -91,6 +91,11 @@ dependencies { } implementation project(path: ':mytransl') implementation project(path: ':ratethisapp') + + + + implementation 'com.burhanrashid52:photoeditor:1.5.1' + implementation project(path: ':cropper') annotationProcessor "com.github.bumptech.glide:compiler:4.12.0" implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'com.github.penfeizhou.android.animation:apng:2.17.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c5fd2dbb8..45a825d14 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,6 +57,9 @@ + { + + private final List colorPickerColors; + private Context context; + private LayoutInflater inflater; + private OnColorPickerClickListener onColorPickerClickListener; + + ColorPickerAdapter(@NonNull Context context, @NonNull List colorPickerColors) { + this.context = context; + this.inflater = LayoutInflater.from(context); + this.colorPickerColors = colorPickerColors; + } + + ColorPickerAdapter(@NonNull Context context) { + this(context, getDefaultColors(context)); + this.context = context; + this.inflater = LayoutInflater.from(context); + } + + public static List getDefaultColors(Context context) { + ArrayList colorPickerColors = new ArrayList<>(); + colorPickerColors.add(ContextCompat.getColor(context, R.color.blue_color_picker)); + colorPickerColors.add(ContextCompat.getColor(context, R.color.brown_color_picker)); + colorPickerColors.add(ContextCompat.getColor(context, R.color.green_color_picker)); + colorPickerColors.add(ContextCompat.getColor(context, R.color.orange_color_picker)); + colorPickerColors.add(ContextCompat.getColor(context, R.color.red_color_picker)); + colorPickerColors.add(ContextCompat.getColor(context, R.color.black)); + colorPickerColors.add(ContextCompat.getColor(context, R.color.red_orange_color_picker)); + colorPickerColors.add(ContextCompat.getColor(context, R.color.sky_blue_color_picker)); + colorPickerColors.add(ContextCompat.getColor(context, R.color.violet_color_picker)); + colorPickerColors.add(ContextCompat.getColor(context, R.color.white)); + colorPickerColors.add(ContextCompat.getColor(context, R.color.yellow_color_picker)); + colorPickerColors.add(ContextCompat.getColor(context, R.color.yellow_green_color_picker)); + return colorPickerColors; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = inflater.inflate(R.layout.color_picker_item_list, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + holder.colorPickerView.setBackgroundColor(colorPickerColors.get(position)); + } + + @Override + public int getItemCount() { + return colorPickerColors.size(); + } + + private void buildColorPickerView(View view, int colorCode) { + view.setVisibility(View.VISIBLE); + + ShapeDrawable biggerCircle = new ShapeDrawable(new OvalShape()); + biggerCircle.setIntrinsicHeight(20); + biggerCircle.setIntrinsicWidth(20); + biggerCircle.setBounds(new Rect(0, 0, 20, 20)); + biggerCircle.getPaint().setColor(colorCode); + + ShapeDrawable smallerCircle = new ShapeDrawable(new OvalShape()); + smallerCircle.setIntrinsicHeight(5); + smallerCircle.setIntrinsicWidth(5); + smallerCircle.setBounds(new Rect(0, 0, 5, 5)); + smallerCircle.getPaint().setColor(Color.WHITE); + smallerCircle.setPadding(10, 10, 10, 10); + Drawable[] drawables = {smallerCircle, biggerCircle}; + + LayerDrawable layerDrawable = new LayerDrawable(drawables); + + view.setBackgroundDrawable(layerDrawable); + } + + public void setOnColorPickerClickListener(OnColorPickerClickListener onColorPickerClickListener) { + this.onColorPickerClickListener = onColorPickerClickListener; + } + + public interface OnColorPickerClickListener { + void onColorPickerClickListener(int colorCode); + } + + class ViewHolder extends RecyclerView.ViewHolder { + View colorPickerView; + + public ViewHolder(View itemView) { + super(itemView); + colorPickerView = itemView.findViewById(R.id.color_picker_view); + itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onColorPickerClickListener != null) + onColorPickerClickListener.onColorPickerClickListener(colorPickerColors.get(getAdapterPosition())); + } + }); + } + } +} diff --git a/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java b/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java new file mode 100644 index 000000000..cc02bb142 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java @@ -0,0 +1,585 @@ +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(); + } + } +} diff --git a/app/src/main/java/app/fedilab/android/imageeditor/EmojiBSFragment.java b/app/src/main/java/app/fedilab/android/imageeditor/EmojiBSFragment.java new file mode 100644 index 000000000..61baa135c --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/EmojiBSFragment.java @@ -0,0 +1,140 @@ +package app.fedilab.android.imageeditor; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import java.util.ArrayList; + +import app.fedilab.android.R; + +public class EmojiBSFragment extends BottomSheetDialogFragment { + + private final BottomSheetBehavior.BottomSheetCallback mBottomSheetBehaviorCallback = new BottomSheetBehavior.BottomSheetCallback() { + + @Override + public void onStateChanged(@NonNull View bottomSheet, int newState) { + if (newState == BottomSheetBehavior.STATE_HIDDEN) { + dismiss(); + } + + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) { + } + }; + private EmojiListener mEmojiListener; + + public EmojiBSFragment() { + // Required empty public constructor + } + + /** + * Provide the list of emoji in form of unicode string + * + * @param context context + * @return list of emoji unicode + */ + public static ArrayList getEmojis(Context context) { + ArrayList convertedEmojiList = new ArrayList<>(); + String[] emojiList = context.getResources().getStringArray(R.array.photo_editor_emoji); + for (String emojiUnicode : emojiList) { + convertedEmojiList.add(convertEmoji(emojiUnicode)); + } + return convertedEmojiList; + } + + private static String convertEmoji(String emoji) { + String returnedEmoji; + try { + int convertEmojiToInt = Integer.parseInt(emoji.substring(2), 16); + returnedEmoji = new String(Character.toChars(convertEmojiToInt)); + } catch (NumberFormatException e) { + returnedEmoji = ""; + } + return returnedEmoji; + } + + @SuppressLint("RestrictedApi") + @Override + public void setupDialog(Dialog dialog, int style) { + super.setupDialog(dialog, style); + View contentView = View.inflate(getContext(), R.layout.fragment_bottom_sticker_emoji_dialog, null); + dialog.setContentView(contentView); + CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) ((View) contentView.getParent()).getLayoutParams(); + CoordinatorLayout.Behavior behavior = params.getBehavior(); + + if (behavior != null && behavior instanceof BottomSheetBehavior) { + ((BottomSheetBehavior) behavior).setBottomSheetCallback(mBottomSheetBehaviorCallback); + } + ((View) contentView.getParent()).setBackgroundColor(getResources().getColor(android.R.color.transparent)); + RecyclerView rvEmoji = contentView.findViewById(R.id.rvEmoji); + + GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 5); + rvEmoji.setLayoutManager(gridLayoutManager); + EmojiAdapter emojiAdapter = new EmojiAdapter(); + rvEmoji.setAdapter(emojiAdapter); + } + + public void setEmojiListener(EmojiListener emojiListener) { + mEmojiListener = emojiListener; + } + + public interface EmojiListener { + void onEmojiClick(String emojiUnicode); + } + + public class EmojiAdapter extends RecyclerView.Adapter { + + ArrayList emojisList = getEmojis(getActivity()); + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_emoji, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + holder.txtEmoji.setText(emojisList.get(position)); + } + + @Override + public int getItemCount() { + return emojisList.size(); + } + + class ViewHolder extends RecyclerView.ViewHolder { + TextView txtEmoji; + + ViewHolder(View itemView) { + super(itemView); + txtEmoji = itemView.findViewById(R.id.txtEmoji); + + itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mEmojiListener != null) { + mEmojiListener.onEmojiClick(emojisList.get(getLayoutPosition())); + } + dismiss(); + } + }); + } + } + } +} + diff --git a/app/src/main/java/app/fedilab/android/imageeditor/FileSaveHelper.java b/app/src/main/java/app/fedilab/android/imageeditor/FileSaveHelper.java new file mode 100644 index 000000000..78fd8be44 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/FileSaveHelper.java @@ -0,0 +1,185 @@ +package app.fedilab.android.imageeditor; + +import android.annotation.SuppressLint; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.Observer; +import androidx.lifecycle.OnLifecycleEvent; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * General contract of this class is to + * create a file on a device. + *
+ * How to Use it- + * Call {@linkplain FileSaveHelper#createFile(String, OnFileCreateResult)} + * if file is created you would receive it's file path and Uri + * and after you are done with File call {@linkplain FileSaveHelper#notifyThatFileIsNowPubliclyAvailable(ContentResolver)} + *
+ * Remember! in order to shutdown executor call {@linkplain FileSaveHelper#addObserver(LifecycleOwner)} or + * create object with the {@linkplain FileSaveHelper#FileSaveHelper(AppCompatActivity)} + */ +public class FileSaveHelper implements LifecycleObserver { + private final ContentResolver mContentResolver; + private final ExecutorService executor; + private final MutableLiveData fileCreatedResult; + private OnFileCreateResult resultListener; + private final Observer observer = fileMeta -> { + if (resultListener != null) { + resultListener.onFileCreateResult(fileMeta.isCreated, + fileMeta.filePath, + fileMeta.error, + fileMeta.uri); + } + }; + + + public FileSaveHelper(ContentResolver contentResolver) { + mContentResolver = contentResolver; + executor = Executors.newSingleThreadExecutor(); + fileCreatedResult = new MutableLiveData<>(); + } + + public FileSaveHelper(AppCompatActivity activity) { + this(activity.getContentResolver()); + addObserver(activity); + } + + public static boolean isSdkHigherThan28() { + return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q); + } + + private void addObserver(LifecycleOwner lifecycleOwner) { + fileCreatedResult.observe(lifecycleOwner, observer); + lifecycleOwner.getLifecycle().addObserver(this); + } + + @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) + public void release() { + if (null != executor) { + executor.shutdownNow(); + } + } + + /** + * The effects of this method are + * 1- insert new Image File data in MediaStore.Images column + * 2- create File on Disk. + * + * @param fileNameToSave fileName + * @param listener result listener + */ + + public void createFile(final String fileNameToSave, OnFileCreateResult listener) { + this.resultListener = listener; + executor.submit(() -> { + Cursor cursor = null; + String filePath; + try { + final ContentValues newImageDetails = new ContentValues(); + Uri imageCollection = buildUriCollection(newImageDetails); + final Uri editedImageUri = getEditedImageUri(fileNameToSave, newImageDetails, imageCollection); + filePath = getFilePath(cursor, editedImageUri); + updateResult(true, filePath, null, editedImageUri, newImageDetails); + } catch (final Exception ex) { + ex.printStackTrace(); + updateResult(false, null, ex.getMessage(), null, null); + } finally { + if (cursor != null) { + cursor.close(); + } + } + }); + } + + private String getFilePath(Cursor cursor, Uri editedImageUri) { + String[] proj = {MediaStore.Images.Media.DATA}; + cursor = mContentResolver.query(editedImageUri, proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } + + private Uri getEditedImageUri(String fileNameToSave, ContentValues newImageDetails, Uri imageCollection) throws IOException { + newImageDetails.put(MediaStore.Images.Media.DISPLAY_NAME, fileNameToSave); + final Uri editedImageUri = mContentResolver.insert(imageCollection, newImageDetails); + final OutputStream outputStream = mContentResolver.openOutputStream(editedImageUri); + outputStream.close(); + return editedImageUri; + } + + @SuppressLint("InlinedApi") + private Uri buildUriCollection(ContentValues newImageDetails) { + Uri imageCollection; + if (isSdkHigherThan28()) { + imageCollection = MediaStore.Images.Media.getContentUri( + MediaStore.VOLUME_EXTERNAL_PRIMARY + ); + newImageDetails.put(MediaStore.Images.Media.IS_PENDING, 1); + } else { + imageCollection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } + return imageCollection; + } + + @SuppressLint("InlinedApi") + public void notifyThatFileIsNowPubliclyAvailable(ContentResolver contentResolver) { + if (isSdkHigherThan28()) { + executor.submit(() -> { + FileMeta value = fileCreatedResult.getValue(); + if (value != null) { + value.imageDetails.clear(); + value.imageDetails.put(MediaStore.Images.Media.IS_PENDING, 0); + contentResolver.update(value.uri, value.imageDetails, null, null); + } + }); + } + } + + private void updateResult(boolean result, String filePath, String error, Uri uri, ContentValues newImageDetails) { + fileCreatedResult.postValue(new FileMeta(result, filePath, uri, error, newImageDetails)); + } + + public interface OnFileCreateResult { + /** + * @param created whether file creation is success or failure + * @param filePath filepath on disk. null in case of failure + * @param error in case file creation is failed . it would represent the cause + * @param Uri Uri to the newly created file. null in case of failure + */ + void onFileCreateResult(boolean created, String filePath, String error, Uri Uri); + } + + private static class FileMeta { + public ContentValues imageDetails; + public boolean isCreated; + public String filePath; + public Uri uri; + public String error; + + public FileMeta(boolean isCreated, String filePath, + Uri uri, String error, + ContentValues newImageDetails) { + this.isCreated = isCreated; + this.filePath = filePath; + this.uri = uri; + this.error = error; + this.imageDetails = newImageDetails; + } + } + +} diff --git a/app/src/main/java/app/fedilab/android/imageeditor/PhotoApp.java b/app/src/main/java/app/fedilab/android/imageeditor/PhotoApp.java new file mode 100644 index 000000000..23d1c058e --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/PhotoApp.java @@ -0,0 +1,53 @@ +package app.fedilab.android.imageeditor; + +import android.app.Application; +import android.content.Context; + +/** + * Created by Burhanuddin Rashid on 1/23/2018. + */ + +public class PhotoApp extends Application { + private static final String TAG = PhotoApp.class.getSimpleName(); + private static PhotoApp sPhotoApp; + + public static PhotoApp getPhotoApp() { + return sPhotoApp; + } + + @Override + public void onCreate() { + super.onCreate(); + sPhotoApp = this; + /* FontRequest fontRequest = new FontRequest( + "com.google.android.gms.fonts", + "com.google.android.gms", + "Noto Color Emoji Compat", + R.array.com_google_android_gms_fonts_certs); + + EmojiCompat.Config config = new FontRequestEmojiCompatConfig(this, fontRequest) + .setReplaceAll(true) + // .setEmojiSpanIndicatorEnabled(true) + // .setEmojiSpanIndicatorColor(Color.GREEN) + .registerInitCallback(new EmojiCompat.InitCallback() { + @Override + public void onInitialized() { + super.onInitialized(); + Log.e(TAG, "Success"); + } + + @Override + public void onFailed(@Nullable Throwable throwable) { + super.onFailed(throwable); + Log.e(TAG, "onFailed: " + throwable.getMessage()); + } + }); + + // BundledEmojiCompatConfig bundledEmojiCompatConfig = new BundledEmojiCompatConfig(this); + EmojiCompat.init(config);*/ + } + + public Context getContext() { + return sPhotoApp.getContext(); + } +} diff --git a/app/src/main/java/app/fedilab/android/imageeditor/PropertiesBSFragment.java b/app/src/main/java/app/fedilab/android/imageeditor/PropertiesBSFragment.java new file mode 100644 index 000000000..aac1ed598 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/PropertiesBSFragment.java @@ -0,0 +1,99 @@ +package app.fedilab.android.imageeditor; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SeekBar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import app.fedilab.android.R; + +public class PropertiesBSFragment extends BottomSheetDialogFragment implements SeekBar.OnSeekBarChangeListener { + + private Properties mProperties; + + public PropertiesBSFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_bottom_properties_dialog, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + RecyclerView rvColor = view.findViewById(R.id.rvColors); + SeekBar sbOpacity = view.findViewById(R.id.sbOpacity); + SeekBar sbBrushSize = view.findViewById(R.id.sbSize); + + sbOpacity.setOnSeekBarChangeListener(this); + sbBrushSize.setOnSeekBarChangeListener(this); + + LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false); + rvColor.setLayoutManager(layoutManager); + rvColor.setHasFixedSize(true); + ColorPickerAdapter colorPickerAdapter = new ColorPickerAdapter(getActivity()); + colorPickerAdapter.setOnColorPickerClickListener(new ColorPickerAdapter.OnColorPickerClickListener() { + @Override + public void onColorPickerClickListener(int colorCode) { + if (mProperties != null) { + dismiss(); + mProperties.onColorChanged(colorCode); + } + } + }); + rvColor.setAdapter(colorPickerAdapter); + } + + public void setPropertiesChangeListener(Properties properties) { + mProperties = properties; + } + + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + switch (seekBar.getId()) { + case R.id.sbOpacity: + if (mProperties != null) { + mProperties.onOpacityChanged(i); + } + break; + case R.id.sbSize: + if (mProperties != null) { + mProperties.onShapeSizeChanged(i); + } + break; + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + + public interface Properties { + void onColorChanged(int colorCode); + + void onOpacityChanged(int opacity); + + void onShapeSizeChanged(int shapeSize); + } +} diff --git a/app/src/main/java/app/fedilab/android/imageeditor/ShapeBSFragment.java b/app/src/main/java/app/fedilab/android/imageeditor/ShapeBSFragment.java new file mode 100644 index 000000000..12dca8794 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/ShapeBSFragment.java @@ -0,0 +1,112 @@ +package app.fedilab.android.imageeditor; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RadioGroup; +import android.widget.SeekBar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import app.fedilab.android.R; +import ja.burhanrashid52.photoeditor.shape.ShapeType; + +public class ShapeBSFragment extends BottomSheetDialogFragment implements SeekBar.OnSeekBarChangeListener { + + private Properties mProperties; + + public ShapeBSFragment() { + // Required empty public constructor + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_bottom_shapes_dialog, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + RecyclerView rvColor = view.findViewById(R.id.shapeColors); + SeekBar sbOpacity = view.findViewById(R.id.shapeOpacity); + SeekBar sbBrushSize = view.findViewById(R.id.shapeSize); + RadioGroup shapeGroup = view.findViewById(R.id.shapeRadioGroup); + + // shape picker + shapeGroup.setOnCheckedChangeListener((group, checkedId) -> { + if (checkedId == R.id.lineRadioButton) { + mProperties.onShapePicked(ShapeType.LINE); + } else if (checkedId == R.id.ovalRadioButton) { + mProperties.onShapePicked(ShapeType.OVAL); + } else if (checkedId == R.id.rectRadioButton) { + mProperties.onShapePicked(ShapeType.RECTANGLE); + } else { + mProperties.onShapePicked(ShapeType.BRUSH); + } + }); + + sbOpacity.setOnSeekBarChangeListener(this); + sbBrushSize.setOnSeekBarChangeListener(this); + + LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false); + rvColor.setLayoutManager(layoutManager); + rvColor.setHasFixedSize(true); + ColorPickerAdapter colorPickerAdapter = new ColorPickerAdapter(getActivity()); + colorPickerAdapter.setOnColorPickerClickListener(colorCode -> { + if (mProperties != null) { + dismiss(); + mProperties.onColorChanged(colorCode); + } + }); + rvColor.setAdapter(colorPickerAdapter); + } + + public void setPropertiesChangeListener(Properties properties) { + mProperties = properties; + } + + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + switch (seekBar.getId()) { + case R.id.shapeOpacity: + if (mProperties != null) { + mProperties.onOpacityChanged(i); + } + break; + case R.id.shapeSize: + if (mProperties != null) { + mProperties.onShapeSizeChanged(i); + } + break; + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + + public interface Properties { + void onColorChanged(int colorCode); + + void onOpacityChanged(int opacity); + + void onShapeSizeChanged(int shapeSize); + + void onShapePicked(ShapeType shapeType); + } +} diff --git a/app/src/main/java/app/fedilab/android/imageeditor/StickerBSFragment.java b/app/src/main/java/app/fedilab/android/imageeditor/StickerBSFragment.java new file mode 100644 index 000000000..370f9a110 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/StickerBSFragment.java @@ -0,0 +1,137 @@ +package app.fedilab.android.imageeditor; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import app.fedilab.android.R; + +public class StickerBSFragment extends BottomSheetDialogFragment { + + private final BottomSheetBehavior.BottomSheetCallback mBottomSheetBehaviorCallback = new BottomSheetBehavior.BottomSheetCallback() { + + @Override + public void onStateChanged(@NonNull View bottomSheet, int newState) { + if (newState == BottomSheetBehavior.STATE_HIDDEN) { + dismiss(); + } + + } + + @Override + public void onSlide(@NonNull View bottomSheet, float slideOffset) { + } + }; + private StickerListener mStickerListener; + + public StickerBSFragment() { + // Required empty public constructor + } + + public void setStickerListener(StickerListener stickerListener) { + mStickerListener = stickerListener; + } + + @SuppressLint("RestrictedApi") + @Override + public void setupDialog(Dialog dialog, int style) { + super.setupDialog(dialog, style); + View contentView = View.inflate(getContext(), R.layout.fragment_bottom_sticker_emoji_dialog, null); + dialog.setContentView(contentView); + CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) ((View) contentView.getParent()).getLayoutParams(); + CoordinatorLayout.Behavior behavior = params.getBehavior(); + + if (behavior != null && behavior instanceof BottomSheetBehavior) { + ((BottomSheetBehavior) behavior).setBottomSheetCallback(mBottomSheetBehaviorCallback); + } + ((View) contentView.getParent()).setBackgroundColor(getResources().getColor(android.R.color.transparent)); + RecyclerView rvEmoji = contentView.findViewById(R.id.rvEmoji); + + GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), 3); + rvEmoji.setLayoutManager(gridLayoutManager); + StickerAdapter stickerAdapter = new StickerAdapter(); + rvEmoji.setAdapter(stickerAdapter); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + } + + private String convertEmoji(String emoji) { + String returnedEmoji = ""; + try { + int convertEmojiToInt = Integer.parseInt(emoji.substring(2), 16); + returnedEmoji = getEmojiByUnicode(convertEmojiToInt); + } catch (NumberFormatException e) { + returnedEmoji = ""; + } + return returnedEmoji; + } + + private String getEmojiByUnicode(int unicode) { + return new String(Character.toChars(unicode)); + } + + public interface StickerListener { + void onStickerClick(Bitmap bitmap); + } + + public class StickerAdapter extends RecyclerView.Adapter { + + int[] stickerList = new int[]{R.drawable.aa, R.drawable.bb}; + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_sticker, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + holder.imgSticker.setImageResource(stickerList[position]); + } + + @Override + public int getItemCount() { + return stickerList.length; + } + + class ViewHolder extends RecyclerView.ViewHolder { + ImageView imgSticker; + + ViewHolder(View itemView) { + super(itemView); + imgSticker = itemView.findViewById(R.id.imgSticker); + + itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mStickerListener != null) { + mStickerListener.onStickerClick( + BitmapFactory.decodeResource(getResources(), + stickerList[getLayoutPosition()])); + } + dismiss(); + } + }); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/imageeditor/TextEditorDialogFragment.java b/app/src/main/java/app/fedilab/android/imageeditor/TextEditorDialogFragment.java new file mode 100644 index 000000000..936056d5c --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/TextEditorDialogFragment.java @@ -0,0 +1,130 @@ +package app.fedilab.android.imageeditor; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import app.fedilab.android.R; + +/** + * Created by Burhanuddin Rashid on 1/16/2018. + */ + +public class TextEditorDialogFragment extends DialogFragment { + + public static final String TAG = TextEditorDialogFragment.class.getSimpleName(); + public static final String EXTRA_INPUT_TEXT = "extra_input_text"; + public static final String EXTRA_COLOR_CODE = "extra_color_code"; + private EditText mAddTextEditText; + private TextView mAddTextDoneTextView; + private InputMethodManager mInputMethodManager; + private int mColorCode; + private TextEditor mTextEditor; + + //Show dialog with provide text and text color + public static TextEditorDialogFragment show(@NonNull AppCompatActivity appCompatActivity, + @NonNull String inputText, + @ColorInt int colorCode) { + Bundle args = new Bundle(); + args.putString(EXTRA_INPUT_TEXT, inputText); + args.putInt(EXTRA_COLOR_CODE, colorCode); + TextEditorDialogFragment fragment = new TextEditorDialogFragment(); + fragment.setArguments(args); + fragment.show(appCompatActivity.getSupportFragmentManager(), TAG); + return fragment; + } + + //Show dialog with default text input as empty and text color white + public static TextEditorDialogFragment show(@NonNull AppCompatActivity appCompatActivity) { + return show(appCompatActivity, + "", ContextCompat.getColor(appCompatActivity, R.color.white)); + } + + @Override + public void onStart() { + super.onStart(); + Dialog dialog = getDialog(); + //Make dialog full screen with transparent background + if (dialog != null) { + int width = ViewGroup.LayoutParams.MATCH_PARENT; + int height = ViewGroup.LayoutParams.MATCH_PARENT; + dialog.getWindow().setLayout(width, height); + dialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); + } + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.add_text_dialog, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mAddTextEditText = view.findViewById(R.id.add_text_edit_text); + mInputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + mAddTextDoneTextView = view.findViewById(R.id.add_text_done_tv); + + //Setup the color picker for text color + RecyclerView addTextColorPickerRecyclerView = view.findViewById(R.id.add_text_color_picker_recycler_view); + LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false); + addTextColorPickerRecyclerView.setLayoutManager(layoutManager); + addTextColorPickerRecyclerView.setHasFixedSize(true); + ColorPickerAdapter colorPickerAdapter = new ColorPickerAdapter(getActivity()); + //This listener will change the text color when clicked on any color from picker + colorPickerAdapter.setOnColorPickerClickListener(new ColorPickerAdapter.OnColorPickerClickListener() { + @Override + public void onColorPickerClickListener(int colorCode) { + mColorCode = colorCode; + mAddTextEditText.setTextColor(colorCode); + } + }); + addTextColorPickerRecyclerView.setAdapter(colorPickerAdapter); + mAddTextEditText.setText(getArguments().getString(EXTRA_INPUT_TEXT)); + mColorCode = getArguments().getInt(EXTRA_COLOR_CODE); + mAddTextEditText.setTextColor(mColorCode); + mInputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + + //Make a callback on activity when user is done with text editing + mAddTextDoneTextView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + mInputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + dismiss(); + String inputText = mAddTextEditText.getText().toString(); + if (!TextUtils.isEmpty(inputText) && mTextEditor != null) { + mTextEditor.onDone(inputText, mColorCode); + } + } + }); + + } + + //Callback to listener if user is done with text editing + public void setOnTextEditorListener(TextEditor textEditor) { + mTextEditor = textEditor; + } + + + public interface TextEditor { + void onDone(String inputText, int colorCode); + } +} diff --git a/app/src/main/java/app/fedilab/android/imageeditor/base/BaseActivity.java b/app/src/main/java/app/fedilab/android/imageeditor/base/BaseActivity.java new file mode 100644 index 000000000..3a7d262dd --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/base/BaseActivity.java @@ -0,0 +1,79 @@ +package app.fedilab.android.imageeditor.base; + +import android.app.ProgressDialog; +import android.content.pm.PackageManager; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import com.google.android.material.snackbar.Snackbar; + +/** + * Created by Burhanuddin Rashid on 1/17/2018. + */ + +public class BaseActivity extends AppCompatActivity { + + public static final int READ_WRITE_STORAGE = 52; + private ProgressDialog mProgressDialog; + + + public boolean requestPermission(String permission) { + boolean isGranted = ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED; + if (!isGranted) { + ActivityCompat.requestPermissions( + this, + new String[]{permission}, + READ_WRITE_STORAGE); + } + return isGranted; + } + + public void isPermissionGranted(boolean isGranted, String permission) { + + } + + public void makeFullScreen() { + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + switch (requestCode) { + case READ_WRITE_STORAGE: + isPermissionGranted(grantResults[0] == PackageManager.PERMISSION_GRANTED, permissions[0]); + break; + } + } + + protected void showLoading(@NonNull String message) { + mProgressDialog = new ProgressDialog(this); + mProgressDialog.setMessage(message); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + mProgressDialog.setCancelable(false); + mProgressDialog.show(); + } + + protected void hideLoading() { + if (mProgressDialog != null) { + mProgressDialog.dismiss(); + } + } + + protected void showSnackbar(@NonNull String message) { + View view = findViewById(android.R.id.content); + if (view != null) { + Snackbar.make(view, message, Snackbar.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + } +} diff --git a/app/src/main/java/app/fedilab/android/imageeditor/base/BaseFragment.java b/app/src/main/java/app/fedilab/android/imageeditor/base/BaseFragment.java new file mode 100644 index 000000000..8e798b42a --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/base/BaseFragment.java @@ -0,0 +1,29 @@ +package app.fedilab.android.imageeditor.base; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +/** + * @author Burhanuddin Rashid + * @version 0.1.2 + * @since 5/25/2018 + */ +public abstract class BaseFragment extends Fragment { + + protected abstract int getLayoutId(); + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + if (getLayoutId() == 0) { + throw new IllegalArgumentException("Invalid layout id"); + } + return inflater.inflate(getLayoutId(), container, false); + } +} diff --git a/app/src/main/java/app/fedilab/android/imageeditor/filters/FilterListener.java b/app/src/main/java/app/fedilab/android/imageeditor/filters/FilterListener.java new file mode 100644 index 000000000..1d627ce93 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/filters/FilterListener.java @@ -0,0 +1,7 @@ +package app.fedilab.android.imageeditor.filters; + +import ja.burhanrashid52.photoeditor.PhotoFilter; + +public interface FilterListener { + void onFilterSelected(PhotoFilter photoFilter); +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/imageeditor/filters/FilterViewAdapter.java b/app/src/main/java/app/fedilab/android/imageeditor/filters/FilterViewAdapter.java new file mode 100644 index 000000000..077e21c07 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/filters/FilterViewAdapter.java @@ -0,0 +1,115 @@ +package app.fedilab.android.imageeditor.filters; + +import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import app.fedilab.android.R; +import ja.burhanrashid52.photoeditor.PhotoFilter; + +/** + * @author Burhanuddin Rashid + * @version 0.1.2 + * @since 5/23/2018 + */ +public class FilterViewAdapter extends RecyclerView.Adapter { + + private final FilterListener mFilterListener; + private final List> mPairList = new ArrayList<>(); + + public FilterViewAdapter(FilterListener filterListener) { + mFilterListener = filterListener; + setupFilters(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_filter_view, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Pair filterPair = mPairList.get(position); + Bitmap fromAsset = getBitmapFromAsset(holder.itemView.getContext(), filterPair.first); + holder.mImageFilterView.setImageBitmap(fromAsset); + holder.mTxtFilterName.setText(filterPair.second.name().replace("_", " ")); + } + + @Override + public int getItemCount() { + return mPairList.size(); + } + + private Bitmap getBitmapFromAsset(Context context, String strName) { + AssetManager assetManager = context.getAssets(); + InputStream istr = null; + try { + istr = assetManager.open(strName); + return BitmapFactory.decodeStream(istr); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private void setupFilters() { + mPairList.add(new Pair<>("filters/original.jpg", PhotoFilter.NONE)); + mPairList.add(new Pair<>("filters/auto_fix.png", PhotoFilter.AUTO_FIX)); + mPairList.add(new Pair<>("filters/brightness.png", PhotoFilter.BRIGHTNESS)); + mPairList.add(new Pair<>("filters/contrast.png", PhotoFilter.CONTRAST)); + mPairList.add(new Pair<>("filters/documentary.png", PhotoFilter.DOCUMENTARY)); + mPairList.add(new Pair<>("filters/dual_tone.png", PhotoFilter.DUE_TONE)); + mPairList.add(new Pair<>("filters/fill_light.png", PhotoFilter.FILL_LIGHT)); + mPairList.add(new Pair<>("filters/fish_eye.png", PhotoFilter.FISH_EYE)); + mPairList.add(new Pair<>("filters/grain.png", PhotoFilter.GRAIN)); + mPairList.add(new Pair<>("filters/gray_scale.png", PhotoFilter.GRAY_SCALE)); + mPairList.add(new Pair<>("filters/lomish.png", PhotoFilter.LOMISH)); + mPairList.add(new Pair<>("filters/negative.png", PhotoFilter.NEGATIVE)); + mPairList.add(new Pair<>("filters/posterize.png", PhotoFilter.POSTERIZE)); + mPairList.add(new Pair<>("filters/saturate.png", PhotoFilter.SATURATE)); + mPairList.add(new Pair<>("filters/sepia.png", PhotoFilter.SEPIA)); + mPairList.add(new Pair<>("filters/sharpen.png", PhotoFilter.SHARPEN)); + mPairList.add(new Pair<>("filters/temprature.png", PhotoFilter.TEMPERATURE)); + mPairList.add(new Pair<>("filters/tint.png", PhotoFilter.TINT)); + mPairList.add(new Pair<>("filters/vignette.png", PhotoFilter.VIGNETTE)); + mPairList.add(new Pair<>("filters/cross_process.png", PhotoFilter.CROSS_PROCESS)); + mPairList.add(new Pair<>("filters/b_n_w.png", PhotoFilter.BLACK_WHITE)); + mPairList.add(new Pair<>("filters/flip_horizental.png", PhotoFilter.FLIP_HORIZONTAL)); + mPairList.add(new Pair<>("filters/flip_vertical.png", PhotoFilter.FLIP_VERTICAL)); + mPairList.add(new Pair<>("filters/rotate.png", PhotoFilter.ROTATE)); + } + + class ViewHolder extends RecyclerView.ViewHolder { + ImageView mImageFilterView; + TextView mTxtFilterName; + + ViewHolder(View itemView) { + super(itemView); + mImageFilterView = itemView.findViewById(R.id.imgFilterView); + mTxtFilterName = itemView.findViewById(R.id.txtFilterName); + itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mFilterListener.onFilterSelected(mPairList.get(getLayoutPosition()).second); + } + }); + } + } +} diff --git a/app/src/main/java/app/fedilab/android/imageeditor/tools/EditingToolsAdapter.java b/app/src/main/java/app/fedilab/android/imageeditor/tools/EditingToolsAdapter.java new file mode 100644 index 000000000..f994af1f1 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/tools/EditingToolsAdapter.java @@ -0,0 +1,85 @@ +package app.fedilab.android.imageeditor.tools; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +import app.fedilab.android.R; + +/** + * @author Burhanuddin Rashid + * @version 0.1.2 + * @since 5/23/2018 + */ +public class EditingToolsAdapter extends RecyclerView.Adapter { + + private final List mToolList = new ArrayList<>(); + private final OnItemSelected mOnItemSelected; + + public EditingToolsAdapter(OnItemSelected onItemSelected) { + mOnItemSelected = onItemSelected; + mToolList.add(new ToolModel("Shape", R.drawable.ic_oval, ToolType.SHAPE)); + mToolList.add(new ToolModel("Text", R.drawable.ic_text, ToolType.TEXT)); + mToolList.add(new ToolModel("Eraser", R.drawable.ic_eraser, ToolType.ERASER)); + mToolList.add(new ToolModel("Filter", R.drawable.ic_photo_filter, ToolType.FILTER)); + mToolList.add(new ToolModel("Emoji", R.drawable.ic_insert_emoticon, ToolType.EMOJI)); + mToolList.add(new ToolModel("Sticker", R.drawable.ic_sticker, ToolType.STICKER)); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.row_editing_tools, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + ToolModel item = mToolList.get(position); + holder.txtTool.setText(item.mToolName); + holder.imgToolIcon.setImageResource(item.mToolIcon); + } + + @Override + public int getItemCount() { + return mToolList.size(); + } + + public interface OnItemSelected { + void onToolSelected(ToolType toolType); + } + + class ToolModel { + private final String mToolName; + private final int mToolIcon; + private final ToolType mToolType; + + ToolModel(String toolName, int toolIcon, ToolType toolType) { + mToolName = toolName; + mToolIcon = toolIcon; + mToolType = toolType; + } + + } + + class ViewHolder extends RecyclerView.ViewHolder { + ImageView imgToolIcon; + TextView txtTool; + + ViewHolder(View itemView) { + super(itemView); + imgToolIcon = itemView.findViewById(R.id.imgToolIcon); + txtTool = itemView.findViewById(R.id.txtTool); + itemView.setOnClickListener(v -> mOnItemSelected.onToolSelected(mToolList.get(getLayoutPosition()).mToolType)); + } + } +} diff --git a/app/src/main/java/app/fedilab/android/imageeditor/tools/ToolType.java b/app/src/main/java/app/fedilab/android/imageeditor/tools/ToolType.java new file mode 100644 index 000000000..0054e22d0 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/imageeditor/tools/ToolType.java @@ -0,0 +1,16 @@ +package app.fedilab.android.imageeditor.tools; + +/** + * @author Burhanuddin Rashid + * @version 0.1.2 + * @since 5/23/2018 + */ +public enum ToolType { + BRUSH, + SHAPE, + TEXT, + ERASER, + FILTER, + EMOJI, + STICKER +} diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java index c1614ccd4..d6528c84c 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java @@ -30,6 +30,7 @@ import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.text.Editable; @@ -99,6 +100,7 @@ import app.fedilab.android.exception.DBException; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; import app.fedilab.android.helper.ThemeHelper; +import app.fedilab.android.imageeditor.EditImageActivity; import app.fedilab.android.viewmodel.mastodon.AccountsVM; import app.fedilab.android.viewmodel.mastodon.SearchVM; import es.dmoral.toasty.Toasty; @@ -476,7 +478,13 @@ public class ComposeAdapter extends RecyclerView.Adapter { + Intent intent = new Intent(context, EditImageActivity.class); + Bundle b = new Bundle(); + intent.putExtra("imageUri", attachment.local_path); + intent.putExtras(b); + context.startActivity(intent); + }); composeAttachmentItemBinding.buttonDescription.setOnClickListener(v -> { AlertDialog.Builder builderInner = new AlertDialog.Builder(context, Helper.dialogStyle()); builderInner.setTitle(R.string.upload_form_description); diff --git a/app/src/main/res/drawable/aa.png b/app/src/main/res/drawable/aa.png new file mode 100644 index 000000000..ae288b864 Binary files /dev/null and b/app/src/main/res/drawable/aa.png differ diff --git a/app/src/main/res/drawable/bb.png b/app/src/main/res/drawable/bb.png new file mode 100644 index 000000000..cd18da80a Binary files /dev/null and b/app/src/main/res/drawable/bb.png differ diff --git a/app/src/main/res/drawable/blank_image.jpg b/app/src/main/res/drawable/blank_image.jpg new file mode 100644 index 000000000..93e8dbd9d Binary files /dev/null and b/app/src/main/res/drawable/blank_image.jpg differ diff --git a/app/src/main/res/drawable/ic_brush.xml b/app/src/main/res/drawable/ic_brush.xml new file mode 100644 index 000000000..154dbcf2d --- /dev/null +++ b/app/src/main/res/drawable/ic_brush.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml new file mode 100644 index 000000000..a7c3363ae --- /dev/null +++ b/app/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 000000000..299773a80 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_crop.xml b/app/src/main/res/drawable/ic_crop.xml new file mode 100644 index 000000000..cb2455ff5 --- /dev/null +++ b/app/src/main/res/drawable/ic_crop.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_eraser.xml b/app/src/main/res/drawable/ic_eraser.xml new file mode 100644 index 000000000..212d2ae16 --- /dev/null +++ b/app/src/main/res/drawable/ic_eraser.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_gallery.xml b/app/src/main/res/drawable/ic_gallery.xml new file mode 100644 index 000000000..0c981fc94 --- /dev/null +++ b/app/src/main/res/drawable/ic_gallery.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_insert_emoticon.xml b/app/src/main/res/drawable/ic_insert_emoticon.xml new file mode 100644 index 000000000..4622233c0 --- /dev/null +++ b/app/src/main/res/drawable/ic_insert_emoticon.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..a339898ee --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_oval.xml b/app/src/main/res/drawable/ic_oval.xml new file mode 100644 index 000000000..b516ac230 --- /dev/null +++ b/app/src/main/res/drawable/ic_oval.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_photo_filter.xml b/app/src/main/res/drawable/ic_photo_filter.xml new file mode 100644 index 000000000..f7f1370c1 --- /dev/null +++ b/app/src/main/res/drawable/ic_photo_filter.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_rectangle.xml b/app/src/main/res/drawable/ic_rectangle.xml new file mode 100644 index 000000000..79ad219ce --- /dev/null +++ b/app/src/main/res/drawable/ic_rectangle.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_redo.xml b/app/src/main/res/drawable/ic_redo.xml new file mode 100644 index 000000000..45e346491 --- /dev/null +++ b/app/src/main/res/drawable/ic_redo.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_save.xml b/app/src/main/res/drawable/ic_save.xml new file mode 100644 index 000000000..b639a3bbe --- /dev/null +++ b/app/src/main/res/drawable/ic_save.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_save_gallery.xml b/app/src/main/res/drawable/ic_save_gallery.xml new file mode 100644 index 000000000..555a07c8b --- /dev/null +++ b/app/src/main/res/drawable/ic_save_gallery.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 000000000..9dad7b85f --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_sticker.xml b/app/src/main/res/drawable/ic_sticker.xml new file mode 100644 index 000000000..8fa270706 --- /dev/null +++ b/app/src/main/res/drawable/ic_sticker.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_text.xml b/app/src/main/res/drawable/ic_text.xml new file mode 100644 index 000000000..f01eeaeb6 --- /dev/null +++ b/app/src/main/res/drawable/ic_text.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_undo.xml b/app/src/main/res/drawable/ic_undo.xml new file mode 100644 index 000000000..b83d4dce8 --- /dev/null +++ b/app/src/main/res/drawable/ic_undo.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/original.jpg b/app/src/main/res/drawable/original.jpg new file mode 100644 index 000000000..353089ede Binary files /dev/null and b/app/src/main/res/drawable/original.jpg differ diff --git a/app/src/main/res/drawable/paris_tower.jpg b/app/src/main/res/drawable/paris_tower.jpg new file mode 100644 index 000000000..fab7eee2d Binary files /dev/null and b/app/src/main/res/drawable/paris_tower.jpg differ diff --git a/app/src/main/res/drawable/rounded_border_text_view.xml b/app/src/main/res/drawable/rounded_border_text_view.xml new file mode 100644 index 000000000..52cf54c46 --- /dev/null +++ b/app/src/main/res/drawable/rounded_border_text_view.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_edit_image.xml b/app/src/main/res/layout/activity_edit_image.xml new file mode 100644 index 000000000..85d104549 --- /dev/null +++ b/app/src/main/res/layout/activity_edit_image.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +