@codebymanan/react-native-photo-editor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +20 -0
  2. package/PhotoEditor.podspec +23 -0
  3. package/README.md +200 -0
  4. package/android/build.gradle +65 -0
  5. package/android/src/main/AndroidManifest.xml +14 -0
  6. package/android/src/main/assets/beyond_wonderland.ttf +0 -0
  7. package/android/src/main/assets/emojione-android.ttf +0 -0
  8. package/android/src/main/assets/filters/auto_fix.png +0 -0
  9. package/android/src/main/assets/filters/b_n_w.png +0 -0
  10. package/android/src/main/assets/filters/brightness.png +0 -0
  11. package/android/src/main/assets/filters/contrast.png +0 -0
  12. package/android/src/main/assets/filters/cross_process.png +0 -0
  13. package/android/src/main/assets/filters/documentary.png +0 -0
  14. package/android/src/main/assets/filters/dual_tone.png +0 -0
  15. package/android/src/main/assets/filters/fill_light.png +0 -0
  16. package/android/src/main/assets/filters/fish_eye.png +0 -0
  17. package/android/src/main/assets/filters/flip_horizental.png +0 -0
  18. package/android/src/main/assets/filters/flip_vertical.png +0 -0
  19. package/android/src/main/assets/filters/grain.png +0 -0
  20. package/android/src/main/assets/filters/gray_scale.png +0 -0
  21. package/android/src/main/assets/filters/lomish.png +0 -0
  22. package/android/src/main/assets/filters/negative.png +0 -0
  23. package/android/src/main/assets/filters/original.jpg +0 -0
  24. package/android/src/main/assets/filters/posterize.png +0 -0
  25. package/android/src/main/assets/filters/rotate.png +0 -0
  26. package/android/src/main/assets/filters/saturate.png +0 -0
  27. package/android/src/main/assets/filters/sepia.png +0 -0
  28. package/android/src/main/assets/filters/sharpen.png +0 -0
  29. package/android/src/main/assets/filters/temprature.png +0 -0
  30. package/android/src/main/assets/filters/tint.png +0 -0
  31. package/android/src/main/assets/filters/vignette.png +0 -0
  32. package/android/src/main/java/com/photoeditor/PhotoEditorModule.kt +130 -0
  33. package/android/src/main/java/com/photoeditor/PhotoEditorPackage.kt +31 -0
  34. package/android/src/main/java/com/photoeditor/editor/ColorPickerAdapter.kt +100 -0
  35. package/android/src/main/java/com/photoeditor/editor/EditImageActivity.kt +407 -0
  36. package/android/src/main/java/com/photoeditor/editor/EditorLocale.kt +41 -0
  37. package/android/src/main/java/com/photoeditor/editor/EditorStrings.kt +46 -0
  38. package/android/src/main/java/com/photoeditor/editor/EmojiBSFragment.kt +121 -0
  39. package/android/src/main/java/com/photoeditor/editor/PropertiesBSFragment.kt +76 -0
  40. package/android/src/main/java/com/photoeditor/editor/ShapeBSFragment.kt +112 -0
  41. package/android/src/main/java/com/photoeditor/editor/StickerBSFragment.kt +130 -0
  42. package/android/src/main/java/com/photoeditor/editor/TextEditorDialogFragment.kt +133 -0
  43. package/android/src/main/java/com/photoeditor/editor/base/BaseActivity.kt +67 -0
  44. package/android/src/main/java/com/photoeditor/editor/base/BaseFragment.kt +10 -0
  45. package/android/src/main/java/com/photoeditor/editor/filters/FilterListener.kt +7 -0
  46. package/android/src/main/java/com/photoeditor/editor/filters/FilterViewAdapter.kt +105 -0
  47. package/android/src/main/java/com/photoeditor/editor/tools/EditingToolsAdapter.kt +70 -0
  48. package/android/src/main/java/com/photoeditor/editor/tools/ToolType.kt +10 -0
  49. package/android/src/main/res/drawable/pe_blank_image.jpg +0 -0
  50. package/android/src/main/res/drawable/pe_ic_brush.xml +9 -0
  51. package/android/src/main/res/drawable/pe_ic_close.xml +9 -0
  52. package/android/src/main/res/drawable/pe_ic_eraser.xml +4 -0
  53. package/android/src/main/res/drawable/pe_ic_insert_emoticon.xml +9 -0
  54. package/android/src/main/res/drawable/pe_ic_oval.xml +9 -0
  55. package/android/src/main/res/drawable/pe_ic_photo_filter.xml +5 -0
  56. package/android/src/main/res/drawable/pe_ic_rectangle.xml +9 -0
  57. package/android/src/main/res/drawable/pe_ic_redo.xml +10 -0
  58. package/android/src/main/res/drawable/pe_ic_redo_disabled.xml +10 -0
  59. package/android/src/main/res/drawable/pe_ic_save.xml +9 -0
  60. package/android/src/main/res/drawable/pe_ic_sticker.xml +9 -0
  61. package/android/src/main/res/drawable/pe_ic_text.xml +9 -0
  62. package/android/src/main/res/drawable/pe_ic_undo.xml +10 -0
  63. package/android/src/main/res/drawable/pe_ic_undo_disabled.xml +10 -0
  64. package/android/src/main/res/drawable/pe_original.jpg +0 -0
  65. package/android/src/main/res/drawable/pe_redo.xml +6 -0
  66. package/android/src/main/res/drawable/pe_rounded_border_text_view.xml +14 -0
  67. package/android/src/main/res/drawable/pe_undo.xml +6 -0
  68. package/android/src/main/res/font/pe_roboto_medium.xml +7 -0
  69. package/android/src/main/res/layout/pe_activity_edit_image.xml +111 -0
  70. package/android/src/main/res/layout/pe_add_text_dialog.xml +56 -0
  71. package/android/src/main/res/layout/pe_color_picker_item_list.xml +15 -0
  72. package/android/src/main/res/layout/pe_fragment_bottom_properties_dialog.xml +77 -0
  73. package/android/src/main/res/layout/pe_fragment_bottom_shapes_dialog.xml +135 -0
  74. package/android/src/main/res/layout/pe_fragment_bottom_sticker_emoji_dialog.xml +62 -0
  75. package/android/src/main/res/layout/pe_row_editing_tools.xml +34 -0
  76. package/android/src/main/res/layout/pe_row_emoji.xml +17 -0
  77. package/android/src/main/res/layout/pe_row_filter_view.xml +35 -0
  78. package/android/src/main/res/layout/pe_row_sticker.xml +23 -0
  79. package/android/src/main/res/values/colors.xml +22 -0
  80. package/android/src/main/res/values/dimens.xml +7 -0
  81. package/android/src/main/res/values/font_certs.xml +17 -0
  82. package/android/src/main/res/values/preloaded_fonts.xml +6 -0
  83. package/android/src/main/res/values/strings.xml +462 -0
  84. package/android/src/main/res/values/styles.xml +28 -0
  85. package/ios/PhotoEditor.h +5 -0
  86. package/ios/PhotoEditor.mm +58 -0
  87. package/ios/PhotoEditorManager.swift +164 -0
  88. package/lib/module/NativePhotoEditor.js +5 -0
  89. package/lib/module/NativePhotoEditor.js.map +1 -0
  90. package/lib/module/index.js +4 -0
  91. package/lib/module/index.js.map +1 -0
  92. package/lib/module/open.js +6 -0
  93. package/lib/module/open.js.map +1 -0
  94. package/lib/module/open.native.js +7 -0
  95. package/lib/module/open.native.js.map +1 -0
  96. package/lib/module/package.json +1 -0
  97. package/lib/module/types.js +2 -0
  98. package/lib/module/types.js.map +1 -0
  99. package/lib/typescript/package.json +1 -0
  100. package/lib/typescript/src/NativePhotoEditor.d.ts +14 -0
  101. package/lib/typescript/src/NativePhotoEditor.d.ts.map +1 -0
  102. package/lib/typescript/src/index.d.ts +3 -0
  103. package/lib/typescript/src/index.d.ts.map +1 -0
  104. package/lib/typescript/src/open.d.ts +3 -0
  105. package/lib/typescript/src/open.d.ts.map +1 -0
  106. package/lib/typescript/src/open.native.d.ts +3 -0
  107. package/lib/typescript/src/open.native.d.ts.map +1 -0
  108. package/lib/typescript/src/types.d.ts +39 -0
  109. package/lib/typescript/src/types.d.ts.map +1 -0
  110. package/package.json +128 -0
  111. package/src/NativePhotoEditor.ts +12 -0
  112. package/src/index.tsx +2 -0
  113. package/src/open.native.tsx +6 -0
  114. package/src/open.tsx +7 -0
  115. package/src/types.ts +36 -0
@@ -0,0 +1,407 @@
1
+ package com.photoeditor.editor
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.content.Context
5
+ import android.content.DialogInterface
6
+ import android.content.Intent
7
+ import android.graphics.Bitmap
8
+ import android.graphics.drawable.Drawable
9
+ import android.os.Bundle
10
+ import android.util.Log
11
+ import android.view.MotionEvent
12
+ import android.view.View
13
+ import android.view.animation.AnticipateOvershootInterpolator
14
+ import android.widget.ImageView
15
+ import android.widget.TextView
16
+ import androidx.appcompat.app.AlertDialog
17
+ import androidx.constraintlayout.widget.ConstraintLayout
18
+ import androidx.constraintlayout.widget.ConstraintSet
19
+ import androidx.lifecycle.lifecycleScope
20
+ import androidx.recyclerview.widget.LinearLayoutManager
21
+ import androidx.recyclerview.widget.RecyclerView
22
+ import androidx.transition.ChangeBounds
23
+ import androidx.transition.TransitionManager
24
+ import com.bumptech.glide.Glide
25
+ import com.bumptech.glide.request.target.CustomTarget
26
+ import com.bumptech.glide.request.transition.Transition
27
+ import com.photoeditor.R
28
+ import com.photoeditor.editor.EmojiBSFragment.EmojiListener
29
+ import com.photoeditor.editor.StickerBSFragment.StickerListener
30
+ import com.photoeditor.editor.base.BaseActivity
31
+ import com.photoeditor.editor.filters.FilterListener
32
+ import com.photoeditor.editor.filters.FilterViewAdapter
33
+ import com.photoeditor.editor.tools.EditingToolsAdapter
34
+ import com.photoeditor.editor.tools.EditingToolsAdapter.OnItemSelected
35
+ import com.photoeditor.editor.tools.ToolType
36
+ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
37
+ import ja.burhanrashid52.photoeditor.OnPhotoEditorListener
38
+ import ja.burhanrashid52.photoeditor.PhotoEditor
39
+ import ja.burhanrashid52.photoeditor.PhotoEditorView
40
+ import ja.burhanrashid52.photoeditor.PhotoFilter
41
+ import ja.burhanrashid52.photoeditor.SaveFileResult
42
+ import ja.burhanrashid52.photoeditor.SaveSettings
43
+ import ja.burhanrashid52.photoeditor.TextStyleBuilder
44
+ import ja.burhanrashid52.photoeditor.ViewType
45
+ import ja.burhanrashid52.photoeditor.shape.ShapeBuilder
46
+ import ja.burhanrashid52.photoeditor.shape.ShapeType
47
+ import kotlinx.coroutines.launch
48
+ import java.io.File
49
+ import androidx.annotation.StringRes
50
+
51
+ class EditImageActivity : BaseActivity(), OnPhotoEditorListener, View.OnClickListener,
52
+ PropertiesBSFragment.Properties, ShapeBSFragment.Properties, EmojiListener, StickerListener,
53
+ OnItemSelected, FilterListener {
54
+
55
+ lateinit var mPhotoEditor: PhotoEditor
56
+ private lateinit var mPhotoEditorView: PhotoEditorView
57
+ private lateinit var mPropertiesBSFragment: PropertiesBSFragment
58
+ private lateinit var mShapeBSFragment: ShapeBSFragment
59
+ private lateinit var mShapeBuilder: ShapeBuilder
60
+ private lateinit var mEmojiBSFragment: EmojiBSFragment
61
+ private lateinit var mStickerBSFragment: StickerBSFragment
62
+ private lateinit var mTxtCurrentTool: TextView
63
+ private lateinit var mRvTools: RecyclerView
64
+ private lateinit var mRvFilters: RecyclerView
65
+ private lateinit var mImgUndo: View
66
+ private lateinit var mImgRedo: View
67
+ private val mEditingToolsAdapter = EditingToolsAdapter(this)
68
+ private val mFilterViewAdapter = FilterViewAdapter(this)
69
+ private lateinit var mRootView: ConstraintLayout
70
+ private val mConstraintSet = ConstraintSet()
71
+ private var mIsFilterVisible = false
72
+
73
+ override fun attachBaseContext(newBase: Context) {
74
+ super.attachBaseContext(newBase)
75
+
76
+ // getIntent() is still null at this point, so the tag comes from EditorLocale.
77
+ // Applied after super() on purpose: AppCompat installs the app-wide locale
78
+ // during attachBaseContext on API < 33, and overriding afterwards is what
79
+ // keeps the per-call language winning on those versions too.
80
+ EditorLocale.overrideConfiguration(baseContext, EditorLocale.pendingTag)
81
+ ?.let(::applyOverrideConfiguration)
82
+ }
83
+
84
+ override fun onCreate(savedInstanceState: Bundle?) {
85
+ super.onCreate(savedInstanceState)
86
+
87
+ // The tag lives in a process-global field, so a process kill loses it and
88
+ // attachBaseContext above will have run without a locale. Put it back and
89
+ // start over. This cannot loop: the field is non-null on the second pass.
90
+ val language = intent.getStringExtra(EXTRA_LANGUAGE)
91
+ if (EditorLocale.pendingTag == null && !language.isNullOrBlank()) {
92
+ EditorLocale.pendingTag = language
93
+ recreate()
94
+ return
95
+ }
96
+
97
+ EditorStrings.setOverrides(intent.getBundleExtra(EXTRA_TRANSLATIONS))
98
+
99
+ makeFullScreen()
100
+ setContentView(R.layout.pe_activity_edit_image)
101
+
102
+ initViews()
103
+ mTxtCurrentTool.setEditorText(R.string.pe_app_name)
104
+
105
+ mPropertiesBSFragment = PropertiesBSFragment()
106
+ mEmojiBSFragment = EmojiBSFragment()
107
+ mStickerBSFragment = StickerBSFragment()
108
+ mShapeBSFragment = ShapeBSFragment()
109
+ mStickerBSFragment.setStickerListener(this)
110
+ mEmojiBSFragment.setEmojiListener(this)
111
+ mPropertiesBSFragment.setPropertiesChangeListener(this)
112
+ mShapeBSFragment.setPropertiesChangeListener(this)
113
+
114
+ intent.getStringArrayListExtra(EXTRA_STICKERS)?.let {
115
+ mStickerBSFragment.setStickers(it)
116
+ }
117
+
118
+ val llmTools = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
119
+ mRvTools.layoutManager = llmTools
120
+ mRvTools.adapter = mEditingToolsAdapter
121
+
122
+ val llmFilters = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
123
+ mRvFilters.layoutManager = llmFilters
124
+ mRvFilters.adapter = mFilterViewAdapter
125
+
126
+ mPhotoEditor = PhotoEditor.Builder(this, mPhotoEditorView)
127
+ .setPinchTextScalable(true)
128
+ .build()
129
+
130
+ mPhotoEditor.setOnPhotoEditorListener(this)
131
+
132
+ loadSourceImage()
133
+ }
134
+
135
+ /** Resolves a string, honouring any overrides from the `translations` option. */
136
+ private fun editorString(@StringRes id: Int): String = EditorStrings.get(this, id)
137
+
138
+ private fun loadSourceImage() {
139
+ val path = intent.getStringExtra(EXTRA_IMAGE_PATH)
140
+ if (path.isNullOrBlank()) {
141
+ setResultError("Missing required option: path")
142
+ return
143
+ }
144
+
145
+ // Glide resolves file paths, file://, content:// and http(s) sources alike
146
+ Glide.with(this)
147
+ .asBitmap()
148
+ .load(path)
149
+ .into(object : CustomTarget<Bitmap>() {
150
+ override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
151
+ mPhotoEditorView.source.setImageBitmap(resource)
152
+ }
153
+
154
+ override fun onLoadCleared(placeholder: Drawable?) = Unit
155
+
156
+ override fun onLoadFailed(errorDrawable: Drawable?) {
157
+ setResultError("Could not load image: $path")
158
+ }
159
+ })
160
+ }
161
+
162
+ private fun initViews() {
163
+ mPhotoEditorView = findViewById(R.id.photoEditorView)
164
+ mTxtCurrentTool = findViewById(R.id.txtCurrentTool)
165
+ mRvTools = findViewById(R.id.rvConstraintTools)
166
+ mRvFilters = findViewById(R.id.rvFilterView)
167
+ mRootView = findViewById(R.id.rootView)
168
+
169
+ mImgUndo = findViewById(R.id.imgUndo)
170
+ mImgUndo.setOnClickListener(this)
171
+
172
+ mImgRedo = findViewById(R.id.imgRedo)
173
+ mImgRedo.setOnClickListener(this)
174
+
175
+ val imgSave: ImageView = findViewById(R.id.imgSave)
176
+ imgSave.setOnClickListener(this)
177
+
178
+ val imgClose: ImageView = findViewById(R.id.imgClose)
179
+ imgClose.setOnClickListener(this)
180
+ }
181
+
182
+ override fun onEditTextChangeListener(rootView: View, text: String, colorCode: Int) {
183
+ val textEditorDialogFragment =
184
+ TextEditorDialogFragment.show(this, text.toString(), colorCode)
185
+ textEditorDialogFragment.setOnTextEditorListener(object :
186
+ TextEditorDialogFragment.TextEditorListener {
187
+ override fun onDone(inputText: String, colorCode: Int) {
188
+ val styleBuilder = TextStyleBuilder()
189
+ styleBuilder.withTextColor(colorCode)
190
+ mPhotoEditor.editText(rootView, inputText, styleBuilder)
191
+ mTxtCurrentTool.setEditorText(R.string.pe_label_text)
192
+ }
193
+ })
194
+ }
195
+
196
+ override fun onAddViewListener(viewType: ViewType, numberOfAddedViews: Int) {
197
+ Log.d(TAG, "onAddViewListener() called with: viewType = [$viewType]")
198
+ }
199
+
200
+ override fun onRemoveViewListener(viewType: ViewType, numberOfAddedViews: Int) {
201
+ Log.d(TAG, "onRemoveViewListener() called with: viewType = [$viewType]")
202
+ }
203
+
204
+ override fun onStartViewChangeListener(viewType: ViewType) {
205
+ Log.d(TAG, "onStartViewChangeListener() called with: viewType = [$viewType]")
206
+ }
207
+
208
+ override fun onStopViewChangeListener(viewType: ViewType) {
209
+ Log.d(TAG, "onStopViewChangeListener() called with: viewType = [$viewType]")
210
+ }
211
+
212
+ override fun onTouchSourceImage(event: MotionEvent) {
213
+ Log.d(TAG, "onTouchView() called with: event = [$event]")
214
+ }
215
+
216
+ @SuppressLint("NonConstantResourceId")
217
+ override fun onClick(view: View) {
218
+ when (view.id) {
219
+ R.id.imgUndo -> mPhotoEditor.undo()
220
+ R.id.imgRedo -> mPhotoEditor.redo()
221
+
222
+ R.id.imgSave -> saveImage()
223
+ R.id.imgClose -> onBackPressed()
224
+ }
225
+ }
226
+
227
+ private fun saveImage() {
228
+ val outputDir = File(cacheDir, "photo_editor")
229
+ if (!outputDir.exists() && !outputDir.mkdirs()) {
230
+ showSnackbar(editorString(R.string.pe_msg_save_failed))
231
+ return
232
+ }
233
+ val outputFile = File(outputDir, "${System.currentTimeMillis()}.png")
234
+
235
+ val saveSettings = SaveSettings.Builder()
236
+ .setClearViewsEnabled(true)
237
+ .setTransparencyEnabled(true)
238
+ .build()
239
+
240
+ showLoading(editorString(R.string.pe_msg_saving))
241
+ lifecycleScope.launch {
242
+ val result = mPhotoEditor.saveAsFile(outputFile.absolutePath, saveSettings)
243
+ hideLoading()
244
+ if (result is SaveFileResult.Success) {
245
+ val data = Intent().putExtra(RESULT_EXTRA_PATH, outputFile.absolutePath)
246
+ setResult(RESULT_OK, data)
247
+ finish()
248
+ } else {
249
+ showSnackbar(editorString(R.string.pe_msg_save_failed))
250
+ }
251
+ }
252
+ }
253
+
254
+ private fun setResultError(message: String) {
255
+ val data = Intent().putExtra(RESULT_EXTRA_ERROR, message)
256
+ setResult(RESULT_ERROR, data)
257
+ finish()
258
+ }
259
+
260
+ override fun onColorChanged(colorCode: Int) {
261
+ mPhotoEditor.setShape(mShapeBuilder.withShapeColor(colorCode))
262
+ mTxtCurrentTool.setEditorText(R.string.pe_label_brush)
263
+ }
264
+
265
+ override fun onOpacityChanged(opacity: Int) {
266
+ mPhotoEditor.setShape(mShapeBuilder.withShapeOpacity(opacity))
267
+ mTxtCurrentTool.setEditorText(R.string.pe_label_brush)
268
+ }
269
+
270
+ override fun onShapeSizeChanged(shapeSize: Int) {
271
+ mPhotoEditor.setShape(mShapeBuilder.withShapeSize(shapeSize.toFloat()))
272
+ mTxtCurrentTool.setEditorText(R.string.pe_label_brush)
273
+ }
274
+
275
+ override fun onShapePicked(shapeType: ShapeType) {
276
+ mPhotoEditor.setShape(mShapeBuilder.withShapeType(shapeType))
277
+ }
278
+
279
+ override fun onEmojiClick(emojiUnicode: String) {
280
+ mPhotoEditor.addEmoji(emojiUnicode)
281
+ mTxtCurrentTool.setEditorText(R.string.pe_label_emoji)
282
+ }
283
+
284
+ override fun onStickerClick(bitmap: Bitmap) {
285
+ mPhotoEditor.addImage(bitmap)
286
+ mTxtCurrentTool.setEditorText(R.string.pe_label_sticker)
287
+ }
288
+
289
+ private fun showSaveDialog() {
290
+ val builder = AlertDialog.Builder(this)
291
+ builder.setMessage(editorString(R.string.pe_msg_save_image))
292
+ builder.setPositiveButton(editorString(R.string.pe_label_save)) { _: DialogInterface?, _: Int -> saveImage() }
293
+ builder.setNegativeButton(editorString(R.string.pe_label_cancel)) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
294
+ builder.setNeutralButton(editorString(R.string.pe_label_discard)) { _: DialogInterface?, _: Int -> cancelAndFinish() }
295
+ builder.create().show()
296
+ }
297
+
298
+ override fun onFilterSelected(photoFilter: PhotoFilter) {
299
+ mPhotoEditor.setFilterEffect(photoFilter)
300
+ }
301
+
302
+ override fun onToolSelected(toolType: ToolType) {
303
+ when (toolType) {
304
+ ToolType.SHAPE -> {
305
+ mPhotoEditor.setBrushDrawingMode(true)
306
+ mShapeBuilder = ShapeBuilder()
307
+ mPhotoEditor.setShape(mShapeBuilder)
308
+ mTxtCurrentTool.setEditorText(R.string.pe_label_shape)
309
+ showBottomSheetDialogFragment(mShapeBSFragment)
310
+ }
311
+
312
+ ToolType.TEXT -> {
313
+ val textEditorDialogFragment = TextEditorDialogFragment.show(this)
314
+ textEditorDialogFragment.setOnTextEditorListener(object :
315
+ TextEditorDialogFragment.TextEditorListener {
316
+ override fun onDone(inputText: String, colorCode: Int) {
317
+ val styleBuilder = TextStyleBuilder()
318
+ styleBuilder.withTextColor(colorCode)
319
+ mPhotoEditor.addText(inputText, styleBuilder)
320
+ mTxtCurrentTool.setEditorText(R.string.pe_label_text)
321
+ }
322
+ })
323
+ }
324
+
325
+ ToolType.ERASER -> {
326
+ mPhotoEditor.brushEraser()
327
+ mTxtCurrentTool.setEditorText(R.string.pe_label_eraser_mode)
328
+ }
329
+
330
+ ToolType.FILTER -> {
331
+ mTxtCurrentTool.setEditorText(R.string.pe_label_filter)
332
+ showFilter(true)
333
+ }
334
+
335
+ ToolType.EMOJI -> showBottomSheetDialogFragment(mEmojiBSFragment)
336
+ ToolType.STICKER -> showBottomSheetDialogFragment(mStickerBSFragment)
337
+ }
338
+ }
339
+
340
+ private fun showBottomSheetDialogFragment(fragment: BottomSheetDialogFragment?) {
341
+ if (fragment == null || fragment.isAdded) {
342
+ return
343
+ }
344
+ fragment.show(supportFragmentManager, fragment.tag)
345
+ }
346
+
347
+ private fun showFilter(isVisible: Boolean) {
348
+ mIsFilterVisible = isVisible
349
+ mConstraintSet.clone(mRootView)
350
+
351
+ val rvFilterId: Int = mRvFilters.id
352
+
353
+ if (isVisible) {
354
+ mConstraintSet.clear(rvFilterId, ConstraintSet.START)
355
+ mConstraintSet.connect(
356
+ rvFilterId, ConstraintSet.START,
357
+ ConstraintSet.PARENT_ID, ConstraintSet.START
358
+ )
359
+ mConstraintSet.connect(
360
+ rvFilterId, ConstraintSet.END,
361
+ ConstraintSet.PARENT_ID, ConstraintSet.END
362
+ )
363
+ } else {
364
+ mConstraintSet.connect(
365
+ rvFilterId, ConstraintSet.START,
366
+ ConstraintSet.PARENT_ID, ConstraintSet.END
367
+ )
368
+ mConstraintSet.clear(rvFilterId, ConstraintSet.END)
369
+ }
370
+
371
+ val changeBounds = ChangeBounds()
372
+ changeBounds.duration = 350
373
+ changeBounds.interpolator = AnticipateOvershootInterpolator(1.0f)
374
+ TransitionManager.beginDelayedTransition(mRootView, changeBounds)
375
+
376
+ mConstraintSet.applyTo(mRootView)
377
+ }
378
+
379
+ private fun cancelAndFinish() {
380
+ setResult(RESULT_CANCELED)
381
+ finish()
382
+ }
383
+
384
+ @Deprecated("Deprecated in Java")
385
+ override fun onBackPressed() {
386
+ if (mIsFilterVisible) {
387
+ showFilter(false)
388
+ mTxtCurrentTool.setEditorText(R.string.pe_app_name)
389
+ } else if (!mPhotoEditor.isCacheEmpty) {
390
+ showSaveDialog()
391
+ } else {
392
+ cancelAndFinish()
393
+ }
394
+ }
395
+
396
+ companion object {
397
+ private const val TAG = "EditImageActivity"
398
+
399
+ const val EXTRA_IMAGE_PATH = "image_path"
400
+ const val EXTRA_STICKERS = "stickers"
401
+ const val EXTRA_LANGUAGE = "language"
402
+ const val EXTRA_TRANSLATIONS = "translations"
403
+ const val RESULT_EXTRA_PATH = "edited_image_path"
404
+ const val RESULT_EXTRA_ERROR = "error_message"
405
+ const val RESULT_ERROR = 2
406
+ }
407
+ }
@@ -0,0 +1,41 @@
1
+ package com.photoeditor.editor
2
+
3
+ import android.content.Context
4
+ import android.content.res.Configuration
5
+ import java.util.Locale
6
+
7
+ /**
8
+ * Carries the requested editor language from the module to the activity.
9
+ *
10
+ * The tag cannot simply ride along in the intent. [android.app.Activity.attach]
11
+ * calls `attachBaseContext()` before it assigns `mIntent`, so `getIntent()` is
12
+ * still null at the only point where the locale can be applied to the context
13
+ * the layouts are inflated from. The activity reads the tag from here instead,
14
+ * and the intent extra exists only so the activity can notice that this field
15
+ * was lost to a process kill.
16
+ */
17
+ internal object EditorLocale {
18
+
19
+ /** Set by the module immediately before launching the editor. */
20
+ @Volatile
21
+ var pendingTag: String? = null
22
+
23
+ /**
24
+ * Builds the configuration the editor should render under, or null when no
25
+ * usable tag was given.
26
+ *
27
+ * Deliberately does not touch [Locale.setDefault]: that is process-wide
28
+ * state belonging to the host app, and the editor has no business changing
29
+ * it. The returned configuration is a full copy of [base]'s, so applying it
30
+ * keeps night mode, font scale and everything else the host already set.
31
+ */
32
+ fun overrideConfiguration(base: Context, tag: String?): Configuration? {
33
+ val locale = tag?.takeIf { it.isNotBlank() }?.let(Locale::forLanguageTag)
34
+ if (locale == null || locale.language.isEmpty()) return null
35
+
36
+ return Configuration(base.resources.configuration).apply {
37
+ setLocale(locale)
38
+ setLayoutDirection(locale)
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,46 @@
1
+ package com.photoeditor.editor
2
+
3
+ import android.content.Context
4
+ import android.os.Bundle
5
+ import android.widget.TextView
6
+ import androidx.annotation.StringRes
7
+
8
+ /**
9
+ * Runtime string overrides supplied through the `translations` option.
10
+ *
11
+ * A `values-<lang>` folder in the host app is still the idiomatic way to
12
+ * translate the editor on Android, and nothing here replaces it. This exists so
13
+ * that one cross-platform map can drive both platforms, because iOS has no
14
+ * equivalent of dropping a resource folder into the app.
15
+ *
16
+ * Keys are resource entry names, so `pe_label_brush` overrides
17
+ * [com.photoeditor.R.string.pe_label_brush]. Keys with no override fall through
18
+ * to the normal resource lookup, which is what keeps `values-<lang>` working
19
+ * alongside this.
20
+ */
21
+ internal object EditorStrings {
22
+
23
+ @Volatile
24
+ private var overrides: Map<String, String> = emptyMap()
25
+
26
+ /**
27
+ * Replaces the current overrides. Called with the intent's extra on every
28
+ * launch, including null, so one session cannot inherit another's strings.
29
+ */
30
+ fun setOverrides(bundle: Bundle?) {
31
+ overrides = bundle?.keySet().orEmpty()
32
+ .mapNotNull { key -> bundle?.getString(key)?.let { key to it } }
33
+ .toMap()
34
+ }
35
+
36
+ fun get(context: Context, @StringRes id: Int): String {
37
+ if (overrides.isEmpty()) return context.getString(id)
38
+ val name = runCatching { context.resources.getResourceEntryName(id) }.getOrNull()
39
+ return overrides[name] ?: context.getString(id)
40
+ }
41
+ }
42
+
43
+ /** Sets text through [EditorStrings] rather than straight from resources. */
44
+ internal fun TextView.setEditorText(@StringRes id: Int) {
45
+ text = EditorStrings.get(context, id)
46
+ }
@@ -0,0 +1,121 @@
1
+ package com.photoeditor.editor
2
+
3
+ import com.photoeditor.R
4
+
5
+ import android.annotation.SuppressLint
6
+ import android.app.Dialog
7
+ import android.content.Context
8
+ import android.view.LayoutInflater
9
+ import android.view.View
10
+ import android.view.ViewGroup
11
+ import android.widget.TextView
12
+ import androidx.coordinatorlayout.widget.CoordinatorLayout
13
+ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
14
+ import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
15
+ import com.google.android.material.bottomsheet.BottomSheetBehavior
16
+ import androidx.recyclerview.widget.RecyclerView
17
+ import androidx.recyclerview.widget.GridLayoutManager
18
+ import java.lang.NumberFormatException
19
+ import java.util.ArrayList
20
+
21
+ class EmojiBSFragment : BottomSheetDialogFragment() {
22
+ private var mEmojiListener: EmojiListener? = null
23
+ private var emojisList: List<String> = emptyList()
24
+
25
+ interface EmojiListener {
26
+ fun onEmojiClick(emojiUnicode: String)
27
+ }
28
+
29
+ private val mBottomSheetBehaviorCallback: BottomSheetCallback = object : BottomSheetCallback() {
30
+ override fun onStateChanged(bottomSheet: View, newState: Int) {
31
+ if (newState == BottomSheetBehavior.STATE_HIDDEN) {
32
+ dismiss()
33
+ }
34
+ }
35
+
36
+ override fun onSlide(bottomSheet: View, slideOffset: Float) {}
37
+ }
38
+
39
+ @SuppressLint("RestrictedApi")
40
+ override fun setupDialog(dialog: Dialog, style: Int) {
41
+ super.setupDialog(dialog, style)
42
+ val contentView = View.inflate(context, R.layout.pe_fragment_bottom_sticker_emoji_dialog, null)
43
+ dialog.setContentView(contentView)
44
+ val params = (contentView.parent as View).layoutParams as CoordinatorLayout.LayoutParams
45
+ val behavior = params.behavior
46
+ if (behavior != null && behavior is BottomSheetBehavior<*>) {
47
+ behavior.setBottomSheetCallback(mBottomSheetBehaviorCallback)
48
+ }
49
+ (contentView.parent as View).setBackgroundColor(resources.getColor(android.R.color.transparent))
50
+ emojisList = getEmojis(requireContext())
51
+ val rvEmoji: RecyclerView = contentView.findViewById(R.id.rvEmoji)
52
+
53
+ contentView.findViewById<TextView>(R.id.txtClose).setEditorText(R.string.pe_label_close)
54
+ contentView.findViewById<TextView>(R.id.txtDone).setEditorText(R.string.pe_label_done)
55
+ val gridLayoutManager = GridLayoutManager(activity, 5)
56
+ rvEmoji.layoutManager = gridLayoutManager
57
+ val emojiAdapter = EmojiAdapter()
58
+ rvEmoji.adapter = emojiAdapter
59
+ rvEmoji.setHasFixedSize(true)
60
+ rvEmoji.setItemViewCacheSize(emojisList.size)
61
+ }
62
+
63
+ fun setEmojiListener(emojiListener: EmojiListener?) {
64
+ mEmojiListener = emojiListener
65
+ }
66
+
67
+ inner class EmojiAdapter : RecyclerView.Adapter<EmojiAdapter.ViewHolder>() {
68
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
69
+ val view =
70
+ LayoutInflater.from(parent.context).inflate(R.layout.pe_row_emoji, parent, false)
71
+ return ViewHolder(view)
72
+ }
73
+
74
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
75
+ holder.txtEmoji.text = emojisList[position]
76
+ }
77
+
78
+ override fun getItemCount(): Int {
79
+ return emojisList.size
80
+ }
81
+
82
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
83
+ val txtEmoji: TextView = itemView.findViewById(R.id.txtEmoji)
84
+
85
+ init {
86
+ itemView.setOnClickListener {
87
+ if (mEmojiListener != null) {
88
+ mEmojiListener!!.onEmojiClick(emojisList[layoutPosition])
89
+ }
90
+ dismiss()
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ companion object {
97
+ /**
98
+ * Provide the list of emoji in form of unicode string
99
+ *
100
+ * @param context context
101
+ * @return list of emoji unicode
102
+ */
103
+ fun getEmojis(context: Context?): ArrayList<String> {
104
+ val convertedEmojiList = ArrayList<String>()
105
+ val emojiList = context!!.resources.getStringArray(R.array.photo_editor_emoji)
106
+ for (emojiUnicode in emojiList) {
107
+ convertedEmojiList.add(convertEmoji(emojiUnicode))
108
+ }
109
+ return convertedEmojiList
110
+ }
111
+
112
+ private fun convertEmoji(emoji: String): String {
113
+ return try {
114
+ val convertEmojiToInt = emoji.substring(2).toInt(16)
115
+ String(Character.toChars(convertEmojiToInt))
116
+ } catch (e: NumberFormatException) {
117
+ ""
118
+ }
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,76 @@
1
+ package com.photoeditor.editor
2
+
3
+ import com.photoeditor.R
4
+
5
+ import android.os.Bundle
6
+ import android.view.LayoutInflater
7
+ import android.view.View
8
+ import android.view.ViewGroup
9
+ import android.widget.SeekBar
10
+ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
11
+ import androidx.recyclerview.widget.RecyclerView
12
+ import androidx.recyclerview.widget.LinearLayoutManager
13
+ import com.photoeditor.editor.ColorPickerAdapter.OnColorPickerClickListener
14
+ import android.widget.TextView
15
+
16
+ class PropertiesBSFragment : BottomSheetDialogFragment(), SeekBar.OnSeekBarChangeListener {
17
+ private var mProperties: Properties? = null
18
+
19
+ interface Properties {
20
+ fun onColorChanged(colorCode: Int)
21
+ fun onOpacityChanged(opacity: Int)
22
+ fun onShapeSizeChanged(shapeSize: Int)
23
+ }
24
+
25
+ override fun onCreateView(
26
+ inflater: LayoutInflater,
27
+ container: ViewGroup?,
28
+ savedInstanceState: Bundle?
29
+ ): View? {
30
+ return inflater.inflate(R.layout.pe_fragment_bottom_properties_dialog, container, false)
31
+ }
32
+
33
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
34
+ super.onViewCreated(view, savedInstanceState)
35
+ val rvColor: RecyclerView = view.findViewById(R.id.rvColors)
36
+ val sbOpacity = view.findViewById<SeekBar>(R.id.sbOpacity)
37
+ val sbBrushSize = view.findViewById<SeekBar>(R.id.sbSize)
38
+
39
+ view.findViewById<TextView>(R.id.txtOpacity).setEditorText(R.string.pe_label_opacity)
40
+ view.findViewById<TextView>(R.id.txtBrushSize).setEditorText(R.string.pe_label_brush)
41
+ sbOpacity.setOnSeekBarChangeListener(this)
42
+ sbBrushSize.setOnSeekBarChangeListener(this)
43
+ val layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false)
44
+ rvColor.layoutManager = layoutManager
45
+ rvColor.setHasFixedSize(true)
46
+ val colorPickerAdapter = activity?.let { ColorPickerAdapter(it) }
47
+ colorPickerAdapter?.setOnColorPickerClickListener(object : OnColorPickerClickListener {
48
+ override fun onColorPickerClickListener(colorCode: Int) {
49
+ if (mProperties != null) {
50
+ dismiss()
51
+ mProperties?.onColorChanged(colorCode)
52
+ }
53
+ }
54
+ })
55
+ rvColor.adapter = colorPickerAdapter
56
+ }
57
+
58
+ fun setPropertiesChangeListener(properties: Properties?) {
59
+ mProperties = properties
60
+ }
61
+
62
+ override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
63
+ when (seekBar.id) {
64
+ R.id.sbOpacity -> if (mProperties != null) {
65
+ mProperties?.onOpacityChanged(i)
66
+ }
67
+ R.id.sbSize -> if (mProperties != null) {
68
+ mProperties?.onShapeSizeChanged(i)
69
+ }
70
+ }
71
+ }
72
+
73
+ override fun onStartTrackingTouch(seekBar: SeekBar) = Unit
74
+
75
+ override fun onStopTrackingTouch(seekBar: SeekBar) = Unit
76
+ }